summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCtibor Brančík <ctibor@brancik.cz>2016-03-20 19:27:01 +0100
committerCtibor Brančík <ctibor@brancik.cz>2016-03-20 19:27:01 +0100
commit29a6913890a675ddf1a9239b4407f105e02dc95d (patch)
treedd9ba21b73e9e704952b49d5153616a9dfa9b98f
parent5ddacae6306ce071d4f7e4d438960d6d3a4c6bd8 (diff)
downloadbrdo-29a6913890a675ddf1a9239b4407f105e02dc95d.tar.gz
brdo-29a6913890a675ddf1a9239b4407f105e02dc95d.tar.bz2
Added drupal modules for site
-rw-r--r--sites/all/modules/advanced_help/LICENSE.txt339
-rw-r--r--sites/all/modules/advanced_help/advanced-help-popup.tpl.php44
-rw-r--r--sites/all/modules/advanced_help/advanced_help.info18
-rw-r--r--sites/all/modules/advanced_help/advanced_help.install61
-rwxr-xr-xsites/all/modules/advanced_help/advanced_help.module1173
-rw-r--r--sites/all/modules/advanced_help/help-icon.css18
-rw-r--r--sites/all/modules/advanced_help/help-popup.css125
-rw-r--r--sites/all/modules/advanced_help/help.css113
-rw-r--r--sites/all/modules/advanced_help/help.pngbin0 -> 1025 bytes
-rw-r--r--sites/all/modules/advanced_help/help/.htaccess4
-rw-r--r--sites/all/modules/advanced_help/help/advanced_help.help.ini18
-rwxr-xr-xsites/all/modules/advanced_help/help/ahelp_tab.pngbin0 -> 11058 bytes
-rw-r--r--sites/all/modules/advanced_help/help/click_icon.pngbin0 -> 4933 bytes
-rw-r--r--sites/all/modules/advanced_help/help/ini-file.html117
-rw-r--r--sites/all/modules/advanced_help/help/readme.html107
-rw-r--r--sites/all/modules/advanced_help/help/translation.html44
-rw-r--r--sites/all/modules/advanced_help/help/using-advanced-help.html152
-rw-r--r--sites/all/modules/advanced_help/help/why-advanced-help.html44
-rw-r--r--sites/all/modules/advanced_help/help_example/help/.htaccess4
-rw-r--r--sites/all/modules/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpgbin0 -> 11007 bytes
-rw-r--r--sites/all/modules/advanced_help/help_example/help/180px-Lerdorf.jpgbin0 -> 14535 bytes
-rw-r--r--sites/all/modules/advanced_help/help_example/help/about-php.html54
-rw-r--r--sites/all/modules/advanced_help/help_example/help/help_example.help.ini19
-rw-r--r--sites/all/modules/advanced_help/help_example/help/history.html21
-rw-r--r--sites/all/modules/advanced_help/help_example/help/security.html4
-rw-r--r--sites/all/modules/advanced_help/help_example/help/syntax.html34
-rw-r--r--sites/all/modules/advanced_help/help_example/help/usage.html11
-rw-r--r--sites/all/modules/advanced_help/help_example/help_example.info40
-rw-r--r--sites/all/modules/advanced_help/help_example/help_example.module31
-rw-r--r--sites/all/modules/advanced_help/translations/help/nb/advanced_help.help.ini14
-rw-r--r--sites/all/modules/advanced_help/translations/help/nb/click_icon.pngbin0 -> 5092 bytes
-rw-r--r--sites/all/modules/advanced_help/translations/help/nb/ini-file.html117
-rw-r--r--sites/all/modules/advanced_help/translations/help/nb/readme.html116
-rw-r--r--sites/all/modules/advanced_help/translations/help/nb/translation.html51
-rw-r--r--sites/all/modules/advanced_help/translations/help/nb/using-advanced-help.html165
-rw-r--r--sites/all/modules/advanced_help/translations/help/nb/why-advanced-help.html44
-rw-r--r--sites/all/modules/ckeditor_link/LICENSE.txt339
-rw-r--r--sites/all/modules/ckeditor_link/README.txt55
-rw-r--r--sites/all/modules/ckeditor_link/ckeditor_link.admin.inc42
-rw-r--r--sites/all/modules/ckeditor_link/ckeditor_link.api.php126
-rw-r--r--sites/all/modules/ckeditor_link/ckeditor_link.css23
-rw-r--r--sites/all/modules/ckeditor_link/ckeditor_link.info12
-rw-r--r--sites/all/modules/ckeditor_link/ckeditor_link.install49
-rw-r--r--sites/all/modules/ckeditor_link/ckeditor_link.module345
-rw-r--r--sites/all/modules/ckeditor_link/includes/ckeditor_link.i18n_menu.inc79
-rw-r--r--sites/all/modules/ckeditor_link/includes/ckeditor_link.i18n_taxonomy.inc70
-rw-r--r--sites/all/modules/ckeditor_link/includes/ckeditor_link.menu.inc84
-rw-r--r--sites/all/modules/ckeditor_link/includes/ckeditor_link.node.inc96
-rw-r--r--sites/all/modules/ckeditor_link/includes/ckeditor_link.taxonomy.inc97
-rw-r--r--sites/all/modules/ckeditor_link/plugins/link/plugin.js213
-rw-r--r--sites/all/modules/ckeditor_link_file/LICENSE.txt339
-rw-r--r--sites/all/modules/ckeditor_link_file/ckeditor_link_file.info14
-rw-r--r--sites/all/modules/ckeditor_link_file/ckeditor_link_file.install14
-rw-r--r--sites/all/modules/ckeditor_link_file/ckeditor_link_file.module21
-rw-r--r--sites/all/modules/ckeditor_link_file/includes/ckeditor_link_file.file.inc119
-rw-r--r--sites/all/modules/ckeditor_link_file/includes/ckeditor_link_file.usage.inc190
-rw-r--r--sites/all/modules/ckeditor_link_file/readme.txt67
-rw-r--r--sites/all/modules/colorbox/LICENSE.txt339
-rw-r--r--sites/all/modules/colorbox/README.txt157
-rw-r--r--sites/all/modules/colorbox/colorbox-insert-image.tpl.php30
-rw-r--r--sites/all/modules/colorbox/colorbox.admin.inc313
-rw-r--r--sites/all/modules/colorbox/colorbox.api.php44
-rw-r--r--sites/all/modules/colorbox/colorbox.info14
-rw-r--r--sites/all/modules/colorbox/colorbox.install120
-rw-r--r--sites/all/modules/colorbox/colorbox.make8
-rw-r--r--sites/all/modules/colorbox/colorbox.module525
-rw-r--r--sites/all/modules/colorbox/colorbox.theme.inc240
-rw-r--r--sites/all/modules/colorbox/colorbox.variable.inc44
-rw-r--r--sites/all/modules/colorbox/drush/colorbox.drush.inc128
-rw-r--r--sites/all/modules/colorbox/images/controls.pngbin0 -> 2104 bytes
-rw-r--r--sites/all/modules/colorbox/images/loading_animation.gifbin0 -> 2767 bytes
-rw-r--r--sites/all/modules/colorbox/images/loading_background.pngbin0 -> 166 bytes
-rw-r--r--sites/all/modules/colorbox/js/colorbox.js27
-rw-r--r--sites/all/modules/colorbox/js/colorbox_admin_settings.js32
-rw-r--r--sites/all/modules/colorbox/js/colorbox_inline.js56
-rw-r--r--sites/all/modules/colorbox/js/colorbox_load.js42
-rw-r--r--sites/all/modules/colorbox/styles/default/colorbox_style.css216
-rw-r--r--sites/all/modules/colorbox/styles/default/colorbox_style.js22
-rw-r--r--sites/all/modules/colorbox/styles/default/images/controls.pngbin0 -> 2104 bytes
-rw-r--r--sites/all/modules/colorbox/styles/default/images/loading_animation.gifbin0 -> 2767 bytes
-rw-r--r--sites/all/modules/colorbox/styles/default/images/loading_background.pngbin0 -> 166 bytes
-rw-r--r--sites/all/modules/colorbox/styles/plain/colorbox_style.css144
-rw-r--r--sites/all/modules/colorbox/styles/plain/colorbox_style.js33
-rw-r--r--sites/all/modules/colorbox/styles/plain/images/controls.pngbin0 -> 2104 bytes
-rw-r--r--sites/all/modules/colorbox/styles/plain/images/loading_animation.gifbin0 -> 2767 bytes
-rw-r--r--sites/all/modules/colorbox/styles/plain/images/loading_background.pngbin0 -> 166 bytes
-rw-r--r--sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_stockholmsyndrome_screen.pngbin0 -> 123870 bytes
-rw-r--r--sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_style.css219
-rw-r--r--sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_style.js20
-rw-r--r--sites/all/modules/colorbox/styles/stockholmsyndrome/images/bg_tab.pngbin0 -> 210 bytes
-rw-r--r--sites/all/modules/colorbox/styles/stockholmsyndrome/images/controls.pngbin0 -> 1909 bytes
-rw-r--r--sites/all/modules/colorbox/styles/stockholmsyndrome/images/loading_animation.gifbin0 -> 2767 bytes
-rw-r--r--sites/all/modules/colorbox/views/colorbox.views.inc28
-rw-r--r--sites/all/modules/colorbox/views/colorbox_handler_field_colorbox.inc205
-rw-r--r--sites/all/modules/ctools/API.txt54
-rw-r--r--sites/all/modules/ctools/CHANGELOG.txt82
-rw-r--r--sites/all/modules/ctools/LICENSE.txt339
-rw-r--r--sites/all/modules/ctools/UPGRADE.txt63
-rw-r--r--sites/all/modules/ctools/bulk_export/bulk_export.css18
-rw-r--r--sites/all/modules/ctools/bulk_export/bulk_export.info14
-rw-r--r--sites/all/modules/ctools/bulk_export/bulk_export.js29
-rw-r--r--sites/all/modules/ctools/bulk_export/bulk_export.module279
-rw-r--r--sites/all/modules/ctools/css/button.css31
-rw-r--r--sites/all/modules/ctools/css/collapsible-div.css26
-rw-r--r--sites/all/modules/ctools/css/context.css10
-rw-r--r--sites/all/modules/ctools/css/ctools.css25
-rw-r--r--sites/all/modules/ctools/css/dropbutton.css66
-rw-r--r--sites/all/modules/ctools/css/dropdown.css73
-rw-r--r--sites/all/modules/ctools/css/export-ui-list.css45
-rw-r--r--sites/all/modules/ctools/css/modal.css130
-rw-r--r--sites/all/modules/ctools/css/ruleset.css11
-rw-r--r--sites/all/modules/ctools/css/stylizer.css129
-rw-r--r--sites/all/modules/ctools/css/wizard.css8
-rw-r--r--sites/all/modules/ctools/ctools.api.php268
-rw-r--r--sites/all/modules/ctools/ctools.info17
-rw-r--r--sites/all/modules/ctools/ctools.install265
-rw-r--r--sites/all/modules/ctools/ctools.module1088
-rw-r--r--sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info13
-rw-r--r--sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.install82
-rw-r--r--sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.module85
-rw-r--r--sites/all/modules/ctools/ctools_access_ruleset/plugins/access/ruleset.inc109
-rw-r--r--sites/all/modules/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc29
-rw-r--r--sites/all/modules/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php53
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css134
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info13
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.install19
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.module756
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/images/ajax-loader.gifbin0 -> 10819 bytes
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/images/loading-large.gifbin0 -> 2545 bytes
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/images/loading.gifbin0 -> 1849 bytes
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/images/popups-border.pngbin0 -> 380 bytes
-rw-r--r--sites/all/modules/ctools/ctools_ajax_sample/js/ctools-ajax-sample.js42
-rw-r--r--sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info13
-rw-r--r--sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.install67
-rw-r--r--sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.module118
-rw-r--r--sites/all/modules/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc20
-rw-r--r--sites/all/modules/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php129
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/README.txt14
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info16
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.module94
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc451
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/help/Access-Plugins--Determining-access-and-visibility.html17
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/help/Argument-Plugins--Starting-at-the-beginning.html20
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/help/Chaos-Tools--CTools--Plugin-Examples.html19
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/help/Content-Type-Plugins--Displaying-content-using-a-context.html17
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/help/Context-plugins--Creating-a--context--from-an-argument.html21
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/help/Module-setup-and-hooks.html20
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/help/Relationships--Letting-one-context-take-us-to-another.html18
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/help/ctools_plugin_example.help.ini42
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/access/arg_length.inc65
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/access/example_role.inc76
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc52
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/icon_example.pngbin0 -> 566 bytes
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc116
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc103
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc129
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc83
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc134
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/panels.pages.inc214
-rw-r--r--sites/all/modules/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc50
-rw-r--r--sites/all/modules/ctools/drush/ctools.drush.inc1017
-rw-r--r--sites/all/modules/ctools/help/about.html29
-rw-r--r--sites/all/modules/ctools/help/ajax.html0
-rw-r--r--sites/all/modules/ctools/help/collapsible-div.html1
-rw-r--r--sites/all/modules/ctools/help/context-access.html12
-rw-r--r--sites/all/modules/ctools/help/context-arguments.html14
-rw-r--r--sites/all/modules/ctools/help/context-content.html157
-rw-r--r--sites/all/modules/ctools/help/context-context.html13
-rw-r--r--sites/all/modules/ctools/help/context-relationships.html13
-rw-r--r--sites/all/modules/ctools/help/context.html0
-rw-r--r--sites/all/modules/ctools/help/css.html1
-rw-r--r--sites/all/modules/ctools/help/ctools.help.ini97
-rw-r--r--sites/all/modules/ctools/help/dependent.html1
-rw-r--r--sites/all/modules/ctools/help/dropbutton.html1
-rw-r--r--sites/all/modules/ctools/help/dropdown.html1
-rw-r--r--sites/all/modules/ctools/help/export-ui.html85
-rw-r--r--sites/all/modules/ctools/help/export.html294
-rw-r--r--sites/all/modules/ctools/help/form.html1
-rw-r--r--sites/all/modules/ctools/help/modal.html215
-rw-r--r--sites/all/modules/ctools/help/object-cache.html132
-rw-r--r--sites/all/modules/ctools/help/plugins-api.html55
-rw-r--r--sites/all/modules/ctools/help/plugins-creating.html203
-rw-r--r--sites/all/modules/ctools/help/plugins-implementing.html62
-rw-r--r--sites/all/modules/ctools/help/plugins.html5
-rw-r--r--sites/all/modules/ctools/help/wizard.html311
-rw-r--r--sites/all/modules/ctools/images/arrow-active.pngbin0 -> 313 bytes
-rw-r--r--sites/all/modules/ctools/images/collapsible-collapsed.pngbin0 -> 108 bytes
-rw-r--r--sites/all/modules/ctools/images/collapsible-expanded.pngbin0 -> 106 bytes
-rw-r--r--sites/all/modules/ctools/images/expanded-options.pngbin0 -> 228 bytes
-rw-r--r--sites/all/modules/ctools/images/icon-close-window.pngbin0 -> 877 bytes
-rw-r--r--sites/all/modules/ctools/images/icon-configure.pngbin0 -> 765 bytes
-rw-r--r--sites/all/modules/ctools/images/icon-delete.pngbin0 -> 877 bytes
-rw-r--r--sites/all/modules/ctools/images/no-icon.pngbin0 -> 574 bytes
-rw-r--r--sites/all/modules/ctools/images/status-active.gifbin0 -> 2196 bytes
-rw-r--r--sites/all/modules/ctools/images/throbber.gifbin0 -> 3208 bytes
-rw-r--r--sites/all/modules/ctools/includes/action-links.theme.inc33
-rw-r--r--sites/all/modules/ctools/includes/ajax.inc157
-rw-r--r--sites/all/modules/ctools/includes/cache.inc169
-rw-r--r--sites/all/modules/ctools/includes/cache.plugin-type.inc11
-rw-r--r--sites/all/modules/ctools/includes/cleanstring.inc204
-rw-r--r--sites/all/modules/ctools/includes/collapsible.theme.inc79
-rw-r--r--sites/all/modules/ctools/includes/content.inc853
-rw-r--r--sites/all/modules/ctools/includes/content.menu.inc179
-rw-r--r--sites/all/modules/ctools/includes/content.plugin-type.inc17
-rw-r--r--sites/all/modules/ctools/includes/content.theme.inc21
-rw-r--r--sites/all/modules/ctools/includes/context-access-admin.inc486
-rw-r--r--sites/all/modules/ctools/includes/context-admin.inc849
-rw-r--r--sites/all/modules/ctools/includes/context-task-handler.inc540
-rw-r--r--sites/all/modules/ctools/includes/context.inc1602
-rw-r--r--sites/all/modules/ctools/includes/context.menu.inc40
-rw-r--r--sites/all/modules/ctools/includes/context.plugin-type.inc24
-rw-r--r--sites/all/modules/ctools/includes/context.theme.inc344
-rw-r--r--sites/all/modules/ctools/includes/css-cache.inc52
-rw-r--r--sites/all/modules/ctools/includes/css.inc575
-rw-r--r--sites/all/modules/ctools/includes/dependent.inc181
-rw-r--r--sites/all/modules/ctools/includes/dropbutton.theme.inc143
-rw-r--r--sites/all/modules/ctools/includes/dropdown.theme.inc90
-rw-r--r--sites/all/modules/ctools/includes/entity-access.inc150
-rw-r--r--sites/all/modules/ctools/includes/export-ui.inc475
-rw-r--r--sites/all/modules/ctools/includes/export-ui.menu.inc24
-rw-r--r--sites/all/modules/ctools/includes/export-ui.plugin-type.inc20
-rw-r--r--sites/all/modules/ctools/includes/export.inc1267
-rw-r--r--sites/all/modules/ctools/includes/fields.inc357
-rw-r--r--sites/all/modules/ctools/includes/jump-menu.inc150
-rw-r--r--sites/all/modules/ctools/includes/language.inc44
-rw-r--r--sites/all/modules/ctools/includes/math-expr.inc388
-rw-r--r--sites/all/modules/ctools/includes/menu.inc98
-rw-r--r--sites/all/modules/ctools/includes/modal.inc262
-rw-r--r--sites/all/modules/ctools/includes/object-cache.cron.inc16
-rw-r--r--sites/all/modules/ctools/includes/object-cache.inc205
-rw-r--r--sites/all/modules/ctools/includes/page-wizard.inc194
-rw-r--r--sites/all/modules/ctools/includes/page-wizard.menu.inc32
-rw-r--r--sites/all/modules/ctools/includes/plugins-admin.inc208
-rw-r--r--sites/all/modules/ctools/includes/plugins.inc917
-rw-r--r--sites/all/modules/ctools/includes/registry.inc77
-rw-r--r--sites/all/modules/ctools/includes/stylizer.inc1654
-rw-r--r--sites/all/modules/ctools/includes/stylizer.theme.inc28
-rw-r--r--sites/all/modules/ctools/includes/utility.inc31
-rw-r--r--sites/all/modules/ctools/includes/uuid.inc68
-rw-r--r--sites/all/modules/ctools/includes/views.inc26
-rw-r--r--sites/all/modules/ctools/includes/wizard.inc534
-rw-r--r--sites/all/modules/ctools/includes/wizard.theme.inc22
-rw-r--r--sites/all/modules/ctools/js/ajax-responder.js126
-rw-r--r--sites/all/modules/ctools/js/auto-submit.js100
-rw-r--r--sites/all/modules/ctools/js/collapsible-div.js241
-rw-r--r--sites/all/modules/ctools/js/dependent.js231
-rw-r--r--sites/all/modules/ctools/js/dropbutton.js94
-rw-r--r--sites/all/modules/ctools/js/dropdown.js87
-rw-r--r--sites/all/modules/ctools/js/jump-menu.js42
-rw-r--r--sites/all/modules/ctools/js/modal.js696
-rw-r--r--sites/all/modules/ctools/js/states-show.js43
-rw-r--r--sites/all/modules/ctools/js/stylizer.js220
-rw-r--r--sites/all/modules/ctools/page_manager/css/page-manager.css372
-rw-r--r--sites/all/modules/ctools/page_manager/help/about.html11
-rw-r--r--sites/all/modules/ctools/page_manager/help/api-task-handler.html43
-rw-r--r--sites/all/modules/ctools/page_manager/help/api-task-type.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/api-task.html38
-rw-r--r--sites/all/modules/ctools/page_manager/help/custom-pages-access.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/custom-pages-arguments.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/custom-pages-menu.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/custom-pages.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/getting-started-create.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/getting-started-custom-nodes.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/getting-started-custom-vocabulary.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/getting-started-members.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/getting-started-page-list.html2
-rw-r--r--sites/all/modules/ctools/page_manager/help/getting-started.html15
-rw-r--r--sites/all/modules/ctools/page_manager/help/page-task-type.html4
-rw-r--r--sites/all/modules/ctools/page_manager/help/page_manager.help.ini59
-rw-r--r--sites/all/modules/ctools/page_manager/help/variants.html2
-rw-r--r--sites/all/modules/ctools/page_manager/images/arrow-active.pngbin0 -> 313 bytes
-rw-r--r--sites/all/modules/ctools/page_manager/images/locked-other.pngbin0 -> 262 bytes
-rw-r--r--sites/all/modules/ctools/page_manager/images/locked.pngbin0 -> 273 bytes
-rw-r--r--sites/all/modules/ctools/page_manager/js/page-list.js44
-rw-r--r--sites/all/modules/ctools/page_manager/page_manager.admin.inc1904
-rw-r--r--sites/all/modules/ctools/page_manager/page_manager.api.php39
-rw-r--r--sites/all/modules/ctools/page_manager/page_manager.info13
-rw-r--r--sites/all/modules/ctools/page_manager/page_manager.install204
-rw-r--r--sites/all/modules/ctools/page_manager/page_manager.module1349
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/cache/page_manager_context.inc70
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/task_handlers/http_response.inc332
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/blog.inc121
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/blog_user.inc152
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/comment_reply.inc162
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/contact_site.inc129
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/contact_user.inc155
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc185
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/node_view.inc166
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/page.admin.inc1521
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/page.inc787
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/poll.inc121
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/search.inc249
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc377
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/user_edit.inc187
-rw-r--r--sites/all/modules/ctools/page_manager/plugins/tasks/user_view.inc161
-rw-r--r--sites/all/modules/ctools/page_manager/theme/page-manager-edit-page.tpl.php53
-rw-r--r--sites/all/modules/ctools/page_manager/theme/page_manager.theme.inc118
-rw-r--r--sites/all/modules/ctools/plugins/access/book.inc94
-rw-r--r--sites/all/modules/ctools/plugins/access/compare_users.inc70
-rw-r--r--sites/all/modules/ctools/plugins/access/context_exists.inc51
-rw-r--r--sites/all/modules/ctools/plugins/access/entity_bundle.inc136
-rw-r--r--sites/all/modules/ctools/plugins/access/entity_field_value.inc410
-rw-r--r--sites/all/modules/ctools/plugins/access/front.inc46
-rw-r--r--sites/all/modules/ctools/plugins/access/node.inc0
-rw-r--r--sites/all/modules/ctools/plugins/access/node_access.inc89
-rw-r--r--sites/all/modules/ctools/plugins/access/node_comment.inc31
-rw-r--r--sites/all/modules/ctools/plugins/access/node_language.inc114
-rw-r--r--sites/all/modules/ctools/plugins/access/node_status.inc33
-rw-r--r--sites/all/modules/ctools/plugins/access/node_type.inc117
-rw-r--r--sites/all/modules/ctools/plugins/access/path_visibility.inc88
-rw-r--r--sites/all/modules/ctools/plugins/access/perm.inc73
-rw-r--r--sites/all/modules/ctools/plugins/access/php.inc64
-rw-r--r--sites/all/modules/ctools/plugins/access/role.inc79
-rw-r--r--sites/all/modules/ctools/plugins/access/site_language.inc87
-rw-r--r--sites/all/modules/ctools/plugins/access/string_equal.inc94
-rw-r--r--sites/all/modules/ctools/plugins/access/string_length.inc80
-rw-r--r--sites/all/modules/ctools/plugins/access/term.inc129
-rw-r--r--sites/all/modules/ctools/plugins/access/term_has_parent.inc172
-rw-r--r--sites/all/modules/ctools/plugins/access/term_parent.inc86
-rw-r--r--sites/all/modules/ctools/plugins/access/term_vocabulary.inc127
-rw-r--r--sites/all/modules/ctools/plugins/access/theme.inc70
-rw-r--r--sites/all/modules/ctools/plugins/arguments/entity_id.inc147
-rw-r--r--sites/all/modules/ctools/plugins/arguments/nid.inc50
-rw-r--r--sites/all/modules/ctools/plugins/arguments/node_add.inc32
-rw-r--r--sites/all/modules/ctools/plugins/arguments/node_edit.inc51
-rw-r--r--sites/all/modules/ctools/plugins/arguments/rid.inc50
-rw-r--r--sites/all/modules/ctools/plugins/arguments/string.inc64
-rw-r--r--sites/all/modules/ctools/plugins/arguments/term.inc163
-rw-r--r--sites/all/modules/ctools/plugins/arguments/terms.inc77
-rw-r--r--sites/all/modules/ctools/plugins/arguments/uid.inc53
-rw-r--r--sites/all/modules/ctools/plugins/arguments/user_edit.inc47
-rw-r--r--sites/all/modules/ctools/plugins/arguments/user_name.inc47
-rw-r--r--sites/all/modules/ctools/plugins/arguments/vid.inc46
-rw-r--r--sites/all/modules/ctools/plugins/cache/export_ui.inc39
-rw-r--r--sites/all/modules/ctools/plugins/cache/simple.inc51
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/block.inc565
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_contrib_block.pngbin0 -> 574 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_contrib_block_empty.pngbin0 -> 450 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_contrib_menu.pngbin0 -> 552 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_contrib_page.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_activeforumtopics.pngbin0 -> 603 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_authorinformation.pngbin0 -> 606 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_block.pngbin0 -> 568 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_block_empty.pngbin0 -> 450 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_block_menu.pngbin0 -> 552 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_booknavigation.pngbin0 -> 626 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_languageswitcher.pngbin0 -> 601 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_navigation.pngbin0 -> 818 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_newforumtopics.pngbin0 -> 604 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_page.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_popularcontent.pngbin0 -> 604 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_primarylinks.pngbin0 -> 892 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_recentblogposts.pngbin0 -> 681 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_recentcomments.pngbin0 -> 662 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_recentpoll.pngbin0 -> 608 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_searchform.pngbin0 -> 717 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_syndicate.pngbin0 -> 803 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_userlogin.pngbin0 -> 601 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_whosnew.pngbin0 -> 732 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/block/icon_core_whosonline.pngbin0 -> 744 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/comment/comment_created.inc74
-rw-r--r--sites/all/modules/ctools/plugins/content_types/comment/comment_links.inc80
-rw-r--r--sites/all/modules/ctools/plugins/content_types/comment/comment_reply_form.inc50
-rw-r--r--sites/all/modules/ctools/plugins/content_types/contact/contact.inc60
-rw-r--r--sites/all/modules/ctools/plugins/content_types/contact/icon_contact.pngbin0 -> 606 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/contact/user_contact.inc66
-rw-r--r--sites/all/modules/ctools/plugins/content_types/custom/custom.inc434
-rw-r--r--sites/all/modules/ctools/plugins/content_types/custom/icon_block_custom.pngbin0 -> 522 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/entity_context/entity_field.inc281
-rw-r--r--sites/all/modules/ctools/plugins/content_types/entity_context/entity_field_extra.inc133
-rw-r--r--sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc167
-rw-r--r--sites/all/modules/ctools/plugins/content_types/form/form.inc62
-rw-r--r--sites/all/modules/ctools/plugins/content_types/form/icon_form.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node/icon_node.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node/node.inc251
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/icon_node.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_attachments.inc44
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_author.inc71
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_body.inc40
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_book_children.inc43
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_book_nav.inc43
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_comment_form.inc79
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_comment_wrapper.inc117
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_comments.inc98
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_content.inc204
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_created.inc74
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_links.inc105
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_terms.inc205
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_title.inc120
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_type_desc.inc47
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_context/node_updated.inc75
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/icon_node_form.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_attachments.inc51
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_author.inc52
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_book.inc50
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_buttons.inc43
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_comment.inc50
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_language.inc41
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_log.inc47
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_menu.inc50
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_path.inc51
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_publishing.inc54
-rw-r--r--sites/all/modules/ctools/plugins/content_types/node_form/node_form_title.inc41
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_actions.inc32
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_breadcrumb.inc32
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_feed_icons.inc32
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_help.inc33
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_logo.inc37
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_messages.inc33
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_primary_links.inc40
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_secondary_links.inc40
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_site_name.inc68
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_slogan.inc32
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_tabs.inc89
-rw-r--r--sites/all/modules/ctools/plugins/content_types/page/page_title.inc121
-rw-r--r--sites/all/modules/ctools/plugins/content_types/search/icon_search.pngbin0 -> 717 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/search/search_form.inc156
-rw-r--r--sites/all/modules/ctools/plugins/content_types/search/search_result.inc204
-rw-r--r--sites/all/modules/ctools/plugins/content_types/term_context/icon_term.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/term_context/term_description.inc51
-rw-r--r--sites/all/modules/ctools/plugins/content_types/term_context/term_list.inc172
-rw-r--r--sites/all/modules/ctools/plugins/content_types/term_context/term_name.inc121
-rw-r--r--sites/all/modules/ctools/plugins/content_types/token/icon_token.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/token/token.inc122
-rw-r--r--sites/all/modules/ctools/plugins/content_types/user_context/icon_user.pngbin0 -> 606 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/user_context/profile_fields.inc129
-rw-r--r--sites/all/modules/ctools/plugins/content_types/user_context/profile_fields_pane.tpl.php16
-rw-r--r--sites/all/modules/ctools/plugins/content_types/user_context/user_links.inc84
-rw-r--r--sites/all/modules/ctools/plugins/content_types/user_context/user_picture.inc54
-rw-r--r--sites/all/modules/ctools/plugins/content_types/user_context/user_profile.inc87
-rw-r--r--sites/all/modules/ctools/plugins/content_types/user_context/user_signature.inc43
-rw-r--r--sites/all/modules/ctools/plugins/content_types/vocabulary_context/icon_vocabulary.pngbin0 -> 460 bytes
-rw-r--r--sites/all/modules/ctools/plugins/content_types/vocabulary_context/vocabulary_terms.inc100
-rw-r--r--sites/all/modules/ctools/plugins/contexts/entity.inc273
-rw-r--r--sites/all/modules/ctools/plugins/contexts/node.inc181
-rw-r--r--sites/all/modules/ctools/plugins/contexts/node_add_form.inc124
-rw-r--r--sites/all/modules/ctools/plugins/contexts/node_edit_form.inc192
-rw-r--r--sites/all/modules/ctools/plugins/contexts/string.inc90
-rw-r--r--sites/all/modules/ctools/plugins/contexts/term.inc166
-rw-r--r--sites/all/modules/ctools/plugins/contexts/terms.inc98
-rw-r--r--sites/all/modules/ctools/plugins/contexts/token.inc62
-rw-r--r--sites/all/modules/ctools/plugins/contexts/user.inc176
-rw-r--r--sites/all/modules/ctools/plugins/contexts/user_edit_form.inc192
-rw-r--r--sites/all/modules/ctools/plugins/contexts/vocabulary.inc72
-rw-r--r--sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.class.php1537
-rw-r--r--sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.inc24
-rw-r--r--sites/all/modules/ctools/plugins/relationships/book_parent.inc68
-rw-r--r--sites/all/modules/ctools/plugins/relationships/entity_from_field.inc229
-rw-r--r--sites/all/modules/ctools/plugins/relationships/entity_from_schema.inc136
-rw-r--r--sites/all/modules/ctools/plugins/relationships/node_edit_form_from_node.inc31
-rw-r--r--sites/all/modules/ctools/plugins/relationships/term_from_node.inc60
-rw-r--r--sites/all/modules/ctools/plugins/relationships/term_parent.inc68
-rw-r--r--sites/all/modules/ctools/plugins/relationships/terms_from_node.inc83
-rw-r--r--sites/all/modules/ctools/plugins/relationships/user_category_edit_form_from_user.inc31
-rw-r--r--sites/all/modules/ctools/plugins/relationships/user_from_node.inc38
-rw-r--r--sites/all/modules/ctools/stylizer/plugins/export_ui/stylizer.inc45
-rw-r--r--sites/all/modules/ctools/stylizer/plugins/export_ui/stylizer_ui.class.php272
-rw-r--r--sites/all/modules/ctools/stylizer/stylizer.info14
-rw-r--r--sites/all/modules/ctools/stylizer/stylizer.install70
-rw-r--r--sites/all/modules/ctools/stylizer/stylizer.module135
-rw-r--r--sites/all/modules/ctools/term_depth/plugins/access/term_depth.inc128
-rw-r--r--sites/all/modules/ctools/term_depth/term_depth.info13
-rw-r--r--sites/all/modules/ctools/term_depth/term_depth.module7
-rw-r--r--sites/all/modules/ctools/tests/context.test62
-rw-r--r--sites/all/modules/ctools/tests/css.test81
-rw-r--r--sites/all/modules/ctools/tests/css_cache.test48
-rwxr-xr-xsites/all/modules/ctools/tests/ctools.drush.sh119
-rw-r--r--sites/all/modules/ctools/tests/ctools.plugins.test100
-rw-r--r--sites/all/modules/ctools/tests/ctools_export_test/ctools_export.test215
-rw-r--r--sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.default_ctools_export_tests.inc31
-rw-r--r--sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info16
-rw-r--r--sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.install86
-rw-r--r--sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.module10
-rw-r--r--sites/all/modules/ctools/tests/ctools_plugin_test.info20
-rw-r--r--sites/all/modules/ctools/tests/ctools_plugin_test.module72
-rw-r--r--sites/all/modules/ctools/tests/math_expression.test129
-rw-r--r--sites/all/modules/ctools/tests/math_expression_stack.test63
-rw-r--r--sites/all/modules/ctools/tests/object_cache.test46
-rw-r--r--sites/all/modules/ctools/tests/plugins/cached/ctoolsCachedPluginArray.class.php7
-rw-r--r--sites/all/modules/ctools/tests/plugins/cached/ctoolsCachedPluginArray2.class.php7
-rw-r--r--sites/all/modules/ctools/tests/plugins/cached/plugin_array.inc20
-rw-r--r--sites/all/modules/ctools/tests/plugins/cached/plugin_array2.inc20
-rw-r--r--sites/all/modules/ctools/tests/plugins/cached/plugin_array_dne.inc15
-rw-r--r--sites/all/modules/ctools/tests/plugins/not_cached/ctoolsNotCachedPluginArray.class.php7
-rw-r--r--sites/all/modules/ctools/tests/plugins/not_cached/ctoolsNotCachedPluginArray2.class.php7
-rw-r--r--sites/all/modules/ctools/tests/plugins/not_cached/plugin_array.inc20
-rw-r--r--sites/all/modules/ctools/tests/plugins/not_cached/plugin_array2.inc20
-rw-r--r--sites/all/modules/ctools/tests/plugins/not_cached/plugin_array_dne.inc15
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/icon_views_block_legacy.pngbin0 -> 599 bytes
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/icon_views_page.pngbin0 -> 716 bytes
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/icon_views_page_legacy.pngbin0 -> 587 bytes
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views.inc556
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_attachments.inc75
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_empty.inc53
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_exposed.inc51
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_feed.inc51
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_footer.inc51
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_header.inc51
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_pager.inc52
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc634
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_row.inc237
-rw-r--r--sites/all/modules/ctools/views_content/plugins/content_types/views_view.inc52
-rw-r--r--sites/all/modules/ctools/views_content/plugins/contexts/view.inc174
-rw-r--r--sites/all/modules/ctools/views_content/plugins/relationships/node_from_view.inc64
-rw-r--r--sites/all/modules/ctools/views_content/plugins/relationships/term_from_view.inc64
-rw-r--r--sites/all/modules/ctools/views_content/plugins/relationships/user_from_view.inc64
-rw-r--r--sites/all/modules/ctools/views_content/plugins/relationships/view_from_argument.inc100
-rw-r--r--sites/all/modules/ctools/views_content/plugins/views/views_content.views.inc65
-rw-r--r--sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_ctools_context.inc272
-rw-r--r--sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc416
-rw-r--r--sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_style_ctools_context.inc53
-rw-r--r--sites/all/modules/ctools/views_content/views_content.admin.inc0
-rw-r--r--sites/all/modules/ctools/views_content/views_content.info18
-rw-r--r--sites/all/modules/ctools/views_content/views_content.module297
-rw-r--r--sites/all/modules/ds/LICENSE.txt339
-rw-r--r--sites/all/modules/ds/README.txt57
-rw-r--r--sites/all/modules/ds/css/ds.admin.css193
-rw-r--r--sites/all/modules/ds/drush/ds.drush.inc218
-rw-r--r--sites/all/modules/ds/drush/example_layout/README.txt2
-rw-r--r--sites/all/modules/ds/drush/example_layout/example-layout.tpl.php49
-rw-r--r--sites/all/modules/ds/drush/example_layout/example_layout-rtl.css12
-rw-r--r--sites/all/modules/ds/drush/example_layout/example_layout.css14
-rw-r--r--sites/all/modules/ds/drush/example_layout/example_layout.inc20
-rw-r--r--sites/all/modules/ds/drush/example_layout/example_layout.pngbin0 -> 760 bytes
-rw-r--r--sites/all/modules/ds/ds.api.php621
-rw-r--r--sites/all/modules/ds/ds.ds_fields_info.inc418
-rw-r--r--sites/all/modules/ds/ds.info21
-rw-r--r--sites/all/modules/ds/ds.install327
-rw-r--r--sites/all/modules/ds/ds.module1366
-rw-r--r--sites/all/modules/ds/ds.views.inc45
-rw-r--r--sites/all/modules/ds/images/arrow.pngbin0 -> 731 bytes
-rw-r--r--sites/all/modules/ds/images/preview.pngbin0 -> 1851 bytes
-rw-r--r--sites/all/modules/ds/includes/ds.contextual.inc51
-rw-r--r--sites/all/modules/ds/includes/ds.displays.inc167
-rw-r--r--sites/all/modules/ds/includes/ds.field_ui.inc2642
-rw-r--r--sites/all/modules/ds/includes/ds.registry.inc590
-rw-r--r--sites/all/modules/ds/includes/ds.revision.inc25
-rw-r--r--sites/all/modules/ds/js/ds.admin.js118
-rw-r--r--sites/all/modules/ds/layouts/ds_1col/ds-1col.tpl.php19
-rw-r--r--sites/all/modules/ds/layouts/ds_1col/ds_1col.pngbin0 -> 787 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_1col_wrapper/ds-1col-wrapper.tpl.php22
-rw-r--r--sites/all/modules/ds/layouts/ds_1col_wrapper/ds_1col_wrapper.pngbin0 -> 787 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_2col/ds-2col.tpl.php26
-rw-r--r--sites/all/modules/ds/layouts/ds_2col/ds_2col-rtl.css12
-rw-r--r--sites/all/modules/ds/layouts/ds_2col/ds_2col.css14
-rw-r--r--sites/all/modules/ds/layouts/ds_2col/ds_2col.pngbin0 -> 760 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_fluid/ds-2col-fluid.tpl.php35
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid-rtl.css12
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid.css20
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid.pngbin0 -> 1127 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_stacked/ds-2col-stacked.tpl.php34
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked-rtl.css12
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked.css21
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked.pngbin0 -> 890 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds-2col-stacked-fluid.tpl.php43
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid-rtl.css12
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid.css27
-rw-r--r--sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid.pngbin0 -> 1552 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_3col/ds-3col.tpl.php30
-rw-r--r--sites/all/modules/ds/layouts/ds_3col/ds_3col-rtl.css16
-rw-r--r--sites/all/modules/ds/layouts/ds_3col/ds_3col.css19
-rw-r--r--sites/all/modules/ds/layouts/ds_3col/ds_3col.pngbin0 -> 772 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_equal_width/ds-3col-equal-width.tpl.php30
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width-rtl.css16
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width.css19
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width.pngbin0 -> 766 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked/ds-3col-stacked.tpl.php38
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked-rtl.css16
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked.css27
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked.pngbin0 -> 862 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds-3col-stacked-equal-width.tpl.php38
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width-rtl.css16
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width.css27
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width.pngbin0 -> 808 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds-3col-stacked-fluid.tpl.php50
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid-rtl.css16
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid.css35
-rw-r--r--sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid.pngbin0 -> 1565 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_4col/ds-4col.tpl.php34
-rw-r--r--sites/all/modules/ds/layouts/ds_4col/ds_4col-rtl.css20
-rw-r--r--sites/all/modules/ds/layouts/ds_4col/ds_4col.css24
-rw-r--r--sites/all/modules/ds/layouts/ds_4col/ds_4col.pngbin0 -> 781 bytes
-rw-r--r--sites/all/modules/ds/layouts/ds_reset/ds-reset.tpl.php11
-rw-r--r--sites/all/modules/ds/layouts/ds_reset/ds_reset.pngbin0 -> 1276 bytes
-rw-r--r--sites/all/modules/ds/modules/ds_devel/ds_devel.info13
-rw-r--r--sites/all/modules/ds/modules/ds_devel/ds_devel.module46
-rw-r--r--sites/all/modules/ds/modules/ds_extras/README.txt20
-rw-r--r--sites/all/modules/ds/modules/ds_extras/ds_extras.ds_fields_info.inc109
-rw-r--r--sites/all/modules/ds/modules/ds_extras/ds_extras.info13
-rw-r--r--sites/all/modules/ds/modules/ds_extras/ds_extras.install135
-rw-r--r--sites/all/modules/ds/modules/ds_extras/ds_extras.module1106
-rw-r--r--sites/all/modules/ds/modules/ds_extras/includes/ds_extras.admin.inc670
-rw-r--r--sites/all/modules/ds/modules/ds_extras/includes/ds_extras.pages.inc83
-rw-r--r--sites/all/modules/ds/modules/ds_extras/includes/ds_extras.registry.inc141
-rw-r--r--sites/all/modules/ds/modules/ds_extras/includes/ds_extras.vd.inc274
-rw-r--r--sites/all/modules/ds/modules/ds_extras/js/ds_extras.admin.js126
-rw-r--r--sites/all/modules/ds/modules/ds_extras/js/ds_extras.js59
-rw-r--r--sites/all/modules/ds/modules/ds_format/ds_format.info13
-rw-r--r--sites/all/modules/ds/modules/ds_format/ds_format.install46
-rw-r--r--sites/all/modules/ds/modules/ds_format/ds_format.module75
-rw-r--r--sites/all/modules/ds/modules/ds_forms/css/ds_forms.admin.css6
-rw-r--r--sites/all/modules/ds/modules/ds_forms/ds_forms.info12
-rw-r--r--sites/all/modules/ds/modules/ds_forms/ds_forms.install16
-rw-r--r--sites/all/modules/ds/modules/ds_forms/ds_forms.module239
-rw-r--r--sites/all/modules/ds/modules/ds_forms/js/ds_forms.admin.js79
-rw-r--r--sites/all/modules/ds/modules/ds_search/css/ds_search.theme.css4
-rw-r--r--sites/all/modules/ds/modules/ds_search/ds_search.info13
-rw-r--r--sites/all/modules/ds/modules/ds_search/ds_search.install32
-rw-r--r--sites/all/modules/ds/modules/ds_search/ds_search.module763
-rw-r--r--sites/all/modules/ds/modules/ds_search/includes/ds_search.admin.inc362
-rw-r--r--sites/all/modules/ds/modules/ds_search/js/ds_search.js69
-rw-r--r--sites/all/modules/ds/modules/ds_ui/ds_ui.info12
-rw-r--r--sites/all/modules/ds/modules/ds_ui/ds_ui.module170
-rw-r--r--sites/all/modules/ds/modules/ds_ui/includes/ds.classes.inc46
-rw-r--r--sites/all/modules/ds/modules/ds_ui/includes/ds.fields.inc623
-rw-r--r--sites/all/modules/ds/modules/ds_ui/includes/ds.view_modes.inc277
-rw-r--r--sites/all/modules/ds/plugins/content_types/dsc/dsc.inc91
-rw-r--r--sites/all/modules/ds/tests/ds.base.test874
-rw-r--r--sites/all/modules/ds/tests/ds.entities.test698
-rw-r--r--sites/all/modules/ds/tests/ds.exportables.test160
-rw-r--r--sites/all/modules/ds/tests/ds.forms.test63
-rw-r--r--sites/all/modules/ds/tests/ds.search.test158
-rw-r--r--sites/all/modules/ds/tests/ds.views.test167
-rw-r--r--sites/all/modules/ds/tests/ds_exportables_test/ds_exportables_test.info12
-rw-r--r--sites/all/modules/ds/tests/ds_exportables_test/ds_exportables_test.module146
-rw-r--r--sites/all/modules/ds/tests/ds_test.info13
-rw-r--r--sites/all/modules/ds/tests/ds_test.module321
-rw-r--r--sites/all/modules/ds/tests/ds_test.views_default.inc361
-rw-r--r--sites/all/modules/ds/tests/dstest_1col/dstest-1col.tpl.php15
-rw-r--r--sites/all/modules/ds/tests/dstest_2col/dstest-2col.tpl.php26
-rw-r--r--sites/all/modules/ds/tests/dstest_2col/dstest_2col.css10
-rw-r--r--sites/all/modules/ds/views/ds-row-fields.tpl.php10
-rw-r--r--sites/all/modules/ds/views/views_plugin_ds_entity_view.inc565
-rw-r--r--sites/all/modules/ds/views/views_plugin_ds_fields_view.inc149
-rw-r--r--sites/all/modules/entity/LICENSE.txt339
-rw-r--r--sites/all/modules/entity/README.txt164
-rw-r--r--sites/all/modules/entity/ctools/content_types/entity_view.inc133
-rw-r--r--sites/all/modules/entity/entity.api.php467
-rw-r--r--sites/all/modules/entity/entity.features.inc205
-rw-r--r--sites/all/modules/entity/entity.i18n.inc210
-rw-r--r--sites/all/modules/entity/entity.info33
-rw-r--r--sites/all/modules/entity/entity.info.inc265
-rw-r--r--sites/all/modules/entity/entity.install142
-rw-r--r--sites/all/modules/entity/entity.module1574
-rw-r--r--sites/all/modules/entity/entity.rules.inc118
-rw-r--r--sites/all/modules/entity/entity.test2050
-rw-r--r--sites/all/modules/entity/entity_token.info13
-rw-r--r--sites/all/modules/entity/entity_token.module6
-rw-r--r--sites/all/modules/entity/entity_token.tokens.inc343
-rw-r--r--sites/all/modules/entity/includes/entity.controller.inc981
-rw-r--r--sites/all/modules/entity/includes/entity.inc423
-rw-r--r--sites/all/modules/entity/includes/entity.property.inc657
-rw-r--r--sites/all/modules/entity/includes/entity.ui.inc767
-rw-r--r--sites/all/modules/entity/includes/entity.wrapper.inc1224
-rw-r--r--sites/all/modules/entity/modules/book.info.inc30
-rw-r--r--sites/all/modules/entity/modules/callbacks.inc1076
-rw-r--r--sites/all/modules/entity/modules/comment.info.inc172
-rw-r--r--sites/all/modules/entity/modules/field.info.inc173
-rw-r--r--sites/all/modules/entity/modules/locale.info.inc40
-rw-r--r--sites/all/modules/entity/modules/node.info.inc166
-rw-r--r--sites/all/modules/entity/modules/poll.info.inc50
-rw-r--r--sites/all/modules/entity/modules/statistics.info.inc40
-rw-r--r--sites/all/modules/entity/modules/system.info.inc132
-rw-r--r--sites/all/modules/entity/modules/taxonomy.info.inc124
-rw-r--r--sites/all/modules/entity/modules/user.info.inc110
-rw-r--r--sites/all/modules/entity/tests/entity_feature.info14
-rw-r--r--sites/all/modules/entity/tests/entity_feature.module32
-rw-r--r--sites/all/modules/entity/tests/entity_test.info15
-rw-r--r--sites/all/modules/entity/tests/entity_test.install170
-rw-r--r--sites/all/modules/entity/tests/entity_test.module287
-rw-r--r--sites/all/modules/entity/tests/entity_test_i18n.info13
-rw-r--r--sites/all/modules/entity/tests/entity_test_i18n.module53
-rw-r--r--sites/all/modules/entity/theme/entity.theme.css4
-rw-r--r--sites/all/modules/entity/theme/entity.theme.inc215
-rw-r--r--sites/all/modules/entity/theme/entity.tpl.php48
-rw-r--r--sites/all/modules/entity/views/entity.views.inc701
-rw-r--r--sites/all/modules/entity/views/entity_views_example_query.php88
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_field_handler_helper.inc527
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_area_entity.inc120
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_boolean.inc99
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_date.inc99
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_duration.inc132
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_entity.inc207
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_field.inc105
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_numeric.inc99
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_options.inc120
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_text.inc101
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_field_uri.inc99
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_relationship.inc51
-rw-r--r--sites/all/modules/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc117
-rw-r--r--sites/all/modules/entity/views/plugins/entity_views_plugin_row_entity_view.inc98
-rw-r--r--sites/all/modules/file_entity/LICENSE.txt339
-rw-r--r--sites/all/modules/file_entity/admin_views_default/file.admin-content-file.inc339
-rw-r--r--sites/all/modules/file_entity/file_entity.admin.inc1124
-rw-r--r--sites/all/modules/file_entity/file_entity.admin.js44
-rw-r--r--sites/all/modules/file_entity/file_entity.api.php407
-rw-r--r--sites/all/modules/file_entity/file_entity.field.inc426
-rw-r--r--sites/all/modules/file_entity/file_entity.file.inc322
-rw-r--r--sites/all/modules/file_entity/file_entity.file_api.inc791
-rw-r--r--sites/all/modules/file_entity/file_entity.file_default_displays.inc174
-rw-r--r--sites/all/modules/file_entity/file_entity.info38
-rw-r--r--sites/all/modules/file_entity/file_entity.install1081
-rw-r--r--sites/all/modules/file_entity/file_entity.js16
-rw-r--r--sites/all/modules/file_entity/file_entity.module2574
-rw-r--r--sites/all/modules/file_entity/file_entity.pages.inc1167
-rw-r--r--sites/all/modules/file_entity/file_entity.pathauto.inc84
-rw-r--r--sites/all/modules/file_entity/file_entity.test1635
-rw-r--r--sites/all/modules/file_entity/file_entity.theme.inc168
-rw-r--r--sites/all/modules/file_entity/file_entity.tokens.inc133
-rw-r--r--sites/all/modules/file_entity/file_entity.tpl.php95
-rw-r--r--sites/all/modules/file_entity/file_entity.views.inc174
-rw-r--r--sites/all/modules/file_entity/plugins/content_types/file_content.inc92
-rw-r--r--sites/all/modules/file_entity/plugins/content_types/file_display.inc142
-rw-r--r--sites/all/modules/file_entity/plugins/entity/PanelizerEntityFile.class.php123
-rw-r--r--sites/all/modules/file_entity/plugins/entity/file.inc22
-rw-r--r--sites/all/modules/file_entity/plugins/tasks/file_view.inc164
-rw-r--r--sites/all/modules/file_entity/tests/file_entity_test.info13
-rw-r--r--sites/all/modules/file_entity/tests/file_entity_test.module15
-rw-r--r--sites/all/modules/file_entity/tests/file_entity_test.pages.inc6
-rw-r--r--sites/all/modules/file_entity/views/views_handler_argument_file_type.inc39
-rw-r--r--sites/all/modules/file_entity/views/views_handler_field_file_filename.inc52
-rw-r--r--sites/all/modules/file_entity/views/views_handler_field_file_link.inc48
-rw-r--r--sites/all/modules/file_entity/views/views_handler_field_file_link_delete.inc31
-rw-r--r--sites/all/modules/file_entity/views/views_handler_field_file_link_download.inc32
-rw-r--r--sites/all/modules/file_entity/views/views_handler_field_file_link_edit.inc31
-rw-r--r--sites/all/modules/file_entity/views/views_handler_field_file_link_usage.inc41
-rw-r--r--sites/all/modules/file_entity/views/views_handler_field_file_rendered.inc45
-rw-r--r--sites/all/modules/file_entity/views/views_handler_field_file_type.inc48
-rw-r--r--sites/all/modules/file_entity/views/views_handler_filter_file_type.inc23
-rw-r--r--sites/all/modules/file_entity/views/views_handler_filter_schema_type.inc41
-rw-r--r--sites/all/modules/file_entity/views/views_plugin_row_file_rss.inc175
-rw-r--r--sites/all/modules/file_entity/views/views_plugin_row_file_view.inc65
-rw-r--r--sites/all/modules/l10n_update/LICENSE.txt339
-rw-r--r--sites/all/modules/l10n_update/README.txt158
-rw-r--r--sites/all/modules/l10n_update/css/l10n_update.admin-rtl.css11
-rw-r--r--sites/all/modules/l10n_update/css/l10n_update.admin.css82
-rw-r--r--sites/all/modules/l10n_update/images/menu-collapsed-rtl.pngbin0 -> 107 bytes
-rw-r--r--sites/all/modules/l10n_update/images/menu-collapsed.pngbin0 -> 105 bytes
-rw-r--r--sites/all/modules/l10n_update/images/menu-expanded.pngbin0 -> 106 bytes
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoHeader.php418
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoItem.php282
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoMemoryWriter.php90
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoMetadataInterface.php48
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoReaderInterface.php21
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoStreamInterface.php42
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoStreamReader.php603
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoStreamWriter.php160
-rw-r--r--sites/all/modules/l10n_update/includes/gettext/PoWriterInterface.php32
-rw-r--r--sites/all/modules/l10n_update/includes/locale/Gettext.php97
-rw-r--r--sites/all/modules/l10n_update/includes/locale/PoDatabaseReader.php178
-rw-r--r--sites/all/modules/l10n_update/includes/locale/PoDatabaseWriter.php296
-rw-r--r--sites/all/modules/l10n_update/includes/locale/SourceString.php52
-rw-r--r--sites/all/modules/l10n_update/includes/locale/StringBase.php184
-rw-r--r--sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php518
-rw-r--r--sites/all/modules/l10n_update/includes/locale/StringInterface.php180
-rw-r--r--sites/all/modules/l10n_update/includes/locale/StringStorageException.php11
-rw-r--r--sites/all/modules/l10n_update/includes/locale/StringStorageInterface.php165
-rw-r--r--sites/all/modules/l10n_update/includes/locale/TranslationString.php126
-rw-r--r--sites/all/modules/l10n_update/includes/locale/TranslationsStreamWrapper.php27
-rw-r--r--sites/all/modules/l10n_update/js/l10n_update.admin.js37
-rw-r--r--sites/all/modules/l10n_update/l10n_update-translation-last-check.tpl.php18
-rw-r--r--sites/all/modules/l10n_update/l10n_update-translation-update-info.tpl.php31
-rw-r--r--sites/all/modules/l10n_update/l10n_update.admin.inc490
-rw-r--r--sites/all/modules/l10n_update/l10n_update.api.php46
-rw-r--r--sites/all/modules/l10n_update/l10n_update.batch.inc246
-rw-r--r--sites/all/modules/l10n_update/l10n_update.bulk.inc726
-rw-r--r--sites/all/modules/l10n_update/l10n_update.compare.inc386
-rw-r--r--sites/all/modules/l10n_update/l10n_update.drush.inc253
-rw-r--r--sites/all/modules/l10n_update/l10n_update.fetch.inc108
-rw-r--r--sites/all/modules/l10n_update/l10n_update.http.inc386
-rw-r--r--sites/all/modules/l10n_update/l10n_update.info38
-rw-r--r--sites/all/modules/l10n_update/l10n_update.install365
-rw-r--r--sites/all/modules/l10n_update/l10n_update.module761
-rw-r--r--sites/all/modules/l10n_update/l10n_update.translation.inc570
-rw-r--r--sites/all/modules/l10n_update/tests/L10nUpdateCronTest.test115
-rw-r--r--sites/all/modules/l10n_update/tests/L10nUpdateInterfaceTest.test91
-rw-r--r--sites/all/modules/l10n_update/tests/L10nUpdateTest.test440
-rw-r--r--sites/all/modules/l10n_update/tests/L10nUpdateTestBase.test284
-rw-r--r--sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.info14
-rw-r--r--sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.install15
-rw-r--r--sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.module143
-rw-r--r--sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.info16
-rw-r--r--sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.module28
-rw-r--r--sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.de.po28
-rw-r--r--sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.nl.po31
-rw-r--r--sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.xx.po40
-rw-r--r--sites/all/modules/l10n_update/tests/test.de.po10
-rw-r--r--sites/all/modules/libraries/CHANGELOG.txt100
-rwxr-xr-xsites/all/modules/libraries/LICENSE.txt339
-rw-r--r--sites/all/modules/libraries/README.txt37
-rw-r--r--sites/all/modules/libraries/libraries.api.php474
-rw-r--r--sites/all/modules/libraries/libraries.drush.inc151
-rw-r--r--sites/all/modules/libraries/libraries.info13
-rw-r--r--sites/all/modules/libraries/libraries.install27
-rw-r--r--sites/all/modules/libraries/libraries.module869
-rw-r--r--sites/all/modules/libraries/tests/libraries.test573
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/README.txt43
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_1.css12
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_1.js18
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_1.php15
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_2.css12
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_2.js18
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_2.php15
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_3.css12
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_3.js18
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_3.php12
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_4.css12
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_4.js18
-rw-r--r--sites/all/modules/libraries/tests/libraries/example/example_4.php12
-rw-r--r--sites/all/modules/libraries/tests/libraries/example_info_file.libraries.info10
-rw-r--r--sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.css12
-rw-r--r--sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.inc11
-rw-r--r--sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.info13
-rw-r--r--sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.js18
-rw-r--r--sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.module626
-rw-r--r--sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module_post_load.inc15
-rw-r--r--sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.css12
-rw-r--r--sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.inc11
-rw-r--r--sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info11
-rw-r--r--sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.js18
-rw-r--r--sites/all/modules/libraries/tests/themes/libraries_test_theme/template.php36
-rw-r--r--sites/all/modules/media/LICENSE.txt339
-rw-r--r--sites/all/modules/media/README.txt7
-rw-r--r--sites/all/modules/media/css/media.css147
-rw-r--r--sites/all/modules/media/images/icons/default/application-octet-stream.pngbin0 -> 1520 bytes
-rw-r--r--sites/all/modules/media/images/icons/default/audio-mpeg.pngbin0 -> 1770 bytes
-rw-r--r--sites/all/modules/media/images/icons/default/audio-x-generic.pngbin0 -> 1770 bytes
-rw-r--r--sites/all/modules/media/images/icons/default/file-unknown.pngbin0 -> 1522 bytes
-rw-r--r--sites/all/modules/media/images/icons/default/image-x-generic.pngbin0 -> 1166 bytes
-rw-r--r--sites/all/modules/media/images/icons/default/video-x-generic.pngbin0 -> 1828 bytes
-rw-r--r--sites/all/modules/media/includes/MediaBrowserPlugin.inc86
-rw-r--r--sites/all/modules/media/includes/MediaBrowserPluginInterface.inc44
-rw-r--r--sites/all/modules/media/includes/MediaBrowserUpload.inc32
-rw-r--r--sites/all/modules/media/includes/MediaBrowserView.inc56
-rw-r--r--sites/all/modules/media/includes/MediaEntityTranslationHandler.inc63
-rw-r--r--sites/all/modules/media/includes/MediaReadOnlyStreamWrapper.inc476
-rw-r--r--sites/all/modules/media/includes/media.admin.inc136
-rw-r--r--sites/all/modules/media/includes/media.browser.inc244
-rw-r--r--sites/all/modules/media/includes/media.fields.inc622
-rw-r--r--sites/all/modules/media/includes/media.pages.inc33
-rw-r--r--sites/all/modules/media/includes/media.theme.inc102
-rw-r--r--sites/all/modules/media/includes/media_views_plugin_display_media_browser.inc19
-rw-r--r--sites/all/modules/media/includes/media_views_plugin_style_media_browser.inc55
-rw-r--r--sites/all/modules/media/js/media.admin.js77
-rw-r--r--sites/all/modules/media/js/media.browser.js124
-rw-r--r--sites/all/modules/media/js/media.core.js19
-rw-r--r--sites/all/modules/media/js/media.js125
-rw-r--r--sites/all/modules/media/js/media.popups.js384
-rw-r--r--sites/all/modules/media/js/plugins/media.views.js173
-rw-r--r--sites/all/modules/media/js/util/ba-debug.min.js12
-rw-r--r--sites/all/modules/media/js/util/json2.js481
-rw-r--r--sites/all/modules/media/media-views-view-media-browser.tpl.php23
-rw-r--r--sites/all/modules/media/media.api.php137
-rw-r--r--sites/all/modules/media/media.file_default_displays.inc69
-rw-r--r--sites/all/modules/media/media.info32
-rw-r--r--sites/all/modules/media/media.install1182
-rw-r--r--sites/all/modules/media/media.media.inc110
-rw-r--r--sites/all/modules/media/media.module1402
-rw-r--r--sites/all/modules/media/media.views.inc133
-rw-r--r--sites/all/modules/media/modules/media_bulk_upload/includes/MediaBrowserBulkUpload.inc30
-rw-r--r--sites/all/modules/media/modules/media_bulk_upload/includes/media_bulk_upload.admin.inc155
-rw-r--r--sites/all/modules/media/modules/media_bulk_upload/includes/media_bulk_upload.pages.inc95
-rw-r--r--sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.info21
-rw-r--r--sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.install14
-rw-r--r--sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.module203
-rw-r--r--sites/all/modules/media/modules/media_bulk_upload/tests/media_bulk_upload.test105
-rw-r--r--sites/all/modules/media/modules/media_internet/includes/MediaBrowserInternet.inc32
-rw-r--r--sites/all/modules/media/modules/media_internet/includes/MediaInternetBaseHandler.inc77
-rw-r--r--sites/all/modules/media/modules/media_internet/includes/MediaInternetFileHandler.inc67
-rw-r--r--sites/all/modules/media/modules/media_internet/includes/MediaInternetNoHandlerException.inc13
-rw-r--r--sites/all/modules/media/modules/media_internet/includes/MediaInternetValidationException.inc13
-rw-r--r--sites/all/modules/media/modules/media_internet/media_internet.api.php47
-rw-r--r--sites/all/modules/media/modules/media_internet/media_internet.info20
-rw-r--r--sites/all/modules/media/modules/media_internet/media_internet.install21
-rw-r--r--sites/all/modules/media/modules/media_internet/media_internet.media.inc35
-rw-r--r--sites/all/modules/media/modules/media_internet/media_internet.module322
-rw-r--r--sites/all/modules/media/modules/media_internet/tests/includes/MediaInternetTestHandler.inc45
-rw-r--r--sites/all/modules/media/modules/media_internet/tests/includes/MediaInternetTestStreamWrapper.inc24
-rw-r--r--sites/all/modules/media/modules/media_internet/tests/media_internet.test394
-rw-r--r--sites/all/modules/media/modules/media_internet/tests/media_internet_test.info15
-rw-r--r--sites/all/modules/media/modules/media_internet/tests/media_internet_test.module67
-rw-r--r--sites/all/modules/media/modules/media_migrate_file_types/includes/media_migrate_file_types.pages.inc203
-rw-r--r--sites/all/modules/media/modules/media_migrate_file_types/media_migrate_file_types.info16
-rw-r--r--sites/all/modules/media/modules/media_migrate_file_types/media_migrate_file_types.module73
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/css/media_wysiwyg.css20
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/images/wysiwyg-media.gifbin0 -> 139 bytes
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.file_usage.inc179
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc372
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.pages.inc111
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.uuid.inc88
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/js/media_wysiwyg.filter.js287
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/js/media_wysiwyg.format_form.js69
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/js/wysiwyg-media.js186
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.api.php71
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.ckeditor.inc25
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.info23
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.install54
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.module371
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.test101
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.variable.inc42
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/tests/media_wysiwyg.file_usage.test237
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/tests/media_wysiwyg.macro.test95
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media.inc34
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/images/icon.gifbin0 -> 126 bytes
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/lang/en.js14
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/library.js183
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/plugin.js217
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.admin.inc77
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info17
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.install15
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.module165
-rw-r--r--sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.test65
-rw-r--r--sites/all/modules/media/modules/mediafield/mediafield.info12
-rw-r--r--sites/all/modules/media/modules/mediafield/mediafield.install43
-rw-r--r--sites/all/modules/media/modules/mediafield/mediafield.module323
-rw-r--r--sites/all/modules/media/modules/mediafield/mediafield.views.inc25
-rw-r--r--sites/all/modules/media/templates/media-dialog-page.tpl.php91
-rw-r--r--sites/all/modules/media/tests/includes/MediaModuleTest.inc30
-rw-r--r--sites/all/modules/media/tests/media.test1187
-rw-r--r--sites/all/modules/media/tests/media_module_test.info14
-rw-r--r--sites/all/modules/media/tests/media_module_test.module106
-rw-r--r--sites/all/modules/media/views/media_default.view.inc171
-rwxr-xr-xsites/all/modules/media_browser_plus/LICENSE.txt339
-rw-r--r--sites/all/modules/media_browser_plus/README.txt9
-rw-r--r--sites/all/modules/media_browser_plus/css/media_browser_plus.views.css91
-rw-r--r--sites/all/modules/media_browser_plus/images/icons/folder_icons.pngbin0 -> 1473 bytes
-rw-r--r--sites/all/modules/media_browser_plus/images/view-refresh-5.pngbin0 -> 1311 bytes
-rw-r--r--sites/all/modules/media_browser_plus/includes/media_browser_plus.admin.inc95
-rw-r--r--sites/all/modules/media_browser_plus/includes/media_browser_plus.folders.inc306
-rw-r--r--sites/all/modules/media_browser_plus/js/media_browser_plus.js358
-rw-r--r--sites/all/modules/media_browser_plus/media_browser_plus.file.inc24
-rw-r--r--sites/all/modules/media_browser_plus/media_browser_plus.info24
-rw-r--r--sites/all/modules/media_browser_plus/media_browser_plus.install147
-rw-r--r--sites/all/modules/media_browser_plus/media_browser_plus.module882
-rw-r--r--sites/all/modules/media_browser_plus/tests/media_browser_plus.base.test169
-rw-r--r--sites/all/modules/media_browser_plus/tests/media_browser_plus.root_folder.test157
-rw-r--r--sites/all/modules/media_browser_plus/tests/media_browser_plus.test518
-rw-r--r--sites/all/modules/media_browser_plus/tests/media_browser_plus_tests.info19
-rw-r--r--sites/all/modules/media_browser_plus/tests/media_browser_plus_tests.module5
-rw-r--r--sites/all/modules/media_browser_plus/views/media-browser-plus-views-view-media-browser.tpl.php35
-rw-r--r--sites/all/modules/media_browser_plus/views/media_browser_plus.views.inc237
-rw-r--r--sites/all/modules/media_browser_plus/views/media_browser_plus.views_default.inc444
-rw-r--r--sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_actions.inc153
-rw-r--r--sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_basket.inc128
-rw-r--r--sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_navigation.inc128
-rw-r--r--sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_field_preview.inc94
-rw-r--r--sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_field_preview_vbo.inc97
-rw-r--r--sites/all/modules/media_browser_plus/views/media_browser_plus_views_plugin_style_media_browser.inc53
-rw-r--r--sites/all/modules/media_gallery/LICENSE.txt339
-rw-r--r--sites/all/modules/media_gallery/README.txt120
-rw-r--r--sites/all/modules/media_gallery/colorbox-display.js80
-rw-r--r--sites/all/modules/media_gallery/css/media_gallery.edit.css14
-rw-r--r--sites/all/modules/media_gallery/ellipsis.xml12
-rw-r--r--sites/all/modules/media_gallery/fields_rsi_prevention.inc55
-rw-r--r--sites/all/modules/media_gallery/images/creative-commons-sprite.pngbin0 -> 14512 bytes
-rw-r--r--sites/all/modules/media_gallery/images/draggable.pngbin0 -> 268 bytes
-rw-r--r--sites/all/modules/media_gallery/images/empty_gallery.pngbin0 -> 125173 bytes
-rw-r--r--sites/all/modules/media_gallery/images/gallery-icon-sprite.pngbin0 -> 22960 bytes
-rw-r--r--sites/all/modules/media_gallery/images/hover-bubble-middle.pngbin0 -> 275 bytes
-rw-r--r--sites/all/modules/media_gallery/images/hover-bubble.pngbin0 -> 1786 bytes
-rw-r--r--sites/all/modules/media_gallery/images/hover-dark-bg.pngbin0 -> 51554 bytes
-rw-r--r--sites/all/modules/media_gallery/images/prev_next.pngbin0 -> 53974 bytes
-rw-r--r--sites/all/modules/media_gallery/images/stack_bg.pngbin0 -> 19039 bytes
-rw-r--r--sites/all/modules/media_gallery/js/media_gallery.edit.js25
-rw-r--r--sites/all/modules/media_gallery/media-gallery-media-item-thumbnail.tpl.php26
-rw-r--r--sites/all/modules/media_gallery/media.pages.inc60
-rw-r--r--sites/all/modules/media_gallery/media_gallery.addimage.js49
-rw-r--r--sites/all/modules/media_gallery/media_gallery.admin.inc17
-rw-r--r--sites/all/modules/media_gallery/media_gallery.css622
-rw-r--r--sites/all/modules/media_gallery/media_gallery.dragdrop.css52
-rw-r--r--sites/all/modules/media_gallery/media_gallery.dragdrop.js139
-rw-r--r--sites/all/modules/media_gallery/media_gallery.fields.inc331
-rw-r--r--sites/all/modules/media_gallery/media_gallery.form.js47
-rw-r--r--sites/all/modules/media_gallery/media_gallery.ie7.css19
-rw-r--r--sites/all/modules/media_gallery/media_gallery.info25
-rw-r--r--sites/all/modules/media_gallery/media_gallery.install1374
-rw-r--r--sites/all/modules/media_gallery/media_gallery.js15
-rw-r--r--sites/all/modules/media_gallery/media_gallery.module1905
-rw-r--r--sites/all/modules/media_gallery/media_gallery.pages.inc419
-rw-r--r--sites/all/modules/media_gallery/media_gallery.theme.inc594
-rw-r--r--sites/all/modules/plupload/CHANGELOG.txt70
-rw-r--r--sites/all/modules/plupload/LICENSE.txt339
-rw-r--r--sites/all/modules/plupload/README.txt89
-rw-r--r--sites/all/modules/plupload/images/add.pngbin0 -> 160 bytes
-rw-r--r--sites/all/modules/plupload/images/backgrounds.gifbin0 -> 2977 bytes
-rw-r--r--sites/all/modules/plupload/images/buttons-disabled.pngbin0 -> 1292 bytes
-rw-r--r--sites/all/modules/plupload/images/buttons.pngbin0 -> 1439 bytes
-rw-r--r--sites/all/modules/plupload/images/delete.gifbin0 -> 180 bytes
-rw-r--r--sites/all/modules/plupload/images/done.gifbin0 -> 1024 bytes
-rw-r--r--sites/all/modules/plupload/images/error.gifbin0 -> 994 bytes
-rwxr-xr-xsites/all/modules/plupload/images/icon-feed.pngbin0 -> 1360 bytes
-rw-r--r--sites/all/modules/plupload/images/throbber.gifbin0 -> 1922 bytes
-rw-r--r--sites/all/modules/plupload/images/transp50.pngbin0 -> 399 bytes
-rw-r--r--sites/all/modules/plupload/images/up_arrow.pngbin0 -> 218 bytes
-rw-r--r--sites/all/modules/plupload/images/up_arrow_disabled.pngbin0 -> 227 bytes
-rw-r--r--sites/all/modules/plupload/images/video-indicator.pngbin0 -> 231 bytes
-rw-r--r--sites/all/modules/plupload/js/i18n.js41
-rw-r--r--sites/all/modules/plupload/plupload.css204
-rw-r--r--sites/all/modules/plupload/plupload.info12
-rw-r--r--sites/all/modules/plupload/plupload.install122
-rw-r--r--sites/all/modules/plupload/plupload.js178
-rw-r--r--sites/all/modules/plupload/plupload.make9
-rw-r--r--sites/all/modules/plupload/plupload.module523
-rw-r--r--sites/all/modules/views/D7UPGRADE.txt2
-rw-r--r--sites/all/modules/views/LICENSE.txt339
-rw-r--r--sites/all/modules/views/README.txt19
-rw-r--r--sites/all/modules/views/css/ie/views-admin.ie7.css91
-rw-r--r--sites/all/modules/views/css/views-admin-rtl.css98
-rw-r--r--sites/all/modules/views/css/views-admin.advanced_help.css24
-rw-r--r--sites/all/modules/views/css/views-admin.bartik-rtl.css12
-rw-r--r--sites/all/modules/views/css/views-admin.bartik.css233
-rw-r--r--sites/all/modules/views/css/views-admin.contextual.css63
-rw-r--r--sites/all/modules/views/css/views-admin.css361
-rw-r--r--sites/all/modules/views/css/views-admin.ctools-rtl.css82
-rw-r--r--sites/all/modules/views/css/views-admin.ctools.css232
-rw-r--r--sites/all/modules/views/css/views-admin.garland-rtl.css13
-rw-r--r--sites/all/modules/views/css/views-admin.garland.css263
-rw-r--r--sites/all/modules/views/css/views-admin.seven-rtl.css43
-rw-r--r--sites/all/modules/views/css/views-admin.seven.css552
-rw-r--r--sites/all/modules/views/css/views-admin.theme-rtl.css208
-rw-r--r--sites/all/modules/views/css/views-admin.theme.css1083
-rw-r--r--sites/all/modules/views/css/views-rtl.css5
-rw-r--r--sites/all/modules/views/css/views.css42
-rw-r--r--sites/all/modules/views/documentation-standards.txt5
-rw-r--r--sites/all/modules/views/drush/views.drush.inc510
-rw-r--r--sites/all/modules/views/handlers/views_handler_area.inc133
-rw-r--r--sites/all/modules/views/handlers/views_handler_area_messages.inc34
-rw-r--r--sites/all/modules/views/handlers/views_handler_area_result.inc96
-rw-r--r--sites/all/modules/views/handlers/views_handler_area_text.inc110
-rw-r--r--sites/all/modules/views/handlers/views_handler_area_text_custom.inc56
-rw-r--r--sites/all/modules/views/handlers/views_handler_area_view.inc98
-rw-r--r--sites/all/modules/views/handlers/views_handler_argument.inc1244
-rw-r--r--sites/all/modules/views/handlers/views_handler_argument_date.inc101
-rw-r--r--sites/all/modules/views/handlers/views_handler_argument_formula.inc63
-rw-r--r--sites/all/modules/views/handlers/views_handler_argument_group_by_numeric.inc29
-rw-r--r--sites/all/modules/views/handlers/views_handler_argument_many_to_one.inc185
-rw-r--r--sites/all/modules/views/handlers/views_handler_argument_null.inc67
-rw-r--r--sites/all/modules/views/handlers/views_handler_argument_numeric.inc116
-rw-r--r--sites/all/modules/views/handlers/views_handler_argument_string.inc274
-rw-r--r--sites/all/modules/views/handlers/views_handler_field.inc1630
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_boolean.inc110
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_contextual_links.inc102
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_counter.inc66
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_custom.inc55
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_date.inc150
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_entity.inc104
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_machine_name.inc73
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_markup.inc59
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_math.inc84
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_numeric.inc137
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_prerender_list.inc158
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_serialized.inc65
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_time_interval.inc37
-rw-r--r--sites/all/modules/views/handlers/views_handler_field_url.inc46
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter.inc1423
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_boolean_operator.inc179
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_boolean_operator_string.inc35
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_combine.inc175
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_date.inc181
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_entity_bundle.inc122
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_equality.inc45
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_fields_compare.inc142
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_group_by_numeric.inc56
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_in_operator.inc426
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_many_to_one.inc125
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_numeric.inc325
-rw-r--r--sites/all/modules/views/handlers/views_handler_filter_string.inc338
-rw-r--r--sites/all/modules/views/handlers/views_handler_relationship.inc186
-rw-r--r--sites/all/modules/views/handlers/views_handler_relationship_groupwise_max.inc382
-rw-r--r--sites/all/modules/views/handlers/views_handler_sort.inc240
-rw-r--r--sites/all/modules/views/handlers/views_handler_sort_date.inc74
-rw-r--r--sites/all/modules/views/handlers/views_handler_sort_group_by_numeric.inc38
-rw-r--r--sites/all/modules/views/handlers/views_handler_sort_menu_hierarchy.inc54
-rw-r--r--sites/all/modules/views/handlers/views_handler_sort_random.inc22
-rw-r--r--sites/all/modules/views/help/about.html62
-rw-r--r--sites/all/modules/views/help/advanced-settings.html43
-rw-r--r--sites/all/modules/views/help/advanced-style-settings.html30
-rw-r--r--sites/all/modules/views/help/aggregation.html1
-rw-r--r--sites/all/modules/views/help/alter-exposed-filter.html31
-rw-r--r--sites/all/modules/views/help/analyze-theme.html24
-rw-r--r--sites/all/modules/views/help/api-default-views.html103
-rw-r--r--sites/all/modules/views/help/api-example.html179
-rw-r--r--sites/all/modules/views/help/api-forms.html88
-rw-r--r--sites/all/modules/views/help/api-handler-area.html45
-rw-r--r--sites/all/modules/views/help/api-tables.html262
-rw-r--r--sites/all/modules/views/help/api-upgrading.html224
-rw-r--r--sites/all/modules/views/help/api.html24
-rw-r--r--sites/all/modules/views/help/argument.html106
-rw-r--r--sites/all/modules/views/help/basic-settings.html20
-rw-r--r--sites/all/modules/views/help/demo-video.html5
-rw-r--r--sites/all/modules/views/help/display-attachment.html1
-rw-r--r--sites/all/modules/views/help/display-block.html11
-rw-r--r--sites/all/modules/views/help/display-default.html3
-rw-r--r--sites/all/modules/views/help/display-feed.html1
-rw-r--r--sites/all/modules/views/help/display-page.html7
-rw-r--r--sites/all/modules/views/help/display.html13
-rw-r--r--sites/all/modules/views/help/drush.html13
-rw-r--r--sites/all/modules/views/help/embed.html24
-rw-r--r--sites/all/modules/views/help/empty-text.html3
-rw-r--r--sites/all/modules/views/help/example-author-block.html77
-rw-r--r--sites/all/modules/views/help/example-filter-by-current-user.html46
-rw-r--r--sites/all/modules/views/help/example-recent-stories.html57
-rw-r--r--sites/all/modules/views/help/example-slideshow-thumb-pager.html54
-rw-r--r--sites/all/modules/views/help/example-user-feed.html73
-rw-r--r--sites/all/modules/views/help/example-users-by-role.html47
-rw-r--r--sites/all/modules/views/help/exposed-form.html24
-rw-r--r--sites/all/modules/views/help/field.html27
-rw-r--r--sites/all/modules/views/help/filter.html35
-rw-r--r--sites/all/modules/views/help/get-total-rows.html16
-rw-r--r--sites/all/modules/views/help/getting-started.html23
-rw-r--r--sites/all/modules/views/help/group-by.html17
-rw-r--r--sites/all/modules/views/help/header.html3
-rw-r--r--sites/all/modules/views/help/images/node-term_node-term_data-large.pngbin0 -> 4141 bytes
-rw-r--r--sites/all/modules/views/help/images/node-term_node-term_data.pngbin0 -> 3457 bytes
-rw-r--r--sites/all/modules/views/help/images/overview-ui-large.pngbin0 -> 83826 bytes
-rw-r--r--sites/all/modules/views/help/images/overview-ui-small.pngbin0 -> 44890 bytes
-rw-r--r--sites/all/modules/views/help/images/style-breakdown-large.pngbin0 -> 47381 bytes
-rw-r--r--sites/all/modules/views/help/images/style-breakdown.pngbin0 -> 15182 bytes
-rw-r--r--sites/all/modules/views/help/images/views1-admin-large.pngbin0 -> 67878 bytes
-rw-r--r--sites/all/modules/views/help/images/views1-admin.pngbin0 -> 24372 bytes
-rw-r--r--sites/all/modules/views/help/images/views1-changeviewtype-large.pngbin0 -> 37394 bytes
-rw-r--r--sites/all/modules/views/help/images/views1-changeviewtype.pngbin0 -> 17456 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-addaview-large.pngbin0 -> 46121 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-addaview.pngbin0 -> 19262 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-adddisplay-large.pngbin0 -> 43413 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-adddisplay.pngbin0 -> 19976 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-addfields-large.pngbin0 -> 29487 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-addfields.pngbin0 -> 13043 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-addfieldsajax-large.pngbin0 -> 26423 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-addfieldsajax.pngbin0 -> 16005 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-admin-large.pngbin0 -> 53418 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-admin.pngbin0 -> 19994 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-changedisplaystyle-large.pngbin0 -> 43090 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-changedisplaystyle.pngbin0 -> 16163 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-fieldspreview-large.pngbin0 -> 40484 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-fieldspreview.pngbin0 -> 12480 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-newview-large.pngbin0 -> 36263 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-newview.pngbin0 -> 17308 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-rearrangefields-large.pngbin0 -> 34183 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-rearrangefields.pngbin0 -> 19129 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-tablestyle-large.pngbin0 -> 38890 bytes
-rw-r--r--sites/all/modules/views/help/images/views2-tablestyle.pngbin0 -> 20917 bytes
-rw-r--r--sites/all/modules/views/help/images/views3-group-aggregation-types.pngbin0 -> 16742 bytes
-rw-r--r--sites/all/modules/views/help/images/views3-group-aggregation.pngbin0 -> 55825 bytes
-rw-r--r--sites/all/modules/views/help/images/views3-jump-style-menu.pngbin0 -> 40328 bytes
-rw-r--r--sites/all/modules/views/help/images/views3-semanticviews.pngbin0 -> 5611 bytes
-rw-r--r--sites/all/modules/views/help/images/views3-views-all.pngbin0 -> 38583 bytes
-rw-r--r--sites/all/modules/views/help/menu.html21
-rw-r--r--sites/all/modules/views/help/misc-notes.html11
-rw-r--r--sites/all/modules/views/help/new.html131
-rw-r--r--sites/all/modules/views/help/other-help.html9
-rw-r--r--sites/all/modules/views/help/overrides.html6
-rw-r--r--sites/all/modules/views/help/path.html7
-rw-r--r--sites/all/modules/views/help/performance-views-vs-displays.html5
-rw-r--r--sites/all/modules/views/help/performance.html1
-rw-r--r--sites/all/modules/views/help/relationship-representative.html14
-rw-r--r--sites/all/modules/views/help/relationship.html17
-rw-r--r--sites/all/modules/views/help/reports.html3
-rw-r--r--sites/all/modules/views/help/select-multple-nids-contextual-filters.html28
-rw-r--r--sites/all/modules/views/help/semantic-views.html18
-rw-r--r--sites/all/modules/views/help/sort.html28
-rw-r--r--sites/all/modules/views/help/style-comment-rss.html1
-rw-r--r--sites/all/modules/views/help/style-fields.html16
-rw-r--r--sites/all/modules/views/help/style-grid.html22
-rw-r--r--sites/all/modules/views/help/style-grouping.html7
-rw-r--r--sites/all/modules/views/help/style-jump.html48
-rw-r--r--sites/all/modules/views/help/style-list.html20
-rw-r--r--sites/all/modules/views/help/style-node-rss.html1
-rw-r--r--sites/all/modules/views/help/style-node.html11
-rw-r--r--sites/all/modules/views/help/style-row.html11
-rw-r--r--sites/all/modules/views/help/style-rss.html5
-rw-r--r--sites/all/modules/views/help/style-settings.html3
-rw-r--r--sites/all/modules/views/help/style-summary-unformatted.html3
-rw-r--r--sites/all/modules/views/help/style-summary.html3
-rw-r--r--sites/all/modules/views/help/style-table.html13
-rw-r--r--sites/all/modules/views/help/style-unformatted.html1
-rw-r--r--sites/all/modules/views/help/style.html15
-rw-r--r--sites/all/modules/views/help/taxonomy-page-override.html41
-rw-r--r--sites/all/modules/views/help/theme-css.html76
-rw-r--r--sites/all/modules/views/help/top-pager.html18
-rw-r--r--sites/all/modules/views/help/ui-crashes.html25
-rw-r--r--sites/all/modules/views/help/updating-view3.html1
-rw-r--r--sites/all/modules/views/help/updating.html7
-rw-r--r--sites/all/modules/views/help/upgrading.html8
-rw-r--r--sites/all/modules/views/help/using-theme.html50
-rw-r--r--sites/all/modules/views/help/view-add.html25
-rw-r--r--sites/all/modules/views/help/view-settings.html5
-rw-r--r--sites/all/modules/views/help/view-type.html21
-rw-r--r--sites/all/modules/views/help/views.help.ini359
-rw-r--r--sites/all/modules/views/images/arrow-active.pngbin0 -> 313 bytes
-rw-r--r--sites/all/modules/views/images/close.pngbin0 -> 227 bytes
-rw-r--r--sites/all/modules/views/images/expanded-options.pngbin0 -> 228 bytes
-rw-r--r--sites/all/modules/views/images/loading-small.gifbin0 -> 2112 bytes
-rw-r--r--sites/all/modules/views/images/loading.gifbin0 -> 6733 bytes
-rw-r--r--sites/all/modules/views/images/overridden.gifbin0 -> 175 bytes
-rw-r--r--sites/all/modules/views/images/sprites.pngbin0 -> 1777 bytes
-rw-r--r--sites/all/modules/views/images/status-active.gifbin0 -> 2196 bytes
-rw-r--r--sites/all/modules/views/includes/admin.inc5465
-rw-r--r--sites/all/modules/views/includes/ajax.inc380
-rw-r--r--sites/all/modules/views/includes/analyze.inc122
-rw-r--r--sites/all/modules/views/includes/base.inc359
-rw-r--r--sites/all/modules/views/includes/cache.inc217
-rw-r--r--sites/all/modules/views/includes/handlers.inc1712
-rw-r--r--sites/all/modules/views/includes/plugins.inc587
-rw-r--r--sites/all/modules/views/includes/view.inc2661
-rw-r--r--sites/all/modules/views/js/ajax.js237
-rw-r--r--sites/all/modules/views/js/ajax_view.js147
-rw-r--r--sites/all/modules/views/js/base.js110
-rw-r--r--sites/all/modules/views/js/jquery.ui.dialog.patch.js27
-rw-r--r--sites/all/modules/views/js/views-admin.js1028
-rw-r--r--sites/all/modules/views/js/views-contextual.js16
-rw-r--r--sites/all/modules/views/js/views-list.js21
-rw-r--r--sites/all/modules/views/modules/aggregator.views.inc406
-rw-r--r--sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_category_cid.inc26
-rw-r--r--sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_fid.inc26
-rw-r--r--sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_iid.inc30
-rw-r--r--sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_category.inc60
-rw-r--r--sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_title_link.inc55
-rw-r--r--sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_xss.inc18
-rw-r--r--sites/all/modules/views/modules/aggregator/views_handler_filter_aggregator_category_cid.inc26
-rw-r--r--sites/all/modules/views/modules/aggregator/views_plugin_row_aggregator_rss.inc74
-rw-r--r--sites/all/modules/views/modules/book.views.inc131
-rw-r--r--sites/all/modules/views/modules/book/views_plugin_argument_default_book_root.inc21
-rw-r--r--sites/all/modules/views/modules/comment.views.inc662
-rw-r--r--sites/all/modules/views/modules/comment.views_default.inc285
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_argument_comment_user_uid.inc61
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment.inc73
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment_depth.inc21
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment_link.inc69
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment_link_approve.inc36
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment_link_delete.inc29
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment_link_edit.inc52
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment_link_reply.inc29
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment_node_link.inc64
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_comment_username.inc58
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_last_comment_timestamp.inc28
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_ncs_last_comment_name.inc54
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_ncs_last_updated.inc18
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_node_comment.inc26
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_field_node_new_comments.inc115
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_filter_comment_user_uid.inc29
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_filter_ncs_last_updated.inc25
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_filter_node_comment.inc21
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_sort_comment_thread.inc28
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_sort_ncs_last_comment_name.inc30
-rw-r--r--sites/all/modules/views/modules/comment/views_handler_sort_ncs_last_updated.inc19
-rw-r--r--sites/all/modules/views/modules/comment/views_plugin_row_comment_rss.inc152
-rw-r--r--sites/all/modules/views/modules/comment/views_plugin_row_comment_view.inc97
-rw-r--r--sites/all/modules/views/modules/contact.views.inc21
-rw-r--r--sites/all/modules/views/modules/contact/views_handler_field_contact_link.inc57
-rw-r--r--sites/all/modules/views/modules/field.views.inc510
-rw-r--r--sites/all/modules/views/modules/field/views_handler_argument_field_list.inc58
-rw-r--r--sites/all/modules/views/modules/field/views_handler_argument_field_list_string.inc59
-rw-r--r--sites/all/modules/views/modules/field/views_handler_field_field.inc938
-rw-r--r--sites/all/modules/views/modules/field/views_handler_filter_field_list.inc32
-rw-r--r--sites/all/modules/views/modules/field/views_handler_filter_field_list_boolean.inc33
-rw-r--r--sites/all/modules/views/modules/field/views_handler_relationship_entity_reverse.inc84
-rw-r--r--sites/all/modules/views/modules/file.views.inc73
-rw-r--r--sites/all/modules/views/modules/filter.views.inc33
-rw-r--r--sites/all/modules/views/modules/filter/views_handler_field_filter_format_name.inc36
-rw-r--r--sites/all/modules/views/modules/image.views.inc72
-rw-r--r--sites/all/modules/views/modules/locale.views.inc221
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_argument_locale_group.inc40
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_argument_locale_language.inc38
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_field_locale_group.inc21
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_field_locale_language.inc36
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_field_locale_link_edit.inc60
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_field_node_language.inc37
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_filter_locale_group.inc23
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_filter_locale_language.inc26
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_filter_locale_version.inc28
-rw-r--r--sites/all/modules/views/modules/locale/views_handler_filter_node_language.inc26
-rw-r--r--sites/all/modules/views/modules/node.views.inc785
-rw-r--r--sites/all/modules/views/modules/node.views_default.inc318
-rw-r--r--sites/all/modules/views/modules/node.views_template.inc135
-rw-r--r--sites/all/modules/views/modules/node/views_handler_argument_dates_various.inc177
-rw-r--r--sites/all/modules/views/modules/node/views_handler_argument_node_language.inc36
-rw-r--r--sites/all/modules/views/modules/node/views_handler_argument_node_nid.inc24
-rw-r--r--sites/all/modules/views/modules/node/views_handler_argument_node_type.inc39
-rw-r--r--sites/all/modules/views/modules/node/views_handler_argument_node_uid_revision.inc18
-rw-r--r--sites/all/modules/views/modules/node/views_handler_argument_node_vid.inc26
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_history_user_timestamp.inc82
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node.inc80
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_link.inc48
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_link_delete.inc31
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_link_edit.inc31
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_path.inc47
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_revision.inc74
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_revision_link.inc66
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_revision_link_delete.inc36
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_revision_link_revert.inc36
-rw-r--r--sites/all/modules/views/modules/node/views_handler_field_node_type.inc49
-rw-r--r--sites/all/modules/views/modules/node/views_handler_filter_history_user_timestamp.inc87
-rw-r--r--sites/all/modules/views/modules/node/views_handler_filter_node_access.inc40
-rw-r--r--sites/all/modules/views/modules/node/views_handler_filter_node_status.inc22
-rw-r--r--sites/all/modules/views/modules/node/views_handler_filter_node_type.inc26
-rw-r--r--sites/all/modules/views/modules/node/views_handler_filter_node_uid_revision.inc25
-rw-r--r--sites/all/modules/views/modules/node/views_plugin_argument_default_node.inc26
-rw-r--r--sites/all/modules/views/modules/node/views_plugin_argument_validate_node.inc135
-rw-r--r--sites/all/modules/views/modules/node/views_plugin_row_node_rss.inc174
-rw-r--r--sites/all/modules/views/modules/node/views_plugin_row_node_view.inc110
-rw-r--r--sites/all/modules/views/modules/poll.views.inc47
-rw-r--r--sites/all/modules/views/modules/profile.views.inc217
-rw-r--r--sites/all/modules/views/modules/profile/views_handler_field_profile_date.inc90
-rw-r--r--sites/all/modules/views/modules/profile/views_handler_field_profile_list.inc41
-rw-r--r--sites/all/modules/views/modules/profile/views_handler_filter_profile_selection.inc30
-rw-r--r--sites/all/modules/views/modules/search.views.inc202
-rw-r--r--sites/all/modules/views/modules/search.views_default.inc119
-rw-r--r--sites/all/modules/views/modules/search/views_handler_argument_search.inc100
-rw-r--r--sites/all/modules/views/modules/search/views_handler_field_search_score.inc81
-rw-r--r--sites/all/modules/views/modules/search/views_handler_filter_search.inc234
-rw-r--r--sites/all/modules/views/modules/search/views_handler_sort_search_score.inc32
-rw-r--r--sites/all/modules/views/modules/search/views_plugin_row_search_view.inc39
-rw-r--r--sites/all/modules/views/modules/statistics.views.inc263
-rw-r--r--sites/all/modules/views/modules/statistics.views_default.inc253
-rw-r--r--sites/all/modules/views/modules/statistics/views_handler_field_accesslog_path.inc58
-rw-r--r--sites/all/modules/views/modules/system.views.inc578
-rw-r--r--sites/all/modules/views/modules/system/views_handler_argument_file_fid.inc28
-rw-r--r--sites/all/modules/views/modules/system/views_handler_field_file.inc61
-rw-r--r--sites/all/modules/views/modules/system/views_handler_field_file_extension.inc49
-rw-r--r--sites/all/modules/views/modules/system/views_handler_field_file_filemime.inc38
-rw-r--r--sites/all/modules/views/modules/system/views_handler_field_file_status.inc18
-rw-r--r--sites/all/modules/views/modules/system/views_handler_field_file_uri.inc35
-rw-r--r--sites/all/modules/views/modules/system/views_handler_filter_file_status.inc19
-rw-r--r--sites/all/modules/views/modules/system/views_handler_filter_system_type.inc21
-rw-r--r--sites/all/modules/views/modules/taxonomy.views.inc540
-rw-r--r--sites/all/modules/views/modules/taxonomy.views_default.inc109
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_argument_taxonomy.inc29
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid.inc49
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid_depth.inc145
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid_depth_modifier.inc64
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_argument_vocabulary_machine_name.inc26
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_argument_vocabulary_vid.inc26
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_field_taxonomy.inc85
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_field_term_link_edit.inc62
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_field_term_node_tid.inc145
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_filter_term_node_tid.inc375
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_filter_term_node_tid_depth.inc100
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_filter_vocabulary_machine_name.inc25
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_filter_vocabulary_vid.inc25
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_handler_relationship_node_term_data.inc97
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_plugin_argument_default_taxonomy_tid.inc154
-rw-r--r--sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc223
-rw-r--r--sites/all/modules/views/modules/tracker.views.inc183
-rw-r--r--sites/all/modules/views/modules/tracker/views_handler_argument_tracker_comment_user_uid.inc26
-rw-r--r--sites/all/modules/views/modules/tracker/views_handler_filter_tracker_boolean_operator.inc31
-rw-r--r--sites/all/modules/views/modules/tracker/views_handler_filter_tracker_comment_user_uid.inc23
-rw-r--r--sites/all/modules/views/modules/translation.views.inc121
-rw-r--r--sites/all/modules/views/modules/translation/views_handler_argument_node_tnid.inc26
-rw-r--r--sites/all/modules/views/modules/translation/views_handler_field_node_link_translate.inc29
-rw-r--r--sites/all/modules/views/modules/translation/views_handler_field_node_translation_link.inc49
-rw-r--r--sites/all/modules/views/modules/translation/views_handler_filter_node_tnid.inc45
-rw-r--r--sites/all/modules/views/modules/translation/views_handler_filter_node_tnid_child.inc22
-rw-r--r--sites/all/modules/views/modules/translation/views_handler_relationship_translation.inc103
-rw-r--r--sites/all/modules/views/modules/user.views.inc575
-rw-r--r--sites/all/modules/views/modules/user/views_handler_argument_user_uid.inc33
-rw-r--r--sites/all/modules/views/modules/user/views_handler_argument_users_roles_rid.inc23
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user.inc55
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_language.inc39
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_link.inc58
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_link_cancel.inc33
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_link_edit.inc30
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_mail.inc44
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_name.inc83
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_permissions.inc68
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_picture.inc114
-rw-r--r--sites/all/modules/views/modules/user/views_handler_field_user_roles.inc57
-rw-r--r--sites/all/modules/views/modules/user/views_handler_filter_user_current.inc36
-rw-r--r--sites/all/modules/views/modules/user/views_handler_filter_user_name.inc162
-rw-r--r--sites/all/modules/views/modules/user/views_handler_filter_user_permissions.inc35
-rw-r--r--sites/all/modules/views/modules/user/views_handler_filter_user_roles.inc28
-rw-r--r--sites/all/modules/views/modules/user/views_plugin_argument_default_current_user.inc18
-rw-r--r--sites/all/modules/views/modules/user/views_plugin_argument_default_user.inc77
-rw-r--r--sites/all/modules/views/modules/user/views_plugin_argument_validate_user.inc140
-rw-r--r--sites/all/modules/views/modules/user/views_plugin_row_user_view.inc81
-rw-r--r--sites/all/modules/views/modules/views.views.inc131
-rw-r--r--sites/all/modules/views/plugins/export_ui/views_ui.class.php447
-rw-r--r--sites/all/modules/views/plugins/export_ui/views_ui.inc37
-rw-r--r--sites/all/modules/views/plugins/views_plugin_access.inc96
-rw-r--r--sites/all/modules/views/plugins/views_plugin_access_none.inc17
-rw-r--r--sites/all/modules/views/plugins/views_plugin_access_perm.inc62
-rw-r--r--sites/all/modules/views/plugins/views_plugin_access_role.inc66
-rw-r--r--sites/all/modules/views/plugins/views_plugin_argument_default.inc94
-rw-r--r--sites/all/modules/views/plugins/views_plugin_argument_default_fixed.inc46
-rw-r--r--sites/all/modules/views/plugins/views_plugin_argument_default_php.inc57
-rw-r--r--sites/all/modules/views/plugins/views_plugin_argument_default_raw.inc50
-rw-r--r--sites/all/modules/views/plugins/views_plugin_argument_validate.inc99
-rw-r--r--sites/all/modules/views/plugins/views_plugin_argument_validate_numeric.inc17
-rw-r--r--sites/all/modules/views/plugins/views_plugin_argument_validate_php.inc57
-rw-r--r--sites/all/modules/views/plugins/views_plugin_cache.inc350
-rw-r--r--sites/all/modules/views/plugins/views_plugin_cache_none.inc25
-rw-r--r--sites/all/modules/views/plugins/views_plugin_cache_time.inc110
-rw-r--r--sites/all/modules/views/plugins/views_plugin_display.inc3094
-rw-r--r--sites/all/modules/views/plugins/views_plugin_display_attachment.inc284
-rw-r--r--sites/all/modules/views/plugins/views_plugin_display_block.inc243
-rw-r--r--sites/all/modules/views/plugins/views_plugin_display_default.inc57
-rw-r--r--sites/all/modules/views/plugins/views_plugin_display_embed.inc14
-rw-r--r--sites/all/modules/views/plugins/views_plugin_display_extender.inc62
-rw-r--r--sites/all/modules/views/plugins/views_plugin_display_feed.inc222
-rw-r--r--sites/all/modules/views/plugins/views_plugin_display_page.inc569
-rw-r--r--sites/all/modules/views/plugins/views_plugin_exposed_form.inc334
-rw-r--r--sites/all/modules/views/plugins/views_plugin_exposed_form_basic.inc13
-rw-r--r--sites/all/modules/views/plugins/views_plugin_exposed_form_input_required.inc97
-rw-r--r--sites/all/modules/views/plugins/views_plugin_localization.inc171
-rw-r--r--sites/all/modules/views/plugins/views_plugin_localization_core.inc109
-rw-r--r--sites/all/modules/views/plugins/views_plugin_localization_none.inc36
-rw-r--r--sites/all/modules/views/plugins/views_plugin_pager.inc236
-rw-r--r--sites/all/modules/views/plugins/views_plugin_pager_full.inc424
-rw-r--r--sites/all/modules/views/plugins/views_plugin_pager_mini.inc70
-rw-r--r--sites/all/modules/views/plugins/views_plugin_pager_none.inc75
-rw-r--r--sites/all/modules/views/plugins/views_plugin_pager_some.inc62
-rw-r--r--sites/all/modules/views/plugins/views_plugin_query.inc185
-rw-r--r--sites/all/modules/views/plugins/views_plugin_query_default.inc1686
-rw-r--r--sites/all/modules/views/plugins/views_plugin_row.inc152
-rw-r--r--sites/all/modules/views/plugins/views_plugin_row_fields.inc86
-rw-r--r--sites/all/modules/views/plugins/views_plugin_row_rss_fields.inc180
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style.inc598
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_default.inc25
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_grid.inc70
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_jump_menu.inc176
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_list.inc53
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_mapping.inc125
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_rss.inc156
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_summary.inc76
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_summary_jump_menu.inc146
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_summary_unformatted.inc34
-rw-r--r--sites/all/modules/views/plugins/views_plugin_style_table.inc307
-rw-r--r--sites/all/modules/views/plugins/views_wizard/comment.inc44
-rw-r--r--sites/all/modules/views/plugins/views_wizard/file_managed.inc26
-rw-r--r--sites/all/modules/views/plugins/views_wizard/node.inc42
-rw-r--r--sites/all/modules/views/plugins/views_wizard/node_revision.inc43
-rw-r--r--sites/all/modules/views/plugins/views_wizard/taxonomy_term.inc30
-rw-r--r--sites/all/modules/views/plugins/views_wizard/users.inc35
-rw-r--r--sites/all/modules/views/plugins/views_wizard/views_ui_base_views_wizard.class.php929
-rw-r--r--sites/all/modules/views/plugins/views_wizard/views_ui_comment_views_wizard.class.php108
-rw-r--r--sites/all/modules/views/plugins/views_wizard/views_ui_file_managed_views_wizard.class.php40
-rw-r--r--sites/all/modules/views/plugins/views_wizard/views_ui_node_revision_views_wizard.class.php68
-rw-r--r--sites/all/modules/views/plugins/views_wizard/views_ui_node_views_wizard.class.php137
-rw-r--r--sites/all/modules/views/plugins/views_wizard/views_ui_taxonomy_term_views_wizard.class.php42
-rw-r--r--sites/all/modules/views/plugins/views_wizard/views_ui_users_views_wizard.class.php42
-rw-r--r--sites/all/modules/views/test_templates/README.txt11
-rw-r--r--sites/all/modules/views/test_templates/views-view--frontpage.tpl.php85
-rw-r--r--sites/all/modules/views/tests/comment/views_handler_argument_comment_user_uid.test106
-rw-r--r--sites/all/modules/views/tests/comment/views_handler_filter_comment_user_uid.test41
-rw-r--r--sites/all/modules/views/tests/field/views_fieldapi.test494
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_area_text.test52
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_argument_null.test72
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_argument_string.test96
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field.test314
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_boolean.test108
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_counter.test70
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_custom.test47
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_date.test117
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_file_extension.test66
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_file_size.test64
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_math.test45
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_url.test60
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_field_xss.test60
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_filter_combine.test105
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_filter_date.test221
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_filter_equality.test173
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_filter_in_operator.test196
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_filter_numeric.test409
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_filter_string.test810
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_sort.test121
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_sort_date.test198
-rw-r--r--sites/all/modules/views/tests/handlers/views_handler_sort_random.test89
-rw-r--r--sites/all/modules/views/tests/handlers/views_handlers.test81
-rw-r--r--sites/all/modules/views/tests/node/views_node_revision_relations.test177
-rw-r--r--sites/all/modules/views/tests/plugins/views_plugin_display.test194
-rw-r--r--sites/all/modules/views/tests/styles/views_plugin_style.test264
-rw-r--r--sites/all/modules/views/tests/styles/views_plugin_style_base.test33
-rw-r--r--sites/all/modules/views/tests/styles/views_plugin_style_jump_menu.test151
-rw-r--r--sites/all/modules/views/tests/styles/views_plugin_style_mapping.test144
-rw-r--r--sites/all/modules/views/tests/styles/views_plugin_style_unformatted.test53
-rw-r--r--sites/all/modules/views/tests/taxonomy/views_handler_relationship_node_term_data.test122
-rw-r--r--sites/all/modules/views/tests/test_handlers/views_test_area_access.inc28
-rw-r--r--sites/all/modules/views/tests/test_plugins/views_test_plugin_access_test_dynamic.inc26
-rw-r--r--sites/all/modules/views/tests/test_plugins/views_test_plugin_access_test_static.inc26
-rw-r--r--sites/all/modules/views/tests/test_plugins/views_test_plugin_style_test_mapping.inc52
-rw-r--r--sites/all/modules/views/tests/user/views_handler_field_user_name.test96
-rw-r--r--sites/all/modules/views/tests/user/views_user.test143
-rw-r--r--sites/all/modules/views/tests/user/views_user_argument_default.test90
-rw-r--r--sites/all/modules/views/tests/user/views_user_argument_validate.test115
-rw-r--r--sites/all/modules/views/tests/views_access.test285
-rw-r--r--sites/all/modules/views/tests/views_analyze.test51
-rw-r--r--sites/all/modules/views/tests/views_argument_default.test137
-rw-r--r--sites/all/modules/views/tests/views_argument_validator.test106
-rw-r--r--sites/all/modules/views/tests/views_basic.test178
-rw-r--r--sites/all/modules/views/tests/views_cache.test308
-rw-r--r--sites/all/modules/views/tests/views_cache.test.css5
-rw-r--r--sites/all/modules/views/tests/views_cache.test.js5
-rw-r--r--sites/all/modules/views/tests/views_exposed_form.test170
-rw-r--r--sites/all/modules/views/tests/views_glossary.test60
-rw-r--r--sites/all/modules/views/tests/views_groupby.test352
-rw-r--r--sites/all/modules/views/tests/views_handlers.test150
-rw-r--r--sites/all/modules/views/tests/views_module.test241
-rw-r--r--sites/all/modules/views/tests/views_pager.test496
-rw-r--r--sites/all/modules/views/tests/views_plugin_localization_test.inc40
-rw-r--r--sites/all/modules/views/tests/views_query.test433
-rw-r--r--sites/all/modules/views/tests/views_test.info13
-rw-r--r--sites/all/modules/views/tests/views_test.install13
-rw-r--r--sites/all/modules/views/tests/views_test.module121
-rw-r--r--sites/all/modules/views/tests/views_test.views_default.inc222
-rw-r--r--sites/all/modules/views/tests/views_translatable.test221
-rw-r--r--sites/all/modules/views/tests/views_ui.test973
-rw-r--r--sites/all/modules/views/tests/views_upgrade.test277
-rw-r--r--sites/all/modules/views/tests/views_view.test290
-rw-r--r--sites/all/modules/views/theme/theme.inc1155
-rw-r--r--sites/all/modules/views/theme/views-exposed-form.tpl.php80
-rw-r--r--sites/all/modules/views/theme/views-more.tpl.php19
-rw-r--r--sites/all/modules/views/theme/views-ui-display-tab-bucket.tpl.php17
-rw-r--r--sites/all/modules/views/theme/views-ui-display-tab-setting.tpl.php15
-rw-r--r--sites/all/modules/views/theme/views-ui-edit-item.tpl.php45
-rw-r--r--sites/all/modules/views/theme/views-ui-edit-view.tpl.php46
-rw-r--r--sites/all/modules/views/theme/views-view-field.tpl.php25
-rw-r--r--sites/all/modules/views/theme/views-view-fields.tpl.php36
-rw-r--r--sites/all/modules/views/theme/views-view-grid.tpl.php32
-rw-r--r--sites/all/modules/views/theme/views-view-grouping.tpl.php25
-rw-r--r--sites/all/modules/views/theme/views-view-list.tpl.php21
-rw-r--r--sites/all/modules/views/theme/views-view-row-comment.tpl.php18
-rw-r--r--sites/all/modules/views/theme/views-view-row-rss.tpl.php15
-rw-r--r--sites/all/modules/views/theme/views-view-rss.tpl.php20
-rw-r--r--sites/all/modules/views/theme/views-view-summary-unformatted.tpl.php20
-rw-r--r--sites/all/modules/views/theme/views-view-summary.tpl.php20
-rw-r--r--sites/all/modules/views/theme/views-view-table.tpl.php48
-rw-r--r--sites/all/modules/views/theme/views-view-unformatted.tpl.php17
-rw-r--r--sites/all/modules/views/theme/views-view.tpl.php90
-rw-r--r--sites/all/modules/views/views.api.php1183
-rw-r--r--sites/all/modules/views/views.info326
-rw-r--r--sites/all/modules/views/views.install647
-rw-r--r--sites/all/modules/views/views.module2580
-rw-r--r--sites/all/modules/views/views.tokens.inc94
-rw-r--r--sites/all/modules/views/views_export/views_export.module10
-rw-r--r--sites/all/modules/views/views_ui.info15
-rw-r--r--sites/all/modules/views/views_ui.module867
-rw-r--r--sites/all/modules/views_bulk_operations/LICENSE.txt339
-rw-r--r--sites/all/modules/views_bulk_operations/README.txt13
-rw-r--r--sites/all/modules/views_bulk_operations/actions/archive.action.inc193
-rw-r--r--sites/all/modules/views_bulk_operations/actions/argument_selector.action.inc49
-rw-r--r--sites/all/modules/views_bulk_operations/actions/book.action.inc79
-rw-r--r--sites/all/modules/views_bulk_operations/actions/delete.action.inc38
-rw-r--r--sites/all/modules/views_bulk_operations/actions/modify.action.inc622
-rw-r--r--sites/all/modules/views_bulk_operations/actions/script.action.inc92
-rw-r--r--sites/all/modules/views_bulk_operations/actions/user_cancel.action.inc82
-rw-r--r--sites/all/modules/views_bulk_operations/actions/user_roles.action.inc63
-rw-r--r--sites/all/modules/views_bulk_operations/actions_permissions.info11
-rw-r--r--sites/all/modules/views_bulk_operations/actions_permissions.module51
-rw-r--r--sites/all/modules/views_bulk_operations/css/modify.action.css4
-rw-r--r--sites/all/modules/views_bulk_operations/css/views_bulk_operations.css35
-rw-r--r--sites/all/modules/views_bulk_operations/js/views_bulk_operations.js127
-rw-r--r--sites/all/modules/views_bulk_operations/plugins/operation_types/action.class.php261
-rw-r--r--sites/all/modules/views_bulk_operations/plugins/operation_types/action.inc104
-rw-r--r--sites/all/modules/views_bulk_operations/plugins/operation_types/base.class.php271
-rw-r--r--sites/all/modules/views_bulk_operations/plugins/operation_types/rules_component.class.php131
-rw-r--r--sites/all/modules/views_bulk_operations/plugins/operation_types/rules_component.inc94
-rw-r--r--sites/all/modules/views_bulk_operations/views/views_bulk_operations.views.inc33
-rw-r--r--sites/all/modules/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc346
-rw-r--r--sites/all/modules/views_bulk_operations/views_bulk_operations.api.php35
-rw-r--r--sites/all/modules/views_bulk_operations/views_bulk_operations.drush.inc196
-rw-r--r--sites/all/modules/views_bulk_operations/views_bulk_operations.info17
-rw-r--r--sites/all/modules/views_bulk_operations/views_bulk_operations.install14
-rw-r--r--sites/all/modules/views_bulk_operations/views_bulk_operations.module1298
-rw-r--r--sites/all/modules/views_bulk_operations/views_bulk_operations.rules.inc298
-rw-r--r--sites/all/modules/views_custom_template/LICENSE.txt339
-rw-r--r--sites/all/modules/views_custom_template/README.txt46
-rw-r--r--sites/all/modules/views_custom_template/views_custom_template.info18
-rw-r--r--sites/all/modules/views_custom_template/views_custom_template.module67
-rw-r--r--sites/all/modules/views_custom_template/views_custom_template.views.inc30
-rw-r--r--sites/all/modules/views_custom_template/views_plugin_display_custom_template.inc94
-rw-r--r--sites/all/modules/views_tree/LICENSE.txt339
-rw-r--r--sites/all/modules/views_tree/views_tree.info13
-rw-r--r--sites/all/modules/views_tree/views_tree.module175
-rw-r--r--sites/all/modules/views_tree/views_tree.views.inc27
-rw-r--r--sites/all/modules/views_tree/views_tree_plugin_style_tree.inc70
-rw-r--r--sites/all/modules/wysiwyg/CHANGELOG.txt345
-rw-r--r--sites/all/modules/wysiwyg/LICENSE.txt339
-rw-r--r--sites/all/modules/wysiwyg/README.txt54
-rw-r--r--sites/all/modules/wysiwyg/editors/ckeditor.inc464
-rw-r--r--sites/all/modules/wysiwyg/editors/css/openwysiwyg.css11
-rw-r--r--sites/all/modules/wysiwyg/editors/css/tinymce-2.css27
-rw-r--r--sites/all/modules/wysiwyg/editors/css/tinymce-3.css24
-rw-r--r--sites/all/modules/wysiwyg/editors/epiceditor.inc102
-rw-r--r--sites/all/modules/wysiwyg/editors/fckeditor.inc292
-rw-r--r--sites/all/modules/wysiwyg/editors/js/ckeditor-3.0.js248
-rw-r--r--sites/all/modules/wysiwyg/editors/js/epiceditor.js38
-rw-r--r--sites/all/modules/wysiwyg/editors/js/fckeditor-2.6.js196
-rw-r--r--sites/all/modules/wysiwyg/editors/js/fckeditor.config.js86
-rw-r--r--sites/all/modules/wysiwyg/editors/js/jwysiwyg.js43
-rw-r--r--sites/all/modules/wysiwyg/editors/js/markitup.js46
-rw-r--r--sites/all/modules/wysiwyg/editors/js/nicedit.js115
-rw-r--r--sites/all/modules/wysiwyg/editors/js/none.js91
-rw-r--r--sites/all/modules/wysiwyg/editors/js/openwysiwyg.js141
-rw-r--r--sites/all/modules/wysiwyg/editors/js/tinymce-2.js203
-rw-r--r--sites/all/modules/wysiwyg/editors/js/tinymce-3.js256
-rw-r--r--sites/all/modules/wysiwyg/editors/js/whizzywig-56.js155
-rw-r--r--sites/all/modules/wysiwyg/editors/js/whizzywig-60.js107
-rw-r--r--sites/all/modules/wysiwyg/editors/js/whizzywig.js154
-rw-r--r--sites/all/modules/wysiwyg/editors/js/wymeditor.js74
-rw-r--r--sites/all/modules/wysiwyg/editors/js/yui.js145
-rw-r--r--sites/all/modules/wysiwyg/editors/jwysiwyg.inc62
-rw-r--r--sites/all/modules/wysiwyg/editors/markitup.inc189
-rw-r--r--sites/all/modules/wysiwyg/editors/nicedit.inc119
-rw-r--r--sites/all/modules/wysiwyg/editors/openwysiwyg.inc173
-rw-r--r--sites/all/modules/wysiwyg/editors/tinymce.inc650
-rw-r--r--sites/all/modules/wysiwyg/editors/whizzywig.inc147
-rw-r--r--sites/all/modules/wysiwyg/editors/wymeditor.inc234
-rw-r--r--sites/all/modules/wysiwyg/editors/yui.inc339
-rw-r--r--sites/all/modules/wysiwyg/plugins/break.inc21
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/break.css10
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/break.js68
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/images/break.gifbin0 -> 108 bytes
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/images/breaktext.gifbin0 -> 255 bytes
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/images/spacer.gifbin0 -> 43 bytes
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/langs/ca.js6
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/langs/de.js6
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/langs/en.js6
-rw-r--r--sites/all/modules/wysiwyg/plugins/break/langs/es.js6
-rw-r--r--sites/all/modules/wysiwyg/tests/wysiwyg.test7
-rw-r--r--sites/all/modules/wysiwyg/tests/wysiwyg_test.info14
-rw-r--r--sites/all/modules/wysiwyg/tests/wysiwyg_test.install7
-rw-r--r--sites/all/modules/wysiwyg/tests/wysiwyg_test.module50
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg-dialog-page.tpl.php28
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.admin.inc594
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.api.js97
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.api.php283
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.dialog.inc183
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.features.inc92
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.info17
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.init.js19
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.install313
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.js269
-rw-r--r--sites/all/modules/wysiwyg/wysiwyg.module1135
1633 files changed, 240403 insertions, 0 deletions
diff --git a/sites/all/modules/advanced_help/LICENSE.txt b/sites/all/modules/advanced_help/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/advanced_help/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/advanced_help/advanced-help-popup.tpl.php b/sites/all/modules/advanced_help/advanced-help-popup.tpl.php
new file mode 100644
index 000000000..23faf29b7
--- /dev/null
+++ b/sites/all/modules/advanced_help/advanced-help-popup.tpl.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @file
+ * Default theme implementation to display a help popup.
+ */
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
+
+<head>
+ <title><?php print $head_title; ?></title>
+ <?php print $head; ?>
+ <?php print $styles; ?>
+ <?php print $scripts; ?>
+ <script type="text/javascript"><?php /* Needed to avoid Flash of Unstyled Content in IE */ ?> </script>
+</head>
+<body class="advanced-help-popup-body">
+ <div id="page">
+ <div id="header">
+ <?php if (!empty($search_box)): ?>
+ <div id="search-box"><?php print $search_box; ?></div>
+ <?php endif; ?>
+ </div> <!-- /header -->
+ <div id="breadcrumb"><?php print $breadcrumb; ?></div>
+
+ <div id="content">
+ <?php if (!empty($title)): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
+ <?php if (!empty($tabs)): ?><div class="tabs"><?php print $tabs; ?></div><?php endif; ?>
+ <?php if (!empty($messages)): print $messages; endif; ?>
+ <?php if (!empty($help)): print $help; endif; ?>
+ <div id="content-content" class="clear-block">
+ <?php print $content; ?>
+ </div> <!-- /content-content -->
+ </div> <!-- /content -->
+
+ <!--
+ See comment about $closure in main module.
+ print $closure;
+ -->
+
+ </div> <!-- /page -->
+</body>
+</html>
diff --git a/sites/all/modules/advanced_help/advanced_help.info b/sites/all/modules/advanced_help/advanced_help.info
new file mode 100644
index 000000000..4700487f0
--- /dev/null
+++ b/sites/all/modules/advanced_help/advanced_help.info
@@ -0,0 +1,18 @@
+name = Advanced help
+description = Provide extended help and documentation.
+core = 7.x
+
+attributions[click_icon][type] = 'asset'
+attributions[click_icon][exception] = '#2460769'
+attributions[click_icon][author] = 'Gisle Hannemyr'
+attributions[click_icon][hide] = TRUE
+attributions[click_icon][title] = 'click_icon.png'
+attributions[click_icon][license] = 'Creative Commons 0'
+attributions[click_icon][license_url] = 'https://creativecommons.org/publicdomain/zero/1.0/'
+
+; Information added by Drupal.org packaging script on 2015-05-25
+version = "7.x-1.3"
+core = "7.x"
+project = "advanced_help"
+datestamp = "1432557782"
+
diff --git a/sites/all/modules/advanced_help/advanced_help.install b/sites/all/modules/advanced_help/advanced_help.install
new file mode 100644
index 000000000..6c4455a04
--- /dev/null
+++ b/sites/all/modules/advanced_help/advanced_help.install
@@ -0,0 +1,61 @@
+<?php
+/**
+ * @file
+ * Contains install and update functions for advanced_help.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function advanced_help_uninstall() {
+ variable_del('advanced_help_last_cron');
+}
+
+/**
+ * Implements hook_schema().
+ */
+function advanced_help_schema() {
+ $schema['advanced_help_index'] = array(
+ 'description' => 'Stores search index correlations for advanced help topics.',
+ 'fields' => array(
+ 'sid' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The primary key to give to the search engine for this topic.',
+ 'no export' => TRUE,
+ ),
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'The module that owns this topic.',
+ ),
+ 'topic' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'The topic id.',
+ ),
+ 'language' => array(
+ 'type' => 'varchar',
+ 'length' => 12,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The language this search index relates to.',
+ ),
+ ),
+ 'primary key' => array('sid'),
+ 'indexes' => array('language' => array('language')),
+ 'foreign keys' => array(
+ 'system' => array(
+ 'table' => 'system',
+ 'columns' => array('name' => 'name'),
+ ),
+ ),
+ );
+
+ return $schema;
+}
diff --git a/sites/all/modules/advanced_help/advanced_help.module b/sites/all/modules/advanced_help/advanced_help.module
new file mode 100755
index 000000000..109bf5007
--- /dev/null
+++ b/sites/all/modules/advanced_help/advanced_help.module
@@ -0,0 +1,1173 @@
+<?php
+/**
+ * @file
+ * Pluggable system to provide advanced help facilities for Drupal and modules.
+ *
+ * Modules utilizing this help system should create a 'help' directory in their
+ * module. Inside that directory place MODULENAME.help.ini which will be
+ * formatted like this:
+ *
+ * @code
+ * [buses]
+ * title = "How buses are tied into the system"
+ * file = buses
+ *
+ * [TOPIC_ID]
+ * title = "Title of topic"
+ * file = filename of topic, without the .html extension
+ * weight = the importance of the topic on the index page
+ * parent = the optional topic parent to use in the breadcrumb.
+ * Can be either topic or module%topic
+ * @endcode
+ *
+ * All topics are addressed by the module that provides the topic, and the topic
+ * id. Links may be embedded as in the following example:
+ *
+ * @code
+ * $output .= theme('advanced_help_topic', array('module' => $module, 'topic' => $topic));
+ * @endcode
+ *
+ * Link to other topics using <a href="topic:module/topic">. (Using
+ * this format ensures the popup status remains consistent for all
+ * links.)
+ */
+
+/**
+ * Implements hook_menu().
+ *
+ * Strings in hook_help() should not be run through t().
+ */
+function advanced_help_menu() {
+ $help_exists = module_exists('help') ? TRUE : FALSE;
+ if ($help_exists) {
+ // Add tabs to core Help page to access Advanced Help.
+ $items['admin/help/tab1'] = array(
+ 'title' => 'Help',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => 0,
+ );
+
+ $items['admin/help/ah'] = array(
+ 'title' => 'Advanced Help',
+ 'page callback' => 'advanced_help_index_page',
+ 'access arguments' => array('view advanced help index'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 2,
+ );
+ }
+ else {
+ // Make Advanced Help the normal help.
+ $items['admin/help/ah'] = array(
+ 'title' => 'Help',
+ 'page callback' => 'advanced_help_index_page',
+ 'access arguments' => array('view advanced help index'),
+ 'type' => MENU_NORMAL_ITEM,
+ 'weight' => 9,
+ );
+ }
+ $items['help/ah/search/%'] = array(
+ 'title' => 'Search help',
+ 'page callback' => 'advanced_help_search_view',
+ 'page arguments' => array('advanced_help'),
+ 'access arguments' => array('view advanced help index'),
+ );
+
+ // View help topic.
+ $items['help/%/%'] = array(
+ 'page callback' => 'advanced_help_topic_page',
+ 'page arguments' => array(1, 2),
+ 'access arguments' => array('view advanced help topic'),
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function advanced_help_menu_alter(&$callbacks) {
+ // We need to fix the menu item provided by search module to restrict access.
+ $callbacks['search/advanced_help/%menu_tail']['access callback'] = 'user_access';
+ $callbacks['search/advanced_help/%menu_tail']['access arguments'] = array('view advanced help index');
+}
+
+/**
+ * Implements hook_theme().
+ */
+function advanced_help_theme() {
+ $hooks['advanced_help_topic'] = array(
+ 'variables' => array(
+ 'module' => NULL,
+ 'topic' => NULL,
+ 'type' => 'icon',
+ ),
+ );
+
+ $hooks['advanced_help_popup'] = array(
+ 'render element' => 'content',
+ 'template' => 'advanced-help-popup',
+ );
+
+ return $hooks;
+}
+
+/**
+ * Helper function to sort topics.
+ */
+function advanced_help_uasort($id_a, $id_b) {
+ $topics = advanced_help_get_topics();
+ list($module_a, $topic_a) = $id_a;
+ $a = $topics[$module_a][$topic_a];
+ list($module_b, $topic_b) = $id_b;
+ $b = $topics[$module_b][$topic_b];
+
+ $a_weight = isset($a['weight']) ? $a['weight'] : 0;
+ $b_weight = isset($b['weight']) ? $b['weight'] : 0;
+ if ($a_weight != $b_weight) {
+ return ($a_weight < $b_weight) ? -1 : 1;
+ }
+
+ if ($a['title'] != $b['title']) {
+ return ($a['title'] < $b['title']) ? -1 : 1;
+ }
+ return 0;
+}
+
+/**
+ * Helper function for grabbing search keys. Function is missing in D7.
+ *
+ * http://api.drupal.org/api/function/search_get_keys/6
+ */
+function advanced_help_search_get_keys() {
+ static $return;
+ if (!isset($return)) {
+ // Extract keys as remainder of path
+ // Note: support old GET format of searches for existing links.
+ $path = explode('/', $_GET['q'], 4);
+ $keys = empty($_REQUEST['keys']) ? '' : $_REQUEST['keys'];
+ $return = count($path) == 4 ? $path[3] : $keys;
+ }
+ return $return;
+}
+
+/**
+ * Page callback for advanced help search.
+ */
+function advanced_help_search_view() {
+ if (!module_exists('search')) {
+ return drupal_not_found();
+ }
+
+ $breadcrumb[] = advanced_help_l(t('Advanced help'), 'admin/help/ah');
+
+ if (!isset($_POST['form_id'])) {
+ $keys = advanced_help_search_get_keys();
+ // Only perform search if there is non-whitespace search term:
+ $results = '';
+ if (trim($keys)) {
+ $search_results = search_data($keys, 'advanced_help');
+ $search_results = drupal_render($search_results);
+ // Collect the search results:
+ $results = array(
+ '#type' => 'markup',
+ '#markup' => $search_results,
+ );
+ }
+
+ // Construct the search form.
+ $output['advanced_help_search_form'] = drupal_get_form('advanced_help_search_form', $keys);
+ $output['results'] = $results;
+
+ }
+ else {
+ $output = drupal_get_form('advanced_help_search_form', empty($keys) ? '' : $keys);
+ }
+
+ $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
+ if ($popup) {
+ // Prevent devel module from spewing.
+ $GLOBALS['devel_shutdown'] = FALSE;
+ // Suppress admin_menu.
+ module_invoke('admin_menu', 'suppress');
+ drupal_set_breadcrumb(array_reverse($breadcrumb));
+ print theme('advanced_help_popup', array('content' => $output));
+ return;
+ }
+
+ $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
+ drupal_set_breadcrumb($breadcrumb);
+ return $output;
+}
+
+/**
+ * Page callback to view the advanced help topic index.
+ *
+ * @param string $module
+ * Name of the module.
+ *
+ * @return array
+ * Returns module index.
+ */
+function advanced_help_index_page($module = '') {
+ $topics = advanced_help_get_topics();
+ $settings = advanced_help_get_settings();
+
+ $output = array();
+ // Print a search widget.
+ $output['advanced_help_search'] = module_exists('search') ? drupal_get_form('advanced_help_search_form') : array('#markup' => t('Enable the search module to search help.'));
+
+ $breadcrumb = array();
+ if ($module) {
+ if (empty($topics[$module])) {
+ return drupal_not_found();
+ }
+
+ advanced_help_get_topic_hierarchy($topics);
+ $items = advanced_help_get_tree($topics, $topics[$module]['']['children']);
+
+ $breadcrumb[] = advanced_help_l(t('Advanced help'), 'admin/help/ah');
+
+ drupal_set_title(t('@module help index', array('@module' => advanced_help_get_module_name($module))));
+ $output['items-module'] = array(
+ '#theme' => 'item_list',
+ '#items' => $items,
+ );
+ }
+ else {
+ // Print a module index.
+ $modules = array();
+ $result = db_query('SELECT * FROM {system}');
+ foreach ($result as $info) {
+ $module_info = unserialize($info->info);
+ $modules[$info->name] = isset($module_info['name']) ? $module_info['name'] : $info->name;
+ }
+
+ asort($modules);
+
+ $items = array();
+ foreach ($modules as $module => $module_name) {
+ if (!empty($topics[$module]) && empty($settings[$module]['hide'])) {
+ if (isset($settings[$module]['index name'])) {
+ $name = $settings[$module]['index name'];
+ }
+ elseif (isset($settings[$module]['name'])) {
+ $name = $settings[$module]['name'];
+ }
+ else {
+ $name = t($module_name);
+ }
+ $items[] = advanced_help_l($name, "admin/help/ah/$module");
+ }
+ }
+
+ drupal_set_title(t('Advanced help'));
+ $output['items-nomodule'] = array(
+ '#theme' => 'item_list',
+ '#items' => $items,
+ );
+ }
+
+ $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
+ if ($popup) {
+ // Prevent devel module from spewing.
+ $GLOBALS['devel_shutdown'] = FALSE;
+ // Suppress admin_menu.
+ module_invoke('admin_menu', 'suppress');
+ drupal_set_breadcrumb(array_reverse($breadcrumb));
+ print theme('advanced_help_popup', array('content' => $output));
+ return;
+ }
+
+ $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
+ drupal_set_breadcrumb($breadcrumb);
+ return $output;
+}
+
+/**
+ * Build a tree of advanced help topics.
+ *
+ * @param array $topics
+ * Topics.
+ * @param array $topic_ids
+ * Topic Ids.
+ * @param int $max_depth
+ * Maximum depth for subtopics.
+ * @param int $depth
+ * Default depth for subtopics.
+ *
+ * @return array
+ * Returns list of topics/subtopics.
+ */
+function advanced_help_get_tree($topics, $topic_ids, $max_depth = -1, $depth = 0) {
+ uasort($topic_ids, 'advanced_help_uasort');
+ $items = array();
+ foreach ($topic_ids as $info) {
+ list($module, $topic) = $info;
+ $item = advanced_help_l($topics[$module][$topic]['title'], "help/$module/$topic");
+ if (!empty($topics[$module][$topic]['children']) && ($max_depth == -1 || $depth < $max_depth)) {
+ $item .= theme('item_list', array(
+ 'items' => advanced_help_get_tree($topics, $topics[$module][$topic]['children'], $max_depth, $depth + 1),
+ ));
+ }
+
+ $items[] = $item;
+ }
+
+ return $items;
+}
+
+/**
+ * Build a hierarchy for a single module's topics.
+ */
+function advanced_help_get_topic_hierarchy(&$topics) {
+ foreach ($topics as $module => $module_topics) {
+ foreach ($module_topics as $topic => $info) {
+ $parent_module = $module;
+ // We have a blank topic that we don't want parented to itself.
+ if (!$topic) {
+ continue;
+ }
+
+ if (empty($info['parent'])) {
+ $parent = '';
+ }
+ elseif (strpos($info['parent'], '%')) {
+ list($parent_module, $parent) = explode('%', $info['parent']);
+ if (empty($topics[$parent_module][$parent])) {
+ // If it doesn't exist, top level.
+ $parent = '';
+ }
+ }
+ else {
+ $parent = $info['parent'];
+ if (empty($module_topics[$parent])) {
+ // If it doesn't exist, top level.
+ $parent = '';
+ }
+ }
+
+ if (!isset($topics[$parent_module][$parent]['children'])) {
+ $topics[$parent_module][$parent]['children'] = array();
+ }
+ $topics[$parent_module][$parent]['children'][] = array($module, $topic);
+ $topics[$module][$topic]['_parent'] = array($parent_module, $parent);
+ }
+ }
+}
+
+/**
+ * Implements hook_form_system_modules_alter().
+ *
+ * Add advanced help links to the modules page.
+ */
+function advanced_help_form_system_modules_alter(&$form, &$form_state) {
+ if (!isset($form['modules'])) {
+ return;
+ }
+ $advanced_help_modules = drupal_map_assoc(array_keys(advanced_help_get_topics()));
+ foreach (element_children($form['modules']) as $group) {
+ foreach (element_children($form['modules'][$group]) as $module) {
+ if (isset($advanced_help_modules[$module])) {
+ $form['modules'][$group][$module]['links']['help'] = array(
+ '#type' => 'link',
+ '#title' => t('Help'),
+ '#href' => "admin/help/ah/$module",
+ '#options' => array(
+ 'attributes' => array(
+ 'class' => array('module-link', 'module-link-help'),
+ 'title' => t('Help'),
+ ),
+ ),
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Form builder callback to build the search form.
+ *
+ * Load search/search.pages so that its template preprocess functions are
+ * visible and can be invoked.
+ */
+function advanced_help_search_form($form, &$form_state, $keys = '') {
+ module_load_include('inc', 'search', 'search.pages');
+ $form = search_form($form, $form_state, 'admin/help/ah', $keys, 'advanced_help', t('Search help'));
+
+ $form['basic']['inline']['submit']['#validate'] = array('search_form_validate');
+ $form['basic']['inline']['submit']['#submit'] = array('advanced_help_search_form_submit');
+
+ return $form;
+}
+
+/**
+ * Process a search form submission.
+ */
+function advanced_help_search_form_submit($form, &$form_state) {
+ $keys = empty($form_state['values']['processed_keys']) ? $form_state['values']['keys'] : $form_state['values']['processed_keys'];
+ if ($keys == '') {
+ form_set_error('keys', t('Please enter some keywords.'));
+ return;
+ }
+
+ $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
+
+ if ($popup) {
+ $form_state['redirect'] = array('help/ah/search/' . $keys, array('query' => array('popup' => 'true')));
+ }
+ else {
+ $form_state['redirect'] = 'help/ah/search/' . $keys;
+ }
+}
+
+/**
+ * Small helper function to get a module's proper name.
+ *
+ * @param string $module
+ * Name of the module.
+ *
+ * @return string
+ * Returns module's descriptive name.
+ */
+function advanced_help_get_module_name($module) {
+ $settings = advanced_help_get_settings();
+ if (isset($settings[$module]['name'])) {
+ $name = $settings[$module]['name'];
+ }
+ else {
+ $info = db_query("SELECT s.info FROM {system} s WHERE s.name = :name",
+ array(':name' => $module))
+ ->fetchField();
+ $info = unserialize($info);
+ $name = t($info['name']);
+ }
+ return $name;
+}
+
+/**
+ * Page callback to view a help topic.
+ */
+function advanced_help_topic_page($module, $topic) {
+ $info = advanced_help_get_topic($module, $topic);
+ if (!$info) {
+ return drupal_not_found();
+ }
+
+ $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
+
+ drupal_set_title($info['title']);
+
+ // Set up breadcrumb.
+ $breadcrumb = array();
+
+ $parent = $info;
+ $pmodule = $module;
+
+ // Loop checker.
+ $checked = array();
+ while (!empty($parent['parent'])) {
+ if (strpos($parent['parent'], '%')) {
+ list($pmodule, $ptopic) = explode('%', $parent['parent']);
+ }
+ else {
+ $ptopic = $parent['parent'];
+ }
+
+ if (!empty($checked[$pmodule][$ptopic])) {
+ break;
+ }
+ $checked[$pmodule][$ptopic] = TRUE;
+
+ $parent = advanced_help_get_topic($pmodule, $ptopic);
+ if (!$parent) {
+ break;
+ }
+
+ $breadcrumb[] = advanced_help_l($parent['title'], "help/$pmodule/$ptopic");
+ }
+
+ $breadcrumb[] = advanced_help_l(advanced_help_get_module_name($pmodule), "admin/help/ah/$pmodule");
+ $breadcrumb[] = advanced_help_l(t('Help'), "admin/help/ah");
+
+ $output = advanced_help_view_topic($module, $topic, $popup);
+ if (empty($output)) {
+ $output = t('Missing help topic.');
+ }
+
+ if ($popup) {
+ // Prevent devel module from spewing.
+ $GLOBALS['devel_shutdown'] = FALSE;
+ // Suppress admin_menu.
+ module_invoke('admin_menu', 'suppress');
+ drupal_set_breadcrumb(array_reverse($breadcrumb));
+ print theme('advanced_help_popup', array('content' => $output));
+ return;
+ }
+
+ drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help.css');
+ $breadcrumb[] = l(t('Home'), '');
+ drupal_set_breadcrumb(array_reverse($breadcrumb));
+ return $output;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function advanced_help_permission() {
+ return array(
+ 'view advanced help topic' => array('title' => t('View help topics')),
+ 'view advanced help popup' => array('title' => t('View help popups')),
+ 'view advanced help index' => array('title' => t('View help index')),
+ );
+}
+
+/**
+ * Display a help icon with a link to view the topic in a popup.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - module: The module that owns this help topic.
+ * - topic: The identifier for the topic
+ * - type
+ * - 'icon' to display the question mark icon
+ * - 'title' to display the topic's title
+ * - any other text to display the text. Be sure to t() it!
+ */
+function theme_advanced_help_topic($variables) {
+ $module = $variables['module'];
+ $topic = $variables['topic'];
+ $type = $variables['type'];
+
+ $info = advanced_help_get_topic($module, $topic);
+ if (!$info) {
+ return;
+ }
+
+ switch ($type) {
+ case 'icon':
+ $text = '<span>' . t('Help') . '</span>';
+ $class = 'advanced-help-link';
+ break;
+
+ case 'title':
+ $text = $info['title'];
+ $class = 'advanced-help-title';
+ break;
+
+ default:
+ $class = 'advanced-help-title';
+ $text = $type;
+ break;
+ }
+
+ if (user_access('view advanced help popup')) {
+ drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help-icon.css');
+ return l($text, "help/$module/$topic", array(
+ 'attributes' => array(
+ 'class' => $class,
+ 'onclick' => "var w=window.open(this.href, 'advanced_help_window', 'width=" . $info['popup width'] . ",height=" . $info['popup height'] . ",scrollbars,resizable'); w.focus(); return false;",
+ 'title' => $info['title'],
+ ),
+ 'query' => array('popup' => TRUE),
+ 'html' => TRUE)
+ );
+ }
+ elseif (user_access('view advanced help topic')) {
+ return l($text, "help/$module/$topic", array(
+ 'attributes' => array(
+ 'class' => $class,
+ 'title' => $info['title'],
+ ),
+ 'html' => TRUE)
+ );
+ }
+}
+
+/**
+ * Load and render a help topic.
+ */
+function advanced_help_get_topic_filename($module, $topic) {
+ $info = advanced_help_get_topic_file_info($module, $topic);
+ if ($info) {
+ return "./$info[path]/$info[file]";
+ }
+}
+/**
+ * Load and render a help topic.
+ */
+function advanced_help_get_topic_file_info($module, $topic) {
+ global $language;
+
+ $info = advanced_help_get_topic($module, $topic);
+ if (empty($info)) {
+ return;
+ }
+
+ // Search paths:
+ $paths = array(
+ // Allow theme override.
+ path_to_theme() . '/help',
+ // Translations.
+ drupal_get_path('module', $module) . "/translations/help/$language->language",
+ // In same directory as .inc file.
+ $info['path'],
+ );
+
+ foreach ($paths as $path) {
+ if (file_exists("./$path/$info[file]")) {
+ return array('path' => $path, 'file' => $info['file']);
+ }
+ }
+}
+
+/**
+ * Load and render a help topic.
+ *
+ * @param string $module
+ * Name of the module.
+ * @param string $topic
+ * Name of the topic.
+ * @param bool $popup
+ * Whether to show in popup or not.
+ *
+ * @return string
+ * Returns formatted topic.
+ */
+function advanced_help_view_topic($module, $topic, $popup = FALSE) {
+ $file_info = advanced_help_get_topic_file_info($module, $topic);
+ if ($file_info) {
+ $info = advanced_help_get_topic($module, $topic);
+ $file = "./$file_info[path]/$file_info[file]";
+
+ $output = file_get_contents($file);
+ if (isset($info['readme file']) && $info['readme file']) {
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ if ('md' == $ext && module_exists('markdown')) {
+ $filters = module_invoke('markdown', 'filter_info');
+ $md_info = $filters['filter_markdown'];
+
+ if (function_exists($md_info['process callback'])) {
+ $function = $md_info['process callback'];
+ $output = '<div class="advanced-help-topic">' . filter_xss_admin($function($output, NULL)) . '</div>';
+ }
+ else {
+ $output = '<div class="advanced-help-topic"><pre class="readme">' . check_plain($output) . '</pre></div>';
+ }
+ }
+ else {
+ $readme = '';
+ if ('md' == $ext) {
+ $readme .=
+ '<p>' .
+ t('If you install the !module module, the text below will be filtered by the module, producing rich text.',
+ array('!module' => l(t('Markdown filter'),
+ 'https://www.drupal.org/project/markdown',
+ array('attributes' => array('title' => t('Link to project.')))))) . '</p>';
+ }
+ $readme .=
+ '<div class="advanced-help-topic"><pre class="readme">' . check_plain($output) . '</pre></div>';
+ $output = $readme;
+ }
+ return $output;
+ }
+
+ // Make some exchanges. The strtr is because url() translates $ into %24
+ // but we need to change it back for the regex replacement.
+ //
+ // Change 'topic:' to the URL for another help topic.
+ if ($popup) {
+ $output = preg_replace('/href="topic:([^"]+)"/', 'href="' . strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')) . '"', $output);
+ $output = preg_replace('/src="topic:([^"]+)"/', 'src="' . strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')) . '"', $output);
+ $output = preg_replace('/&topic:([^"]+)&/', strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')), $output);
+ }
+ else {
+ $output = preg_replace('/href="topic:([^"]+)"/', 'href="' . strtr(url('help/$1'), array('%24' => '$')) . '"', $output);
+ $output = preg_replace('/src="topic:([^"]+)"/', 'src="' . strtr(url('help/$1'), array('%24' => '$')) . '"', $output);
+ $output = preg_replace('/&topic:([^"]+)&/', strtr(url('help/$1'), array('%24' => '$')), $output);
+ }
+
+ global $base_path;
+
+ // Change 'path:' to the URL to the base help directory.
+ $output = preg_replace('/href="path:([^"]+)"/', 'href="' . $base_path . $info['path'] . '/$1"', $output);
+ $output = preg_replace('/src="path:([^"]+)"/', 'src="' . $base_path . $info['path'] . '/$1"', $output);
+ $output = str_replace('&path&', $base_path . $info['path'] . '/', $output);
+
+ // Change 'trans_path:' to the URL to the actual help directory.
+ $output = preg_replace('/href="trans_path:([^"]+)"/', 'href="' . $base_path . $file_info['path'] . '/$1"', $output);
+ $output = preg_replace('/src="trans_path:([^"]+)"/', 'src="' . $base_path . $file_info['path'] . '/$1"', $output);
+ $output = str_replace('&trans_path&', $base_path . $file_info['path'] . '/', $output);
+
+ // Change 'base_url:' to the URL to the site.
+ $output = preg_replace('/href="base_url:([^"]+)"/', 'href="' . strtr(url('$1'), array('%24' => '$')) . '"', $output);
+ $output = preg_replace('/src="base_url:([^"]+)"/', 'src="' . strtr(url('$1'), array('%24' => '$')) . '"', $output);
+ $output = preg_replace('/&base_url&([^"]+)"/', strtr(url('$1'), array('%24' => '$')) . '"', $output);
+
+ // Run the line break filter if requested.
+ if (!empty($info['line break'])) {
+ // Remove the header since it adds an extra <br /> to the filter.
+ $output = preg_replace('/^<!--[^\n]*-->\n/', '', $output);
+
+ $output = _filter_autop($output);
+ }
+
+ if (!empty($info['navigation'])) {
+ $topics = advanced_help_get_topics();
+ advanced_help_get_topic_hierarchy($topics);
+ if (!empty($topics[$module][$topic]['children'])) {
+ $items = advanced_help_get_tree($topics, $topics[$module][$topic]['children']);
+ $output .= theme('item_list', array('items' => $items));
+ }
+
+ list($parent_module, $parent_topic) = $topics[$module][$topic]['_parent'];
+ if ($parent_topic) {
+ $parent = $topics[$module][$topic]['_parent'];
+ $up = "help/$parent[0]/$parent[1]";
+ }
+ else {
+ $up = "admin/help/ah/$module";
+ }
+
+ $siblings = $topics[$parent_module][$parent_topic]['children'];
+ uasort($siblings, 'advanced_help_uasort');
+ $prev = $next = NULL;
+ $found = FALSE;
+ foreach ($siblings as $sibling) {
+ list($sibling_module, $sibling_topic) = $sibling;
+ if ($found) {
+ $next = $sibling;
+ break;
+ }
+ if ($sibling_module == $module && $sibling_topic == $topic) {
+ $found = TRUE;
+ continue;
+ }
+ $prev = $sibling;
+ }
+
+ if ($prev || $up || $next) {
+ $navigation = '<div class="help-navigation clear-block">';
+
+ if ($prev) {
+ $navigation .= advanced_help_l('«« ' . $topics[$prev[0]][$prev[1]]['title'], "help/$prev[0]/$prev[1]", array('attributes' => array('class' => 'help-left')));
+ }
+ if ($up) {
+ $navigation .= advanced_help_l(t('Up'), $up, array('attributes' => array('class' => $prev ? 'help-up' : 'help-up-noleft')));
+ }
+ if ($next) {
+ $navigation .= advanced_help_l($topics[$next[0]][$next[1]]['title'] . ' »»', "help/$next[0]/$next[1]", array('attributes' => array('class' => 'help-right')));
+ }
+
+ $navigation .= '</div>';
+
+ $output .= $navigation;
+ }
+ }
+
+ if (!empty($info['css'])) {
+ drupal_add_css($info['path'] . '/' . $info['css']);
+ }
+
+ $output = '<div class="advanced-help-topic">' . $output . '</div>';
+ drupal_alter('advanced_help_topic', $output, $popup);
+
+ return $output;
+ }
+}
+
+/**
+ * Get the information for a single help topic.
+ */
+function advanced_help_get_topic($module, $topic) {
+ $topics = advanced_help_get_topics();
+ if (!empty($topics[$module][$topic])) {
+ return $topics[$module][$topic];
+ }
+}
+
+/**
+ * Search the system for all available help topics.
+ */
+function advanced_help_get_topics() {
+ $ini = _advanced_help_parse_ini();
+ return $ini['topics'];
+}
+
+/**
+ * Returns advanced help settings.
+ */
+function advanced_help_get_settings() {
+ $ini = _advanced_help_parse_ini();
+ return $ini['settings'];
+}
+
+/**
+ * Funtion to parse ini / txt files.
+ */
+function _advanced_help_parse_ini() {
+ global $language;
+ static $ini = NULL;
+
+ if (!isset($ini)) {
+ $cache = cache_get('advanced_help_ini:' . $language->language);
+ if ($cache) {
+ $ini = $cache->data;
+ }
+ }
+
+ if (!isset($ini)) {
+ $ini = array('topics' => array(), 'settings' => array());
+
+ $help_path = drupal_get_path('module', 'advanced_help') . '/modules';
+ foreach (array_merge(module_list(), list_themes()) as $plugin) {
+ $module = is_string($plugin) ? $plugin : $plugin->name;
+ $module_path = drupal_get_path(is_string($plugin) ? 'module' : 'theme', $module);
+ $info = array();
+ if (file_exists("$module_path/help/$module.help.ini")) {
+ $path = "$module_path/help";
+ $info = parse_ini_file("./$module_path/help/$module.help.ini", TRUE);
+ }
+ elseif (file_exists("$help_path/$module/$module.help.ini")) {
+ $path = "$help_path/$module";
+ $info = parse_ini_file("./$help_path/$module/$module.help.ini", TRUE);
+ }
+ elseif (!file_exists("$module_path/help")) {
+ // Look for one or more README files.
+ $files = file_scan_directory("./$module_path",
+ '/^(readme).*\.(txt|md)$/i', array('recurse' => FALSE));
+ $path = "./$module_path";
+ foreach ($files as $name => $fileinfo) {
+ $info[$fileinfo->filename] = array(
+ 'line break' => TRUE,
+ 'readme file' => TRUE,
+ 'file' => $fileinfo->filename,
+ 'title' => $fileinfo->name,
+ );
+ }
+ }
+
+ if (!empty($info)) {
+ // Get translated titles:
+ $translation = array();
+ if (file_exists("$module_path/translations/help/$language->language/$module.help.ini")) {
+ $translation = parse_ini_file("$module_path/translations/help/$language->language/$module.help.ini", TRUE);
+ }
+
+ $ini['settings'][$module] = array();
+ if (!empty($info['advanced help settings'])) {
+ $ini['settings'][$module] = $info['advanced help settings'];
+ unset($info['advanced help settings']);
+
+ // Check translated strings for translatable global settings.
+ if (isset($translation['advanced help settings']['name'])) {
+ $ini['settings']['name'] = $translation['advanced help settings']['name'];
+ }
+ if (isset($translation['advanced help settings']['index name'])) {
+ $ini['settings']['index name'] = $translation['advanced help settings']['index name'];
+ }
+
+ }
+
+ foreach ($info as $name => $topic) {
+ // Each topic should have a name, a title, a file and path.
+ $file = !empty($topic['file']) ? $topic['file'] : $name;
+ $ini['topics'][$module][$name] = array(
+ 'name' => $name,
+ 'module' => $module,
+ 'ini' => $topic,
+ 'title' => !empty($translation[$name]['title']) ? $translation[$name]['title'] : $topic['title'],
+ 'weight' => isset($topic['weight']) ? $topic['weight'] : 0,
+ 'parent' => isset($topic['parent']) ? $topic['parent'] : 0,
+ 'popup width' => isset($topic['popup width']) ? $topic['popup width'] : 500,
+ 'popup height' => isset($topic['popup height']) ? $topic['popup height'] : 500,
+ // Require extension.
+ 'file' => isset($topic['readme file']) ? $file : $file . '.html',
+ // Not in .ini file.
+ 'path' => $path,
+ 'line break' => isset($topic['line break']) ? $topic['line break'] : (isset($ini['settings'][$module]['line break']) ? $ini['settings'][$module]['line break'] : FALSE),
+ 'navigation' => isset($topic['navigation']) ? $topic['navigation'] : (isset($ini['settings'][$module]['navigation']) ? $ini['settings'][$module]['navigation'] : TRUE),
+ 'css' => isset($topic['css']) ? $topic['css'] : (isset($ini['settings'][$module]['css']) ? $ini['settings'][$module]['css'] : NULL),
+ 'readme file' => isset($topic['readme file']) ? $topic['readme file'] : FALSE,
+ );
+ }
+ }
+ }
+ drupal_alter('advanced_help_topic_info', $ini);
+
+ cache_set('advanced_help_ini:' . $language->language, $ini);
+ }
+ return $ini;
+}
+
+/**
+ * Implements hook_search_info().
+ *
+ * Returns title for the tab on search page & path component after 'search/'.
+ */
+function advanced_help_search_info() {
+ return array(
+ 'title' => t('Help'),
+ 'path' => 'advanced_help',
+ );
+}
+
+/**
+ * Implements hook_search_execute().
+ */
+function advanced_help_search_execute($keys = NULL) {
+ $topics = advanced_help_get_topics();
+
+ $query = db_select('search_index', 'i', array('target' => 'slave'))
+ ->extend('SearchQuery')
+ ->extend('PagerDefault');
+ $query->join('advanced_help_index', 'ahi', 'i.sid = ahi.sid');
+ $query->searchExpression($keys, 'help');
+
+ // Only continue if the first pass query matches.
+ if (!$query->executeFirstPass()) {
+ return array();
+ }
+
+ $results = array();
+
+ $find = $query->execute();
+ foreach ($find as $item) {
+ $sids[] = $item->sid;
+ }
+
+ $query = db_select('advanced_help_index', 'ahi');
+ $result = $query
+ ->fields('ahi')
+ ->condition('sid', $sids, 'IN')
+ ->execute();
+
+ foreach ($result as $sid) {
+ // Guard against removed help topics that are still indexed.
+ if (empty($topics[$sid->module][$sid->topic])) {
+ continue;
+ }
+ $info = $topics[$sid->module][$sid->topic];
+ $text = advanced_help_view_topic($sid->module, $sid->topic);
+ $results[] = array(
+ 'link' => advanced_help_url("help/$sid->module/$sid->topic"),
+ 'title' => $info['title'],
+ 'snippet' => search_excerpt($keys, $text),
+ );
+ }
+ return $results;
+}
+
+/**
+ * Implements hook_search_reset().
+ */
+function advanced_help_search_reset() {
+ variable_del('advanced_help_last_cron');
+}
+
+/**
+ * Implements hook_search_status().
+ */
+function advanced_help_search_status() {
+ $topics = advanced_help_get_topics();
+ $total = 0;
+ foreach ($topics as $module => $module_topics) {
+ foreach ($module_topics as $topic => $info) {
+ $file = advanced_help_get_topic_filename($module, $topic);
+ if ($file) {
+ $total++;
+ }
+ }
+ }
+
+ $last_cron = variable_get('advanced_help_last_cron', array('time' => 0));
+ $indexed = 0;
+ if ($last_cron['time'] != 0) {
+ $indexed = db_query("SELECT COUNT(*) FROM {search_dataset} sd WHERE sd.type = 'help' AND sd.sid IS NOT NULL AND sd.reindex = 0")->fetchField();
+ }
+ return array('remaining' => $total - $indexed, 'total' => $total);
+}
+
+/**
+ * Gets search id for each topic.
+ *
+ * Get or create an sid (search id) that correlates to each topic for
+ * the search system.
+ */
+function advanced_help_get_sids(&$topics) {
+ global $language;
+ $result = db_query("SELECT * FROM {advanced_help_index} WHERE language = :language",
+ array(':language' => $language->language));
+ foreach ($result as $sid) {
+ if (empty($topics[$sid->module][$sid->topic])) {
+ db_query("DELETE FROM {advanced_help_index} WHERE sid = :sid",
+ array(':sid' => $sid->sid));
+ }
+ else {
+ $topics[$sid->module][$sid->topic]['sid'] = $sid->sid;
+ }
+ }
+}
+
+/**
+ * Implements hook_update_index().
+ */
+function advanced_help_update_index() {
+ global $language;
+
+ // If we got interrupted by limit, this will contain the last module
+ // and topic we looked at.
+ $last = variable_get('advanced_help_last_cron', array('time' => 0));
+ $limit = intval(variable_get('search_cron_limit', 100));
+ $topics = advanced_help_get_topics();
+ advanced_help_get_sids($topics);
+
+ $count = 0;
+
+ foreach ($topics as $module => $module_topics) {
+ // Fast forward if necessary.
+ if (!empty($last['module']) && $last['module'] != $module) {
+ continue;
+ }
+
+ foreach ($module_topics as $topic => $info) {
+ // Fast forward if necessary.
+ if (!empty($last['topic']) && $last['topic'] != $topic) {
+ continue;
+ }
+
+ // If we've been looking to catch up, and we have, reset so we
+ // stop fast forwarding.
+ if (!empty($last['module'])) {
+ unset($last['topic']);
+ unset($last['module']);
+ }
+
+ $file = advanced_help_get_topic_filename($module, $topic);
+ if ($file && (empty($info['sid']) || filemtime($file) > $last['time'])) {
+ if (empty($info['sid'])) {
+ $info['sid'] = db_insert('advanced_help_index')
+ ->fields(array(
+ 'module' => $module,
+ 'topic' => $topic,
+ 'language' => $language->language,
+ ))
+ ->execute();
+ }
+
+ search_index($info['sid'], 'help', '<h1>' . $info['title'] . '</h1>' . file_get_contents($file));
+ $count++;
+ if ($count >= $limit) {
+ $last['topic'] = $topic;
+ $last['module'] = $module;
+ // Don't change time if we stop.
+ variable_set('advanced_help_last_cron', $last);
+ return;
+ }
+ }
+ }
+ }
+
+ variable_set('advanced_help_last_cron', array('time' => time()));
+}
+
+/**
+ * Fill in a bunch of page variables for our specialized popup page.
+ */
+function template_preprocess_advanced_help_popup(&$variables) {
+ // Add favicon.
+ if (theme_get_setting('toggle_favicon')) {
+ drupal_add_html_head('<link rel="shortcut icon" href="' . check_url(theme_get_setting('favicon')) . '" type="image/x-icon" />');
+ }
+
+ global $theme;
+ // Construct page title.
+ if (drupal_get_title()) {
+ $head_title = array(
+ strip_tags(drupal_get_title()),
+ variable_get('site_name', 'Drupal'),
+ );
+ }
+ else {
+ $head_title = array(variable_get('site_name', 'Drupal'));
+ if (variable_get('site_slogan', '')) {
+ $head_title[] = variable_get('site_slogan', '');
+ }
+ }
+
+ drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help-popup.css');
+ drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help.css');
+
+ $variables['head_title'] = implode(' | ', $head_title);
+ $variables['base_path'] = base_path();
+ $variables['front_page'] = url();
+ $variables['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb()));
+ $variables['feed_icons'] = drupal_get_feeds();
+ $variables['head'] = drupal_get_html_head();
+ $variables['language'] = $GLOBALS['language'];
+ $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
+ $variables['logo'] = theme_get_setting('logo');
+ $variables['messages'] = theme('status_messages');
+ $variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
+ $variables['css'] = drupal_add_css();
+ $css = drupal_add_css();
+
+ // Remove theme css.
+ foreach ($css as $key => $value) {
+ if ($value['group'] == CSS_THEME) {
+ $exclude[$key] = FALSE;
+ }
+ }
+ $css = array_diff_key($css, $exclude);
+
+ $variables['styles'] = drupal_get_css($css);
+ $variables['scripts'] = drupal_get_js();
+ $variables['title'] = drupal_get_title();
+
+ // This function can be called either with a render array or
+ // an already rendered string.
+ if (is_array($variables['content'])) {
+ $variables['content'] = drupal_render($variables['content']);
+ }
+ // Closure should be filled last.
+ // There has never been a theme hook for closure (going back to
+ // first release 2008-04-17). Unable to figure out its purpose.
+ // $variables['closure'] = theme('closure');
+}
+
+/**
+ * Format a link but preserve popup identity.
+ */
+function advanced_help_l($text, $dest, $options = array()) {
+ $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
+ if ($popup) {
+ if (empty($options['query'])) {
+ $options['query'] = array();
+ }
+
+ if (is_array($options['query'])) {
+ $options['query'] += array('popup' => TRUE);
+ }
+ else {
+ $options['query'] += '&popup=TRUE';
+ }
+ }
+
+ return l($text, $dest, $options);
+}
+
+/**
+ * Format a URL but preserve popup identity.
+ */
+function advanced_help_url($dest, $options = array()) {
+ $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
+ if ($popup) {
+ if (empty($options['query'])) {
+ $options['query'] = array();
+ }
+
+ $options['query'] += array('popup' => TRUE);
+ }
+
+ return url($dest, $options);
+}
diff --git a/sites/all/modules/advanced_help/help-icon.css b/sites/all/modules/advanced_help/help-icon.css
new file mode 100644
index 000000000..e6e462581
--- /dev/null
+++ b/sites/all/modules/advanced_help/help-icon.css
@@ -0,0 +1,18 @@
+
+.advanced-help-link {
+ background: transparent url('help.png') no-repeat top left;
+ background-position: 0px 0px;
+ display: inline-block;
+ height: 12px;
+ margin-top: 2px;
+ padding: 0px;
+ width: 12px;
+}
+
+.advanced-help-link span {
+ display: none;
+}
+
+.advanced-help-link:hover {
+ background-position: 0px -12px;
+}
diff --git a/sites/all/modules/advanced_help/help-popup.css b/sites/all/modules/advanced_help/help-popup.css
new file mode 100644
index 000000000..c4fc2d9de
--- /dev/null
+++ b/sites/all/modules/advanced_help/help-popup.css
@@ -0,0 +1,125 @@
+
+body {
+ background: #edf5fa;
+ color: #494949;
+ font: 12px/170% Verdana, sans-serif;
+ margin: 0;
+ padding: 0;
+}
+
+input {
+ color: #494949;
+ font: 12px/100% Verdana, sans-serif;
+}
+
+textarea,
+select {
+ color: #494949;
+ font: 12px/160% Verdana, sans-serif;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: Helvetica, Arial, sans-serif;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+
+h1 {
+ font-size: 170%;
+}
+
+h2 {
+ font-size: 160%;
+ line-height: 130%;
+}
+
+h3 {
+ font-size: 140%;
+}
+
+h4 {
+ font-size: 130%;
+}
+
+h5 {
+ font-size: 120%;
+}
+
+h6 {
+ font-size: 110%;
+}
+
+ul,
+quote,
+code,
+fieldset {
+ margin: .5em 0;
+}
+
+p {
+ margin: 0.6em 0 1.2em;
+ padding: 0;
+}
+
+a:link,
+a:visited {
+ color: #027ac6;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #0062a0;
+ text-decoration: underline;
+}
+
+a:active,
+a.active {
+ color: #5895be;
+}
+
+hr {
+ background: #5294c1;
+ border: none;
+ height: 1px;
+ margin: 0;
+ padding: 0;
+}
+
+ol li,
+ul li {
+ margin: 0.4em 0 0.4em .5em; /* LTR */
+}
+
+#content {
+ margin: .5em 1em 1em 1em;
+}
+
+#content #page-title {
+ padding-bottom: .5em;
+}
+
+div#breadcrumb {
+ background-color: white;
+ border-bottom: 1px solid #ccc;
+ height: 2em;
+ padding-left: 1em;
+}
+
+div#breadcrumb .breadcrumb {
+ margin: 0;
+ padding: 0;
+}
+
+pre {
+ background: #f1f1f1;
+ border: 1px solid #ccc;
+ display: block;
+ margin: 1em;
+ padding: .2em;
+}
diff --git a/sites/all/modules/advanced_help/help.css b/sites/all/modules/advanced_help/help.css
new file mode 100644
index 000000000..8190c8e53
--- /dev/null
+++ b/sites/all/modules/advanced_help/help.css
@@ -0,0 +1,113 @@
+.advanced-help-topic pre {
+ background: #f1f1f1;
+ border: 1px solid #ccc;
+ display: block;
+ margin: 1em;
+ padding: .4em;
+}
+
+.advanced-help-topic p.bob {
+ background: #f1f1f1;
+ border: 1px solid #ccc;
+ display: block;
+ margin: 1em;
+ padding: .4em;
+}
+
+.advanced-help-topic pre.readme {
+ background: white;
+ border: none;
+ margin: 0em;
+ padding: .4em;
+}
+
+.advanced-help-topic h3,
+.advanced-help-topic h4,
+.advanced-help-topic h5,
+.advanced-help-topic h6,
+.advanced-help-topic dt {
+ font-weight: bold;
+}
+
+.advanced-help-topic li h3,
+.advanced-help-topic li h4,
+.advanced-help-topic li h5,
+.advanced-help-topic li h6 {
+ font-weight: normal;
+}
+
+.attribution {
+ font-size: 0.8em;
+ color: #aaa;
+}
+
+.help-left {
+ display: block;
+ float: left; /* LTR */
+ text-align: left;
+ width: 42%;
+}
+
+.help-leftalign {
+ text-align: left;
+}
+
+.help-up {
+ display: block;
+ float: left; /* LTR */
+ margin: 0 5%;
+ text-align: center;
+ width: 10%;
+}
+
+.help-up-noleft {
+ display: block;
+ float: left; /* LTR */
+ margin: 0 5%;
+ text-align: right;
+ width: 42%;
+}
+
+.help-right {
+ display: block;
+ float: right;
+ text-align: right;
+ width: 42%;
+}
+
+.help-box {
+ margin: .5em;
+}
+
+.help-block {
+ display: block;
+ clear: both;
+}
+
+.help-img-left {
+ margin-right: 1.5em;
+ border: solid 1px #ccc;
+}
+
+.ta-center {
+ margin: 1em;
+ text-align: center;
+}
+
+.help-img-center {
+ margin-left: auto;
+ margin-right: auto;
+ border: solid 1px #ccc;
+}
+
+.help-navigation {
+ border-top: 1px dotted #ccc;
+}
+
+.help-previous {
+ float: left;
+}
+
+.help-next {
+ float: right;
+}
diff --git a/sites/all/modules/advanced_help/help.png b/sites/all/modules/advanced_help/help.png
new file mode 100644
index 000000000..7645023a6
--- /dev/null
+++ b/sites/all/modules/advanced_help/help.png
Binary files differ
diff --git a/sites/all/modules/advanced_help/help/.htaccess b/sites/all/modules/advanced_help/help/.htaccess
new file mode 100644
index 000000000..cddefd235
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/.htaccess
@@ -0,0 +1,4 @@
+<Files *\.html>
+Order Allow,Deny
+Deny from all
+</Files>
diff --git a/sites/all/modules/advanced_help/help/advanced_help.help.ini b/sites/all/modules/advanced_help/help/advanced_help.help.ini
new file mode 100644
index 000000000..fe7babef9
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/advanced_help.help.ini
@@ -0,0 +1,18 @@
+[readme]
+title = README
+weight = -11
+
+[using-advanced-help]
+title = Using advanced help
+weight = -10
+
+[translation]
+title = Translating advanced help
+
+[ini-file]
+title = Advanced help .ini file format
+line break = FALSE
+
+[why-advanced-help]
+title = Why advanced help?
+line break = TRUE
diff --git a/sites/all/modules/advanced_help/help/ahelp_tab.png b/sites/all/modules/advanced_help/help/ahelp_tab.png
new file mode 100755
index 000000000..37ef6be4f
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/ahelp_tab.png
Binary files differ
diff --git a/sites/all/modules/advanced_help/help/click_icon.png b/sites/all/modules/advanced_help/help/click_icon.png
new file mode 100644
index 000000000..0f05444eb
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/click_icon.png
Binary files differ
diff --git a/sites/all/modules/advanced_help/help/ini-file.html b/sites/all/modules/advanced_help/help/ini-file.html
new file mode 100644
index 000000000..05b85e435
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/ini-file.html
@@ -0,0 +1,117 @@
+<p>The advanced help configuration file is in simple .ini file format.
+It has an optional section for global settings that might be inherited
+for each help file, followed by sections for each help file.</p>
+
+<p>Global settings may be put into a section named <code>[advanced help
+settings]</code>. This means that this name is reserved and it cannot
+be a help file in any module or theme. The following settings may be
+set in this section, with the default value (if any) in brackets.</p>
+
+<dl>
+<dt><code>line break</code> (FALSE)</dt>
+<dd>If set, the line break filter will be applied to all help files
+defined by this module or theme, unless that help file specifically is
+set otherwise. The line break converts line breaks
+into <code>br</code> and <code>p</code> tags automatically.</dd>
+
+<dt><code>navigation</code> (TRUE)</dt>
+<dd>If set, this navigation will be displayed at the end of the topic:
+<em>previous topic</em>, Up (parent), <em>next topic</em>.</dd>
+
+<dt><code>css</code></dt>
+<dd>Specify a css file that will be used for all help files (unless
+overridden), including the .css extension. This .css file must reside
+in the help directory along with the .html files, and will not be
+affected by translation.</dd>
+
+<dt><code>name</code></dt>
+<dd>May be set to override the module or theme name as displayed in
+both the module/theme index as well as the navigation and breadcrumb
+trail. Usually, this is not set, but for some projects you may want to
+use a more friendly name than appears in the project's .info file.</dd>
+
+<dt><code>index name</code></dt>
+<dd>This may be set to change the name of the module or theme in the
+module/theme index. It overrides the <code>name</code> setting above,
+as well as the project's name in its .info file.</dd>
+
+<dt><code>hide</code> (FALSE)</dt>
+<dd>This may be used to hide a module or theme in the module/theme
+index. This is particularly useful for modules who insert their help
+files into the hierarchy of another module or theme, as might be done
+by modules that extend <strong>Views</strong> or derived themes that
+extend base themes like <strong>Zen</strong>. By setting this to TRUE
+the project will not appear as its own entry.</dd>
+</dl>
+
+<p>Each section after that will correspond to a single help file for a
+single topic. It starts with the topic name in square brackets
+(e.g. <code>[ini-file]</code>). The following settings may be set for
+each file/topic, with the default value (if any) in brackets.</p>
+
+<dl>
+<dt><code>title</code></dt>
+<dd>The title of the topic, presented to the user and used in
+links. If you have special characters in your title, be sure to
+enclose it in quotes. (This setting is currently not optional.)</dd>
+
+<dt><code>file</code> (topic name)</dt>
+<dd>The filename, without the .html extension, used for the file with
+the help text for the topic. This is optional; if not specified, the
+topic name wil be the file name.</dd>
+
+<dt><code>weight</code> (0)</dt>
+<dd>The weight, used for sorting topics on the administration
+page. The default is 0 (zero) if unspecified. Items with the same weight
+are sorted alphabetically.</dd>
+
+<dt><code>parent</code></dt>
+<dd>The topic ID to use in a hierarchy; children will be listed
+beneath parents in the topic list, and will have the parent in their
+breadcrumb trail. You may parent this topic to a topic in another
+module or theme by using <code>module%topic</code>
+or <code>theme%topic</code> as the identifier,
+where <code>module</code> or <code>theme</code> is the project's short
+name. For example if parent is set to, '<code>views%display</code>',
+the topic will be regarded as a child of the
+<code>display</code> topic in the <strong>Views</strong> module.</dd>
+
+<dt><code>line break</code> (FALSE)</dt>
+<dd>Set the line break filter for this topic. Set to FALSE to disable
+the line break filter if this has been turned on in the global
+settings.</dd>
+
+<dt><code>css</code></dt>
+<dd>Specify a css file that will be used for this file. This .css file
+must reside in the help directory along with the .html files. This
+will override any .css file added by the global settings.</dd>
+
+<dt><code>popup width</code> (500)</dt>
+<dd>The width in pixels of the popup window.</dd>
+
+<dt><code>popup height</code> (500)</dt>
+<dd>The height in pixels of the popup window.</dd>
+</dl>
+
+<p>For example, here is a version of the <code>advanced_help.help.ini</code> file:</p>
+
+<pre>
+[readme]
+title = README
+weight = -11
+
+[using-advanced-help]
+title = Using advanced help
+weight = -10
+
+[translation]
+title = Translating advanced help
+
+[ini-file]
+title = Help .ini file format
+line break = FALSE
+
+[why-advanced-help]
+title = Why advanced help?
+line break = TRUE
+</pre>
diff --git a/sites/all/modules/advanced_help/help/readme.html b/sites/all/modules/advanced_help/help/readme.html
new file mode 100644
index 000000000..68579bd5d
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/readme.html
@@ -0,0 +1,107 @@
+<h2 id="project-description">Synopsis</h2>
+
+<p>The <strong>Advanced help</strong> module provides a framework that allows
+module and theme developers integrate help texts in a Drupal site.</p>
+
+<p>These help texts are stored in ordinary <code>.html</code>-files
+that lives in the file system (as opposed to the database). These
+files are distributed from the project Drupal.org repo in the same
+package as the module or theme, and placed in a subdirectory named
+<code>help</code> in the project or theme directory. This means that
+the help texts can be easiely kept in sync with the project they
+provide help texts for, but also that read access to these files
+are not managed by any content access restrictions imposed by Drupal.</p>
+
+<p>The help texts can be marked up with standard HTML. They will be
+rendered using your site's theme.</p>
+
+<p>If the module or theme author does not make use of the
+<em>Advanced help</em> HTML-framework, but if there is a
+<code>README.md</code> or <code>README.txt</code> in the package,
+the content of that file will be shown instead.</p>
+
+<p>The help texts may appear in a popup or not as the project prefers.
+By taking away access to view the popups, a site can hide popups from
+users.</p>
+
+<p>The help texts can be placed in a hierarchy, allowing for top down
+navigation of help.</p>
+
+<p>The help texts may be made searchable. If advanced help search is
+enabled, all help texts are fully indexed. This means that the entire
+contents of the advanced help set of pages can be searched for
+keywords.</p>
+
+<h2 id="use">Using the module</h2>
+
+<p>When you enable the module, a new tab with the legend “Advanced
+help” will show up under “Help”:
+
+<div class="ta-center">
+<img class="help-img-center" alt="ahelp_tab.png" src="&path&ahelp_tab.png" width="661" height="225" border="1" />
+</div>
+
+<p>By itself, this module doesn't do much. The <strong>Advanced
+help</strong> assists other modules and themes in showing help texts.
+Nothing will show up until you enable at least one other module that
+makes use of the advanced help framework or comes with a file
+named <code>README.md</code> or <code>README.txt</code>. However, it
+comes with a small companion demo module named
+<strong>Advanced help example</strong> to demonstrate how it works.
+For more extensive example of use of the advanced help features, see
+the <strong>Views</strong> project.</p>
+
+<!--
+<h2 id="project-recommended">Recommended modules</h2>
+
+<ul>
+<li><a href="https://www.drupal.org/project/markdown">Markdown filter</a>:<br>
+When this module is enabled, display of any <code>README.md</code> the
+module shows will be rendered with markdown.</li>
+<li><a href="https://www.drupal.org/project/attributions">Attributions</a>:<br>
+When this module is enabled, attributions of third party content used
+by the project (i.e. some text from Wikipedia) will be available in an
+attribution block and on an atribution page.</li>
+</ul>
+-->
+
+<h2 id="support-status">Support status</h2>
+
+<p>Reported bugs for the Drupal 7 branch will be fixed in a timely
+manner. Bugs in the issue queue for the Drupal 6 branch will only be
+fixed if accompanied with a patch, after the patch has been reviewed
+and tested by the community. No Drupal 8 version is currently under
+development. Post a message in
+the <a href="https://www.drupal.org/node/1928218">issue queue</a> if
+you're interested in managing a port of the project to to Drupal
+8. Older versions are no longer supported.</p>
+
+<p>Community support in the form of patches are very welcome for both
+Drupal 6 and Drupal 7 versions, and will be given priority. For QA,
+the project needs community support in the form of reviews of patches,
+development versions and releases.</p>
+
+<p>The primary goal of the module is to remain <strong>light-weight
+and simple</strong>. This means that not all feature requests will be
+implemented, even if they are a good idea. Feature requests
+accompanied by patches are more likely to make it into a release.</p>
+
+<p>The maintainer hopes that the community is willing to help out by
+answering &amp; closing support requests.</p>
+
+<!--
+<h2 id="project-problems">Known problems</h2>
+-->
+
+
+
+<h2 id="project-maintainers">Credits</h2>
+
+<ul>
+<li><a href="https://www.drupal.org/u/merlinofchaos"">merlinofchaos</a> (52 commits, original creator)</li>
+<li><a href="https://www.drupal.org/u/redndahead">redndahead</a> (8 commits)</li>
+<li><a href="https://www.drupal.org/u/dmitrig01">dmitrig01</a> (3 commits)</li>
+<li><a href="https://www.drupal.org/u/amitgoyal">amitgoyal </a> (5 commits)</li>
+<li><a href="https://www.drupal.org/u/gisle">gisle</a> (current maintainer, D7)</li>
+<li><a href="https://www.drupal.org/u/gnuget">gnuget</a> (current maintainer, D8)</li>
+</ul>
diff --git a/sites/all/modules/advanced_help/help/translation.html b/sites/all/modules/advanced_help/help/translation.html
new file mode 100644
index 000000000..8faf0e904
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/translation.html
@@ -0,0 +1,44 @@
+<p>To translate a help-file indexed by <strong>Advanced help</strong>,
+first create a directory
+<code>translations/help/<em>language</em></code> in the project's
+root directory. The <em>language</em> is the language code that
+appears on the <em>Languages</em> page in the administrative UI.</p>
+
+<p>Then, copy the <code>.ini</code> file and all
+the <code>.html</code> files from the help directory into this. If
+you need to alter an image to use it in a translation, you may also
+put the altered image there.</p>
+
+<p>In the topics section, the <code>.ini</code> file only needs to
+keep the topic names (unaltered) and titles (translated). If there is
+a <code>name</code> or <code>index name</code> setting in the
+'advanced help settings' portion, that should be retained. Any
+retained settings should be translated. The rest of the data in the
+<code>.ini</code> file may be discarded or ignored.</p>
+
+<p>Each <code>.html</code> file should then be translated in place.</p>
+
+<p>When translating a <code>.html</code> file, you will find that
+the <code>&amp;path&amp;</code> keyword (used for images and links)
+will lead to the original directory. If you must translate items that
+are linked, such as images containing text,
+use <code>&amp;trans_path&amp;</code> instead, which will lead to the
+translated directory. This will allow you to pick and choose which
+linked items, if any, will be translated.</p>
+
+<p>If a topic is not tranlated, the default (untranslated) version
+will be shown instead.</p>
+
+<h2>Translating Advanced help's help files</h2>
+
+<p>If you want to help with the translation of
+<strong>Advanced help</strong> help texts for a particular language, look for an issue named named “Translation to XXX” (where
+“XXX” is the language you want to translate the help texts to) in the <a href="https://www.drupal.org/project/issues/advanced_help">issue queue for Advanced help</a>.
+If such an issue does not exist, please can create it.
+Choose <em>Category</em> “Task”,
+<em>Status</em> “Needs review” and
+<em>Component</em> “Documentation”.
+Upload translated files as an attachment (change the file type from <code>.html</code> to <code>.txt</code> to be allowed to upload). </p>
+
+<p>Uploaded translations will be included in the next version if
+reviewed and approved by other users (i.e. gets to status “RTBC”).</p>
diff --git a/sites/all/modules/advanced_help/help/using-advanced-help.html b/sites/all/modules/advanced_help/help/using-advanced-help.html
new file mode 100644
index 000000000..c47750bb4
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/using-advanced-help.html
@@ -0,0 +1,152 @@
+<p>The <strong>Advanced help</strong> module provides a framework that
+allows module and theme developers integrate help texts in a Drupal
+site. Although the <strong>Advanced help</strong> does not provide
+general help by itself, it provides a powerful and easy framework that
+modules and themes may use to provide their own help.</p>
+
+<p>Modules and themes utilizing <strong>Advanced help</strong> should
+create a subdirectory named <code>help</code> inside their own main
+directory. Place the file
+<em>MODULENAME</em>.help.ini (resp. <em>THEMENAME</em>.help.ini) in this subdirectory.
+formatted similar to the following example:</p>
+
+<pre>
+[about-php]
+title = About PHP
+file = about-php
+weight = -10
+
+[history]
+title = History of PHP
+parent = about-php
+
+[usage]
+title = Usage of PHP
+weight = 1
+
+[security]
+title = Security of PHP
+weight = 2
+
+[syntax]
+title = PHP syntax
+parent = usage
+</pre>
+
+<p>This file defines five help topics (inside the square brackets), and
+some settings for them.
+See: <a href="&topic:advanced_help/ini-file&">Advanced help .ini file format</a> for
+a list of defined settings.</p>
+
+
+<p>All topics are addressed by the module or theme providing the
+topic, and by the topic id. To produce a themed link to popup
+about a topic, use the a format similar to the following example:</p>
+
+<!-- D6
+<pre>
+$output = theme('advanced_help_topic', 'help_example', 'about-php');
+$output .= '&nbsp;' . t('Click the help icon!');
+</pre>
+-->
+
+<!-- D7 -->
+<pre>
+$output = theme('advanced_help_topic', array(
+ 'module' => 'help_example',
+ 'topic' => 'about-php',
+));
+$output .= '&nbsp;' . t('Click the help icon!');
+</pre>
+
+<p>This produces the following output:</p>
+
+<pre>
+&lt;a class="advanced-help-link" title="About PHP"
+ onclick="var w=window.open(this.href, 'advanced_help_window',
+ 'width=500,height=500,scrollbars,resizable');
+ w.focus(); return false;"
+ href="/help/help_example/about-php?popup=1"&gt;
+&lt;span&gt;Help&lt;/span&gt;
+&lt;/a&gt;
+ Click the help icon!
+&lt;/div&gt;
+</pre>
+
+<p>This produces a clickable help icon like the one shown below:</p>
+
+<div class="ta-center">
+<img class="help-img-center" alt="clickable icon" src="&path&click_icon.png" width="180" height="90" border="0" />
+</div>
+
+<p>Inside your help file, you may link to other help topics using this format:</p>
+<pre>
+&lt;a href="&amp;topic:module/topic&amp;"&gt;topic&lt;/a&gt;
+</pre>
+<p>This format will ensure the popup status remains consistent when
+switching between links.</p>
+
+<p>To reference items within the help directory, such as images you wish to embed within the help text, use:</p>
+
+<pre>
+&lt;img src="&amp;path&amp;example.png"/&gt;
+&lt;img src="&amp;trans_path&amp;example.png"/&gt;
+</pre>
+
+<p>The <code>trans_path</code> keyword refers to a translated version of the image in the translation directory and may be used it differs from the original.</p>
+
+<p>To reference any normal path in the site, use:</p>
+<pre>
+&lt;a href="&amp;base_url&amp;admin/settings/site-configuration"&gt;anchor text&lt;/a&gt;
+</pre>
+
+<p><strong>NOTE: </strong> In previous versions <strong>Advanced
+help</strong> did not require the &amp;'s to be wrapped around
+<code>topic</code>, <code>path</code>, and <code>base_url</code>.
+This is currently still supported, but will be removed in a future
+version. By adding the &amp;'s these tokens are now not limited
+to <code>href=""</code> and <code>src=""</code> parameters.</p>
+
+<h2 id="access-control">Access control</h2>
+
+<p>When this module is installed, users with the
+<code>view advanced help index</code>
+permission can access the advanced help index by going to
+<em>Administer &rarr; Advanced Help</em>
+(<code>admin/advanced_help</code>). Additional permissions
+<code>view advanced help topic</code> and
+<code>view advanced help popup</code>
+enable users to access the actual help pages and popups.</p>
+
+<p>The help texts are stored as plain .html-files and can, unless
+protected, be accessed by anyone who knows their URL. To protect
+them, place the following four lines in a file named
+<code>.htaccess</code> in project's <code>help</code> directory:</p>
+
+<pre>
+&lt;Files *\.html&gt;
+Order Allow,Deny
+Deny from all
+&lt;/Files&gt;
+</pre>
+
+<p>It as the responsibility of the site manager to make sure this type
+of protection is in place if the site has help files that merits
+protection from direct access.</p>
+
+<p>See also this tracker in the project's issue queue:
+<a href="https://www.drupal.org/node/1980936">#1980936 Typing complete path to .html help files in module bypasses user permissions</a>.</p>
+
+<h2 id="search">Search</h2>
+
+<p>To enable advanced help search, navigate to
+<em>Administration → Configuration → Search and metadata → Search settings</em>.
+Scroll down to <em>Active search modules</em> and tick the box to the
+left of “Advanced help”. The search form will appear on the top of
+the advanced help index pages.</p>
+
+<p>If the core <strong>Search</strong> module is enabled, the contents
+of the advanced help framework will be indexed on cron. If you enable
+new modules or themes and wish to immediately index their help text,
+navigate to <em>Administration → Reports → Status report</em> and
+click the link “run cron manually”.</p>
diff --git a/sites/all/modules/advanced_help/help/why-advanced-help.html b/sites/all/modules/advanced_help/help/why-advanced-help.html
new file mode 100644
index 000000000..3f36b3dda
--- /dev/null
+++ b/sites/all/modules/advanced_help/help/why-advanced-help.html
@@ -0,0 +1,44 @@
+The <strong>Advanced help</strong> framework was designed to replace the original Drupal help system, which has several flaws that make it hard to create new help, hard to maintain existing help, and particularly hard to access help.
+
+The primary goal, then, is to increase the accessibility of help, meaning the ability of both the user and the help text author to access the needed tools to use, create, maintain and translate the help.
+
+This system is completely separate from Drupal's <code>hook_help()</code>. In Drupal 6 and 7, it actually co-exists with it; in the future, it is hoped that it will completely replace it allowing <code>hook_help()</code> to be deprecated and removed.
+
+Messages added to the top of a page are not really “help”. Often these messages are an introduction to a form or a short blurb telling a user what to do with a particular page. The problem is, these messages are always there, they are easily ignored, and they come before the actual page. In general, when users are learning, they want to see the page first, then ask questions. The reverse order is much less conducive to actually teaching a user how to use something. By allowing help to be available on request, the system conforms more naturally to how most people work.
+
+<h2>Advanced help is organized by topic</h2>
+With the <code>hook_help()</code> method, help text is organized by URL path. This is fine if you have help text describing how to use a particular page or what a particular page does, but ultimately is limiting because manuals and documentation are usually grouped by topic, and those topics are determined by the material itself.
+
+<strong>Advanced help</strong> allows the documentation author to organize topics as he or she sees fit; the only restriction, really, is that each individual chunk of text needs to stand on its own as a discrete topic.
+
+What's more, modules and themes can insert their topics into another's hierarchy. This would allow the Drupal core to create a task based help navigation system which allows modules and themes to insert topics into that navigation fluidly. This allows modules and themes to continue to keep their systems separate, yet integrate into the main system.
+
+<h2>Advanced help topics are processed HTML in their own files</h2>
+This separation makes it easy to find and modify. Currently, everything is lumped together in <code>hook_help()</code> in PHP strings that are run through <code>t()</code>, and there is a fair amount of PHP code necessary in this system that actually gets in the way of writing good, explanatory text.
+
+In fact, requiring a documentation author to understand PHP at all is a detriment. The idea that documentation writers need to have PHP development as a skill seriously reduces the number of available contributors. HTML, on the other hand, is a much more common skill, is relatively easy to learn, and the amount of HTML needed to write documentation is only a little bit more than the HTML used in forum posts.
+
+Another benefit to not using PHP is that the files themselves are safe. They are filtered to escape malicious script code that could take over the server or do worse things. This means that these files can be used relatively easily on Drupal.org itself. It also means that descriptions of the project pulled from the project's help pages or README.txt or README.md can be made on the project's Drupal.org without the need to download the module or theme to look at the contents of these files. This also simplifies maintenance, as the files can be corrected easily by patches, which are updated by tyhe maintainer and pushed to git.
+
+By moving all docymentation into help files, we could, if we wanted, package the Drupal.org handbooks, or a subset of them, directly into a Drupal distribution, or a Drupal add-on, so that Drupal administrators can have Drupal help without needing to visit Drupal.org. This can be valuable in locked down corporate environments and on planes. But more importantly, the handbooks can be made version aware much more easily than the current method on Drupal.org.
+
+The downside to this method is that these books can't easily be made dynamic. Though the use of alter hooks could allow a module or theme to make modifications to the help as needed, doing this could make the help files less useful when you take them out of context.
+
+<h2>Advanced help files are translated as a file</h2>
+It is actually not easy to translate documents as strings, particularly when the language being used is very much unlike English. In fact, when translating a document, the organization of the sentences may change drastically. It is also a burden on the CPU to do this, as you are indexing on very long strings.
+
+Translators have a much better time translating a document as a unit, because of the presence of the entire context.
+
+<h2>Advanced help has its own navigation system</h2>
+By making use of a navigation system specified in a .ini file (which is not PHP code and therefore safe to use), the help can be structured like a book, which is typical of online manuals. This is familiar to users, can be organized (and re-organized) and allows a module or theme to include significantly richer text without burdening the PHP code with having its help loaded unnecessarily.
+
+This book can be navigated hierarchically as well, making it easy to keep related topics together.
+<h2>Advanced help is indexed by the search engine</h2>
+An important goal of this system was to add searchability to the help. By being able to enter keywords into the search box and find relevant topics, we come up with a system that resembles the kind of help that comes with many operating systems. This is very valuable when searching through manuals trying to find out how to do a particular thing.
+
+This search is specific to the help, meaning that the help will not be mixed in with the global node search. This can be considered both an advantage and a disadvantage. For the most part, this help system is meant to provide help to site administrators, and content searches should not find it. The disadvantage, of course, is when you want to use it for end user help, you will not be able to.
+
+<h2>Inline help can be brought in via popups</h2>
+In addition to the manual-like hierarchical navigation, <strong>Advanced help</strong> can also provide context-sensitive additional help through a popup. While popups are controversial, the argument for using them is that when getting help while on a form, <i>a popup will not throw away a user's data.</i> Browsers are not very friendly to input forms if they are not submitted, and navigating away from the form can be dangerous. There are various other solutions to this problem, but each one has a drawback. The drawbacks to popups are well known, but mostly it is the irritation of having new windows. When getting help, though, a popup is usually invited. Help should not interfere with what the operation the user is trying to complete. It differs greatly from the uninvited popup, which are usually ads or popups meant to prevent users from navigating away from a site.
+
+Popups can be added to a page with plain text links or themed icon links.
diff --git a/sites/all/modules/advanced_help/help_example/help/.htaccess b/sites/all/modules/advanced_help/help_example/help/.htaccess
new file mode 100644
index 000000000..367017476
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/.htaccess
@@ -0,0 +1,4 @@
+<Files *\.html>
+Order Allow,Deny
+Deny from all
+</Files> \ No newline at end of file
diff --git a/sites/all/modules/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpg b/sites/all/modules/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpg
new file mode 100644
index 000000000..b182676fb
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpg
Binary files differ
diff --git a/sites/all/modules/advanced_help/help_example/help/180px-Lerdorf.jpg b/sites/all/modules/advanced_help/help_example/help/180px-Lerdorf.jpg
new file mode 100644
index 000000000..5a437f468
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/180px-Lerdorf.jpg
Binary files differ
diff --git a/sites/all/modules/advanced_help/help_example/help/about-php.html b/sites/all/modules/advanced_help/help_example/help/about-php.html
new file mode 100644
index 000000000..47e32757a
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/about-php.html
@@ -0,0 +1,54 @@
+<p><b>PHP</b> (<i>PHP: Hypertext Preprocessor</i>) is a computer <a href="http://en.wikipedia.org/wiki/Scripting_language" title="Scripting language">scripting language</a>, originally designed for producing <a href="http://en.wikipedia.org/wiki/Dynamic_web_page" title="Dynamic web page">dynamic web pages</a>. It is mainly used in <a href="http://en.wikipedia.org/wiki/Server-side_scripting" title="Server-side scripting">server-side scripting</a>, but can be used from a <a href="http://en.wikipedia.org/wiki/Command_line_interface"title="Command line interface">command line interface</a> or in <a href="http://en.wikipedia.org/wiki/Standalone" title="Standalone">standalone</a> <a href="http://en.wikipedia.org/wiki/Graphical_user_interface" title="Graphical user interface">graphical applications</a>.</p>
+
+<p>While PHP was originally created by <a
+href="http://en.wikipedia.org/wiki/Rasmus_Lerdorf" title="Rasmus
+Lerdorf">Rasmus Lerdorf</a> in 1994, the main implementation of PHP is
+now produced by The PHP Group and serves as the <a
+href="http://en.wikipedia.org/wiki/De_facto_standard" title="De facto
+standard"><i>de facto</i> standard</a> for PHP as there is
+no <a
+href="http://en.wikipedia.org/wiki/Formal_specification" title="Formal
+specification">formal specification</a>. Released under the <a
+href="http://en.wikipedia.org/wiki/PHP_License" title="PHP
+License">PHP License</a>, the <a
+href="http://en.wikipedia.org/wiki/Free_Software_Foundation"
+title="Free Software Foundation">Free Software Foundation</a>
+considers it to be <a
+href="http://en.wikipedia.org/wiki/Free_software" title="Free
+software">free software</a>.</p>
+
+<p>PHP is a widely-used general-purpose scripting language that is
+especially suited for <a
+href="http://en.wikipedia.org/wiki/Web_development" title="Web
+development">web development</a> and can be embedded
+into <a href="http://en.wikipedia.org/wiki/HTML"
+title="HTML">HTML</a>. It generally runs on a <a
+href="http://en.wikipedia.org/wiki/Web_server" title="Web server">web
+server</a>, taking PHP code as its input and
+creating <a
+href="http://en.wikipedia.org/wiki/Web_page" title="Web page">web
+pages</a> as output. It can be deployed on most web servers and on
+almost every <a
+href="http://en.wikipedia.org/wiki/Operating_system" title="Operating
+system">operating system</a> and <a
+href="http://en.wikipedia.org/wiki/Platform_%28computing%29"
+class="mw-redirect" title="Platform (computing)">platform</a> free of
+charge. PHP is installed on more than 20 million
+websites and 1 million <a
+href="http://en.wikipedia.org/wiki/Server_%28computing%29"
+title="Server (computing)">servers</a>, although the number of
+websites with PHP <a
+href="http://en.wikipedia.org/wiki/Installation_%28computer_programs%29"
+title="Installation (computer programs)">installed</a> has declined
+since August 2005.
+It is also the most popular <a
+href="http://en.wikipedia.org/wiki/Apache_HTTP_Server" title="Apache
+HTTP Server">Apache</a> module among computers using Apache as a web
+server.
+The most recent major release of PHP was version 5.2.5
+on <a href="http://en.wikipedia.org/wiki/November_8"
+title="November 8">November 8</a>, <a
+href="http://en.wikipedia.org/wiki/2007"
+title="2007">2007</a>.</p>
+
+<p class="attribution">This excerpt is adapted from <a href="http://en.wikipedia.org/wiki/PHP">Wikipedia: PHP</a>. It is used here under a <a href="http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License">Creative Commons BY-SA 3.0</a> license.</p>
diff --git a/sites/all/modules/advanced_help/help_example/help/help_example.help.ini b/sites/all/modules/advanced_help/help_example/help/help_example.help.ini
new file mode 100644
index 000000000..6e83b28d2
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/help_example.help.ini
@@ -0,0 +1,19 @@
+[about-php]
+title = About PHP
+weight = -10
+
+[history]
+title = History of PHP
+parent = about-php
+
+[usage]
+title = Usage of PHP
+weight = 1
+
+[security]
+title = Security of PHP
+weight = 2
+
+[syntax]
+title = PHP syntax
+parent = usage \ No newline at end of file
diff --git a/sites/all/modules/advanced_help/help_example/help/history.html b/sites/all/modules/advanced_help/help_example/help/history.html
new file mode 100644
index 000000000..2edb1cea1
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/history.html
@@ -0,0 +1,21 @@
+<div class="help-box help-left">
+<a href="http://en.wikipedia.org/wiki/Image:Lerdorf.jpg" class="image" title="Rasmus Lerdorf, who wrote the original Common Gateway Interface binaries"><img alt="Rasmus Lerdorf, who wrote the original Common Gateway Interface binaries" src="&path&180px-Lerdorf.jpg" width="180" height="270" border="0" /></a>
+<div><a href="http://en.wikipedia.org/wiki/Rasmus_Lerdorf" title="Rasmus Lerdorf">Rasmus Lerdorf</a>, who wrote the original <a href="http://en.wikipedia.org/wiki/Common_Gateway_Interface" title="Common Gateway Interface">Common Gateway Interface</a> binaries.</div>
+</div>
+
+<p>PHP, standing for Personal Home Page, began as a set of <a href="http://en.wikipedia.org/wiki/Common_Gateway_Interface" title="Common Gateway Interface">Common Gateway Interface</a> <a href="http://en.wikipedia.org/wiki/Binary_file" title="Binary file">binaries</a> written in the <a href="http://en.wikipedia.org/wiki/C_programming_language" class="mw-redirect" title="C programming language">C programming language</a> in 1994 by the <a href="http://en.wikipedia.org/wiki/Danish_people" title="Danish people">Danish</a>/<a href="http://en.wikipedia.org/wiki/Greenland" title="Greenland">Greenlandic</a> programmer <a href="http://en.wikipedia.org/wiki/Rasmus_Lerdorf" title="Rasmus Lerdorf">Rasmus Lerdorf</a>. Lerdorf initially created these Personal Home Page Tools to replace a small set of <a href="http://en.wikipedia.org/wiki/Perl" title="Perl">Perl</a> scripts he had been using to maintain his <a href="http://en.wikipedia.org/wiki/Personal_homepage" class="mw-redirect" title="Personal homepage">personal homepage</a>. The tools were originally created to perform tasks such as displaying his <a href="http://en.wikipedia.org/wiki/R%C3%A9sum%C3%A9" title="Résumé">résumé</a> and recording how much <a href="http://en.wikipedia.org/wiki/Web_traffic" title="Web traffic">traffic</a> his page was receiving. He combined these binaries with his Form Interpreter to create PHP/FI, which had more functionality. It included a larger <a href="http://en.wikipedia.org/wiki/C_%28programming_language%29" title="C (programming language)">C implementation</a> which could communicate with <a href="http://en.wikipedia.org/wiki/Database" title="Database">databases</a> and helped build simple, dynamic <a href="http://en.wikipedia.org/wiki/Web_application" title="Web application">web applications</a>. He released PHP publicly on <a href="http://en.wikipedia.org/wiki/June_8" title="June 8">June 8</a>, <a href="http://en.wikipedia.org/wiki/1995" title="1995">1995</a> to speed up the finding of <a href="http://en.wikipedia.org/wiki/Software_bug" title="Software bug">bugs</a> and improving the code. This release was named PHP version 2, and already had basic functionality that PHP has today. This includes Perl-like variables, form handling, and the ability to embed HTML. The syntax was similar to Perl but was more limited, simpler, and less consistent.</p>
+
+<div class="help-box help-right">
+<a href="http://en.wikipedia.org/wiki/Image:Andi_Gutmans_1.jpg" class="image" title="Andi Gutmans, who, along with Zeev Suraski, rewrote the parser that formed PHP 3"><img alt="Andi Gutmans, who, along with Zeev Suraski, rewrote the parser that formed PHP 3" src="&path&180px-Andi_Gutmans_1.jpg" width="180" height="244" border="0" class="thumbimage" /></a>
+<div class="help-leftalign"><a href="http://en.wikipedia.org/wiki/Andi_Gutmans" title="Andi Gutmans">Andi Gutmans</a>, who, along with <a href="http://en.wikipedia.org/wiki/Zeev_Suraski" title="Zeev Suraski">Zeev Suraski</a>, rewrote the <a href="http://en.wikipedia.org/wiki/Parsing#Parser" title="Parser">parser</a> that formed PHP&nbsp;3.</div>
+</div>
+
+<p><a href="http://en.wikipedia.org/wiki/Zeev_Suraski" title="Zeev Suraski">Zeev Suraski</a> and <a href="http://en.wikipedia.org/wiki/Andi_Gutmans" title="Andi Gutmans">Andi Gutmans</a>, two <a href="http://en.wikipedia.org/wiki/Israelis" title="Israelis">Israeli</a> developers at the <a href="http://en.wikipedia.org/wiki/Technion_IIT" class="mw-redirect" title="Technion IIT">Technion IIT</a>, rewrote the <a href="http://en.wikipedia.org/wiki/Parsing#Parser title="Parser">parser</a> in 1997 and formed the base of PHP 3, changing the language's name to the <a href="http://en.wikipedia.org/wiki/Recursive_initialism" class="mw-redirect" title="Recursive initialism">recursive initialism</a> <i>PHP: Hypertext Preprocessor</i>. The development team officially released PHP/FI 2 in November 1997 after months of <a href="http://en.wikipedia.org/wiki/Development_stage#beta" class="mw-redirect" title="Development stage">beta</a> testing. Afterwards, public testing of PHP 3 began, and the official launch came in June 1998. Suraski and Gutmans then started a new <a href="http://en.wikipedia.org/wiki/Rewrite_%28programming%29" title="Rewrite (programming)">rewrite</a> of PHP's core, producing the <a href="http://en.wikipedia.org/wiki/Zend_Engine" title="Zend Engine">Zend Engine</a> in 1999. They also founded <a href="http://en.wikipedia.org/wiki/Zend_Technologies" title="Zend Technologies">Zend Technologies</a> in <a href="http://en.wikipedia.org/wiki/Ramat_Gan" title="Ramat Gan">Ramat Gan</a>, Israel, which manages the development of PHP.</p>
+
+<p>On <a href="http://en.wikipedia.org/wiki/May_22" title="May 22">May 22</a>, <a href="http://en.wikipedia.org/wiki/2000" title="2000">2000</a>, PHP 4, powered by the Zend Engine 1.0, was released. On <a href="http://en.wikipedia.org/wiki/July_13" title="July 13">July 13</a>, <a href="http://en.wikipedia.org/wiki/2004" title="2004">2004</a>, PHP 5 was released and is powered by the new Zend Engine II. PHP 5 included new features such as improved support for <a href="http://en.wikipedia.org/wiki/Object-oriented_programming" title="Object-oriented programming">object-oriented programming</a>, the PHP Data Objects extension (which defines a lightweight and consistent interface for accessing databases), and numerous performance enhancements. The most recent update released by The PHP Group is for the older PHP version 4 code branch. As of January 2008, this branch is up to version 4.4.8. PHP 4 will be supported by security updates until <a href="http://en.wikipedia.org/wiki/August_8" title="August 8">August 8</a>, <a href="http://en.wikipedia.org/wiki/2008" title="2008">2008</a>.</p>
+
+<p>PHP 5 is the only stable version still being developed. <a href="http://en.wikipedia.org/wiki/Late_static_binding" class="mw-redirect" title="Late static binding">Late static binding</a> has been missing from PHP and will be added in version 5.3. Development on PHP 4 ceased at the end of 2007, except for the critical security updates for PHP 4 already mentioned. PHP 6 is now under development and major changes include the removal of <code>register_globals</code>, <a href="http://en.wikipedia.org/wiki/Magic_quotes" title="Magic quotes">magic quotes</a>, and <a href="http://en.wikipedia.org/wiki/Safe_mode#Application_software_safe_mode" title="Safe mode">safe mode</a>. PHP does not have complete native support for <a href="http://en.wikipedia.org/Unicode" title="Unicode">Unicode</a> or multibyte strings; unicode support will be added in PHP 6. Many high profile open source projects ceased to support PHP 4 in new code as of <a href="http://en.wikipedia.org/wiki/February_5" title="February 5">February 5</a>, <a href="http://en.wikipedia.org/wiki/2008" title="2008">2008</a>, due to the GoPHP5 initiative, provided by a consortium of PHP developers promoting the transition from PHP 4 to PHP 5.</p>
+
+<p class="attribution">This excerpt is adapted from <a href="http://en.wikipedia.org/wiki/PHP#History">Wikipedia: PHP - history</a>. It is used here under a <a href="http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License">Creative Commons BY-SA 3.0</a> license.
+Picture of Rasmus <a href="http://en.wikipedia.org/wiki/File:Lerdorf.jpg" title="Link to work">Lerdorf</a> by Jud Dagnall, available under <a href="http://creativecommons.org/licenses/by-sa/3.0/deed.en" title="Link to license">Creative Commons BY-SA 3.0</a>.
+Picture of <a href="http://en.wikipedia.org/wiki/File:Andi_Gutmans_1.jpg" title="Link to work">Andi Gutmans</a> by <a href="https://www.flickr.com/people/81342178@N00" title="Link to author">Jim Winstead</a>, available under <a href="http://creativecommons.org/licenses/by/2.0/deed.en" title="Link to license">Creative Commons BY 2.0</a>.</p>
diff --git a/sites/all/modules/advanced_help/help_example/help/security.html b/sites/all/modules/advanced_help/help_example/help/security.html
new file mode 100644
index 000000000..3eafcbe07
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/security.html
@@ -0,0 +1,4 @@
+<p>PHP is a popular target of <a href="http://en.wikipedia.org/wiki/Hacker" title="Hacker">hackers</a> who exploit vulnerable applications written in PHP. Software vulnerabilities related to PHP are identified among the <a href="http://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures" title="Common Vulnerabilities and Exposures">CVE (Common Vulnerabilities and Exposures)</a> records, available from the <a href="http://en.wikipedia.org/wiki/National_Vulnerability_Database" title="National Vulnerability Database">National Vulnerability Database</a>. The proportion of vulnerabilities related to PHP, out of the total of all common vulnerabilities, amounted to: 12% in 2003, 20% in 2004, 28% in 2005, 43% in 2006, 36% in 2007, and 33.8% for the first quarter of 2008. More than a quarter of all software vulnerabilities listed in this database are related to PHP, and more than a third of vulnerabilities listed recently. Most of these vulnerabilities can be exploited remotely, that is without being logged on the computer hosting the vulnerable application. Such exploitation is made possible due to poor programming habits, such as failing to check data before entering it into a database, and features of the language such as <code>register_globals</code>, which is now deprecated. These result in <a href="http://en.wikipedia.org/wiki/Code_injection" title="Code injection">code injection</a>, <a href="http://en.wikipedia.org/wiki/Cross-site_scripting" title="Cross-site scripting">cross-site scripting</a> and other <a href="http://en.wikipedia.org/wiki/Application_security" title="Application security">application security</a> issues. It's important to note that none of these attacks are exclusive to PHP and all are avoidable by following proper coding techniques and principles.</p>
+
+<p class="attribution">This excerpt is adapted from <a href="http://en.wikipedia.org/wiki/PHP#Security">Wikipedia: PHP - security</a>. It is used here under a <a href="http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License">Creative Commons BY-SA 3.0</a> license.</p>
+
diff --git a/sites/all/modules/advanced_help/help_example/help/syntax.html b/sites/all/modules/advanced_help/help_example/help/syntax.html
new file mode 100644
index 000000000..1c4b1133b
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/syntax.html
@@ -0,0 +1,34 @@
+<h2>Syntax</h2>
+<p>PHP only parses code within its <a href="http://en.wikipedia.org/wiki/Delimiter" title="Delimiter">delimiters</a>. Anything outside its delimiters is sent directly to the output and is not parsed by PHP. The most common delimiters are <span s>&lt;?php</span> and <span>?&gt;</span>, which are open and close delimiters respectively. <span>&lt;script language="php"&gt;</span> and <span>&lt;/script&gt;</span> delimiters are also available. Short tags (<span>&lt;?</span> or <span>&lt;?=</span> and <span>?&gt;</span>) are also commonly used, but like ASP-style tags (<span>&lt;%</span> or <span>&lt;%=</span> and <span>%&gt;</span>), they are less portable as they can be disabled in the PHP configuration. For this reason, the use of short tags and ASP-style tags is discouraged. The purpose of these delimiters is to separate PHP code from non-PHP code, including HTML. Everything outside the delimiters is ignored by the parser and is passed through as output.</p>
+
+<p>Variables are prefixed with a <a href="http://en.wikipedia.org/wiki/Dollar_sign" title="Dollar sign">dollar symbol</a> and a <a href="http://en.wikipedia.org/wiki/Primitive_type" title="Primitive type">type</a> does not need to be specified in advance. Unlike function and class names, variable names are case sensitive. Both double-quoted (<span>""</span>) and <a href="http://en.wikipedia.org/wiki/Heredoc" class="mw-redirect" title="Heredoc">heredoc</a> strings allow the ability to embed the variable's value into the string. PHP treats <a href="http://en.wikipedia.org/wiki/Newline" title="Newline">newlines</a> as <a href="http://en.wikipedia.org/wiki/Whitespace_%28computer_science%29" title="Whitespace (computer science)">whitespace</a> in the manner of a <a href="http://en.wikipedia.org/wiki/Free-form_language" title="Free-form language">free-form language</a> (except when inside string quotes), and statements are terminated by a semicolon. PHP has three types of <a href="http://en.wikipedia.org/wiki/Comparison_of_programming_languages_%28syntax%29#Comments" title="Comparison of programming languages (syntax)">comment syntax</a>: <span>/* */</span> serves as block comments, and <span>//</span> as well as <span>#</span> are used for inline comments. To output text to the browser, either the <tt>print</tt> function or the <tt>echo</tt> function is used. Both functions are nearly identical; the major difference is that <tt>print</tt> is slower than <tt>echo</tt> because the former will return a status indicating if it was successful or not, whereas the latter does not return a status and only returns the text for output.</p>
+
+<p><a name="Data_types" id="Data_types"></a></p>
+<h3><span class="mw-headline">Data types</span></h3>
+<p>PHP stores whole numbers in a platform-dependent range. This range is typically that of 32-bit <a href="http://en.wikipedia.org/wiki/Signed_number_representations" title="Signed number representations">signed</a> <a href="http://en.wikipedia.org/wiki/Integer_%28computer_science%29" title="Integer (computer science)">integers</a>. Unsigned integers are converted to signed values in certain situations; this behavior is different from other programming languages. Integer variables can be assigned using decimal (positive and negative), <a href="http://en.wikipedia.org/wiki/Octal" title="Octal">octal</a>, and <a href="http://en.wikipedia.org/wiki/Hexadecimal" title="Hexadecimal">hexadecimal</a> notations. <a href="http://en.wikipedia.org/wiki/Real_numbers" class="mw-redirect" title="Real numbers">Real numbers</a> are also stored in a platform-specific range. They can be specified using <a href="http://en.wikipedia.org/wiki/Floating_point" title="Floating point">floating point</a> notation, or two forms of <a href="http://en.wikipedia.org/wiki/Scientific_notation" title="Scientific notation">scientific notation</a>. PHP has a native <a href="http://en.wikipedia.org/wiki/Boolean_datatype" title="Boolean datatype">Boolean</a> type that is similar to the native Boolean types in <a href="http://en.wikipedia.org/wiki/Java_%28programming_language%29" title="Java (programming language)">Java</a> and <a href="http://en.wikipedia.org/wiki/C%2B%2B" title="C++">C++</a>. Using the Boolean type conversion rules, non-zero values are interpreted as true and zero as false, as in Perl and C++. The null data type represents a variable that has no value. The only value in the null data type is <i>NULL</i>. Variables of the "resource" type represent references to resources from external sources. These are typically created by functions from a particular extension, and can only be processed by functions from the same extension; examples include file, image, and database resources. Arrays can contain elements of any type that PHP can handle, including resources, objects, and even other arrays. Order is preserved in lists of values and in <a href="http://en.wikipedia.org/wiki/Hash_table" title="Hash table">hashes</a> with both keys and values, and the two can be intermingled. PHP also supports <a href="http://en.wikipedia.org/wiki/String_%28computing%29" class="mw-redirect" title="String (computing)">strings</a>, which can be used with single quotes, double quotes, or <a href="http://en.wikipedia.org/wiki/Heredoc" class="mw-redirect" title="Heredoc">heredoc syntax</a>.</p>
+
+<p><a name="Functions" id="Functions"></a></p>
+<h3><span class="mw-headline">Functions</span></h3>
+<p>PHP has hundreds of base functions and thousands more from extensions. Functions are not <a href="http://en.wikipedia.org/wiki/First-class_function" title="First-class function">first-class functions</a> and can only be referenced by their name. User-defined functions can be created at any time without being prototyped. Functions can be defined inside code blocks, permitting a <a href="http://en.wikipedia.org/wiki/Dynamic_dispatch" title="Dynamic dispatch">run-time decision</a> as to whether or not a function should be defined. Function calls must use parentheses, with the exception of zero argument class <a href="http://en.wikipedia.org/wiki/Constructor_%28computer_science%29" title="Constructor (computer science)">constructor</a> functions called with the PHP <span>new</span> operator, where parentheses are optional. PHP supports quasi-<a href="http://en.wikipedia.org/wiki/Anonymous_function" title="Anonymous function">anonymous functions</a> through the <span>create_function()</span> function, although they are not true anonymous functions because anonymous functions are nameless, but functions can only be referenced by name, or indirectly through a variable <span>$function_name();</span>, in PHP.</p>
+
+<p><a name="Objects" id="Objects"></a></p>
+<h3><span class="mw-headline">Objects</span></h3>
+<p>Basic <a href="http://en.wikipedia.org/wiki/Object-oriented_programming" title="Object-oriented programming">object-oriented programming</a> functionality was added in PHP 3. Object handling was completely rewritten for PHP 5, expanding the feature set and enhancing performance. In previous versions of PHP, objects were handled like <a href="http://en.wikipedia.org/wiki/Primitive_type" title="Primitive type">primitive types</a>. The drawback of this method was that the whole object was copied when a variable was assigned or passed as a parameter to a method. In the new approach, objects are referenced by <a href="http://en.wikipedia.org/wiki/Smart_pointer#Handles" title="Smart pointer">handle</a>, and not by value. PHP 5 introduced private and protected <a href="http://en.wikipedia.org/wiki/Member_variable" class="mw-redirect" title="Member variable">member variables</a> and methods, along with <a href="http://en.wikipedia.org/wiki/Abstract_type" title="Abstract type">abstract classes</a> and <a href="http://en.wikipedia.org/wiki/Final_type" class="mw-redirect" title="Final type">final classes</a> as well as <a href="http://en.wikipedia.org/wiki/Abstract_method" class="mw-redirect" title="Abstract method">abstract methods</a> and <a href="http://en.wikipedia.org/wiki/Final_method" class="mw-redirect" title="Final method">final methods</a>. It also introduced a standard way of declaring <a href="http://en.wikipedia.org/wiki/Constructor_%28computer_science%29" title="Constructor (computer science)">constructors</a> and <a href="http://en.wikipedia.org/wiki/Destructor_%28computer_science%29" title="Destructor (computer science)">destructors</a>, similar to that of other object-oriented languages such as <a href="http://en.wikipedia.org/wiki/C%2B%2B" title="C++">C++</a>, and a standard <a href="http://en.wikipedia.org/wiki/Exception_handling" title="Exception handling">exception handling</a> model. Furthermore, PHP 5 added <a href="http://en.wikipedia.org/wiki/Interfaces" class="mw-redirect" title="Interfaces">interfaces</a> and allowed for multiple interfaces to be implemented. There are special interfaces that allow objects to interact with the runtime system. <a href="http://en.wikipedia.org/wiki/Object" title="Object">Objects</a> implementing <a href="http://en.wikipedia.org/wiki/ArrayAccess" class="mw-redirect" title="ArrayAccess">ArrayAccess</a> can be used with array syntax and <a href="http://en.wikipedia.org/wiki/Object" title="Object">objects</a> implementing <a href="http://en.wikipedia.org/wiki/Iterator" title="Iterator">Iterator</a> or <a href="http://en.wikipedia.org/wiki/IteratorAggregate" class="mw-redirect" title="IteratorAggregate">IteratorAggregate</a> can be used with the <span>foreach</span> language construct. There is no <a href="http://en.wikipedia.org/wiki/Virtual_table" class="mw-redirect" title="Virtual table">virtual table</a> feature in the engine, so <a href="http://en.wikipedia.org/wiki/Static_variable" title="Static variable">static variables</a> are bound with a name instead of a reference at compile time.</p>
+
+<p>If the developer creates a copy of an object using the reserved word <i>clone</i>, the Zend engine will check if a <tt>__clone()</tt> method has been defined or not. If not, it will call a default <tt>__clone()</tt> which will copy the object's properties. If a <tt>__clone()</tt> method is defined, then it will be responsible for setting the necessary properties in the created object. For convenience, the engine will supply a function that imports the properties of the source object, so that the programmer can start with a by-value <a href="http://en.wiktionary.org/wiki/replica" class="extiw" title="wikt:replica">replica</a> of the source object and only override properties that need to be changed.</p>
+
+<p><a name="Resources" id="Resources"></a></p>
+<h2> <span class="mw-headline">Resources</span></h2>
+<p>PHP includes <a href="http://en.wikipedia.org/wiki/List_of_PHP_libraries" title="List of PHP libraries">free and open source libraries</a> with the core build. PHP is a fundamentally <a href="http://en.wikipedia.org/wiki/Internet" title="Internet">Internet</a>-aware system with modules built in for accessing <a href="http://en.wikipedia.org/wiki/File_transfer_protocol" class="mw-redirect" title="File transfer protocol">FTP</a> servers, many database servers, embedded SQL libraries such as embedded <a href="http://en.wikipedia.org/wiki/MySQL" title="MySQL">MySQL</a> and <a href="http://en.wikipedia.org/wiki/SQLite" title="SQLite">SQLite</a>, <a href="http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol" title="Lightweight Directory Access Protocol">LDAP</a> servers, and others. Many functions familiar to C programmers such as those in the <tt><a href="http://en.wikipedia.org/wiki/Stdio.h" title="Stdio.h">stdio</a></tt> family are available in the standard PHP build. PHP has traditionally used features such as "<a href="http://en.wikipedia.org/wiki/Magic_quotes" title="Magic quotes">magic_quotes_gpc</a>" and "magic_quotes_runtime" which attempt to escape apostrophes (') and quotes (") in strings in the assumption that they will be used in databases, to prevent <a href="http://en.wikipedia.org/wiki/SQL_injection" title="SQL injection">SQL injection</a> attacks. This leads to confusion over which data is escaped and which is not, and to problems when data is not in fact used as input to a database and when the escaping used is not completely correct. To make code portable between servers which do and do not use magic quotes, developers can preface their code with a script to reverse the effect of magic quotes when it is applied.</p>
+
+<p>PHP allows developers to write <a href="http://en.wikipedia.org/wiki/Extension_%28computing%29" class="mw-redirect" title="Extension (computing)">extensions</a> in <a href="http://en.wikipedia.org/wiki/C_%28programming_language%29" title="C (programming language)">C</a> to add functionality to the PHP language. These can then be compiled into PHP or loaded dynamically at runtime. Extensions have been written to add support for the <a href="http://en.wikipedia.org/wiki/Windows_API" title="Windows API">Windows API</a>, process management on <a href="http://en.wikipedia.org/wiki/Unix-like" title="Unix-like">Unix-like</a> <a href="http://en.wikipedia.org/wiki/Operating_system" title="Operating system">operating systems</a>, multibyte strings (<a href="http://en.wikipedia.org/wiki/Unispan" title="Unispan">Unispan</a>), <a href="http://en.wikipedia.org/wiki/CURL" title="CURL">cURL</a>, and several popular <a href="http://en.wikipedia.org/wiki/Compression_formats" class="mw-redirect" title="Compression formats">compression formats</a>. Some more unusual features include integration with <a href="http://en.wikipedia.org/wiki/Internet_relay_chat" class="mw-redirect" title="Internet relay chat">Internet relay chat</a>, dynamic generation of images and <a href="http://en.wikipedia.org/wiki/Adobe_Flash" title="Adobe Flash">Adobe Flash</a> content, and even <a href="http://en.wikipedia.org/wiki/Speech_synthesis" title="Speech synthesis">speech synthesis</a>. The <a href="http://en.wikipedia.org/wiki/PHP_Extension_Community_Library" class="mw-redirect" title="PHP Extension Community Library">PHP Extension Community Library</a> (PECL) project is a repository for extensions to the PHP language.</p>
+
+<p>As with many scripting languages, PHP scripts are normally kept as human-readable source code, even on production web servers. While this allows flexibility, releasing scripts in source form is undesirable for commercial software developers, and can raise issues with security of web servers; as an example, if a hacker acquires control of a server, database passwords may be quickly discovered, and undesirable changes to scripts may be made that remain undiscovered indefinitely. Various encoding tools are available for PHP to offer code protection.</p>
+<p>span optimizers improve the quality of the compiled code by reducing its size and making changes that can reduce the execution time and improve performance. The nature of the PHP <a href="http://en.wikipedia.org/wiki/Compiler" title="Compiler">compiler</a> is such that there are often opportunities for <a href="http://en.wikipedia.org/wiki/Optimization_%28computer_science%29" title="Optimization (computer science)">span optimization</a>, and an example of a code optimizer is the <a href="http://en.wikipedia.org/wiki/PHP_accelerator#Zend_Optimizer" title="PHP accelerator">Zend Optimizer</a> PHP extension.</p>
+
+<p><a href="http://en.wikipedia.org/wiki/PHP_accelerator" title="PHP accelerator">PHP accelerators</a> can offer significant performance gains by <a href="http://en.wikipedia.org/wiki/Caching" class="mw-redirect" title="Caching">caching</a> the compiled form of a PHP script in <a href="http://en.wikipedia.org/wiki/Shared_memory" title="Shared memory">shared memory</a> to avoid the overhead of <a href="http://en.wikipedia.org/wiki/Parsing" title="Parsing">parsing</a> and <a href="http://en.wikipedia.org/wiki/Compiling" class="mw-redirect" title="Compiling">compiling</a> the code every time the script runs. They may also perform <a href="http://en.wikipedia.org/wiki/span_optimization" class="mw-redirect" title="span optimization">span optimization</a> to provide increased execution performance.</p>
+
+<p class="attribution">This excerpt is adapted from <a href="http://en.wikipedia.org/wiki/PHP#Syntax">Wikipedia: PHP - syntax</a>. It is used here under a <a href="http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License">Creative Commons BY-SA 3.0</a> license.</p>
+
+
+
diff --git a/sites/all/modules/advanced_help/help_example/help/usage.html b/sites/all/modules/advanced_help/help_example/help/usage.html
new file mode 100644
index 000000000..f26c127e6
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help/usage.html
@@ -0,0 +1,11 @@
+<p>PHP is a general-purpose scripting language that is especially suited for <a href="http://en.wikipedia.org/wiki/Web_development" title="Web development">web development</a>. It is the fourth most popular computer programming language, ranking behind <a href="http://en.wikipedia.org/wiki/Java_%28programming_language%29" title="Java (programming language)">Java</a>, <a href="http://en.wikipedia.org/wiki/C_%28programming_language%29" title="C (programming language)">C</a>, and <a href="http://en.wikipedia.org/wiki/Visual_Basic" title="Visual Basic">Visual Basic</a>. PHP generally runs on a <a href="http://en.wikipedia.org/wiki/Web_server" title="Web server">web server</a>, taking PHP code as its input and creating <a href="http://en.wikipedia.org/wiki/Web_page" title="Web page">web pages</a> as output. It can also be used for <a href="http://en.wikipedia.org/wiki/Command-line" class="mw-redirect" title="Command-line">command-line</a> scripting and <a href="http://en.wikipedia.org/wiki/Client-side" title="Client-side">client-side</a> <a href="http://en.wikipedia.org/wiki/Graphical_user_interface" title="Graphical user interface">GUI</a> applications. PHP can be deployed on most <a href="http://en.wikipedia.org/wiki/Web_server" title="Web server">web servers</a>, many <a href="http://en.wikipedia.org/wiki/Operating_system" title="Operating system">operating systems</a> and <a href="http://en.wikipedia.org/wiki/Platform_%28computing%29" class="mw-redirect" title="Platform (computing)">platforms</a>, and can be used with many <a href="http://en.wikipedia.org/wiki/Relational_database_management_system" title="Relational database management system">relational database management systems</a>. It is available free of charge, and the PHP Group provides the complete source code for users to build, customize and extend for their own use.</p>
+
+<p>PHP primarily acts as a <a href="http://en.wikipedia.org/wiki/Filter_%28software%29" title="Filter (software)">filter</a>, taking input from a file or stream containing text and/or PHP instructions and outputs another stream of data; most commonly the output will be HTML. From PHP 4, the PHP <a href="http://en.wikipedia.org/wiki/Parser" class="mw-redirect" title="Parser">parser</a> <a href="http://en.wikipedia.org/wiki/Compiler" title="Compiler">compiles</a> input to produce <a href="http://en.wikipedia.org/wiki/Bytecode" title="Bytecode">bytecode</a> for processing by the <a href="http://en.wikipedia.org/wiki/Zend_Engine" title="Zend Engine">Zend Engine</a>, giving improved performance over its <a href="http://en.wikipedia.org/wiki/Interpreter_%28computing%29" title="Interpreter (computing)">interpreter</a> predecessor.</p>
+
+<p>Originally designed to create dynamic web pages, PHP's principal focus is <a href="http://en.wikipedia.org/wiki/Server-side_scripting" title="Server-side scripting">server-side scripting</a>, and it is similar to other server-side scripting languages that provide dynamic content from a web server to a <a href="http://en.wikipedia.org/wiki/Client_%28computing%29" title="Client (computing)">client</a>, such as <a href="http://en.wikipedia.org/wiki/Microsoft" title="Microsoft">Microsoft</a>'s <a href="http://en.wikipedia.org/wiki/ASP.NET" title="ASP.NET">ASP.NET</a> system, <a href="http://en.wikipedia.org/wiki/Sun_Microsystems" title="Sun Microsystems">Sun Microsystems</a>' <a href="http://en.wikipedia.org/wiki/JavaServer_Pages" title="JavaServer Pages">JavaServer Pages</a>, and <a href="http://en.wikipedia.org/wiki/Mod_perl" title="Mod perl">mod_perl</a>. PHP has also attracted the development of many <a href="http://en.wikipedia.org/wiki/Software_framework" title="Software framework">frameworks</a> that provide building blocks and a design structure to promote <a href="http://en.wikipedia.org/wiki/Rapid_application_development" title="Rapid application development">rapid application development</a> (RAD). Some of these include <a href="http://en.wikipedia.org/wiki/CakePHP" title="CakePHP">CakePHP</a>, <a href="http://en.wikipedia.org/wiki/PRADO" title="PRADO">PRADO</a>, <a href="http://en.wikipedia.org/wiki/Symfony" title="Symfony">Symfony</a> and <a href="http://en.wikipedia.org/wiki/Zend_Framework" title="Zend Framework">Zend Framework</a>, offering features similar to other <a href="http://en.wikipedia.org/wiki/List_of_web_application_frameworks" title="List of web application frameworks">web application frameworks</a>.</p>
+
+<p>The <a href="http://en.wikipedia.org/wiki/LAMP_%28software_bundle%29" title="LAMP (software bundle)">LAMP</a> architecture has become popular in the web industry as a way of deploying web applications. PHP is commonly used as the <i>P</i> in this bundle alongside <a href="http://en.wikipedia.org/wiki/Linux" title="Linux">Linux</a>, <a href="http://en.wikipedia.org/wiki/Apache_HTTP_Server" title="Apache HTTP Server">Apache</a> and <a href="http://en.wikipedia.org/wiki/MySQL" title="MySQL">MySQL</a>, although the <i>P</i> can also refer to <a href="http://en.wikipedia.org/wiki/Python_%28programming_language%29" title="Python (programming language)">Python</a> or <a href="http://en.wikipedia.org/wiki/Perl" title="Perl">Perl</a>.</p>
+
+<p>As of April 2007, over 20 million Internet domains were hosted on servers with PHP installed, and PHP was recorded as the most popular Apache module.</p>
+
+<p class="attribution">This excerpt is adapted from <a href="http://en.wikipedia.org/wiki/PHP#Use">Wikipedia: PHP - use</a>. It is used here under a <a href="http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License">Creative Commons BY-SA 3.0</a> license.</p>
diff --git a/sites/all/modules/advanced_help/help_example/help_example.info b/sites/all/modules/advanced_help/help_example/help_example.info
new file mode 100644
index 000000000..7960ca119
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help_example.info
@@ -0,0 +1,40 @@
+name = Advanced help example
+description = A example help module to demonstrate the advanced help module.
+core = 7.x
+dependencies[] = advanced_help
+
+attributions[wikipedia_adaption][type] = 'asset'
+attributions[wikipedia_adaption][weight] = -10
+attributions[wikipedia_adaption][exception] = '#2460769'
+attributions[wikipedia_adaption][title] = 'About PHP, History of PHP, Usage of PHP, PHP Syntax, Security of PHP'
+attributions[wikipedia_adaption][license_url] = 'http://creativecommons.org/licenses/by-sa/3.0/'
+attributions[wikipedia_adaption][org_title] = 'PHP (Wikipedia)'
+attributions[wikipedia_adaption][org_author] = 'multiple Wikipedia contributors'
+attributions[wikipedia_adaption][org_author_url] = 'http://en.wikipedia.org/w/index.php?title=PHP&action=history'
+attributions[wikipedia_adaption][org_work_url] = 'http://en.wikipedia.org/wiki/PHP'
+attributions[wikipedia_adaption][org_license] = 'Creative Commons BY-SA 3.0'
+attributions[wikipedia_adaption][org_license_url] = 'http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License'
+
+attributions[lerdorf][type] = 'asset'
+attributions[lerdorf][exception] = '#2460769'
+attributions[lerdorf][author] = 'Jud Dagnall'
+attributions[lerdorf][work_url] = 'http://en.wikipedia.org/wiki/File:Lerdorf.jpg'
+attributions[lerdorf][title] = 'Lerdorf'
+attributions[lerdorf][license] = 'Creative Commons BY-SA 3.0'
+attributions[lerdorf][license_url] = 'http://creativecommons.org/licenses/by-sa/3.0/deed.en'
+
+attributions[gutmans][type] = 'asset'
+attributions[gutmans][exception] = '#2460769'
+attributions[gutmans][author] = 'Jim Winstead'
+attributions[gutmans][author_url] = 'https://www.flickr.com/people/81342178@N00'
+attributions[gutmans][work_url] = 'http://en.wikipedia.org/wiki/File:Andi_Gutmans_1.jpg'
+attributions[gutmans][title] = 'Andi Gutmans'
+attributions[gutmans][license] = 'Creative Commons BY 2.0'
+attributions[gutmans][license_url] = 'http://creativecommons.org/licenses/by/2.0/deed.en'
+
+; Information added by Drupal.org packaging script on 2015-05-25
+version = "7.x-1.3"
+core = "7.x"
+project = "advanced_help"
+datestamp = "1432557782"
+
diff --git a/sites/all/modules/advanced_help/help_example/help_example.module b/sites/all/modules/advanced_help/help_example/help_example.module
new file mode 100644
index 000000000..43ab81cea
--- /dev/null
+++ b/sites/all/modules/advanced_help/help_example/help_example.module
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @file
+ * Provide example help for the advanced help module.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function help_example_menu() {
+ // View help topic index.
+ $items['admin/help_example'] = array(
+ 'title' => 'Example help',
+ 'page callback' => 'help_example_index_page',
+ 'access arguments' => array('view advanced help index'),
+ 'weight' => 9,
+ );
+ return $items;
+}
+
+/**
+ * Topic index callback.
+ */
+function help_example_index_page() {
+ $output = theme('advanced_help_topic', array(
+ 'module' => 'help_example',
+ 'topic' => 'about-php',
+ ));
+ $output .= '&nbsp;' . t('Click the help icon to view some example help about the PHP programming language (from wikipedia.org). Be sure to run cron to update the index if you want to try out the search features.');
+ return $output;
+}
diff --git a/sites/all/modules/advanced_help/translations/help/nb/advanced_help.help.ini b/sites/all/modules/advanced_help/translations/help/nb/advanced_help.help.ini
new file mode 100644
index 000000000..daf4c7f2b
--- /dev/null
+++ b/sites/all/modules/advanced_help/translations/help/nb/advanced_help.help.ini
@@ -0,0 +1,14 @@
+[readme]
+title = LESMEG
+
+[using-advanced-help]
+title = Bruk av Avansert hjelp
+
+[translation]
+title = Oversettelse av Avansert hjelp
+
+[ini-file]
+title = Avansert hjelp .ini-filformat
+
+[why-advanced-help]
+title = Hvorfor Avansert hjelp?
diff --git a/sites/all/modules/advanced_help/translations/help/nb/click_icon.png b/sites/all/modules/advanced_help/translations/help/nb/click_icon.png
new file mode 100644
index 000000000..ff41a3333
--- /dev/null
+++ b/sites/all/modules/advanced_help/translations/help/nb/click_icon.png
Binary files differ
diff --git a/sites/all/modules/advanced_help/translations/help/nb/ini-file.html b/sites/all/modules/advanced_help/translations/help/nb/ini-file.html
new file mode 100644
index 000000000..05b85e435
--- /dev/null
+++ b/sites/all/modules/advanced_help/translations/help/nb/ini-file.html
@@ -0,0 +1,117 @@
+<p>The advanced help configuration file is in simple .ini file format.
+It has an optional section for global settings that might be inherited
+for each help file, followed by sections for each help file.</p>
+
+<p>Global settings may be put into a section named <code>[advanced help
+settings]</code>. This means that this name is reserved and it cannot
+be a help file in any module or theme. The following settings may be
+set in this section, with the default value (if any) in brackets.</p>
+
+<dl>
+<dt><code>line break</code> (FALSE)</dt>
+<dd>If set, the line break filter will be applied to all help files
+defined by this module or theme, unless that help file specifically is
+set otherwise. The line break converts line breaks
+into <code>br</code> and <code>p</code> tags automatically.</dd>
+
+<dt><code>navigation</code> (TRUE)</dt>
+<dd>If set, this navigation will be displayed at the end of the topic:
+<em>previous topic</em>, Up (parent), <em>next topic</em>.</dd>
+
+<dt><code>css</code></dt>
+<dd>Specify a css file that will be used for all help files (unless
+overridden), including the .css extension. This .css file must reside
+in the help directory along with the .html files, and will not be
+affected by translation.</dd>
+
+<dt><code>name</code></dt>
+<dd>May be set to override the module or theme name as displayed in
+both the module/theme index as well as the navigation and breadcrumb
+trail. Usually, this is not set, but for some projects you may want to
+use a more friendly name than appears in the project's .info file.</dd>
+
+<dt><code>index name</code></dt>
+<dd>This may be set to change the name of the module or theme in the
+module/theme index. It overrides the <code>name</code> setting above,
+as well as the project's name in its .info file.</dd>
+
+<dt><code>hide</code> (FALSE)</dt>
+<dd>This may be used to hide a module or theme in the module/theme
+index. This is particularly useful for modules who insert their help
+files into the hierarchy of another module or theme, as might be done
+by modules that extend <strong>Views</strong> or derived themes that
+extend base themes like <strong>Zen</strong>. By setting this to TRUE
+the project will not appear as its own entry.</dd>
+</dl>
+
+<p>Each section after that will correspond to a single help file for a
+single topic. It starts with the topic name in square brackets
+(e.g. <code>[ini-file]</code>). The following settings may be set for
+each file/topic, with the default value (if any) in brackets.</p>
+
+<dl>
+<dt><code>title</code></dt>
+<dd>The title of the topic, presented to the user and used in
+links. If you have special characters in your title, be sure to
+enclose it in quotes. (This setting is currently not optional.)</dd>
+
+<dt><code>file</code> (topic name)</dt>
+<dd>The filename, without the .html extension, used for the file with
+the help text for the topic. This is optional; if not specified, the
+topic name wil be the file name.</dd>
+
+<dt><code>weight</code> (0)</dt>
+<dd>The weight, used for sorting topics on the administration
+page. The default is 0 (zero) if unspecified. Items with the same weight
+are sorted alphabetically.</dd>
+
+<dt><code>parent</code></dt>
+<dd>The topic ID to use in a hierarchy; children will be listed
+beneath parents in the topic list, and will have the parent in their
+breadcrumb trail. You may parent this topic to a topic in another
+module or theme by using <code>module%topic</code>
+or <code>theme%topic</code> as the identifier,
+where <code>module</code> or <code>theme</code> is the project's short
+name. For example if parent is set to, '<code>views%display</code>',
+the topic will be regarded as a child of the
+<code>display</code> topic in the <strong>Views</strong> module.</dd>
+
+<dt><code>line break</code> (FALSE)</dt>
+<dd>Set the line break filter for this topic. Set to FALSE to disable
+the line break filter if this has been turned on in the global
+settings.</dd>
+
+<dt><code>css</code></dt>
+<dd>Specify a css file that will be used for this file. This .css file
+must reside in the help directory along with the .html files. This
+will override any .css file added by the global settings.</dd>
+
+<dt><code>popup width</code> (500)</dt>
+<dd>The width in pixels of the popup window.</dd>
+
+<dt><code>popup height</code> (500)</dt>
+<dd>The height in pixels of the popup window.</dd>
+</dl>
+
+<p>For example, here is a version of the <code>advanced_help.help.ini</code> file:</p>
+
+<pre>
+[readme]
+title = README
+weight = -11
+
+[using-advanced-help]
+title = Using advanced help
+weight = -10
+
+[translation]
+title = Translating advanced help
+
+[ini-file]
+title = Help .ini file format
+line break = FALSE
+
+[why-advanced-help]
+title = Why advanced help?
+line break = TRUE
+</pre>
diff --git a/sites/all/modules/advanced_help/translations/help/nb/readme.html b/sites/all/modules/advanced_help/translations/help/nb/readme.html
new file mode 100644
index 000000000..cdd9a593c
--- /dev/null
+++ b/sites/all/modules/advanced_help/translations/help/nb/readme.html
@@ -0,0 +1,116 @@
+<h2>Om denne oversettelsen</h2>
+
+<p>Dette er en (delvis) oversettelse til <em>norsk</em> (bokmål) av
+hjelpeteksene for <strong>Avansert hjelp</strong>. Den er i første
+rekke laget for å vise hvordan en oversettelse av hjelpetekstene ser
+ut.</p>
+
+<h2 id="project-description">Synopsis</h2>
+
+<p>Modulen <strong>Avansert hjelp</strong> tilbyr et rammeverk som gjør
+det mulig for modul- og theme-utviklere å integrere hjelpetekster i et
+Drupal-nettsted.</p>
+
+<p>Disse hjelpetekstene lagres som vanlige <code>.html</code>-filer
+som lever i filsystemet (ikke i databasen). Disse filene distribueres
+fra prosjektes repo på Drupal.org i den samme pakken som prosjektets
+øvrige filer, og plasseres i en en underkatalog med navnet
+<code>help</code> i prosjektets katalog. Det innebærer at
+hjelpetekstene på en enkel måte kan holdes synkronisert med det
+prosjektet hjelpen er knyttet til, men også at tilgangen til disse
+filene ikke er underlagt de tilgangsbegrensningene som Drupal
+administrerer.</p>
+
+<p>Hjelpetekstene kan bruke standard HTML-markeringer De vil bli vist
+med ditt nettsteds theme.</p>
+
+<p>Dersom prosjektets forfatter ikke bruker HTML-rammeverket for
+<em>Avansert hjelp</em>, men det finnes en
+<code>README.md</code> eller <code>README.txt</code> i pakken,
+så vil innholdet av den filen vises i stedet.</p>
+
+<p>Hjelpetekstene kan vises i et sprettopp-vindu eller ikke, ut fra
+prosjektets preferanser. Ved å fjerne tilgangen til å se
+sprettopp-vinduer kan nedstedet skjule sprettopp-vinduer fra
+brukere.</p>
+
+<p>Hjelpetekstene kan organiseres hierarkisk, noe som gjør det mulig
+og navigere fra toppen av og nedover for hjelp.</p>
+
+<p>Hjelpetekstene kan gjøres søkbare. Dersom søk av avansert hjelp er
+slått på, vil all hjelpetekst indekseres. Dette innebærer at alt
+innhold på sidene med avansert hjelp er søkbart med nøkkelord.</p>
+
+<h2 id="use">Bruk av modulen</h2>
+
+<p>When you enable the module, a new tab with the legend “Advanced
+help” will show up under “Help”:
+
+<div class="ta-center">
+<img class="help-img-center" alt="ahelp_tab.png" src="&path&ahelp_tab.png" width="661" height="225" border="1" />
+</div>
+
+<p>By itself, this module doesn't do much. The <strong>Avansert
+hjelp</strong> assists other modules and themes in showing help texts.
+Nothing will show up until you enable at least one other module that
+makes use of the advanced help framework or comes with a file
+named <code>README.md</code> or <code>README.txt</code>. However, it
+comes with a small companion demo module named
+<strong>Eksempel på hjelp</strong> to demonstrate how it works.
+For more extensive example of use of the advanced help features, see
+the <strong>Views</strong> project.</p>
+
+<!--
+<h2 id="project-recommended">Anbefalte moduler</h2>
+
+<ul>
+<li><a href="https://www.drupal.org/project/markdown">Markdown filter</a>:<br>
+When this module is enabled, display of any <code>README.md</code> the
+module shows will be rendered with markdown.</li>
+<li><a href="https://www.drupal.org/project/attributions">Attributions</a>:<br>
+When this module is enabled, attributions of third party content used
+by the project (i.e. some text from Wikipedia) will be available in an
+attribution block and on an atribution page.</li>
+</ul>
+-->
+
+<h2 id="support-status">Status for oppfølging</h2>
+
+<p>Reported bugs for the Drupal 7 branch will be fixed in a timely
+manner. Bugs in the issue queue for the Drupal 6 branch will only be
+fixed if accompanied with a patch, after the patch has been reviewed
+and tested by the community. No Drupal 8 version is currently under
+development. Post a message in
+the <a href="https://www.drupal.org/node/1928218">issue queue</a> if
+you're interested in managing a port of the project to to Drupal
+8. Older versions are no longer supported.</p>
+
+<p>Community support in the form of patches are very welcome for both
+Drupal 6 and Drupal 7 versions, and will be given priority. For QA,
+the project needs community support in the form of reviews of patches,
+development versions and releases.</p>
+
+<p>The primary goal of the module is to remain <strong>light-weight
+and simple</strong>. This means that not all feature requests will be
+implemented, even if they are a good idea. Feature requests
+accompanied by patches are more likely to make it into a release.</p>
+
+<p>The maintainer hopes that the community is willing to help out by
+answering &amp; closing support requests.</p>
+
+<!--
+<h2 id="project-problems">Kjente problemer</h2>
+-->
+
+
+
+<h2 id="project-maintainers">Kreditering</h2>
+
+<ul>
+<li><a href="https://www.drupal.org/u/merlinofchaos"">merlinofchaos</a> (52 commits, opprinnelig forfatter)</li>
+<li><a href="https://www.drupal.org/u/redndahead">redndahead</a> (8 commits)</li>
+<li><a href="https://www.drupal.org/u/dmitrig01">dmitrig01</a> (3 commits)</li>
+<li><a href="https://www.drupal.org/u/amitgoyal">amitgoyal </a> (5 commits)</li>
+<li><a href="https://www.drupal.org/u/gisle">gisle</a> (nåværende administrator, D7)</li>
+<li><a href="https://www.drupal.org/u/gnuget">gnuget</a> (nåværende administrator, D8)</li>
+</ul>
diff --git a/sites/all/modules/advanced_help/translations/help/nb/translation.html b/sites/all/modules/advanced_help/translations/help/nb/translation.html
new file mode 100644
index 000000000..843623818
--- /dev/null
+++ b/sites/all/modules/advanced_help/translations/help/nb/translation.html
@@ -0,0 +1,51 @@
+<p>For å oversette en hjelpe-fil indeksert av <strong>Avansert hjelp</strong>
+må du først opprette en katalog
+<code>translations/help/<em>språk</em></code> i prosjektets rotkatalog der
+<em>språk</em> er språkkoden som vises på siden <em>Språk</em> i det
+administrative grensesnittet.</p>
+
+<p>Deretter kopierer du <code>.ini</code>-fila og alle
+<code>.html</code> filene fra den opprinnelige hjelpe-katalogen
+katalogen til denne katalogen. Om du trenger å endre et bilde for å
+bruke det i en oversettelse, kan du også plassere det endrede bildet
+her.</p>
+
+<p>I emne-delen av <code>.ini</code>-fila trenger du bare å beholde
+emnenavnene (uendret) og titlene (oversatt). Hvis det finnes
+en <code>name</code> eller <code>index name</code> innstilling i
+'advanced help settings'-delen, bør disse beholdes. Alle innstillinger
+som beholdes skal oversettes. Resten av dataene i
+<code>.ini</code>-filen kan fjernes eller ignoreres. </p>
+
+<p>Hver enkelt <code>.html</code>-fil skal deretter oversettes uten å
+endre navn eller plassering.</p>
+
+<p>Når du oversetter en <code>.html</code>-fil, vil du finne at
+nøkkelordet <code>&amp;path&amp;</code> (brukes for bilder og linker)
+vil føre til den opprinnelige katalogen. Hvis du må oversette
+elementer som er lenket, for eksempel bilder som inneholder tekst,
+bruke <code>&amp;trans_path&amp;</code> i stedet. Dette nøkkelordet
+vil peke på den oversatte katalogen. Dette vil tillate deg å velge
+hvilke lenkede elementer, om noen, som vil bli oversatt.</p>
+
+<p> Hvis et emne ikke er oversatt, så vil den uoversatte versjonen
+vil bli vist i stedet.</p>
+
+
+
+<h2>Oversettelse av hjelpetekstene til Avansert hjelp</h2>
+
+<p>Dersom du ønsker å hjelpe til med å oversette
+hjelpetekstene til <strong>Avansert hjelp</strong> til et bestemt språk
+kan du se etter en tråd i <a href="https://www.drupal.org/project/issues/advanced_help">sporingskøen for Advanced help</a>
+med navnet “Translation to XXX” (der
+“XXX” språket du ønsker å oversette hjelpetekstene til).
+Dersom en slik tråd ikke eksisterer kan du opprette den.
+Velg <em>Category</em> “Task”,
+<em>Status</em> “Needs review” og
+<em>Component</em> “Documentation”.
+Last opp filer med oversatte hjelpetekster som
+vedlegg (endre filtype fra <code>.html</code> til <code>.txt</code> for å få lov til å laste opp).</p>
+
+<p>Oversettelser som lastes opp vil bli inkludert i neste versjon dersom de godkjennes av andre brukere (ved at de oppnår status “RTBC”).
+
diff --git a/sites/all/modules/advanced_help/translations/help/nb/using-advanced-help.html b/sites/all/modules/advanced_help/translations/help/nb/using-advanced-help.html
new file mode 100644
index 000000000..ee191498a
--- /dev/null
+++ b/sites/all/modules/advanced_help/translations/help/nb/using-advanced-help.html
@@ -0,0 +1,165 @@
+<p>Modulen <strong>Avansert hjelp</strong> tilbyr et rammeverk som gjør
+det mulig for modul- og theme-utviklere å integrere hjelpetekster i et
+Drupal-nettsted. Selv om <strong>Avansert hjelp</strong> ikke selv
+inneholder generell hjelp tilbyr den et kraftig og enkelt rammeverk som
+prosjekter kan benytte for å tilby egen hjelp.</p>
+
+<p>Prosjekter som benytter <strong>Avansert hjelp</strong> må
+ha en underkatalog med navnet <code>help</code> i sin rotkatalog
+I denne katalogen opprettes filen
+<em>MODULNAVN</em>.help.ini (evt. <em>THEMENAVN</em>.help.ini).
+Formatet på denne fila framgår av følgende eksempel:</p>
+
+<pre>
+[about-php]
+title = About PHP
+file = about-php
+weight = -10
+
+[history]
+title = History of PHP
+parent = about-php
+
+[usage]
+title = Usage of PHP
+weight = 1
+
+[security]
+title = Security of PHP
+weight = 2
+
+[syntax]
+title = PHP syntax
+parent = usage
+</pre>
+
+<p>Lag alltid den engelske versjonen av disse temaene først. Ønsker
+du å ha temaene oversatt til et annet språk følger du anvisningene i
+<a href="&topic:advanced_help/translation&">Oversettelse av
+Avansert hjelp</a>.</p>
+
+<p>Denne fila definerer fem hjelpe-emner (i firkant-parenteser), og
+noen innstillinger for dem.
+Se: <a href="&topic:advanced_help/ini-file&">Avansert hjelp .ini-filformat</a> for
+en liste over definerte innstillinger.</p>
+
+<p>Alle emner er beskrevet av det prosjektet som tilbyr
+emnet, og av emne-id. For å produsere en themet lenke til et
+sprettopp-vindu om et emne, bruk et format etter mønster fra følgende
+eksempel:</p>
+
+<!-- D6
+<pre>
+$output = theme('advanced_help_topic', 'help_example', 'about-php');
+$output .= '&nbsp;' . t('Click the help icon!');
+</pre>
+-->
+
+<!-- D7 -->
+<pre>
+$output = theme('advanced_help_topic', array(
+ 'module' => 'help_example',
+ 'topic' => 'about-php',
+));
+$output .= '&nbsp;' . t('Click the help icon.');
+</pre>
+
+<p>Ikke oversett strengen inne i <code>t()</code>. Bruk alltid engelsk, og benytt Drupals lokaliserings-modul til å oversette.</p>
+
+<p>Forutsatt at strengene er korrekt oversatt (ved hjelp av Drupals lokaliserings-modul) genererer dette følgende markeringer:</p>
+
+<pre>
+&lt;a class="advanced-help-link" title="Om PHP"
+ onclick="var w=window.open(this.href, 'advanced_help_window',
+ 'width=500,height=500,scrollbars,resizable');
+ w.focus(); return false;"
+ href="/help/help_example/about-php?popup=1"&gt;
+&lt;span&gt;Hjelp&lt;/span&gt;
+&lt;/a&gt;
+ Klikk på hjelpe-ikonet.
+&lt;/div&gt;
+</pre>
+
+<p>Dette produserer et klikkbart hjelpe-ikon lik det som vises under:</p>
+
+<div class="ta-center">
+<img class="help-img-center" alt="klikkbart ikon" src="&trans_path&click_icon.png" width="180" height="90" border="0" />
+</div>
+
+<p>Inne i en hjelpe-fil kan du lenke til andre hjelpe-emner ved å
+bruke dette formatet:</p>
+<pre>
+&lt;a href="&amp;topic:module/topic&amp;"&gt;topic&lt;/a&gt;
+</pre>
+<p>Ved å bruke dette formatet i stedet for standard-lenker vil sprettopp-status være konsistent når man trykker på lenker.</p>
+
+<p>For å referere til andre elementer som befinner seg i
+hjelpe-katalogen, som bilder som du ønsker å bake inn i teksten,
+bruk:</p>
+<pre>
+&lt;img src="&amp;path&amp;example.png"/&gt;
+&lt;img src="&amp;trans_path&amp;example.png"/&gt;
+</pre>
+
+<p>Nøkkelordet <code>trans_path</code> vil peke på en oversatt versjon
+av bildet i oversettelse-katalogen og kan benyttes dersom du ønsker å
+vise et bilde med oversatt tekst i stedet for originalen.</p>
+
+<p>For å peke på en normal sti på nettstedet, bruk:</p>
+<pre>
+&lt;a href="&amp;base_url&amp;admin/settings/site-configuration"&gt;anchor text&lt;/a&gt;
+</pre>
+
+<p><strong>NB: </strong> I tidligere versjoner av <strong>Advanced
+help</strong> var det ikke nødvendig å benytte &amp;-tegn rundt
+<code>topic:</code>, <code>path</code>, og <code>base_url</code>.
+Dette fungerer fortsatt, men kan bli fjernet i en senere versjon.
+Ved å bruke &amp;-tegn for å markere disse nøkkelordene kan de
+brukes overalt, og ikke bare som parametere til
+<code>href=""</code> og <code>src=""</code>.</p>
+
+<h2 id="access-control">Adgangskontroll</h2>
+
+<p>Når denne modulen er installert vil brukere med tillatelsen
+<code>view advanced help index</code>
+se indeks-siden for <strong>Avansert hjelp</strong> ved å gå til
+<em>Administrasjon &rarr; Advanced Help</em>
+(sti: <code>admin/advanced_help</code>). Øvrige tillatelser
+<code>view advanced help topic</code> og
+<code>view advanced help popup</code>
+gir brukere adgang til de aktuelle hjelpe-sidene og sprettopp-vinduer.</p>
+
+<p>Hjelpeteksten er lagret som vanlige <code>.html</code>-filer og
+kan, med mindre de beskyttes, bli sett av alle som kjenner deres URL.
+Dersom du ønsker å beskytte dem kan du beskytte dem mot innsyn ved å
+plassere følgende fire linjer i en fil med navn
+<code>.htaccess</code> i katalogen <code>help</code> i prosjektkatalogen:</p>
+
+<pre>
+&lt;Files *\.html&gt;
+Order Allow,Deny
+Deny from all
+&lt;/Files&gt;
+</pre>
+
+<p>Det er nettstedsansvarlig som har ansvaret for at denne typen
+beskyttelse er på plass dersom nettstedet har hjelpe-filer som må
+beskyttes fra direkte innsyn.</p>
+
+<p>Se også denne tråden i prosjektets sporings-kø:
+<a href="https://www.drupal.org/node/1980936">#1980936 Typing complete path to .html help files in module bypasses user permissions</a>.</p>
+
+<h2 id="search">Søk</h2>
+
+<p>For å skru på søk for <strong>Avansert hjelp</strong>, naviger til
+<em>Administrasjon → Oppsett → Søk og metadata → Søkeinnstillinger</em>.
+Rull ned til feltet <em>Aktive søkemoduler</em> og kryss av feltet til venstre for
+“Advanced help”. Søkefeltet vil deretter dukke opp på toppen av indekssidene for
+<strong>Avansert hjelp</strong>.</p>
+
+<p>Dersom modulen <strong>Search</strong> i core er skrudd på, vil alt
+innhold på hjelpesidene bli indeksert av cron. Dersom du installerer
+og skrur på nye prosjekter og ønsker å indeksere deres
+hjelpetekster umiddelbart kan du navigere til <em>Administrasjon →
+Rapporter → Status</em> og klikke på lenken for å “kjøre cron
+manuelt.”.</p>
diff --git a/sites/all/modules/advanced_help/translations/help/nb/why-advanced-help.html b/sites/all/modules/advanced_help/translations/help/nb/why-advanced-help.html
new file mode 100644
index 000000000..72ff3fb4c
--- /dev/null
+++ b/sites/all/modules/advanced_help/translations/help/nb/why-advanced-help.html
@@ -0,0 +1,44 @@
+The <strong>Advanced help</strong> framework was designed to replace the original Drupal help system, which has several flaws that make it hard to create new help, hard to maintain existing help, and particularly hard to access help.
+
+The primary goal, then, is to increase the accessibility of help, meaning the ability of both the user and the help text author to access the needed tools to use, create, maintain and translate the help.
+
+This system is completely separate from Drupal's <code>hook_help()</code>. In Drupal 6 and 7, it actually co-exists with it; in the future, it is hoped that it will completely replace it allowing <code>hook_help()</code> to be deprecated and removed.
+
+Messages added to the top of a page are not really “help”. Often these messages are an introduction to a form or a short blurb telling a user what to do with a particular page. The problem is, these messages are always there, they are easily ignored, and they come before the actual page. In general, when users are learning, they want to see the page first, then ask questions. The reverse order is much less conducive to actually teaching a user how to use something. By allowing help to be available on request, the system conforms more naturally to how most people work.
+
+<h2>Advanced help er organisert etter emner</h2>
+With the <code>hook_help()</code> method, help text is organized by URL path. This is fine if you have help text describing how to use a particular page or what a particular page does, but ultimately is limiting because manuals and documentation are usually grouped by topic, and those topics are determined by the material itself.
+
+<strong>Advanced help</strong> allows the documentation author to organize topics as he or she sees fit; the only restriction, really, is that each individual chunk of text needs to stand on its own as a discrete topic.
+
+What's more, modules and themes can insert their topics into another's hierarchy. This would allow the Drupal core to create a task based help navigation system which allows modules and themes to insert topics into that navigation fluidly. This allows modules and themes to continue to keep their systems separate, yet integrate into the main system.
+
+<h2>Advanced help-emner er behandlet som HTML i egne filer</h2>
+This separation makes it easy to find and modify. Currently, everything is lumped together in <code>hook_help()</code> in PHP strings that are run through <code>t()</code>, and there is a fair amount of PHP code necessary in this system that actually gets in the way of writing good, explanatory text.
+
+In fact, requiring a documentation author to understand PHP at all is a detriment. The idea that documentation writers need to have PHP development as a skill seriously reduces the number of available contributors. HTML, on the other hand, is a much more common skill, is relatively easy to learn, and the amount of HTML needed to write documentation is only a little bit more than the HTML used in forum posts.
+
+Another benefit to not using PHP is that the files themselves are safe. They are filtered to escape malicious script code that could take over the server or do worse things. This means that these files can be used relatively easily on Drupal.org itself. It also means that descriptions of the project pulled from the project's help pages or README.txt or README.md can be made on the project's Drupal.org without the need to download the module or theme to look at the contents of these files. This also simplifies maintenance, as the files can be corrected easily by patches, which are updated by tyhe maintainer and pushed to git.
+
+By moving all docymentation into help files, we could, if we wanted, package the Drupal.org handbooks, or a subset of them, directly into a Drupal distribution, or a Drupal add-on, so that Drupal administrators can have Drupal help without needing to visit Drupal.org. This can be valuable in locked down corporate environments and on planes. But more importantly, the handbooks can be made version aware much more easily than the current method on Drupal.org.
+
+The downside to this method is that these books can't easily be made dynamic. Though the use of alter hooks could allow a module or theme to make modifications to the help as needed, doing this could make the help files less useful when you take them out of context.
+
+<h2>Advanced help-filer oversettes som en fullstendig fil</h2>
+It is actually not easy to translate documents as strings, particularly when the language being used is very much unlike English. In fact, when translating a document, the organization of the sentences may change drastically. It is also a burden on the CPU to do this, as you are indexing on very long strings.
+
+Translators have a much better time translating a document as a unit, because of the presence of the entire context.
+
+<h2>Advanced help har sitt eget navigasjons-system</h2>
+By making use of a navigation system specified in a .ini file (which is not PHP code and therefore safe to use), the help can be structured like a book, which is typical of online manuals. This is familiar to users, can be organized (and re-organized) and allows a module or theme to include significantly richer text without burdening the PHP code with having its help loaded unnecessarily.
+
+This book can be navigated hierarchically as well, making it easy to keep related topics together.
+<h2>Advanced help indekseres av nettstedets søkemotor</h2>
+An important goal of this system was to add searchability to the help. By being able to enter keywords into the search box and find relevant topics, we come up with a system that resembles the kind of help that comes with many operating systems. This is very valuable when searching through manuals trying to find out how to do a particular thing.
+
+This search is specific to the help, meaning that the help will not be mixed in with the global node search. This can be considered both an advantage and a disadvantage. For the most part, this help system is meant to provide help to site administrators, and content searches should not find it. The disadvantage, of course, is when you want to use it for end user help, you will not be able to.
+
+<h2>Hjelp for en bestemt side kan hentes inn via sprettopp-vinduer</h2>
+In addition to the manual-like hierarchical navigation, <strong>Advanced help</strong> can also provide context-sensitive additional help through a popup. While popups are controversial, the argument for using them is that when getting help while on a form, <i>a popup will not throw away a user's data.</i> Browsers are not very friendly to input forms if they are not submitted, and navigating away from the form can be dangerous. There are various other solutions to this problem, but each one has a drawback. The drawbacks to popups are well known, but mostly it is the irritation of having new windows. When getting help, though, a popup is usually invited. Help should not interfere with what the operation the user is trying to complete. It differs greatly from the uninvited popup, which are usually ads or popups meant to prevent users from navigating away from a site.
+
+Popups can be added to a page with plain text links or themed icon links.
diff --git a/sites/all/modules/ckeditor_link/LICENSE.txt b/sites/all/modules/ckeditor_link/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/ckeditor_link/README.txt b/sites/all/modules/ckeditor_link/README.txt
new file mode 100644
index 000000000..68256aa74
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/README.txt
@@ -0,0 +1,55 @@
+CKEDITOR LINK - A PLUGIN TO EASILY CREATE LINKS TO DRUPAL INTERNAL PATHS
+http://drupal.org/project/ckeditor_link
+
+
+
+REQUIREMENTS
+The CKEditor module or the Wysiwyg module
+The CKEditor editor
+Clean URLs need to be enabled.
+
+
+
+INSTALLATION
+Copy the ckeditor_link folder to your sites/all/modules directory.
+Go to admin/modules and enable the module.
+
+*Set permissions*
+Go to admin/people/permissions and grant the CKEditor Link related permissions
+to the desired roles.
+
+*When using the CKEditor module*
+Go to admin/config/content/ckeditor and edit the desired profile.
+Under "Editor appearance" > "Plugins", check the "CKEditor Link" box.
+Save changes.
+
+*When using the Wysiwyg module*
+Go to admin/config/content/wysiwyg and edit the desired CKEditor-enabled input
+format.
+Under "Buttons and plugins", check both "Link" and "CKEditor Link" boxes.
+Save changes.
+
+*Set up CKEditor Link Filter*
+Go to admin/config/content/formats and edit the desired text format.
+Check the "CKEditor Link Filter" box.
+If you use other path converting filters like Pathologic or Path Filter, make
+sure that CKEditor Link Filter comes before them:
+Under "Filter processing order", drag and drop CKEditor Link Filter before
+these filters in the list.
+Save changes.
+
+*Configure CKEditor Link*
+Go to admin/config/content/ckeditor_link.
+Change settings as desired.
+Save changes.
+
+
+
+EXTENDING CKEDITOR LINK
+Developers, see the ckeditor_link.api.php file.
+
+
+
+CONTACT
+Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+http://www.absyx.fr
diff --git a/sites/all/modules/ckeditor_link/ckeditor_link.admin.inc b/sites/all/modules/ckeditor_link/ckeditor_link.admin.inc
new file mode 100644
index 000000000..d1e14a11d
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/ckeditor_link.admin.inc
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+function ckeditor_link_settings_form() {
+ $form['general'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('General settings'),
+ );
+ $form['general']['ckeditor_link_type_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Link type name'),
+ '#description' => t('The name of the option added to the Link Type select box. Enter %site_name to use the name of this website.', array('%site_name' => '!site_name')),
+ '#default_value' => variable_get('ckeditor_link_type_name', 'Internal path'),
+ );
+ $form['general']['ckeditor_link_type_selected'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Selected by default'),
+ '#description' => t('Whether the %link_type link type should be selected by default instead of the URL link type.', array('%link_type' => ckeditor_link_get_link_type_name())),
+ '#default_value' => variable_get('ckeditor_link_type_selected', 1),
+ );
+ $form['general']['ckeditor_link_limit'] = array(
+ '#type' => 'select',
+ '#title' => t('Number of suggestions'),
+ '#description' => t('The maximum number of suggestions to display on autocomplete.'),
+ '#options' => drupal_map_assoc(array(10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100)),
+ '#default_value' => variable_get('ckeditor_link_limit', 10),
+ );
+
+ $types = ckeditor_link_get_types();
+ foreach ($types as $type) {
+ $func = $type['module'] .'_ckeditor_link_'. $type['type'] .'_settings';
+ if (function_exists($func)) {
+ $form = array_merge($form, $func());
+ }
+ }
+
+ return system_settings_form($form);
+}
diff --git a/sites/all/modules/ckeditor_link/ckeditor_link.api.php b/sites/all/modules/ckeditor_link/ckeditor_link.api.php
new file mode 100644
index 000000000..610cdea3a
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/ckeditor_link.api.php
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * @file
+ * Hooks that can be implemented by other modules to extend CKEditor Link.
+ */
+
+/**
+ * Declare the path types handled by the module.
+ *
+ * @return
+ * An array of the types handled by the module.
+ * Each value is either a type name or a sub-array with the following
+ * attributes:
+ * - 'type'
+ * The type name. Required.
+ * - 'file'
+ * A file that will be included before other hooks are invoked.
+ * The file should be relative to the implementing module's directory.
+ */
+function hook_ckeditor_link_types() {
+ return array(
+ 'mytype1',
+ array('type' => 'mytype2', 'file' => 'includes/mymodule.mytype2.inc'),
+ );
+}
+
+/**
+ * Alter types.
+ *
+ * @param $types
+ * The types returned by hook_ckeditor_link_types(). The types are keyed by
+ * 'MODULE.TYPE' for easy lookup.
+ *
+ * @see hook_ckeditor_link_types()
+ */
+function hook_ckeditor_link_types_alter(&$types) {
+ // Change types
+}
+
+/**
+ * Get autocomplete suggestions for the given string.
+ *
+ * Implementing modules should return only suggestions the current user has
+ * access to.
+ *
+ * @param $string
+ * The string to autocomplete.
+ * @param $limit
+ * The maximum number of suggestions to return.
+ *
+ * @return
+ * An array of suggestions where keys are non-aliased internal paths
+ * and values are titles.
+ */
+function hook_ckeditor_link_TYPE_autocomplete($string, $limit) {
+ $matches = array();
+
+ $matches['the-path/123'] = 'The title 1';
+ $matches['the-path/the-path-2/5'] = 'The title 2';
+
+ return $matches;
+}
+
+/**
+ * Alter autocomplete suggestions.
+ *
+ * @param $results
+ * The results returned by hook_ckeditor_link_TYPE_autocomplete.
+ * @param $string
+ * The string to autocomplete.
+ *
+ * @see hook_ckeditor_link_TYPE_autocomplete()
+ */
+function hook_ckeditor_link_autocomplete_alter(&$results, $string) {
+ // Change results.
+}
+
+/**
+ * Revert a path to a user-friendly title.
+ *
+ * @param $path
+ * The path to revert.
+ * @param $langcode
+ * The language code of the path if any. The implementing module may fix it
+ * if necessary, based on the given path.
+ *
+ * @return
+ * A title, FALSE if not found, or nothing if the implementing module is not
+ * responsible for the given path.
+ */
+function hook_ckeditor_link_TYPE_revert($path, &$langcode) {
+ //
+}
+
+/**
+ * Convert an internal path into an aliased and, if applicable, language
+ * prefixed URL.
+ *
+ * @param $path
+ * The internal path to convert.
+ * @param $langcode
+ * The language code of the path if any, the language code of the text to be
+ * filtered otherwise. It should only be used as a fallback when the content
+ * being linked to does not have any intrisic language.
+ *
+ * @return
+ * An URL alias, or nothing if the implementing module is not responsible for
+ * the given path.
+ */
+function hook_ckeditor_link_TYPE_url($path, $langcode) {
+ //
+}
+
+/**
+ * Add settings to the CKEditor Link settings form.
+ *
+ * @return
+ * An array containing the form elements to add.
+ */
+function hook_ckeditor_link_TYPE_settings() {
+ $form = array(
+ //
+ );
+ return $form;
+}
diff --git a/sites/all/modules/ckeditor_link/ckeditor_link.css b/sites/all/modules/ckeditor_link/ckeditor_link.css
new file mode 100644
index 000000000..9278d23e4
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/ckeditor_link.css
@@ -0,0 +1,23 @@
+
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+div.cke_dialog_ui_input_text #autocomplete li.selected *
+{
+ cursor: default;
+ color: #fff;
+}
+
+input.cke_dialog_ui_input_text.form-autocomplete
+{
+ min-height: 16px;
+}
+
+div.cke_dialog_ui_input_text #autocomplete
+{
+ max-height: 100%;
+ overflow-y: auto;
+}
diff --git a/sites/all/modules/ckeditor_link/ckeditor_link.info b/sites/all/modules/ckeditor_link/ckeditor_link.info
new file mode 100644
index 000000000..804a41ce7
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/ckeditor_link.info
@@ -0,0 +1,12 @@
+name = CKEditor Link
+description = Easily create links to Drupal internal paths through CKEditor.
+package = User interface
+core = 7.x
+configure = admin/config/content/ckeditor_link
+
+; Information added by Drupal.org packaging script on 2016-01-30
+version = "7.x-2.4"
+core = "7.x"
+project = "ckeditor_link"
+datestamp = "1454115840"
+
diff --git a/sites/all/modules/ckeditor_link/ckeditor_link.install b/sites/all/modules/ckeditor_link/ckeditor_link.install
new file mode 100644
index 000000000..1cfc46ac0
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/ckeditor_link.install
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+/**
+ * Implementation of hook_install().
+ */
+function ckeditor_link_install() {
+ db_update('system')
+ ->fields(array('weight' => 1))
+ ->condition('name', 'ckeditor_link')
+ ->execute();
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function ckeditor_link_uninstall() {
+ db_query("DELETE FROM {variable} WHERE name LIKE 'ckeditor_link_%%'");
+}
+
+/**
+ * Implementation of hook_requirements().
+ */
+function ckeditor_link_requirements($phase) {
+ $requirements = array();
+
+ if (($phase == 'runtime') && !variable_get('clean_url', 0)) {
+ $requirements['ckeditor_link_clean_url'] = array(
+ 'title' => t('CKEditor Link'),
+ 'value' => t('Clean URLs are disabled.'),
+ 'description' => t('<em>CKEditor Link</em> requires <a href="@url">Clean URLs</a> to be enabled in order to function properly.', array('@url' => url('admin/config/search/clean-urls'))),
+ 'severity' => REQUIREMENT_ERROR,
+ );
+ }
+
+ return $requirements;
+}
+
+/**
+ * Expose strings to potx.
+ */
+function _ckeditor_link_potx() {
+ t('Internal path');
+}
diff --git a/sites/all/modules/ckeditor_link/ckeditor_link.module b/sites/all/modules/ckeditor_link/ckeditor_link.module
new file mode 100644
index 000000000..c86c0bdf4
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/ckeditor_link.module
@@ -0,0 +1,345 @@
+<?php
+
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+/**
+ * Implementation of hook_permission().
+ */
+function ckeditor_link_permission() {
+ return array(
+ 'access ckeditor link' => array('title' => t('Access <em>CKEditor Link</em>')),
+ 'administer ckeditor link' => array('title' => t('Administer <em>CKEditor Link</em>')),
+ );
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function ckeditor_link_menu() {
+ $items['ckeditor_link/autocomplete'] = array(
+ 'page callback' => 'ckeditor_link_autocomplete',
+ 'access arguments' => array('access ckeditor link'),
+ 'type' => MENU_CALLBACK,
+ );
+ $items['ckeditor_link/revert'] = array(
+ 'page callback' => 'ckeditor_link_revert',
+ 'access arguments' => array('access ckeditor link'),
+ 'type' => MENU_CALLBACK,
+ );
+ $items['admin/config/content/ckeditor_link'] = array(
+ 'title' => 'CKEditor Link',
+ 'description' => 'Configure CKEditor Link.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ckeditor_link_settings_form'),
+ 'access arguments' => array('administer ckeditor link'),
+ 'file' => 'ckeditor_link.admin.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function ckeditor_link_theme() {
+ return array(
+ 'ckeditor_link_autocomplete_title' => array(
+ 'variables' => array('title' => NULL),
+ ),
+ );
+}
+
+function ckeditor_link_autocomplete($string = '') {
+ $matches = array();
+
+ if ($string !== '') {
+ $types = ckeditor_link_get_types();
+ $limit = variable_get('ckeditor_link_limit', 10);
+ $results = array();
+ foreach ($types as $type) {
+ $func = $type['module'] .'_ckeditor_link_'. $type['type'] .'_autocomplete';
+ if (function_exists($func)) {
+ $results += $func($string, $limit);
+ if (count($results) > $limit) {
+ break;
+ }
+ }
+ }
+ drupal_alter('ckeditor_link_autocomplete', $results, $string);
+
+ array_splice($results, $limit);
+ foreach ($results as $path => $title) {
+ $matches[$title . ' (' . $path . ')'] = theme('ckeditor_link_autocomplete_title', array('title' => $title));
+ }
+ }
+
+ drupal_json_output($matches);
+}
+
+function theme_ckeditor_link_autocomplete_title($variables) {
+ return '<div class="reference-autocomplete">' . check_plain($variables['title']) . '</div>';
+}
+
+function ckeditor_link_revert() {
+ $output = NULL;
+
+ $args = func_get_args();
+ $path = trim(implode('/', $args), '/');
+ if ($path !== '') {
+ $langcode = LANGUAGE_NONE;
+ $path = ckeditor_link_path_strip_language($path, $langcode);
+ $path = drupal_get_normal_path($path, $langcode);
+ $types = ckeditor_link_get_types();
+ foreach ($types as $type) {
+ $func = $type['module'] .'_ckeditor_link_'. $type['type'] .'_revert';
+ if (function_exists($func)) {
+ $result = $func($path, $langcode);
+ if ($result !== NULL) {
+ $output = ($result !== FALSE) ? $result .' ('. ckeditor_link_path_prefix_language($path, $langcode) .')' : FALSE;
+ break;
+ }
+ }
+ }
+ }
+
+ drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
+ echo drupal_json_encode($output);
+}
+
+function ckeditor_link_get_types() {
+ static $types;
+
+ if (!isset($types)) {
+ $types = array();
+
+ $data = array();
+ foreach (module_implements('ckeditor_link_types') as $module) {
+ $func = $module .'_ckeditor_link_types';
+ $data[$module] = $func();
+ }
+
+ foreach ($data as $module => $_types) {
+ foreach ($_types as $type) {
+ if (!is_array($type)) {
+ $type = array('type' => $type);
+ }
+ $type['module'] = $module;
+ $types[$module .'.'. $type['type']] = $type;
+ }
+ }
+
+ drupal_alter('ckeditor_link_types', $types);
+ $types = array_values($types);
+
+ foreach ($types as $type) {
+ if (isset($type['file'])) {
+ require_once(drupal_get_path('module', $type['module']) .'/'. $type['file']);
+ }
+ }
+ }
+
+ return $types;
+}
+
+/**
+ * Implementation of hook_ckeditor_link_types().
+ */
+function ckeditor_link_ckeditor_link_types() {
+ $types[] = array('type' => 'node', 'file' => 'includes/ckeditor_link.node.inc');
+
+ if (module_exists('taxonomy')) {
+ $types[] = array('type' => 'taxonomy', 'file' => 'includes/ckeditor_link.taxonomy.inc');
+ if (module_exists('i18n_taxonomy')) {
+ $types[] = array('type' => 'i18n_taxonomy', 'file' => 'includes/ckeditor_link.i18n_taxonomy.inc');
+ }
+ }
+
+ $types[] = array('type' => 'menu', 'file' => 'includes/ckeditor_link.menu.inc');
+ if (module_exists('i18n_menu')) {
+ $types[] = array('type' => 'i18n_menu', 'file' => 'includes/ckeditor_link.i18n_menu.inc');
+ }
+
+ return $types;
+}
+
+/**
+ * Implementation of hook_element_info_alter().
+ */
+function ckeditor_link_element_info_alter(&$type) {
+ if (user_access('access ckeditor link')) {
+ $type['text_format']['#pre_render'][] = 'ckeditor_link_text_format_pre_render';
+ $type['text_format']['#post_render'][] = 'ckeditor_link_text_format_post_render';
+ }
+}
+
+function ckeditor_link_text_format_pre_render($element) {
+ _ckeditor_link_has_text_format(TRUE);
+ return $element;
+}
+
+function _ckeditor_link_has_text_format($set = FALSE) {
+ static $has = FALSE;
+ if (!$set) {
+ return $has;
+ }
+ $has = TRUE;
+}
+
+function ckeditor_link_text_format_post_render($content, $element) {
+ static $added;
+ if (!isset($added) && _ckeditor_link_has_text_format() && ($js = drupal_add_js()) && isset($js['settings']['data'])) {
+ $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']);
+ if (isset($settings['ckeditor']) || isset($settings['wysiwyg']['configs']['ckeditor'])) {
+ $added = TRUE;
+ drupal_add_css(drupal_get_path('module', 'ckeditor_link') .'/ckeditor_link.css');
+ drupal_add_js('misc/autocomplete.js');
+ drupal_add_js(array('ckeditor_link' => array(
+ 'module_path' => base_path() . drupal_get_path('module', 'ckeditor_link'),
+ 'autocomplete_path' => url('ckeditor_link/autocomplete'),
+ 'revert_path' => url('ckeditor_link/revert'),
+ 'msg_invalid_path' => t('Link must be a valid internal path.'),
+ 'type_name' => ckeditor_link_get_link_type_name(),
+ 'type_selected' => (bool) variable_get('ckeditor_link_type_selected', 1),
+ )), 'setting');
+ }
+ }
+ return $content;
+}
+
+/**
+ * Implementation of hook_ckeditor_plugin().
+ */
+function ckeditor_link_ckeditor_plugin() {
+ return array('ckeditor_link' => array(
+ 'name' => 'drupal_path',
+ 'desc' => t('CKEditor Link - A plugin to easily create links to Drupal internal paths'),
+ 'path' => drupal_get_path('module', 'ckeditor_link') .'/plugins/link/',
+ ));
+}
+
+/**
+ * Implementation of hook_wysiwyg_plugin().
+ */
+function ckeditor_link_wysiwyg_plugin($editor, $version) {
+ if ($editor == 'ckeditor') {
+ return array('drupal_path' => array(
+ 'path' => drupal_get_path('module', 'ckeditor_link') .'/plugins/link/',
+ 'load' => TRUE,
+ 'extensions' => array('Link' => t('CKEditor Link')),
+ ));
+ }
+}
+
+/**
+ * Implementation of hook_filter_info().
+ */
+function ckeditor_link_filter_info() {
+ $filters['ckeditor_link_filter'] = array(
+ 'title' => t('CKEditor Link Filter'),
+ 'description' => t('Converts links added through <em>CKEditor Link</em> into aliased and language prefixed URLs.'),
+ 'process callback' => 'ckeditor_link_filter_process',
+ );
+ return $filters;
+}
+
+function ckeditor_link_filter_process($text, $filter, $format, $langcode, $cache, $cache_id) {
+ _ckeditor_link_filter_process(NULL, $langcode);
+ return preg_replace_callback('`\bhref="'. preg_quote(base_path(), '`') .'([^?#"]+)`', '_ckeditor_link_filter_process', $text);
+}
+
+function _ckeditor_link_filter_process($matches, $langcode = NULL) {
+ static $stored_langcode = LANGUAGE_NONE;
+ if ($matches === NULL) {
+ $stored_langcode = $langcode;
+ return;
+ }
+
+ $path = urldecode($matches[1]);
+
+ $langcode = $stored_langcode;
+ $path = ckeditor_link_path_strip_language($path, $langcode);
+
+ $types = ckeditor_link_get_types();
+ foreach ($types as $type) {
+ $func = $type['module'] .'_ckeditor_link_'. $type['type'] .'_url';
+ if (function_exists($func)) {
+ $url = $func($path, $langcode);
+ if ($url) {
+ return 'href="'. $url;
+ }
+ }
+ }
+
+ return 'href="'. base_path() . $matches[1];
+}
+
+function ckeditor_link_get_link_type_name() {
+ return t(variable_get('ckeditor_link_type_name', 'Internal path'), array('!site_name' => variable_get('site_name', 'Drupal')));
+}
+
+function ckeditor_link_path_strip_language($path, &$langcode) {
+ $languages = ckeditor_link_get_languages();
+ if ($languages) {
+ $args = explode('/', $path);
+ $prefix = array_shift($args);
+ foreach ($languages as $language) {
+ if (!empty($language->prefix) && ($language->prefix == $prefix)) {
+ $langcode = $language->language;
+ $path = implode('/', $args);
+ break;
+ }
+ }
+ }
+
+ return $path;
+}
+
+function ckeditor_link_path_prefix_language($path, $langcode) {
+ if ($langcode != LANGUAGE_NONE) {
+ $languages = ckeditor_link_get_languages();
+ if ($languages && isset($languages[$langcode])) {
+ $language = $languages[$langcode];
+ if (!empty($language->prefix)) {
+ $path = (empty($path)) ? $language->prefix : $language->prefix .'/'. $path;
+ }
+ }
+ }
+
+ return $path;
+}
+
+function ckeditor_link_get_languages() {
+ static $languages;
+
+ if (!isset($languages)) {
+ $languages = FALSE;
+ if (drupal_multilingual() && module_exists('locale') && language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_URL)) {
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+ }
+ }
+
+ return $languages;
+}
+
+function ckeditor_link_url($path = NULL, $langcode, $options = array()) {
+ if ($langcode != LANGUAGE_NONE) {
+ $languages = ckeditor_link_get_languages();
+ if ($languages && isset($languages[$langcode])) {
+ $options['language'] = $languages[$langcode];
+ }
+ }
+
+ if (!isset($options['language'])) {
+ $options['language'] = language_default();
+ }
+
+ return url($path, $options);
+}
+
+function _ckeditor_link_check_path($path) {
+ return preg_match('`^[a-z][\w\/\.-]*$`i', $path);
+}
diff --git a/sites/all/modules/ckeditor_link/includes/ckeditor_link.i18n_menu.inc b/sites/all/modules/ckeditor_link/includes/ckeditor_link.i18n_menu.inc
new file mode 100644
index 000000000..1398f837f
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/includes/ckeditor_link.i18n_menu.inc
@@ -0,0 +1,79 @@
+<?php
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_autocomplete().
+ */
+function ckeditor_link_ckeditor_link_i18n_menu_autocomplete($string, $limit) {
+ // Currently, this function only supports MySQL.
+ // TODO: Add support for pgsql.
+ if (!in_array(db_driver(), array('mysql'))) {
+ return array();
+ }
+
+ $matches = array();
+
+ $menus = array_keys(array_filter(variable_get('ckeditor_link_autocomplete_menus', array())));
+ if (count($menus)) {
+ $query = db_select('menu_links', 'ml');
+ $query->innerJoin('locales_source', 'ls', 'ls.context = CONCAT(:prefix, ml.mlid, :suffix)', array(':prefix' => 'item:', ':suffix' => ':title'));
+ $query->innerJoin('locales_target', 'lt', 'lt.lid = ls.lid');
+ $query->fields('ml', array('link_path'));
+ $query->addExpression('CONVERT(lt.translation USING utf8)', 'link_title');
+ $query->fields('lt', array('language'));
+ $query->where('CONVERT(lt.translation USING utf8) LIKE :pattern', array(':pattern' => '%'. db_like($string) .'%'));
+ $query->condition('ml.hidden', 0);
+ $query->condition('ml.external', 0);
+ $query->orderBy('link_title');
+ $query->range(0, $limit);
+ if (!in_array('- any -', $menus)) {
+ $query->condition('ml.menu_name', $menus, 'IN');
+ }
+ $result = $query->execute();
+ foreach ($result as $item) {
+ if (_ckeditor_link_check_path($item->link_path)) {
+ $router_item = menu_get_item($item->link_path);
+ if ($router_item && $router_item['access']) {
+ $path = ckeditor_link_path_prefix_language($item->link_path, $item->language);
+ $matches[$path] = $item->link_title;
+ }
+ }
+ }
+ }
+
+ return $matches;
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_revert().
+ */
+function ckeditor_link_ckeditor_link_i18n_menu_revert($path, &$langcode) {
+ $router_item = menu_get_item($path);
+ if ($router_item) {
+ if (!$router_item['access']) {
+ return FALSE;
+ }
+ $result = db_query('SELECT mlid, link_title, language FROM {menu_links} WHERE link_path = :link_path AND hidden = 0 ORDER BY customized DESC', array(':link_path' => $path));
+ $default_langcode = language_default('language');
+ $link_title = NULL;
+ foreach ($result as $item) {
+ if ($item->language == $langcode) {
+ $link_title = $item->link_title;
+ break;
+ }
+ elseif (($item->language == $default_langcode) && ($langcode == LANGUAGE_NONE)) {
+ $langcode = $default_langcode;
+ $link_title = $item->link_title;
+ break;
+ }
+ elseif (!$link_title && ($item->language == LANGUAGE_NONE)) {
+ $link_title = i18n_string_translate(array('menu', 'item', $item->mlid, 'title'), $item->link_title, array('langcode' => $langcode));
+ }
+ }
+ return ($link_title) ? $link_title : NULL;
+ }
+}
diff --git a/sites/all/modules/ckeditor_link/includes/ckeditor_link.i18n_taxonomy.inc b/sites/all/modules/ckeditor_link/includes/ckeditor_link.i18n_taxonomy.inc
new file mode 100644
index 000000000..66d32e029
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/includes/ckeditor_link.i18n_taxonomy.inc
@@ -0,0 +1,70 @@
+<?php
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_autocomplete().
+ */
+function ckeditor_link_ckeditor_link_i18n_taxonomy_autocomplete($string, $limit) {
+ // Currently, this function only supports MySQL.
+ // TODO: Add support for pgsql.
+ if (!in_array(db_driver(), array('mysql'))) {
+ return array();
+ }
+
+ $matches = array();
+
+ $vocabularies = array_keys(array_filter(variable_get('ckeditor_link_autocomplete_vocabularies', array())));
+ if (count($vocabularies)) {
+ $query = db_select('taxonomy_term_data', 't');
+ $query->innerJoin('locales_source', 'ls', 'ls.context = CONCAT(:prefix, t.tid, :suffix)', array(':prefix' => 'term:', ':suffix' => ':name'));
+ $query->innerJoin('locales_target', 'lt', 'lt.lid = ls.lid');
+ $query->fields('t', array('tid'));
+ $query->addExpression('CONVERT(lt.translation USING utf8)', 'name');
+ $query->fields('lt', array('language'));
+ $query->where('CONVERT(lt.translation USING utf8) LIKE :pattern', array(':pattern' => '%'. db_like($string) .'%'));
+ $query->orderBy('name');
+ $query->range(0, $limit);
+ $query->addTag('term_access');
+ if (!in_array('- any -', $vocabularies)) {
+ $query->condition('t.vid', $vocabularies, 'IN');
+ }
+ $result = $query->execute();
+ foreach ($result as $term) {
+ $path = ckeditor_link_path_prefix_language('taxonomy/term/'. $term->tid, $term->language);
+ $matches[$path] = $term->name;
+ }
+ }
+
+ return $matches;
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_revert().
+ */
+function ckeditor_link_ckeditor_link_i18n_taxonomy_revert($path, &$langcode) {
+ if (!preg_match('`^taxonomy/term/(\d+)$`', $path, $matches)) {
+ return;
+ }
+
+ $tid = $matches[1];
+ $result = db_select('taxonomy_term_data', 't')
+ ->fields('t', array('tid', 'vid', 'name', 'language'))
+ ->condition('t.tid', $tid)
+ ->addTag('term_access')
+ ->execute();
+ if ($term = $result->fetchObject()) {
+ if ($term->language == LANGUAGE_NONE) {
+ return i18n_taxonomy_term_name($term, $langcode);
+ }
+ else {
+ $langcode = LANGUAGE_NONE;
+ return $term->name;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/sites/all/modules/ckeditor_link/includes/ckeditor_link.menu.inc b/sites/all/modules/ckeditor_link/includes/ckeditor_link.menu.inc
new file mode 100644
index 000000000..2faeb15da
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/includes/ckeditor_link.menu.inc
@@ -0,0 +1,84 @@
+<?php
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_autocomplete().
+ */
+function ckeditor_link_ckeditor_link_menu_autocomplete($string, $limit) {
+ $matches = array();
+
+ $menus = array_keys(array_filter(variable_get('ckeditor_link_autocomplete_menus', array())));
+ if (count($menus)) {
+ $query = db_select('menu_links')
+ ->fields('menu_links', array('link_path', 'link_title'))
+ ->condition('link_title', '%'. db_like($string) .'%', 'LIKE')
+ ->condition('hidden', 0)
+ ->condition('external', 0)
+ ->orderBy('link_title')
+ ->range(0, $limit);
+ if (function_exists('ckeditor_link_ckeditor_link_i18n_menu_autocomplete')) {
+ $query->fields('menu_links', array('language'));
+ }
+ if (!in_array('- any -', $menus)) {
+ $query->condition('menu_name', $menus, 'IN');
+ }
+ $result = $query->execute();
+ foreach ($result as $item) {
+ if (_ckeditor_link_check_path($item->link_path)) {
+ $router_item = menu_get_item($item->link_path);
+ if ($router_item && $router_item['access']) {
+ $langcode = (isset($item->language)) ? $item->language : LANGUAGE_NONE;
+ $path = ckeditor_link_path_prefix_language($item->link_path, $langcode);
+ $matches[$path] = $item->link_title;
+ }
+ }
+ }
+ }
+
+ return $matches;
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_revert().
+ */
+function ckeditor_link_ckeditor_link_menu_revert($path, &$langcode) {
+ if (function_exists('ckeditor_link_ckeditor_link_i18n_menu_revert')) {
+ return;
+ }
+
+ $router_item = menu_get_item($path);
+ if ($router_item) {
+ if (!$router_item['access']) {
+ return FALSE;
+ }
+ $link_title = db_query("SELECT link_title FROM {menu_links} WHERE link_path = :link_path AND hidden = 0 ORDER BY customized DESC", array(':link_path' => $path))->fetchField();
+ return ($link_title) ? $link_title : NULL;
+ }
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_settings().
+ */
+function ckeditor_link_ckeditor_link_menu_settings() {
+ $form = array();
+
+ if (module_exists('menu')) {
+ $form['menu'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Menu items'),
+ );
+ $form['menu']['ckeditor_link_autocomplete_menus'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Menus'),
+ '#options' => array('- any -' => t('<em>Any menu</em>')) + array_map('check_plain', menu_get_menus()),
+ '#default_value' => variable_get('ckeditor_link_autocomplete_menus', array()),
+ '#description' => t('Select the menus to be available as autocomplete suggestions.'),
+ );
+ }
+
+ return $form;
+}
diff --git a/sites/all/modules/ckeditor_link/includes/ckeditor_link.node.inc b/sites/all/modules/ckeditor_link/includes/ckeditor_link.node.inc
new file mode 100644
index 000000000..0cbe20a01
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/includes/ckeditor_link.node.inc
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_autocomplete().
+ */
+function ckeditor_link_ckeditor_link_node_autocomplete($string, $limit) {
+ $matches = array();
+
+ $node_types = array_keys(array_filter(variable_get('ckeditor_link_autocomplete_node_types', array('- any -' => '- any -'))));
+ if (count($node_types)) {
+ $query = db_select('node', 'n')
+ ->fields('n', array('nid', 'title'))
+ ->condition('n.title', '%'. db_like($string) .'%', 'LIKE')
+ ->orderBy('n.title')
+ ->orderBy('n.type')
+ ->range(0, $limit)
+ ->addTag('node_access');
+ if (!in_array('- any -', $node_types)) {
+ $query->condition('n.type', $node_types, 'IN');
+ }
+ $result = $query->execute();
+ foreach ($result as $node) {
+ $matches['node/'. $node->nid] = $node->title;
+ }
+ }
+
+ return $matches;
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_revert().
+ */
+function ckeditor_link_ckeditor_link_node_revert($path, &$langcode) {
+ if (!preg_match('`^node/(\d+)$`', $path, $matches)) {
+ return;
+ }
+
+ $nid = $matches[1];
+ $query = db_select('node', 'n')
+ ->fields('n', array('title', 'language'))
+ ->condition('n.nid', $nid)
+ ->addTag('node_access');
+ if ($node = $query->execute()->fetchObject()) {
+ if ($node->language != LANGUAGE_NONE) {
+ $langcode = LANGUAGE_NONE;
+ }
+ return $node->title;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_url().
+ */
+function ckeditor_link_ckeditor_link_node_url($path, $langcode) {
+ if (!preg_match('`^node/(\d+)$`', $path, $matches)) {
+ return;
+ }
+
+ $nid = $matches[1];
+
+ $languages = ckeditor_link_get_languages();
+ if ($languages) {
+ $language = db_query('SELECT language FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
+ if ($language && ($language != LANGUAGE_NONE) && isset($languages[$language])) {
+ $langcode = $language;
+ }
+ }
+
+ return ckeditor_link_url("node/$nid", $langcode);
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_settings().
+ */
+function ckeditor_link_ckeditor_link_node_settings() {
+ $form['node'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Nodes'),
+ );
+ $form['node']['ckeditor_link_autocomplete_node_types'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Content types'),
+ '#options' => array('- any -' => t('<em>Any content type</em>')) + array_map('check_plain', node_type_get_names()),
+ '#default_value' => variable_get('ckeditor_link_autocomplete_node_types', array('- any -' => '- any -')),
+ '#description' => t('Select the content types to be available as autocomplete suggestions.'),
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/ckeditor_link/includes/ckeditor_link.taxonomy.inc b/sites/all/modules/ckeditor_link/includes/ckeditor_link.taxonomy.inc
new file mode 100644
index 000000000..a38ed5048
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/includes/ckeditor_link.taxonomy.inc
@@ -0,0 +1,97 @@
+<?php
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ */
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_autocomplete().
+ */
+function ckeditor_link_ckeditor_link_taxonomy_autocomplete($string, $limit) {
+ $matches = array();
+
+ $vocabularies = array_keys(array_filter(variable_get('ckeditor_link_autocomplete_vocabularies', array())));
+ if (count($vocabularies)) {
+ $query = db_select('taxonomy_term_data', 't')
+ ->fields('t', array('tid', 'name'))
+ ->condition('t.name', '%'. db_like($string) .'%', 'LIKE')
+ ->orderBy('t.name')
+ ->range(0, $limit)
+ ->addTag('term_access');
+ if (!in_array('- any -', $vocabularies)) {
+ $query->condition('t.vid', $vocabularies, 'IN');
+ }
+ $result = $query->execute();
+ foreach ($result as $term) {
+ $matches['taxonomy/term/'. $term->tid] = $term->name;
+ }
+ }
+
+ return $matches;
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_revert().
+ */
+function ckeditor_link_ckeditor_link_taxonomy_revert($path, &$langcode) {
+ if (function_exists('ckeditor_link_ckeditor_link_i18n_taxonomy_revert')
+ || !preg_match('`^taxonomy/term/(\d+)$`', $path, $matches)) {
+ return;
+ }
+
+ $tid = $matches[1];
+ $name = db_select('taxonomy_term_data', 't')
+ ->fields('t', array('name'))
+ ->condition('t.tid', $tid)
+ ->addTag('term_access')
+ ->execute()
+ ->fetchField();
+ return ($name) ? $name : FALSE;
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_url().
+ */
+function ckeditor_link_ckeditor_link_taxonomy_url($path, $langcode) {
+ if (!preg_match('`^taxonomy/term/(\d+)$`', $path, $matches)) {
+ return;
+ }
+
+ $tid = $matches[1];
+
+ $languages = ckeditor_link_get_languages();
+ if ($languages) {
+ $term = taxonomy_term_load($tid);
+ if ($term && ($language = @$term->language) && ($language != LANGUAGE_NONE) && isset($languages[$language])) {
+ $langcode = $language;
+ }
+ }
+
+ return ckeditor_link_url("taxonomy/term/$tid", $langcode);
+}
+
+/**
+ * Implementation of hook_ckeditor_link_TYPE_settings().
+ */
+function ckeditor_link_ckeditor_link_taxonomy_settings() {
+ $form['taxonomy'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Taxonomy terms'),
+ );
+
+ $vocabularies = taxonomy_get_vocabularies();
+ $options = array('- any -' => t('<em>Any vocabulary</em>'));
+ foreach ($vocabularies as $vid => $vocabulary) {
+ $options[$vid] = check_plain($vocabulary->name);
+ }
+ $form['taxonomy']['ckeditor_link_autocomplete_vocabularies'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Vocabularies'),
+ '#options' => $options,
+ '#default_value' => variable_get('ckeditor_link_autocomplete_vocabularies', array()),
+ '#description' => t('Select the vocabularies to be available as autocomplete suggestions.'),
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/ckeditor_link/plugins/link/plugin.js b/sites/all/modules/ckeditor_link/plugins/link/plugin.js
new file mode 100644
index 000000000..c9071a57c
--- /dev/null
+++ b/sites/all/modules/ckeditor_link/plugins/link/plugin.js
@@ -0,0 +1,213 @@
+/**
+ * @file
+ * Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
+ * http://www.absyx.fr
+ *
+ * Portions of code:
+ * Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.html or http://ckeditor.com/license
+ */
+
+(function($) {
+
+ // Get a CKEDITOR.dialog.contentDefinition object by its ID.
+ var getById = function(array, id, recurse) {
+ for (var i = 0, item; (item = array[i]); i++) {
+ if (item.id == id) return item;
+ if (recurse && item[recurse]) {
+ var retval = getById(item[recurse], id, recurse);
+ if (retval) return retval;
+ }
+ }
+ return null;
+ };
+
+ var resetInitValues = function(dialog) {
+ dialog.foreach(function(contentObj) {
+ contentObj.setInitValue && contentObj.setInitValue();
+ });
+ };
+
+ var initAutocomplete = function(input, uri) {
+ input.setAttribute('autocomplete', 'OFF');
+ var jsAC = new Drupal.jsAC($(input), new Drupal.ACDB(uri));
+
+ // Override Drupal.jsAC.prototype.onkeydown().
+ // @see https://drupal.org/node/1991076
+ var _onkeydown = jsAC.onkeydown;
+ jsAC.onkeydown = function(input, e) {
+ if (!e) {
+ e = window.event;
+ }
+ switch (e.keyCode) {
+ case 13: // Enter.
+ this.hidePopup(e.keyCode);
+ return true;
+ default: // All other keys.
+ return _onkeydown.call(this, input, e);
+ }
+ };
+ };
+
+ var extractPath = function(value) {
+ value = CKEDITOR.tools.trim(value);
+ var match;
+ match = /\(([^\(]*?)\)$/i.exec(value);
+ if (match && match[1]) {
+ value = match[1];
+ }
+ var basePath = Drupal.settings.basePath;
+ if (value.indexOf(basePath) == 0) {
+ value = value.substr(basePath.length);
+ }
+ if (/^[a-z][\w\/\.-]*$/i.test(value)) {
+ return value;
+ }
+ return false;
+ };
+
+ var cache = {}, revertPath = function(value, callback) {
+ var path = extractPath(value);
+ if (!path) {
+ return false;
+ }
+ if (cache[path] !== undefined) {
+ return cache[path];
+ }
+ $.getJSON(Drupal.settings.ckeditor_link.revert_path + '/' + Drupal.encodePath(path), function(data) {
+ cache[path] = data;
+ callback();
+ });
+ };
+
+ CKEDITOR.plugins.add('drupal_path', {
+
+ init: function(editor, pluginPath) {
+ CKEDITOR.on('dialogDefinition', function(e) {
+ if ((e.editor != editor) || (e.data.name != 'link') || !Drupal.settings.ckeditor_link) return;
+
+ // Overrides definition.
+ var definition = e.data.definition;
+ definition.onFocus = CKEDITOR.tools.override(definition.onFocus, function(original) {
+ return function() {
+ original.call(this);
+ if (this.getValueOf('info', 'linkType') == 'drupal') {
+ this.getContentElement('info', 'drupal_path').select();
+ }
+ };
+ });
+ definition.onOk = CKEDITOR.tools.override(definition.onOk, function(original) {
+ return function() {
+ var process = false;
+ if ((this.getValueOf('info', 'linkType') == 'drupal') && !this._.selectedElement) {
+ var ranges = editor.getSelection().getRanges(true);
+ if ((ranges.length == 1) && ranges[0].collapsed) {
+ process = true;
+ }
+ }
+ original.call(this);
+ if (process) {
+ var value = this.getValueOf('info', 'drupal_path');
+ var index = value.lastIndexOf('(');
+ if (index != -1) {
+ var text = CKEDITOR.tools.trim(value.substr(0, index));
+ if (text) {
+ CKEDITOR.plugins.link.getSelectedLink(editor).setText(text);
+ }
+ }
+ }
+ };
+ });
+
+ // Overrides linkType definition.
+ var infoTab = definition.getContents('info');
+ var content = getById(infoTab.elements, 'linkType');
+ content.items.unshift([Drupal.settings.ckeditor_link.type_name, 'drupal']);
+ infoTab.elements.push({
+ type: 'vbox',
+ id: 'drupalOptions',
+ children: [{
+ type: 'text',
+ id: 'drupal_path',
+ label: editor.lang.link.title,
+ required: true,
+ onLoad: function() {
+ this.getInputElement().addClass('form-autocomplete');
+ initAutocomplete(this.getInputElement().$, Drupal.settings.ckeditor_link.autocomplete_path);
+ },
+ setup: function(data) {
+ this.setValue(data.drupal_path || '');
+ },
+ validate: function() {
+ var dialog = this.getDialog();
+ if (dialog.getValueOf('info', 'linkType') != 'drupal') {
+ return true;
+ }
+ var func = CKEDITOR.dialog.validate.notEmpty(editor.lang.link.noUrl);
+ if (!func.apply(this)) {
+ return false;
+ }
+ if (!extractPath(this.getValue())) {
+ alert(Drupal.settings.ckeditor_link.msg_invalid_path);
+ this.focus();
+ return false;
+ }
+ return true;
+ }
+ }]
+ });
+ content.onChange = CKEDITOR.tools.override(content.onChange, function(original) {
+ return function() {
+ original.call(this);
+ var dialog = this.getDialog();
+ var element = dialog.getContentElement('info', 'drupalOptions').getElement().getParent().getParent();
+ if (this.getValue() == 'drupal') {
+ element.show();
+ if (editor.config.linkShowTargetTab) {
+ dialog.showPage('target');
+ }
+ var uploadTab = dialog.definition.getContents('upload');
+ if (uploadTab && !uploadTab.hidden) {
+ dialog.hidePage('upload');
+ }
+ }
+ else {
+ element.hide();
+ }
+ };
+ });
+ content.setup = function(data) {
+ if (!data.type || (data.type == 'url') && !data.url) {
+ if (Drupal.settings.ckeditor_link.type_selected) {
+ data.type = 'drupal';
+ }
+ }
+ else if (data.url && !data.url.protocol && data.url.url) {
+ var dialog = this.getDialog();
+ var path = revertPath(data.url.url, function() {
+ dialog.setupContent(data);
+ resetInitValues(dialog);
+ });
+ if (path) {
+ data.type = 'drupal';
+ data.drupal_path = path;
+ delete data.url;
+ }
+ }
+ this.setValue(data.type || 'url');
+ };
+ content.commit = CKEDITOR.tools.override(content.commit, function(original) {
+ return function(data) {
+ original.call(this, data);
+ if (data.type == 'drupal') {
+ data.type = 'url';
+ var dialog = this.getDialog();
+ dialog.setValueOf('info', 'protocol', '');
+ dialog.setValueOf('info', 'url', Drupal.settings.basePath + extractPath(dialog.getValueOf('info', 'drupal_path')));
+ }
+ };
+ });
+ });
+ }
+ });
+})(jQuery); \ No newline at end of file
diff --git a/sites/all/modules/ckeditor_link_file/LICENSE.txt b/sites/all/modules/ckeditor_link_file/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/ckeditor_link_file/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/ckeditor_link_file/ckeditor_link_file.info b/sites/all/modules/ckeditor_link_file/ckeditor_link_file.info
new file mode 100644
index 000000000..972c710e8
--- /dev/null
+++ b/sites/all/modules/ckeditor_link_file/ckeditor_link_file.info
@@ -0,0 +1,14 @@
+name = CKEditor Link for Files
+description = Easily create links to Drupal files through CKEditor.
+package = User interface
+core = 7.x
+dependencies[] = ckeditor_link
+dependencies[] = file_entity
+configure = admin/config/content/ckeditor_link
+
+; Information added by drupal.org packaging script on 2013-09-10
+version = "7.x-1.3"
+core = "7.x"
+project = "ckeditor_link_file"
+datestamp = "1378786013"
+
diff --git a/sites/all/modules/ckeditor_link_file/ckeditor_link_file.install b/sites/all/modules/ckeditor_link_file/ckeditor_link_file.install
new file mode 100644
index 000000000..95a455866
--- /dev/null
+++ b/sites/all/modules/ckeditor_link_file/ckeditor_link_file.install
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the ckeditor_link_file module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function ckeditor_link_file_uninstall() {
+ variable_del('ckeditor_link_file_link_method');
+ variable_del('ckeditor_link_file_autocomplete_file_types');
+}
diff --git a/sites/all/modules/ckeditor_link_file/ckeditor_link_file.module b/sites/all/modules/ckeditor_link_file/ckeditor_link_file.module
new file mode 100644
index 000000000..bba605a21
--- /dev/null
+++ b/sites/all/modules/ckeditor_link_file/ckeditor_link_file.module
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Integrates CKEditor Link with file entities.
+ */
+
+// Functions for recording file usage when using CKEditor Link File links.
+require_once dirname(__FILE__) . '/includes/ckeditor_link_file.usage.inc';
+
+/**
+ * Implements hook_ckeditor_link_types().
+ */
+function ckeditor_link_file_ckeditor_link_types() {
+ $types[] = array(
+ 'type' => 'file',
+ 'file' => 'includes/ckeditor_link_file.file.inc',
+ );
+
+ return $types;
+}
diff --git a/sites/all/modules/ckeditor_link_file/includes/ckeditor_link_file.file.inc b/sites/all/modules/ckeditor_link_file/includes/ckeditor_link_file.file.inc
new file mode 100644
index 000000000..7e5b39da0
--- /dev/null
+++ b/sites/all/modules/ckeditor_link_file/includes/ckeditor_link_file.file.inc
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * @file
+ * Implementation of CKEditor's hooks for file entities.
+ */
+
+/**
+ * Implements hook_ckeditor_link_TYPE_autocomplete().
+ */
+function ckeditor_link_file_ckeditor_link_file_autocomplete($string, $limit) {
+ $matches = array();
+
+ $file_types = array_keys(array_filter(variable_get('ckeditor_link_file_autocomplete_file_types', array('- any -' => '- any -'))));
+ if (count($file_types)) {
+ $query = db_select('file_managed', 'fm')
+ ->fields('fm', array('fid', 'filename'))
+ ->condition('fm.filename', '%'. db_like($string) .'%', 'LIKE')
+ ->orderBy('fm.filename')
+ ->orderBy('fm.type')
+ ->range(0, $limit)
+ ->addTag('file_access');
+ if (!in_array('- any -', $file_types)) {
+ $query->condition('fm.type', $file_types, 'IN');
+ }
+ $result = $query->execute();
+ foreach ($result as $file) {
+ $matches['file/'. $file->fid] = $file->filename;
+ }
+ }
+
+ return $matches;
+}
+
+/**
+ * Implements hook_ckeditor_link_TYPE_revert().
+ */
+function ckeditor_link_file_ckeditor_link_file_revert($path, &$langcode) {
+ if (!preg_match('`^file/(\d+)$`', $path, $matches)) {
+ return;
+ }
+
+ $fid = $matches[1];
+ $query = db_select('file_managed', 'fm')
+ ->fields('fm', array('filename'))
+ ->condition('fm.fid', $fid)
+ ->addTag('file_access');
+ if ($file = $query->execute()->fetchObject()) {
+ return $file->filename;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Implements hook_ckeditor_link_TYPE_url().
+ */
+function ckeditor_link_file_ckeditor_link_file_url($path, $langcode) {
+ if (!preg_match('`^file/(\d+)$`', $path, $matches)) {
+ return;
+ }
+
+ $fid = $matches[1];
+
+ $link_method = variable_get('ckeditor_link_file_link_method', 'file');
+
+ switch ($link_method) {
+ case 'file':
+ $url = "file/$fid";
+ break;
+ case 'url':
+ $file = file_load($fid);
+ $url = file_create_url($file->uri);
+ break;
+ case 'download':
+ $url = "file/$fid/download";
+ break;
+ }
+
+ return ckeditor_link_url($url, $langcode);
+}
+
+/**
+ * Implements hook_ckeditor_link_TYPE_settings().
+ */
+function ckeditor_link_file_ckeditor_link_file_settings() {
+ $form['file'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Files'),
+ );
+
+ $form['file']['ckeditor_link_file_link_method'] = array(
+ '#type' => 'radios',
+ '#title' => t('Link method'),
+ '#options' => array(
+ 'file' => t('File'),
+ 'url' => t('URL'),
+ 'download' => t('Download'),
+ ),
+ '#default_value' => variable_get('ckeditor_link_file_link_method', 'file'),
+ '#description' => t('Choose whether the link points to the file, the URL of the file or a download of the file.'),
+ );
+
+ $options = array();
+
+ foreach (file_type_get_enabled_types() as $type) {
+ $options[$type->type] = $type->label;
+ }
+
+ $form['file']['ckeditor_link_file_autocomplete_file_types'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('File types'),
+ '#options' => array('- any -' => t('<em>Any file type</em>')) + array_map('check_plain', $options),
+ '#default_value' => variable_get('ckeditor_link_file_autocomplete_file_types', array('- any -' => '- any -')),
+ '#description' => t('Select the file types to be available as autocomplete suggestions.'),
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/ckeditor_link_file/includes/ckeditor_link_file.usage.inc b/sites/all/modules/ckeditor_link_file/includes/ckeditor_link_file.usage.inc
new file mode 100644
index 000000000..c4f9b7709
--- /dev/null
+++ b/sites/all/modules/ckeditor_link_file/includes/ckeditor_link_file.usage.inc
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * @file
+ * Functions for recording file usage when using CKEditor Link File links.
+ */
+
+/**
+ * Implements hook_field_attach_insert().
+ *
+ * Track file usage for file links included in formatted text. Note that this is
+ * heavy-handed, and should be replaced when Drupal's filter system is
+ * context-aware.
+ */
+function ckeditor_link_file_field_attach_insert($entity_type, $entity) {
+ _ckeditor_link_file_filter_add_file_usage_from_fields($entity_type, $entity);
+}
+
+/**
+ * Implements hook_field_attach_update().
+ *
+ * @see ckeditor_link_file_field_attach_insert().
+ */
+function ckeditor_link_file_field_attach_update($entity_type, $entity) {
+ _ckeditor_link_file_filter_add_file_usage_from_fields($entity_type, $entity);
+}
+
+/**
+ * Add file usage from file references in an entity's text fields.
+ */
+function _ckeditor_link_file_filter_add_file_usage_from_fields($entity_type, $entity) {
+ // Track the total usage for files from all fields combined.
+ $entity_files = ckeditor_link_file_entity_field_count_files($entity_type, $entity);
+
+ list($entity_id, $entity_vid, $entity_bundle) = entity_extract_ids($entity_type, $entity);
+
+ // When an entity has revisions and then is saved again NOT as new version the
+ // previous revision of the entity has be loaded to get the last known good
+ // count of files. The saved data is compared against the last version
+ // so that a correct file count can be created for that (the current) version
+ // id. This code may assume some things about entities that are only true for
+ // node objects. This should be reviewed.
+ // @TODO this conditional can probably be condensed
+ if (empty($entity->revision) && empty($entity->old_vid) && empty($entity->is_new) && ! empty($entity->original)) {
+ $old_files = ckeditor_link_file_entity_field_count_files($entity_type, $entity->original);
+
+ foreach ($old_files as $fid => $old_file_count) {
+ // Were there more files on the node just prior to saving?
+ if (empty($entity_files[$fid])) {
+ $entity_files[$fid] = 0;
+ }
+ if ($old_file_count > $entity_files[$fid]) {
+ $deprecate = $old_file_count - $entity_files[$fid];
+
+ // Now deprecate this usage
+ $file = file_load($fid);
+
+ if ($file) {
+ file_usage_delete($file, 'ckeditor_link_file', $entity_type, $entity_id, $deprecate);
+ }
+ // Usage is deleted, nothing more to do with this file
+ unset($entity_files[$fid]);
+ }
+ // There are the same number of files, nothing to do
+ elseif ($entity_files[$fid] == $old_file_count) {
+ unset($entity_files[$fid]);
+ }
+ // There are more files now, adjust the difference for the greater number.
+ // file_usage incrementing will happen below.
+ else {
+ // We just need to adjust what the file count will account for the new
+ // images that have been added since the increment process below will
+ // just add these additional ones in
+ $entity_files[$fid] = $entity_files[$fid] - $old_file_count;
+ }
+ }
+ }
+
+ // Each entity revision counts for file usage. If versions are not enabled
+ // the file_usage table will have no entries for this because of the delete
+ // query above.
+ foreach ($entity_files as $fid => $entity_count) {
+ if ($file = file_load($fid)) {
+ file_usage_add($file, 'ckeditor_link_file', $entity_type, $entity_id, $entity_count);
+ }
+ }
+
+}
+
+/**
+ * Parse file references from an entity's text fields and return them as an array.
+ */
+function ckeditor_link_file_filter_parse_from_fields($entity_type, $entity) {
+ $file_references = array();
+
+ foreach (_ckeditor_link_file_filter_fields_with_text_filtering($entity_type, $entity) as $field_name) {
+ if ($field_items = field_get_items($entity_type, $entity, $field_name)) {
+ foreach ($field_items as $field_item) {
+ // Find all links in the text field.
+ $dom = new DOMDocument;
+ $dom->loadHTML($field_item['value']);
+ $links = $dom->getElementsByTagName('a');
+
+ // Loop through all of the links and check if they represent files.
+ foreach ($links as $link) {
+ // Find the link's href and trim off the leading slash.
+ $href = ltrim($link->getAttribute('href'), '/');
+
+ // Check if the link belongs to CKEditor Link File.
+ preg_match_all('`^file/(\d+)$`', $href, $matches);
+
+ foreach ($matches[1] as $fid) {
+ $file_references[] = array('fid' => $fid);
+ }
+ }
+ }
+ }
+ }
+
+ return $file_references;
+}
+
+/**
+ * Returns an array containing the names of all fields that perform text filtering.
+ */
+function _ckeditor_link_file_filter_fields_with_text_filtering($entity_type, $entity) {
+ list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
+ $fields = field_info_instances($entity_type, $bundle);
+
+ // Get all of the fields on this entity that allow text filtering.
+ $fields_with_text_filtering = array();
+
+ foreach ($fields as $field_name => $field) {
+ if (!empty($field['settings']['text_processing'])) {
+ $fields_with_text_filtering[] = $field_name;
+ }
+ }
+
+ return $fields_with_text_filtering;
+}
+
+/**
+ * Utility function to get the file count in this entity
+ *
+ * @param type $entity
+ * @param type $entity_type
+ * @return int
+ */
+function ckeditor_link_file_entity_field_count_files($entity_type, $entity) {
+ $entity_files = array();
+
+ foreach (ckeditor_link_file_filter_parse_from_fields($entity_type, $entity) as $file_reference) {
+ if (empty($entity_files[$file_reference['fid']])) {
+ $entity_files[$file_reference['fid']] = 1;
+ }
+ else {
+ $entity_files[$file_reference['fid']]++;
+ }
+ }
+
+ return $entity_files;
+}
+
+/**
+ * Implements hook_entity_delete().
+ */
+function ckeditor_link_file_entity_delete($entity, $type) {
+ list($entity_id) = entity_extract_ids($type, $entity);
+
+ db_delete('file_usage')
+ ->condition('module', 'ckeditor_link_file')
+ ->condition('type', $type)
+ ->condition('id', $entity_id)
+ ->execute();
+}
+
+/**
+ * Implements hook_field_attach_delete_revision().
+ */
+function ckeditor_link_file_field_attach_delete_revision($entity_type, $entity) {
+ list($entity_id) = entity_extract_ids($entity_type, $entity);
+
+ $files = ckeditor_link_file_entity_field_count_files($entity_type, $entity);
+
+ foreach ($files as $fid => $count) {
+ if ($file = file_load($fid)) {
+ file_usage_delete($file, 'ckeditor_link_file', $entity_type , $entity_id, $count);
+ }
+ }
+}
diff --git a/sites/all/modules/ckeditor_link_file/readme.txt b/sites/all/modules/ckeditor_link_file/readme.txt
new file mode 100644
index 000000000..cfcb90c9c
--- /dev/null
+++ b/sites/all/modules/ckeditor_link_file/readme.txt
@@ -0,0 +1,67 @@
+CONTENTS OF THIS FILE
+---------------------
+
+ * Introduction
+ * Requirements
+ * Installation
+ * Usage
+ * Sponsors
+
+INTRODUCTION
+------------
+
+Current Maintainers:
+
+ * Devin Carlson <http://drupal.org/user/290182>
+
+CKEditor Link File provides integration between CKEditor Link and File entity,
+allowing editors to link to files from within CKEditor.
+
+CKEditor Link File gives editors three important tools:
+
+ * A simple method of linking to existing files (promoting file reuse and
+ helping to eliminate duplicate files).
+ * The ability to link to files, file URLs or file downloads (configurable by an
+ administrator).
+ * The ability to restrict links to certain file types such as audio, video,
+ images or documents (configurable by an administrator).
+
+REQUIREMENTS
+------------
+
+CKEditor Link File has two dependencies.
+
+Contributed modules
+ * CKEditor Link
+ * File entity
+
+INSTALLATION
+------------
+
+To install CKEditor Link File:
+
+ * CKEditor Link File can be installed using the standard module installation
+ process (http://drupal.org/documentation/install/modules-themes/modules-7).
+
+USAGE
+-----
+
+CKEditor Link File adds file support to CKEditor Link.
+
+With CKEditor Link File installed, users can link to files in addition to menu
+items, taxonomy terms and nodes which CKEditor Link supports out of the box.
+
+CKEditor Link File provides three file link methods: file, URL and download.
+ * File
+ Provide a link to view the file entity itself (file/%fid)
+ * URL
+ Link to a web-accessible URL of the file (example.com/files/image.jpg)
+ * Download
+ A special link to the file which forces the visitor's browser to download
+ the file instead of opening it.
+
+Sponsors
+--------
+
+Development of CKEditor Link File is sponsored by the Ontario Ministry of
+Northern Development and Mines (http://www.mndm.gov.on.ca).
diff --git a/sites/all/modules/colorbox/LICENSE.txt b/sites/all/modules/colorbox/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/colorbox/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/colorbox/README.txt b/sites/all/modules/colorbox/README.txt
new file mode 100644
index 000000000..31cb89015
--- /dev/null
+++ b/sites/all/modules/colorbox/README.txt
@@ -0,0 +1,157 @@
+Drupal colorbox module:
+------------------------
+Maintainers:
+ Fredrik Jonsson (http://drupal.org/user/5546)
+Requires - Drupal 7
+License - GPL (see LICENSE)
+
+
+Overview:
+--------
+Colorbox is a light-weight, customizable lightbox plugin for jQuery 1.4.3+.
+This module allows for integration of Colorbox into Drupal.
+The jQuery library is a part of Drupal since version 5+.
+
+Images, iframed or inline content etc. can be displayed in a
+overlay above the current page.
+
+* jQuery - http://jquery.com/
+* Colorbox - http://www.jacklmoore.com/colorbox/
+
+
+Features:
+---------
+
+The Colorbox module:
+
+* Excellent integration with Image field and Image styles
+* Choose between a default style and 5 example styles that are included.
+* Style the Colorbox with a custom colorbox.css file in your theme.
+* Drush command to download and install the Colorbox plugin in
+ sites/all/libraries
+
+The Colorbox plugin:
+
+* Supports photos, grouping, slideshow, ajax, inline, and iframed content.
+* Appearance is controlled through CSS so it can be restyled.
+* Preloads upcoming images in a photo group.
+* Completely unobtrusive, options are set in the JS and require no
+ changes to existing HTML.
+* Compatible with: jQuery 1.3.2+ in Firefox, Safari, Chrome, Opera,
+ Internet Explorer 7+.
+* Released under the MIT License.
+
+
+Installation:
+------------
+1. Download and unpack the Libraries module directory in your modules folder
+ (this will usually be "sites/all/modules/").
+ Link: http://drupal.org/project/libraries
+2. Download and unpack the Colorbox module directory in your modules folder
+ (this will usually be "sites/all/modules/").
+3. Download and unpack the Colorbox plugin in "sites/all/libraries".
+ Make sure the path to the plugin file becomes:
+ "sites/all/libraries/colorbox/jquery.colorbox-min.js"
+ Link: https://github.com/jackmoore/colorbox/archive/1.x.zip
+ Drush users can use the command "drush colorbox-plugin".
+4. Go to "Administer" -> "Modules" and enable the Colorbox module.
+
+
+Configuration:
+-------------
+Go to "Configuration" -> "Media" -> "Colorbox" to find
+all the configuration options.
+
+
+Use the Views Colorbox Trigger field:
+------------------------------------
+TODO
+
+
+Add a custom Colorbox style to your theme:
+----------------------------------------
+The easiest way is to start with either the default style or one of the
+example styles included in the Colorbox JS library download. Simply copy the entire
+style folder to your theme and rename it to something logical like "mycolorbox".
+Inside that folder are both a .css and .js file, rename both of those as well to match
+your folder name: i.e. "colorbox_mycolorbox.css" and "colorbox_mycolorbox.js"
+
+Add entries in your theme's .info file for the Colorbox CSS/JS files:
+
+stylesheets[all][] = mycolorbox/colorbox_mycolorbox.css
+scripts[] = mycolorbox/colorbox_mycolorbox.js
+
+Go to "Configuration" -> "Media" -> "Colorbox" and select "None" under
+"Styles and Options". This will leave the styling of Colorbox up to your theme.
+Make any CSS adjustments to your "colorbox_mycolorbox.css" file.
+
+
+Load images from custom links in a Colorbox:
+--------------------------------------------
+
+Add the class "colorbox" to the link and point its href attribute to the image
+you want to display in the Colorbox.
+
+
+Load content in a Colorbox:
+---------------------------
+Check the "Enable Colorbox load" option in Colorbox settings.
+
+This enables custom links that can open content in a Colorbox.
+Add the class "colorbox-load" to the link and build the url like
+this "[path]?width=500&height=500&iframe=true"
+or "[path]?width=500&height=500" if you don't want an iframe.
+
+Other modules may activate this for easy Colorbox integration.
+
+
+Load inline content in a Colorbox:
+----------------------------------
+Check the "Enable Colorbox inline" option in Colorbox settings.
+
+This enables custom links that can open inline content in a Colorbox.
+Inline in this context means some part/tag of the current page, e.g. a div.
+Replace "id-of-content" with the id of the tag you want to open.
+
+Add the class "colorbox-inline" to the link and build the url like
+this "?width=500&height=500&inline=true#id-of-content".
+
+It could e.g. look like this.
+
+<a class="colorbox-inline" href="?width=500&height=500&inline=true#id-of-content">Link to click</a>
+
+<div style="display: none;">
+<div id="id-of-content">What ever content you want to display in a Colorbox.</div>
+</div>
+
+Other modules may activate this for easy Colorbox integration.
+
+
+Drush:
+------
+A Drush command is provides for easy installation of the Colorbox
+plugin itself.
+
+% drush colorbox-plugin
+
+The command will download the plugin and unpack it in "sites/all/libraries".
+It is possible to add another path as an option to the command, but not
+recommended unless you know what you are doing.
+
+
+Image in Colorbox not displayed in Internet Explorer 8:
+-------------------------------------------------------
+
+If your theme has CSS like this (popular in responsive design):
+
+img {
+ max-width: 100%;
+}
+
+Internet Explorer 8 will have problems with showing images in the Colorbox.
+The fix is to add this to the theme CSS:
+
+#cboxLoadedContent img {
+ max-width: none;
+}
+
diff --git a/sites/all/modules/colorbox/colorbox-insert-image.tpl.php b/sites/all/modules/colorbox/colorbox-insert-image.tpl.php
new file mode 100644
index 000000000..623db20e3
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox-insert-image.tpl.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Template file for Colorbox content inserted via the Insert module.
+ *
+ * Available variables:
+ * - $item: The complete item being inserted.
+ * - $url: The URL to the image.
+ * - $path: The URL to the image that Colorbox should open.
+ * - $class: A set of classes assigned to this image (if any).
+ * - $width: The width of the image (if known).
+ * - $height: The height of the image (if known).
+ * - $style_name: The Image style being used.
+ * - $gallery_id: The ID of the Colorbox gallery.
+ *
+ * Note that ALT and Title fields should not be filled in here, instead they
+ * should use placeholders that will be updated through JavaScript when the
+ * image is inserted.
+ *
+ * Available placeholders:
+ * - __alt__: The ALT text, intended for use in the <img> tag.
+ * - __title__: The Title text, intended for use in the <img> tag.
+ * - __description__: A description of the image, sometimes used as a caption.
+ * - __filename__: The file name.
+ * - __[token]_or_filename__: Any of the above tokens if available, otherwise
+ * use the file's name. i.e. __title_or_filename__.
+ */
+?>
+<a href="<?php print $path; ?>" title="__title__" class="colorbox colorbox-insert-image" rel="<?php print $gallery_id; ?>"><img src="<?php print $url; ?>" <?php if ($width && $height): ?>width="<?php print $width; ?>" height="<?php print $height; ?>" <?php endif; ?>alt="__alt__" title="__title__" class="<?php print $class; ?>" /></a>
diff --git a/sites/all/modules/colorbox/colorbox.admin.inc b/sites/all/modules/colorbox/colorbox.admin.inc
new file mode 100644
index 000000000..002c50bb4
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox.admin.inc
@@ -0,0 +1,313 @@
+<?php
+
+/**
+ * @file
+ * Administrative page callbacks for the colorbox module.
+ */
+
+/**
+ * General configuration form for controlling the colorbox behaviour.
+ */
+function colorbox_admin_settings() {
+ drupal_add_js(drupal_get_path('module', 'colorbox') . '/js/colorbox_admin_settings.js', array('preprocess' => FALSE));
+
+ $library = libraries_detect('colorbox');
+
+ if (module_exists('insert')) {
+ $form['colorbox_insert_module'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Insert module settings'),
+ );
+ $image_styles = image_style_options(FALSE);
+ $form['colorbox_insert_module']['colorbox_image_style'] = array(
+ '#type' => 'select',
+ '#title' => t('Image style'),
+ '#empty_option' => t('None (original image)'),
+ '#options' => $image_styles,
+ '#default_value' => variable_get('colorbox_image_style', ''),
+ '#description' => t('Select which image style to use for viewing images in the colorbox.'),
+ );
+ $form['colorbox_insert_module']['colorbox_insert_gallery'] = array(
+ '#type' => 'radios',
+ '#title' => t('Insert image gallery'),
+ '#default_value' => variable_get('colorbox_insert_gallery', 0),
+ '#options' => array(0 => t('Per page gallery'), 3 => t('No gallery')),
+ '#description' => t('Should the gallery be all images on the page (default) or disabled.'),
+ );
+ }
+
+ $form['colorbox_extra_features'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Extra features'),
+ );
+ $form['colorbox_extra_features']['colorbox_load'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable Colorbox load'),
+ '#default_value' => variable_get('colorbox_load', 0),
+ '#description' => t('This enables custom links that can open forms and paths in a Colorbox. Add the class "colorbox-load" to the link and build the url like this for paths "[path]?width=500&height=500&iframe=true" or "[path]?width=500&height=500" if you don\'t want an iframe. Other modules may activate this for easy Colorbox integration.'),
+ );
+ $form['colorbox_extra_features']['colorbox_inline'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable Colorbox inline'),
+ '#default_value' => variable_get('colorbox_inline', 0),
+ '#description' => t('This enables custom links that can open inline content in a Colorbox. Add the class "colorbox-inline" to the link and build the url like this "?width=500&height=500&inline=true#id-of-content". Other modules may activate this for easy Colorbox integration.'),
+ );
+
+ $form['colorbox_custom_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Styles and options'),
+ );
+ $colorbox_styles = array(
+ 'default' => t('Default'),
+ 'plain' => t('Plain (mainly for images)'),
+ 'stockholmsyndrome' => t('Stockholm Syndrome'),
+ $library['library path'] . '/example1' => t('Example 1'),
+ $library['library path'] . '/example2' => t('Example 2'),
+ $library['library path'] . '/example3' => t('Example 3'),
+ $library['library path'] . '/example4' => t('Example 4'),
+ $library['library path'] . '/example5' => t('Example 5'),
+ 'none' => t('None'),
+ );
+ $form['colorbox_custom_settings']['colorbox_style'] = array(
+ '#type' => 'select',
+ '#title' => t('Style'),
+ '#options' => $colorbox_styles,
+ '#default_value' => variable_get('colorbox_style', 'default'),
+ '#description' => t('Select the style to use for the Colorbox. The example styles are the ones that come with the Colorbox plugin. Select "None" if you have added Colorbox styles to your theme.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_custom_settings_activate'] = array(
+ '#type' => 'radios',
+ '#title' => t('Options'),
+ '#options' => array(0 => t('Default'), 1 => t('Custom')),
+ '#default_value' => variable_get('colorbox_custom_settings_activate', 0),
+ '#description' => t('Use the default or custom options for Colorbox.'),
+ '#prefix' => '<div class="colorbox-custom-settings-activate">',
+ '#suffix' => '</div>',
+ );
+
+ $js_hide = variable_get('colorbox_custom_settings_activate', 0) ? '' : ' js-hide';
+ $form['colorbox_custom_settings']['wrapper_start'] = array(
+ '#markup' => '<div class="colorbox-custom-settings' . $js_hide . '">',
+ );
+
+ $form['colorbox_custom_settings']['colorbox_transition_type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Transition type'),
+ '#options' => array('elastic' => t('Elastic'), 'fade' => t('Fade'), 'none' => t('None')),
+ '#default_value' => variable_get('colorbox_transition_type', 'elastic'),
+ '#description' => t('The transition type.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_transition_speed'] = array(
+ '#type' => 'select',
+ '#title' => t('Transition speed'),
+ '#options' => drupal_map_assoc(array(100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600)),
+ '#default_value' => variable_get('colorbox_transition_speed', 350),
+ '#description' => t('Sets the speed of the fade and elastic transitions, in milliseconds.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_opacity'] = array(
+ '#type' => 'select',
+ '#title' => t('Opacity'),
+ '#options' => drupal_map_assoc(array('0', '0.10', '0.15', '0.20', '0.25', '0.30', '0.35', '0.40', '0.45', '0.50', '0.55', '0.60', '0.65', '0.70', '0.75', '0.80', '0.85', '0.90', '0.95', '1')),
+ '#default_value' => variable_get('colorbox_opacity', '0.85'),
+ '#description' => t('The overlay opacity level. Range: 0 to 1.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_text_current'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Current'),
+ '#default_value' => variable_get('colorbox_text_current', '{current} of {total}'),
+ '#size' => 30,
+ '#description' => t('Text format for the content group / gallery count. {current} and {total} are detected and replaced with actual numbers while Colorbox runs.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_text_previous'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Previous'),
+ '#default_value' => variable_get('colorbox_text_previous', '« Prev'),
+ '#size' => 30,
+ '#description' => t('Text for the previous button in a shared relation group.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_text_next'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Next'),
+ '#default_value' => variable_get('colorbox_text_next', 'Next »'),
+ '#size' => 30,
+ '#description' => t('Text for the next button in a shared relation group.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_text_close'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Close'),
+ '#default_value' => variable_get('colorbox_text_close', 'Close'),
+ '#size' => 30,
+ '#description' => t('Text for the close button. The "Esc" key will also close Colorbox.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_overlayclose'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Overlay close'),
+ '#default_value' => variable_get('colorbox_overlayclose', 1),
+ '#description' => t('Enable closing Colorbox by clicking on the background overlay.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_maxwidth'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Max width'),
+ '#default_value' => variable_get('colorbox_maxwidth', '98%'),
+ '#size' => 30,
+ '#description' => t('Set a maximum width for loaded content. Example: "100%", 500, "500px".'),
+ );
+ $form['colorbox_custom_settings']['colorbox_maxheight'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Max height'),
+ '#default_value' => variable_get('colorbox_maxheight', '98%'),
+ '#size' => 30,
+ '#description' => t('Set a maximum height for loaded content. Example: "100%", 500, "500px".'),
+ );
+ $form['colorbox_custom_settings']['colorbox_initialwidth'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Initial width'),
+ '#default_value' => variable_get('colorbox_initialwidth', '300'),
+ '#size' => 30,
+ '#description' => t('Set the initial width, prior to any content being loaded. Example: "100%", 500, "500px".'),
+ );
+ $form['colorbox_custom_settings']['colorbox_initialheight'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Initial height'),
+ '#default_value' => variable_get('colorbox_initialheight', '250'),
+ '#size' => 30,
+ '#description' => t('Set the initial height, prior to any content being loaded. Example: "100%", 500, "500px".'),
+ );
+ $form['colorbox_custom_settings']['colorbox_fixed'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Fixed'),
+ '#default_value' => variable_get('colorbox_fixed', 1),
+ '#description' => t('If the Colorbox should be displayed in a fixed position within the visitor\'s viewport or relative to the document.'),
+ );
+
+ $form['colorbox_custom_settings']['colorbox_slideshow_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Slideshow settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['colorbox_custom_settings']['colorbox_slideshow_settings']['colorbox_slideshow'] = array(
+ '#type' => 'radios',
+ '#title' => t('Slideshow'),
+ '#options' => array(0 => t('Off'), 1 => t('On')),
+ '#default_value' => variable_get('colorbox_slideshow', 0),
+ '#description' => t('An automatic slideshow to a content group / gallery.'),
+ '#prefix' => '<div class="colorbox-slideshow-settings-activate">',
+ '#suffix' => '</div>',
+ );
+ $form['colorbox_custom_settings']['colorbox_scrolling'] = array(
+ '#type' => 'radios',
+ '#title' => t('Scrollbars'),
+ '#options' => array(1 => t('On'), 0 => t('Off')),
+ '#default_value' => variable_get('colorbox_scrolling', 1),
+ '#description' => t('If false, Colorbox will hide scrollbars for overflowing content. This could be used on conjunction with the resize method for a smoother transition if you are appending content to an already open instance of Colorbox.'),
+ );
+
+ $js_hide = variable_get('colorbox_slideshow', 0) ? '' : ' js-hide';
+ $form['colorbox_custom_settings']['colorbox_slideshow_settings']['wrapper_start'] = array(
+ '#markup' => '<div class="colorbox-slideshow-settings' . $js_hide . '">',
+ );
+
+ $form['colorbox_custom_settings']['colorbox_slideshow_settings']['colorbox_slideshowauto'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Slideshow autostart'),
+ '#default_value' => variable_get('colorbox_slideshowauto', 1),
+ '#description' => t('If the slideshow should automatically start to play.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_slideshow_settings']['colorbox_slideshowspeed'] = array(
+ '#type' => 'select',
+ '#title' => t('Slideshow speed'),
+ '#options' => drupal_map_assoc(array(1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000)),
+ '#default_value' => variable_get('colorbox_slideshowspeed', 2500),
+ '#description' => t('Sets the speed of the slideshow, in milliseconds.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_slideshow_settings']['colorbox_text_start'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Start slideshow'),
+ '#default_value' => variable_get('colorbox_text_start', 'start slideshow'),
+ '#size' => 30,
+ '#description' => t('Text for the slideshow start button.'),
+ );
+ $form['colorbox_custom_settings']['colorbox_slideshow_settings']['colorbox_text_stop'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Stop slideshow'),
+ '#default_value' => variable_get('colorbox_text_stop', 'stop slideshow'),
+ '#size' => 30,
+ '#description' => t('Text for the slideshow stop button.'),
+ );
+
+ $form['colorbox_custom_settings']['colorbox_slideshow_settings']['wrapper_stop'] = array(
+ '#markup' => '</div>',
+ );
+
+ $form['colorbox_custom_settings']['wrapper_stop'] = array(
+ '#markup' => '</div>',
+ );
+
+ $form['colorbox_advanced_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Advanced settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['colorbox_advanced_settings']['colorbox_mobile_detect'] = array(
+ '#type' => 'radios',
+ '#title' => t('Mobile detection'),
+ '#options' => array(1 => t('On'), 0 => t('Off')),
+ '#default_value' => variable_get('colorbox_mobile_detect', 1),
+ '#description' => t('If on (default) Colorbox will not be active for devices with a the max width set below.'),
+ );
+ $form['colorbox_advanced_settings']['colorbox_mobile_device_width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Device with'),
+ '#default_value' => variable_get('colorbox_mobile_device_width', '480px'),
+ '#size' => 30,
+ '#description' => t('Set the mobile device max with. Default: 480px.'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="colorbox_mobile_detect"]' => array('value' => '1'),
+ ),
+ ),
+ );
+ $form['colorbox_advanced_settings']['colorbox_caption_trim'] = array(
+ '#type' => 'radios',
+ '#title' => t('Caption shortening'),
+ '#options' => array(0 => t('Default'), 1 => t('Yes')),
+ '#default_value' => variable_get('colorbox_caption_trim', 0),
+ '#description' => t('If the caption should be made shorter in the Colorbox to avoid layout problems. The default is to shorten for the example styles, they need it, but not for other styles.'),
+ );
+ $form['colorbox_advanced_settings']['colorbox_caption_trim_length'] = array(
+ '#type' => 'select',
+ '#title' => t('Caption max length'),
+ '#options' => drupal_map_assoc(array(40, 45, 50, 55, 60, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120)),
+ '#default_value' => variable_get('colorbox_caption_trim_length', 75),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="colorbox_caption_trim"]' => array('value' => '1'),
+ ),
+ ),
+ );
+ $form['colorbox_advanced_settings']['colorbox_visibility'] = array(
+ '#type' => 'radios',
+ '#title' => t('Show Colorbox on specific pages'),
+ '#options' => array(0 => t('All pages except those listed'), 1 => t('Only the listed pages')),
+ '#default_value' => variable_get('colorbox_visibility', 0),
+ );
+ $form['colorbox_advanced_settings']['colorbox_pages'] = array(
+ '#type' => 'textarea',
+ '#title' => '<span class="element-invisible">' . t('Pages') . '</span>',
+ '#default_value' => variable_get('colorbox_pages', "admin*\nimagebrowser*\nimg_assist*\nimce*\nnode/add/*\nnode/*/edit\nprint/*\nprintpdf/*\nsystem/ajax\nsystem/ajax/*"),
+ '#description' => t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>')),
+ );
+ $form['colorbox_advanced_settings']['colorbox_compression_type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Choose Colorbox compression level'),
+ '#options' => array(
+ 'minified' => t('Production (Minified)'),
+ 'source' => t('Development (Uncompressed Code)'),
+ ),
+ '#default_value' => variable_get('colorbox_compression_type', 'minified'),
+ );
+
+ return system_settings_form($form);
+}
diff --git a/sites/all/modules/colorbox/colorbox.api.php b/sites/all/modules/colorbox/colorbox.api.php
new file mode 100644
index 000000000..61495babf
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox.api.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * API documentation for the colorbox module.
+ */
+
+/**
+ * Allows to override Colorbox settings and style.
+ *
+ * Implements hook_colorbox_settings_alter().
+ *
+ * @param $settings
+ * An associative array of Colorbox settings. See the
+ * @link http://colorpowered.com/colorbox/ Colorbox documentation @endlink
+ * for the full list of supported parameters.
+ * @param $style
+ * The name of the active style plugin. If $style is 'none', no Colorbox
+ * theme will be loaded.
+ */
+function hook_colorbox_settings_alter(&$settings, &$style) {
+ // Disable automatic downscaling of images to maxWidth/maxHeight size.
+ $settings['scalePhotos'] = FALSE;
+
+ // Use custom style plugin specifically for node/123.
+ if ($_GET['q'] == 'node/123') {
+ $style = 'mystyle';
+ }
+}
+
+/**
+ * Allows to override activation of Colobox for the current URL.
+ *
+ * @param $active
+ * A boolean indicating whether colorbox should be active for the current
+ * URL or not.
+ */
+function hook_colorbox_active_alter(&$active) {
+ $path = drupal_get_path_alias($_GET['q']);
+ if (drupal_match_path($path, 'admin/config/colorbox_test')) {
+ // Enable colorbox for this URL.
+ $active = TRUE;
+ }
+}
diff --git a/sites/all/modules/colorbox/colorbox.info b/sites/all/modules/colorbox/colorbox.info
new file mode 100644
index 000000000..4b5fda874
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox.info
@@ -0,0 +1,14 @@
+name = Colorbox
+description = A light-weight, customizable lightbox plugin for jQuery 1.4.3+.
+dependencies[] = libraries (>=2.x)
+core = 7.x
+configure = admin/config/media/colorbox
+
+files[] = views/colorbox_handler_field_colorbox.inc
+
+; Information added by Drupal.org packaging script on 2015-10-01
+version = "7.x-2.10"
+core = "7.x"
+project = "colorbox"
+datestamp = "1443691449"
+
diff --git a/sites/all/modules/colorbox/colorbox.install b/sites/all/modules/colorbox/colorbox.install
new file mode 100644
index 000000000..e1c8adf2a
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox.install
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the colorbox module.
+ */
+
+/**
+ * Implements hook_requirements().
+ */
+function colorbox_requirements($phase) {
+ $requirements = array();
+
+ if ($phase == 'runtime') {
+ $t = get_t();
+ $library = libraries_detect('colorbox');
+ $error_type = isset($library['error']) ? drupal_ucfirst($library['error']) : '';
+ $error_message = isset($library['error message']) ? $library['error message'] : '';
+
+ if (empty($library['installed'])) {
+ $requirements['colorbox_plugin'] = array(
+ 'title' => $t('Colorbox plugin'),
+ 'value' => $t('@e: At least @a', array('@e' => $error_type, '@a' => COLORBOX_MIN_PLUGIN_VERSION)),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => $t('!error You need to download the !colorbox, extract the archive and place the colorbox directory in the %path directory on your server.', array('!error' => $error_message, '!colorbox' => l($t('Colorbox plugin'), $library['download url']), '%path' => 'sites/all/libraries')),
+ );
+ }
+ elseif (version_compare($library['version'], COLORBOX_MIN_PLUGIN_VERSION, '>=')) {
+ $requirements['colorbox_plugin'] = array(
+ 'title' => $t('Colorbox plugin'),
+ 'severity' => REQUIREMENT_OK,
+ 'value' => $library['version'],
+ );
+ }
+ else {
+ $requirements['colorbox_plugin'] = array(
+ 'title' => $t('Colorbox plugin'),
+ 'value' => $t('At least @a', array('@a' => COLORBOX_MIN_PLUGIN_VERSION)),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => $t('You need to download a later version of the !colorbox and replace the old version located in the %path directory on your server.', array('!colorbox' => l($t('Colorbox plugin'), $library['download url']), '%path' => $library['library path'])),
+ );
+ }
+ }
+
+ return $requirements;
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function colorbox_uninstall() {
+ db_delete('variable')->condition('name', db_like('colorbox_') . '%', 'LIKE')->execute();
+}
+
+/**
+ * Delete the unused colorbox_login_form variable.
+ */
+function colorbox_update_7001() {
+ $ret = array();
+ variable_del('colorbox_login_form');
+
+ return $ret;
+}
+
+/**
+ * Delete the unused colorbox_title_trim and
+ * colorbox_title_trim_length variables.
+ */
+function colorbox_update_7002() {
+ $ret = array();
+ $colorbox_title_trim = variable_get('colorbox_title_trim', NULL);
+ $colorbox_title_trim_length = variable_get('colorbox_title_trim_length', NULL);
+ if (!empty($colorbox_title_trim)) {
+ variable_set('colorbox_caption_trim', $colorbox_title_trim);
+ }
+ if (!empty($colorbox_title_trim_length)) {
+ variable_set('colorbox_caption_trim_length', $colorbox_title_trim_length);
+ }
+ variable_del('colorbox_title_trim');
+ variable_del('colorbox_title_trim_length');
+
+ return $ret;
+}
+
+/**
+ * Delete the unused colorbox_login and colorbox_login_links variables.
+ */
+function colorbox_update_7200() {
+ $ret = array();
+ variable_del('colorbox_login');
+ variable_del('colorbox_login_links');
+
+ return $ret;
+}
+
+/**
+ * Delete the unused colorbox_auto_image_nodes variable.
+ */
+function colorbox_update_7201() {
+ $ret = array();
+ variable_del('colorbox_auto_image_nodes');
+
+ return $ret;
+}
+
+/**
+ * Update the colorbox_compression_type variable.
+ */
+function colorbox_update_7202() {
+ $ret = array();
+ if (variable_get('colorbox_compression_type', 'minified') == 'none') {
+ variable_set('colorbox_compression_type', 'source');
+ }
+ else {
+ variable_set('colorbox_compression_type', 'minified');
+ }
+
+ return $ret;
+}
+
diff --git a/sites/all/modules/colorbox/colorbox.make b/sites/all/modules/colorbox/colorbox.make
new file mode 100644
index 000000000..66b47fabc
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox.make
@@ -0,0 +1,8 @@
+core = 7.x
+api = 2
+
+libraries[colorbox][type] = "libraries"
+libraries[colorbox][download][type] = "file"
+libraries[colorbox][download][url] = "https://github.com/jackmoore/colorbox/archive/1.x.zip"
+libraries[colorbox][directory_name] = "colorbox"
+libraries[colorbox][destination] = "libraries"
diff --git a/sites/all/modules/colorbox/colorbox.module b/sites/all/modules/colorbox/colorbox.module
new file mode 100644
index 000000000..e3d94d230
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox.module
@@ -0,0 +1,525 @@
+<?php
+
+/**
+ * @file
+ * A light-weight, customizable lightbox plugin for jQuery 1.3
+ */
+
+/**
+ * The default path to the Colorbox directory.
+ */
+define('COLORBOX_MIN_PLUGIN_VERSION', '1.3.21.1');
+
+
+/**
+ * Implements hook_theme().
+ */
+function colorbox_theme() {
+ return array(
+ 'colorbox_imagefield' => array(
+ 'variables' => array(
+ 'image' => array(),
+ 'path' => NULL,
+ 'title' => NULL,
+ 'gid' => NULL,
+ ),
+ 'file' => 'colorbox.theme.inc',
+ ),
+
+ 'colorbox_insert_image' => array(
+ 'variables' => array(
+ 'item' => NULL,
+ 'widget' => NULL,
+ ),
+ 'template' => 'colorbox-insert-image',
+ 'pattern' => 'colorbox_insert_image__[a-z0-9_]+',
+ 'file' => 'colorbox.theme.inc',
+ ),
+
+ 'colorbox_image_formatter' => array(
+ 'variables' => array(
+ 'item' => NULL,
+ 'entity_type' => NULL,
+ 'entity' => NULL,
+ 'node' => NULL, // Left for legacy support.
+ 'field' => array(),
+ 'display_settings' => array(),
+ 'delta' => null,
+ ),
+ 'file' => 'colorbox.theme.inc',
+ ),
+ );
+}
+
+/**
+ * Implements hook_init().
+ */
+function colorbox_init() {
+ // Do not load colorbox during the Drupal installation process, e.g. if part
+ // of installation profiles.
+ if (!drupal_installation_attempted()) {
+ _colorbox_doheader();
+ }
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function colorbox_views_api() {
+ return array(
+ 'api' => 2,
+ 'path' => drupal_get_path('module', 'colorbox') . '/views',
+ );
+}
+
+/**
+ * Implements hook_libraries_info().
+ */
+function colorbox_libraries_info() {
+ $libraries['colorbox'] = array(
+ 'name' => 'Colorbox plugin',
+ 'vendor url' => 'http://www.jacklmoore.com/colorbox',
+ 'download url' => 'https://github.com/jackmoore/colorbox/archive/1.x.zip',
+ 'version arguments' => array(
+ 'file' => 'jquery.colorbox-min.js',
+ 'pattern' => '@(?i:Colorbox)\sv?([0-9\.a-z]+)@',
+ 'lines' => 5,
+ ),
+ 'files' => array(
+ 'js' => array(
+ 'jquery.colorbox-min.js',
+ ),
+ ),
+ 'variants' => array(
+ 'minified' => array(
+ 'files' => array(
+ 'js' => array(
+ 'jquery.colorbox-min.js',
+ ),
+ ),
+ ),
+ 'source' => array(
+ 'files' => array(
+ 'js' => array(
+ 'jquery.colorbox.js',
+ ),
+ ),
+ ),
+ ),
+ );
+
+ return $libraries;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function colorbox_menu() {
+ $items = array();
+
+ $items['admin/config/media/colorbox'] = array(
+ 'title' => 'Colorbox',
+ 'description' => 'Adjust Colorbox settings.',
+ 'file' => 'colorbox.admin.inc',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('colorbox_admin_settings'),
+ 'access arguments' => array('administer site configuration'),
+ );
+
+ return $items;
+}
+
+/**
+ * Check if Colorbox should be active for the current URL.
+ *
+ * @return
+ * TRUE if Colorbox should be active for the current page.
+ */
+function _colorbox_active() {
+ // Make it possible deactivate Colorbox with
+ // parameter ?colorbox=no in the url.
+ if (isset($_GET['colorbox']) && $_GET['colorbox'] == 'no') {
+ return FALSE;
+ }
+
+ // Code from the block_list function in block.module.
+ $path = drupal_get_path_alias($_GET['q']);
+ $colorbox_pages = variable_get('colorbox_pages', "admin*\nimagebrowser*\nimg_assist*\nimce*\nnode/add/*\nnode/*/edit\nprint/*\nprintpdf/*\nsystem/ajax\nsystem/ajax/*");
+ // Compare with the internal and path alias (if any).
+ $page_match = drupal_match_path($path, $colorbox_pages);
+ if ($path != $_GET['q']) {
+ $page_match = $page_match || drupal_match_path($_GET['q'], $colorbox_pages);
+ }
+ $page_match = variable_get('colorbox_visibility', 0) == 0 ? !$page_match : $page_match;
+
+ // Allow other modules to change the state of colorbox for the current URL.
+ drupal_alter('colorbox_active', $page_match);
+
+ return $page_match;
+}
+
+/**
+ * Loads the various js and css files.
+ */
+function _colorbox_doheader() {
+ static $already_added = FALSE;
+ if ($already_added) {
+ return; // Don't add the JavaScript and CSS multiple times.
+ }
+ if (!_colorbox_active()) {
+ return; // Don't add the JavaScript and CSS on specified paths.
+ }
+
+ // Insert options and translated strings as javascript settings.
+ if (variable_get('colorbox_custom_settings_activate', 0)) {
+ $js_settings = array(
+ 'transition' => variable_get('colorbox_transition_type', 'elastic'),
+ 'speed' => variable_get('colorbox_transition_speed', 350),
+ 'opacity' => variable_get('colorbox_opacity', '0.85'),
+ 'slideshow' => variable_get('colorbox_slideshow', 0) ? TRUE : FALSE,
+ 'slideshowAuto' => variable_get('colorbox_slideshowauto', 1) ? TRUE : FALSE,
+ 'slideshowSpeed' => variable_get('colorbox_slideshowspeed', 2500),
+ 'slideshowStart' => variable_get('colorbox_text_start', 'start slideshow'),
+ 'slideshowStop' => variable_get('colorbox_text_stop', 'stop slideshow'),
+ 'current' => strip_tags(variable_get('colorbox_text_current', '{current} of {total}')),
+ 'previous' => strip_tags(variable_get('colorbox_text_previous', '« Prev')),
+ 'next' => strip_tags(variable_get('colorbox_text_next', 'Next »')),
+ 'close' => strip_tags(variable_get('colorbox_text_close', 'Close')),
+ 'overlayClose' => variable_get('colorbox_overlayclose', 1) ? TRUE : FALSE,
+ 'maxWidth' => variable_get('colorbox_maxwidth', '98%'),
+ 'maxHeight' => variable_get('colorbox_maxheight', '98%'),
+ 'initialWidth' => variable_get('colorbox_initialwidth', '300'),
+ 'initialHeight' => variable_get('colorbox_initialheight', '250'),
+ 'fixed' => variable_get('colorbox_fixed', 1) ? TRUE : FALSE,
+ 'scrolling' => variable_get('colorbox_scrolling', 1) ? TRUE : FALSE,
+ 'mobiledetect' => variable_get('colorbox_mobile_detect', 1) ? TRUE : FALSE,
+ 'mobiledevicewidth' => variable_get('colorbox_mobile_device_width', '480px'),
+ );
+ }
+ else {
+ $js_settings = array(
+ 'opacity' => '0.85',
+ 'current' => t('{current} of {total}'),
+ 'previous' => t('« Prev'),
+ 'next' => t('Next »'),
+ 'close' => t('Close'),
+ 'maxWidth' => '98%',
+ 'maxHeight' => '98%',
+ 'fixed' => TRUE,
+ 'mobiledetect' => variable_get('colorbox_mobile_detect', 1) ? TRUE : FALSE,
+ 'mobiledevicewidth' => variable_get('colorbox_mobile_device_width', '480px'),
+ );
+ }
+
+ $path = drupal_get_path('module', 'colorbox');
+ $style = variable_get('colorbox_style', 'default');
+
+ // Give other modules the possibility to override Colorbox settings and style.
+ $data = &$js_settings;
+ drupal_alter('colorbox_settings', $data, $style);
+
+ drupal_add_js(array('colorbox' => $js_settings), array('type' => 'setting', 'scope' => JS_DEFAULT));
+
+ // Add and initialise the Colorbox plugin.
+ $variant = variable_get('colorbox_compression_type', 'minified');
+ if (module_exists('libraries')) {
+ libraries_load('colorbox', $variant);
+ }
+ drupal_add_js($path . '/js/colorbox.js');
+
+ // Add JS and CSS based on selected style.
+ switch ($style) {
+ case 'none':
+ break;
+ case 'default':
+ case 'plain':
+ case 'stockholmsyndrome':
+ drupal_add_css($path . '/styles/' . $style . '/colorbox_style.css');
+ drupal_add_js($path . '/styles/' . $style . '/colorbox_style.js');
+ break;
+ default:
+ drupal_add_css($style . '/colorbox.css');
+ }
+
+ if (variable_get('colorbox_load', 0)) {
+ drupal_add_js($path . '/js/colorbox_load.js');
+ }
+
+ if (variable_get('colorbox_inline', 0)) {
+ drupal_add_js($path . '/js/colorbox_inline.js');
+ }
+
+ $already_added = TRUE;
+}
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function colorbox_field_formatter_info() {
+ return array(
+ 'colorbox' => array(
+ 'label' => t('Colorbox'),
+ 'field types' => array('image'),
+ 'settings' => array(
+ 'colorbox_node_style' => '',
+ 'colorbox_node_style_first' => '',
+ 'colorbox_image_style' => '',
+ 'colorbox_gallery' => 'post',
+ 'colorbox_gallery_custom' => '',
+ 'colorbox_caption' => 'auto',
+ 'colorbox_caption_custom' => '',
+ 'colorbox_multivalue_index' => NULL,
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ */
+function colorbox_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $image_styles = image_style_options(FALSE);
+ $image_styles_hide = $image_styles;
+ $image_styles_hide['hide'] = t('Hide (do not display image)');
+ $element['colorbox_node_style'] = array(
+ '#title' => t('Content image style'),
+ '#type' => 'select',
+ '#default_value' => $settings['colorbox_node_style'],
+ '#empty_option' => t('None (original image)'),
+ '#options' => $image_styles_hide,
+ '#description' => t('Image style to use in the content.'),
+ );
+ $element['colorbox_node_style_first'] = array(
+ '#title' => t('Content image style for first image'),
+ '#type' => 'select',
+ '#default_value' => $settings['colorbox_node_style_first'],
+ '#empty_option' => t('No special style.'),
+ '#options' => $image_styles,
+ '#description' => t('Image style to use in the content for the first image.'),
+ );
+ $element['colorbox_image_style'] = array(
+ '#title' => t('Colorbox image style'),
+ '#type' => 'select',
+ '#default_value' => $settings['colorbox_image_style'],
+ '#empty_option' => t('None (original image)'),
+ '#options' => $image_styles,
+ '#description' => t('Image style to use in the Colorbox.'),
+ );
+
+ $gallery = array(
+ 'post' => t('Per post gallery'),
+ 'page' => t('Per page gallery'),
+ 'field_post' => t('Per field in post gallery'),
+ 'field_page' => t('Per field in page gallery'),
+ 'custom' => t('Custom'),
+ 'none' => t('No gallery'),
+ );
+ $element['colorbox_gallery'] = array(
+ '#title' => t('Gallery (image grouping)'),
+ '#type' => 'select',
+ '#default_value' => $settings['colorbox_gallery'],
+ '#options' => $gallery,
+ '#description' => t('How Colorbox should group the image galleries.'),
+ );
+ $element['colorbox_gallery_custom'] = array(
+ '#title' => t('Custom gallery'),
+ '#type' => 'textfield',
+ '#maxlength' => 32,
+ '#default_value' => $settings['colorbox_gallery_custom'],
+ '#description' => t('All images on a page with the same gallery value (rel attribute) will be grouped together. It must only contain lowercase letters, numbers, hyphen and underscores.'),
+ '#element_validate' => array('colorbox_gallery_custom_validate'),
+ '#required' => FALSE,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[settings_edit_form][settings][colorbox_gallery]"]' => array('value' => 'custom'),
+ ),
+ ),
+ );
+
+ $caption = array(
+ 'auto' => t('Automatic'),
+ 'title' => t('Title text'),
+ 'alt' => t('Alt text'),
+ 'node_title' => t('Content title'),
+ 'custom' => t('Custom (with tokens)'),
+ 'none' => t('None'),
+ );
+ $element['colorbox_caption'] = array(
+ '#title' => t('Caption'),
+ '#type' => 'select',
+ '#default_value' => $settings['colorbox_caption'],
+ '#options' => $caption,
+ '#description' => t('Automatic will use the first non-empty value of the title, the alt text and the content title.'),
+ );
+ $element['colorbox_caption_custom'] = array(
+ '#title' => t('Custom caption'),
+ '#type' => 'textfield',
+ '#default_value' => $settings['colorbox_caption_custom'],
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[settings_edit_form][settings][colorbox_caption]"]' => array('value' => 'custom'),
+ ),
+ ),
+ );
+ // Allow users to hide or set a custom recursion limit.
+ // The module token_tweaks sets a global recursion limit that can not be bypassed.
+ if (module_exists('token') && $recursion_limit = min(variable_get('token_tree_recursion_limit', 3), variable_get('colorbox_token_recursion_limit', 3))) {
+ $element['colorbox_token'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Replacement patterns'),
+ '#theme' => 'token_tree',
+ '#token_types' => array($instance['entity_type'], 'file'),
+ '#recursion_limit' => $recursion_limit,
+ '#dialog' => TRUE,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[settings_edit_form][settings][colorbox_caption]"]' => array('value' => 'custom'),
+ ),
+ ),
+ );
+ }
+ else {
+ $element['colorbox_token'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Replacement patterns'),
+ '#description' => '<strong class="error">' . t('For token support the <a href="@token_url">token module</a> must be installed.', array('@token_url' => 'http://drupal.org/project/token')) . '</strong>',
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[settings_edit_form][settings][colorbox_caption]"]' => array('value' => 'custom'),
+ ),
+ ),
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Validate function for colorbox_gallery_custom.
+ */
+function colorbox_gallery_custom_validate($element, &$form_state) {
+ if (!empty($element['#value']) && !preg_match('!^[a-z0-9_-]+$!', $element['#value'])) {
+ form_error($element, t('%name must only contain lowercase letters, numbers, hyphen and underscores.', array('%name' => $element['#title'])));
+ }
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function colorbox_field_formatter_settings_summary($field, $instance, $view_mode) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $summary = array();
+
+ $image_styles = image_style_options(FALSE);
+ // Unset possible 'No defined styles' option.
+ unset($image_styles['']);
+ // Styles could be lost because of enabled/disabled modules that defines
+ // their styles in code.
+ if (isset($image_styles[$settings['colorbox_node_style']])) {
+ $summary[] = t('Content image style: @style', array('@style' => $image_styles[$settings['colorbox_node_style']]));
+ }
+ elseif ($settings['colorbox_node_style'] == 'hide') {
+ $summary[] = t('Content image style: Hide');
+ }
+ else {
+ $summary[] = t('Content image style: Original image');
+ }
+
+ if (isset($image_styles[$settings['colorbox_node_style_first']])) {
+ $summary[] = t('Content image style of first image: @style', array('@style' => $image_styles[$settings['colorbox_node_style_first']]));
+ }
+
+ if (isset($image_styles[$settings['colorbox_image_style']])) {
+ $summary[] = t('Colorbox image style: @style', array('@style' => $image_styles[$settings['colorbox_image_style']]));
+ }
+ else {
+ $summary[] = t('Colorbox image style: Original image');
+ }
+
+ $gallery = array(
+ 'post' => t('Per post gallery'),
+ 'page' => t('Per page gallery'),
+ 'field_post' => t('Per field in post gallery'),
+ 'field_page' => t('Per field in page gallery'),
+ 'custom' => t('Custom'),
+ 'none' => t('No gallery'),
+ );
+ if (isset($settings['colorbox_gallery'])) {
+ $summary[] = t('Colorbox gallery type: @type', array('@type' => $gallery[$settings['colorbox_gallery']])) . ($settings['colorbox_gallery'] == 'custom' ? ' (' . $settings['colorbox_gallery_custom'] . ')' : '');
+ }
+
+ $caption = array(
+ 'auto' => t('Automatic'),
+ 'title' => t('Title text'),
+ 'alt' => t('Alt text'),
+ 'node_title' => t('Content title'),
+ 'custom' => t('Custom (with tokens)'),
+ 'none' => t('None'),
+ );
+ if (isset($settings['colorbox_caption'])) {
+ $summary[] = t('Colorbox caption: @type', array('@type' => $caption[$settings['colorbox_caption']]));
+ }
+
+ return implode('<br />', $summary);
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ */
+function colorbox_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+ $element = array();
+ $index = $display['settings']['colorbox_multivalue_index'];
+
+ foreach ($items as $delta => $item) {
+ if ($index === NULL || $index === $delta) {
+ $element[$delta] = array(
+ '#theme' => 'colorbox_image_formatter',
+ '#item' => $item,
+ '#entity_type' => $entity_type,
+ '#entity' => $entity,
+ '#node' => $entity, // Left for legacy support.
+ '#field' => $field,
+ '#display_settings' => $display['settings'],
+ '#delta' => $delta,
+ );
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_insert_styles().
+ */
+function colorbox_insert_styles() {
+ $insert_styles = array();
+ foreach (image_styles() as $key => $style) {
+ $label = isset($style['label']) ? $style['label'] : $style['name'];
+ $insert_styles['colorbox__' . $key] = array('label' => t('Colorbox @style', array('@style' => $label)));
+ }
+
+ return $insert_styles;
+}
+
+/**
+ * Implements hook_insert_content().
+ */
+function colorbox_insert_content($item, $style, $widget) {
+ list($item['module_name'], $item['style_name']) = explode('__', $style['name'], 2);
+ return theme(array('colorbox_insert_image__' . str_replace('-', '_', $item['style_name']), 'colorbox_insert_image'), array('item' => $item, 'widget' => $widget));
+}
+
+/**
+ * Machine names normally need to be unique but that does not apply to galleries.
+ *
+ * @return
+ * Always FALSE
+ */
+function colorbox_gallery_exists() {
+ return FALSE;
+}
diff --git a/sites/all/modules/colorbox/colorbox.theme.inc b/sites/all/modules/colorbox/colorbox.theme.inc
new file mode 100644
index 000000000..74ceaf886
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox.theme.inc
@@ -0,0 +1,240 @@
+<?php
+
+/**
+ * @file
+ * Colorbox theme functions.
+ */
+
+/**
+ * Returns HTML for an Colorbox image field formatter.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - item: An array of image data.
+ * - image_style: An optional image style.
+ * - path: An array containing the link 'path' and link 'options'.
+ *
+ * @ingroup themeable
+ */
+function theme_colorbox_image_formatter($variables) {
+ static $gallery_token = NULL;
+ $item = $variables['item'];
+ $entity_type = $variables['entity_type'];
+ $entity = $variables['entity'];
+ $field = $variables['field'];
+ $settings = $variables['display_settings'];
+
+ $image = array(
+ 'path' => $item['uri'],
+ 'alt' => isset($item['alt']) ? $item['alt'] : '',
+ 'title' => isset($item['title']) ? $item['title'] : '',
+ 'style_name' => $settings['colorbox_node_style'],
+ );
+
+ if ($variables['delta'] == 0 && !empty($settings['colorbox_node_style_first'])) {
+ $image['style_name'] = $settings['colorbox_node_style_first'];
+ }
+
+ if (isset($item['width']) && isset($item['height'])) {
+ $image['width'] = $item['width'];
+ $image['height'] = $item['height'];
+ }
+
+ if (isset($item['attributes'])) {
+ $image['attributes'] = $item['attributes'];
+ }
+
+ // Allow image attributes to be overridden.
+ if (isset($variables['item']['override']['attributes'])) {
+ foreach (array('width', 'height', 'alt', 'title') as $key) {
+ if (isset($variables['item']['override']['attributes'][$key])) {
+ $image[$key] = $variables['item']['override']['attributes'][$key];
+ unset($variables['item']['override']['attributes'][$key]);
+ }
+ }
+ if (isset($image['attributes'])) {
+ $image['attributes'] = $variables['item']['override']['attributes'] + $image['attributes'];
+ }
+ else {
+ $image['attributes'] = $variables['item']['override']['attributes'];
+ }
+ }
+
+ $entity_title = entity_label($entity_type, $entity);
+
+ switch ($settings['colorbox_caption']) {
+ case 'auto':
+ // If the title is empty use alt or the entity title in that order.
+ if (!empty($image['title'])) {
+ $caption = $image['title'];
+ }
+ elseif (!empty($image['alt'])) {
+ $caption = $image['alt'];
+ }
+ elseif (!empty($entity_title)) {
+ $caption = $entity_title;
+ }
+ else {
+ $caption = '';
+ }
+ break;
+ case 'title':
+ $caption = $image['title'];
+ break;
+ case 'alt':
+ $caption = $image['alt'];
+ break;
+ case 'node_title':
+ $caption = $entity_title;
+ break;
+ case 'custom':
+ $caption = token_replace($settings['colorbox_caption_custom'], array($entity_type => $entity, 'file' => (object) $item), array('clear' => TRUE));
+ break;
+ default:
+ $caption = '';
+ }
+
+ // Shorten the caption for the example styles or when caption shortening is active.
+ $colorbox_style = variable_get('colorbox_style', 'default');
+ $trim_length = variable_get('colorbox_caption_trim_length', 75);
+ if (((strpos($colorbox_style, 'colorbox/example') !== FALSE) || variable_get('colorbox_caption_trim', 0)) && (drupal_strlen($caption) > $trim_length)) {
+ $caption = drupal_substr($caption, 0, $trim_length - 5) . '...';
+ }
+
+ // Build the gallery id.
+ list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+ $entity_id = !empty($id) ? $entity_type . '-' . $id : 'entity-id';
+ switch ($settings['colorbox_gallery']) {
+ case 'post':
+ $gallery_id = 'gallery-' . $entity_id;
+ break;
+ case 'page':
+ $gallery_id = 'gallery-all';
+ break;
+ case 'field_post':
+ $gallery_id = 'gallery-' . $entity_id . '-' . $field['field_name'];
+ break;
+ case 'field_page':
+ $gallery_id = 'gallery-' . $field['field_name'];
+ break;
+ case 'custom':
+ $gallery_id = $settings['colorbox_gallery_custom'];
+ break;
+ default:
+ $gallery_id = '';
+ }
+
+ // If gallery id is not empty add unique per-request token to avoid images being added manually to galleries.
+ if (!empty($gallery_id)) {
+ // Check if gallery token has alrady been set, we need to reuse the token for the whole request.
+ if (is_null($gallery_token)) {
+ // We use a short token since randomness is not critical.
+ $gallery_token = drupal_random_key(8);
+ }
+ $gallery_id = $gallery_id . '-' . $gallery_token;
+ }
+
+ if ($style_name = $settings['colorbox_image_style']) {
+ $path = image_style_url($style_name, $image['path']);
+ }
+ else {
+ $path = file_create_url($image['path']);
+ }
+
+ return theme('colorbox_imagefield', array('image' => $image, 'path' => $path, 'title' => $caption, 'gid' => $gallery_id));
+}
+
+/**
+ * Returns HTML for an image using a specific Colorbox image style.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - image: image item as array.
+ * - path: The path of the image that should be displayed in the Colorbox.
+ * - title: The title text that will be used as a caption in the Colorbox.
+ * - gid: Gallery id for Colorbox image grouping.
+ *
+ * @ingroup themeable
+ */
+function theme_colorbox_imagefield($variables) {
+ $class = array('colorbox');
+
+ if ($variables['image']['style_name'] == 'hide') {
+ $image = '';
+ $class[] = 'js-hide';
+ }
+ elseif (!empty($variables['image']['style_name'])) {
+ $image = theme('image_style', $variables['image']);
+ }
+ else {
+ $image = theme('image', $variables['image']);
+ }
+
+ $options = drupal_parse_url($variables['path']);
+ $options += array(
+ 'html' => TRUE,
+ 'attributes' => array(
+ 'title' => $variables['title'],
+ 'class' => $class,
+ 'rel' => $variables['gid'],
+ ),
+ 'language' => array('language' => NULL),
+ );
+
+ return l($image, $options['path'], $options);
+}
+
+/**
+ * Preprocess variables for the colorbox-insert-image.tpl.php file.
+ */
+function template_preprocess_colorbox_insert_image(&$variables) {
+ $item = $variables['item'];
+ $variables['file'] = file_load($item['fid']);
+ $variables['style_name'] = $item['style_name'];
+ $variables['width'] = isset($item['width']) ? $item['width'] : NULL;
+ $variables['height'] = isset($item['height']) ? $item['height'] : NULL;
+
+ // Determine dimensions of the image after the image style transformations.
+ image_style_transform_dimensions($variables['style_name'], $variables);
+
+ $class = array();
+ if (!empty($variables['widget']['settings']['insert_class'])) {
+ $class = explode(' ', $variables['widget']['settings']['insert_class']);
+ }
+ $class[] = 'image-' . $variables['style_name'];
+
+ foreach ($class as $key => $value) {
+ $class[$key] = drupal_html_class($value);
+ }
+
+ $variables['class'] = implode(' ', $class);
+
+ $variables['uri'] = image_style_path($variables['style_name'], $variables['file']->uri);
+ $absolute = isset($variables['widget']['settings']['insert_absolute']) ? $variables['widget']['settings']['insert_absolute'] : NULL;
+ $variables['url'] = insert_create_url($variables['uri'], $absolute, variable_get('clean_url'));
+
+ // http://drupal.org/node/1923336
+ if (function_exists('image_style_path_token')) {
+ $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($variables['style_name'], $variables['file']->uri));
+ $variables['url'] .= (strpos($variables['url'], '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
+ }
+
+ if ($style_name = variable_get('colorbox_image_style', '')) {
+ $variables['path'] = image_style_url($style_name, $variables['file']->uri);
+ }
+ else {
+ $variables['path'] = file_create_url($variables['file']->uri);
+ }
+
+ $variables['gallery_id'] = '';
+ switch (variable_get('colorbox_insert_gallery', 0)) {
+ case 0:
+ case 1:
+ case 2:
+ $variables['gallery_id'] = 'gallery-all';
+ break;
+ case 3:
+ $variables['gallery_id'] = '';
+ break;
+ }
+}
diff --git a/sites/all/modules/colorbox/colorbox.variable.inc b/sites/all/modules/colorbox/colorbox.variable.inc
new file mode 100644
index 000000000..250e08cbc
--- /dev/null
+++ b/sites/all/modules/colorbox/colorbox.variable.inc
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Colorbox translatable variables.
+ */
+
+/**
+ * Implements hook_variable_info().
+ */
+function colorbox_variable_info($options) {
+ $variable['colorbox_text_start'] = array(
+ 'title' => t('Colorbox Start slideshow'),
+ 'description' => t('Text for the slideshow start button.'),
+ 'type' => 'string',
+ );
+ $variable['colorbox_text_stop'] = array(
+ 'title' => t('Colorbox Stop slideshow'),
+ 'description' => t('Text for the slideshow stop button.'),
+ 'type' => 'string',
+ );
+ $variable['colorbox_text_current'] = array(
+ 'title' => t('Colorbox current text'),
+ 'description' => t('Text for the content group / gallery count'),
+ 'type' => 'string',
+ );
+ $variable['colorbox_text_previous'] = array(
+ 'title' => t('Colorbox Previous'),
+ 'description' => t('Text for the previous button in a shared relation group.'),
+ 'type' => 'string',
+ );
+ $variable['colorbox_text_next'] = array(
+ 'title' => t('Colorbox Next'),
+ 'description' => t('Text for the next button in a shared relation group.'),
+ 'type' => 'string',
+ );
+ $variable['colorbox_text_close'] = array(
+ 'title' => t('Colorbox Close'),
+ 'description' => t('Text for the close button.'),
+ 'type' => 'string',
+ );
+
+ return $variable;
+}
diff --git a/sites/all/modules/colorbox/drush/colorbox.drush.inc b/sites/all/modules/colorbox/drush/colorbox.drush.inc
new file mode 100644
index 000000000..e45c40fe1
--- /dev/null
+++ b/sites/all/modules/colorbox/drush/colorbox.drush.inc
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ * drush integration for colorbox.
+ */
+
+/**
+ * The Colorbox plugin URI.
+ */
+define('COLORBOX_DOWNLOAD_URI', 'https://github.com/jackmoore/colorbox/archive/1.x.zip');
+define('COLORBOX_DOWNLOAD_PREFIX', 'colorbox-');
+
+/**
+ * Implementation of hook_drush_command().
+ *
+ * In this hook, you specify which commands your
+ * drush module makes available, what it does and
+ * description.
+ *
+ * Notice how this structure closely resembles how
+ * you define menu hooks.
+ *
+ * See `drush topic docs-commands` for a list of recognized keys.
+ *
+ * @return
+ * An associative array describing your command(s).
+ */
+function colorbox_drush_command() {
+ $items = array();
+
+ // the key in the $items array is the name of the command.
+ $items['colorbox-plugin'] = array(
+ 'callback' => 'drush_colorbox_plugin',
+ 'description' => dt('Download and install the Colorbox plugin.'),
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap.
+ 'arguments' => array(
+ 'path' => dt('Optional. A path where to install the Colorbox plugin. If omitted Drush will use the default location.'),
+ ),
+ 'aliases' => array('colorboxplugin'),
+ );
+
+ return $items;
+}
+
+/**
+ * Implementation of hook_drush_help().
+ *
+ * This function is called whenever a drush user calls
+ * 'drush help <name-of-your-command>'
+ *
+ * @param
+ * A string with the help section (prepend with 'drush:')
+ *
+ * @return
+ * A string with the help text for your command.
+ */
+function colorbox_drush_help($section) {
+ switch ($section) {
+ case 'drush:colorbox-plugin':
+ return dt('Download and install the Colorbox plugin from jacklmoore.com/colorbox, default location is sites/all/libraries.');
+ }
+}
+
+/**
+ * Implements drush_MODULE_post_pm_enable().
+ */
+// function drush_colorbox_post_pm_enable() {
+// $modules = func_get_args();
+// if (in_array('colorbox', $modules)) {
+// drush_colorbox_plugin();
+// }
+// }
+
+/**
+ * Command to download the Colorbox plugin.
+ */
+function drush_colorbox_plugin() {
+ $args = func_get_args();
+ if (!empty($args[0])) {
+ $path = $args[0];
+ }
+ else {
+ $path = 'sites/all/libraries';
+ }
+
+ // Create the path if it does not exist.
+ if (!is_dir($path)) {
+ drush_op('mkdir', $path);
+ drush_log(dt('Directory @path was created', array('@path' => $path)), 'notice');
+ }
+
+ // Set the directory to the download location.
+ $olddir = getcwd();
+ chdir($path);
+
+ // Download the zip archive
+ if ($filepath = drush_download_file(COLORBOX_DOWNLOAD_URI)) {
+ $filename = basename($filepath);
+ $dirname = COLORBOX_DOWNLOAD_PREFIX . basename($filepath, '.zip');
+
+ // Remove any existing Colorbox plugin directory
+ if (is_dir($dirname) || is_dir('colorbox')) {
+ drush_delete_dir($dirname, TRUE);
+ drush_delete_dir('colorbox', TRUE);
+ drush_log(dt('A existing Colorbox plugin was deleted from @path', array('@path' => $path)), 'notice');
+ }
+
+ // Decompress the zip archive
+ drush_tarball_extract($filename);
+
+ // Change the directory name to "colorbox" if needed.
+ if ($dirname != 'colorbox') {
+ drush_move_dir($dirname, 'colorbox', TRUE);
+ $dirname = 'colorbox';
+ }
+ }
+
+ if (is_dir($dirname)) {
+ drush_log(dt('Colorbox plugin has been installed in @path', array('@path' => $path)), 'success');
+ }
+ else {
+ drush_log(dt('Drush was unable to install the Colorbox plugin to @path', array('@path' => $path)), 'error');
+ }
+
+ // Set working directory back to the previous working directory.
+ chdir($olddir);
+}
diff --git a/sites/all/modules/colorbox/images/controls.png b/sites/all/modules/colorbox/images/controls.png
new file mode 100644
index 000000000..223897276
--- /dev/null
+++ b/sites/all/modules/colorbox/images/controls.png
Binary files differ
diff --git a/sites/all/modules/colorbox/images/loading_animation.gif b/sites/all/modules/colorbox/images/loading_animation.gif
new file mode 100644
index 000000000..f864d5fd3
--- /dev/null
+++ b/sites/all/modules/colorbox/images/loading_animation.gif
Binary files differ
diff --git a/sites/all/modules/colorbox/images/loading_background.png b/sites/all/modules/colorbox/images/loading_background.png
new file mode 100644
index 000000000..9de11f467
--- /dev/null
+++ b/sites/all/modules/colorbox/images/loading_background.png
Binary files differ
diff --git a/sites/all/modules/colorbox/js/colorbox.js b/sites/all/modules/colorbox/js/colorbox.js
new file mode 100644
index 000000000..cd0520bb6
--- /dev/null
+++ b/sites/all/modules/colorbox/js/colorbox.js
@@ -0,0 +1,27 @@
+(function ($) {
+
+Drupal.behaviors.initColorbox = {
+ attach: function (context, settings) {
+ if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') {
+ return;
+ }
+
+ if (settings.colorbox.mobiledetect && window.matchMedia) {
+ // Disable Colorbox for small screens.
+ var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")");
+ if (mq.matches) {
+ return;
+ }
+ }
+
+ $('.colorbox', context)
+ .once('init-colorbox')
+ .colorbox(settings.colorbox);
+
+ $(context).bind('cbox_complete', function () {
+ Drupal.attachBehaviors('#cboxLoadedContent');
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/colorbox/js/colorbox_admin_settings.js b/sites/all/modules/colorbox/js/colorbox_admin_settings.js
new file mode 100644
index 000000000..46608f81c
--- /dev/null
+++ b/sites/all/modules/colorbox/js/colorbox_admin_settings.js
@@ -0,0 +1,32 @@
+(function ($) {
+
+Drupal.behaviors.initColorboxAdminSettings = {
+ attach: function (context, settings) {
+ $('div.colorbox-custom-settings-activate input.form-radio', context).click(function () {
+ if (this.value == 1) {
+ $('div.colorbox-custom-settings', context).show();
+ }
+ else {
+ $('div.colorbox-custom-settings', context).hide();
+ }
+ });
+ $('div.colorbox-slideshow-settings-activate input.form-radio', context).click(function () {
+ if (this.value == 1) {
+ $('div.colorbox-slideshow-settings', context).show();
+ }
+ else {
+ $('div.colorbox-slideshow-settings', context).hide();
+ }
+ });
+ $('div.colorbox-title-trim-settings-activate input.form-radio', context).click(function () {
+ if (this.value == 1) {
+ $('div.colorbox-title-trim-settings', context).show();
+ }
+ else {
+ $('div.colorbox-title-trim-settings', context).hide();
+ }
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/colorbox/js/colorbox_inline.js b/sites/all/modules/colorbox/js/colorbox_inline.js
new file mode 100644
index 000000000..0b27b655c
--- /dev/null
+++ b/sites/all/modules/colorbox/js/colorbox_inline.js
@@ -0,0 +1,56 @@
+(function ($) {
+
+Drupal.behaviors.initColorboxInline = {
+ attach: function (context, settings) {
+ if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') {
+ return;
+ }
+ $.urlParam = function(name, url){
+ if (name == 'fragment') {
+ var results = new RegExp('(#[^&#]*)').exec(url);
+ }
+ else {
+ var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(url);
+ }
+ if (!results) { return ''; }
+ return results[1] || '';
+ };
+ $('.colorbox-inline', context).once('init-colorbox-inline').colorbox({
+ transition:settings.colorbox.transition,
+ speed:settings.colorbox.speed,
+ opacity:settings.colorbox.opacity,
+ slideshow:settings.colorbox.slideshow,
+ slideshowAuto:settings.colorbox.slideshowAuto,
+ slideshowSpeed:settings.colorbox.slideshowSpeed,
+ slideshowStart:settings.colorbox.slideshowStart,
+ slideshowStop:settings.colorbox.slideshowStop,
+ current:settings.colorbox.current,
+ previous:settings.colorbox.previous,
+ next:settings.colorbox.next,
+ close:settings.colorbox.close,
+ overlayClose:settings.colorbox.overlayClose,
+ maxWidth:settings.colorbox.maxWidth,
+ maxHeight:settings.colorbox.maxHeight,
+ innerWidth:function(){
+ return $.urlParam('width', $(this).attr('href'));
+ },
+ innerHeight:function(){
+ return $.urlParam('height', $(this).attr('href'));
+ },
+ title:function(){
+ return decodeURIComponent($.urlParam('title', $(this).attr('href')));
+ },
+ iframe:function(){
+ return $.urlParam('iframe', $(this).attr('href'));
+ },
+ inline:function(){
+ return $.urlParam('inline', $(this).attr('href'));
+ },
+ href:function(){
+ return $.urlParam('fragment', $(this).attr('href'));
+ }
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/colorbox/js/colorbox_load.js b/sites/all/modules/colorbox/js/colorbox_load.js
new file mode 100644
index 000000000..30e99a778
--- /dev/null
+++ b/sites/all/modules/colorbox/js/colorbox_load.js
@@ -0,0 +1,42 @@
+(function ($) {
+
+Drupal.behaviors.initColorboxLoad = {
+ attach: function (context, settings) {
+ if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') {
+ return;
+ }
+ $.urlParams = function (url) {
+ var p = {},
+ e,
+ a = /\+/g, // Regex for replacing addition symbol with a space
+ r = /([^&=]+)=?([^&]*)/g,
+ d = function (s) { return decodeURIComponent(s.replace(a, ' ')); },
+ q = url.split('?');
+ while (e = r.exec(q[1])) {
+ e[1] = d(e[1]);
+ e[2] = d(e[2]);
+ switch (e[2].toLowerCase()) {
+ case 'true':
+ case 'yes':
+ e[2] = true;
+ break;
+ case 'false':
+ case 'no':
+ e[2] = false;
+ break;
+ }
+ if (e[1] == 'width') { e[1] = 'innerWidth'; }
+ if (e[1] == 'height') { e[1] = 'innerHeight'; }
+ p[e[1]] = e[2];
+ }
+ return p;
+ };
+ $('.colorbox-load', context)
+ .once('init-colorbox-load', function () {
+ var params = $.urlParams($(this).attr('href'));
+ $(this).colorbox($.extend({}, settings.colorbox, params));
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/colorbox/styles/default/colorbox_style.css b/sites/all/modules/colorbox/styles/default/colorbox_style.css
new file mode 100644
index 000000000..2517c7f36
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/default/colorbox_style.css
@@ -0,0 +1,216 @@
+/**
+ * Colorbox Core Style:
+ * The following CSS is consistent between example themes and should not be altered.
+ */
+#colorbox, #cboxOverlay, #cboxWrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 9999;
+ overflow: hidden;
+}
+#cboxOverlay {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+}
+#cboxMiddleLeft, #cboxBottomLeft {
+ clear: left;
+}
+#cboxContent {
+ position: relative;
+}
+#cboxLoadedContent {
+ overflow: auto;
+ -webkit-overflow-scrolling: touch;
+}
+#cboxTitle {
+ margin: 0;
+}
+#cboxLoadingOverlay, #cboxLoadingGraphic {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+/**
+ * These elements are buttons, and may need to have additional
+ * styles reset to avoid unwanted base styles.
+ */
+#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow {
+ border: 0;
+ padding: 0;
+ margin: 0;
+ overflow: visible;
+ width: auto;
+ background: none;
+ cursor: pointer;
+}
+/**
+ * Avoid outlines on :active (mouseclick),
+ * but preserve outlines on :focus (tabbed navigating)
+ */
+#cboxPrevious:active, #cboxNext:active, #cboxClose:active, #cboxSlideshow:active {
+ outline: 0;
+}
+.cboxPhoto {
+ float: left;
+ margin: auto;
+ border: 0;
+ display: block;
+ max-width: none;
+}
+.cboxIframe {
+ width: 100%;
+ height: 100%;
+ display: block;
+ border: 0;
+}
+/* Reset box sizing to content-box if theme is using border-box. */
+#colorbox, #cboxContent, #cboxLoadedContent {
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+/**
+ * Colorbox module default style:
+ * The styles are ordered & tabbed in a way that represents
+ * the nesting of the generated HTML.
+ */
+#cboxOverlay {
+ background: #000;
+}
+#colorbox {
+ outline: 0;
+}
+ #cboxWrapper {
+ background: #fff;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ }
+ #cboxTopLeft {
+ width: 15px;
+ height: 15px;
+ }
+ #cboxTopCenter {
+ height: 15px;
+ }
+ #cboxTopRight {
+ width: 15px;
+ height: 15px;
+ }
+ #cboxBottomLeft {
+ width: 15px;
+ height: 10px;
+ }
+ #cboxBottomCenter {
+ height: 10px;
+ }
+ #cboxBottomRight {
+ width: 15px;
+ height: 10px;
+ }
+ #cboxMiddleLeft {
+ width: 15px;
+ }
+ #cboxMiddleRight {
+ width: 15px;
+ }
+ #cboxContent {
+ background: #fff;
+ overflow: hidden;
+ }
+ #cboxError {
+ padding: 50px;
+ border: 1px solid #ccc;
+ }
+ #cboxLoadedContent {
+ margin-bottom: 28px;
+ }
+ #cboxTitle {
+ position: absolute;
+ background: rgba(255, 255, 255, 0.7);
+ bottom: 28px;
+ left: 0;
+ color: #535353;
+ width: 100%;
+ padding: 4px 6px;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ #cboxCurrent {
+ position: absolute;
+ bottom: 4px;
+ left: 60px;
+ color: #949494;
+ }
+ .cboxSlideshow_on #cboxSlideshow {
+ position: absolute;
+ bottom: 0px;
+ right: 30px;
+ background: url(images/controls.png) no-repeat -75px -50px;
+ width: 25px;
+ height: 25px;
+ text-indent: -9999px;
+ }
+ .cboxSlideshow_on #cboxSlideshow:hover {
+ background-position: -101px -50px;
+ }
+ .cboxSlideshow_off #cboxSlideshow {
+ position: absolute;
+ bottom: 0px;
+ right: 30px;
+ background: url(images/controls.png) no-repeat -25px -50px;
+ width: 25px;
+ height: 25px;
+ text-indent: -9999px;
+ }
+ .cboxSlideshow_off #cboxSlideshow:hover {
+ background-position: -49px -50px;
+ }
+ #cboxPrevious {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ background: url(images/controls.png) no-repeat -75px 0px;
+ width: 25px;
+ height: 25px;
+ text-indent: -9999px;
+ }
+ #cboxPrevious:hover {
+ background-position: -75px -25px;
+ }
+ #cboxNext {
+ position: absolute;
+ bottom: 0;
+ left: 27px;
+ background: url(images/controls.png) no-repeat -50px 0px;
+ width: 25px;
+ height: 25px;
+ text-indent: -9999px;
+ }
+ #cboxNext:hover {
+ background-position: -50px -25px;
+ }
+ #cboxLoadingOverlay {
+ background: #fff;
+ }
+ #cboxLoadingGraphic {
+ background: url(images/loading_animation.gif) no-repeat center center;
+ }
+ #cboxClose {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ background: url(images/controls.png) no-repeat -25px 0px;
+ width: 25px;
+ height: 25px;
+ text-indent: -9999px;
+ }
+ #cboxClose:hover {
+ background-position: -25px -25px;
+ }
diff --git a/sites/all/modules/colorbox/styles/default/colorbox_style.js b/sites/all/modules/colorbox/styles/default/colorbox_style.js
new file mode 100644
index 000000000..47875ffdb
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/default/colorbox_style.js
@@ -0,0 +1,22 @@
+(function ($) {
+
+Drupal.behaviors.initColorboxDefaultStyle = {
+ attach: function (context, settings) {
+ $(context).bind('cbox_complete', function () {
+ // Only run if there is a title.
+ if ($('#cboxTitle:empty', context).length == false) {
+ $('#cboxLoadedContent img', context).bind('mouseover', function () {
+ $('#cboxTitle', context).slideDown();
+ });
+ $('#cboxOverlay', context).bind('mouseover', function () {
+ $('#cboxTitle', context).slideUp();
+ });
+ }
+ else {
+ $('#cboxTitle', context).hide();
+ }
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/colorbox/styles/default/images/controls.png b/sites/all/modules/colorbox/styles/default/images/controls.png
new file mode 100644
index 000000000..223897276
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/default/images/controls.png
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/default/images/loading_animation.gif b/sites/all/modules/colorbox/styles/default/images/loading_animation.gif
new file mode 100644
index 000000000..f864d5fd3
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/default/images/loading_animation.gif
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/default/images/loading_background.png b/sites/all/modules/colorbox/styles/default/images/loading_background.png
new file mode 100644
index 000000000..9de11f467
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/default/images/loading_background.png
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/plain/colorbox_style.css b/sites/all/modules/colorbox/styles/plain/colorbox_style.css
new file mode 100644
index 000000000..0b500daf6
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/plain/colorbox_style.css
@@ -0,0 +1,144 @@
+/**
+ * Colorbox Core Style:
+ * The following CSS is consistent between example themes and should not be altered.
+ */
+#colorbox, #cboxOverlay, #cboxWrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 9999;
+ overflow: hidden;
+}
+#cboxOverlay {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+}
+#cboxMiddleLeft, #cboxBottomLeft {
+ clear: left;
+}
+#cboxContent {
+ position: relative;
+}
+#cboxLoadedContent {
+ overflow: auto;
+ -webkit-overflow-scrolling: touch;
+}
+#cboxTitle {
+ margin: 0;
+}
+#cboxLoadingOverlay, #cboxLoadingGraphic {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+/**
+ * These elements are buttons, and may need to have additional
+ * styles reset to avoid unwanted base styles.
+ */
+#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow {
+ border: 0;
+ padding: 0;
+ margin: 0;
+ overflow: visible;
+ width: auto;
+ background: none;
+ cursor: pointer;
+}
+/**
+ * Avoid outlines on :active (mouseclick),
+ * but preserve outlines on :focus (tabbed navigating)
+ */
+#cboxPrevious:active, #cboxNext:active, #cboxClose:active, #cboxSlideshow:active {
+ outline: 0;
+}
+.cboxPhoto {
+ float: left;
+ margin: auto;
+ border: 0;
+ display: block;
+ max-width: none;
+}
+.cboxIframe {
+ width: 100%;
+ height: 100%;
+ display: block;
+ border: 0;
+}
+/* Reset box sizing to content-box if theme is using border-box. */
+#colorbox, #cboxContent, #cboxLoadedContent {
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+/**
+ * Colorbox module plain style:
+ * The styles are ordered & tabbed in a way that represents
+ * the nesting of the generated HTML.
+ */
+#cboxOverlay {
+ background: #000;
+}
+#colorbox {
+ outline: 0;
+}
+ #cboxWrapper {}
+ #cboxContent {
+ overflow: hidden;
+ }
+ #cboxContent, .cboxPhoto {
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ }
+ #cboxError {
+ padding: 50px;
+ border: 1px solid #ccc;
+ }
+ #cboxTitle {
+ position: absolute;
+ background: rgba(255, 255, 255, 0.7);
+ bottom: 0;
+ left: 0;
+ color: #535353;
+ width: 100%;
+ padding: 4px 6px;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+ -moz-border-radius-bottomright: 5px;
+ -moz-border-radius-bottomleft: 5px;
+ border-bottom-right-radius: 5px;
+ border-bottom-left-radius: 5px;
+ }
+
+ #cboxLoadingOverlay {
+ background: #fff;
+ }
+ #cboxLoadingGraphic {
+ background: url(images/loading_animation.gif) no-repeat center center;
+ }
+ #cboxClose.cbox-close-plain {
+ position: absolute;
+ font-size: 20px;
+ line-height: 18px;
+ text-align: center;
+ color: rgba(255, 255, 255, 0.7);
+ background: rgba(0, 0, 0, 0.5);
+ top: 4px;
+ right: 4px;
+ width: 20px;
+ height: 20px;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+ }
+ .cbox-close-plain:hover {
+ color: rgba(255, 255, 255, 0.9);
+ background: rgba(0, 0, 0, 0.8);
+ }
diff --git a/sites/all/modules/colorbox/styles/plain/colorbox_style.js b/sites/all/modules/colorbox/styles/plain/colorbox_style.js
new file mode 100644
index 000000000..19d8e66e0
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/plain/colorbox_style.js
@@ -0,0 +1,33 @@
+(function ($) {
+
+Drupal.behaviors.initColorboxPlainStyle = {
+ attach: function (context, settings) {
+ $(context).bind('cbox_complete', function () {
+ // Make all the controls invisible.
+ $('#cboxCurrent, #cboxSlideshow, #cboxPrevious, #cboxNext', context).addClass('element-invisible');
+ // Replace "Close" with "×" and show.
+ $('#cboxClose', context).html('\327').addClass('cbox-close-plain');
+ // Hide empty title.
+ if ($('#cboxTitle:empty', context).length == true) {
+ $('#cboxTitle', context).hide();
+ }
+ $('#cboxLoadedContent', context).bind('mouseover', function () {
+ $('#cboxClose', context).animate({opacity: 1}, {queue: false, duration: "fast"});
+ if ($('#cboxTitle:empty', context).length == false) {
+ $('#cboxTitle', context).slideDown();
+ }
+ });
+ $('#cboxOverlay', context).bind('mouseover', function () {
+ $('#cboxClose', context).animate({opacity: 0}, {queue: false, duration: "fast"});
+ if ($('#cboxTitle:empty', context).length == false) {
+ $('#cboxTitle', context).slideUp();
+ }
+ });
+ });
+ $(context).bind('cbox_closed', function () {
+ $('#cboxClose', context).removeClass('cbox-close-plain');
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/colorbox/styles/plain/images/controls.png b/sites/all/modules/colorbox/styles/plain/images/controls.png
new file mode 100644
index 000000000..223897276
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/plain/images/controls.png
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/plain/images/loading_animation.gif b/sites/all/modules/colorbox/styles/plain/images/loading_animation.gif
new file mode 100644
index 000000000..f864d5fd3
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/plain/images/loading_animation.gif
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/plain/images/loading_background.png b/sites/all/modules/colorbox/styles/plain/images/loading_background.png
new file mode 100644
index 000000000..9de11f467
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/plain/images/loading_background.png
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_stockholmsyndrome_screen.png b/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_stockholmsyndrome_screen.png
new file mode 100644
index 000000000..3ff1269b3
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_stockholmsyndrome_screen.png
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_style.css b/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_style.css
new file mode 100644
index 000000000..6cdfa33c9
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_style.css
@@ -0,0 +1,219 @@
+/**
+ * Colorbox Core Style:
+ * The following CSS is consistent between example themes and should not be altered.
+ */
+#colorbox, #cboxOverlay, #cboxWrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 9999;
+ overflow: hidden;
+}
+#cboxOverlay {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+}
+#cboxMiddleLeft, #cboxBottomLeft {
+ clear: left;
+}
+#cboxContent {
+ position: relative;
+}
+#cboxLoadedContent {
+ overflow: auto;
+ -webkit-overflow-scrolling: touch;
+}
+#cboxTitle {
+ margin: 0;
+}
+#cboxLoadingOverlay, #cboxLoadingGraphic {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+/**
+ * These elements are buttons, and may need to have additional
+ * styles reset to avoid unwanted base styles.
+ */
+#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow {
+ border: 0;
+ padding: 0;
+ margin: 0;
+ overflow: visible;
+ width: auto;
+ background: none;
+ cursor: pointer;
+}
+/**
+ * Avoid outlines on :active (mouseclick),
+ * but preserve outlines on :focus (tabbed navigating)
+ */
+#cboxPrevious:active, #cboxNext:active, #cboxClose:active, #cboxSlideshow:active {
+ outline: 0;
+}
+.cboxPhoto {
+ float: left;
+ margin: auto;
+ border: 0;
+ display: block;
+ max-width: none;
+}
+.cboxIframe {
+ width: 100%;
+ height: 100%;
+ display: block;
+ border: 0;
+}
+/* Reset box sizing to content-box if theme is using border-box. */
+#colorbox, #cboxContent, #cboxLoadedContent {
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+/**
+ * Colorbox module Stockholm syndrome style:
+ * The styles are ordered & tabbed in a way that represents
+ * the nesting of the generated HTML.
+ */
+#cboxOverlay {
+ background: #000;
+}
+
+#colorbox {
+ background: #fff url(images/bg_tab.png) center bottom repeat-x;
+ -moz-box-shadow: 3px 3px 16px #333;
+ -webkit-box-shadow: 3px 3px 16px #333;
+ box-shadow: 3px 3px 16px #333;
+ -moz-border-radius-bottomleft: 9px;
+ -moz-border-radius-bottomright: 9px;
+ -webkit-border-bottom-left-radius: 9px;
+ -webkit-border-bottom-right-radius: 9px;
+ border-bottom-left-radius: 9px;
+ border-bottom-right-radius: 9px;
+ outline: 0;
+}
+#colorbox, #colorbox div {
+ overflow: visible; /* Required by the close button. */
+}
+ #cboxWrapper {
+ -moz-border-radius-bottomleft: 9px;
+ -moz-border-radius-bottomright: 9px;
+ -webkit-border-bottom-left-radius: 9px;
+ -webkit-border-bottom-right-radius: 9px;
+ border-bottom-left-radius: 9px;
+ border-bottom-right-radius: 9px;
+ }
+ #cboxTopLeft {
+ width: 0;
+ height: 0;
+ }
+ #cboxTopCenter {
+ height: 0;
+ }
+ #cboxTopRight {
+ width: 0;
+ height: 0;
+ }
+ #cboxBottomLeft {
+ width: 15px;
+ height: 10px;
+ }
+ #cboxBottomCenter {
+ height: 10px;
+ }
+ #cboxBottomRight {
+ width: 15px;
+ height: 10px;
+ }
+ #cboxMiddleLeft {
+ width: 0;
+ }
+ #cboxMiddleRight {
+ width: 0;
+ }
+ #cboxContent {
+ background: #fff;
+ overflow: hidden;
+ margin-bottom: 28px;
+ }
+ #cboxError {
+ padding: 50px;
+ border: 1px solid #ccc;
+ }
+ #cboxLoadedContent {
+ }
+ #cboxTitle {
+ left: 0;
+ height: 38px;
+ color: #313131;
+ padding: 0 140px 0 15px;
+ display: table-cell !important;
+ vertical-align: middle;
+ float: none !important;
+ }
+ #cboxCurrent {
+ position: absolute;
+ bottom: -26px;
+ right: 80px;
+ color: #313131;
+ border-left: 1px solid #313131;
+ padding: 0 0 0 15px;
+ }
+ /* Slideshow not implemented. */
+ .cboxSlideshow_on #cboxSlideshow {
+ display: none;
+ }
+ .cboxSlideshow_on #cboxSlideshow:hover {
+ }
+ .cboxSlideshow_off #cboxSlideshow {
+ display: none;
+ }
+ .cboxSlideshow_off #cboxSlideshow:hover {
+ }
+ #cboxPrevious {
+ position: absolute;
+ bottom: -26px;
+ right: 45px;
+ background: url(images/controls.png) no-repeat 0 -48px;
+ width: 21px;
+ height: 15px;
+ text-indent: -9999px;
+ }
+ #cboxPrevious:hover {
+ background-position: 0 -111px;
+ }
+ #cboxNext {
+ position: absolute;
+ bottom: -26px;
+ right: 15px;
+ background: url(images/controls.png) no-repeat 0 -29px;
+ width: 21px;
+ height: 15px;
+ text-indent: -9999px;
+ }
+ #cboxNext:hover {
+ background-position: 0 -92px;
+ }
+ #cboxLoadingOverlay {
+ background: #e6e6e6;
+ }
+ #cboxLoadingGraphic {
+ background: url(images/loading_animation.gif) no-repeat center center;
+ }
+ #cboxClose {
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ background: url(images/controls.png) no-repeat 0px 0px;
+ width: 25px;
+ height: 25px;
+ text-indent: -9999px;
+ opacity: 0;
+ }
+ #cboxClose:hover {
+ background-position: 0 -63px;
+ }
diff --git a/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_style.js b/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_style.js
new file mode 100644
index 000000000..db3ab3efe
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/stockholmsyndrome/colorbox_style.js
@@ -0,0 +1,20 @@
+(function ($) {
+
+Drupal.behaviors.initColorboxStockholmsyndromeStyle = {
+ attach: function (context, settings) {
+ $(context).bind('cbox_open', function () {
+ // Hide close button initially.
+ $('#cboxClose', context).css('opacity', 0);
+ });
+ $(context).bind('cbox_load', function () {
+ // Hide close button. (It doesn't handle the load animation well.)
+ $('#cboxClose', context).css('opacity', 0);
+ });
+ $(context).bind('cbox_complete', function () {
+ // Show close button with a delay.
+ $('#cboxClose', context).fadeTo('fast', 0, function () {$(this).css('opacity', 1)});
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/colorbox/styles/stockholmsyndrome/images/bg_tab.png b/sites/all/modules/colorbox/styles/stockholmsyndrome/images/bg_tab.png
new file mode 100644
index 000000000..03064d92d
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/stockholmsyndrome/images/bg_tab.png
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/stockholmsyndrome/images/controls.png b/sites/all/modules/colorbox/styles/stockholmsyndrome/images/controls.png
new file mode 100644
index 000000000..6745faa2a
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/stockholmsyndrome/images/controls.png
Binary files differ
diff --git a/sites/all/modules/colorbox/styles/stockholmsyndrome/images/loading_animation.gif b/sites/all/modules/colorbox/styles/stockholmsyndrome/images/loading_animation.gif
new file mode 100644
index 000000000..f864d5fd3
--- /dev/null
+++ b/sites/all/modules/colorbox/styles/stockholmsyndrome/images/loading_animation.gif
Binary files differ
diff --git a/sites/all/modules/colorbox/views/colorbox.views.inc b/sites/all/modules/colorbox/views/colorbox.views.inc
new file mode 100644
index 000000000..40f90a517
--- /dev/null
+++ b/sites/all/modules/colorbox/views/colorbox.views.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * colorbox.views.inc
+ */
+
+/**
+ * Implementation of hook_views_data()
+ */
+function colorbox_views_data() {
+
+ $data['colorbox']['table']['group'] = t('Colorbox');
+
+ $data['colorbox']['table']['join'] = array(
+ '#global' => array(),
+ );
+
+ $data['colorbox']['colorbox'] = array(
+ 'title' => t('Colorbox trigger'),
+ 'help' => t('Provide custom text or link.'),
+ 'field' => array(
+ 'handler' => 'colorbox_handler_field_colorbox',
+ ),
+ );
+
+ return $data;
+}
diff --git a/sites/all/modules/colorbox/views/colorbox_handler_field_colorbox.inc b/sites/all/modules/colorbox/views/colorbox_handler_field_colorbox.inc
new file mode 100644
index 000000000..205d09950
--- /dev/null
+++ b/sites/all/modules/colorbox/views/colorbox_handler_field_colorbox.inc
@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * @file
+ * Views handlers for Colorbox module.
+ */
+
+/**
+ * A handler to provide a field that is completely custom by the administrator.
+ *
+ * @ingroup views_field_handlers
+ */
+class colorbox_handler_field_colorbox extends views_handler_field {
+ function query() {
+ // Do nothing, as this handler does not need to do anything to the query itself.
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['trigger_field'] = array('default' => '');
+ $options['popup'] = array('default' => '');
+ $options['caption'] = array('default' => '');
+ $options['gid'] = array('default' => TRUE);
+ $options['custom_gid'] = array('default' => '');
+ $options['width'] = array('default' => '600px');
+ $options['height'] = array('default' => '400px');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ // Get a list of the available fields and arguments for trigger field and token replacement.
+ $options = array();
+ $fields = array('trigger_field' => t('- None -'));
+ foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
+ $options[t('Fields')]["[$field]"] = $handler->ui_name();
+ // We only use fields up to (and including) this one.
+ if ($field == $this->options['id']) {
+ break;
+ }
+
+ $fields[$field] = $handler->definition['title'];
+ }
+ $count = 0; // This lets us prepare the key as we want it printed.
+ foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
+ $options[t('Arguments')]['%' . ++$count] = t('@argument title', array('@argument' => $handler->ui_name()));
+ $options[t('Arguments')]['!' . $count] = t('@argument input', array('@argument' => $handler->ui_name()));
+ }
+
+ $this->document_self_tokens($options[t('Fields')]);
+
+ // Default text.
+ $patterns = t('<p>You must add some additional fields to this display before using this field. These fields may be marked as <em>Exclude from display</em> if you prefer. Note that due to rendering order, you cannot use fields that come after this field; if you need a field not listed here, rearrange your fields.</p>');
+ // We have some options, so make a list.
+ if (!empty($options)) {
+ $patterns = t('<p>The following tokens are available for this field. Note that due to rendering order, you cannot use fields that come after this field; if you need a field not listed here, rearrange your fields.
+If you would like to have the characters %5B and %5D please use the html entity codes \'%5B\' or \'%5D\' or they will get replaced with empty space.</p>');
+ foreach (array_keys($options) as $type) {
+ if (!empty($options[$type])) {
+ $items = array();
+ foreach ($options[$type] as $key => $value) {
+ $items[] = $key . ' == ' . $value;
+ }
+ $patterns .= theme('item_list',
+ array(
+ 'items' => $items,
+ 'type' => $type
+ ));
+ }
+ }
+ }
+
+ $form['trigger_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Trigger field'),
+ '#description' => t('Select the field that should be turned into the trigger for the Colorbox. Only fields that appear before this one in the field list may be used.'),
+ '#options' => $fields,
+ '#default_value' => $this->options['trigger_field'],
+ '#weight' => -12,
+ );
+
+ $form['popup'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Popup'),
+ '#description' => t('The Colorbox popup content. You may include HTML. You may enter data from this view as per the "Replacement patterns" below.'),
+ '#default_value' => $this->options['popup'],
+ '#weight' => -11,
+ );
+
+ $form['caption'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Caption'),
+ '#description' => t('The Colorbox Caption. You may include HTML. You may enter data from this view as per the "Replacement patterns" below.'),
+ '#default_value' => $this->options['caption'],
+ '#weight' => -10,
+ );
+
+ $form['gid'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Automatic generated Colorbox gallery'),
+ '#description' => t('Enable Colorbox gallery using a generated gallery id for this view.'),
+ '#default_value' => $this->options['gid'],
+ '#weight' => -9,
+ );
+
+ $form['custom_gid'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Custom Colorbox gallery'),
+ '#description' => t('Enable Colorbox gallery with a given string as gallery. Overrides the automatically generated gallery id above. You may enter data from this view as per the "Replacement patterns" below.'),
+ '#default_value' => $this->options['custom_gid'],
+ '#weight' => -8,
+ );
+
+ $form['width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Width'),
+ '#description' => t('Specify the width of the Colorbox popup window. Because the content is dynamic, we cannot detect this value automatically. Example: "100%", 500, "500px".'),
+ '#default_value' => $this->options['width'],
+ '#weight' => -6,
+ );
+
+ $form['height'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Height'),
+ '#description' => t('Specify the height of the Colorbox popup window. Because the content is dynamic, we cannot detect this value automatically. Example: "100%", 500, "500px".'),
+ '#default_value' => $this->options['height'],
+ '#weight' => -7,
+ );
+
+ $form['patterns'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Replacement patterns'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#value' => $patterns,
+ );
+ }
+
+ /**
+ * Render the trigger field and its linked popup information.
+ */
+ function render($values) {
+ // Load the necessary js file for Colorbox activation.
+ if (_colorbox_active() && !variable_get('colorbox_inline', 0)) {
+ drupal_add_js(drupal_get_path('module', 'colorbox') . '/js/colorbox_inline.js');
+ }
+
+ // We need to have multiple unique IDs, one for each record.
+ static $i = 0;
+ $i = mt_rand();
+
+ // Return nothing if no trigger filed is selected.
+ if (empty($this->options['trigger_field'])) {
+ return;
+ }
+
+ // Get the token information and generate the value for the popup and the
+ // caption.
+ $tokens = $this->get_render_tokens($this->options['alter']);
+ $popup = filter_xss_admin($this->options['popup']);
+ $caption = filter_xss_admin($this->options['caption']);
+ $gallery = filter_xss_admin($this->options['custom_gid']);
+ $popup = strtr($popup, $tokens);
+ $caption = strtr($caption, $tokens);
+ $gallery = drupal_html_class(strtr($gallery, $tokens));
+
+ // Return nothing if popup is empty.
+ if (empty($popup)) {
+ return;
+ }
+
+ $width = $this->options['width'] ? $this->options['width'] : '';
+ $height = $this->options['height'] ? $this->options['height'] : '';
+ $gallery_id = !empty($gallery) ? $gallery : ($this->options['gid'] ? 'gallery-' . $this->view->name : '');
+ $link_text = $tokens["[{$this->options['trigger_field']}]"];
+ $link_options = array(
+ 'html' => TRUE,
+ 'fragment' => 'colorbox-inline-' . $i,
+ 'query' => array(
+ 'width' => $width,
+ 'height' => $height,
+ 'title' => $caption,
+ 'inline' => 'true'
+ ),
+ 'attributes' => array(
+ 'class' => array('colorbox-inline'),
+ 'rel' => $gallery_id
+ )
+ );
+ // Remove any parameters that aren't set.
+ $link_options['query'] = array_filter($link_options['query']);
+
+ // If the nid is present make the link degrade to the node page if
+ // JavaScript is off.
+ $link_target = isset($values->nid) ? 'node/' . $values->nid : '';
+ $link_tag = l($link_text, $link_target, $link_options);
+
+ // The outside div is there to hide all of the divs because if the specific Colorbox
+ // div is hidden it won't show up as a Colorbox.
+ return $link_tag . '<div style="display: none;"><div id="colorbox-inline-' . $i . '">' . $popup . '</div></div>';
+ }
+}
diff --git a/sites/all/modules/ctools/API.txt b/sites/all/modules/ctools/API.txt
new file mode 100644
index 000000000..b698b7986
--- /dev/null
+++ b/sites/all/modules/ctools/API.txt
@@ -0,0 +1,54 @@
+Current API Version: 2.0.8
+
+Please note that the API version is an internal number and does not match release numbers. It is entirely possible that releases will not increase the API version number, and increasing this number too often would burden contrib module maintainers who need to keep up with API changes.
+
+This file contains a log of changes to the API.
+API Version 2.0.9
+Changed import permissions to use the new 'use ctools import' permission.
+
+API Version 2.0.8
+ Introduce ctools_class_add().
+ Introduce ctools_class_remove().
+
+API Version 2.0.7
+ All ctools object cache database functions can now accept session_id as an optional
+ argument to facilitate using non-session id keys.
+
+API Version 2.0.6
+ Introduce a hook to alter the implementors of a certain api via hook_[ctools_api_hook]_alter.
+
+API Version 2.0.5
+ Introduce ctools_fields_get_fields_by_type().
+ Add language.inc
+ Introduce hook_ctools_content_subtype_alter($subtype, $plugin);
+
+API Version 2.0.4
+ Introduce ctools_form_include_file()
+
+API Version 2.0.3
+ Introduce ctools_field_invoke_field() and ctools_field_invoke_field_default().
+
+API Version 2.0.2
+ Introduce ctools_export_crud_load_multiple() and 'load multiple callback' to
+ export schema.
+
+API Version 2.0.1
+ Introduce ctools_export_crud_enable(), ctools_export_crud_disable() and
+ ctools_export_crud_set_status() and requisite changes.
+ Introduce 'object factory' to export schema, allowing modules to control
+ how the exportable objects are instantiated.
+ Introduce 'hook_ctools_math_expression_functions_alter'.
+
+API Version 2.0
+ Remove the deprecated callback-based behavior of the 'defaults' property on
+ plugin types; array addition is now the only option. If you need more
+ complex logic, do it with the 'process' callback.
+ Introduce a global plugin type registration hook and remove the per-plugin
+ type magic callbacks.
+ Introduce $owner . '_' . $api . '_hook_name' allowing modules to use their own
+ API hook in place of 'hook_ctools_plugin_api'.
+ Introduce ctools_plugin_api_get_hook() to get the hook name above.
+ Introduce 'cache defaults' and 'default cache bin' keys to export.inc
+
+Versions prior to 2.0 have been removed from this document. See the D6 version
+for that information.
diff --git a/sites/all/modules/ctools/CHANGELOG.txt b/sites/all/modules/ctools/CHANGELOG.txt
new file mode 100644
index 000000000..c5bd5e6dc
--- /dev/null
+++ b/sites/all/modules/ctools/CHANGELOG.txt
@@ -0,0 +1,82 @@
+Current API VERSION: 2.0. See API.txt for more information.
+
+ctools 7.x-1.x-dev
+==================
+#1008120: "New custom content" shows empty form if custom content panes module is not enabled.
+#999302 by troky: Fix jump menu. Apparently this wasn't actually committed the last time it was committed.
+#1065976 by tekante and David_Rothstein: Reset plugin static cache during module enable to prevent stale data from harming export ui.
+#1016510 by EclipseGC: Make the taxonomy system page functional.
+
+ctools 7.x-1.x-alpha2 (05-Jan-2011)
+===================================
+
+#911396 by alex_b: Prevent notices in export UI.
+#919768 by mikey_p: Allow url options to be sent to ctools_ajax_command_url().
+#358953 by cedarm: Allow term context to return lowercase, spaces to dashes versions of terms.
+#931434 by EclipseGc: Argument plugin for node revision ID.
+#910656: CTools AJAX sample wizard demo "domesticated" checkbox value not stored.
+#922442 by EugenMayer, neclimdul and voxpelli: Make sure ctools_include can handle '' or NULL directory.
+#919956 by traviss359: Correct example in wizard advanced help.
+#942968: Fix taxonomy term access rule with tag term vocabs.
+#840344: node add argument had crufty code causing notices.
+#944462 by longhairedgit: Invalid character in regex causes rare notice.
+#938778 by dereine: Fix profile content type for D7 updates.
+Add detach event to modal close so that wysiwyg can detach the editor.
+Variant titles showing up as blank if more than one variant on a page.
+#940016: token support was not yet updated for D7.
+#940446: Skip validation on back and cancel buttons in all wizards.
+#954492: Redirect not always working in wizard.inc
+#955348: Lack of redirect on "Update" button in Page Manager causing data loss sometimes.
+#941778: Update and save button should not appear in the "Add variant" path.
+#955070 by EclipseGc: Update ctools internal page tokens to work properly on content all content.
+#956890 by EclipseGc: Update views_content to not use views dependency since that is gone.
+#954728 by EclipseGc: Update node template page function name to not collide with new hook_node_view().
+#946534 by EclipseGc: Add support for field content on all entitities.
+#952586 by EclipseGc: Fix node_author content type.
+#959206: If a context is not set when rendering content, attempt to guess the context (fixes Views panes where "From context" was added but pane was never edited.)
+#961654 by benshell: drupal_alter() only supports 4 arguments.
+#911362 by alex_b: Facilitate plugin cache resets for tests.
+#945360 by naxoc: node_tag_new() not updated to D7.
+#953804 by EclipseGc: Fix node comment rendering.
+#953542 by EclipseGc: Fix node rendering.
+#953776 by EclipseGc: Fix node link rendering.
+#954772 by EclipseGc: Fix node build mode selection in node content type.
+#954762 by EclipseGc: Fix comment forbidden theme call.
+#954894 by EclipseGc: Fix breadcrumb content type.
+#955180 by EclipseGc: Fix page primary navigation type.
+#957190 by EclipseGc: Fix page secondary navigation type.
+#957194 by EclipseGc: Remove mission content type, since D7 no longer has a site mission.
+#957348 by EclipseGc: Fix search form URL path.
+#952586 by andypost: Use format_username for displaying unlinked usernames.
+#963800 by benshell: Fix query to fetch custom block title.
+#983496 by Amitaibu: Fix term argument to use proper load function.
+#989484 by Amitaibu: Fix notice in views plugin.
+#982496: Fix token context.
+#995026: Fix export UI during enable/disable which would throw notices and not properly set/unset menu items.
+#998870 by Amitaibu: Fix notice when content has no icon by using function already designed for that.
+#983576 by Amitaibu: Node view fallback task showed white screen.
+#1004644 by pillarsdotnet: Update a missed theme() call to D7.
+#1006162 by aspilicious: .info file cleanup.
+#998312 by dereine: Support the expanded/hidden options that Views did for dependent.js
+#955030: Remove no longer supported footer message content type.
+Fix broken query in term context config.
+#992022 by pcambra: Fix node autocomplete.
+#946302 by BerdArt and arywyr: Fix PHP 5.3 reference error.
+#980528 by das-peter: Notice fix with entity settings.
+#999302 by troky: ctools_jump_menu() needed updating to new form parameters.
+#964174: stylizer plugin theme delegation was in the wrong place, causing errors.
+#991658 by burlap: Fully load the "user" context for the logged in user because not all fields are in $user.
+#1014866 by das-peter: Smarter title panes, notice fix on access plugin descriptions.
+#1015662 by troky: plugin .info files were not using correct filepaths.
+#941780 by EclipseGc: Restore the "No blocks" functionality.
+#951048 by EclipseGc: Tighter entity integration so that new entities are automatic contexts and relationships.
+#941800 by me and aspilicious: Use Drupal 7 #machine_name automation on page manager pages and all export_ui defaults.
+Disabled exportables and pages not properly greyed out.
+#969208 by me and benshell: Get user_view and user profile working.
+#941796: Recategorize blocks
+
+ctools 7.x-1.x-alpha1
+=====================
+
+Changelog reset for 7.x
+Basic conversion done during sprint.
diff --git a/sites/all/modules/ctools/LICENSE.txt b/sites/all/modules/ctools/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/ctools/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/ctools/UPGRADE.txt b/sites/all/modules/ctools/UPGRADE.txt
new file mode 100644
index 000000000..844ecce42
--- /dev/null
+++ b/sites/all/modules/ctools/UPGRADE.txt
@@ -0,0 +1,63 @@
+Upgrading from ctools-6.x-1.x to ctools-7.x-2.x:
+
+ - Remove ctools_ajax_associate_url_to_element as it shouldn't be necessary
+ with the new AJAX api's in Drupal core.
+
+ - All calls to the ctools_ajax_command_prepend() should be replace with
+ the core function ajax_command_prepend();
+ This is also the case for append, insert, after, before, replace, html,
+ and remove commands.
+ Each of these commands have been incorporated into the
+ Drupal.ajax.prototype.commands.insert
+ function with a corresponding parameter specifying which method to use.
+
+ - All calls to ctools_ajax_render() should be replaced with calls to core
+ ajax_render(). Note that ctools_ajax_render() printed the json object and
+ exited, ajax_render() gives you this responsibility.
+
+ ctools_ajax_render()
+
+ becomes
+
+ print ajax_render();
+ exit;
+
+ - All calls to ctools_static*() should be replaced with corresponding calls
+ to drupal_static*().
+
+ - All calls to ctools_css_add_css should be replaced with calls to
+ drupal_add_css(). Note that the arguments to drupal_add_css() have changed.
+
+ - All wizard form builder functions must now return a form array().
+
+ - ctools_build_form is very close to being removed. In anticipation of this,
+ all $form_state['wrapper callback']s must now be
+ $form_state['wrapper_callback']. In addition to this $form_state['args']
+ must now be $form_state['build_info']['args'].
+
+ NOTE: Previously checking to see if the return from ctools_build_form()
+ is empty would be enough to see if the form was submitted. This is no
+ longer true. Please check for $form_state['executed']. If using a wizard
+ check for $form_state['complete'].
+
+ - Plugin types now must be explicitly registered via a registration hook,
+ hook_ctools_plugin_type(); info once provided in magically-named functions
+ (e.g., ctools_ctools_plugin_content_types() was the old function to
+ provide plugin type info for ctools' content_type plugins) now must be
+ provided in that global hook. See http://drupal.org/node/910538 for more
+ details.
+
+ - Plugins that use 'theme arguments' now use 'theme variables' instead.
+
+ - Context, argument and relationship plugins now use 'add form' and/or
+ 'edit form' rather than 'settings form'. These plugins now support
+ form wizards just like content plugins. These forms now all take
+ $form, &$form_state as arguments, and the configuration for the plugin
+ can be found in $form_state['conf'].
+
+ For all these forms, the submit handler MUST put appropriate data in
+ $form_state['conf']. Data will no longer be stored automatically.
+
+ For all of these forms, the separate settings #trees in the form are now
+ gone, so form ids may be adjusted. Also, these are now all real forms
+ using CTools form wizard instead of fake subforms as previously. \ No newline at end of file
diff --git a/sites/all/modules/ctools/bulk_export/bulk_export.css b/sites/all/modules/ctools/bulk_export/bulk_export.css
new file mode 100644
index 000000000..45a172d46
--- /dev/null
+++ b/sites/all/modules/ctools/bulk_export/bulk_export.css
@@ -0,0 +1,18 @@
+.export-container {
+ width: 48%;
+ float: left;
+ padding: 5px 1% 0;
+}
+.export-container table {
+ width: 100%;
+}
+.export-container table input,
+.export-container table th,
+.export-container table td {
+ padding: 0 0 .2em .5em;
+ margin: 0;
+ vertical-align: middle;
+}
+.export-container .select-all {
+ width: 1.5em;
+}
diff --git a/sites/all/modules/ctools/bulk_export/bulk_export.info b/sites/all/modules/ctools/bulk_export/bulk_export.info
new file mode 100644
index 000000000..dd9f3e913
--- /dev/null
+++ b/sites/all/modules/ctools/bulk_export/bulk_export.info
@@ -0,0 +1,14 @@
+name = Bulk Export
+description = Performs bulk exporting of data objects known about by Chaos tools.
+core = 7.x
+dependencies[] = ctools
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/bulk_export/bulk_export.js b/sites/all/modules/ctools/bulk_export/bulk_export.js
new file mode 100644
index 000000000..a4fb3f2ec
--- /dev/null
+++ b/sites/all/modules/ctools/bulk_export/bulk_export.js
@@ -0,0 +1,29 @@
+
+/**
+ * @file
+ * CTools Bulk Export javascript functions.
+ */
+
+(function ($) {
+
+Drupal.behaviors.CToolsBulkExport = {
+ attach: function (context) {
+
+ $('#bulk-export-export-form .vertical-tabs-pane', context).drupalSetSummary(function (context) {
+
+ // Check if any individual checkbox is checked.
+ if ($('.bulk-selection input:checked', context).length > 0) {
+ return Drupal.t('Exportables selected');
+ }
+
+ return '';
+ });
+
+ // Special bind click on the select-all checkbox.
+ $('.select-all').bind('click', function(context) {
+ $(this, '.vertical-tabs-pane').drupalSetSummary(context);
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/ctools/bulk_export/bulk_export.module b/sites/all/modules/ctools/bulk_export/bulk_export.module
new file mode 100644
index 000000000..afb15b9e5
--- /dev/null
+++ b/sites/all/modules/ctools/bulk_export/bulk_export.module
@@ -0,0 +1,279 @@
+<?php
+
+/**
+ * @file
+ * Perform bulk exports.
+ */
+
+/**
+ * Implements hook_permission().
+ */
+function bulk_export_permission() {
+ return array(
+ 'use bulk exporter' => array(
+ 'title' => t('Access Bulk Exporter'),
+ 'description' => t('Export various system objects into code.'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function bulk_export_menu() {
+ $items['admin/structure/bulk-export'] = array(
+ 'title' => 'Bulk Exporter',
+ 'description' => 'Bulk-export multiple CTools-handled data objects to code.',
+ 'access arguments' => array('use bulk exporter'),
+ 'page callback' => 'bulk_export_export',
+ );
+ $items['admin/structure/bulk-export/results'] = array(
+ 'access arguments' => array('use bulk exporter'),
+ 'page callback' => 'bulk_export_export',
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+}
+
+/**
+ * FAPI gateway to the bulk exporter.
+ *
+ * @param $cli
+ * Whether this function is called from command line.
+ * @param $options
+ * A collection of options, only passed in by drush_ctools_export().
+ */
+function bulk_export_export($cli = FALSE, $options = array()) {
+ ctools_include('export');
+ $form = array();
+ $schemas = ctools_export_get_schemas(TRUE);
+ $exportables = $export_tables = array();
+
+ foreach ($schemas as $table => $schema) {
+ if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) {
+ $exportables[$table] = $schema['export']['list callback']();
+ }
+ else {
+ $exportables[$table] = ctools_export_default_list($table, $schema);
+ }
+ natcasesort($exportables[$table]);
+ $export_tables[$table] = $schema['module'];
+ }
+ if ($exportables) {
+ $form_state = array(
+ 're_render' => FALSE,
+ 'no_redirect' => TRUE,
+ 'exportables' => $exportables,
+ 'export_tables' => $export_tables,
+ 'name' => '',
+ 'code' => '',
+ 'module' => '',
+ );
+
+ // If called from drush_ctools_export, get the module name and
+ // select all exportables and call the submit function directly.
+ if ($cli) {
+ $module_name = $options['name'];
+ $form_state['values']['name'] = $module_name;
+ if (isset($options['selections'])) {
+ $exportables = $options['selections'];
+ }
+ $form_state['values']['tables'] = array();
+ foreach ($exportables as $table => $names) {
+ if (!empty($names)) {
+ $form_state['values']['tables'][] = $table;
+ $form_state['values'][$table] = array();
+ foreach ($names as $name => $title) {
+ $form_state['values'][$table][$name] = $name;
+ }
+ }
+ }
+ $output = bulk_export_export_form_submit($form, $form_state);
+ }
+ else {
+ $output = drupal_build_form('bulk_export_export_form', $form_state);
+ $module_name = $form_state['module'];
+ }
+
+ if (!empty($form_state['submitted']) || $cli) {
+ drupal_set_title(t('Bulk export results'));
+ $output = '';
+ $module_code = '';
+ $api_code = array();
+ $dependencies = $file_data = array();
+ foreach ($form_state['code'] as $module => $api_info) {
+ if ($module == 'general') {
+ $module_code .= $api_info;
+ }
+ else {
+ foreach ($api_info as $api => $info) {
+ $api_hook = ctools_plugin_api_get_hook($module, $api);
+ if (empty($api_code[$api_hook])) {
+ $api_code[$api_hook] = '';
+ }
+ $api_code[$api_hook] .= " if (\$module == '$module' && \$api == '$api') {\n";
+ $api_code[$api_hook] .= " return array('version' => $info[version]);\n";
+ $api_code[$api_hook] .= " }\n";
+ $dependencies[$module] = TRUE;
+
+ $file = $module_name . '.' . $api . '.inc';
+ $code = "<?php\n\n";
+ $code .= "/**\n";
+ $code .= " * @file\n";
+ $code .= " * Bulk export of $api objects generated by Bulk export module.\n";
+ $code .= " */\n\n";
+ $code .= $info['code'];
+ if ($cli) {
+ $file_data[$file] = $code;
+ }
+ else {
+ $export_form = drupal_get_form('ctools_export_form', $code, t('Place this in @file', array('@file' => $file)));
+ $output .= drupal_render($export_form);
+ }
+ }
+ }
+ }
+
+ // Add hook_ctools_plugin_api at the top of the module code, if there is any.
+ if ($api_code) {
+ foreach ($api_code as $api_hook => $text) {
+ $api = "\n/**\n";
+ $api .= " * Implements hook_$api_hook().\n";
+ $api .= " */\n";
+ $api .= "function {$module_name}_$api_hook(\$module, \$api) {\n";
+ $api .= $text;
+ $api .= "}\n";
+ $module_code = $api . $module_code;
+ }
+ }
+
+ if ($module_code) {
+ $module = "<?php\n\n";
+ $module .= "/**\n";
+ $module .= " * @file\n";
+ $module .= " * Bulk export of objects generated by Bulk export module.\n";
+ $module .= " */\n";
+ $module .= $module_code;
+ if ($cli) {
+ $file_data[$module_name . '.module'] = $module;
+ }
+ else {
+ $export_form = drupal_get_form('ctools_export_form', $module, t('Place this in @file', array('@file' => $form_state['module'] . '.module')));
+ $output = drupal_render($export_form) . $output;
+ }
+ }
+
+ $info = strtr("name = @module export module\n", array('@module' => $form_state['module']));
+ $info .= strtr("description = Export objects from CTools\n", array('@module' => $form_state['values']['name']));
+ foreach ($dependencies as $module => $junk) {
+ $info .= "dependencies[] = $module\n";
+ }
+ $info .= "package = Chaos tool suite\n";
+ $info .= "core = 7.x\n";
+ if ($cli) {
+ $file_data[$module_name . '.info'] = $info;
+ }
+ else {
+ $export_form = drupal_get_form('ctools_export_form', $info, t('Place this in @file', array('@file' => $form_state['module'] . '.info')));
+ $output = drupal_render($export_form) . $output;
+ }
+ }
+
+ if ($cli) {
+ return $file_data;
+ }
+ else {
+ return $output;
+ }
+ }
+ else {
+ return t('There are no objects to be exported at this time.');
+ }
+}
+
+/**
+ * FAPI definition for the bulk exporter form.
+ *
+ */
+function bulk_export_export_form($form, &$form_state) {
+
+ $files = system_rebuild_module_data();
+
+ $form['additional_settings'] = array(
+ '#type' => 'vertical_tabs',
+ );
+
+ $options = $tables = array();
+ foreach ($form_state['exportables'] as $table => $list) {
+ if (empty($list)) {
+ continue;
+ }
+
+ foreach ($list as $id => $title) {
+ $options[$table][$id] = array($title);
+ $options[$table][$id]['#attributes'] = array('class' => array('bulk-selection'));
+ }
+
+ $module = $form_state['export_tables'][$table];
+ $header = array($table);
+ $module_name = $files[$module]->info['name'];
+ $tables[] = $table;
+
+ if (!isset($form[$module_name])) {
+ $form[$files[$module]->info['name']] = array(
+ '#type' => 'fieldset',
+ '#group' => 'additional_settings',
+ '#title' => $module_name,
+ );
+ }
+
+ $form[$module_name]['tables'][$table] = array(
+ '#prefix' => '<div class="export-container">',
+ '#suffix' => '</div>',
+ '#type' => 'tableselect',
+ '#header' => $header,
+ '#options' => $options[$table],
+ );
+ }
+
+ $form['tables'] = array(
+ '#type' => 'value',
+ '#value' => $tables,
+ );
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Module name'),
+ '#description' => t('Enter the module name to export code to.'),
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Export'),
+ );
+
+ $form['#action'] = url('admin/structure/bulk-export/results');
+ $form['#attached']['css'][] = drupal_get_path('module', 'bulk_export') . '/bulk_export.css';
+ $form['#attached']['js'][] = drupal_get_path('module', 'bulk_export') . '/bulk_export.js';
+ return $form;
+}
+
+/**
+ * Process the bulk export submit form and make the results available.
+ */
+function bulk_export_export_form_submit($form, &$form_state) {
+ $code = array();
+ $name = empty($form_state['values']['name']) ? 'foo' : $form_state['values']['name'];
+ $tables = $form_state['values']['tables'];
+
+ foreach ($tables as $table) {
+ $names = array_keys(array_filter($form_state['values'][$table]));
+ if ($names) {
+ natcasesort($names);
+ ctools_export_to_hook_code($code, $table, $names, $name);
+ }
+ }
+
+ $form_state['code'] = $code;
+ $form_state['module'] = $name;
+}
diff --git a/sites/all/modules/ctools/css/button.css b/sites/all/modules/ctools/css/button.css
new file mode 100644
index 000000000..15e484be3
--- /dev/null
+++ b/sites/all/modules/ctools/css/button.css
@@ -0,0 +1,31 @@
+
+.ctools-button-processed {
+ border-style: solid;
+ border-width: 1px;
+ display: inline-block;
+ line-height: 1;
+}
+
+.ctools-button-processed:hover {
+ cursor: pointer;
+}
+
+.ctools-button-processed .ctools-content {
+ padding-bottom: 2px;
+ padding-top: 2px;
+}
+
+.ctools-no-js .ctools-content ul,
+.ctools-button-processed .ctools-content ul {
+ list-style-image: none;
+ list-style-type: none;
+}
+
+.ctools-button-processed li {
+ line-height: 1.3333;
+}
+
+.ctools-button li a {
+ padding-left: 12px;
+ padding-right: 12px;
+}
diff --git a/sites/all/modules/ctools/css/collapsible-div.css b/sites/all/modules/ctools/css/collapsible-div.css
new file mode 100644
index 000000000..ff648138f
--- /dev/null
+++ b/sites/all/modules/ctools/css/collapsible-div.css
@@ -0,0 +1,26 @@
+
+.ctools-collapsible-container .ctools-toggle {
+ float: left;
+ width: 21px;
+ height: 21px;
+ cursor: pointer;
+ background-position: 7px 7px;
+ background-repeat: no-repeat;
+ background-image: url(../images/collapsible-expanded.png);
+}
+
+.ctools-collapsible-container .ctools-collapsible-handle {
+ display: none;
+}
+
+html.js .ctools-collapsible-container .ctools-collapsible-handle {
+ display: block;
+}
+
+.ctools-collapsible-container .ctools-collapsible-handle {
+ cursor: pointer;
+}
+
+.ctools-collapsible-container .ctools-toggle-collapsed {
+ background-image: url(../images/collapsible-collapsed.png);
+}
diff --git a/sites/all/modules/ctools/css/context.css b/sites/all/modules/ctools/css/context.css
new file mode 100644
index 000000000..5093104c8
--- /dev/null
+++ b/sites/all/modules/ctools/css/context.css
@@ -0,0 +1,10 @@
+.ctools-context-holder .ctools-context-title {
+ float: left;
+ width: 49%;
+ font-style: italic;
+}
+
+.ctools-context-holder .ctools-context-content {
+ float: right;
+ width: 49%;
+}
diff --git a/sites/all/modules/ctools/css/ctools.css b/sites/all/modules/ctools/css/ctools.css
new file mode 100644
index 000000000..7372988df
--- /dev/null
+++ b/sites/all/modules/ctools/css/ctools.css
@@ -0,0 +1,25 @@
+.ctools-locked {
+ color: red;
+ border: 1px solid red;
+ padding: 1em;
+}
+
+.ctools-owns-lock {
+ background: #FFFFDD none repeat scroll 0 0;
+ border: 1px solid #F0C020;
+ padding: 1em;
+}
+
+a.ctools-ajaxing,
+input.ctools-ajaxing,
+button.ctools-ajaxing,
+select.ctools-ajaxing {
+ padding-right: 18px !important;
+ background: url(../images/status-active.gif) right center no-repeat;
+}
+
+div.ctools-ajaxing {
+ float: left;
+ width: 18px;
+ background: url(../images/status-active.gif) center center no-repeat;
+}
diff --git a/sites/all/modules/ctools/css/dropbutton.css b/sites/all/modules/ctools/css/dropbutton.css
new file mode 100644
index 000000000..5e3ea242d
--- /dev/null
+++ b/sites/all/modules/ctools/css/dropbutton.css
@@ -0,0 +1,66 @@
+
+.ctools-dropbutton-processed {
+ padding-right: 18px;
+ position: relative;
+ background-color: inherit;
+}
+
+.ctools-dropbutton-processed.open {
+ z-index: 200;
+}
+
+.ctools-dropbutton-processed .ctools-content li,
+.ctools-dropbutton-processed .ctools-content a {
+ display: block;
+}
+
+.ctools-dropbutton-processed .ctools-link {
+ bottom: 0;
+ display: block;
+ height: auto;
+ position: absolute;
+ right: 0;
+ text-indent: -9999px; /* LTR */
+ top: 0;
+ width: 17px;
+}
+
+.ctools-dropbutton-processed .ctools-link a {
+ overflow: hidden;
+}
+
+.ctools-dropbutton-processed .ctools-content ul {
+ margin: 0;
+ overflow: hidden;
+}
+
+.ctools-dropbutton-processed.open li + li {
+ padding-top: 4px;
+}
+
+/**
+ * This creates the dropbutton arrow and inherits the link color
+ */
+.ctools-twisty {
+ border-bottom-color: transparent;
+ border-left-color: transparent;
+ border-right-color: transparent;
+ border-style: solid;
+ border-width: 4px 4px 0;
+ line-height: 0;
+ right: 6px;
+ position: absolute;
+ top: 0.75em;
+}
+
+.ctools-dropbutton-processed.open .ctools-twisty {
+ border-bottom: 4px solid;
+ border-left-color: transparent;
+ border-right-color: transparent;
+ border-top-color: transparent;
+ top: 0.5em;
+}
+
+.ctools-no-js .ctools-twisty {
+ display: none;
+}
diff --git a/sites/all/modules/ctools/css/dropdown.css b/sites/all/modules/ctools/css/dropdown.css
new file mode 100644
index 000000000..bb50f3f44
--- /dev/null
+++ b/sites/all/modules/ctools/css/dropdown.css
@@ -0,0 +1,73 @@
+html.js div.ctools-dropdown div.ctools-dropdown-container {
+ z-index: 1001;
+ display: none;
+ text-align: left;
+ position: absolute;
+}
+
+html.js div.ctools-dropdown div.ctools-dropdown-container ul li a {
+ display: block;
+}
+
+html.js div.ctools-dropdown div.ctools-dropdown-container ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+html.js div.ctools-dropdown div.ctools-dropdown-container ul li {
+ display: block;
+ /* prevent excess right margin in IE */
+ margin-right: 0;
+ margin-left: 0;
+ padding-right: 0;
+ padding-left: 0;
+ background-image: none; /* prevent list backgrounds from mucking things up */
+}
+
+.ctools-dropdown-no-js .ctools-dropdown-link,
+.ctools-dropdown-no-js span.text {
+ display: none;
+}
+
+/* Everything from here down is purely visual style and can be overridden. */
+
+html.js div.ctools-dropdown a.ctools-dropdown-text-link {
+ background: url(../images/collapsible-expanded.png) 3px 5px no-repeat;
+ padding-left: 12px;
+}
+
+html.js div.ctools-dropdown div.ctools-dropdown-container {
+ width: 175px;
+ background: #fff;
+ border: 1px solid black;
+ margin: 4px 1px 0 0;
+ padding: 0;
+ color: #494949;
+}
+
+html.js div.ctools-dropdown div.ctools-dropdown-container ul li li a {
+ padding-left: 25px;
+ width: 150px;
+ color: #027AC6;
+}
+
+html.js div.ctools-dropdown div.ctools-dropdown-container ul li a {
+ text-decoration: none;
+ padding-left: 5px;
+ width: 170px;
+ color: #027AC6;
+}
+
+html.js div.ctools-dropdown div.ctools-dropdown-container ul li span {
+ display: block;
+}
+
+html.js div.ctools-dropdown div.ctools-dropdown-container ul li span.text {
+ font-style: italic;
+ padding-left: 5px;
+}
+
+html.js .ctools-dropdown-hover {
+ background-color: #ECECEC;
+}
diff --git a/sites/all/modules/ctools/css/export-ui-list.css b/sites/all/modules/ctools/css/export-ui-list.css
new file mode 100644
index 000000000..170d128ad
--- /dev/null
+++ b/sites/all/modules/ctools/css/export-ui-list.css
@@ -0,0 +1,45 @@
+body form#ctools-export-ui-list-form {
+ margin: 0 0 20px 0;
+}
+
+#ctools-export-ui-list-form .form-item {
+ padding-right: 1em; /* LTR */
+ float: left; /* LTR */
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#ctools-export-ui-list-items {
+ width: 100%;
+}
+
+#edit-order-wrapper {
+ clear: left; /* LTR */
+}
+
+#ctools-export-ui-list-form .form-submit {
+ margin-top: 1.65em;
+ float: left; /* LTR */
+}
+
+tr.ctools-export-ui-disabled td {
+ color: #999;
+}
+
+th.ctools-export-ui-operations,
+td.ctools-export-ui-operations {
+ text-align: right; /* LTR */
+ vertical-align: top;
+}
+
+/* Force the background color to inherit so that the dropbuttons do not need
+ a specific background color. */
+td.ctools-export-ui-operations {
+ background-color: inherit;
+}
+
+td.ctools-export-ui-operations .ctools-dropbutton {
+ text-align: left; /* LTR */
+ position: absolute;
+ right: 10px;
+}
diff --git a/sites/all/modules/ctools/css/modal.css b/sites/all/modules/ctools/css/modal.css
new file mode 100644
index 000000000..def374be3
--- /dev/null
+++ b/sites/all/modules/ctools/css/modal.css
@@ -0,0 +1,130 @@
+div.ctools-modal-content {
+ background: #fff;
+ color: #000;
+ padding: 0;
+ margin: 2px;
+ border: 1px solid #000;
+ width: 600px;
+ text-align: left;
+}
+
+div.ctools-modal-content .modal-title {
+ font-size: 120%;
+ font-weight: bold;
+ color: white;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+div.ctools-modal-content .modal-header {
+ background-color: #2385c2;
+ padding: 0 .25em 0 1em;
+}
+
+div.ctools-modal-content .modal-header a {
+ color: white;
+}
+
+div.ctools-modal-content .modal-content {
+ padding: 1em 1em 0 1em;
+ overflow: auto;
+ position: relative; /* Keeps IE7 from flowing outside the modal. */
+}
+
+div.ctools-modal-content .modal-form {
+}
+
+div.ctools-modal-content a.close {
+ color: white;
+ float: right;
+}
+
+div.ctools-modal-content a.close:hover {
+ text-decoration: none;
+}
+
+div.ctools-modal-content a.close img {
+ position: relative;
+ top: 1px;
+}
+
+div.ctools-modal-content .modal-content .modal-throbber-wrapper {
+ text-align: center;
+}
+
+div.ctools-modal-content .modal-content .modal-throbber-wrapper img {
+ margin-top: 160px;
+}
+
+/** modal forms CSS **/
+div.ctools-modal-content .form-item label {
+ width: 15em;
+ float: left;
+}
+
+div.ctools-modal-content .form-item label.option {
+ width: auto;
+ float: none;
+}
+
+div.ctools-modal-content .form-item .description {
+ clear: left;
+}
+
+div.ctools-modal-content .form-item .description .tips {
+ margin-left: 2em;
+}
+
+div.ctools-modal-content .no-float .form-item * {
+ float: none;
+}
+
+div.ctools-modal-content .modal-form .no-float label {
+ width: auto;
+}
+
+div.ctools-modal-content fieldset,
+div.ctools-modal-content .form-radios,
+div.ctools-modal-content .form-checkboxes {
+ clear: left;
+}
+
+div.ctools-modal-content .vertical-tabs-panes > fieldset {
+ clear: none;
+}
+
+div.ctools-modal-content .resizable-textarea {
+ width: auto;
+ margin-left: 15em;
+ margin-right: 5em;
+}
+
+div.ctools-modal-content .container-inline .form-item {
+ margin-right: 2em;
+}
+
+#views-exposed-pane-wrapper .form-item {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.ctools-modal-content label.hidden-options {
+ background: transparent url(../images/arrow-active.png) no-repeat right;
+ height: 12px;
+ padding-right: 12px;
+}
+
+div.ctools-modal-content label.expanded-options {
+ background: transparent url(../images/expanded-options.png) no-repeat right;
+ height: 12px;
+ padding-right: 16px;
+}
+
+div.ctools-modal-content .option-text-aligner label.expanded-options,
+div.ctools-modal-content .option-text-aligner label.hidden-options {
+ background: none;
+}
+
+div.ctools-modal-content .dependent-options {
+ padding-left: 30px;
+}
diff --git a/sites/all/modules/ctools/css/ruleset.css b/sites/all/modules/ctools/css/ruleset.css
new file mode 100644
index 000000000..891455f01
--- /dev/null
+++ b/sites/all/modules/ctools/css/ruleset.css
@@ -0,0 +1,11 @@
+.ctools-right-container {
+ float: right;
+ padding: 0 0 0 .5em;
+ margin: 0;
+ width: 48.5%;
+}
+
+.ctools-left-container {
+ padding-right: .5em;
+ width: 48.5%;
+}
diff --git a/sites/all/modules/ctools/css/stylizer.css b/sites/all/modules/ctools/css/stylizer.css
new file mode 100644
index 000000000..a16ec789b
--- /dev/null
+++ b/sites/all/modules/ctools/css/stylizer.css
@@ -0,0 +1,129 @@
+
+/* Farbtastic placement */
+.color-form {
+ max-width: 50em;
+ position: relative;
+ min-height: 195px;
+}
+#placeholder {
+/*
+ position: absolute;
+ top: 0;
+ right: 0;
+*/
+ margin: 0 auto;
+ width: 195px;
+}
+
+/* Palette */
+.color-form .form-item {
+ height: 2em;
+ line-height: 2em;
+ padding-left: 1em; /* LTR */
+ margin: 0.5em 0;
+}
+
+.color-form .form-item input {
+ margin-top: .2em;
+}
+
+.color-form label {
+ float: left; /* LTR */
+ clear: left; /* LTR */
+ width: 14em;
+}
+.color-form .form-text, .color-form .form-select {
+ float: left; /* LTR */
+}
+.color-form .form-text {
+ text-align: center;
+ margin-right: 5px; /* LTR */
+ cursor: pointer;
+}
+
+#palette .hook {
+ float: left; /* LTR */
+ margin-top: 3px;
+ width: 16px;
+ height: 16px;
+}
+#palette .up {
+ background-position: 100% -27px; /* LTR */
+}
+#palette .both {
+ background-position: 100% -54px; /* LTR */
+}
+
+
+#palette .form-item {
+ width: 24em;
+}
+#palette .item-selected {
+ background: #eee;
+}
+
+/* Preview */
+#preview {
+ width: 45%;
+ float: right;
+ margin: 0;
+}
+
+#ctools_stylizer_color_scheme_form {
+ float: left;
+ width: 45%;
+ margin: 0;
+}
+
+/* general style for the layout-icon */
+.ctools-style-icon .caption {
+ width: 100px;
+ margin-bottom: 1em;
+ line-height: 1em;
+ text-align: center;
+ cursor: default;
+}
+
+.ctools-style-icons .form-item {
+ width: 100px;
+ float: left;
+ margin: 0 3px !important;
+}
+
+.ctools-style-icons .form-item .ctools-style-icon {
+ float: none;
+ height: 150px;
+ width: 100px;
+}
+
+.ctools-style-icons .form-item label.option {
+ width: 100px;
+ display: block;
+ text-align: center;
+}
+
+.ctools-style-icons .form-item label.option input {
+ margin: 0 auto;
+}
+
+.ctools-style-icons .ctools-style-category {
+ height: 190px;
+}
+
+.ctools-style-icons .ctools-style-category label {
+ font-weight: bold;
+ width: 100%;
+ float: left;
+}
+
+/**
+ * Stylizer font editor widget
+ */
+.ctools-stylizer-spacing-form .form-item {
+ float: left;
+ margin: .25em;
+}
+
+#edit-font-font {
+ width: 9em;
+}
diff --git a/sites/all/modules/ctools/css/wizard.css b/sites/all/modules/ctools/css/wizard.css
new file mode 100644
index 000000000..d42a2db06
--- /dev/null
+++ b/sites/all/modules/ctools/css/wizard.css
@@ -0,0 +1,8 @@
+
+.wizard-trail {
+ font-size: 120%;
+}
+
+.wizard-trail-current {
+ font-weight: bold;
+}
diff --git a/sites/all/modules/ctools/ctools.api.php b/sites/all/modules/ctools/ctools.api.php
new file mode 100644
index 000000000..a7ab78395
--- /dev/null
+++ b/sites/all/modules/ctools/ctools.api.php
@@ -0,0 +1,268 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Chaos Tool Suite.
+ *
+ * This file is divided into static hooks (hooks with string literal names) and
+ * dynamic hooks (hooks with pattern-derived string names).
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Inform CTools about plugin types.
+ *
+ * @return array
+ * An array of plugin types, keyed by the type name.
+ * See the advanced help topic 'plugins-creating' for details of the array
+ * properties.
+ */
+function hook_ctools_plugin_type() {
+ $plugins['my_type'] = array(
+ 'load themes' => TRUE,
+ );
+
+ return $plugins;
+}
+
+/**
+ * This hook is used to inform the CTools plugin system about the location of a
+ * directory that should be searched for files containing plugins of a
+ * particular type. CTools invokes this same hook for all plugins, using the
+ * two passed parameters to indicate the specific type of plugin for which it
+ * is searching.
+ *
+ * The $plugin_type parameter is self-explanatory - it is the string name of the
+ * plugin type (e.g., Panels' 'layouts' or 'styles'). The $owner parameter is
+ * necessary because CTools internally namespaces plugins by the module that
+ * owns them. This is an extension of Drupal best practices on avoiding global
+ * namespace pollution by prepending your module name to all its functions.
+ * Consequently, it is possible for two different modules to create a plugin
+ * type with exactly the same name and have them operate in harmony. In fact,
+ * this system renders it impossible for modules to encroach on other modules'
+ * plugin namespaces.
+ *
+ * Given this namespacing, it is important that implementations of this hook
+ * check BOTH the $owner and $plugin_type parameters before returning a path.
+ * If your module does not implement plugins for the requested module/plugin
+ * combination, it is safe to return nothing at all (or NULL). As a convenience,
+ * it is also safe to return a path that does not exist for plugins your module
+ * does not implement - see form 2 for a use case.
+ *
+ * Note that modules implementing a plugin also must implement this hook to
+ * instruct CTools as to the location of the plugins. See form 3 for a use case.
+ *
+ * The conventional structure to return is "plugins/$plugin_type" - that is, a
+ * 'plugins' subdirectory in your main module directory, with individual
+ * directories contained therein named for the plugin type they contain.
+ *
+ * @param string $owner
+ * The system name of the module owning the plugin type for which a base
+ * directory location is being requested.
+ * @param string $plugin_type
+ * The name of the plugin type for which a base directory is being requested.
+ * @return string
+ * The path where CTools' plugin system should search for plugin files,
+ * relative to your module's root. Omit leading and trailing slashes.
+ */
+function hook_ctools_plugin_directory($owner, $plugin_type) {
+ // Form 1 - for a module implementing only the 'content_types' plugin owned
+ // by CTools, this would cause the plugin system to search the
+ // <moduleroot>/plugins/content_types directory for .inc plugin files.
+ if ($owner == 'ctools' && $plugin_type == 'content_types') {
+ return 'plugins/content_types';
+ }
+
+ // Form 2 - if your module implements only Panels plugins, and has 'layouts'
+ // and 'styles' plugins but no 'cache' or 'display_renderers', it is OK to be
+ // lazy and return a directory for a plugin you don't actually implement (so
+ // long as that directory doesn't exist). This lets you avoid ugly in_array()
+ // logic in your conditional, and also makes it easy to add plugins of those
+ // types later without having to change this hook implementation.
+ if ($owner == 'panels') {
+ return "plugins/$plugin_type";
+ }
+
+ // Form 3 - CTools makes no assumptions about where your plugins are located,
+ // so you still have to implement this hook even for plugins created by your
+ // own module.
+ if ($owner == 'mymodule') {
+ // Yes, this is exactly like Form 2 - just a different reasoning for it.
+ return "plugins/$plugin_type";
+ }
+ // Finally, if nothing matches, it's safe to return nothing at all (or NULL).
+}
+
+/**
+ * Alter a plugin before it has been processed.
+ *
+ * This hook is useful for altering flags or other information that will be
+ * used or possibly overriden by the process hook if defined.
+ *
+ * @param $plugin
+ * An associative array defining a plugin.
+ * @param $info
+ * An associative array of plugin type info.
+ */
+function hook_ctools_plugin_pre_alter(&$plugin, &$info) {
+ // Override a function defined by the plugin.
+ if ($info['type'] == 'my_type') {
+ $plugin['my_flag'] = 'new_value';
+ }
+}
+
+/**
+ * Alter a plugin after it has been processed.
+ *
+ * This hook is useful for overriding the final values for a plugin after it
+ * has been processed.
+ *
+ * @param $plugin
+ * An associative array defining a plugin.
+ * @param $info
+ * An associative array of plugin type info.
+ */
+function hook_ctools_plugin_post_alter(&$plugin, &$info) {
+ // Override a function defined by the plugin.
+ if ($info['type'] == 'my_type') {
+ $plugin['my_function'] = 'new_function';
+ }
+}
+
+/**
+ * Alter the list of modules/themes which implement a certain api.
+ *
+ * The hook named here is just an example, as the real existing hooks are named
+ * for example 'hook_views_api_alter'.
+ *
+ * @param array $list
+ * An array of informations about the implementors of a certain api.
+ * The key of this array are the module names/theme names.
+ */
+function hook_ctools_api_hook_alter(&$list) {
+ // Alter the path of the node implementation.
+ $list['node']['path'] = drupal_get_path('module', 'node');
+}
+
+/**
+ * Alter the available functions to be used in ctools math expression api.
+ *
+ * One usecase would be to create your own function in your module and
+ * allow to use it in the math expression api.
+ *
+ * @param $functions
+ * An array which has the functions as value.
+ */
+function hook_ctools_math_expression_functions_alter(&$functions) {
+ // Allow to convert from degrees to radiant.
+ $functions[] = 'deg2rad';
+}
+
+/**
+ * Alter everything.
+ *
+ * @param $info
+ * An associative array containing the following keys:
+ * - content: The rendered content.
+ * - title: The content's title.
+ * - no_blocks: A boolean to decide if blocks should be displayed.
+ * @param $page
+ * If TRUE then this renderer owns the page and can use theme('page')
+ * for no blocks; if false, output is returned regardless of any no
+ * blocks settings.
+ * @param $context
+ * An associative array containing the following keys:
+ * - args: The raw arguments behind the contexts.
+ * - contexts: The context objects in use.
+ * - task: The task object in use.
+ * - subtask: The subtask object in use.
+ * - handler: The handler object in use.
+ */
+function hook_ctools_render_alter(&$info, &$page, &$context) {
+ if ($context['handler']->name == 'my_handler') {
+ ctools_add_css('my_module.theme', 'my_module');
+ }
+}
+
+/**
+ * Alter a content plugin subtype.
+ *
+ * While content types can be altered via hook_ctools_plugin_pre_alter() or
+ * hook_ctools_plugin_post_alter(), the subtypes that content types rely on
+ * are special and require their own hook.
+ *
+ * This hook can be used to add things like 'render last' or change icons
+ * or categories or to rename content on specific sites.
+ */
+function hook_ctools_content_subtype_alter($subtype, $plugin) {
+ $subtype['render last'] = TRUE;
+}
+
+/**
+ * Alter the definition of an entity context plugin.
+ *
+ * @param array $plugin
+ * An associative array defining a plugin.
+ * @param array $entity
+ * The entity info array of a specific entity type.
+ * @param string $plugin_id
+ * The plugin ID, in the format NAME:KEY.
+ */
+function hook_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) {
+ ctools_include('context');
+ switch ($plugin_id) {
+ case 'entity_id:taxonomy_term':
+ $plugin['no ui'] = TRUE;
+ case 'entity:user':
+ $plugin = ctools_get_context('user');
+ unset($plugin['no ui']);
+ unset($plugin['no required context ui']);
+ break;
+ }
+}
+
+/**
+ * Alter the definition of entity context plugins.
+ *
+ * @param array $plugins
+ * An associative array of plugin definitions, keyed by plugin ID.
+ *
+ * @see hook_ctools_entity_context_alter()
+ */
+function hook_ctools_entity_contexts_alter(&$plugins) {
+ $plugins['entity_id:taxonomy_term']['no ui'] = TRUE;
+}
+
+/**
+ * Change cleanstring settings.
+ *
+ * @param array $settings
+ * An associative array of cleanstring settings.
+ *
+ * @see ctools_cleanstring()
+ */
+function hook_ctools_cleanstring_alter(&$settings) {
+ // Convert all strings to lower case.
+ $settings['lower case'] = TRUE;
+}
+
+/**
+ * Change cleanstring settings for a specific clean ID.
+ *
+ * @param array $settings
+ * An associative array of cleanstring settings.
+ *
+ * @see ctools_cleanstring()
+ */
+function hook_ctools_cleanstring_CLEAN_ID_alter(&$settings) {
+ // Convert all strings to lower case.
+ $settings['lower case'] = TRUE;
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/sites/all/modules/ctools/ctools.info b/sites/all/modules/ctools/ctools.info
new file mode 100644
index 000000000..02c86f523
--- /dev/null
+++ b/sites/all/modules/ctools/ctools.info
@@ -0,0 +1,17 @@
+name = Chaos tools
+description = A library of helpful tools by Merlin of Chaos.
+core = 7.x
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+files[] = includes/context.inc
+files[] = includes/css-cache.inc
+files[] = includes/math-expr.inc
+files[] = includes/stylizer.inc
+files[] = tests/css_cache.test
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/ctools.install b/sites/all/modules/ctools/ctools.install
new file mode 100644
index 000000000..e96c74326
--- /dev/null
+++ b/sites/all/modules/ctools/ctools.install
@@ -0,0 +1,265 @@
+<?php
+
+/**
+ * @file
+ * Contains install and update functions for ctools.
+ */
+
+/**
+ * Use requirements to ensure that the CTools CSS cache directory can be
+ * created and that the PHP version requirement is met.
+ */
+function ctools_requirements($phase) {
+ $requirements = array();
+ if ($phase == 'runtime') {
+ $requirements['ctools_css_cache'] = array(
+ 'title' => t('CTools CSS Cache'),
+ 'severity' => REQUIREMENT_OK,
+ 'value' => t('Exists'),
+ );
+
+ $path = 'public://ctools/css';
+ if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
+ $requirements['ctools_css_cache']['description'] = t('The CTools CSS cache directory, %path could not be created due to a misconfigured files directory. Please ensure that the files directory is correctly configured and that the webserver has permission to create directories.', array('%path' => file_uri_target($path)));
+ $requirements['ctools_css_cache']['severity'] = REQUIREMENT_ERROR;
+ $requirements['ctools_css_cache']['value'] = t('Unable to create');
+ }
+
+ if (!function_exists('error_get_last')) {
+ $requirements['ctools_php_52']['title'] = t('CTools PHP requirements');
+ $requirements['ctools_php_52']['description'] = t('CTools requires certain features only available in PHP 5.2.0 or higher.');
+ $requirements['ctools_php_52']['severity'] = REQUIREMENT_WARNING;
+ $requirements['ctools_php_52']['value'] = t('PHP !version', array('!version' => phpversion()));
+ }
+ }
+
+ return $requirements;
+}
+
+/**
+ * Implements hook_schema().
+ */
+function ctools_schema() {
+ return ctools_schema_3();
+}
+
+/**
+ * Version 3 of the CTools schema.
+ */
+function ctools_schema_3() {
+ $schema = ctools_schema_2();
+
+ // update the 'obj' field to be 128 bytes long:
+ $schema['ctools_object_cache']['fields']['obj']['length'] = 128;
+
+ return $schema;
+}
+
+/**
+ * Version 2 of the CTools schema.
+ */
+function ctools_schema_2() {
+ $schema = ctools_schema_1();
+
+ // update the 'name' field to be 128 bytes long:
+ $schema['ctools_object_cache']['fields']['name']['length'] = 128;
+
+ // Update the 'data' field to be type 'blob'.
+ $schema['ctools_object_cache']['fields']['data'] = array(
+ 'type' => 'blob',
+ 'size' => 'big',
+ 'description' => 'Serialized data being stored.',
+ 'serialize' => TRUE,
+ );
+
+ // DO NOT MODIFY THIS TABLE -- this definition is used to create the table.
+ // Changes to this table must be made in schema_3 or higher.
+ $schema['ctools_css_cache'] = array(
+ 'description' => 'A special cache used to store CSS that must be non-volatile.',
+ 'fields' => array(
+ 'cid' => array(
+ 'type' => 'varchar',
+ 'length' => '128',
+ 'description' => 'The CSS ID this cache object belongs to.',
+ 'not null' => TRUE,
+ ),
+ 'filename' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'The filename this CSS is stored in.',
+ ),
+ 'css' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'CSS being stored.',
+ 'serialize' => TRUE,
+ ),
+ 'filter' => array(
+ 'type' => 'int',
+ 'size' => 'tiny',
+ 'description' => 'Whether or not this CSS needs to be filtered.',
+ ),
+ ),
+ 'primary key' => array('cid'),
+ );
+
+ return $schema;
+}
+
+/**
+ * CTools' initial schema; separated for the purposes of updates.
+ *
+ * DO NOT MAKE CHANGES HERE. This schema version is locked.
+ */
+function ctools_schema_1() {
+ $schema['ctools_object_cache'] = array(
+ 'description' => t('A special cache used to store objects that are being edited; it serves to save state in an ordinarily stateless environment.'),
+ 'fields' => array(
+ 'sid' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'not null' => TRUE,
+ 'description' => 'The session ID this cache object belongs to.',
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '32',
+ 'not null' => TRUE,
+ 'description' => 'The name of the object this cache is attached to.',
+ ),
+ 'obj' => array(
+ 'type' => 'varchar',
+ 'length' => '32',
+ 'not null' => TRUE,
+ 'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.',
+ ),
+ 'updated' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The time this cache was created or updated.',
+ ),
+ 'data' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Serialized data being stored.',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('sid', 'obj', 'name'),
+ 'indexes' => array('updated' => array('updated')),
+ );
+ return $schema;
+}
+
+/**
+ * Implements hook_install().
+ */
+function ctools_install() {
+ // Activate our custom cache handler for the CSS cache.
+ variable_set('cache_class_cache_ctools_css', 'CToolsCssCache');
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function ctools_uninstall() {
+ variable_del('cache_class_cache_ctools_css');
+}
+
+/**
+ * Enlarge the ctools_object_cache.name column to prevent truncation and weird
+ * errors.
+ */
+function ctools_update_6001() {
+ // Perform updates like this to reduce code duplication.
+ $schema = ctools_schema_2();
+
+ db_change_field('ctools_object_cache', 'name', 'name', $schema['ctools_object_cache']['fields']['name']);
+}
+
+/**
+ * Add the new css cache table.
+ */
+function ctools_update_6002() {
+ // Schema 2 is locked and should not be changed.
+ $schema = ctools_schema_2();
+
+ db_create_table('ctools_css_cache', $schema['ctools_css_cache']);
+}
+
+/**
+ * Take over for the panels_views module if it was on.
+ */
+function ctools_update_6003() {
+ $result = db_query('SELECT status FROM {system} WHERE name = :name', array(':name' => 'panels_views'))->fetchField();
+ if ($result) {
+ db_delete('system')->condition('name', 'panels_views')->execute();
+ module_enable(array('views_content'), TRUE);
+ }
+}
+
+/**
+ * Add primary key to the ctools_object_cache table.
+ */
+function ctools_update_6004() {
+ db_add_primary_key('ctools_object_cache', array('sid', 'obj', 'name'));
+ db_drop_index('ctools_object_cache', 'sid_obj_name');
+}
+
+/**
+ * Removed update.
+ */
+function ctools_update_6005() {
+ return array();
+}
+
+/**
+ * ctools_custom_content table was originally here, but is now moved to
+ * its own module.
+ */
+function ctools_update_6007() {
+ $ret = array();
+ if (db_table_exists('ctools_custom_content')) {
+ // Enable the module to make everything as seamless as possible.
+ module_enable(array('ctools_custom_content'), TRUE);
+ }
+
+ return $ret;
+}
+
+/**
+ * ctools_object_cache needs to be defined as a blob.
+ */
+function ctools_update_6008() {
+ db_delete('ctools_object_cache')
+ ->execute();
+
+ db_change_field('ctools_object_cache', 'data', 'data', array(
+ 'type' => 'blob',
+ 'size' => 'big',
+ 'description' => 'Serialized data being stored.',
+ 'serialize' => TRUE,
+ )
+ );
+}
+
+/**
+ * Enable the custom CSS cache handler.
+ */
+function ctools_update_7000() {
+ variable_set('cache_class_cache_ctools_css', 'CToolsCssCache');
+}
+
+/**
+ * Increase the length of the ctools_object_cache.obj column.
+ */
+function ctools_update_7001() {
+ db_change_field('ctools_object_cache', 'obj', 'obj', array(
+ 'type' => 'varchar',
+ 'length' => '128',
+ 'not null' => TRUE,
+ 'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.',
+ ));
+}
diff --git a/sites/all/modules/ctools/ctools.module b/sites/all/modules/ctools/ctools.module
new file mode 100644
index 000000000..008214e21
--- /dev/null
+++ b/sites/all/modules/ctools/ctools.module
@@ -0,0 +1,1088 @@
+<?php
+
+/**
+ * @file
+ * CTools primary module file.
+ *
+ * Most of the CTools tools are in their own .inc files. This contains
+ * nothing more than a few convenience functions and some hooks that
+ * must be implemented in the module file.
+ */
+
+define('CTOOLS_API_VERSION', '2.0.8');
+
+/**
+ * The current working ctools version.
+ *
+ * In a release, it should be 7.x-1.x, which should match what drush make will
+ * create. In a dev format, it should be 7.x-1.(x+1)-dev, which will allow
+ * modules depending on new features in ctools to depend on ctools > 7.x-1.x.
+ *
+ * To define a specific version of CTools as a dependency for another module,
+ * simply include a dependency line in that module's info file, e.g.:
+ * ; Requires CTools v7.x-1.4 or newer.
+ * dependencies[] = ctools (>=1.4)
+ */
+define('CTOOLS_MODULE_VERSION', '7.x-1.9');
+
+/**
+ * Test the CTools API version.
+ *
+ * This function can always be used to safely test if CTools has the minimum
+ * API version that your module can use. It can also try to protect you from
+ * running if the CTools API version is too new, but if you do that you need
+ * to be very quick about watching CTools API releases and release new versions
+ * of your software as soon as the new release is made, or people might end up
+ * updating CTools and having your module shut down without any recourse.
+ *
+ * It is recommended that every hook of your module that might use CTools or
+ * might lead to a use of CTools be guarded like this:
+ *
+ * @code
+ * if (!module_invoke('ctools', 'api_version', '1.0')) {
+ * return;
+ * }
+ * @endcode
+ *
+ * Note that some hooks such as _menu() or _theme() must return an array().
+ *
+ * You can use it in your hook_requirements to report this error condition
+ * like this:
+ *
+ * @code
+ * define('MODULENAME_MINIMUM_CTOOLS_API_VERSION', '1.0');
+ * define('MODULENAME_MAXIMUM_CTOOLS_API_VERSION', '1.1');
+ *
+ * function MODULENAME_requirements($phase) {
+ * $requirements = array();
+ * if (!module_invoke('ctools', 'api_version', MODULENAME_MINIMUM_CTOOLS_API_VERSION, MODULENAME_MAXIMUM_CTOOLS_API_VERSION)) {
+ * $requirements['MODULENAME_ctools'] = array(
+ * 'title' => $t('MODULENAME required Chaos Tool Suite (CTools) API Version'),
+ * 'value' => t('Between @a and @b', array('@a' => MODULENAME_MINIMUM_CTOOLS_API_VERSION, '@b' => MODULENAME_MAXIMUM_CTOOLS_API_VERSION)),
+ * 'severity' => REQUIREMENT_ERROR,
+ * );
+ * }
+ * return $requirements;
+ * }
+ * @endcode
+ *
+ * Please note that the version is a string, not an floating point number.
+ * This will matter once CTools reaches version 1.10.
+ *
+ * A CTools API changes history will be kept in API.txt. Not every new
+ * version of CTools will necessarily update the API version.
+ * @param $minimum
+ * The minimum version of CTools necessary for your software to run with it.
+ * @param $maximum
+ * The maximum version of CTools allowed for your software to run with it.
+ */
+function ctools_api_version($minimum, $maximum = NULL) {
+ if (version_compare(CTOOLS_API_VERSION, $minimum, '<')) {
+ return FALSE;
+ }
+
+ if (isset($maximum) && version_compare(CTOOLS_API_VERSION, $maximum, '>')) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+// -----------------------------------------------------------------------
+// General utility functions
+
+/**
+ * Include .inc files as necessary.
+ *
+ * This fuction is helpful for including .inc files for your module. The
+ * general case is including ctools funcitonality like this:
+ *
+ * @code
+ * ctools_include('plugins');
+ * @endcode
+ *
+ * Similar funcitonality can be used for other modules by providing the $module
+ * and $dir arguments like this:
+ *
+ * @code
+ * // include mymodule/includes/import.inc
+ * ctools_include('import', 'mymodule');
+ * // include mymodule/plugins/foobar.inc
+ * ctools_include('foobar', 'mymodule', 'plugins');
+ * @endcode
+ *
+ * @param $file
+ * The base file name to be included.
+ * @param $module
+ * Optional module containing the include.
+ * @param $dir
+ * Optional subdirectory containing the include file.
+ */
+function ctools_include($file, $module = 'ctools', $dir = 'includes') {
+ static $used = array();
+
+ $dir = '/' . ($dir ? $dir . '/' : '');
+
+ if (!isset($used[$module][$dir][$file])) {
+ require_once DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "$dir$file.inc";
+ $used[$module][$dir][$file] = TRUE;
+ }
+}
+
+/**
+ * Include .inc files in a form context.
+ *
+ * This is a variant of ctools_include that will save information in the
+ * the form_state so that cached forms will properly include things.
+ */
+function ctools_form_include(&$form_state, $file, $module = 'ctools', $dir = 'includes') {
+ if (!isset($form_state['build_info']['args'])) {
+ $form_state['build_info']['args'] = array();
+ }
+
+ $dir = '/' . ($dir ? $dir . '/' : '');
+ form_load_include($form_state, 'inc', $module, $dir . $file);
+}
+
+/**
+ * Add an arbitrary path to the $form_state so it can work with form cache.
+ *
+ * module_load_include uses an unfortunately annoying syntax to work, making it
+ * difficult to translate the more simple $path + $file syntax.
+ */
+function ctools_form_include_file(&$form_state, $filename) {
+ if (!isset($form_state['build_info']['args'])) {
+ $form_state['build_info']['args'] = array();
+ }
+
+ // Now add this to the build info files so that AJAX requests will know to load it.
+ $form_state['build_info']['files']["$filename"] = $filename;
+ require_once DRUPAL_ROOT . '/' . $filename;
+}
+
+/**
+ * Provide the proper path to an image as necessary.
+ *
+ * This helper function is used by ctools but can also be used in other
+ * modules in the same way as explained in the comments of ctools_include.
+ *
+ * @param $image
+ * The base file name (with extension) of the image to be included.
+ * @param $module
+ * Optional module containing the include.
+ * @param $dir
+ * Optional subdirectory containing the include file.
+ */
+function ctools_image_path($image, $module = 'ctools', $dir = 'images') {
+ return drupal_get_path('module', $module) . "/$dir/" . $image;
+}
+
+/**
+ * Include css files as necessary.
+ *
+ * This helper function is used by ctools but can also be used in other
+ * modules in the same way as explained in the comments of ctools_include.
+ *
+ * @param $file
+ * The base file name to be included.
+ * @param $module
+ * Optional module containing the include.
+ * @param $dir
+ * Optional subdirectory containing the include file.
+ */
+function ctools_add_css($file, $module = 'ctools', $dir = 'css') {
+ drupal_add_css(drupal_get_path('module', $module) . "/$dir/$file.css");
+}
+
+/**
+ * Format a css file name for use with $form['#attached']['css'].
+ *
+ * This helper function is used by ctools but can also be used in other
+ * modules in the same way as explained in the comments of ctools_include.
+ *
+ * @code
+ * $form['#attached']['css'] = array(ctools_attach_css('collapsible-div'));
+ * $form['#attached']['css'][ctools_attach_css('collapsible-div')] = array('preprocess' => FALSE);
+ * @endcode
+ *
+ * @param $file
+ * The base file name to be included.
+ * @param $module
+ * Optional module containing the include.
+ * @param $dir
+ * Optional subdirectory containing the include file.
+ */
+function ctools_attach_css($file, $module = 'ctools', $dir = 'css') {
+ return drupal_get_path('module', $module) . "/$dir/$file.css";
+}
+
+/**
+ * Include js files as necessary.
+ *
+ * This helper function is used by ctools but can also be used in other
+ * modules in the same way as explained in the comments of ctools_include.
+ *
+ * @param $file
+ * The base file name to be included.
+ * @param $module
+ * Optional module containing the include.
+ * @param $dir
+ * Optional subdirectory containing the include file.
+ */
+function ctools_add_js($file, $module = 'ctools', $dir = 'js') {
+ drupal_add_js(drupal_get_path('module', $module) . "/$dir/$file.js");
+}
+
+/**
+ * Format a javascript file name for use with $form['#attached']['js'].
+ *
+ * This helper function is used by ctools but can also be used in other
+ * modules in the same way as explained in the comments of ctools_include.
+ *
+ * @code
+ * $form['#attached']['js'] = array(ctools_attach_js('auto-submit'));
+ * @endcode
+ *
+ * @param $file
+ * The base file name to be included.
+ * @param $module
+ * Optional module containing the include.
+ * @param $dir
+ * Optional subdirectory containing the include file.
+ */
+function ctools_attach_js($file, $module = 'ctools', $dir = 'js') {
+ return drupal_get_path('module', $module) . "/$dir/$file.js";
+}
+
+/**
+ * Get a list of roles in the system.
+ *
+ * @return
+ * An array of role names keyed by role ID.
+ *
+ * @deprecated
+ * user_roles() should be used instead.
+ */
+function ctools_get_roles() {
+ return user_roles();
+}
+
+/*
+ * Break x,y,z and x+y+z into an array. Numeric only.
+ *
+ * @param $str
+ * The string to parse.
+ *
+ * @return $object
+ * An object containing
+ * - operator: Either 'and' or 'or'
+ * - value: An array of numeric values.
+ */
+function ctools_break_phrase($str) {
+ $object = new stdClass();
+
+ if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
+ // The '+' character in a query string may be parsed as ' '.
+ $object->operator = 'or';
+ $object->value = preg_split('/[+ ]/', $str);
+ }
+ else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
+ $object->operator = 'and';
+ $object->value = explode(',', $str);
+ }
+
+ // Keep an 'error' value if invalid strings were given.
+ if (!empty($str) && (empty($object->value) || !is_array($object->value))) {
+ $object->value = array(-1);
+ $object->invalid_input = TRUE;
+ return $object;
+ }
+
+ if (empty($object->value)) {
+ $object->value = array();
+ }
+
+ // Doubly ensure that all values are numeric only.
+ foreach ($object->value as $id => $value) {
+ $object->value[$id] = intval($value);
+ }
+
+ return $object;
+}
+
+/**
+ * Set a token/value pair to be replaced later in the request, specifically in
+ * ctools_page_token_processing().
+ *
+ * @param $token
+ * The token to be replaced later, during page rendering. This should
+ * ideally be a string inside of an HTML comment, so that if there is
+ * no replacement, the token will not render on the page.
+ * @param $type
+ * The type of the token. Can be either 'variable', which will pull data
+ * directly from the page variables
+ * @param $argument
+ * If $type == 'variable' then argument should be the key to fetch from
+ * the $variables. If $type == 'callback' then it should either be the
+ * callback, or an array that will be sent to call_user_func_array().
+ *
+ * @return
+ * A array of token/variable names to be replaced.
+ */
+function ctools_set_page_token($token = NULL, $type = NULL, $argument = NULL) {
+ static $tokens = array();
+
+ if (isset($token)) {
+ $tokens[$token] = array($type, $argument);
+ }
+ return $tokens;
+}
+
+/**
+ * Easily set a token from the page variables.
+ *
+ * This function can be used like this:
+ * $token = ctools_set_variable_token('tabs');
+ *
+ * $token will then be a simple replacement for the 'tabs' about of the
+ * variables available in the page template.
+ */
+function ctools_set_variable_token($token) {
+ $string = '<!-- ctools-page-' . $token . ' -->';
+ ctools_set_page_token($string, 'variable', $token);
+ return $string;
+}
+
+/**
+ * Easily set a token from the page variables.
+ *
+ * This function can be used like this:
+ * $token = ctools_set_variable_token('id', 'mymodule_myfunction');
+ */
+function ctools_set_callback_token($token, $callback) {
+ // If the callback uses arguments they are considered in the token.
+ if (is_array($callback)) {
+ $token .= '-' . md5(serialize($callback));
+ }
+ $string = '<!-- ctools-page-' . $token . ' -->';
+ ctools_set_page_token($string, 'callback', $callback);
+ return $string;
+}
+
+/**
+ * Tell CTools that sidebar blocks should not be rendered.
+ *
+ * It is often desirable to not display sidebars when rendering a page,
+ * particularly when using Panels. This informs CTools to alter out any
+ * sidebar regions during block render.
+ */
+function ctools_set_no_blocks($blocks = FALSE) {
+ $status = &drupal_static(__FUNCTION__, TRUE);
+ $status = $blocks;
+}
+
+/**
+ * Wrapper function to create UUIDs via ctools, falls back on UUID module
+ * if it is enabled. This code is a copy of uuid.inc from the uuid module.
+ * @see http://php.net/uniqid#65879
+ */
+
+function ctools_uuid_generate() {
+ if (!module_exists('uuid')) {
+ ctools_include('uuid');
+
+ $callback = drupal_static(__FUNCTION__);
+
+ if (empty($callback)) {
+ if (function_exists('uuid_create') && !function_exists('uuid_make')) {
+ $callback = '_ctools_uuid_generate_pecl';
+ }
+ elseif (function_exists('com_create_guid')) {
+ $callback = '_ctools_uuid_generate_com';
+ }
+ else {
+ $callback = '_ctools_uuid_generate_php';
+ }
+ }
+ return $callback();
+ }
+ else {
+ return uuid_generate();
+ }
+}
+
+/**
+ * Check that a string appears to be in the format of a UUID.
+ * @see http://drupal.org/project/uuid
+ *
+ * @param $uuid
+ * The string to test.
+ *
+ * @return
+ * TRUE if the string is well formed.
+ */
+function ctools_uuid_is_valid($uuid = '') {
+ if (empty($uuid)) {
+ return FALSE;
+ }
+ if (function_exists('uuid_is_valid') || module_exists('uuid')) {
+ return uuid_is_valid($uuid);
+ }
+ else {
+ ctools_include('uuid');
+ return uuid_is_valid($uuid);
+ }
+}
+
+/**
+ * Add an array of classes to the body.
+ *
+ * @param mixed $classes
+ * A string or an array of class strings to add.
+ * @param string $hook
+ * The theme hook to add the class to. The default is 'html' which will
+ * affect the body tag.
+ */
+function ctools_class_add($classes, $hook = 'html') {
+ if (!is_array($classes)) {
+ $classes = array($classes);
+ }
+
+ $static = &drupal_static('ctools_process_classes', array());
+ if (!isset($static[$hook]['add'])) {
+ $static[$hook]['add'] = array();
+ }
+ foreach ($classes as $class) {
+ $static[$hook]['add'][] = $class;
+ }
+}
+
+/**
+ * Remove an array of classes from the body.
+ *
+ * @param mixed $classes
+ * A string or an array of class strings to remove.
+ * @param string $hook
+ * The theme hook to remove the class from. The default is 'html' which will
+ * affect the body tag.
+ */
+function ctools_class_remove($classes, $hook = 'html') {
+ if (!is_array($classes)) {
+ $classes = array($classes);
+ }
+
+ $static = &drupal_static('ctools_process_classes', array());
+ if (!isset($static[$hook]['remove'])) {
+ $static[$hook]['remove'] = array();
+ }
+ foreach ($classes as $class) {
+ $static[$hook]['remove'][] = $class;
+ }
+}
+
+// -----------------------------------------------------------------------
+// Drupal core hooks
+
+/**
+ * Implement hook_init to keep our global CSS at the ready.
+ */
+function ctools_init() {
+ ctools_add_css('ctools');
+ // If we are sure that CTools' AJAX is in use, change the error handling.
+ if (!empty($_REQUEST['ctools_ajax'])) {
+ ini_set('display_errors', 0);
+ register_shutdown_function('ctools_shutdown_handler');
+ }
+
+ // Clear plugin cache on the module page submit.
+ if ($_GET['q'] == 'admin/modules/list/confirm' && !empty($_POST)) {
+ cache_clear_all('ctools_plugin_files:', 'cache', TRUE);
+ }
+}
+
+/**
+ * Shutdown handler used during ajax operations to help catch fatal errors.
+ */
+function ctools_shutdown_handler() {
+ if (function_exists('error_get_last') AND ($error = error_get_last())) {
+ switch ($error['type']) {
+ case E_ERROR:
+ case E_CORE_ERROR:
+ case E_COMPILE_ERROR:
+ case E_USER_ERROR:
+ // Do this manually because including files here is dangerous.
+ $commands = array(
+ array(
+ 'command' => 'alert',
+ 'title' => t('Error'),
+ 'text' => t('Unable to complete operation. Fatal error in @file on line @line: @message', array(
+ '@file' => $error['file'],
+ '@line' => $error['line'],
+ '@message' => $error['message'],
+ )),
+ ),
+ );
+
+ // Change the status code so that the client will read the AJAX returned.
+ header('HTTP/1.1 200 OK');
+ drupal_json($commands);
+ }
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function ctools_theme() {
+ ctools_include('utility');
+ $items = array();
+ ctools_passthrough('ctools', 'theme', $items);
+ return $items;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function ctools_menu() {
+ ctools_include('utility');
+ $items = array();
+ ctools_passthrough('ctools', 'menu', $items);
+ return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function ctools_permission() {
+ return array(
+ 'use ctools import' => array(
+ 'title' => t('Use CTools importer'),
+ 'description' => t('The import functionality allows users to execute arbitrary PHP code, so extreme caution must be taken.'),
+ 'restrict access' => TRUE,
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_cron. Clean up old caches.
+ */
+function ctools_cron() {
+ ctools_include('utility');
+ $items = array();
+ ctools_passthrough('ctools', 'cron', $items);
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function ctools_flush_caches() {
+ // Only return the CSS cache bin if it has been activated, to avoid
+ // drupal_flush_all_caches() from trying to truncate a non-existing table.
+ return variable_get('cache_class_cache_ctools_css', FALSE) ? array('cache_ctools_css') : array();
+}
+
+/**
+ * Implements hook_element_info_alter().
+ *
+ */
+function ctools_element_info_alter(&$type) {
+ ctools_include('dependent');
+ ctools_dependent_element_info_alter($type);
+}
+
+/**
+ * Implementation of hook_file_download()
+ *
+ * When using the private file system, we have to let Drupal know it's ok to
+ * download CSS and image files from our temporary directory.
+ */
+function ctools_file_download($filepath) {
+ if (strpos($filepath, 'ctools') === 0) {
+ $mime = file_get_mimetype($filepath);
+ // For safety's sake, we allow only text and images.
+ if (strpos($mime, 'text') === 0 || strpos($mime, 'image') === 0) {
+ return array('Content-type:' . $mime);
+ }
+ }
+}
+
+/**
+ * Implements hook_registry_files_alter().
+ *
+ * Alter the registry of files to automagically include all classes in
+ * class-based plugins.
+ */
+function ctools_registry_files_alter(&$files, $indexed_modules) {
+ ctools_include('registry');
+ return _ctools_registry_files_alter($files, $indexed_modules);
+}
+
+// -----------------------------------------------------------------------
+// FAPI hooks that must be in the .module file.
+
+/**
+ * Alter the comment form to get a little more control over it.
+ */
+function ctools_form_comment_form_alter(&$form, &$form_state) {
+ if (!empty($form_state['ctools comment alter'])) {
+ // Force the form to post back to wherever we are.
+ $form['#action'] = url($_GET['q'], array('fragment' => 'comment-form'));
+ if (empty($form['#submit'])) {
+ $form['#submit'] = array('comment_form_submit');
+ }
+ $form['#submit'][] = 'ctools_node_comment_form_submit';
+ }
+}
+
+function ctools_node_comment_form_submit(&$form, &$form_state) {
+ $form_state['redirect'][0] = $_GET['q'];
+}
+
+// -----------------------------------------------------------------------
+// CTools hook implementations.
+
+/**
+ * Implementation of hook_ctools_plugin_directory() to let the system know
+ * where all our own plugins are.
+ */
+function ctools_ctools_plugin_directory($owner, $plugin_type) {
+ if ($owner == 'ctools') {
+ return 'plugins/' . $plugin_type;
+ }
+}
+
+/**
+ * Implements hook_ctools_plugin_type().
+ */
+function ctools_ctools_plugin_type() {
+ ctools_include('utility');
+ $items = array();
+ // Add all the plugins that have their own declaration space elsewhere.
+ ctools_passthrough('ctools', 'plugin-type', $items);
+
+ return $items;
+}
+
+// -----------------------------------------------------------------------
+// Drupal theme preprocess hooks that must be in the .module file.
+
+/**
+ * A theme preprocess function to automatically allow panels-based node
+ * templates based upon input when the panel was configured.
+ */
+function ctools_preprocess_node(&$vars) {
+ // The 'ctools_template_identifier' attribute of the node is added when the pane is
+ // rendered.
+ if (!empty($vars['node']->ctools_template_identifier)) {
+ $vars['ctools_template_identifier'] = check_plain($vars['node']->ctools_template_identifier);
+ $vars['theme_hook_suggestions'][] = 'node__panel__' . check_plain($vars['node']->ctools_template_identifier);
+ }
+}
+
+
+/**
+ * Implements hook_page_alter().
+ *
+ * Last ditch attempt to remove sidebar regions if the "no blocks"
+ * functionality has been activated.
+ *
+ * @see ctools_block_list_alter().
+ */
+function ctools_page_alter(&$page) {
+ $check = drupal_static('ctools_set_no_blocks', TRUE);
+ if (!$check) {
+ foreach ($page as $region_id => $region) {
+ // @todo -- possibly we can set configuration for this so that users can
+ // specify which blocks will not get rendered.
+ if (strpos($region_id, 'sidebar') !== FALSE) {
+ unset($page[$region_id]);
+ }
+ }
+ }
+ $page['#post_render'][] = 'ctools_page_token_processing';
+}
+
+/**
+ * A theme post_render callback to allow content type plugins to use page
+ * template variables which are not yet available when the content type is
+ * rendered.
+ */
+function ctools_page_token_processing($children, $elements) {
+ $tokens = ctools_set_page_token();
+ if (!empty($tokens)) {
+ foreach ($tokens as $token => $key) {
+ list($type, $argument) = $key;
+ switch ($type) {
+ case 'variable':
+ $tokens[$token] = isset($elements[$argument]) ? $elements[$argument] : '';
+ break;
+ case 'callback':
+ if (is_string($argument) && function_exists($argument)) {
+ $tokens[$token] = $argument($elements);
+ }
+ if (is_array($argument) && function_exists($argument[0])) {
+ $function = array_shift($argument);
+ $argument = array_merge(array(&$elements), $argument);
+ $tokens[$token] = call_user_func_array($function, $argument);
+ }
+ break;
+ }
+ }
+ $children = strtr($children, $tokens);
+ }
+ return $children;
+}
+
+/**
+ * Implements hook_process().
+ *
+ * Add and remove CSS classes from the variables array. We use process so that
+ * we alter anything added in the preprocess hooks.
+ */
+function ctools_process(&$variables, $hook) {
+ if (!isset($variables['classes'])) {
+ return;
+ }
+
+ $classes = drupal_static('ctools_process_classes', array());
+
+ // Process the classses to add.
+ if (!empty($classes[$hook]['add'])) {
+ $add_classes = array_map('drupal_clean_css_identifier', $classes[$hook]['add']);
+ $variables['classes_array'] = array_unique(array_merge($variables['classes_array'], $add_classes));
+ }
+
+ // Process the classes to remove.
+ if (!empty($classes[$hook]['remove'])) {
+ $remove_classes = array_map('drupal_clean_css_identifier', $classes[$hook]['remove']);
+ $variables['classes_array'] = array_diff($variables['classes_array'], $remove_classes);
+ }
+
+ // Since this runs after template_process(), we need to re-implode the
+ // classes array.
+ $variables['classes'] = implode(' ', $variables['classes_array']);
+}
+
+// -----------------------------------------------------------------------
+// Menu callbacks that must be in the .module file.
+
+/**
+ * Determine if the current user has access via a plugin.
+ *
+ * This function is meant to be embedded in the Drupal menu system, and
+ * therefore is in the .module file since sub files can't be loaded, and
+ * takes arguments a little bit more haphazardly than ctools_access().
+ *
+ * @param $access
+ * An access control array which contains the following information:
+ * - 'logic': and or or. Whether all tests must pass or one must pass.
+ * - 'plugins': An array of access plugins. Each contains:
+ * - - 'name': The name of the plugin
+ * - - 'settings': The settings from the plugin UI.
+ * - - 'context': Which context to use.
+ * @param ...
+ * zero or more context arguments generated from argument plugins. These
+ * contexts must have an 'id' attached to them so that they can be
+ * properly associated. The argument plugin system should set this, but
+ * if the context is coming from elsewhere it will need to be set manually.
+ *
+ * @return
+ * TRUE if access is granted, false if otherwise.
+ */
+function ctools_access_menu($access) {
+ // Short circuit everything if there are no access tests.
+ if (empty($access['plugins'])) {
+ return TRUE;
+ }
+
+ $contexts = array();
+ foreach (func_get_args() as $arg) {
+ if (is_object($arg) && get_class($arg) == 'ctools_context') {
+ $contexts[$arg->id] = $arg;
+ }
+ }
+
+ ctools_include('context');
+ return ctools_access($access, $contexts);
+}
+
+/**
+ * Determine if the current user has access via checks to multiple different
+ * permissions.
+ *
+ * This function is a thin wrapper around user_access that allows multiple
+ * permissions to be easily designated for use on, for example, a menu callback.
+ *
+ * @param ...
+ * An indexed array of zero or more permission strings to be checked by
+ * user_access().
+ *
+ * @return
+ * Iff all checks pass will this function return TRUE. If an invalid argument
+ * is passed (e.g., not a string), this function errs on the safe said and
+ * returns FALSE.
+ */
+function ctools_access_multiperm() {
+ foreach (func_get_args() as $arg) {
+ if (!is_string($arg) || !user_access($arg)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Check to see if the incoming menu item is js capable or not.
+ *
+ * This can be used as %ctools_js as part of a path in hook menu. CTools
+ * ajax functions will automatically change the phrase 'nojs' to 'ajax'
+ * when it attaches ajax to a link. This can be used to autodetect if
+ * that happened.
+ */
+function ctools_js_load($js) {
+ if ($js == 'ajax') {
+ return TRUE;
+ }
+ return 0;
+}
+
+/**
+ * Provides the default value for %ctools_js.
+ *
+ * This allows drupal_valid_path() to work with %ctools_js.
+ */
+function ctools_js_to_arg($arg) {
+ return empty($arg) || $arg == '%' ? 'nojs' : $arg;
+}
+
+/**
+ * Menu _load hook.
+ *
+ * This function will be called to load an object as a replacement for
+ * %ctools_export_ui in menu paths.
+ */
+function ctools_export_ui_load($item_name, $plugin_name) {
+ $return = &drupal_static(__FUNCTION__, FALSE);
+
+ if (!$return) {
+ ctools_include('export-ui');
+ $plugin = ctools_get_export_ui($plugin_name);
+ $handler = ctools_export_ui_get_handler($plugin);
+
+ if ($handler) {
+ return $handler->load_item($item_name);
+ }
+ }
+
+ return $return;
+}
+
+// -----------------------------------------------------------------------
+// Caching callbacks on behalf of export-ui.
+
+/**
+ * Menu access callback for various tasks of export-ui.
+ */
+function ctools_export_ui_task_access($plugin_name, $op, $item = NULL) {
+ ctools_include('export-ui');
+ $plugin = ctools_get_export_ui($plugin_name);
+ $handler = ctools_export_ui_get_handler($plugin);
+
+ if ($handler) {
+ return $handler->access($op, $item);
+ }
+
+ // Deny access if the handler cannot be found.
+ return FALSE;
+}
+
+/**
+ * Callback for access control ajax form on behalf of export ui.
+ *
+ * Returns the cached access config and contexts used.
+ * Note that this is assuming that access will be in $item->access -- if it
+ * is not, an export UI plugin will have to make its own callbacks.
+ */
+function ctools_export_ui_ctools_access_get($argument) {
+ ctools_include('export-ui');
+ list($plugin_name, $key) = explode(':', $argument, 2);
+
+ $plugin = ctools_get_export_ui($plugin_name);
+ $handler = ctools_export_ui_get_handler($plugin);
+
+ if ($handler) {
+ ctools_include('context');
+ $item = $handler->edit_cache_get($key);
+ if (!$item) {
+ $item = ctools_export_crud_load($handler->plugin['schema'], $key);
+ }
+
+ $contexts = ctools_context_load_contexts($item);
+ return array($item->access, $contexts);
+ }
+}
+
+/**
+ * Callback for access control ajax form on behalf of export ui
+ *
+ * Returns the cached access config and contexts used.
+ * Note that this is assuming that access will be in $item->access -- if it
+ * is not, an export UI plugin will have to make its own callbacks.
+ */
+function ctools_export_ui_ctools_access_set($argument, $access) {
+ ctools_include('export-ui');
+ list($plugin_name, $key) = explode(':', $argument, 2);
+
+ $plugin = ctools_get_export_ui($plugin_name);
+ $handler = ctools_export_ui_get_handler($plugin);
+
+ if ($handler) {
+ ctools_include('context');
+ $item = $handler->edit_cache_get($key);
+ if (!$item) {
+ $item = ctools_export_crud_load($handler->plugin['schema'], $key);
+ }
+ $item->access = $access;
+ return $handler->edit_cache_set_key($item, $key);
+ }
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ */
+function ctools_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ ctools_include('menu');
+ _ctools_menu_add_dynamic_items($data, $router_item, $root_path);
+}
+
+/**
+ * Implement hook_block_list_alter() to potentially remove blocks.
+ *
+ * This exists in order to replicate Drupal 6's "no blocks" functionality.
+ */
+function ctools_block_list_alter(&$blocks) {
+ $check = drupal_static('ctools_set_no_blocks', TRUE);
+ if (!$check) {
+ foreach ($blocks as $block_id => $block) {
+ // @todo -- possibly we can set configuration for this so that users can
+ // specify which blocks will not get rendered.
+ if (strpos($block->region, 'sidebar') !== FALSE) {
+ unset($blocks[$block_id]);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_modules_enabled().
+ *
+ * Clear caches for detecting new plugins.
+ */
+function ctools_modules_enabled($modules) {
+ ctools_include('plugins');
+ ctools_get_plugins_reset();
+ cache_clear_all('ctools_plugin_files:', 'cache', TRUE);
+}
+
+/**
+ * Implements hook_modules_disabled().
+ *
+ * Clear caches for removing disabled plugins.
+ */
+function ctools_modules_disabled($modules) {
+ ctools_include('plugins');
+ ctools_get_plugins_reset();
+ cache_clear_all('ctools_plugin_files:', 'cache', TRUE);
+}
+
+/**
+ * Menu theme callback.
+ *
+ * This simply ensures that Panels ajax calls are rendered in the same
+ * theme as the original page to prevent .css file confusion.
+ *
+ * To use this, set this as the theme callback on AJAX related menu
+ * items. Since the ajax page state won't be sent during ajax requests,
+ * it should be safe to use even if ajax isn't invoked.
+ */
+function ctools_ajax_theme_callback() {
+ if (!empty($_POST['ajax_page_state']['theme'])) {
+ return $_POST['ajax_page_state']['theme'];
+ }
+}
+
+/**
+ * Implements hook_ctools_entity_context_alter().
+ */
+function ctools_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) {
+ ctools_include('context');
+ switch ($plugin_id) {
+ case 'entity_id:taxonomy_term':
+ $plugin['no ui'] = TRUE;
+ break;
+ case 'entity:user':
+ $plugin = ctools_get_context('user');
+ unset($plugin['no ui']);
+ unset($plugin['no required context ui']);
+ break;
+ }
+
+ // Apply restrictions on taxonomy term reverse relationships whose
+ // restrictions are in the settings on the field.
+ if (!empty($plugin['parent']) &&
+ $plugin['parent'] == 'entity_from_field' &&
+ !empty($plugin['reverse']) &&
+ $plugin['to entity'] == 'taxonomy_term') {
+ $field = field_info_field($plugin['field name']);
+ if (isset($field['settings']['allowed_values'][0]['vocabulary'])) {
+ $plugin['required context']->restrictions = array('vocabulary' => array($field['settings']['allowed_values'][0]['vocabulary']));
+ }
+ }
+}
+
+/**
+ * Implements hook_field_create_field().
+ */
+function ctools_field_create_field($field) {
+ ctools_flush_field_caches();
+}
+
+/**
+ * Implements hook_field_create_instance().
+ */
+function ctools_field_create_instance($instance) {
+ ctools_flush_field_caches();
+}
+/**
+ * Implements hook_field_delete_field().
+ */
+function ctools_field_delete_field($field) {
+ ctools_flush_field_caches();
+}
+/**
+ * Implements hook_field_delete_instance().
+ */
+function ctools_field_delete_instance($instance) {
+ ctools_flush_field_caches();
+}
+/**
+ * Implements hook_field_update_field().
+ */
+function ctools_field_update_field($field, $prior_field, $has_data) {
+ ctools_flush_field_caches();
+}
+
+/**
+ * Implements hook_field_update_instance().
+ */
+function ctools_field_update_instance($instance, $prior_instance) {
+ ctools_flush_field_caches();
+}
+
+/**
+ * Clear field related caches.
+ */
+function ctools_flush_field_caches() {
+ // Clear caches of 'Entity field' content type plugin.
+ cache_clear_all('ctools_entity_field_content_type_content_types', 'cache');
+}
diff --git a/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info
new file mode 100644
index 000000000..66ca12c08
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info
@@ -0,0 +1,13 @@
+name = Custom rulesets
+description = Create custom, exportable, reusable access rulesets for applications like Panels.
+core = 7.x
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+dependencies[] = ctools
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.install b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.install
new file mode 100644
index 000000000..3f0087725
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.install
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * Schema for customizable access rulesets.
+ */
+function ctools_access_ruleset_schema() {
+ return ctools_access_ruleset_schema_1();
+}
+
+function ctools_access_ruleset_schema_1() {
+ $schema = array();
+
+ $schema['ctools_access_ruleset'] = array(
+ 'description' => 'Contains exportable customized access rulesets.',
+ 'export' => array(
+ 'identifier' => 'ruleset',
+ 'bulk export' => TRUE,
+ 'primary key' => 'rsid',
+ 'api' => array(
+ 'owner' => 'ctools_access_ruleset',
+ 'api' => 'ctools_rulesets',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+ 'fields' => array(
+ 'rsid' => array(
+ 'type' => 'serial',
+ 'description' => 'A database primary key to ensure uniqueness',
+ 'not null' => TRUE,
+ 'no export' => TRUE,
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Unique ID for this ruleset. Used to identify it programmatically.',
+ ),
+ 'admin_title' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Administrative title for this ruleset.',
+ ),
+ 'admin_description' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Administrative description for this ruleset.',
+ 'object default' => '',
+ ),
+ 'requiredcontexts' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Any required contexts for this ruleset.',
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ 'contexts' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Any embedded contexts for this ruleset.',
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ 'relationships' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Any relationships for this ruleset.',
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ 'access' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'The actual group of access plugins for this ruleset.',
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ ),
+ 'primary key' => array('rsid'),
+ );
+
+ return $schema;
+}
diff --git a/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.module b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.module
new file mode 100644
index 000000000..fb39f3791
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.module
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * ctools_access_ruleset module
+ *
+ * This module allows styles to be created and managed on behalf of modules
+ * that implement styles.
+ *
+ * The ctools_access_ruleset tool allows recolorable styles to be created via a miniature
+ * scripting language. Panels utilizes this to allow administrators to add
+ * styles directly to any panel display.
+ */
+
+/**
+ * Implementation of hook_permission()
+ */
+function ctools_access_ruleset_permission() {
+ return array(
+ 'administer ctools access ruleset' => array(
+ 'title' => t('Administer access rulesets'),
+ 'description' => t('Add, delete and edit custom access rulesets.'),
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_ctools_plugin_directory() to let the system know
+ * we implement task and task_handler plugins.
+ */
+function ctools_access_ruleset_ctools_plugin_directory($module, $plugin) {
+ // Most of this module is implemented as an export ui plugin, and the
+ // rest is in ctools/includes/ctools_access_ruleset.inc
+ if ($module == 'ctools' && ($plugin == 'export_ui' || $plugin == 'access')) {
+ return 'plugins/' . $plugin;
+ }
+}
+
+/**
+ * Implementation of hook_panels_dashboard_blocks().
+ *
+ * Adds page information to the Panels dashboard.
+ */
+function ctools_access_ruleset_panels_dashboard_blocks(&$vars) {
+ $vars['links']['ctools_access_ruleset'] = array(
+ 'title' => l(t('Custom ruleset'), 'admin/structure/ctools-rulesets/add'),
+ 'description' => t('Custom rulesets are combinations of access plugins you can use for access control, selection criteria and pane visibility.'),
+ );
+
+ // Load all mini panels and their displays.
+ ctools_include('export');
+ $items = ctools_export_crud_load_all('ctools_access_ruleset');
+ $count = 0;
+ $rows = array();
+
+ foreach ($items as $item) {
+ $rows[] = array(
+ check_plain($item->admin_title),
+ array(
+ 'data' => l(t('Edit'), "admin/structure/ctools-rulesets/list/$item->name/edit"),
+ 'class' => 'links',
+ ),
+ );
+
+ // Only show 10.
+ if (++$count >= 10) {
+ break;
+ }
+ }
+
+ if ($rows) {
+ $content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
+ }
+ else {
+ $content = '<p>' . t('There are no custom rulesets.') . '</p>';
+ }
+
+ $vars['blocks']['ctools_access_ruleset'] = array(
+ 'title' => t('Manage custom rulesets'),
+ 'link' => l(t('Go to list'), 'admin/structure/ctools-rulesets'),
+ 'content' => $content,
+ 'class' => 'dashboard-ruleset',
+ 'section' => 'right',
+ );
+}
diff --git a/sites/all/modules/ctools/ctools_access_ruleset/plugins/access/ruleset.inc b/sites/all/modules/ctools/ctools_access_ruleset/plugins/access/ruleset.inc
new file mode 100644
index 000000000..f8abea6df
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_access_ruleset/plugins/access/ruleset.inc
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based on user rulesetission strings.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => '',
+ 'description' => '',
+ 'callback' => 'ctools_ruleset_ctools_access_check',
+ 'settings form' => 'ctools_ruleset_ctools_access_settings',
+ 'summary' => 'ctools_ruleset_ctools_access_summary',
+
+ // This access plugin actually just contains child plugins that are
+ // exportable, UI configured rulesets.
+ 'get child' => 'ctools_ruleset_ctools_access_get_child',
+ 'get children' => 'ctools_ruleset_ctools_access_get_children',
+);
+
+/**
+ * Merge the main access plugin with a loaded ruleset to form a child plugin.
+ */
+function ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item) {
+ $plugin['name'] = $parent . ':' . $item->name;
+ $plugin['title'] = check_plain($item->admin_title);
+ $plugin['description'] = check_plain($item->admin_description);
+
+ // TODO: Generalize this in CTools.
+ if (!empty($item->requiredcontexts)) {
+ $plugin['required context'] = array();
+ foreach ($item->requiredcontexts as $context) {
+ $info = ctools_get_context($context['name']);
+ // TODO: allow an optional setting
+ $plugin['required context'][] = new ctools_context_required($context['identifier'], $info['context name']);
+ }
+ }
+
+ // Store the loaded ruleset in the plugin.
+ $plugin['ruleset'] = $item;
+ return $plugin;
+}
+
+/**
+ * Get a single child access plugin.
+ */
+function ctools_ruleset_ctools_access_get_child($plugin, $parent, $child) {
+ ctools_include('export');
+ $item = ctools_export_crud_load('ctools_access_ruleset', $child);
+ if ($item) {
+ return ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item);
+ }
+}
+
+/**
+ * Get all child access plugins.
+ */
+function ctools_ruleset_ctools_access_get_children($plugin, $parent) {
+ $plugins = array();
+ ctools_include('export');
+ $items = ctools_export_crud_load_all('ctools_access_ruleset');
+ foreach ($items as $name => $item) {
+ $child = ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item);
+ $plugins[$child['name']] = $child;
+ }
+
+ return $plugins;
+}
+
+/**
+ * Settings form for the 'by ruleset' access plugin
+ */
+function ctools_ruleset_ctools_access_settings(&$form, &$form_state, $conf) {
+ if (!empty($form_state['plugin']['ruleset']->admin_description)) {
+ $form['markup'] = array(
+ '#markup' => '<div class="description">' . check_plain($form_state['plugin']['ruleset']->admin_description) . '</div>',
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_ruleset_ctools_access_check($conf, $context, $plugin) {
+ // Load up any contexts we might be using.
+ $contexts = ctools_context_match_required_contexts($plugin['ruleset']->requiredcontexts, $context);
+ $contexts = ctools_context_load_contexts($plugin['ruleset'], FALSE, $contexts);
+
+ return ctools_access($plugin['ruleset']->access, $contexts);
+}
+
+/**
+ * Provide a summary description based upon the checked roles.
+ */
+function ctools_ruleset_ctools_access_summary($conf, $context, $plugin) {
+ if (!empty($plugin['ruleset']->admin_description)) {
+ return check_plain($plugin['ruleset']->admin_description);
+ }
+ else {
+ return check_plain($plugin['ruleset']->admin_title);
+ }
+}
+
diff --git a/sites/all/modules/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc b/sites/all/modules/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc
new file mode 100644
index 000000000..d2a1c6056
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc
@@ -0,0 +1,29 @@
+<?php
+
+$plugin = array(
+ 'schema' => 'ctools_access_ruleset',
+ 'access' => 'administer ctools access ruleset',
+
+ 'menu' => array(
+ 'menu item' => 'ctools-rulesets',
+ 'menu title' => 'Custom access rulesets',
+ 'menu description' => 'Add, edit or delete custom access rulesets for use with Panels and other systems that utilize CTools content plugins.',
+ ),
+
+ 'title singular' => t('ruleset'),
+ 'title singular proper' => t('Ruleset'),
+ 'title plural' => t('rulesets'),
+ 'title plural proper' => t('Rulesets'),
+
+ 'handler' => 'ctools_access_ruleset_ui',
+
+ 'use wizard' => TRUE,
+ 'form info' => array(
+ 'order' => array(
+ 'basic' => t('Basic information'),
+ 'context' => t('Contexts'),
+ 'rules' => t('Rules'),
+ ),
+ ),
+);
+
diff --git a/sites/all/modules/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php b/sites/all/modules/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php
new file mode 100644
index 000000000..b18146455
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php
@@ -0,0 +1,53 @@
+<?php
+
+class ctools_access_ruleset_ui extends ctools_export_ui {
+
+ function edit_form_context(&$form, &$form_state) {
+ ctools_include('context-admin');
+ ctools_context_admin_includes();
+ ctools_add_css('ruleset');
+
+ $form['right'] = array(
+ '#prefix' => '<div class="ctools-right-container">',
+ '#suffix' => '</div>',
+ );
+
+ $form['left'] = array(
+ '#prefix' => '<div class="ctools-left-container clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ // Set this up and we can use CTools' Export UI's built in wizard caching,
+ // which already has callbacks for the context cache under this name.
+ $module = 'export_ui::' . $this->plugin['name'];
+ $name = $this->edit_cache_get_key($form_state['item'], $form_state['form type']);
+
+ ctools_context_add_context_form($module, $form, $form_state, $form['right']['contexts_table'], $form_state['item'], $name);
+ ctools_context_add_required_context_form($module, $form, $form_state, $form['left']['required_contexts_table'], $form_state['item'], $name);
+ ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $form_state['item'], $name);
+ }
+
+ function edit_form_rules(&$form, &$form_state) {
+ // The 'access' UI passes everything via $form_state, unlike the 'context' UI.
+ // The main difference is that one is about 3 years newer than the other.
+ ctools_include('context');
+ ctools_include('context-access-admin');
+
+ $form_state['access'] = $form_state['item']->access;
+ $form_state['contexts'] = ctools_context_load_contexts($form_state['item']);
+
+ $form_state['module'] = 'ctools_export_ui';
+ $form_state['callback argument'] = $form_state['object']->plugin['name'] . ':' . $form_state['object']->edit_cache_get_key($form_state['item'], $form_state['form type']);
+ $form_state['no buttons'] = TRUE;
+
+ $form = ctools_access_admin_form($form, $form_state);
+ }
+
+ function edit_form_rules_submit(&$form, &$form_state) {
+ $form_state['item']->access['logic'] = $form_state['values']['logic'];
+ }
+
+ function edit_form_submit(&$form, &$form_state) {
+ parent::edit_form_submit($form, $form_state);
+ }
+}
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css b/sites/all/modules/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css
new file mode 100644
index 000000000..8df17de5f
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css
@@ -0,0 +1,134 @@
+div.ctools-sample-modal-content {
+ background:none;
+ border:0;
+ color:#000000;
+ margin:0;
+ padding:0;
+ text-align:left;
+}
+div.ctools-sample-modal-content .modal-scroll{
+ overflow:hidden;
+ overflow-y:auto;
+}
+div.ctools-sample-modal-content #popups-overlay {
+ background-color:transparent;
+}
+div.ctools-sample-modal-content #popups-loading {
+ width:248px;
+ position:absolute;
+ display:none;
+ opacity:1;
+ -moz-border-radius: 8px;
+ -webkit-border-radius: 8px;
+ z-index:99;
+}
+div.ctools-sample-modal-content #popups-loading span.popups-loading-message {
+ background:#FFF url(../images/loading-large.gif) no-repeat 8px center;
+ display:block;
+ color:#444444;
+ font-family:Arial;
+ font-size:22px;
+ font-weight:bold;
+ height:36px;
+ line-height:36px;
+ padding:0 40px;
+}
+div.ctools-sample-modal-content #popups-loading table,
+div.ctools-sample-modal-content .popups-box table {
+ margin:0px;
+}
+div.ctools-sample-modal-content #popups-loading tbody,
+div.ctools-sample-modal-content .popups-box tbody {
+ border:none;
+}
+div.ctools-sample-modal-content .popups-box tr {
+ background-color:transparent;
+}
+div.ctools-sample-modal-content td.popups-border {
+ background: url(../images/popups-border.png);
+ background-color:transparent;
+ border: none;
+}
+div.ctools-sample-modal-content td.popups-tl,
+div.ctools-sample-modal-content td.popups-tr,
+div.ctools-sample-modal-content td.popups-bl,
+div.ctools-sample-modal-content td.popups-br {
+ background-repeat: no-repeat;
+ height:10px;
+ padding:0px;
+}
+div.ctools-sample-modal-content td.popups-tl { background-position: 0px 0px; }
+div.ctools-sample-modal-content td.popups-t,
+div.ctools-sample-modal-content td.popups-b {
+ background-position: 0px -40px;
+ background-repeat: repeat-x;
+}
+div.ctools-sample-modal-content td.popups-tr { background-position: 0px -10px; width: 10px; }
+div.ctools-sample-modal-content td.popups-cl,
+div.ctools-sample-modal-content td.popups-cr {
+ background-position: -10px 0;
+ background-repeat: repeat-y;
+ width:10px;
+}
+div.ctools-sample-modal-content td.popups-cl,
+div.ctools-sample-modal-content td.popups-cr,
+div.ctools-sample-modal-content td.popups-c { padding:0; border: none; }
+div.ctools-sample-modal-content td.popups-c { background:#fff; }
+div.ctools-sample-modal-content td.popups-bl { background-position: 0px -20px; }
+div.ctools-sample-modal-content td.popups-br { background-position: 0px -30px; width: 10px; }
+
+div.ctools-sample-modal-content .popups-box,
+div.ctools-sample-modal-content #popups-loading {
+ border: 0px solid #454545;
+ opacity:1;
+ overflow:hidden;
+ padding:0;
+ background-color:transparent;
+}
+div.ctools-sample-modal-content .popups-container {
+ overflow:hidden;
+ height:100%;
+ background-color:#fff;
+}
+div.ctools-sample-modal-content div.popups-title {
+ -moz-border-radius-topleft: 0px;
+ -webkit-border-radius-topleft: 0px;
+ margin-bottom:0px;
+ background-color:#ff7200;
+ border:1px solid #ce5c00;
+ padding:4px 10px 5px;
+ color:white;
+ font-size:1em;
+ font-weight:bold;
+}
+div.ctools-sample-modal-content .popups-body {
+ background-color:#fff;
+ padding:8px;
+}
+div.ctools-sample-modal-content .popups-box .popups-buttons,
+div.ctools-sample-modal-content .popups-box .popups-footer {
+ background-color:#fff;
+}
+div.ctools-sample-modal-content .popups-title a.close {
+ color: #fff;
+ text-decoration:none;
+}
+div.ctools-sample-modal-content .popups-close {
+ font-size:120%;
+ float:right;
+ text-align:right;
+}
+div.ctools-sample-modal-content .modal-loading-wrapper {
+ width:220px;
+ height:19px;
+ margin:0 auto;
+ margin-top:2%;
+}
+
+div.ctools-sample-modal-content tbody{
+ border:none;
+}
+
+div.ctools-sample-modal-content .modal-content .modal-throbber-wrapper img {
+ margin-top: 100px;
+}
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info
new file mode 100644
index 000000000..f5d1e7459
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info
@@ -0,0 +1,13 @@
+name = Chaos Tools (CTools) AJAX Example
+description = Shows how to use the power of Chaos AJAX.
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+dependencies[] = ctools
+core = 7.x
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.install b/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.install
new file mode 100644
index 000000000..04325dbf4
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.install
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Implementation of hook_install()
+ */
+function ctools_ajax_sample_install() {
+
+}
+
+/**
+ * Implementation of hook_uninstall()
+ */
+function ctools_ajax_sample_uninstall() {
+
+}
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.module b/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.module
new file mode 100644
index 000000000..4638ac34d
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.module
@@ -0,0 +1,756 @@
+<?php
+
+/**
+ * @file
+ * Sample AJAX functionality so people can see some of the CTools AJAX
+ * features in use.
+ */
+
+// ---------------------------------------------------------------------------
+// Drupal hooks.
+
+/**
+ * Implementation of hook_menu()
+ */
+function ctools_ajax_sample_menu() {
+ $items['ctools_ajax_sample'] = array(
+ 'title' => 'Chaos Tools AJAX Demo',
+ 'page callback' => 'ctools_ajax_sample_page',
+ 'access callback' => TRUE,
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ $items['ctools_ajax_sample/simple_form'] = array(
+ 'title' => 'Simple Form',
+ 'page callback' => 'ctools_ajax_simple_form',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['ctools_ajax_sample/%ctools_js/hello'] = array(
+ 'title' => 'Hello World',
+ 'page callback' => 'ctools_ajax_sample_hello',
+ 'page arguments' => array(1),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['ctools_ajax_sample/%ctools_js/tablenix/%'] = array(
+ 'title' => 'Hello World',
+ 'page callback' => 'ctools_ajax_sample_tablenix',
+ 'page arguments' => array(1, 3),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['ctools_ajax_sample/%ctools_js/login'] = array(
+ 'title' => 'Login',
+ 'page callback' => 'ctools_ajax_sample_login',
+ 'page arguments' => array(1),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['ctools_ajax_sample/%ctools_js/animal'] = array(
+ 'title' => 'Animal',
+ 'page callback' => 'ctools_ajax_sample_animal',
+ 'page arguments' => array(1),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['ctools_ajax_sample/%ctools_js/login/%'] = array(
+ 'title' => 'Post-Login Action',
+ 'page callback' => 'ctools_ajax_sample_login_success',
+ 'page arguments' => array(1, 3),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['ctools_ajax_sample/jumped'] = array(
+ 'title' => 'Successful Jumping',
+ 'page callback' => 'ctools_ajax_sample_jump_menu_page',
+ 'access callback' => TRUE,
+ 'type' => MENU_NORMAL_ITEM,
+ );
+
+ return $items;
+}
+
+function ctools_ajax_simple_form() {
+ ctools_include('content');
+ ctools_include('context');
+ $node = node_load(1);
+ $context = ctools_context_create('node', $node);
+ $context = array('context_node_1' => $context);
+ return ctools_content_render('node_comment_form', 'node_comment_form', ctools_ajax_simple_form_pane(), array(), array(), $context);
+}
+
+function ctools_ajax_simple_form_pane() {
+ $configuration = array(
+ 'anon_links' => 0,
+ 'context' => 'context_node_1',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ );
+ return $configuration;
+}
+
+/**
+ * Implementation of hook_theme()
+ *
+ * Render some basic output for this module.
+ */
+function ctools_ajax_sample_theme() {
+ return array(
+ // Sample theme functions.
+ 'ctools_ajax_sample_container' => array(
+ 'arguments' => array('content' => NULL),
+ ),
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Page callbacks
+
+/**
+ * Page callback to display links and render a container for AJAX stuff.
+ */
+function ctools_ajax_sample_page() {
+ global $user;
+
+ // Include the CTools tools that we need.
+ ctools_include('ajax');
+ ctools_include('modal');
+
+ // Add CTools' javascript to the page.
+ ctools_modal_add_js();
+
+ // Create our own javascript that will be used to theme a modal.
+ $sample_style = array(
+ 'ctools-sample-style' => array(
+ 'modalSize' => array(
+ 'type' => 'fixed',
+ 'width' => 500,
+ 'height' => 300,
+ 'addWidth' => 20,
+ 'addHeight' => 15,
+ ),
+ 'modalOptions' => array(
+ 'opacity' => .5,
+ 'background-color' => '#000',
+ ),
+ 'animation' => 'fadeIn',
+ 'modalTheme' => 'CToolsSampleModal',
+ 'throbber' => theme('image', array('path' => ctools_image_path('ajax-loader.gif', 'ctools_ajax_sample'), 'alt' => t('Loading...'), 'title' => t('Loading'))),
+ ),
+ );
+
+ drupal_add_js($sample_style, 'setting');
+
+ // Since we have our js, css and images in well-known named directories,
+ // CTools makes it easy for us to just use them without worrying about
+ // using drupal_get_path() and all that ugliness.
+ ctools_add_js('ctools-ajax-sample', 'ctools_ajax_sample');
+ ctools_add_css('ctools-ajax-sample', 'ctools_ajax_sample');
+
+ // Create a list of clickable links.
+ $links = array();
+
+ // Only show login links to the anonymous user.
+ if ($user->uid == 0) {
+ $links[] = ctools_modal_text_button(t('Modal Login (default style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'));
+
+ // The extra class points to the info in ctools-sample-style which we added
+ // to the settings, prefixed with 'ctools-modal'.
+ $links[] = ctools_modal_text_button(t('Modal Login (custom style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'), 'ctools-modal-ctools-sample-style');
+ }
+
+ // Four ways to do our animal picking wizard.
+ $button_form = ctools_ajax_sample_ajax_button_form();
+ $links[] = l(t('Wizard (no modal)'), 'ctools_ajax_sample/nojs/animal');
+ $links[] = ctools_modal_text_button(t('Wizard (default modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'));
+ $links[] = ctools_modal_text_button(t('Wizard (custom modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'), 'ctools-modal-ctools-sample-style');
+ $links[] = drupal_render($button_form);
+
+ $links[] = ctools_ajax_text_button(t('Hello world!'), "ctools_ajax_sample/nojs/hello", t('Replace text with "hello world"'));
+
+ $output = theme('item_list', array('items' => $links, 'title' => t('Actions')));
+
+ // This container will have data AJAXed into it.
+ $output .= theme('ctools_ajax_sample_container', array('content' => '<h1>' . t('Sample Content') . '</h1>'));
+
+ // Create a table that we can have data removed from via AJAX.
+ $header = array(t('Row'), t('Content'), t('Actions'));
+ $rows = array();
+ for($i = 1; $i < 11; $i++) {
+ $rows[] = array(
+ 'class' => array('ajax-sample-row-'. $i),
+ 'data' => array(
+ $i,
+ md5($i),
+ ctools_ajax_text_button("remove", "ctools_ajax_sample/nojs/tablenix/$i", t('Delete this row')),
+ ),
+ );
+ }
+
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, array('class' => array('ajax-sample-table'))));
+
+ // Show examples of ctools javascript widgets
+ $output .= '<h2>'. t('CTools Javascript Widgets') .'</h2>';
+
+ // Create a drop down menu
+ $links = array();
+ $links[] = array('title' => t('Link 1'), 'href' => $_GET['q']);
+ $links[] = array('title' => t('Link 2'), 'href' => $_GET['q']);
+ $links[] = array('title' => t('Link 3'), 'href' => $_GET['q']);
+
+ $output .= '<h3>' . t('Drop Down Menu') . '</h3>';
+ $output .= theme('ctools_dropdown', array('title' => t('Click to Drop Down'), 'links' => $links));
+
+ // Create a collapsible div
+ $handle = t('Click to Collapse');
+ $content = 'Nulla ligula ante, aliquam at adipiscing egestas, varius vel arcu. Etiam laoreet elementum mi vel consequat. Etiam scelerisque lorem vel neque consequat quis bibendum libero congue. Nulla facilisi. Mauris a elit a leo feugiat porta. Phasellus placerat cursus est vitae elementum.';
+ $output .= '<h3>'. t('Collapsible Div') .'</h3>';
+ $output .= theme('ctools_collapsible', array('handle' => $handle, 'content' => $content, 'collapsed' => FALSE));
+
+ // Create a jump menu
+ ctools_include('jump-menu');
+ $form = drupal_get_form('ctools_ajax_sample_jump_menu_form');
+ $output .= '<h3>'. t('Jump Menu') .'</h3>';
+ $output .= drupal_render($form);
+
+ return array('markup' => array('#markup' => $output));
+}
+
+/**
+ * Returns a "take it all over" hello world style request.
+ */
+function ctools_ajax_sample_hello($js = NULL) {
+ $output = '<h1>' . t('Hello World') . '</h1>';
+ if ($js) {
+ ctools_include('ajax');
+ $commands = array();
+ $commands[] = ajax_command_html('#ctools-sample', $output);
+ print ajax_render($commands); // this function exits.
+ exit;
+ }
+ else {
+ return $output;
+ }
+}
+
+/**
+ * Nix a row from a table and restripe.
+ */
+function ctools_ajax_sample_tablenix($js, $row) {
+ if (!$js) {
+ // We don't support degrading this from js because we're not
+ // using the server to remember the state of the table.
+ return MENU_ACCESS_DENIED;
+ }
+ ctools_include('ajax');
+
+ $commands = array();
+ $commands[] = ajax_command_remove("tr.ajax-sample-row-$row");
+ $commands[] = ajax_command_restripe("table.ajax-sample-table");
+ print ajax_render($commands);
+ exit;
+}
+
+/**
+ * A modal login callback.
+ */
+function ctools_ajax_sample_login($js = NULL) {
+ // Fall back if $js is not set.
+ if (!$js) {
+ return drupal_get_form('user_login');
+ }
+
+ ctools_include('modal');
+ ctools_include('ajax');
+ $form_state = array(
+ 'title' => t('Login'),
+ 'ajax' => TRUE,
+ );
+ $output = ctools_modal_form_wrapper('user_login', $form_state);
+ if (!empty($form_state['executed'])) {
+ // We'll just overwrite the form output if it was successful.
+ $output = array();
+ $inplace = ctools_ajax_text_button(t('remain here'), 'ctools_ajax_sample/nojs/login/inplace', t('Go to your account'));
+ $account = ctools_ajax_text_button(t('your account'), 'ctools_ajax_sample/nojs/login/user', t('Go to your account'));
+ $output[] = ctools_modal_command_display(t('Login Success'), '<div class="modal-message">Login successful. You can now choose whether to '. $inplace .', or go to '. $account.'.</div>');
+ }
+ print ajax_render($output);
+ exit;
+}
+
+/**
+ * Post-login processor: should we go to the user account or stay in place?
+ */
+function ctools_ajax_sample_login_success($js, $action) {
+ if (!$js) {
+ // we should never be here out of ajax context
+ return MENU_NOT_FOUND;
+ }
+
+ ctools_include('ajax');
+ ctools_add_js('ajax-responder');
+ $commands = array();
+ if ($action == 'inplace') {
+ // stay here
+ $commands[] = ctools_ajax_command_reload();
+ }
+ else {
+ // bounce bounce
+ $commands[] = ctools_ajax_command_redirect('user');
+ }
+ print ajax_render($commands);
+ exit;
+}
+
+/**
+ * A modal login callback.
+ */
+function ctools_ajax_sample_animal($js = NULL, $step = NULL) {
+ if ($js) {
+ ctools_include('modal');
+ ctools_include('ajax');
+ }
+
+ $form_info = array(
+ 'id' => 'animals',
+ 'path' => "ctools_ajax_sample/" . ($js ? 'ajax' : 'nojs') . "/animal/%step",
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'show cancel' => TRUE,
+ 'show return' => FALSE,
+ 'next callback' => 'ctools_ajax_sample_wizard_next',
+ 'finish callback' => 'ctools_ajax_sample_wizard_finish',
+ 'cancel callback' => 'ctools_ajax_sample_wizard_cancel',
+ // this controls order, as well as form labels
+ 'order' => array(
+ 'start' => t('Choose animal'),
+ ),
+ // here we map a step to a form id.
+ 'forms' => array(
+ // e.g. this for the step at wombat/create
+ 'start' => array(
+ 'form id' => 'ctools_ajax_sample_start'
+ ),
+ ),
+ );
+
+ // We're not using any real storage here, so we're going to set our
+ // object_id to 1. When using wizard forms, id management turns
+ // out to be one of the hardest parts. Editing an object with an id
+ // is easy, but new objects don't usually have ids until somewhere
+ // in creation.
+ //
+ // We skip all this here by just using an id of 1.
+
+ $object_id = 1;
+
+ if (empty($step)) {
+ // We reset the form when $step is NULL because that means they have
+ // for whatever reason started over.
+ ctools_ajax_sample_cache_clear($object_id);
+ $step = 'start';
+ }
+
+ // This automatically gets defaults if there wasn't anything saved.
+ $object = ctools_ajax_sample_cache_get($object_id);
+
+ $animals = ctools_ajax_sample_animals();
+
+ // Make sure we can't somehow accidentally go to an invalid animal.
+ if (empty($animals[$object->type])) {
+ $object->type = 'unknown';
+ }
+
+ // Now that we have our object, dynamically add the animal's form.
+ if ($object->type == 'unknown') {
+ // If they haven't selected a type, add a form that doesn't exist yet.
+ $form_info['order']['unknown'] = t('Configure animal');
+ $form_info['forms']['unknown'] = array('form id' => 'nothing');
+ }
+ else {
+ // Add the selected animal to the order so that it shows up properly in the trail.
+ $form_info['order'][$object->type] = $animals[$object->type]['config title'];
+ }
+
+ // Make sure all animals forms are represented so that the next stuff can
+ // work correctly:
+ foreach ($animals as $id => $animal) {
+ $form_info['forms'][$id] = array('form id' => $animals[$id]['form']);
+ }
+
+ $form_state = array(
+ 'ajax' => $js,
+ // Put our object and ID into the form state cache so we can easily find
+ // it.
+ 'object_id' => $object_id,
+ 'object' => &$object,
+ );
+
+ // Send this all off to our form. This is like drupal_get_form only wizardy.
+ ctools_include('wizard');
+ $form = ctools_wizard_multistep_form($form_info, $step, $form_state);
+ $output = drupal_render($form);
+
+ if ($output === FALSE || !empty($form_state['complete'])) {
+ // This creates a string based upon the animal and its setting using
+ // function indirection.
+ $animal = $animals[$object->type]['output']($object);
+ }
+
+ // If $output is FALSE, there was no actual form.
+ if ($js) {
+ // If javascript is active, we have to use a render array.
+ $commands = array();
+ if ($output === FALSE || !empty($form_state['complete'])) {
+ // Dismiss the modal.
+ $commands[] = ajax_command_html('#ctools-sample', $animal);
+ $commands[] = ctools_modal_command_dismiss();
+ }
+ else if (!empty($form_state['cancel'])) {
+ // If cancelling, return to the activity.
+ $commands[] = ctools_modal_command_dismiss();
+ }
+ else {
+ $commands = ctools_modal_form_render($form_state, $output);
+ }
+ print ajax_render($commands);
+ exit;
+ }
+ else {
+ if ($output === FALSE || !empty($form_state['complete'])) {
+ return $animal;
+ }
+ else if (!empty($form_state['cancel'])) {
+ drupal_goto('ctools_ajax_sample');
+ }
+ else {
+ return $output;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Themes
+
+/**
+ * Theme function for main rendered output.
+ */
+function theme_ctools_ajax_sample_container($vars) {
+ $output = '<div id="ctools-sample">';
+ $output .= $vars['content'];
+ $output .= '</div>';
+
+ return $output;
+}
+
+// ---------------------------------------------------------------------------
+// Stuff needed for our little wizard.
+
+/**
+ * Get a list of our animals and associated forms.
+ *
+ * What we're doing is making it easy to add more animals in just one place,
+ * which is often how it will work in the real world. If using CTools, what
+ * you would probably really have, here, is a set of plugins for each animal.
+ */
+function ctools_ajax_sample_animals() {
+ return array(
+ 'sheep' => array(
+ 'title' => t('Sheep'),
+ 'config title' => t('Configure sheep'),
+ 'form' => 'ctools_ajax_sample_configure_sheep',
+ 'output' => 'ctools_ajax_sample_show_sheep',
+ ),
+ 'lizard' => array(
+ 'title' => t('Lizard'),
+ 'config title' => t('Configure lizard'),
+ 'form' => 'ctools_ajax_sample_configure_lizard',
+ 'output' => 'ctools_ajax_sample_show_lizard',
+ ),
+ 'raptor' => array(
+ 'title' => t('Raptor'),
+ 'config title' => t('Configure raptor'),
+ 'form' => 'ctools_ajax_sample_configure_raptor',
+ 'output' => 'ctools_ajax_sample_show_raptor',
+ ),
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Wizard caching helpers.
+
+/**
+ * Store our little cache so that we can retain data from form to form.
+ */
+function ctools_ajax_sample_cache_set($id, $object) {
+ ctools_include('object-cache');
+ ctools_object_cache_set('ctools_ajax_sample', $id, $object);
+}
+
+/**
+ * Get the current object from the cache, or default.
+ */
+function ctools_ajax_sample_cache_get($id) {
+ ctools_include('object-cache');
+ $object = ctools_object_cache_get('ctools_ajax_sample', $id);
+ if (!$object) {
+ // Create a default object.
+ $object = new stdClass;
+ $object->type = 'unknown';
+ $object->name = '';
+ }
+
+ return $object;
+}
+
+/**
+ * Clear the wizard cache.
+ */
+function ctools_ajax_sample_cache_clear($id) {
+ ctools_include('object-cache');
+ ctools_object_cache_clear('ctools_ajax_sample', $id);
+}
+
+// ---------------------------------------------------------------------------
+// Wizard in-between helpers; what to do between or after forms.
+
+/**
+ * Handle the 'next' click on the add/edit pane form wizard.
+ *
+ * All we need to do is store the updated pane in the cache.
+ */
+function ctools_ajax_sample_wizard_next(&$form_state) {
+ ctools_ajax_sample_cache_set($form_state['object_id'], $form_state['object']);
+}
+
+/**
+ * Handle the 'finish' click on the add/edit pane form wizard.
+ *
+ * All we need to do is set a flag so the return can handle adding
+ * the pane.
+ */
+function ctools_ajax_sample_wizard_finish(&$form_state) {
+ $form_state['complete'] = TRUE;
+}
+
+/**
+ * Handle the 'cancel' click on the add/edit pane form wizard.
+ */
+function ctools_ajax_sample_wizard_cancel(&$form_state) {
+ $form_state['cancel'] = TRUE;
+}
+
+// ---------------------------------------------------------------------------
+// Wizard forms for our simple info collection wizard.
+
+/**
+ * Wizard start form. Choose an animal.
+ */
+function ctools_ajax_sample_start($form, &$form_state) {
+ $form_state['title'] = t('Choose animal');
+
+ $animals = ctools_ajax_sample_animals();
+ foreach ($animals as $id => $animal) {
+ $options[$id] = $animal['title'];
+ }
+
+ $form['type'] = array(
+ '#title' => t('Choose your animal'),
+ '#type' => 'radios',
+ '#options' => $options,
+ '#default_value' => $form_state['object']->type,
+ '#required' => TRUE,
+ );
+
+ return $form;
+}
+
+/**
+ * They have selected a sheep. Set it.
+ */
+function ctools_ajax_sample_start_submit(&$form, &$form_state) {
+ $form_state['object']->type = $form_state['values']['type'];
+ // Override where to go next based on the animal selected.
+ $form_state['clicked_button']['#next'] = $form_state['values']['type'];
+}
+
+/**
+ * Wizard form to configure your sheep.
+ */
+function ctools_ajax_sample_configure_sheep($form, &$form_state) {
+ $form_state['title'] = t('Configure sheep');
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name your sheep'),
+ '#default_value' => $form_state['object']->name,
+ '#required' => TRUE,
+ );
+
+ $form['sheep'] = array(
+ '#title' => t('What kind of sheep'),
+ '#type' => 'radios',
+ '#options' => array(
+ t('Wensleydale') => t('Wensleydale'),
+ t('Merino') => t('Merino'),
+ t('Corriedale') => t('Coriedale'),
+ ),
+ '#default_value' => !empty($form_state['object']->sheep) ? $form_state['object']->sheep : '',
+ '#required' => TRUE,
+ );
+ return $form;
+}
+
+/**
+ * Submit the sheep and store the values from the form.
+ */
+function ctools_ajax_sample_configure_sheep_submit(&$form, &$form_state) {
+ $form_state['object']->name = $form_state['values']['name'];
+ $form_state['object']->sheep = $form_state['values']['sheep'];
+}
+
+/**
+ * Provide some output for our sheep.
+ */
+function ctools_ajax_sample_show_sheep($object) {
+ return t('You have a @type sheep named "@name".', array(
+ '@type' => $object->sheep,
+ '@name' => $object->name,
+ ));
+}
+
+/**
+ * Wizard form to configure your lizard.
+ */
+function ctools_ajax_sample_configure_lizard($form, &$form_state) {
+ $form_state['title'] = t('Configure lizard');
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name your lizard'),
+ '#default_value' => $form_state['object']->name,
+ '#required' => TRUE,
+ );
+
+ $form['lizard'] = array(
+ '#title' => t('Venomous'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($form_state['object']->lizard),
+ );
+ return $form;
+}
+
+/**
+ * Submit the lizard and store the values from the form.
+ */
+function ctools_ajax_sample_configure_lizard_submit(&$form, &$form_state) {
+ $form_state['object']->name = $form_state['values']['name'];
+ $form_state['object']->lizard = $form_state['values']['lizard'];
+}
+
+/**
+ * Provide some output for our raptor.
+ */
+function ctools_ajax_sample_show_lizard($object) {
+ return t('You have a @type lizard named "@name".', array(
+ '@type' => empty($object->lizard) ? t('non-venomous') : t('venomous'),
+ '@name' => $object->name,
+ ));
+}
+
+/**
+ * Wizard form to configure your raptor.
+ */
+function ctools_ajax_sample_configure_raptor($form, &$form_state) {
+ $form_state['title'] = t('Configure raptor');
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name your raptor'),
+ '#default_value' => $form_state['object']->name,
+ '#required' => TRUE,
+ );
+
+ $form['raptor'] = array(
+ '#title' => t('What kind of raptor'),
+ '#type' => 'radios',
+ '#options' => array(
+ t('Eagle') => t('Eagle'),
+ t('Hawk') => t('Hawk'),
+ t('Owl') => t('Owl'),
+ t('Buzzard') => t('Buzzard'),
+ ),
+ '#default_value' => !empty($form_state['object']->raptor) ? $form_state['object']->raptor : '',
+ '#required' => TRUE,
+ );
+
+ $form['domesticated'] = array(
+ '#title' => t('Domesticated'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($form_state['object']->domesticated),
+ );
+ return $form;
+}
+
+/**
+ * Submit the raptor and store the values from the form.
+ */
+function ctools_ajax_sample_configure_raptor_submit(&$form, &$form_state) {
+ $form_state['object']->name = $form_state['values']['name'];
+ $form_state['object']->raptor = $form_state['values']['raptor'];
+ $form_state['object']->domesticated = $form_state['values']['domesticated'];
+}
+
+/**
+ * Provide some output for our raptor.
+ */
+function ctools_ajax_sample_show_raptor($object) {
+ return t('You have a @type @raptor named "@name".', array(
+ '@type' => empty($object->domesticated) ? t('wild') : t('domesticated'),
+ '@raptor' => $object->raptor,
+ '@name' => $object->name,
+ ));
+}
+
+/**
+ * Helper function to provide a sample jump menu form
+ */
+function ctools_ajax_sample_jump_menu_form() {
+ $url = url('ctools_ajax_sample/jumped');
+ $form_state = array();
+ $form = ctools_jump_menu(array(), $form_state, array($url => t('Jump!')), array());
+ return $form;
+}
+
+/**
+ * Provide a message to the user that the jump menu worked
+ */
+function ctools_ajax_sample_jump_menu_page() {
+ $return_link = l(t('Return to the examples page.'), 'ctools_ajax_sample');
+ $output = t('You successfully jumped! !return_link', array('!return_link' => $return_link));
+ return $output;
+}
+
+/**
+ * Provide a form for an example ajax modal button
+ */
+function ctools_ajax_sample_ajax_button_form() {
+ $form = array();
+
+ $form['url'] = array(
+ '#type' => 'hidden',
+ // The name of the class is the #id of $form['ajax_button'] with "-url"
+ // suffix.
+ '#attributes' => array('class' => array('ctools-ajax-sample-button-url')),
+ '#value' => url('ctools_ajax_sample/nojs/animal'),
+ );
+
+ $form['ajax_button'] = array(
+ '#type' => 'button',
+ '#value' => 'Wizard (button modal)',
+ '#attributes' => array('class' => array('ctools-use-modal')),
+ '#id' => 'ctools-ajax-sample-button',
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/images/ajax-loader.gif b/sites/all/modules/ctools/ctools_ajax_sample/images/ajax-loader.gif
new file mode 100644
index 000000000..d84f65378
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/images/ajax-loader.gif
Binary files differ
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/images/loading-large.gif b/sites/all/modules/ctools/ctools_ajax_sample/images/loading-large.gif
new file mode 100644
index 000000000..1c72ebb55
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/images/loading-large.gif
Binary files differ
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/images/loading.gif b/sites/all/modules/ctools/ctools_ajax_sample/images/loading.gif
new file mode 100644
index 000000000..dc21df183
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/images/loading.gif
Binary files differ
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/images/popups-border.png b/sites/all/modules/ctools/ctools_ajax_sample/images/popups-border.png
new file mode 100644
index 000000000..ba939f899
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/images/popups-border.png
Binary files differ
diff --git a/sites/all/modules/ctools/ctools_ajax_sample/js/ctools-ajax-sample.js b/sites/all/modules/ctools/ctools_ajax_sample/js/ctools-ajax-sample.js
new file mode 100644
index 000000000..0cbfc871a
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_ajax_sample/js/ctools-ajax-sample.js
@@ -0,0 +1,42 @@
+/**
+* Provide the HTML to create the modal dialog.
+*/
+Drupal.theme.prototype.CToolsSampleModal = function () {
+ var html = '';
+
+ html += '<div id="ctools-modal" class="popups-box">';
+ html += ' <div class="ctools-modal-content ctools-sample-modal-content">';
+ html += ' <table cellpadding="0" cellspacing="0" id="ctools-face-table">';
+ html += ' <tr>';
+ html += ' <td class="popups-tl popups-border"></td>';
+ html += ' <td class="popups-t popups-border"></td>';
+ html += ' <td class="popups-tr popups-border"></td>';
+ html += ' </tr>';
+ html += ' <tr>';
+ html += ' <td class="popups-cl popups-border"></td>';
+ html += ' <td class="popups-c" valign="top">';
+ html += ' <div class="popups-container">';
+ html += ' <div class="modal-header popups-title">';
+ html += ' <span id="modal-title" class="modal-title"></span>';
+ html += ' <span class="popups-close"><a class="close" href="#">' + Drupal.CTools.Modal.currentSettings.closeText + '</a></span>';
+ html += ' <div class="clear-block"></div>';
+ html += ' </div>';
+ html += ' <div class="modal-scroll"><div id="modal-content" class="modal-content popups-body"></div></div>';
+ html += ' <div class="popups-buttons"></div>'; //Maybe someday add the option for some specific buttons.
+ html += ' <div class="popups-footer"></div>'; //Maybe someday add some footer.
+ html += ' </div>';
+ html += ' </td>';
+ html += ' <td class="popups-cr popups-border"></td>';
+ html += ' </tr>';
+ html += ' <tr>';
+ html += ' <td class="popups-bl popups-border"></td>';
+ html += ' <td class="popups-b popups-border"></td>';
+ html += ' <td class="popups-br popups-border"></td>';
+ html += ' </tr>';
+ html += ' </table>';
+ html += ' </div>';
+ html += '</div>';
+
+ return html;
+
+}
diff --git a/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info b/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info
new file mode 100644
index 000000000..185d8b640
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info
@@ -0,0 +1,13 @@
+name = Custom content panes
+description = Create custom, exportable, reusable content panes for applications like Panels.
+core = 7.x
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+dependencies[] = ctools
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.install b/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.install
new file mode 100644
index 000000000..b4512f2a4
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.install
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Schema for CTools custom content.
+ */
+function ctools_custom_content_schema() {
+ return ctools_custom_content_schema_1();
+}
+
+function ctools_custom_content_schema_1() {
+ $schema = array();
+
+ $schema['ctools_custom_content'] = array(
+ 'description' => 'Contains exportable customized content for this site.',
+ 'export' => array(
+ 'identifier' => 'content',
+ 'bulk export' => TRUE,
+ 'primary key' => 'cid',
+ 'api' => array(
+ 'owner' => 'ctools_custom_content',
+ 'api' => 'ctools_content',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ 'create callback' => 'ctools_content_type_new',
+ ),
+ 'fields' => array(
+ 'cid' => array(
+ 'type' => 'serial',
+ 'description' => 'A database primary key to ensure uniqueness',
+ 'not null' => TRUE,
+ 'no export' => TRUE,
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Unique ID for this content. Used to identify it programmatically.',
+ ),
+ 'admin_title' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Administrative title for this content.',
+ ),
+ 'admin_description' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Administrative description for this content.',
+ 'object default' => '',
+ ),
+ 'category' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Administrative category for this content.',
+ ),
+ 'settings' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Serialized settings for the actual content to be used',
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ ),
+ 'primary key' => array('cid'),
+ );
+
+ return $schema;
+}
diff --git a/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.module b/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.module
new file mode 100644
index 000000000..3e316a918
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.module
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * ctools_custom_content module
+ *
+ * This module allows styles to be created and managed on behalf of modules
+ * that implement styles.
+ *
+ * The ctools_custom_content tool allows recolorable styles to be created via a miniature
+ * scripting language. Panels utilizes this to allow administrators to add
+ * styles directly to any panel display.
+ */
+
+/**
+ * Implementation of hook_permission()
+ */
+function ctools_custom_content_permission() {
+ return array(
+ 'administer custom content' => array(
+ 'title' => t('Administer custom content'),
+ 'description' => t('Add, edit and delete CTools custom stored custom content'),
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_ctools_plugin_directory() to let the system know
+ * we implement task and task_handler plugins.
+ */
+function ctools_custom_content_ctools_plugin_directory($module, $plugin) {
+ // Most of this module is implemented as an export ui plugin, and the
+ // rest is in ctools/includes/ctools_custom_content.inc
+ if ($module == 'ctools' && $plugin == 'export_ui') {
+ return 'plugins/' . $plugin;
+ }
+}
+
+/**
+ * Implements hook_get_pane_links_alter().
+ */
+function ctools_custom_content_get_pane_links_alter(&$links, $pane, $content_type) {
+ if ($pane->type == 'custom') {
+ if(!isset($pane->configuration['name'])) {
+ $name_of_pane = $pane->subtype;
+ }
+ else {
+ $name_of_pane = $pane->configuration['name'];
+ }
+
+ $links['top']['edit_custom_content'] = array(
+ 'title' => t('Edit custom content pane'),
+ 'href' => url('admin/structure/ctools-content/list/' . $name_of_pane . '/edit', array('absolute' => TRUE)),
+ 'attributes' => array('target' => array('_blank')),
+ );
+ }
+}
+
+/**
+ * Create callback for creating a new CTools custom content type.
+ *
+ * This ensures we get proper defaults from the plugin for its settings.
+ */
+function ctools_content_type_new($set_defaults) {
+ $item = ctools_export_new_object('ctools_custom_content', $set_defaults);
+ ctools_include('content');
+ $plugin = ctools_get_content_type('custom');
+ $item->settings = ctools_content_get_defaults($plugin, array());
+ return $item;
+}
+
+/**
+ * Implementation of hook_panels_dashboard_blocks().
+ *
+ * Adds page information to the Panels dashboard.
+ */
+function ctools_custom_content_panels_dashboard_blocks(&$vars) {
+ $vars['links']['ctools_custom_content'] = array(
+ 'title' => l(t('Custom content'), 'admin/structure/ctools-content/add'),
+ 'description' => t('Custom content panes are basic HTML you enter that can be reused in all of your panels.'),
+ );
+
+ // Load all mini panels and their displays.
+ ctools_include('export');
+ $items = ctools_export_crud_load_all('ctools_custom_content');
+ $count = 0;
+ $rows = array();
+
+ foreach ($items as $item) {
+ $rows[] = array(
+ check_plain($item->admin_title),
+ array(
+ 'data' => l(t('Edit'), "admin/structure/ctools-content/list/$item->name/edit"),
+ 'class' => 'links',
+ ),
+ );
+
+ // Only show 10.
+ if (++$count >= 10) {
+ break;
+ }
+ }
+
+ if ($rows) {
+ $content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
+ }
+ else {
+ $content = '<p>' . t('There are no custom content panes.') . '</p>';
+ }
+
+ $vars['blocks']['ctools_custom_content'] = array(
+ 'title' => t('Manage custom content'),
+ 'link' => l(t('Go to list'), 'admin/structure/ctools-content'),
+ 'content' => $content,
+ 'class' => 'dashboard-content',
+ 'section' => 'right',
+ );
+}
diff --git a/sites/all/modules/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc b/sites/all/modules/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc
new file mode 100644
index 000000000..467dc5804
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc
@@ -0,0 +1,20 @@
+<?php
+
+$plugin = array(
+ 'schema' => 'ctools_custom_content',
+ 'access' => 'administer custom content',
+
+ 'menu' => array(
+ 'menu item' => 'ctools-content',
+ 'menu title' => 'Custom content panes',
+ 'menu description' => 'Add, edit or delete custom content panes.',
+ ),
+
+ 'title singular' => t('content pane'),
+ 'title singular proper' => t('Content pane'),
+ 'title plural' => t('content panes'),
+ 'title plural proper' => t('Content panes'),
+
+ 'handler' => 'ctools_custom_content_ui',
+);
+
diff --git a/sites/all/modules/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php b/sites/all/modules/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php
new file mode 100644
index 000000000..56fe4b214
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php
@@ -0,0 +1,129 @@
+<?php
+
+class ctools_custom_content_ui extends ctools_export_ui {
+
+ function edit_form(&$form, &$form_state) {
+ // Correct for an error that came in because filter format changed.
+ if (is_array($form_state['item']->settings['body'])) {
+ $form_state['item']->settings['format'] = $form_state['item']->settings['body']['format'];
+ $form_state['item']->settings['body'] = $form_state['item']->settings['body']['value'];
+ }
+ parent::edit_form($form, $form_state);
+
+ $form['category'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Category'),
+ '#description' => t('What category this content should appear in. If left blank the category will be "Miscellaneous".'),
+ '#default_value' => $form_state['item']->category,
+ );
+
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $form_state['item']->settings['title'],
+ '#title' => t('Title'),
+ );
+
+ $form['body'] = array(
+ '#type' => 'text_format',
+ '#title' => t('Body'),
+ '#default_value' => $form_state['item']->settings['body'],
+ '#format' => $form_state['item']->settings['format'],
+ );
+
+ $form['substitute'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use context keywords'),
+ '#description' => t('If checked, context keywords will be substituted in this content.'),
+ '#default_value' => !empty($form_state['item']->settings['substitute']),
+ );
+ }
+
+ function edit_form_submit(&$form, &$form_state) {
+ parent::edit_form_submit($form, $form_state);
+
+ // Since items in our settings are not in the schema, we have to do these manually:
+ $form_state['item']->settings['title'] = $form_state['values']['title'];
+ $form_state['item']->settings['body'] = $form_state['values']['body']['value'];
+ $form_state['item']->settings['format'] = $form_state['values']['body']['format'];
+ $form_state['item']->settings['substitute'] = $form_state['values']['substitute'];
+ }
+
+ function list_form(&$form, &$form_state) {
+ parent::list_form($form, $form_state);
+
+ $options = array('all' => t('- All -'));
+ foreach ($this->items as $item) {
+ $options[$item->category] = $item->category;
+ }
+
+ $form['top row']['category'] = array(
+ '#type' => 'select',
+ '#title' => t('Category'),
+ '#options' => $options,
+ '#default_value' => 'all',
+ '#weight' => -10,
+ );
+ }
+
+ function list_filter($form_state, $item) {
+ if ($form_state['values']['category'] != 'all' && $form_state['values']['category'] != $item->category) {
+ return TRUE;
+ }
+
+ return parent::list_filter($form_state, $item);
+ }
+
+ function list_sort_options() {
+ return array(
+ 'disabled' => t('Enabled, title'),
+ 'title' => t('Title'),
+ 'name' => t('Name'),
+ 'category' => t('Category'),
+ 'storage' => t('Storage'),
+ );
+ }
+
+ function list_build_row($item, &$form_state, $operations) {
+ // Set up sorting
+ switch ($form_state['values']['order']) {
+ case 'disabled':
+ $this->sorts[$item->name] = empty($item->disabled) . $item->admin_title;
+ break;
+ case 'title':
+ $this->sorts[$item->name] = $item->admin_title;
+ break;
+ case 'name':
+ $this->sorts[$item->name] = $item->name;
+ break;
+ case 'category':
+ $this->sorts[$item->name] = $item->category;
+ break;
+ case 'storage':
+ $this->sorts[$item->name] = $item->type . $item->admin_title;
+ break;
+ }
+
+ $ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
+
+ $this->rows[$item->name] = array(
+ 'data' => array(
+ array('data' => check_plain($item->name), 'class' => array('ctools-export-ui-name')),
+ array('data' => check_plain($item->admin_title), 'class' => array('ctools-export-ui-title')),
+ array('data' => check_plain($item->category), 'class' => array('ctools-export-ui-category')),
+ array('data' => $ops, 'class' => array('ctools-export-ui-operations')),
+ ),
+ 'title' => check_plain($item->admin_description),
+ 'class' => array(!empty($item->disabled) ? 'ctools-export-ui-disabled' : 'ctools-export-ui-enabled'),
+ );
+ }
+
+ function list_table_header() {
+ return array(
+ array('data' => t('Name'), 'class' => array('ctools-export-ui-name')),
+ array('data' => t('Title'), 'class' => array('ctools-export-ui-title')),
+ array('data' => t('Category'), 'class' => array('ctools-export-ui-category')),
+ array('data' => t('Operations'), 'class' => array('ctools-export-ui-operations')),
+ );
+ }
+
+}
diff --git a/sites/all/modules/ctools/ctools_plugin_example/README.txt b/sites/all/modules/ctools/ctools_plugin_example/README.txt
new file mode 100644
index 000000000..42edcdc90
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/README.txt
@@ -0,0 +1,14 @@
+
+The CTools Plugin Example is an example for developers of how to CTools
+access, argument, content type, context, and relationship plugins.
+
+There are a number of ways to profit from this:
+
+1. The code itself intends to be as simple and self-explanatory as possible.
+ Nothing fancy is attempted: It's just trying to use the plugin API to show
+ how it can be used.
+
+2. There is a sample panel. You can access it at /ctools_plugin_example/xxxx
+ to see how it works.
+
+3. There is Advanced Help at admin/advanced_help/ctools_plugin_example. \ No newline at end of file
diff --git a/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info
new file mode 100644
index 000000000..d378641ef
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info
@@ -0,0 +1,16 @@
+name = Chaos Tools (CTools) Plugin Example
+description = Shows how an external module can provide ctools plugins (for Panels, etc.).
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+dependencies[] = ctools
+dependencies[] = panels
+dependencies[] = page_manager
+dependencies[] = advanced_help
+core = 7.x
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.module b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.module
new file mode 100644
index 000000000..01d533826
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.module
@@ -0,0 +1,94 @@
+<?php
+
+/*
+ * @file
+ *
+ * Working sample module to demonstrate CTools 3 plugins
+ *
+ * This sample module is only intended to demonstrate how external modules can
+ * provide ctools plugins. There is no useful functionality, and it's only
+ * intended for developers or for educational use.
+ *
+ * As far as possible, everything is kept very simple, not exercising all of
+ * the capabilities of CTools or Panels.
+ *
+ * Although the ctools documentation suggests that strict naming conventions
+ * be followed, this code attempts to follow only the conventions which are
+ * required (the hooks), in order to demonstrate the difference. You can
+ * certainly use the conventions, but it's important to know the difference
+ * between a convention and a requirement.
+ *
+ * The advanced_help module is required, because both CTools and this module
+ * provide help that way.
+ *
+ * There is a demonstration panel provided at /ctools_plugin_example/123
+ */
+
+/**
+ * Implements hook_menu
+ */
+function ctools_plugin_example_menu() {
+ $items = array();
+
+ $items["admin/settings/ctools_plugin_example"] = array(
+ 'title' => 'CTools plugin example',
+ 'description' => t("Demonstration code, advanced help, and a demo panel to show how to build ctools plugins."),
+ 'page callback' => 'ctools_plugin_example_explanation_page',
+ 'access arguments' => array('administer site configuration'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_ctools_plugin_directory().
+ *
+ * It simply tells panels where to find the .inc files that define various
+ * args, contexts, content_types. In this case the subdirectories of
+ * ctools_plugin_example/panels are used.
+ */
+function ctools_plugin_example_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'ctools' && !empty($plugin)) {
+ return "plugins/$plugin";
+ }
+}
+
+/**
+ * Implement hook_ctools_plugin_api().
+ *
+ * If you do this, CTools will pick up default panels pages in
+ * <modulename>.pages_default.inc
+ */
+function ctools_plugin_example_ctools_plugin_api($module, $api) {
+ // @todo -- this example should explain how to put it in a different file.
+ if ($module == 'panels_mini' && $api == 'panels_default') {
+ return array('version' => 1);
+ }
+ if ($module == 'page_manager' && $api == 'pages_default') {
+ return array('version' => 1);
+ }
+}
+
+/**
+ * Just provide an explanation page for the admin section
+ * @return unknown_type
+ */
+function ctools_plugin_example_explanation_page() {
+ $content = '<p>' . t("The CTools Plugin Example is simply a developer's demo of how to create plugins for CTools. It provides no useful functionality for an ordinary user.") . '</p>';
+
+ $content .= '<p>' . t(
+ 'There is a demo panel demonstrating much of the functionality provided at
+ <a href="@demo_url">CTools demo panel</a>, and you can find documentation on the examples at
+ !ctools_plugin_example_help.
+ CTools itself provides documentation at !ctools_help. Mostly, though, the code itself is intended to be the teacher.
+ You can find it in %path.',
+ array(
+ '@demo_url' => url('ctools_plugin_example/xxxxx'),
+ '!ctools_plugin_example_help' => theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')),
+ '!ctools_help' => theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')),
+ '%path' => drupal_get_path('module', 'ctools_plugin_example'),
+ )) . '</p>';
+
+ return $content;
+}
diff --git a/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc
new file mode 100644
index 000000000..10a761938
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc
@@ -0,0 +1,451 @@
+<?php
+
+/**
+ * @file
+ * This module provides default panels to demonstrate the behavior of the plugins.
+ */
+
+/**
+ * Default panels pages for CTools Plugin Example
+ *
+ * To pick up this file, your module needs to implement
+ * hook_ctools_plugin_api() - See ctools_plugin_example_ctools_plugin_api() in
+ * ctools_plugin_example.module.
+ *
+ *
+ * Note the naming of the file: <modulename>.pages_default.inc
+ * With this naming, no additional code needs to be provided. CTools will just find the file.
+ * The name of the hook is <modulename>_default_page_manager_pages()
+ *
+ * This example provides two pages, but the returned array could
+ * have several pages.
+ *
+ * @return
+ * Array of pages, normally exported from Panels.
+ */
+
+function ctools_plugin_example_default_page_manager_pages() {
+
+ // begin exported panel.
+
+ $page = new stdClass;
+ $page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */
+ $page->api_version = 1;
+ $page->name = 'ctools_plugin_example';
+ $page->task = 'page';
+ $page->admin_title = 'CTools plugin example';
+ $page->admin_description = 'This panel provides no functionality to a working Drupal system. It\'s intended to display the various sample plugins provided by the CTools Plugin Example module. ';
+ $page->path = 'ctools_plugin_example/%sc';
+ $page->access = array(
+ 'logic' => 'and',
+ );
+ $page->menu = array(
+ 'type' => 'normal',
+ 'title' => 'CTools plugin example',
+ 'name' => 'navigation',
+ 'weight' => '0',
+ 'parent' => array(
+ 'type' => 'none',
+ 'title' => '',
+ 'name' => 'navigation',
+ 'weight' => '0',
+ ),
+ );
+ $page->arguments = array(
+ 'sc' => array(
+ 'id' => 2,
+ 'identifier' => 'simplecontext-arg',
+ 'name' => 'simplecontext_arg',
+ 'settings' => array(),
+ ),
+ );
+ $page->conf = array();
+ $page->default_handlers = array();
+ $handler = new stdClass;
+ $handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */
+ $handler->api_version = 1;
+ $handler->name = 'page_ctools_panel_context';
+ $handler->task = 'page';
+ $handler->subtask = 'ctools_plugin_example';
+ $handler->handler = 'panel_context';
+ $handler->weight = 0;
+ $handler->conf = array(
+ 'title' => 'Panel',
+ 'no_blocks' => FALSE,
+ 'css_id' => '',
+ 'css' => '',
+ 'contexts' => array(
+ '0' => array(
+ 'name' => 'simplecontext',
+ 'id' => 1,
+ 'identifier' => 'Configured simplecontext (not from argument)',
+ 'keyword' => 'configured_simplecontext',
+ 'context_settings' => array(
+ 'sample_simplecontext_setting' => 'default simplecontext setting',
+ ),
+ ),
+ ),
+ 'relationships' => array(
+ '0' => array(
+ 'context' => 'argument_simplecontext_arg_2',
+ 'name' => 'relcontext_from_simplecontext',
+ 'id' => 1,
+ 'identifier' => 'Relcontext from simplecontext (from relationship)',
+ 'keyword' => 'relcontext',
+ ),
+ ),
+ 'access' => array(
+ 'logic' => 'and',
+ ),
+ );
+ $display = new panels_display;
+ $display->layout = 'threecol_33_34_33_stacked';
+ $display->layout_settings = array();
+ $display->panel_settings = array(
+ 'style' => 'rounded_corners',
+ 'style_settings' => array(
+ 'default' => array(
+ 'corner_location' => 'pane',
+ ),
+ ),
+ );
+ $display->cache = array();
+ $display->title = 'CTools plugin example panel';
+ $display->hide_title = FALSE;
+ $display->title_pane = 1;
+ $display->content = array();
+ $display->panels = array();
+ $pane = new stdClass;
+ $pane->pid = 'new-1';
+ $pane->panel = 'left';
+ $pane->type = 'no_context_content_type';
+ $pane->subtype = 'no_context_content_type';
+ $pane->shown = TRUE;
+ $pane->access = array();
+ $pane->configuration = array(
+ 'item1' => 'contents of config item 1',
+ 'item2' => 'contents of config item 2',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 0;
+ $display->content['new-1'] = $pane;
+ $display->panels['left'][0] = 'new-1';
+ $pane = new stdClass;
+ $pane->pid = 'new-2';
+ $pane->panel = 'left';
+ $pane->type = 'custom';
+ $pane->subtype = 'custom';
+ $pane->shown = TRUE;
+ $pane->access = array(
+ 'plugins' => array(
+ '0' => array(
+ 'name' => 'arg_length',
+ 'settings' => array(
+ 'greater_than' => '1',
+ 'arg_length' => '4',
+ ),
+ 'context' => 'argument_simplecontext_arg_2',
+ ),
+ ),
+ );
+ $pane->configuration = array(
+ 'title' => 'Long Arg Visibility Block',
+ 'body' => 'This block will be here when the argument is longer than configured arg length. It uses the \'arg_length\' access plugin to test against the length of the argument used for Simplecontext.',
+ 'format' => '1',
+ 'substitute' => 1,
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 1;
+ $display->content['new-2'] = $pane;
+ $display->panels['left'][1] = 'new-2';
+ $pane = new stdClass;
+ $pane->pid = 'new-3';
+ $pane->panel = 'left';
+ $pane->type = 'custom';
+ $pane->subtype = 'custom';
+ $pane->shown = TRUE;
+ $pane->access = array(
+ 'plugins' => array(
+ '0' => array(
+ 'name' => 'arg_length',
+ 'settings' => array(
+ 'greater_than' => '0',
+ 'arg_length' => '4',
+ ),
+ 'context' => 'argument_simplecontext_arg_2',
+ ),
+ ),
+ );
+ $pane->configuration = array(
+ 'title' => 'Short Arg Visibility',
+ 'body' => 'This block appears when the simplecontext argument is <i>less than</i> the configured length.',
+ 'format' => '1',
+ 'substitute' => 1,
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 2;
+ $display->content['new-3'] = $pane;
+ $display->panels['left'][2] = 'new-3';
+ $pane = new stdClass;
+ $pane->pid = 'new-4';
+ $pane->panel = 'middle';
+ $pane->type = 'simplecontext_content_type';
+ $pane->subtype = 'simplecontext_content_type';
+ $pane->shown = TRUE;
+ $pane->access = array();
+ $pane->configuration = array(
+ 'buttons' => NULL,
+ '#validate' => NULL,
+ '#submit' => NULL,
+ '#action' => NULL,
+ 'context' => 'argument_simplecontext_arg_2',
+ 'aligner_start' => NULL,
+ 'override_title' => 1,
+ 'override_title_text' => 'Simplecontext (with an arg)',
+ 'aligner_stop' => NULL,
+ 'override_title_markup' => NULL,
+ 'config_item_1' => 'Config item 1 contents',
+ '#build_id' => NULL,
+ '#type' => NULL,
+ '#programmed' => NULL,
+ 'form_build_id' => 'form-19c4ae6cb54fad8f096da46e95694e5a',
+ '#token' => NULL,
+ 'form_token' => '17141d3531eaa7b609da78afa6f3b560',
+ 'form_id' => 'simplecontext_content_type_edit_form',
+ '#id' => NULL,
+ '#description' => NULL,
+ '#attributes' => NULL,
+ '#required' => NULL,
+ '#tree' => NULL,
+ '#parents' => NULL,
+ '#method' => NULL,
+ '#post' => NULL,
+ '#processed' => NULL,
+ '#defaults_loaded' => NULL,
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 0;
+ $display->content['new-4'] = $pane;
+ $display->panels['middle'][0] = 'new-4';
+ $pane = new stdClass;
+ $pane->pid = 'new-5';
+ $pane->panel = 'middle';
+ $pane->type = 'simplecontext_content_type';
+ $pane->subtype = 'simplecontext_content_type';
+ $pane->shown = TRUE;
+ $pane->access = array();
+ $pane->configuration = array(
+ 'buttons' => NULL,
+ '#validate' => NULL,
+ '#submit' => NULL,
+ '#action' => NULL,
+ 'context' => 'context_simplecontext_1',
+ 'aligner_start' => NULL,
+ 'override_title' => 1,
+ 'override_title_text' => 'Configured simplecontext content type (not from arg)',
+ 'aligner_stop' => NULL,
+ 'override_title_markup' => NULL,
+ 'config_item_1' => '(configuration for simplecontext)',
+ '#build_id' => NULL,
+ '#type' => NULL,
+ '#programmed' => NULL,
+ 'form_build_id' => 'form-d016200490abd015dc5b8a7e366d76ea',
+ '#token' => NULL,
+ 'form_token' => '17141d3531eaa7b609da78afa6f3b560',
+ 'form_id' => 'simplecontext_content_type_edit_form',
+ '#id' => NULL,
+ '#description' => NULL,
+ '#attributes' => NULL,
+ '#required' => NULL,
+ '#tree' => NULL,
+ '#parents' => NULL,
+ '#method' => NULL,
+ '#post' => NULL,
+ '#processed' => NULL,
+ '#defaults_loaded' => NULL,
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 1;
+ $display->content['new-5'] = $pane;
+ $display->panels['middle'][1] = 'new-5';
+ $pane = new stdClass;
+ $pane->pid = 'new-6';
+ $pane->panel = 'middle';
+ $pane->type = 'custom';
+ $pane->subtype = 'custom';
+ $pane->shown = TRUE;
+ $pane->access = array();
+ $pane->configuration = array(
+ 'admin_title' => 'Simplecontext keyword usage',
+ 'title' => 'Simplecontext keyword usage',
+ 'body' => 'Demonstrating context keyword usage:
+ item1 is %sc:item1
+ item2 is %sc:item2
+ description is %sc:description',
+ 'format' => '1',
+ 'substitute' => 1,
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 2;
+ $display->content['new-6'] = $pane;
+ $display->panels['middle'][2] = 'new-6';
+ $pane = new stdClass;
+ $pane->pid = 'new-7';
+ $pane->panel = 'right';
+ $pane->type = 'relcontext_content_type';
+ $pane->subtype = 'relcontext_content_type';
+ $pane->shown = TRUE;
+ $pane->access = array();
+ $pane->configuration = array(
+ 'buttons' => NULL,
+ '#validate' => NULL,
+ '#submit' => NULL,
+ '#action' => NULL,
+ 'context' => 'relationship_relcontext_from_simplecontext_1',
+ 'aligner_start' => NULL,
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ 'aligner_stop' => NULL,
+ 'override_title_markup' => NULL,
+ 'config_item_1' => 'some stuff in this one',
+ '#build_id' => NULL,
+ '#type' => NULL,
+ '#programmed' => NULL,
+ 'form_build_id' => 'form-8485f84511bd06e51b4a48e998448054',
+ '#token' => NULL,
+ 'form_token' => '1c3356396374d51d7d2531a10fd25310',
+ 'form_id' => 'relcontext_edit_form',
+ '#id' => NULL,
+ '#description' => NULL,
+ '#attributes' => NULL,
+ '#required' => NULL,
+ '#tree' => NULL,
+ '#parents' => NULL,
+ '#method' => NULL,
+ '#post' => NULL,
+ '#processed' => NULL,
+ '#defaults_loaded' => NULL,
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 0;
+ $display->content['new-7'] = $pane;
+ $display->panels['right'][0] = 'new-7';
+ $pane = new stdClass;
+ $pane->pid = 'new-8';
+ $pane->panel = 'top';
+ $pane->type = 'custom';
+ $pane->subtype = 'custom';
+ $pane->shown = TRUE;
+ $pane->access = array();
+ $pane->configuration = array(
+ 'title' => 'Demonstrating ctools plugins',
+ 'body' => 'The CTools Plugin Example module (and this panel page) are just here to demonstrate how to build CTools plugins.
+
+ ',
+ 'format' => '2',
+ 'substitute' => 1,
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 0;
+ $display->content['new-8'] = $pane;
+ $display->panels['top'][0] = 'new-8';
+ $handler->conf['display'] = $display;
+ $page->default_handlers[$handler->name] = $handler;
+
+ // end of exported panel.
+ $pages['ctools_plugin_example_demo_page'] = $page;
+
+ // begin exported panel
+
+ $page = new stdClass;
+ $page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */
+ $page->api_version = 1;
+ $page->name = 'ctools_plugin_example_base';
+ $page->task = 'page';
+ $page->admin_title = 'CTools Plugin Example base page';
+ $page->admin_description = 'This panel is for when people hit /ctools_plugin_example without an argument. We can use it to tell people to move on.';
+ $page->path = 'ctools_plugin_example';
+ $page->access = array();
+ $page->menu = array();
+ $page->arguments = array();
+ $page->conf = array();
+ $page->default_handlers = array();
+ $handler = new stdClass;
+ $handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */
+ $handler->api_version = 1;
+ $handler->name = 'page_ctools_plugin_example_base_panel_context';
+ $handler->task = 'page';
+ $handler->subtask = 'ctools_plugin_example_base';
+ $handler->handler = 'panel_context';
+ $handler->weight = 0;
+ $handler->conf = array(
+ 'title' => 'Panel',
+ 'no_blocks' => FALSE,
+ 'css_id' => '',
+ 'css' => '',
+ 'contexts' => array(),
+ 'relationships' => array(),
+ );
+ $display = new panels_display;
+ $display->layout = 'onecol';
+ $display->layout_settings = array();
+ $display->panel_settings = array();
+ $display->cache = array();
+ $display->title = '';
+ $display->hide_title = FALSE;
+ $display->content = array();
+ $display->panels = array();
+ $pane = new stdClass;
+ $pane->pid = 'new-1';
+ $pane->panel = 'middle';
+ $pane->type = 'custom';
+ $pane->subtype = 'custom';
+ $pane->shown = TRUE;
+ $pane->access = array();
+ $pane->configuration = array(
+ 'title' => 'Use this page with an argument',
+ 'body' => 'This demo page works if you use an argument, like <a href="ctools_plugin_example/xxxxx">ctools_plugin_example/xxxxx</a>.',
+ 'format' => '1',
+ 'substitute' => NULL,
+ );
+ $pane->cache = array();
+ $pane->style = array();
+ $pane->css = array();
+ $pane->extras = array();
+ $pane->position = 0;
+ $display->content['new-1'] = $pane;
+ $display->panels['middle'][0] = 'new-1';
+ $handler->conf['display'] = $display;
+ $page->default_handlers[$handler->name] = $handler;
+ // end exported panel.
+
+ $pages['base_page'] = $page;
+
+ return $pages;
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/ctools_plugin_example/help/Access-Plugins--Determining-access-and-visibility.html b/sites/all/modules/ctools/ctools_plugin_example/help/Access-Plugins--Determining-access-and-visibility.html
new file mode 100644
index 000000000..781260ed1
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/help/Access-Plugins--Determining-access-and-visibility.html
@@ -0,0 +1,17 @@
+<div id="node-16" class="node">
+
+
+
+
+ <div class="content clear-block">
+ <p>We can use access plugins to determine access to a page or visibility of the panes in a page. Basically, we just determine access based on configuration settings and the various contexts that are available to us.</p>
+<p>The arbitrary example in plugins/access/arg_length.inc determines access based on the length of the simplecontext argument. You can configure whether access should be granted if the simplecontext argument is greater or less than some number.</p>
+ </div>
+
+ <div class="clear-block">
+ <div class="meta">
+ </div>
+
+ </div>
+
+</div>
diff --git a/sites/all/modules/ctools/ctools_plugin_example/help/Argument-Plugins--Starting-at-the-beginning.html b/sites/all/modules/ctools/ctools_plugin_example/help/Argument-Plugins--Starting-at-the-beginning.html
new file mode 100644
index 000000000..4dd569d71
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/help/Argument-Plugins--Starting-at-the-beginning.html
@@ -0,0 +1,20 @@
+<div id="node-12" class="node">
+
+
+
+
+ <div class="content clear-block">
+ <p>Contexts are fundamental to CTools, and they almost always start with an argument to a panels page, so we'll start there too.</p>
+<p>We first need to process an argument.</p>
+<p>We're going to work with a "Simplecontext" context type and argument, and then with a content type that displays it. So we'll start by with the Simplecontext argument plugin in plugins/arguments/simplecontext_arg.inc.</p>
+<p>Note that the name of the file (simplecontext_arg.inc) is built from the machine name of our plugin (simplecontext_arg). And note also that the primary function that we use to provide our argument (ctools_plugin_example_simplecontext_arg_ctools_arguments()) is also built from the machine name. This magic is most of the naming magic that you have to know.</p>
+<p>You can browse plugins/arguments/simplecontext_arg.inc and see the little that it does.</p>
+ </div>
+
+ <div class="clear-block">
+ <div class="meta">
+ </div>
+
+ </div>
+
+</div>
diff --git a/sites/all/modules/ctools/ctools_plugin_example/help/Chaos-Tools--CTools--Plugin-Examples.html b/sites/all/modules/ctools/ctools_plugin_example/help/Chaos-Tools--CTools--Plugin-Examples.html
new file mode 100644
index 000000000..7576c80bc
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/help/Chaos-Tools--CTools--Plugin-Examples.html
@@ -0,0 +1,19 @@
+<div id="node-10" class="node">
+
+
+
+
+ <div class="content clear-block">
+ <p>This demonstration module is intended for developers to look at and play with. CTools plugins are not terribly difficult to do, but it can be hard to sort through the various arguments and required functions. The idea here is that you should have a starting point for most anything you want to do. Just work through the example, and then start experimenting with changing it.</p>
+<p>There are two parts to this demo: </p>
+<p>First, there is a sample panel provided that uses all the various plugins. It's at <a href="/ctools_plugin_example/12345">ctools_example/12345</a>. You can edit the panel and configure all the panes on it.</p>
+<p>Second, the code is there for you to experiment with and change as you see fit. Sometimes starting with simple code and working with it can take you places that it's hard to go when you're looking at more complex examples.</p>
+ </div>
+
+ <div class="clear-block">
+ <div class="meta">
+ </div>
+
+ </div>
+
+</div>
diff --git a/sites/all/modules/ctools/ctools_plugin_example/help/Content-Type-Plugins--Displaying-content-using-a-context.html b/sites/all/modules/ctools/ctools_plugin_example/help/Content-Type-Plugins--Displaying-content-using-a-context.html
new file mode 100644
index 000000000..918a13cb8
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/help/Content-Type-Plugins--Displaying-content-using-a-context.html
@@ -0,0 +1,17 @@
+<div id="node-14" class="node">
+
+
+
+
+ <div class="content clear-block">
+ <p>Now we get to the heart of the matter: Building a content type plugin. A content type plugin uses the contexts available to it to display something. plugins/content_types/simplecontext_content_type.inc does this work for us.</p>
+<p>Note that our content type also has an edit form which can be used to configure its behavior. This settings form is accessed through the panels interface, and it's up to you what the settings mean to the code and the generation of content in the display rendering.</p>
+ </div>
+
+ <div class="clear-block">
+ <div class="meta">
+ </div>
+
+ </div>
+
+</div>
diff --git a/sites/all/modules/ctools/ctools_plugin_example/help/Context-plugins--Creating-a--context--from-an-argument.html b/sites/all/modules/ctools/ctools_plugin_example/help/Context-plugins--Creating-a--context--from-an-argument.html
new file mode 100644
index 000000000..e8efbb390
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/help/Context-plugins--Creating-a--context--from-an-argument.html
@@ -0,0 +1,21 @@
+<div id="node-13" class="node">
+
+
+
+
+ <div class="content clear-block">
+ <p>Now that we have a plugin for a simplecontext argument, we can create a plugin for a simplecontext context. </p>
+<p>Normally, a context would take an argument which is a key like a node ID (nid) and retrieve a more complex object from a database or whatever. In our example, we'll artificially transform the argument into an arbitrary "context" data object. </p>
+<p>plugins/contexts/simplecontext.inc implements our context.</p>
+<p>Note that there are actually two ways to create a context. The normal one, which we've been referring to, is to create a context from an argument. However, it is also possible to configure a context in a panel using the panels interface. This is quite inflexible, but might be useful for configuring single page. However, it means that we have a settings form for exactly that purpose. Our context would have to know how to create itself from a settings form as well as from an argument. Simplecontext can do that.</p>
+<p>A context plugin can also provide keywords that expose parts of its context using keywords like masterkeyword:dataitem. The node plugin for ctools has node:nid and node:title, for example. The simplecontext plugin here provides the simplest of keywords.</p>
+
+ </div>
+
+ <div class="clear-block">
+ <div class="meta">
+ </div>
+
+ </div>
+
+</div>
diff --git a/sites/all/modules/ctools/ctools_plugin_example/help/Module-setup-and-hooks.html b/sites/all/modules/ctools/ctools_plugin_example/help/Module-setup-and-hooks.html
new file mode 100644
index 000000000..f816917dd
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/help/Module-setup-and-hooks.html
@@ -0,0 +1,20 @@
+<div id="node-11" class="node">
+
+
+
+
+ <div class="content clear-block">
+ <p>Your module must provide a few things so that your plugins can be found.</p>
+<p>First, you need to implement hook_ctools_plugin_directory(). Here we're telling CTools that our plugins will be found in the module's directory in the plugins/&lt;plugintype&gt; directory. Context plugins will be in ctools_plugin_example/plugins/contexts, Content-type plugins will be in ctools_plugin_example/plugins/content_types.</p>
+<p><div class="codeblock"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">ctools_plugin_example_ctools_plugin_directory</span><span style="color: #007700">(</span><span style="color: #0000BB">$module</span><span style="color: #007700">, </span><span style="color: #0000BB">$plugin</span><span style="color: #007700">) {<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'ctools' </span><span style="color: #007700">&amp;&amp; !empty(</span><span style="color: #0000BB">$plugin</span><span style="color: #007700">)) {<br />&nbsp;&nbsp;&nbsp; return </span><span style="color: #DD0000">"plugins/$plugin"</span><span style="color: #007700">;<br />&nbsp; }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></p>
+<p>Second, if you module wants to provide default panels pages, you can implement hook_ctools_plugin_api(). CTools will then pick up your panels pages in the file named &lt;modulename&gt;.pages_default.inc.</p>
+<p><div class="codeblock"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">ctools_plugin_example_ctools_plugin_api</span><span style="color: #007700">(</span><span style="color: #0000BB">$module</span><span style="color: #007700">, </span><span style="color: #0000BB">$api</span><span style="color: #007700">) {<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'panels_mini' </span><span style="color: #007700">&amp;&amp; </span><span style="color: #0000BB">$api </span><span style="color: #007700">== </span><span style="color: #DD0000">'panels_default'</span><span style="color: #007700">) {<br />&nbsp;&nbsp;&nbsp; return array(</span><span style="color: #DD0000">'version' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">1</span><span style="color: #007700">);<br />&nbsp; }<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'page_manager' </span><span style="color: #007700">&amp;&amp; </span><span style="color: #0000BB">$api </span><span style="color: #007700">== </span><span style="color: #DD0000">'pages_default'</span><span style="color: #007700">) {<br />&nbsp;&nbsp;&nbsp; return array(</span><span style="color: #DD0000">'version' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">1</span><span style="color: #007700">);<br />&nbsp; }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></p>
+ </div>
+
+ <div class="clear-block">
+ <div class="meta">
+ </div>
+
+ </div>
+
+</div>
diff --git a/sites/all/modules/ctools/ctools_plugin_example/help/Relationships--Letting-one-context-take-us-to-another.html b/sites/all/modules/ctools/ctools_plugin_example/help/Relationships--Letting-one-context-take-us-to-another.html
new file mode 100644
index 000000000..7691245ad
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/help/Relationships--Letting-one-context-take-us-to-another.html
@@ -0,0 +1,18 @@
+<div id="node-15" class="node">
+
+
+
+
+ <div class="content clear-block">
+ <p>Often a single data type can lead us to other data types. For example, a node has a user (the author) and the user has data associated with it.</p>
+<p>A relationship plugin allows this kind of data to be accessed. </p>
+<p>An example relationship plugin is provided in plugins/relationships/relcontext_from_simplecontext.inc. It looks at a simplecontext (which we got from an argument) and builds an (artificial) "relcontext" from that.</p>
+ </div>
+
+ <div class="clear-block">
+ <div class="meta">
+ </div>
+
+ </div>
+
+</div>
diff --git a/sites/all/modules/ctools/ctools_plugin_example/help/ctools_plugin_example.help.ini b/sites/all/modules/ctools/ctools_plugin_example/help/ctools_plugin_example.help.ini
new file mode 100644
index 000000000..6fb3d4c08
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/help/ctools_plugin_example.help.ini
@@ -0,0 +1,42 @@
+[Chaos-Tools--CTools--Plugin-Examples]
+title = CTools Plugin Examples
+file = Chaos-Tools--CTools--Plugin-Examples
+weight = 0
+parent =
+
+[Module-setup-and-hooks]
+title = Module setup and hooks
+file = Module-setup-and-hooks
+weight = -15
+parent = Chaos-Tools--CTools--Plugin-Examples
+
+[Argument-Plugins--Starting-at-the-beginning]
+title = Argument Plugins: Starting at the beginning
+file = Argument-Plugins--Starting-at-the-beginning
+weight = -14
+parent = Chaos-Tools--CTools--Plugin-Examples
+
+[Context-plugins--Creating-a--context--from-an-argument]
+title = Context plugins: Creating a context from an argument
+file = Context-plugins--Creating-a--context--from-an-argument
+weight = -13
+parent = Chaos-Tools--CTools--Plugin-Examples
+
+[Content-Type-Plugins--Displaying-content-using-a-context]
+title = Content Type Plugins: Displaying content using a context
+file = Content-Type-Plugins--Displaying-content-using-a-context
+weight = -12
+parent = Chaos-Tools--CTools--Plugin-Examples
+
+[Access-Plugins--Determining-access-and-visibility]
+title = Access Plugins: Determining access and visibility
+file = Access-Plugins--Determining-access-and-visibility
+weight = -11
+parent = Chaos-Tools--CTools--Plugin-Examples
+
+[Relationships--Letting-one-context-take-us-to-another]
+title = Relationships: Letting one context take us to another
+file = Relationships--Letting-one-context-take-us-to-another
+weight = -10
+parent = Chaos-Tools--CTools--Plugin-Examples
+
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/access/arg_length.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/access/arg_length.inc
new file mode 100644
index 000000000..2a09eea12
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/access/arg_length.inc
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control/visibility based on length of
+ * simplecontext argument (in URL).
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Arg length"),
+ 'description' => t('Control access by length of simplecontext argument.'),
+ 'callback' => 'ctools_plugin_example_arg_length_ctools_access_check',
+ 'settings form' => 'ctools_plugin_example_arg_length_ctools_access_settings',
+ 'summary' => 'ctools_plugin_example_arg_length_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Simplecontext'), 'simplecontext'),
+);
+
+/**
+ * Settings form for the 'by role' access plugin.
+ */
+function ctools_plugin_example_arg_length_ctools_access_settings(&$form, &$form_state, $conf) {
+ $form['settings']['greater_than'] = array(
+ '#type' => 'radios',
+ '#title' => t('Grant access if simplecontext argument length is'),
+ '#options' => array(1 => t('Greater than'), 0 => t('Less than or equal to')),
+ '#default_value' => $conf['greater_than'],
+ );
+ $form['settings']['arg_length'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Length of simplecontext argument'),
+ '#size' => 3,
+ '#default_value' => $conf['arg_length'],
+ '#description' => t('Access/visibility will be granted based on arg length.'),
+ );
+}
+
+/**
+ * Check for access.
+ */
+function ctools_plugin_example_arg_length_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data)) {
+ return FALSE;
+ }
+ $compare = ($context->arg_length > $conf['arg_length']);
+ if (($compare && $conf['greater_than']) || (!$compare && !$conf['greater_than'])) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Provide a summary description based upon the checked roles.
+ */
+function ctools_plugin_example_arg_length_ctools_access_summary($conf, $context) {
+ return t('Simpletext argument must be !comp @length characters',
+ array('!comp' => $conf['greater_than'] ? 'greater than' : 'less than or equal to',
+ '@length' => $conf['arg_length']));
+}
+
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/access/example_role.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/access/example_role.inc
new file mode 100644
index 000000000..bbe364c15
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/access/example_role.inc
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon role membership.
+ * This is directly from the ctools module, but serves as a good
+ * example of an access plugin
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("CTools example: role"),
+ 'description' => t('Control access by role.'),
+ 'callback' => 'ctools_plugin_example_example_role_ctools_access_check',
+ 'default' => array('rids' => array()),
+ 'settings form' => 'ctools_plugin_example_example_role_ctools_access_settings',
+ 'summary' => 'ctools_plugin_example_example_role_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+);
+
+/**
+ * Settings form for the 'by role' access plugin.
+ */
+function ctools_plugin_example_example_role_ctools_access_settings(&$form, &$form_state, $conf) {
+ $form['settings']['rids'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Role'),
+ '#default_value' => $conf['rids'],
+ '#options' => ctools_get_roles(),
+ '#description' => t('Only the checked roles will be granted access.'),
+ );
+}
+
+/**
+ * Compress the roles allowed to the minimum.
+ */
+function ctools_plugin_example_example_role_ctools_access_settings_submit(&$form, &$form_state) {
+ $form_state['values']['settings']['rids'] = array_keys(array_filter($form_state['values']['settings']['rids']));
+}
+
+/**
+ * Check for access.
+ */
+function ctools_plugin_example_example_role_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || !isset($context->data->roles)) {
+ return FALSE;
+ }
+
+ $roles = array_keys($context->data->roles);
+ $roles[] = $context->data->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
+ return (bool) array_intersect($conf['rids'], $roles);
+}
+
+/**
+ * Provide a summary description based upon the checked roles.
+ */
+function ctools_plugin_example_example_role_ctools_access_summary($conf, $context) {
+ if (!isset($conf['rids'])) {
+ $conf['rids'] = array();
+ }
+ $roles = ctools_get_roles();
+ $names = array();
+ foreach (array_filter($conf['rids']) as $rid) {
+ $names[] = check_plain($roles[$rid]);
+ }
+ if (empty($names)) {
+ return t('@identifier can have any role', array('@identifier' => $context->identifier));
+ }
+ return format_plural(count($names), '@identifier must have role "@roles"', '@identifier can be one of "@roles"', array('@roles' => implode(', ', $names), '@identifier' => $context->identifier));
+}
+
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc
new file mode 100644
index 000000000..51c7c601c
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ *
+ * Sample plugin to provide an argument handler for a simplecontext.
+ *
+ * Given any argument to the page, simplecontext will get it
+ * and turn it into a piece of data (a "context") just by adding some text to it.
+ * Normally, the argument would be a key into some database (like the node
+ * database, for example, and the result of using the argument would be to load
+ * a specific "context" or data item that we can use elsewhere.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Simplecontext arg"),
+ // keyword to use for %substitution
+ 'keyword' => 'simplecontext',
+ 'description' => t('Creates a "simplecontext" from the arg.'),
+ 'context' => 'simplecontext_arg_context',
+ // 'settings form' => 'simplecontext_arg_settings_form',
+
+ // placeholder_form is used in panels preview, for example, so we can
+ // preview without getting the arg from a URL
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the simplecontext arg'),
+ ),
+);
+
+/**
+ * Get the simplecontext context using the arg. In this case we're just going
+ * to manufacture the context from the data in the arg, but normally it would
+ * be an API call, db lookup, etc.
+ */
+function simplecontext_arg_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If $empty == TRUE it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('simplecontext');
+ }
+ // Do whatever error checking is required, returning FALSE if it fails the test
+ // Normally you'd check
+ // for a missing object, one you couldn't create, etc.
+ if (empty($arg)) {
+ return FALSE;
+ }
+ return ctools_context_create('simplecontext', $arg);
+}
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/icon_example.png b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/icon_example.png
new file mode 100644
index 000000000..58c6bee4f
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/icon_example.png
Binary files differ
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc
new file mode 100644
index 000000000..3c02ab84f
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * @file
+ * "No context" sample content type. It operates with no context at all. It would
+ * be basically the same as a 'custom content' block, but it's not even that
+ * sophisticated.
+ *
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('CTools example no context content type'),
+ 'description' => t('No context content type - requires and uses no context.'),
+
+ // 'single' => TRUE means has no subtypes.
+ 'single' => TRUE,
+ // Constructor.
+ 'content_types' => array('no_context_content_type'),
+ // Name of a function which will render the block.
+ 'render callback' => 'no_context_content_type_render',
+ // The default context.
+ 'defaults' => array(),
+
+ // This explicitly declares the config form. Without this line, the func would be
+ // ctools_plugin_example_no_context_content_type_edit_form.
+ 'edit form' => 'no_context_content_type_edit_form',
+
+ // Icon goes in the directory with the content type.
+ 'icon' => 'icon_example.png',
+ 'category' => array(t('CTools Examples'), -9),
+
+ // this example does not provide 'admin info', which would populate the
+ // panels builder page preview.
+);
+
+/**
+ * Run-time rendering of the body of the block.
+ *
+ * @param $subtype
+ * @param $conf
+ * Configuration as done at admin time.
+ * @param $args
+ * @param $context
+ * Context - in this case we don't have any.
+ *
+ * @return
+ * An object with at least title and content members.
+ */
+function no_context_content_type_render($subtype, $conf, $args, $context) {
+ $block = new stdClass();
+
+ $ctools_help = theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title'));
+ $ctools_plugin_example_help = theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title'));
+
+ // The title actually used in rendering
+ $block->title = check_plain("No-context content type");
+ $block->content = t("
+ <div>Welcome to the CTools Plugin Example demonstration content type.
+
+ This block is a content type which requires no context at all. It's like a custom block,
+ but not even that sophisticated.
+
+ For more information on the example plugins, please see the advanced help for
+
+ {$ctools_help} and {$ctools_plugin_example_help}
+ </div>
+ ");
+ if (!empty($conf)) {
+ $block->content .= '<div>The only information that can be displayed in this block comes from the code and its settings form: </div>';
+ $block->content .= '<div style="border: 1px solid red;">' . var_export($conf, TRUE) . '</div>';
+ }
+
+ return $block;
+
+}
+
+/**
+ * 'Edit form' callback for the content type.
+ * This example just returns a form; validation and submission are standard drupal
+ * Note that if we had not provided an entry for this in hook_content_types,
+ * this could have had the default name
+ * ctools_plugin_example_no_context_content_type_edit_form.
+ *
+ */
+function no_context_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['item1'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Item1'),
+ '#size' => 50,
+ '#description' => t('The setting for item 1.'),
+ '#default_value' => !empty($conf['item1']) ? $conf['item1'] : '',
+ '#prefix' => '<div class="clear-block no-float">',
+ '#suffix' => '</div>',
+ );
+ $form['item2'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Item2'),
+ '#size' => 50,
+ '#description' => t('The setting for item 2'),
+ '#default_value' => !empty($conf['item2']) ? $conf['item2'] : '',
+ '#prefix' => '<div class="clear-block no-float">',
+ '#suffix' => '</div>',
+ );
+ return $form;
+}
+
+function no_context_content_type_edit_form_submit($form, &$form_state) {
+ foreach (array('item1', 'item2') as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc
new file mode 100644
index 000000000..bf54dce64
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc
@@ -0,0 +1,103 @@
+<?php
+
+
+/**
+ * @file
+ * Content type that displays the relcontext context type.
+ *
+ * This example is for use with the relcontext relationship to show
+ * how we can get a relationship-context into a data type.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ // Used in add content dialogs.
+ 'title' => t('CTools example relcontext content type'),
+ 'admin info' => 'ctools_plugin_example_relcontext_content_type_admin_info',
+ 'content_types' => 'relcontext_content_type',
+ 'single' => TRUE,
+ 'render callback' => 'relcontext_content_type_render',
+ // Icon goes in the directory with the content type. Here, in plugins/content_types.
+ 'icon' => 'icon_example.png',
+ 'description' => t('Relcontext content type - works with relcontext context.'),
+ 'required context' => new ctools_context_required(t('Relcontext'), 'relcontext'),
+ 'category' => array(t('CTools Examples'), -9),
+ 'edit form' => 'relcontext_edit_form',
+
+ // this example does not provide 'admin info', which would populate the
+ // panels builder page preview.
+
+);
+
+/**
+ * Run-time rendering of the body of the block.
+ *
+ * @param $subtype
+ * @param $conf
+ * Configuration as done at admin time
+ * @param $args
+ * @param $context
+ * Context - in this case we don't have any
+ *
+ * @return
+ * An object with at least title and content members
+ */
+function relcontext_content_type_render($subtype, $conf, $args, $context) {
+ $data = $context->data;
+ $block = new stdClass();
+
+ // Don't forget to check this data if it's untrusted.
+ // The title actually used in rendering.
+ $block->title = "Relcontext content type";
+ $block->content = t("
+ This is a block of data created by the Relcontent content type.
+ Data in the block may be assembled from static text (like this) or from the
+ content type settings form (\$conf) for the content type, or from the context
+ that is passed in. <br />
+ In our case, the configuration form (\$conf) has just one field, 'config_item_1;
+ and it's configured with:
+ ");
+ if (!empty($conf)) {
+ $block->content .= '<div style="border: 1px solid red;">' . var_export($conf['config_item_1'], TRUE) . '</div>';
+ }
+ if (!empty($context)) {
+ $block->content .= '<br />The args ($args) were <div style="border: 1px solid yellow;" >' .
+ var_export($args, TRUE) . '</div>';
+ }
+ $block->content .= '<br />And the relcontext context ($context->data->description)
+ (which was created from a
+ simplecontext context) was <div style="border: 1px solid green;" >' .
+ print_r($context->data->description, TRUE) . '</div>';
+ return $block;
+}
+
+/**
+ * 'Edit' callback for the content type.
+ * This example just returns a form.
+ *
+ */
+function relcontext_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['config_item_1'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Config Item 1 (relcontext)'),
+ '#size' => 50,
+ '#description' => t('Setting for relcontext.'),
+ '#default_value' => !empty($conf['config_item_1']) ? $conf['config_item_1'] : '',
+ '#prefix' => '<div class="clear-block no-float">',
+ '#suffix' => '</div>',
+ );
+ return $form;
+}
+
+function relcontext_edit_form_submit($form, &$form_state) {
+ foreach (element_children($form) as $key) {
+ if (!empty($form_state['values'][$key])) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc
new file mode 100644
index 000000000..a308683c5
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc
@@ -0,0 +1,129 @@
+<?php
+
+
+/**
+ * @file
+ * Sample ctools content type that takes advantage of context.
+ *
+ * This example uses the context it gets (simplecontext) to demo how a
+ * ctools content type can access and use context. Note that the simplecontext
+ * can be either configured manually into a panel or it can be retrieved via
+ * an argument.
+ *
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Simplecontext content type'),
+ 'content_types' => 'simplecontext_content_type',
+ // 'single' means not to be subtyped.
+ 'single' => TRUE,
+ // Name of a function which will render the block.
+ 'render callback' => 'simplecontext_content_type_render',
+
+ // Icon goes in the directory with the content type.
+ 'icon' => 'icon_example.png',
+ 'description' => t('Simplecontext content type - works with a simplecontext context.'),
+ 'required context' => new ctools_context_required(t('Simplecontext'), 'simplecontext'),
+ 'edit form' => 'simplecontext_content_type_edit_form',
+ 'admin title' => 'ctools_plugin_example_simplecontext_content_type_admin_title',
+
+ // presents a block which is used in the preview of the data.
+ // Pn Panels this is the preview pane shown on the panels building page.
+ 'admin info' => 'ctools_plugin_example_simplecontext_content_type_admin_info',
+ 'category' => array(t('CTools Examples'), -9),
+);
+
+function ctools_plugin_example_simplecontext_content_type_admin_title($subtype, $conf, $context = NULL) {
+ $output = t('Simplecontext');
+ if ($conf['override_title'] && !empty($conf['override_title_text'])) {
+ $output = filter_xss_admin($conf['override_title_text']);
+ }
+ return $output;
+}
+
+/**
+ * Callback to provide administrative info (the preview in panels when building
+ * a panel).
+ *
+ * In this case we'll render the content with a dummy argument and
+ * a dummy context.
+ */
+function ctools_plugin_example_simplecontext_content_type_admin_info($subtype, $conf, $context = NULL) {
+ $context = new stdClass();
+ $context->data = new stdClass();
+ $context->data->description = t("no real context");
+ $block = simplecontext_content_type_render($subtype, $conf, array("Example"), $context);
+ return $block;
+}
+
+/**
+ * Run-time rendering of the body of the block (content type)
+ *
+ * @param $subtype
+ * @param $conf
+ * Configuration as done at admin time
+ * @param $args
+ * @param $context
+ * Context - in this case we don't have any
+ *
+ * @return
+ * An object with at least title and content members
+ */
+function simplecontext_content_type_render($subtype, $conf, $args, $context) {
+ $data = $context->data;
+ $block = new stdClass();
+
+ // Don't forget to check this data if it's untrusted.
+ // The title actually used in rendering.
+ $block->title = "Simplecontext content type";
+ $block->content = t("
+ This is a block of data created by the Simplecontext content type.
+ Data in the block may be assembled from static text (like this) or from the
+ content type settings form (\$conf) for the content type, or from the context
+ that is passed in. <br />
+ In our case, the configuration form (\$conf) has just one field, 'config_item_1;
+ and it's configured with:
+ ");
+ if (!empty($conf)) {
+ $block->content .= '<div style="border: 1px solid red;">' . print_r(filter_xss_admin($conf['config_item_1']), TRUE) . '</div>';
+ }
+ if (!empty($context)) {
+ $block->content .= '<br />The args ($args) were <div style="border: 1px solid yellow;" >' .
+ var_export($args, TRUE) . '</div>';
+ }
+ $block->content .= '<br />And the simplecontext context ($context->data->description) was <div style="border: 1px solid green;" >' .
+ print_r($context->data->description, TRUE) . '</div>';
+ return $block;
+}
+
+/**
+ * 'Edit' callback for the content type.
+ * This example just returns a form.
+ *
+ */
+function simplecontext_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['config_item_1'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Config Item 1 for simplecontext content type'),
+ '#size' => 50,
+ '#description' => t('The stuff for item 1.'),
+ '#default_value' => !empty($conf['config_item_1']) ? $conf['config_item_1'] : '',
+ '#prefix' => '<div class="clear-block no-float">',
+ '#suffix' => '</div>',
+ );
+
+ return $form;
+}
+
+function simplecontext_content_type_edit_form_submit($form, &$form_state) {
+ foreach (element_children($form) as $key) {
+ if (!empty($form_state['values'][$key])) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc
new file mode 100644
index 000000000..0c7ef1135
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Sample ctools context type plugin that
+ * is used in this demo to create a relcontext from an existing simplecontext.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Relcontext"),
+ 'description' => t('A relcontext object.'),
+ // Function to create the relcontext.
+ 'context' => 'ctools_plugin_example_context_create_relcontext',
+ // Function that does the settings.
+ 'settings form' => 'relcontext_settings_form',
+ 'keyword' => 'relcontext',
+ 'context name' => 'relcontext',
+);
+
+/**
+ * Create a context, either from manual configuration (form) or from an argument on the URL.
+ *
+ * @param $empty
+ * If true, just return an empty context.
+ * @param $data
+ * If from settings form, an array as from a form. If from argument, a string.
+ * @param $conf
+ * TRUE if the $data is coming from admin configuration, FALSE if it's from a URL arg.
+ *
+ * @return
+ * a Context object.
+ */
+function ctools_plugin_example_context_create_relcontext($empty, $data = NULL, $conf = FALSE) {
+ $context = new ctools_context('relcontext');
+ $context->plugin = 'relcontext';
+ if ($empty) {
+ return $context;
+ }
+ if ($conf) {
+ if (!empty($data)) {
+ $context->data = new stdClass();
+ // For this simple item we'll just create our data by stripping non-alpha and
+ // adding 'sample_relcontext_setting' to it.
+ $context->data->description = 'relcontext_from__' . preg_replace('/[^a-z]/i', '', $data['sample_relcontext_setting']);
+ $context->data->description .= '_from_configuration_sample_simplecontext_setting';
+ $context->title = t("Relcontext context from simplecontext");
+ return $context;
+ }
+ }
+ else {
+ // $data is coming from an arg - it's just a string.
+ // This is used for keyword.
+ $context->title = "relcontext_" . $data->data->description;
+ $context->argument = $data->argument;
+ // Make up a bogus context.
+ $context->data = new stdClass();
+ // For this simple item we'll just create our data by stripping non-alpha and
+ // prepend 'relcontext_' and adding '_created_from_from_simplecontext' to it.
+ $context->data->description = 'relcontext_' . preg_replace('/[^a-z]/i', '', $data->data->description);
+ $context->data->description .= '_created_from_simplecontext';
+ return $context;
+ }
+}
+
+function relcontext_settings_form($conf, $external = FALSE) {
+ $form = array();
+
+ $form['sample_relcontext_setting'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Relcontext setting'),
+ '#size' => 50,
+ '#description' => t('Just an example setting.'),
+ '#default_value' => !empty($conf['sample_relcontext_setting']) ? $conf['sample_relcontext_setting'] : '',
+ '#prefix' => '<div class="clear-block no-float">',
+ '#suffix' => '</div>',
+ );
+ return $form;
+}
+
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc
new file mode 100644
index 000000000..e19a84229
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc
@@ -0,0 +1,134 @@
+<?php
+
+
+/**
+ * @file
+ * Sample ctools context type plugin that shows how to create a context from an arg.
+ *
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Simplecontext"),
+ 'description' => t('A single "simplecontext" context, or data element.'),
+ 'context' => 'ctools_plugin_example_context_create_simplecontext', // func to create context
+ 'context name' => 'simplecontext',
+ 'settings form' => 'simplecontext_settings_form',
+ 'keyword' => 'simplecontext',
+
+ // Provides a list of items which are exposed as keywords.
+ 'convert list' => 'simplecontext_convert_list',
+ // Convert keywords into data.
+ 'convert' => 'simplecontext_convert',
+
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter some data to represent this "simplecontext".'),
+ ),
+);
+
+/**
+ * Create a context, either from manual configuration or from an argument on the URL.
+ *
+ * @param $empty
+ * If true, just return an empty context.
+ * @param $data
+ * If from settings form, an array as from a form. If from argument, a string.
+ * @param $conf
+ * TRUE if the $data is coming from admin configuration, FALSE if it's from a URL arg.
+ *
+ * @return
+ * a Context object/
+ */
+function ctools_plugin_example_context_create_simplecontext($empty, $data = NULL, $conf = FALSE) {
+ $context = new ctools_context('simplecontext');
+ $context->plugin = 'simplecontext';
+
+ if ($empty) {
+ return $context;
+ }
+
+ if ($conf) {
+ if (!empty($data)) {
+ $context->data = new stdClass();
+ // For this simple item we'll just create our data by stripping non-alpha and
+ // adding '_from_configuration_item_1' to it.
+ $context->data->item1 = t("Item1");
+ $context->data->item2 = t("Item2");
+ $context->data->description = preg_replace('/[^a-z]/i', '', $data['sample_simplecontext_setting']);
+ $context->data->description .= '_from_configuration_sample_simplecontext_setting';
+ $context->title = t("Simplecontext context from config");
+ return $context;
+ }
+ }
+ else {
+ // $data is coming from an arg - it's just a string.
+ // This is used for keyword.
+ $context->title = $data;
+ $context->argument = $data;
+ // Make up a bogus context
+ $context->data = new stdClass();
+ $context->data->item1 = t("Item1");
+ $context->data->item2 = t("Item2");
+
+ // For this simple item we'll just create our data by stripping non-alpha and
+ // adding '_from_simplecontext_argument' to it.
+ $context->data->description = preg_replace('/[^a-z]/i', '', $data);
+ $context->data->description .= '_from_simplecontext_argument';
+ $context->arg_length = strlen($context->argument);
+ return $context;
+ }
+}
+
+function simplecontext_settings_form($conf, $external = FALSE) {
+ if (empty($conf)) {
+ $conf = array(
+ 'sample_simplecontext_setting' => 'default simplecontext setting',
+ );
+ }
+ $form = array();
+ $form['sample_simplecontext_setting'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Setting for simplecontext'),
+ '#size' => 50,
+ '#description' => t('An example setting that could be used to configure a context'),
+ '#default_value' => $conf['sample_simplecontext_setting'],
+ '#prefix' => '<div class="clear-block no-float">',
+ '#suffix' => '</div>',
+ );
+ return $form;
+}
+
+
+
+/**
+ * Provide a list of sub-keywords.
+ *
+ * This is used to provide keywords from the context for use in a content type,
+ * pane, etc.
+ */
+function simplecontext_convert_list() {
+ return array(
+ 'item1' => t('Item1'),
+ 'item2' => t('Item2'),
+ 'description' => t('Description'),
+ );
+}
+
+/**
+ * Convert a context into a string to be used as a keyword by content types, etc.
+ */
+function simplecontext_convert($context, $type) {
+ switch ($type) {
+ case 'item1':
+ return $context->data->item1;
+ case 'item2':
+ return $context->data->item2;
+ case 'description':
+ return $context->data->description;
+ }
+}
+
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/panels.pages.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/panels.pages.inc
new file mode 100644
index 000000000..d3022af7f
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/panels.pages.inc
@@ -0,0 +1,214 @@
+<?php
+
+/**
+ * @file
+ * Holds the panels pages export.
+ */
+
+/**
+ * Implements hook_default_panel_pages()
+ */
+function ctools_plugin_example_default_panel_pages() {
+ $page = new stdClass();
+ $page->pid = 'new';
+ $page->did = 'new';
+ $page->name = 'ctools_plugin_example_demo_panel';
+ $page->title = 'Panels Plugin Example Demo Panel';
+ $page->access = array();
+ $page->path = 'demo_panel';
+ $page->load_flags = 1;
+ $page->css_id = '';
+ $page->arguments = array(
+ 0 =>
+ array(
+ 'name' => 'simplecontext_arg',
+ 'id' => 1,
+ 'default' => '404',
+ 'title' => '',
+ 'identifier' => 'Simplecontext arg',
+ 'keyword' => 'simplecontext',
+ ),
+ );
+ $page->relationships = array(
+ 0 =>
+ array(
+ 'context' => 'argument_simplecontext_arg_1',
+ 'name' => 'relcontext_from_simplecontext',
+ 'id' => 1,
+ 'identifier' => 'Relcontext from Simplecontext',
+ 'keyword' => 'relcontext',
+ ),
+ );
+ $page->no_blocks = '0';
+ $page->switcher_options = array();
+ $page->menu = '0';
+ $page->contexts = array();
+ $display = new ctools_display();
+ $display->did = 'new';
+ $display->layout = 'threecol_33_34_33_stacked';
+ $display->layout_settings = array();
+ $display->panel_settings = array();
+ $display->content = array();
+ $display->panels = array();
+ $pane = new stdClass();
+ $pane->pid = 'new-1';
+ $pane->panel = 'left';
+ $pane->type = 'custom';
+ $pane->shown = '1';
+ $pane->subtype = 'custom';
+ $pane->access = array();
+ $pane->configuration = array(
+ 'style' => 'default',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ 'css_id' => '',
+ 'css_class' => '',
+ 'title' => '"No Context Item"',
+ 'body' => 'The "no context item" content type is here to demonstrate that you can create a content_type that does not require a context. This is probably the same as just creating a custom php block on the fly, and might serve the same purpose.',
+ 'format' => '1',
+ );
+ $pane->cache = array();
+ $display->content['new-1'] = $pane;
+ $display->panels['left'][0] = 'new-1';
+ $pane = new stdClass();
+ $pane->pid = 'new-2';
+ $pane->panel = 'left';
+ $pane->type = 'no_context_item';
+ $pane->shown = '1';
+ $pane->subtype = 'description';
+ $pane->access = array();
+ $pane->configuration = array(
+ 'style' => 'default',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ 'css_id' => '',
+ 'css_class' => '',
+ 'item1' => 'one',
+ 'item2' => 'two',
+ 'item3' => 'three',
+ );
+ $pane->cache = array();
+ $display->content['new-2'] = $pane;
+ $display->panels['left'][1] = 'new-2';
+ $pane = new stdClass();
+ $pane->pid = 'new-3';
+ $pane->panel = 'middle';
+ $pane->type = 'custom';
+ $pane->shown = '1';
+ $pane->subtype = 'custom';
+ $pane->access = array();
+ $pane->configuration = array(
+ 'style' => 'default',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ 'css_id' => '',
+ 'css_class' => '',
+ 'title' => 'Simplecontext',
+ 'body' => 'The "Simplecontext" content and content type demonstrate a very basic context and how to display it.
+
+ Simplecontext includes configuration, so it can get info from the config. It can also get its information to run from a simplecontext context, generated either from an arg to the panels page or via explicitly adding a context to the page.',
+ 'format' => '1',
+ );
+ $pane->cache = array();
+ $display->content['new-3'] = $pane;
+ $display->panels['middle'][0] = 'new-3';
+ $pane = new stdClass();
+ $pane->pid = 'new-4';
+ $pane->panel = 'middle';
+ $pane->type = 'simplecontext_item';
+ $pane->shown = '1';
+ $pane->subtype = 'description';
+ $pane->access = array(
+ 0 => '2',
+ 1 => '4',
+ );
+ $pane->configuration = array(
+ 'context' => 'argument_simplecontext_arg_1',
+ 'style' => 'default',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ 'css_id' => '',
+ 'css_class' => '',
+ 'config_item_1' => 'simplecontext called from arg',
+ );
+ $pane->cache = array();
+ $display->content['new-4'] = $pane;
+ $display->panels['middle'][1] = 'new-4';
+ $pane = new stdClass();
+ $pane->pid = 'new-5';
+ $pane->panel = 'right';
+ $pane->type = 'custom';
+ $pane->shown = '1';
+ $pane->subtype = 'custom';
+ $pane->access = array();
+ $pane->configuration = array(
+ 'style' => 'default',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ 'css_id' => '',
+ 'css_class' => '',
+ 'title' => 'Relcontext',
+ 'body' => 'The relcontext content_type gets its data from a relcontext, which is an example of a relationship. This panel should be run with an argument like "/xxx", which allows the simplecontext to get its context, and then the relcontext is configured in this panel to get (create) its data from the simplecontext.',
+ 'format' => '1',
+ );
+ $pane->cache = array();
+ $display->content['new-5'] = $pane;
+ $display->panels['right'][0] = 'new-5';
+ $pane = new stdClass();
+ $pane->pid = 'new-6';
+ $pane->panel = 'right';
+ $pane->type = 'relcontext_item';
+ $pane->shown = '1';
+ $pane->subtype = 'description';
+ $pane->access = array();
+ $pane->configuration = array(
+ 'context' => 'relationship_relcontext_from_simplecontext_1',
+ 'style' => 'default',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ 'css_id' => '',
+ 'css_class' => '',
+ 'config_item_1' => 'default1',
+ );
+ $pane->cache = array();
+ $display->content['new-6'] = $pane;
+ $display->panels['right'][1] = 'new-6';
+ $pane = new stdClass();
+ $pane->pid = 'new-7';
+ $pane->panel = 'top';
+ $pane->type = 'custom_php';
+ $pane->shown = '1';
+ $pane->subtype = 'custom_php';
+ $pane->access = array();
+ $pane->configuration = array(
+ 'style' => 'default',
+ 'override_title' => 0,
+ 'override_title_text' => '',
+ 'css_id' => '',
+ 'css_class' => '',
+ 'title' => '',
+ 'body' => '$arg = arg(1);
+ $arg0 = arg(0);
+ if (!$arg) {
+ $block->content = <<<END
+ <em>This page is intended to run with an arg and you don\'t have one.</em>
+ <br />
+ Without an arg, the page doesn\'t have any context.
+ <br />Please try something like "/$arg0/xxx"
+END;
+
+ $block->title = "This is intended to run with an argument";
+ } else {
+ $block->content = "The arg for this page is \'$arg\'";
+ }',
+ );
+ $pane->cache = array();
+ $display->content['new-7'] = $pane;
+ $display->panels['top'][0] = 'new-7';
+ $page->display = $display;
+ $page->displays = array();
+ $pages['ctools_plugin_example'] = $page;
+
+
+ return $pages;
+}
diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc
new file mode 100644
index 000000000..622462104
--- /dev/null
+++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc
@@ -0,0 +1,50 @@
+<?php
+
+
+/**
+ * @file
+ *
+ * Sample relationship plugin.
+ *
+ * We take a simplecontext, look in it for what we need to make a relcontext, and make it.
+ * In the real world, this might be getting a taxonomy id from a node, for example.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Relcontext from simplecontext"),
+ 'keyword' => 'relcontext',
+ 'description' => t('Adds a relcontext from existing simplecontext.'),
+ 'required context' => new ctools_context_required(t('Simplecontext'), 'simplecontext'),
+ 'context' => 'ctools_relcontext_from_simplecontext_context',
+ 'settings form' => 'ctools_relcontext_from_simplecontext_settings_form',
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_relcontext_from_simplecontext_context($context = NULL, $conf) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data)) {
+ return ctools_context_create_empty('relcontext', NULL);
+ }
+
+ // You should do error-checking here.
+
+ // Create the new context from some element of the parent context.
+ // In this case, we'll pass in the whole context so it can be used to
+ // create the relcontext.
+ return ctools_context_create('relcontext', $context);
+}
+
+/**
+ * Settings form for the relationship.
+ */
+function ctools_relcontext_from_simplecontext_settings_form($conf) {
+ // We won't configure it in this case.
+ return array();
+}
+
diff --git a/sites/all/modules/ctools/drush/ctools.drush.inc b/sites/all/modules/ctools/drush/ctools.drush.inc
new file mode 100644
index 000000000..1862dbe94
--- /dev/null
+++ b/sites/all/modules/ctools/drush/ctools.drush.inc
@@ -0,0 +1,1017 @@
+<?php
+
+/**
+ * @file
+ * CTools Drush commands.
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function ctools_drush_command() {
+ $items = array();
+
+ $module_text = 'Filter the list of exportables by module. This will come from the \'export_module\' key on the exportable.';
+ $all_text = 'Perform this operation all CTools exportables available on the system (all tables).';
+
+ $items['ctools-export'] = array(
+ 'aliases' => array('ctex'),
+ 'callback' => 'ctools_drush_export',
+ 'description' => 'Export multiple CTools exportable objects directly to code.',
+ 'arguments' => array(
+ 'module' => 'Name of your module.',
+ ),
+ 'options' => array(
+ 'subdir' => 'The name of the sub directory to create the module in. Defaults to ctools_export which will be placed into sites/all/modules.',
+ 'remove' => 'Remove existing files before writing, except the .module file.',
+ 'filter' => 'Filter the list of exportables by status. Available options are enabled, disabled, overridden, database, code and all. Defaults to enabled.',
+ 'tables' => 'Comma separated list of exportable table names to filter by.',
+ ),
+ 'examples' => array(
+ 'drush ctex export_module' => 'Export CTools exportables to a module called "export_module".',
+ 'drush ctex export_module --subdir=exports' => 'Same as above, but into the sites/all/modules/exports directory.',
+ 'drush ctex export_module --subdir=exports --remove' => 'Same as above, but automatically removing all files, except for the .module file.',
+ 'drush ctex --filter="views_view"' => 'Filter export selection to the views_view table only.',
+ ),
+ );
+
+ $items['ctools-export-info'] = array(
+ 'aliases' => array('ctei'),
+ 'callback' => 'ctools_drush_export_info',
+ 'description' => 'Show available CTools exportable objects.',
+ 'arguments' => array(),
+ 'options' => array(
+ 'format' => 'Display exportables info in a different format such as print_r, json, export. The default is to show in a tabular format.',
+ 'tables-only' => 'Only show list of exportable types/table names and not available objects.',
+ 'filter' => 'Filter the list of exportables by status. Available options are enabled, disabled, overridden, database, and code.',
+ 'module' => $module_text,
+ ),
+ 'examples' => array(
+ 'drush ctools-export-info' => 'View export info on all exportables.',
+ 'drush ctools-export-info views_view variable' => 'View export info for views_view and variable exportable types only.',
+ 'drush ctei --filter=enabled' => 'Show all enabled exportables.',
+ 'drush ctei views_view --filter=disabled' => 'Show all enabled exportables.',
+ 'drush ctei views_view --module=node' => 'Show all exportables provided by/on behalf of the node module.',
+ ),
+ );
+
+ $items['ctools-export-view'] = array(
+ 'aliases' => array('ctev'),
+ 'callback' => 'ctools_drush_export_op_command',
+ 'description' => 'View CTools exportable object code output.',
+ 'arguments' => array(
+ 'table name' => 'Base table of the exportable you want to view.',
+ 'machine names' => 'Space separated list of exportables you want to view.',
+ ),
+ 'options' => array(
+ 'indent' => 'The string to use for indentation when dispalying the exportable export code. Defaults to \'\'.',
+ 'no-colour' => 'Remove any colour formatting from export string output. Ideal if you are sending the output of this command to a file.',
+ 'module' => $module_text,
+ 'all' => $all_text,
+ ),
+ 'examples' => array(
+ 'drush ctools-export-view views_view' => 'View all views exportable objects.',
+ 'drush ctools-export-view views_view archive' => 'View default views archive view.',
+ ),
+ );
+
+ $items['ctools-export-revert'] = array(
+ 'aliases' => array('cter'),
+ 'callback' => 'ctools_drush_export_op_command',
+ 'description' => 'Revert CTools exportables from changes overridden in the database.',
+ 'arguments' => array(
+ 'table name' => 'Base table of the exportable you want to revert.',
+ 'machine names' => 'Space separated list of exportables you want to revert.',
+ ),
+ 'options' => array(
+ 'module' => $module_text,
+ 'all' => $all_text,
+ ),
+ 'examples' => array(
+ 'drush ctools-export-revert views_view' => 'Revert all overridden views exportable objects.',
+ 'drush ctools-export-revert views_view archive' => 'Revert overridden default views archive view.',
+ 'drush ctools-export-revert --all' => 'Revert all exportables on the system.',
+ ),
+ );
+
+ $items['ctools-export-enable'] = array(
+ 'aliases' => array('ctee'),
+ 'callback' => 'ctools_drush_export_op_command',
+ 'description' => 'Enable CTools exportables.',
+ 'arguments' => array(
+ 'table name' => 'Base table of the exportable you want to enable.',
+ 'machine names' => 'Space separated list of exportables you want to enable.',
+ ),
+ 'options' => array(
+ 'module' => $module_text,
+ 'all' => $all_text,
+ ),
+ 'examples' => array(
+ 'drush ctools-export-enable views_view' => 'Enable all overridden views exportable objects.',
+ 'drush ctools-export-enable views_view archive' => 'Enable overridden default views archive view.',
+ ),
+ );
+
+ $items['ctools-export-disable'] = array(
+ 'aliases' => array('cted'),
+ 'callback' => 'ctools_drush_export_op_command',
+ 'description' => 'Disable CTools exportables.',
+ 'arguments' => array(
+ 'table name' => 'Base table of the exportable you want to disable.',
+ 'machine names' => 'Space separated list of exportables you want to disable.',
+ ),
+ 'options' => array(
+ 'module' => $module_text,
+ 'all' => $all_text,
+ ),
+ 'examples' => array(
+ 'drush ctools-export-disable views_view' => 'Disable all overridden views exportable objects.',
+ 'drush ctools-export-disable views_view archive' => 'Disable overridden default views archive view.',
+ ),
+ );
+
+ return $items;
+}
+
+/**
+ * Implementation of hook_drush_help().
+ */
+function ctools_drush_help($section) {
+ switch ($section) {
+ case 'meta:ctools:title':
+ return dt('CTools commands');
+ case 'meta:entity:summary':
+ return dt('CTools drush commands.');
+ }
+}
+
+/**
+ * Drush callback: export
+ */
+function ctools_drush_export($module = 'foo') {
+ $error = FALSE;
+ if (preg_match('@[^a-z_]+@', $module)) {
+ $error = dt('The name of the module must contain only lowercase letters and underscores') . '.';
+ drush_log($error, 'error');
+ return;
+ }
+
+ // Filter by tables.
+ $tables = _ctools_drush_explode_options('tables');
+
+ // Check status.
+ $filter = drush_get_option('filter', FALSE);
+ if (empty($filter)) {
+ drush_set_option('filter', 'enabled');
+ }
+
+ // Selection.
+ $options = array('all' => dt('Export everything'), 'select' => dt('Make selection'));
+ $selection = drush_choice($options, dt('Select to proceed'));
+
+ if (!$selection) {
+ return;
+ }
+
+ // Present the selection screens.
+ if ($selection == 'select') {
+ $selections = _ctools_drush_selection_screen($tables);
+ }
+ else {
+ $info = _ctools_drush_export_info($tables, TRUE);
+ $selections = $info['exportables'];
+ }
+
+ // Subdirectory.
+ $dest_exists = FALSE;
+ $subdir = drush_get_option('subdir', 'ctools_export');
+ $dest = 'sites/all/modules/' . $subdir . '/' . $module;
+
+ // Overwriting files can be set with 'remove' argument.
+ $remove = drush_get_option('remove', FALSE);
+
+ // Check if folder exists.
+ if (file_exists($dest)) {
+ $dest_exists = TRUE;
+ if ($remove) {
+ if (drush_confirm(dt('All files except for the .info and .module files in !module will be removed. You can choose later if you want to overwrite these files as well. Are you sure you want to proceed ?', array('!module' => $module)))) {
+ $remove = TRUE;
+ drush_log(dt('Files will be removed'), 'success');
+ }
+ else {
+ drush_log(dt('Export aborted.'), 'success');
+ return;
+ }
+ }
+ }
+
+ // Remove files (except for the .module file) if the destination folder exists.
+ if ($remove && $dest_exists) {
+ _ctools_drush_file_delete($dest);
+ }
+
+ // Create new dir if needed.
+ if (!$dest_exists) {
+ if (!file_exists('sites/all/modules/' . $subdir)) {
+ drush_mkdir('sites/all/modules/' . $subdir);
+ }
+ }
+
+ // Create destination directory.
+ drush_mkdir($dest);
+
+ // Load bulk export module.
+ module_load_include('module', 'bulk_export');
+
+ // Create options and call Bulk export function.
+ // We create an array, because maybe in the future we can pass in more
+ // options to the export function (pre-selected modules and/or exportables).
+ $options = array(
+ 'name' => $module,
+ 'selections' => $selections,
+ );
+ $files = bulk_export_export(TRUE, $options);
+
+ $alter = array(
+ 'module' => $module,
+ 'files' => $files,
+ );
+ // Let other drush commands alter the files.
+ drush_command_invoke_all_ref('drush_ctex_files_alter', $alter);
+ $files = $alter['files'];
+
+ // Start writing.
+ if (is_array($files)) {
+ foreach ($files as $base_file => $data) {
+ $filename = $dest . '/' . $base_file;
+ // Extra check for .module file.
+ if ($base_file == ($module . '.module' || $module . '.info') && file_exists($filename)) {
+ if (!drush_confirm(dt('Do you want to overwrite !module_file', array('!module_file' => $base_file)))) {
+ drush_log(dt('Writing of !filename skipped. This is the code that was supposed to be written:', array('!filename' => $filename)), 'success');
+ drush_print('---------');
+ drush_print(shellColours::getColouredOutput("\n$data", 'light_green'));
+ drush_print('---------');
+ continue;
+ }
+ }
+ if (file_put_contents($filename, $data)) {
+ drush_log(dt('Succesfully written !filename', array('!filename' => $filename)), 'success');
+ }
+ else {
+ drush_log(dt('Error writing !filename', array('!filename' => $filename)), 'error');
+ }
+ }
+ }
+ else {
+ drush_log(dt('No files were found to be written.'), 'error');
+ }
+}
+
+/**
+ * Helper function to select the exportables. By default, all exportables
+ * will be selected, so it will be easier to deselect them.
+ *
+ * @param $tables
+ */
+function _ctools_drush_selection_screen(array $tables = array()) {
+ $selections = $build = array();
+ $files = system_rebuild_module_data();
+
+ $selection_number = 0;
+
+ $info = _ctools_drush_export_info($tables, TRUE);
+ $exportables = $info['exportables'];
+ $schemas = $info['schemas'];
+
+ $export_tables = array();
+
+ foreach (array_keys($exportables) as $table) {
+ natcasesort($exportables[$table]);
+ $export_tables[$table] = $files[$schemas[$table]['module']]->info['name'] . ' (' . $table . ')';
+ }
+
+ foreach ($export_tables as $table => $table_title) {
+ if (!empty($exportables[$table])) {
+ $table_count = count($exportables[$table]);
+ $selection_number += $table_count;
+ foreach ($exportables[$table] as $key => $title) {
+ $build[$table]['title'] = $table_title;
+ $build[$table]['items'][$key] = $title;
+ $build[$table]['count'] = $table_count;
+ $selections[$table][$key] = $key;
+ }
+ }
+ }
+
+ drush_print(dt('Number of exportables selected: !number', array('!number' => $selection_number)));
+ drush_print(dt('By default all exportables are selected. Select a table to deselect exportables. Select "cancel" to start writing the files.'));
+
+ // Let's go into a loop.
+ $return = FALSE;
+ while (!$return) {
+
+ // Present the tables choice.
+ $table_rows = array();
+ foreach ($build as $table => $info) {
+ $table_rows[$table] = $info['title'] . ' (' . $info['count'] . ')';
+ }
+ $table_choice = drush_choice($table_rows, dt('Select a table. Select cancel to start writing files.'));
+
+ // Bail out.
+ if (!$table_choice) {
+ drush_log(dt('Selection mode done, starting to write the files.'), 'notice');
+ $return = TRUE;
+ return $selections;
+ }
+
+ // Present the exportables choice, using the drush_choice_multiple.
+ $max = count($build[$table_choice]['items']);
+ $exportable_rows = array();
+ foreach ($build[$table_choice]['items'] as $key => $title) {
+ $exportable_rows[$key] = $title;
+ }
+ drush_print(dt('Exportables from !table', array('!table' => $build[$table_choice]['title'])));
+ $multi_select = drush_choice_multiple($exportable_rows, $selections[$table_choice], dt('Select exportables.'), '!value', '!value (selected)', 0, $max);
+
+ // Update selections.
+ if (is_array($multi_select)) {
+ $build[$table_choice]['count'] = count($multi_select);
+ $selections[$table_choice] = array();
+ foreach ($multi_select as $key) {
+ $selections[$table_choice][$key] = $key;
+ }
+ }
+ }
+}
+
+/**
+ * Delete files in a directory, keeping the .module and .info files.
+ *
+ * @param $path
+ * Path to directory in which to remove files.
+ */
+function _ctools_drush_file_delete($path) {
+ if (is_dir($path)) {
+ $files = new DirectoryIterator($path);
+ foreach ($files as $fileInfo) {
+ if (!$fileInfo->isDot() && !in_array($fileInfo->getExtension(), array('module', 'info'))) {
+ unlink($fileInfo->getPathname());
+ }
+ }
+ }
+}
+
+/**
+ * Drush callback: Export info.
+ *
+ * @params $table_names
+ * Each argument will be taken as a CTools exportable table name.
+ */
+function ctools_drush_export_info() {
+ // Collect array of table names from args.
+ $table_names = func_get_args();
+
+ // Get format option to allow for alternative output.
+ $format = drush_get_option('format', FALSE);
+ $tables_only = drush_get_option('tables-only', FALSE);
+ $filter = drush_get_option('filter', FALSE);
+ $export_module = drush_get_option('module', FALSE);
+
+ $load = (bool) $filter || $export_module;
+
+ // Get info on these tables, or all tables if none specified.
+ $info = _ctools_drush_export_info($table_names, $load);
+ $schemas = $info['schemas'];
+ $exportables = $info['exportables'];
+
+ if (empty($exportables)) {
+ drush_log(dt('There are no exportables available.'), 'warning');
+ return;
+ }
+
+ // Filter by export module.
+ if (is_string($export_module)) {
+ $exportables = _ctools_drush_export_module_filter($exportables, $export_module);
+ }
+
+ if (empty($exportables)) {
+ drush_log(dt('There are no exportables matching this criteria.'), 'notice');
+ return;
+ }
+
+ $exportable_counts = _ctools_drush_count_exportables($exportables);
+
+ // Only use array keys if --tables-only option is set.
+ if ($tables_only) {
+ $exportables = array_keys($exportables);
+ }
+
+ // Use format from --format option if it's present, and send to drush_format.
+ if ($format) {
+ // Create array with all exportable info and counts in one.
+ $output = array(
+ 'exportables' => $exportables,
+ 'count' => $exportable_counts,
+ );
+ drush_print(drush_format($output, NULL, $format));
+ }
+ // Build a tabular output as default.
+ else {
+ $header = $tables_only ? array() : array(dt('Module'), dt('Base table'), dt('Exportables'));
+ $rows = array();
+ foreach ($exportables as $table => $info) {
+ if (is_array($info)) {
+ $row = array(
+ $schemas[$table]['module'],
+ $table,
+ // Machine name is better for this?
+ shellColours::getColouredOutput(implode("\n", array_keys($info)), 'light_green') . "\n",
+ );
+ $rows[] = $row;
+ }
+ else {
+ $rows[] = array($info);
+ }
+ }
+ if (!empty($rows)) {
+ drush_print("\n");
+ array_unshift($rows, $header);
+ drush_print_table($rows, TRUE, array(20, 20));
+ drush_print(dt('Total exportables found: !total', array('!total' => $exportable_counts['total'])));
+ foreach ($exportable_counts['exportables'] as $table_name => $count) {
+ drush_print(dt('!table_name (!count)', array('!table_name' => $table_name, '!count' => $count)), 2);
+ }
+ drush_print("\n");
+ }
+ }
+}
+/**
+ * Drush callback: Acts as the hub for all op commands to keep
+ * all arg handling etc in one place.
+ */
+function ctools_drush_export_op_command() {
+ // Get all info for the current drush command.
+ $command = drush_get_command();
+ $op = '';
+
+ switch ($command['command']) {
+ case 'ctools-export-view':
+ $op = 'view';
+ break;
+ case 'ctools-export-revert':
+ // Revert is same as deleting. As any objects in the db are deleted.
+ $op = 'delete';
+ break;
+ case 'ctools-export-enable':
+ $op = 'enable';
+ break;
+ case 'ctools-export-disable':
+ $op = 'disable';
+ break;
+ }
+
+ if (!$op) {
+ return;
+ }
+
+ if (drush_get_option('all', FALSE)) {
+ $info = _ctools_drush_export_info('', TRUE);
+ $exportable_info = $info['exportables'];
+
+ $all = drush_confirm(dt('Are you sure you would like to !op all exportables on the system?',
+ array('!op' => _ctools_drush_export_op_alias($op))));
+
+ if ($all && $exportable_info) {
+ foreach ($exportable_info as $table => $exportables) {
+ if (!empty($exportables)) {
+ ctools_drush_export_op($op, $table, $exportables);
+ }
+ }
+ }
+ }
+ else {
+ $args = func_get_args();
+ // Table name should always be first arg...
+ $table_name = array_shift($args);
+ // Any additional args are assumed to be exportable names.
+ $object_names = $args;
+
+ // Return any exportables based on table name, object names, options.
+ $exportables = _ctools_drush_export_op_command_logic($op, $table_name, $object_names);
+
+ if ($exportables) {
+ ctools_drush_export_op($op, $table_name, $exportables);
+ }
+ }
+}
+
+/**
+ * Iterate through exportable object names, load them, and pass each
+ * object to the correct op function.
+ *
+ * @param $op
+ * @param $table_name
+ * @param $exportables
+ *
+ */
+function ctools_drush_export_op($op = '', $table_name = '', $exportables = NULL) {
+ $objects = ctools_export_crud_load_multiple($table_name, array_keys($exportables));
+
+ $function = '_ctools_drush_export_' . $op;
+ if (function_exists($function)) {
+ foreach ($objects as $object) {
+ $function($table_name, $object);
+ }
+ }
+ else {
+ drush_log(dt('CTools CRUD function !function doesn\'t exist.',
+ array('!function' => $function)), 'error');
+ }
+}
+
+/**
+ * Helper function to abstract logic for selecting exportable types/objects
+ * from individual commands as they will all share this same error
+ * handling/arguments for returning list of exportables.
+ *
+ * @param $table_name
+ * @param $object_names
+ *
+ * @return
+ * Array of exportable objects (filtered if necessary, by name etc..) or FALSE if not.
+ */
+function _ctools_drush_export_op_command_logic($op = '', $table_name = NULL, array $object_names = array()) {
+ if (!$table_name) {
+ drush_log(dt('Exportable table name empty. Use the --all command if you want to perform this operation on all tables.'), 'error');
+ return FALSE;
+ }
+
+ // Get export info based on table name.
+ $info = _ctools_drush_export_info(array($table_name), TRUE);
+
+ if (!isset($info['exportables'][$table_name])) {
+ drush_log(dt('Exportable table name not found.'), 'error');
+ return FALSE;
+ }
+
+ $exportables = &$info['exportables'];
+
+ if (empty($object_names)) {
+ $all = drush_confirm(dt('No object names entered. Would you like to try and !op all exportables of type !type',
+ array('!op' => _ctools_drush_export_op_alias($op), '!type' => $table_name)));
+ if (!$all) {
+ drush_log(dt('Command cancelled'), 'success');
+ return FALSE;
+ }
+ }
+ else {
+ // Iterate through object names and check they exist in exportables array.
+ // Log error and unset them if they don't.
+ foreach ($object_names as $object_name) {
+ if (!isset($exportables[$table_name][$object_name])) {
+ drush_log(dt('Invalid exportable: !exportable', array('!exportable' => $object_name)), 'error');
+ unset($object_names[$table_name][$object_name]);
+ }
+ }
+ // Iterate through exportables to get just a list of selected ones.
+ foreach (array_keys($exportables[$table_name]) as $exportable) {
+ if (!in_array($exportable, $object_names)) {
+ unset($exportables[$table_name][$exportable]);
+ }
+ }
+ }
+
+ $export_module = drush_get_option('module', FALSE);
+
+ if (is_string($export_module)) {
+ $exportables = _ctools_drush_export_module_filter($exportables, $export_module);
+ }
+
+ return $exportables[$table_name];
+}
+
+/**
+ * Return array of CTools exportable info based on available tables returned from
+ * ctools_export_get_schemas().
+ *
+ * @param $table_names
+ * Array of table names to return.
+ * @param $load
+ * (bool) should ctools exportable objects be loaded for each type.
+ * The default behaviour will load just a list of exportable names.
+ *
+ * @return
+ * Nested arrays of available exportables, keyed by table name.
+ */
+function _ctools_drush_export_info(array $table_names = array(), $load = FALSE) {
+ ctools_include('export');
+ // Get available schemas that declare exports.
+ $schemas = ctools_export_get_schemas(TRUE);
+ $exportables = array();
+
+ if (!empty($schemas)) {
+ // Remove types we don't want, if any.
+ if (!empty($table_names)) {
+ foreach (array_keys($schemas) as $table_name) {
+ if (!in_array($table_name, $table_names)) {
+ unset($schemas[$table_name]);
+ }
+ }
+ }
+ // Load array of available exportables for each schema.
+ foreach ($schemas as $table_name => $schema) {
+ // Load all objects.
+ if ($load) {
+ $exportables[$table_name] = ctools_export_crud_load_all($table_name);
+ }
+ // Get a list of exportable names.
+ else {
+ if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) {
+ $exportables[$table_name] = $schema['export']['list callback']();
+ }
+ else {
+ $exportables[$table_name] = ctools_export_default_list($table_name, $schema);
+ }
+ }
+ }
+ }
+
+ if ($load) {
+ $filter = drush_get_option('filter', FALSE);
+ $exportables = _ctools_drush_filter_exportables($exportables, $filter);
+ }
+
+ return array('exportables' => $exportables, 'schemas' => $schemas);
+}
+
+/*
+ * View a single object.
+ *
+ * @param $table_name
+ * @param $object
+ */
+function _ctools_drush_export_view($table_name, $object) {
+ $indent = drush_get_option('indent', '');
+ $no_colour = drush_get_option('no-colour', FALSE);
+ $export = ctools_export_crud_export($table_name, $object, $indent);
+ if ($no_colour) {
+ drush_print("\n$export");
+ }
+ else {
+ drush_print(shellColours::getColouredOutput("\n$export", 'light_green'));
+ }
+}
+
+/*
+ * Revert a single object.
+ *
+ * @param $table_name
+ * @param $object
+ */
+function _ctools_drush_export_delete($table_name, $object) {
+ $name = _ctools_drush_get_export_name($table_name, $object);
+
+ if (_ctools_drush_object_is_overridden($object)) {
+ // Remove from db.
+ ctools_export_crud_delete($table_name, $object);
+ drush_log("Reverted object: $name", 'success');
+ }
+ else {
+ drush_log("Nothing to revert for: $name", 'notice');
+ }
+}
+
+/*
+ * Enable a single object.
+ *
+ * @param $table_name
+ * @param $object
+ */
+function _ctools_drush_export_enable($table_name, $object) {
+ $name = _ctools_drush_get_export_name($table_name, $object);
+
+ if (_ctools_drush_object_is_disabled($object)) {
+
+ // Enable object.
+ ctools_export_crud_enable($table_name, $object);
+ drush_log("Enabled object: $name", 'success');
+ }
+ else {
+ drush_log("$name is already Enabled", 'notice');
+ }
+}
+
+/*
+ * Disable a single object.
+ *
+ * @param $table_name
+ * @param $object
+ */
+function _ctools_drush_export_disable($table_name, $object) {
+ $name = _ctools_drush_get_export_name($table_name, $object);
+
+ if (!_ctools_drush_object_is_disabled($object)) {
+ // Disable object.
+ ctools_export_crud_disable($table_name, $object);
+ drush_log("Disabled object: $name", 'success');
+ }
+ else {
+ drush_log("$name is already disabled", 'notice');
+ }
+}
+
+/**
+ * Filter a nested array of exportables by export module.
+ *
+ * @param $exportables array
+ * Passed by reference. A nested array of exportables, keyed by table name.
+ * @param $export_module string
+ * The name of the export module providing the exportable.
+ */
+function _ctools_drush_export_module_filter($exportables, $export_module) {
+ $module_list = module_list();
+
+ if (!isset($module_list[$export_module])) {
+ drush_log(dt('Invalid export module: !export_module', array('!export_module' => $export_module)), 'error');
+ }
+
+ foreach ($exportables as $table => $objects) {
+ foreach ($objects as $key => $object) {
+ if (empty($object->export_module) || ($object->export_module !== $export_module)) {
+ unset($exportables[$table][$key]);
+ }
+ }
+ }
+
+ return array_filter($exportables);
+}
+
+/**
+ * Gets the key for an exportable type.
+ *
+ * @param $table_name
+ * The exportable table name.
+ * @param $object
+ * The exportable object.
+ *
+ * @return string
+ * The key defined in the export schema data.
+ */
+function _ctools_drush_get_export_name($table_name, $object) {
+ $info = _ctools_drush_export_info(array($table_name));
+ $key = $info['schemas'][$table_name]['export']['key'];
+ return $object->{$key};
+}
+
+/**
+ * Determine if an object is disabled.
+ *
+ * @param $object
+ * Loaded CTools exportable object.
+ *
+ * @return TRUE or FALSE
+ */
+function _ctools_drush_object_is_disabled($object) {
+ return (isset($object->disabled) && ($object->disabled == TRUE)) ? TRUE : FALSE;
+}
+
+/**
+ * Determine if an object is enabled.
+ *
+ * @see _ctools_drush_object_is_disabled.
+ */
+function _ctools_drush_object_is_enabled($object) {
+ return (empty($object->disabled)) ? TRUE : FALSE;
+}
+
+/**
+ * Determine if an object is overridden.
+ */
+function _ctools_drush_object_is_overridden($object) {
+ $status = EXPORT_IN_CODE + EXPORT_IN_DATABASE;
+ return ($object->export_type == $status) ? TRUE : FALSE;
+}
+
+/**
+ * Determine if an object is not overridden.
+ */
+function _ctools_drush_object_is_not_overridden($object) {
+ $status = EXPORT_IN_CODE + EXPORT_IN_DATABASE;
+ return ($object->export_type == $status) ? FALSE : TRUE;
+}
+
+/**
+ * Determine if an object is only in the db.
+ */
+function _ctools_drush_object_is_db_only($object) {
+ return ($object->export_type == EXPORT_IN_DATABASE) ? TRUE : FALSE;
+}
+
+/**
+ * Determine if an object is not in the db.
+ */
+function _ctools_drush_object_is_not_db_only($object) {
+ return ($object->export_type == EXPORT_IN_DATABASE) ? FALSE : TRUE;
+}
+
+/**
+ * Determine if an object is a code only default.
+ */
+function _ctools_drush_object_is_code_only($object) {
+ return ($object->export_type == EXPORT_IN_CODE) ? TRUE : FALSE;
+}
+
+/**
+ * Determine if an object is not a code only default.
+ */
+function _ctools_drush_object_is_not_code_only($object) {
+ return ($object->export_type == EXPORT_IN_CODE) ? FALSE : TRUE;
+}
+
+/**
+ * Return an array of count information based on exportables array.
+ *
+ * @param $exportables
+ * Array of exportables to count.
+ *
+ * @return
+ * Array of count data containing the following:
+ * 'total' - A total count of all exportables.
+ * 'exportables' - An array of exportable counts per table.
+ */
+function _ctools_drush_count_exportables($exportables) {
+ $count = array('exportables' => array());
+
+ foreach ($exportables as $table => $objects) {
+ // Add the object count for each table.
+ $count['exportables'][$table] = count($objects);
+ }
+
+ // Once all tables have been counted, total these up.
+ $count['total'] = array_sum($count['exportables']);
+
+ return $count;
+}
+
+/**
+ * Filters a collection of exportables based on filters.
+ *
+ * @param $exportables
+ * @param $filter
+ */
+function _ctools_drush_filter_exportables($exportables, $filter) {
+ $eval = FALSE;
+
+ if (is_string($filter)) {
+ switch ($filter) {
+ // Show enabled exportables only.
+ case 'enabled':
+ $eval = '_ctools_drush_object_is_disabled';
+ break;
+ // Show disabled exportables only.
+ case 'disabled':
+ $eval = '_ctools_drush_object_is_enabled';
+ break;
+ // Show overridden exportables only.
+ case 'overridden':
+ $eval = '_ctools_drush_object_is_not_overridden';
+ break;
+ // Show database only exportables.
+ case 'database':
+ $eval = '_ctools_drush_object_is_not_db_only';
+ break;
+ // Show code only exportables.
+ case 'code':
+ $eval = '_ctools_drush_object_is_not_code_only';
+ break;
+ // Do nothing.
+ case 'all':
+ break;
+ default:
+ drush_log(dt('Invalid filter option. Available options are: enabled, disabled, overridden, database, and code.'), 'error');
+ return;
+ }
+
+ if ($eval) {
+ foreach ($exportables as $table => $objects) {
+ foreach ($objects as $key => $object) {
+ if ($eval($object)) {
+ unset($exportables[$table][$key]);
+ }
+ }
+ }
+ }
+ }
+
+ return array_filter($exportables);
+}
+
+/**
+ * Return an alias for an op, that will be used to show as output.
+ * For now, this is mainly necessary for delete => revert alias.
+ *
+ * @param $op
+ * The op name. Such as 'enable', 'disable', or 'delete'.
+ *
+ * @return
+ * The matched alias value or the original $op passed in if not found.
+ */
+function _ctools_drush_export_op_alias($op) {
+ $aliases = array(
+ 'delete' => 'revert',
+ );
+
+ if (isset($aliases[$op])) {
+ return $aliases[$op];
+ }
+
+ return $op;
+}
+
+/**
+ * Convert the drush options from a csv list into an array.
+ *
+ * @param $drush_option
+ * The drush option name to invoke.
+ *
+ * @return
+ * Exploded array of options.
+ */
+function _ctools_drush_explode_options($drush_option) {
+ $options = drush_get_option($drush_option, array());
+ if (!empty($options)) {
+ $options = explode(',', $options);
+ return array_map('trim', $options);
+ }
+
+ return $options;
+}
+
+/**
+ * Class to deal with wrapping output strings with
+ * colour formatting for the shell.
+ */
+class shellColours {
+
+ private static $foreground_colours = array(
+ 'black' => '0;30',
+ 'dark_gray' => '1;30',
+ 'blue' => '0;34',
+ 'light_blue' => '1;34',
+ 'green' => '0;32',
+ 'light_green' => '1;32',
+ 'cyan' => '0;36',
+ 'light_cyan' => '1;36',
+ 'red' => '0;31',
+ 'light_red' => '1;31',
+ 'purple' => '0;35',
+ 'light_purple' => '1;35',
+ 'brown' => '0;33',
+ 'yellow' => '1;33',
+ 'light_gray' => '0;37',
+ 'white' => '1;37',
+ );
+
+ private static $background_colours = array(
+ 'black' => '40',
+ 'red' => '41',
+ 'green' => '42',
+ 'yellow' => '43',
+ 'blue' => '44',
+ 'magenta' => '45',
+ 'cyan' => '46',
+ 'light_gray' => '47',
+ );
+
+ private function __construct() {}
+
+ // Returns coloured string
+ public static function getColouredOutput($string, $foreground_colour = NULL, $background_colour = NULL) {
+ $coloured_string = "";
+
+ // Check if given foreground colour found
+ if ($foreground_colour) {
+ $coloured_string .= "\033[" . self::$foreground_colours[$foreground_colour] . "m";
+ }
+ // Check if given background colour found
+ if ($background_colour) {
+ $coloured_string .= "\033[" . self::$background_colours[$background_colour] . "m";
+ }
+
+ // Add string and end colouring
+ $coloured_string .= $string . "\033[0m";
+
+ return $coloured_string;
+ }
+
+ // Returns all foreground colour names
+ public static function getForegroundColours() {
+ return array_keys(self::$foreground_colours);
+ }
+
+ // Returns all background colour names
+ public static function getBackgroundColours() {
+ return array_keys(self::$background_colours);
+ }
+
+} // shellColours
diff --git a/sites/all/modules/ctools/help/about.html b/sites/all/modules/ctools/help/about.html
new file mode 100644
index 000000000..30b64c2d3
--- /dev/null
+++ b/sites/all/modules/ctools/help/about.html
@@ -0,0 +1,29 @@
+<p>The Chaos Tool Suite is a series of tools for developers to make code that I've found to be very useful to Views and Panels more readily available. Certain methods of doing things, particularly with AJAX, exportable objects and a plugin system, are proving to be ideas that are useful outside of just Views and Panels. This module does not offer much directly to the end user, but instead, creates a library for other modules to use. If you are an end user and some module asked you to install the CTools suite, then this is far as you really need to go. If you're a developer and are interested in these tools, read on!</p>
+
+<h2>Tools provided by CTools</h2>
+
+<dl>
+<dt><a href="&topic:ctools/plugins&">Plugins</a></dt>
+<dd>The plugins tool allows a module to allow <b>other</b> modules (and themes!) to provide plugins which provide some kind of functionality or some kind of task. For example, in Panels there are several types of plugins: Content types (which are like blocks), layouts (which are page layouts) and styles (which can be used to style a panel). Each plugin is represented by a .inc file, and the functionality they offer can differ wildly.</dd>
+
+<dt><a href="&topic:ctools/context&">Context</a></dt>
+<dd>Context is the idea that the objects that are used in page generation have more value than simply creating a single piece of output. Instead, contexts can be used to create multiple pieces of content that can all be put onto the page. Additionally, contexts can be used to derive other contexts via relationships, such as determining the node author and displaying data about the new context.</dd>
+
+<dt><a href="&topic:ctools/ajax&">AJAX Tools</a></dt>
+<dd>AJAX (also known as AHAH) is a method of allowing the browser and the server to communicate without requiring a page refresh. It can be used to create complicated interactive forms, but it is somewhat difficult to integrate into Drupal's Form API. These tools make it easier to accomplish this goal. In addition, CTools provides a few other javascript helpers, such as a modal dialog, a collapsible div, a simple dropdown and dependent checkboxes.</dd>
+
+<dt><a href="&topic:ctools/css&">CSS scrubbing and caching</a></dt>
+<dd>Drupal comes with a fantastic array of tools to ensure HTML is safe to output but does not contain any similar tools for CSS. CTools provides a small tool to sanitize CSS, so user-input CSS code can still be safely used. It also provides a method for caching CSS for better performance.</dd>
+
+<dt><a href="&topic:ctools/export&">Exportable objects</a></dt>
+<dd>Views and Panels both use objects that can either be in code or in the database, and the objects can be exported into a piece of PHP code, so they can be moved from site to site or out of the database entirely. This library abstracts that functionality, so other modules can use this same concept for their data.</dd>
+
+<dt><a href="&topic:ctools/form&">Form tools</a></dt>
+<dd>Drupal 6's FAPI really improved over Drupal 5, and made a lot of things possible. Still, it missed a few items that were needed to make form wizards and truly dynamic AJAX forms possible. CTools includes a replacement for drupal_get_form() that has a few more options and allows the caller to examine the $form_state once the form has completed.</dd>
+
+<dt><a href="&topic:ctools/wizard&">Form wizards</a></dt>
+<dd>Finally! An easy way to have form wizards, which is any 'form' that is actually a string of forms that build up to a final conclusion. The form wizard supports a single entry point, the ability to choose whether or not the user can go forward/back/up on the form and easy callbacks to handle the difficult job of dealing with data in between forms.</dd>
+
+<dt><a href="&topic:ctools/object-cache&">Temporary object cache</a></dt>
+<dd>For normal forms, all of the data needed for an object is stored in the form so that the browser handles a lot of the work. For multi-step and ajax forms, however, this is impractical, and letting the browser store data can be insecure. The object cache provides a non-volatile location to store temporary data while the form is being worked on. This is much safer than the standard Drupal caching mechanism, which is volatile, meaning it can be cleared at any time and any system using it must be capable of recreating the data that was there. This system also allows for object locking, since any object which has an item in the cache from another person can be assumed to be 'locked for editing'.</dd>
+</dl>
diff --git a/sites/all/modules/ctools/help/ajax.html b/sites/all/modules/ctools/help/ajax.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/sites/all/modules/ctools/help/ajax.html
diff --git a/sites/all/modules/ctools/help/collapsible-div.html b/sites/all/modules/ctools/help/collapsible-div.html
new file mode 100644
index 000000000..b9b6d9c63
--- /dev/null
+++ b/sites/all/modules/ctools/help/collapsible-div.html
@@ -0,0 +1 @@
+<p>To be written.</p>
diff --git a/sites/all/modules/ctools/help/context-access.html b/sites/all/modules/ctools/help/context-access.html
new file mode 100644
index 000000000..95a8d7fbe
--- /dev/null
+++ b/sites/all/modules/ctools/help/context-access.html
@@ -0,0 +1,12 @@
+<p>Access plugins allow context based access control to pages.</p>
+<pre> 'title' => Title of the plugin
+ 'description' => Description of the plugin
+ 'callback' => callback to see if there is access is available. params: $conf, $contexts, $account
+ 'required context' => zero or more required contexts for this access plugin
+ 'default' => an array of defaults or a callback giving defaults
+ 'settings form' => settings form. params: &$form, &$form_state, $conf
+ settings form validate
+ settings form submit
+</pre>
+
+<p><strong>Warning:</strong> your settings array will be stored <strong>in the meny system</strong> to reduce loads, so be <strong>trim</strong>.</p> \ No newline at end of file
diff --git a/sites/all/modules/ctools/help/context-arguments.html b/sites/all/modules/ctools/help/context-arguments.html
new file mode 100644
index 000000000..5c479ae65
--- /dev/null
+++ b/sites/all/modules/ctools/help/context-arguments.html
@@ -0,0 +1,14 @@
+<p>Arguments create a context from external input, which is assumed to be a string as though it came from a URL element.</p>
+
+<pre>'title' => title
+ 'description' => Description
+ 'keyword' => Default keyword for the context
+ 'context' => Callback to create the context. Params: $arg = NULL, $conf = NULL, $empty = FALSE
+ 'default' => either an array of default settings or a string which is a callback or null to not use.
+ 'settings form' => params: $form, $form_state, $conf -- gets the whole form. Should put anything it wants to keep automatically in $form['settings']
+ 'settings form validate' => params: $form, $form_state
+ 'settings form submit' => params: $form, $form_state
+ 'criteria form' => params: $form, &$form_state, $conf, $argument, $id -- gets the whole argument. It should only put form widgets in $form[$id]. $conf may not be properly initialized so always guard against this due to arguments being changed and handlers not being updated to match.
+ + submit + validate
+ 'criteria select' => returns true if the selected criteria matches the context. params: $context, $conf
+</pre>
diff --git a/sites/all/modules/ctools/help/context-content.html b/sites/all/modules/ctools/help/context-content.html
new file mode 100644
index 000000000..c1c6a356d
--- /dev/null
+++ b/sites/all/modules/ctools/help/context-content.html
@@ -0,0 +1,157 @@
+<p>The CTools pluggable content system provides various pieces of content as discrete bits of data that can be added to other applications, such as Panels or Dashboard via the UI. Whatever the content is added to stores the configuration for that individual piece of content, and provides this to the content.</p>
+
+<p>Each content type plugin will be contained in a .inc file, with subsidiary files, if necessary, in or near the same directory. Each content type consists of some information and one or more subtypes, which all use the same renderer. Subtypes are considered to be instances of the type. For example, the 'views' content type would have each view in the system as a subtype. Many content types will have exactly one subtype.</p>
+
+<p>Because the content and forms can be provided via ajax, the plugin also provides a list of CSS and JavaScript information that should be available on whatever page the content or forms may be AJAXed onto.</p>
+
+<p>For the purposes of selecting content from the UI, each content subtype will have the following information:</p>
+
+<ul>
+ <li>A title</li>
+ <li>A short description</li>
+ <li>A category [Do we want to add hierarchy categories? Do we want category to be more than just a string?]</li>
+ <li>An icon [do we want multiple icons? This becomes a hefty requirement]</li>
+</ul>
+
+<p>Each piece of content provides one or more configuration forms, if necessary, and the system that includes the content will handle the data storage. These forms can be provided in sequence as wizards or as extra forms that can be accessed through advanced administration.</p>
+
+<p>The plugin for a content type should contain:</p>
+
+<dl>
+ <dt>title</dt>
+ <dd>For use on the content permissions screen.</dd>
+ <dt>content types</dt>
+ <dd>Either an array of content type definitions, or a callback that will return content type definitions. This callback will get the plugin definition as an argument.</dd>
+
+ <dt>content type</dt>
+ <dd>[Optional] Provide a single content type definition. This is only necessary if content types might be intensive.</dd>
+
+ <dt>render callback</dt>
+ <dd>The callback to render the content. Parameters:
+ <dl>
+ <dt>$subtype</dt>
+ <dd>The name of the subtype being rendered. NOT the loaded subtype data.</dd>
+
+ <dt>$conf</dt>
+ <dd>The stored configuration for the content.</dd>
+
+ <dt>$args</dt>
+ <dd>Any arguments passed.</dd>
+
+ <dt>$context</dt>
+ <dd>An array of contexts requested by the required contexts and assigned by the configuration step.</dd>
+
+ <dt>$incoming_content</dt>
+ <dd>Any 'incoming content' if this is a wrapper.</dd>
+ </dl>
+ </dd>
+
+ <dt>admin title</dt>
+ <dd>A callback to provide the administrative title. If it is not a function, then it will be counted as a string to use as the admin title.</dd>
+
+ <dt>admin info</dt>
+ <dd>A callback to provide administrative information about the content, to be displayed when manipulating the content. It should contain a summary of configuration.</dd>
+
+ <dt>edit form</dt>
+ <dd>Either a single form ID or an array of forms *keyed* by form ID with the value to be used as the title of the form. %title me be used as a placeholder for the administrative title if necessary.
+ Example:
+<pre>array(
+ 'ctools_example_content_form_second' =&gt; t('Configure first form'),
+ 'ctools_example_content_form_first' =&gt; t('Configure second form'),
+),
+</pre>
+The first form will always have required configuration added to it. These forms are normal FAPI forms, but you do not need to provide buttons, these will be added by the form wizard.
+ </dd>
+
+ <dt>add form</dt>
+ <dd>[Optional] If different from the edit forms, provide them here in the same manner. Also may be set to FALSE to not have an add form.</dd>
+
+ <dt>css</dt>
+ <dd>A file or array of CSS files that are necessary for the content.</dd>
+
+ <dt>js</dt>
+ <dd>A file or array of javascript files that are necessary for the content to be displayed.</dd>
+
+ <dt>admin css</dt>
+ <dd>A file or array of CSS files that are necessary for the forms.</dd>
+
+ <dt>admin js</dt>
+ <dd>A file or array of JavaScript files that are necessary for the forms.</dd>
+
+ <dt>extra forms</dt>
+ <dd>An array of form information to handle extra administrative forms.</dd>
+
+ <dt>no title override</dt>
+ <dd>Set to TRUE if the title cannot be overridden.</dd>
+
+ <dt>single</dt>
+ <dd>Set to TRUE if this content provides exactly one subtype.</dd>
+
+ <dt>render last</dt>
+ <dd>Set to true if for some reason this content needs to render after other content. This is primarily used for forms to ensure that render order is correct.</dd>
+</dl>
+
+<p>TODO: many of the above callbacks can be assumed based upon patterns: modulename + '_' + name + '_' + function. i.e, render, admin_title, admin_info, etc.</p>
+
+<p>TODO: Some kind of simple access control to easily filter out content.</p>
+
+<p>The subtype definition should contain:</p>
+
+<dl>
+ <dt>title</dt>
+ <dd>The title of the subtype.</dd>
+
+ <dt>icon</dt>
+ <dd>The icon to display for the subtype.</dd>
+
+ <dt>path</dt>
+ <dd>The path for the icon if it is not in the same directory as the plugin.</dd>
+
+ <dt>description</dt>
+ <dd>The short description of the subtype, to be used when selecting it in the UI.</dd>
+
+ <dt>category</dt>
+ <dd>Either a text string for the category, or an array of the text string followed by the category weight.</dd>
+
+ <dt>required context [Optional]</dt>
+
+ <dd>Either a ctools_context_required or ctools_context_optional or array of contexts for this content. If omitted, no contexts are used.</dd>
+
+ <dt>create content access [Optional]</dt>
+
+ <dd>An optional callback to determine if a user can access this subtype. The callback will receive two arguments, the type and subtype.</dd>
+</dl>
+
+<h2>Rendered content</h2>
+
+<p>Rendered content is a little more than just HTML.</p>
+
+<dl>
+ <dt>title</dt>
+ <dd>The safe to render title of the content.</dd>
+
+ <dt>content</dt>
+ <dd>The safe to render HTML content.</dd>
+
+ <dt>links</dt>
+ <dd>An array of links associated with the content suitable for theme('links').</dd>
+
+ <dt>more</dt>
+ <dd>An optional 'more' link (destination only)</dd>
+
+ <dt>admin_links</dt>
+ <dd>Administrative links associated with the content, suitable for theme('links').</dd>
+
+ <dt>feeds</dt>
+ <dd>An array of feed icons or links associated with the content. Each member of the array is rendered HTML.</dd>
+
+ <dt>type</dt>
+ <dd>The content type.</dd>
+
+ <dt>subtype</dt>
+ <dd>The content subtype. These two may be used together as module-delta for block style rendering.</dd>
+</dl>
+
+<h2>Todo: example</h2>
+
+<p>Todo after implementations are updated to new version.</p>
diff --git a/sites/all/modules/ctools/help/context-context.html b/sites/all/modules/ctools/help/context-context.html
new file mode 100644
index 000000000..2314bd5ff
--- /dev/null
+++ b/sites/all/modules/ctools/help/context-context.html
@@ -0,0 +1,13 @@
+<p>Context plugin data:</p>
+
+<pre>
+ 'title' => Visible title
+ 'description' => Description of context
+ 'context' => Callback to create a context. Params: $empty, $data = NULL, $conf = FALSE
+ 'settings form' => Callback to show a context setting form. Params: ($conf, $external = FALSE)
+ 'settings form validate' => params: ($form, &$form_values, &$form_state)
+ 'settings form submit' => params: 'ctools_context_node_settings_form_submit',
+ 'keyword' => The default keyword to use.
+ 'context name' => The unique identifier for this context for use by required context checks.
+ 'no ui' => if TRUE this context cannot be selected.
+</pre> \ No newline at end of file
diff --git a/sites/all/modules/ctools/help/context-relationships.html b/sites/all/modules/ctools/help/context-relationships.html
new file mode 100644
index 000000000..cc9969e1f
--- /dev/null
+++ b/sites/all/modules/ctools/help/context-relationships.html
@@ -0,0 +1,13 @@
+<p>Relationship plugin data:</p>
+
+<pre>
+ 'title' => The title to display.
+ 'description' => Description to display.
+ 'keyword' => Default keyword for the context created by this relationship.
+ 'required context' => One or more ctools_context_required/optional objects
+ describing the context input.
+ new panels_required_context(t('Node'), 'node'),
+ 'context' => The callback to create the context. Params: ($context = NULL, $conf)
+ 'settings form' => Settings form. Params: $conf
+ 'settings form validate' => Validate.
+</pre>
diff --git a/sites/all/modules/ctools/help/context.html b/sites/all/modules/ctools/help/context.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/sites/all/modules/ctools/help/context.html
diff --git a/sites/all/modules/ctools/help/css.html b/sites/all/modules/ctools/help/css.html
new file mode 100644
index 000000000..b9b6d9c63
--- /dev/null
+++ b/sites/all/modules/ctools/help/css.html
@@ -0,0 +1 @@
+<p>To be written.</p>
diff --git a/sites/all/modules/ctools/help/ctools.help.ini b/sites/all/modules/ctools/help/ctools.help.ini
new file mode 100644
index 000000000..fcb121b8d
--- /dev/null
+++ b/sites/all/modules/ctools/help/ctools.help.ini
@@ -0,0 +1,97 @@
+[advanced help settings]
+line break = TRUE
+
+[about]
+title = About Chaos Tool Suite
+weight = -100
+
+[context]
+title = Context tool
+weight = -40
+
+[context-access]
+title = Context based access control plugins
+parent = context
+
+[context-context]
+title = Context plugins
+parent = context
+
+[context-arguments]
+title = Argument plugins
+parent = context
+
+[context-relationships]
+title = Relationship plugins
+parent = context
+
+[context-content]
+title = Content plugins
+parent = context
+
+[css]
+title = CSS scrubbing and caching tool
+
+[menu]
+title = Miscellaneous menu helper tool
+
+[plugins]
+title = Plugins and APIs tool
+weight = -50
+
+[plugins-api]
+title = Implementing APIs
+parent = plugins
+
+[plugins-creating]
+title = Creating plugins
+parent = plugins
+
+[plugins-implementing]
+title = Implementing plugins
+parent = plugins
+
+[export]
+title = Exportable objects tool
+
+[export-ui]
+title = Exportable objects UI creator
+
+[form]
+title = Form tools
+
+[wizard]
+title = Form wizard tool
+
+[ajax]
+title = AJAX and Javascript helper tools
+weight = -30
+
+[modal]
+title = Javascript modal tool
+parent = ajax
+
+[collapsible-div]
+title = Javascript collapsible DIV
+parent = ajax
+
+[dropdown]
+title = Javascript dropdown
+parent = ajax
+
+[dropbutton]
+title = Javascript dropbutton
+parent = ajax
+
+[dependent]
+title = Dependent checkboxes and radio buttons
+parent = ajax
+
+[object-cache]
+title = Temporary object caching
+
+; A bunch of this stuff we'll put in panels.
+
+[plugins-content]
+title = Creating content type plugins
+parent = panels%api
diff --git a/sites/all/modules/ctools/help/dependent.html b/sites/all/modules/ctools/help/dependent.html
new file mode 100644
index 000000000..b9b6d9c63
--- /dev/null
+++ b/sites/all/modules/ctools/help/dependent.html
@@ -0,0 +1 @@
+<p>To be written.</p>
diff --git a/sites/all/modules/ctools/help/dropbutton.html b/sites/all/modules/ctools/help/dropbutton.html
new file mode 100644
index 000000000..b9b6d9c63
--- /dev/null
+++ b/sites/all/modules/ctools/help/dropbutton.html
@@ -0,0 +1 @@
+<p>To be written.</p>
diff --git a/sites/all/modules/ctools/help/dropdown.html b/sites/all/modules/ctools/help/dropdown.html
new file mode 100644
index 000000000..b9b6d9c63
--- /dev/null
+++ b/sites/all/modules/ctools/help/dropdown.html
@@ -0,0 +1 @@
+<p>To be written.</p>
diff --git a/sites/all/modules/ctools/help/export-ui.html b/sites/all/modules/ctools/help/export-ui.html
new file mode 100644
index 000000000..e6b1086e0
--- /dev/null
+++ b/sites/all/modules/ctools/help/export-ui.html
@@ -0,0 +1,85 @@
+<p>Most user interfaces for exportables are very similar, so CTools includes a tool to provide the framework for the most common UI. This tool is a plugin of the 'export_ui' type. In order to create a UI for your exportable object with this tool, you first need to ensure that your module supports the plugin:</p>
+
+<pre>
+function HOOK_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'ctools' && $plugin == 'export_ui') {
+ return 'plugins/' . $plugin;
+ }
+}
+</pre>
+
+<p>Then, you need to create a plugin .inc file describing your UI. Most of the UI runs with sane but simple defaults, so for the very simplest UI you don't need to do very much. This is a very simple example plugin for the 'example' export type:</p>
+
+<pre>
+$plugin = array(
+ // The name of the table as found in the schema in hook_install. This
+ // must be an exportable type with the 'export' section defined.
+ 'schema' => 'example',
+
+ // The access permission to use. If not provided it will default to
+ // 'administer site configuration'
+ 'access' => 'administer example',
+
+ // You can actually define large chunks of the menu system here. Nothing
+ // is required here. If you leave out the values, the prefix will default
+ // to admin/structure and the item will default to the plugin name.
+ 'menu' => array(
+ 'menu prefix' => 'admin/structure',
+ 'menu item' => 'example',
+ // Title of the top level menu. Note this should not be translated,
+ // as the menu system will translate it.
+ 'menu title' => 'Example',
+ // Description of the top level menu, which is usually needed for
+ // menu items in an administration list. Will be translated
+ // by the menu system.
+ 'menu description' => 'Administer site example objects.',
+ ),
+
+ // These are required to provide proper strings for referring to the
+ // actual type of exportable. "proper" means it will appear at the
+ // beginning of a sentence.
+ 'title singular' => t('example'),
+ 'title singular proper' => t('Example'),
+ 'title plural' => t('examples'),
+ 'title plural proper' => t('Examples'),
+
+ // This will provide you with a form for editing the properties on your
+ // exportable, with validate and submit handler.
+ //
+ // The item being edited will be in $form_state['item'].
+ //
+ // The submit handler is only responsible for moving data from
+ // $form_state['values'] to $form_state['item'].
+ //
+ // All callbacks will accept &$form and &$form_state as arguments.
+ 'form' => array(
+ 'settings' => 'example_ctools_export_ui_form',
+ 'validate' => 'example_ctools_export_ui_form_validate',
+ 'submit' => 'example_ctools_export_ui_form_submit',
+ ),
+
+);
+</pre>
+
+<p>For a more complete list of what you can set in your plugin, please see ctools_export_ui_defaults() in includes/export-ui.inc to see what the defaults are.</p>
+
+<h2>More advanced UIs</h2>
+
+<p>The bulk of this UI is built on an class called ctools_export_ui, which is itself stored in ctools/plugins/export_ui as the default plugin. Many UIs will have more complex needs than the defaults provide. Using OO and overriding methods can allow an implementation to use the basics and still provide more where it is needed. To utilize this, first add a 'handler' directive to your plugin .inc file:</p>
+
+<pre>
+ 'handler' => array(
+ 'class' => 'ctools_export_ui_example',
+ 'parent' => 'ctools_export_ui',
+ ),
+</pre>
+
+Then create your class in ctools_export_ui_example.class.php in your plugins directory:
+
+<pre>
+class ctools_export_ui_example extends ctools_export_ui {
+
+}
+</pre>
+
+<p>You can override any method found in the class (see the source file for details). In particular, there are several list methods that are good candidates for overriding if you need to provide richer data for listing, sorting or filtering. If you need multi-step add/edit forms, you can override edit_page(), add_page(), clone_page(), and import_page() to put your wizard in place of the basic editing system. For an example of how to use multi-step wizards, see the import_page() method.</p>
diff --git a/sites/all/modules/ctools/help/export.html b/sites/all/modules/ctools/help/export.html
new file mode 100644
index 000000000..ce24cad9f
--- /dev/null
+++ b/sites/all/modules/ctools/help/export.html
@@ -0,0 +1,294 @@
+<p>Exportable objects are objects that can live either in the database or in code, or in both. If they live in both, then the object in code is considered to be "overridden", meaning that the version in code is ignored in favor of the version in the database.</p>
+
+<p>The main benefit to this is that you can move objects that are intended to be structure or feature-related into code, thus removing them entirely from the database. This is a very important part of the deployment path, since in an ideal world, the database is primarily user generated content, whereas site structure and site features should be in code. However, many many features in Drupal rely on objects being in the database and provide UIs to create them.</p>
+
+<p>Using this system, you can give your objects dual life. They can be created in the UI, exported into code and put in revision control. Views and Panels both use this system heavily. Plus, any object that properly implements this system can be utilized by the Features module to be used as part of a bundle of objects that can be turned into feature modules.</p>
+
+<p>Typically, exportable objects have two identifiers. One identifier is a simple serial used for database identification. It is a primary key in the database and can be used locally. It also has a name which is an easy way to uniquely identify it. This makes it much less likely that importing and exporting these objects across systems will have collisions. They still can, of course, but with good name selection, these problems can be worked around.</p>
+
+<h2>Making your objects exportable</h2>
+
+<p>To make your objects exportable, you do have to do a medium amount of work.</p>
+
+<ol>
+ <li>Create a chunk of code in your object's schema definition in the .install file to introduce the object to CTools' export system.</li>
+ <li>Create a load function for your object that utilizes ctools_export_load_object().</li>
+ <li>Create a save function for your object that utilizes drupal_write_record() or any method you desire.</li>
+ <li>Create an import and export mechanism from the UI.</li>
+</ol>
+
+<h2>The export section of the schema file</h2>
+
+<p>Exportable objects are created by adding definition to the schema in an 'export' section. For example:</p>
+
+<pre>
+function mymodule_schema() {
+ $schema['mymodule_myobj'] = array(
+ 'description' => t('Table storing myobj definitions.'),
+ 'export' => array(
+ 'key' => 'name',
+ 'key name' => 'Name',
+ 'primary key' => 'oid',
+ 'identifier' => 'myobj', // Exports will be as $myobj
+ 'default hook' => 'default_mymodule_myobj', // Function hook name.
+ 'api' => array(
+ 'owner' => 'mymodule',
+ 'api' => 'default_mymodule_myobjs', // Base name for api include files.
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ // If the key is stored in a table that is joined in, specify it:
+ 'key in table' => 'my_join_table',
+
+ ),
+
+ // If your object's data is split up across multiple tables, you can
+ // specify additional tables to join. This is very useful when working
+ // with modules like exportables.module that has a special table for
+ // translating keys to local database IDs.
+ //
+ // The joined table must have its own schema definition.
+ //
+ // If using joins, you should implement a 'delete callback' (see below)
+ // to ensure that deletes happen properly. export.inc does not do this
+ // automatically!
+ 'join' => array(
+ 'exportables' => array(
+ // The following parameters will be used in this way:
+ // SELECT ... FROM {mymodule_myobj} t__0 INNER JOIN {my_join_table} t__1 ON t__0.id = t__1.id AND extras
+ 'table' => 'my_join_table',
+ 'left_key' => 'format',
+ 'right_key' => 'id',
+ // Optionally you can define a callback to add custom conditions or
+ // alter the query as necessary. The callback function takes 3 args:
+ //
+ // myjoincallback(&$query, $schema, $join_schema);
+ //
+ // where $query is the database query object, $schema is the schema for
+ // the export base table and $join_schema is the schema for the current
+ // join table.
+ 'callback' => 'myjoincallback',
+
+ // You must specify which fields will be loaded. These fields must
+ // exist in the schema definition of the joined table.
+ 'load' => array(
+ 'machine',
+ ),
+
+ // And finally you can define other tables to perform INNER JOINS
+ //'other_joins' => array(
+ // 'table' => ...
+ //),
+ ),
+ )
+ 'fields' => array(
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Unique ID for this object. Used to identify it programmatically.',
+ ),
+ 'oid' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.',
+ 'no export' => TRUE, // Do not export database-only keys.
+ ),
+ // ......
+ 'primary key' => array('oid'),
+ 'unique keys' => array(
+ 'name' => array('name'),
+ ),
+ );
+ return $schema;
+}
+</pre>
+
+<dl>
+<dt>key</dt>
+<dd>This is the primary key of the exportable object and should be a string as names are more portable across systems. It is possible to use numbers here, but be aware that export collisions are very likely. Defaults to 'name'.</dd>
+
+<dt>key name</dt>
+<dd>Human readable title of the export key. Defaults to 'Name'. Because the schema is cached, do not translate this. It must instead be translated when used.</dd>
+
+<dt>primary key</dt>
+<dd>A single field within the table that is to be used as the main identifier to discern whether or not the object has been written. As the schema definition's primary key value will be used by default, it is not usually necessary to define this.</dd>
+
+<dt>object</dt>
+<dd>The class the object should be created as, if 'object factory' is not set. If this is not set either, defaults as stdClass.</dd>
+
+<dt>object factory</dt>
+<dd>Function used to create the object. The function receives the schema and the loaded data as a parameters: your_factory_function($schema, $data). If this is set, 'object' has no effect since you can use your function to create whatever class you wish.</dd>
+
+<dt>admin_title</dt>
+<dd>A convenience field that may contain the field which represents the human readable administrative title for use in export UI. If a field "admin_title" exists, it will automatically be used.</dd>
+
+<dt>admin_description</dt>
+<dd>A convenience field that may contain the field which represents the human readable administrative title for use in export UI. If a field "admin_title" exists, it will automatically be used.</dd>
+
+<dt>can disable</dt>
+<dd>Control whether or not the exportable objects can be disabled. All this does is cause the 'disabled' field on the object to always be set appropriately, and a variable is kept to record the state. Changes made to this state must be handled by the owner of the object. Defaults to TRUE.</dd>
+
+<dt>status</dt>
+<dd>Exportable objects can be enabled or disabled, and this status is stored in a variable. This defines what variable that is. Defaults to: 'default_' . $table.</dd>
+
+<dt>default hook</dt>
+<dd>What hook to invoke to find exportable objects that are currently defined. These will all be gathered into a giant array. Defaults to 'default_' . $table.</dd>
+
+<dt>cache defaults</dt>
+<dd>If true, default objects will be cached so that the processing of the hook does not need to be called often. Defaults to FALSE. Recommended if you will potentially have a lot of objects in code. Not recommended if code will be the exception.</dd>
+
+<dt>default cache bin</dt>
+<dd>If default object caching is enabled, what cache bin to use. This defaults to the basic "cache". It is highly recommended that you use a different cache bin if possible.</dd>
+
+<dt>identifier</dt>
+<dd>When exporting the object, the identifier is the variable that the exported object will be placed in. Defaults to $table.</dd>
+
+<dt>bulk export</dt>
+<dd>Declares whether or not the exportable will be available for bulk exporting.</dd>
+
+<dt>export type string</dt>
+<dd>The export type string (Local, Overridden, Database) is normally stored as $item-&gt;type. Since type is a very common keyword, it's possible to specify what key to actually use. </dd>
+
+<dt>list callback</dt>
+<dd>Bulk export callback to provide a list of exportable objects to be chosen for bulk exporting. Defaults to $module . '_' . $table . '_list' if the function exists. If it is not, a default listing function will be provided that will make a best effort to list the titles. See ctools_export_default_list().</dd>
+
+<dt>to hook code callback</dt>
+<dd>Function used to generate an export for the bulk export process. This is only necessary if the export is more complicated than simply listing the fields. Defaults to $module . '_' . $table . '_to_hook_code'.</dt>
+
+<dt>boolean</dt>
+<dd>Explicitly indicate if a table field contains a boolean or not. The Schema API does not model the
+difference between a tinyint and a boolean type. Boolean values are stored in tinyint fields. This may cause mismatch errors when exporting a non-boolean value from a tinyint field. Add this to a tinyint field if it contains boolean data and can be exported. Defaults to TRUE.
+
+<dt>create callback</dt>
+<dd>CRUD callback to use to create a new exportable item in memory. If not provided, the default function will be used. The single argument is a boolean used to determine if defaults should be set on the object. This object will not be written to the database by this callback.</dd>
+
+<dt>load callback</dt>
+<dd>CRUD callback to use to load a single item. If not provided, the default load function will be used. The callback will accept a single argument which should be an identifier of the export key.</dd>
+
+<dt>load multiple callback</dt>
+<dd>CRUD callback to use to load multiple items. If not provided, the default multiple load function will be used. The callback will accept an array which should be the identifiers of the export key.</dd>
+
+<dt>load all callback</dt>
+<dd>CRUD callback to use to load all items, usually for administrative purposes. If not provided, the default load function will be used. The callback will accept a single argument to determine if the load cache should be reset or not.</dd>
+
+<dt>save callback</dt>
+<dd>CRUD callback to use to save a single item. If not provided, the default save function will be used. The callback will accept a single argument which should be the complete exportable object to save.</dd>
+
+<dt>delete callback</dt>
+<dd>CRUD callback to use to delete a single item. If not provided, the default delete function will be used. The callback will accept a single argument which can be *either* the object or just the export key to delete. The callback MUST be able to accept either.</dd>
+
+<dt>export callback</dt>
+<dd>CRUD callback to use for exporting. If not provided, the default export function will be used. The callback will accept two arguments, the first is the item to export, the second is the indent to place on the export, if any.</dd>
+
+<dt>import callback</dt>
+<dd>CRUD callback to use for importing. If not provided, the default export function will be used. This function will accept the code as a single argument and, if the code evaluates, return an object represented by that code. In the case of failure, this will return a string with human readable errors.</dd>
+
+<dt>status callback</dt>
+<dd>CRUD callback to use for updating the status of an object. If the status is TRUE the object will be disabled. If the status is FALSE the object will be enabled.</dd>
+
+<dt>api</dt>
+<dd>The 'api' key can optionally contain some information for the plugin API definition. This means that the imports can be tied to an API name which is used to have automatic inclusion of files, and can be used to prevent dangerous objects from older versions from being loaded, causing a loss of functionality rather than site crashes or security loopholes.
+
+<p>If not present, no additional files will be loaded and the default hook will always be a simple hook that must be either part of the .module file or loaded during normal operations.</p>
+
+<p>api supports these subkeys:</p>
+
+<dl>
+<dt>owner</dt>
+<dd>The module that owns the API. Typically this is the name of the module that owns the schema. This will be one of the two keys used by hook_ctools_plugin_api() to determine version compatibility. Note that the name of this hook can be tailored via the use of hook_ctools_plugin_api_hook_name(). See ctools_plugin_api_get_hook() for details.</dd>
+<dt>api</dt>
+<dd>This is the name of the API, and will be the second parameter to the above mentioned hook. It will also be used as part of the name of the file that the hook containing default objects will be in, which comes in the form of MODULENAME.API.inc.</dd>
+<dt>minimum_version</dt>
+<dd>The minimum version supported. Any module reporting an API less than this will not have its default objects used. This should be updated only when API changes can cause older objects to crash or otherwise break badly.</dd>
+<dt>current_version</dt>
+<dd>The current version of the API. Any module reporting a required API higher than this will not have its default objects used.</dd>
+</dl>
+</dd>
+
+</dl>
+<p>In addition, each field can contain the following:</p>
+<dl>
+<dt>no export</dt>
+<dd>Set to TRUE to prevent that field from being exported.</dd>
+
+<dt>export callback</dt>
+<dd>A function to override the export behavior. It will receive ($myobject, $field, $value, $indent) as arguments. By default, fields are exported through ctools_var_export().</dd>
+</dl>
+
+<h2>Reserved keys on exportable objects</h2>
+
+<p>Exportable objects have several reserved keys that are used by the CTools export API. Each key can be found at <code>$myobj-&gt;{$key}</code> on an object loaded through <code>ctools_export_load_object()</code>. Implementing modules should not use these keys as they will be overwritten by the CTools export API.</p>
+<dl>
+<dt>api_version</dt>
+<dd>The API version that this object implements.</dd>
+
+<dt>disabled</dt>
+<dd>A boolean for whether the object is disabled.</dd>
+
+<dt>export_module</dt>
+<dd>For objects that live in code, the module which provides the default object.</dd>
+
+<dt>export_type</dt>
+<dd>A bitmask representation of an object current storage. You can use this bitmask in combination with the <code>EXPORT_IN_CODE</code> and <code>EXPORT_IN_DATABASE</code> constants to test for an object's storage in your code.
+</dd>
+
+<dt>in_code_only</dt>
+<dd>A boolean for whether the object lives only in code.</dd>
+
+<dt>table</dt>
+<dd>The schema API table that this object belongs to.</dd>
+
+<dt>type</dt>
+<dd>A string representing the storage type of this object. Can be one of the following:
+<ul>
+<li><em>Normal</em> is an object that lives only in the database.</li>
+<li><em>Overridden</em> is an object that lives in the database and is overriding the exported configuration of a corresponding object in code.</li>
+<li><em>Default</em> is an object that lives only in code.</li>
+</ul>
+<i>Note: This key can be changed by setting 'export type string' to something else, to try and prevent "type" from conflicting.</i>
+</dd>
+</dl>
+
+<h2>The load callback</h2>
+<p>Calling ctools_export_crud_load($table, $name) will invoke your load callback, calling ctools_export_crud_load_multiple($table, $names) will invoke your load multiple callback, and calling ctools_export_crud_load_all($table, $reset) will invoke your load all callback. The default handlers should be sufficient for most uses.</p>
+
+<p>Typically, there will be three load functions. A 'single' load, to load just one object, a 'multiple' load to load multiple objects, and an 'all' load, to load all of the objects for use in administrating the objects or utilizing the objects when you need all of them. Using ctools_export_load_object() you can easily do both, as well as quite a bit in between. This example duplicates the default functionality for loading one myobj.</p>
+
+<pre>
+/**
+ * Implements 'load callback' for myobj exportables.
+ */
+function mymodule_myobj_load($name) {
+ ctools_include('export');
+ $result = ctools_export_load_object('mymodule_myobjs', 'names', array($name));
+ if (isset($result[$name])) {
+ return $result[$name];
+ }
+}
+
+/**
+ * Implements 'load multiple callback' for myobj exportables.
+ */
+function mymodule_myobj_load_multiple(array $names) {
+ ctools_include('export')
+ $results = ctools_export_load_object('mymodule_myobjs', 'names', $names);
+ return array_filter($results);
+}
+</pre>
+
+<h2>The save callback</h2>
+Calling ctools_export_crud_save($table, $object) will invoke your save callback. The default handlers should be sufficient for most uses. For the default save mechanism to work, you <b>must</b> define a 'primary key' in the 'export' section of your schema. The following example duplicates the default functionality for the myobj.
+
+<pre>
+/**
+* Save a single myobj.
+*/
+function mymodule_myobj_save(&$myobj) {
+ $update = (isset($myobj->oid) && is_numeric($myobj->oid)) ? array('oid') : array();
+ return drupal_write_record('myobj', $myobj, $update);
+}
+</pre>
+
+<h2>Default hooks for your exports</h2>
+<p>All exportables come with a 'default' hook, which can be used to put your exportable into code. The easiest way to actually use this hook is to set up your exportable for bulk exporting, enable the bulk export module and export an object.</p>
diff --git a/sites/all/modules/ctools/help/form.html b/sites/all/modules/ctools/help/form.html
new file mode 100644
index 000000000..b9b6d9c63
--- /dev/null
+++ b/sites/all/modules/ctools/help/form.html
@@ -0,0 +1 @@
+<p>To be written.</p>
diff --git a/sites/all/modules/ctools/help/modal.html b/sites/all/modules/ctools/help/modal.html
new file mode 100644
index 000000000..ea823a0de
--- /dev/null
+++ b/sites/all/modules/ctools/help/modal.html
@@ -0,0 +1,215 @@
+<p>CTools provides a simple modal that can be used as a popup to place forms. It differs from the normal modal frameworks in that it does not do its work via an iframe. This is both an advantage and a disadvantage. The iframe simply renders normal pages in a sub-browser and they can do their thing. That makes it much easier to put arbitrary pages and forms in a modal. However, the iframe is not very good at actually communicating changes to the main page, so you cannot open the modal, have it do some work, and then modify the page. </p>
+
+<h2>Invoking the modal</h2>
+
+<p>The basic form of the modal can be set up just by including the javascript and adding the proper class to a link or form that will open the modal. To include the proper javascript, simply include the library and call the add_js function:</p>
+
+<code>ctools_include('modal');
+ctools_modal_add_js();
+</code>
+
+<p>You can have links and buttons bound to use the modal by adding the class ctools-use-modal. For convenience, there is a helper function to try and do this, though it's not great at doing all links so using this is optional:</p>
+
+<code>/**
+ * Render an image as a button link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * @param $image
+ * The path to an image to use that will be sent to theme('image') for rendering.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ */
+function ctools_modal_image_button($image, $dest, $alt, $class = '') {
+ return ctools_ajax_text_button(theme('image', array('path' => $image), $dest, $alt, $class, 'ctools-use-modal');
+}
+
+/**
+ * Render text as a link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will
+ * not use user input so this is a very minor concern.
+ *
+ * @param $text
+ * The text to display as the link.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ */
+function ctools_modal_text_button($text, $dest, $alt, $class = '') {
+ return ctools_ajax_text_button($text, $dest, $alt, $class, 'ctools-use-modal');
+}
+</code>
+
+<p>Like with all CTools' AJAX functionality, the href of the link will be the destination, with any appearance of /nojs/ converted to /ajax/.</p>
+
+<p>For submit buttons, however, the URL may be found a different, slightly more complex way. If you do not wish to simply submit the form to the modal, you can create a URL using hidden form fields. The ID of the item is taken and -url is appended to it to derive a class name. Then, all form elements that contain that class name are founded and their values put together to form a URL.</p>
+
+<p>For example, let's say you have an 'add' button, and it has a select form item that tells your system what widget it is adding. If the id of the add button is edit-add, you would place a hidden input with the base of your URL in the form and give it a class of 'edit-add-url'. You would then add 'edit-add-url' as a class to the select widget allowing you to convert this value to the form without posting. If no URL is found, the action of the form will be used and the entire form posted to it.</p>
+
+<h2>Customizing the modal</h2>
+
+<p>If you do not wish to use the default modal, the modal can be customized by creating an array of data to define a customized modal. To do this, you add an array of info to the javascript settings to define the customizations for the modal and add an additional class to your modal link or button to tell it which modal to use.</p>
+
+<p>First, you need to create a settings array. You can do this most easily with a bit of PHP:</p>
+
+<pre>drupal_add_js(array(
+ 'my-modal-style' => array(
+ 'modalSize' => array(
+ 'type' => 'fixed',
+ 'width' => 250,
+ 'height' => 250,
+ ),
+ ),
+ ), 'setting');
+</pre>
+
+<p>The key to the array above (in this case, my-modal-style) is the identifier to your modal theme. You can have multiple modal themes on a page, so be sure to use an ID that will not collide with some other module's use. Using your module or theme as a prefix is a good idea.</p>
+
+<p>Then, when adding the ctools-use-modal class to your link or button, also add the following class: ctools-modal-ID (in the example case, that would be ctools-modal-my-modal-style).</p>
+
+<p>modalSize can be 'fixed' or 'scale'. If fixed it will be a raw pixel value; if 'scale' it will be a percentage of the screen.</p>
+
+<p>You can set:</p>
+ <ul>
+ <li> <strong>modalSize</strong>: an array of data to control the sizing of the modal. It can contain:
+ <ul>
+ <li> <strong>type</strong>: Either <em>fixed</em> or <em>scale</em>. If fixed, the modal will always be a fixed size. If <em>scale</em> the modal will scale to a percentage of the browser window. <em>Default: scale</em>.
+<li> <strong>width</strong>: If </em>fixed</em> the width in pixels. If <em>scale</em> the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). <em>Default: .8</em></li>
+<li> <strong>height</strong>: If <em>fixed</em> the height in pixels. If <em>scale</em> the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). <em>Default: .8</em></li>
+<li> <strong>addWidth</strong>: Any additional width to add to the modal in pixels. Only useful if the type is scale. <em>Default: 0</em></li>
+<li> <strong>addHeight</strong>: Any additional height to add to the modal in pixels. Only useful if the type is scale. <em>Default: 0</em></li>
+<li> <strong>contentRight</strong>: The number of pixels to remove from the content inside the modal to make room for scroll bar and decorations. <em>Default: 25</em></li>
+<li> <strong>contentBottom</strong>: The number of pixels to remove from the content inside the modal to make room for scroll bar and decorations. <em>Default: 45</em></li>
+</ul>
+</li>
+<li> <strong>modalTheme</strong>: The Drupal javascript themable function which controls how the modal will be rendered. This function must be in the <em>Drupal.theme.prototype</em> namespace. If you set this value, you must include a corresponding function in a javascript file and use drupal_add_js() to add that file. <em>Default: CToolsModalDialog</em>
+<pre>
+ Drupal.theme.prototype.CToolsModalDialog = function () {
+ var html = ''
+ html += ' &lt;div id="ctools-modal"&gt;'
+ html += ' &lt;div class="ctools-modal-content"&gt;' // panels-modal-content
+ html += ' &lt;div class="modal-header"&gt;';
+ html += ' &lt;a class="close" href="#"&gt;';
+ html += Drupal.CTools.Modal.currentSettings.closeText + Drupal.CTools.Modal.currentSettings.closeImage;
+ html += ' &lt;/a&gt;';
+ html += ' &lt;span id="modal-title" class="modal-title"&gt;&nbsp;&lt;/span&gt;';
+ html += ' &lt;/div&gt;';
+ html += ' &lt;div id="modal-content" class="modal-content"&gt;';
+ html += ' &lt;/div&gt;';
+ html += ' &lt;/div&gt;';
+ html += ' &lt;/div&gt;';
+
+ return html;
+ }
+</pre></li>
+<li> <strong>throbberTheme</strong>: The Drupal javascript themable function which controls how the modal throbber will be rendered. This function must be in the <em>Drupal.theme.prototype</em> namespace. If you set this value, you must include a corresponding function in a javascript file and use drupal_add_js() to add that file. <em>Default: CToolsModalThrobber</em>
+<pre>
+ Drupal.theme.prototype.CToolsModalThrobber = function () {
+ var html = '';
+ html += ' &lt;div id="modal-throbber"&gt;';
+ html += ' &lt;div class="modal-throbber-wrapper"&gt;';
+ html += Drupal.CTools.Modal.currentSettings.throbber;
+ html += ' &lt;/div&gt;';
+ html += ' &lt;/div&gt;';
+
+ return html;
+ };
+</pre>
+</li>
+<li> <strong>modalOptions</strong>: The options object that's sent to Drupal.CTools.Modal.modalContent. Can contain any CSS settings that will be applied to the modal backdrop, in particular settings such as <strong>opacity</strong> and <strong>background</strong>. <em>Default: array('opacity' => .55, 'background' => '#fff');</em></li>
+<li> <strong>animation</strong>: Controls how the modal is animated when it is first drawn. Either <strong>show</strong>, <strong>fadeIn</strong> or <strong>slideDown</strong>. <em>Default: show</em></li>
+<li> <strong>animationSpeed</strong>: The speed of the animation, expressed either as a word jQuery understands (slow, medium or fast) or a number of milliseconds for the animation to run. <em>Defaults: fast</em></li>
+<li><strong>closeText</strong>: The text to display for the close button. Be sure to wrap this in t() for translatability. <em>Default: t('Close window')</em></li>
+<li><strong>closeImage</strong>: The image to use for the close button of the modal. <em>Default: theme('image', array('path' => ctools_image_path('icon-close-window.png'), 'alt' => t('Close window'), 'title' => t('Close window')))</em></li>
+<li><strong>loadingText</strong>: The text to display for the modal title during loading, along with the throbber. Be sure to wrap this in t() for translatability. <em>Default: t('Close window')</em></li>
+<li><strong>throbber</strong>: The HTML to display for the throbber image. You can use this instead of CToolsModalThrobber theme if you just want to change the image but not the throbber HTML. <em>Default: theme('image', array('path' => ctools_image_path('throbber.gif'), 'alt' => t('Loading...'), 'title' => t('Loading')))</em></li>
+</ul>
+
+<h2>Rendering within the modal</h2>
+<p>To render your data inside the modal, you need to provide a page callback in your module that responds more or less like a normal page.</p>
+
+<p>In order to handle degradability, it's nice to allow your page to work both inside and outside of the modal so that users whose javascript is turned off can still use your page. There are two ways to accomplish this. First, you can embed 'nojs' as a portion of the URL and then watch to see if that turns into 'ajax' when the link is clicked. Second, if you do not wish to modify the URLs, you can check $_POST['js'] or $_POST['ctools_js'] to see if that flag was set. The URL method is the most flexible because it is easy to send the two links along completely different paths if necessary, and it is also much easier to manually test your module's output by manually visiting the nojs URL. It's actually quite difficult to do this if you have to set $_POST['js'] to test.</p>
+
+<p>In your hook_menu, you can use the a CTools' AJAX convenience loader to help:</p>
+
+<pre>
+ $items['ctools_ajax_sample/%ctools_js/login'] = array(
+ 'title' => 'Login',
+ 'page callback' => 'ctools_ajax_sample_login',
+ 'page arguments' => array(1),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+</pre>
+
+<p>The first argument to the page callback will be the result of ctools_js_load() which will return TRUE if 'ajax' was the string, and FALSE if anything else (i.e, nojs) is the string. Which means you can then declare your function like this:</p>
+
+<pre>
+function ctools_ajax_sample_login($js) {
+ if ($js) {
+ // react with the modal
+ }
+ else {
+ // react without the modal
+ }
+}
+</pre>
+
+<p>If your modal does not include a form, rendering the output you wish to give the user is just a matter of calling the modal renderer with your data:</p>
+
+<pre>
+function ctools_ajax_hello_world($js) {
+ $title = t('Greetings');
+ $output = '&lt;p&gt;' . t('Hello world') . ''&lt;/p&gt;';
+ if ($js) {
+ ctools_modal_render($title, $output);
+ }
+ else {
+ drupal_set_title($title);
+ return $output;
+ }
+}
+</pre>
+
+<p>If you need to do more than just render your modal, you can use a CTools $commands array. See the function ctools_modal_render() for more information on what you need to do here.</p>
+
+<p>If you are displaying a form -- and the vast majority of modals display forms -- then you need to do just slightly more. Fortunately there is the ctools_modal_form_wrapper() function:</p>
+
+<pre>
+ ctools_include('modal');
+ ctools_include('ajax');
+ $form_state = array(
+ 'title' => t('Title of my form'),
+ 'ajax' => $js,
+ );
+ $output = ctools_modal_form_wrapper('my_form', $form_state);
+ // There are some possible states after calling the form wrapper:
+ // 1) We are not using $js and the form has been executed.
+ // 2) We are using $js and the form was successfully submitted and
+ // we need to dismiss the modal.
+ // Most other states are handled automatically unless you set flags in
+ // $form_state to avoid handling them, so we only deal with those two
+ // states.
+ if ($form_state['executed'] && $js) {
+ $commands = array();
+ $commands[] = ctools_modal_command_dismiss(t('Login Success'));
+ // In typical usage you will do something else here, such as update a
+ // div with HTML or redirect the page based upon the results of the modal
+ // form.
+ print ajax_render($commands);
+ exit;
+ }
+
+ // Otherwise, just return the output.
+ return $output;
+</pre>
+
+<p>You can also use CTools' form wizard inside the modal. You do not need to do much special beyond setting $form_state['modal'] = TRUE in the wizard form; it already knows how to handle the rest.</p>
diff --git a/sites/all/modules/ctools/help/object-cache.html b/sites/all/modules/ctools/help/object-cache.html
new file mode 100644
index 000000000..801a83672
--- /dev/null
+++ b/sites/all/modules/ctools/help/object-cache.html
@@ -0,0 +1,132 @@
+<p>The CTools Object Cache is a specialized cache for storing data that is non-volatile. This differs from the standard Drupal cache mechanism, which is volatile, meaning that the data can be cleared at any time and it is expected that any cached data can easily be reconstructed. In contrast, data stored in this cache is not expected to be reconstructable. It is primarily used for storing user input which is retrieved in stages, allowing for more complex user interface interactions.</p>
+
+<p>The object cache consists of 3 normal functions for cache maintenance, and 2 additional functions to facilitate locking.</p>
+
+<p>To use any of these functions, you must first use ctools_include:</p>
+
+<pre>
+ctools_include('object-cache');
+</pre>
+
+<pre>
+/**
+ * Get an object from the non-volatile ctools cache.
+ *
+ * This function caches in memory as well, so that multiple calls to this
+ * will not result in multiple database reads.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being stored.
+ * @param $skip_cache
+ * Skip the memory cache, meaning this must be read from the db again.
+ *
+ * @return
+ * The data that was cached.
+ */
+function ctools_object_cache_get($obj, $name, $skip_cache = FALSE) {
+</pre>
+
+<pre>
+/**
+ * Store an object in the non-volatile ctools cache.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being stored.
+ * @param $cache
+ * The object to be cached. This will be serialized prior to writing.
+ */
+function ctools_object_cache_set($obj, $name, $cache) {
+</pre>
+
+<pre>
+/**
+ * Remove an object from the non-volatile ctools cache
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ */
+function ctools_object_cache_clear($obj, $name) {
+</pre>
+
+<p>To facilitate locking, which is the ability to prohibit updates by other users while one user has an object cached, this system provides two functions:</p>
+
+<pre>
+/**
+ * Determine if another user has a given object cached.
+ *
+ * This is very useful for 'locking' objects so that only one user can
+ * modify them.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ *
+ * @return
+ * An object containing the UID and updated date if found; NULL if not.
+ */
+function ctools_object_cache_test($obj, $name) {
+</pre>
+
+<p>The object returned by ctools_object_cache_test can be directly used to determine whether a user should be allowed to cache their own version of an object.</p>
+
+<p>To allow the concept of breaking a lock, that is, clearing another users changes:</p>
+
+<pre>
+/**
+ * Remove an object from the non-volatile ctools cache for all session IDs.
+ *
+ * This is useful for clearing a lock.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ */
+function ctools_object_cache_clear_all($obj, $name) {
+</pre>
+
+<p>Typical best practice is to use wrapper functions such as these:</p>
+
+<pre>
+/**
+ * Get the cached changes to a given task handler.
+ */
+function delegator_page_get_page_cache($name) {
+ ctools_include('object-cache');
+ $cache = ctools_object_cache_get('delegator_page', $name);
+ if (!$cache) {
+ $cache = delegator_page_load($name);
+ $cache->locked = ctools_object_cache_test('delegator_page', $name);
+ }
+
+ return $cache;
+}
+
+/**
+ * Store changes to a task handler in the object cache.
+ */
+function delegator_page_set_page_cache($name, $page) {
+ ctools_include('object-cache');
+ $cache = ctools_object_cache_set('delegator_page', $name, $page);
+}
+
+/**
+ * Remove an item from the object cache.
+ */
+function delegator_page_clear_page_cache($name) {
+ ctools_include('object-cache');
+ ctools_object_cache_clear('delegator_page', $name);
+}
+</pre>
diff --git a/sites/all/modules/ctools/help/plugins-api.html b/sites/all/modules/ctools/help/plugins-api.html
new file mode 100644
index 000000000..548f17b51
--- /dev/null
+++ b/sites/all/modules/ctools/help/plugins-api.html
@@ -0,0 +1,55 @@
+<p>APIs are a form of plugins that are tightly associated with a module. Instead of a module providing any number of plugins, each module provides only one file for an API and this file can contain hooks that the module should invoke.</p>
+
+<p>Modules support this API by implementing hook_ctools_plugin_api($module, $api). If they support the API, they return a packet of data:</p>
+
+<pre>
+function mymodule_ctools_plugin_api($module, $api) {
+ if ($module == 'some module' && $api = 'some api') {
+ return array(
+ 'version' => The minimum API version this system supports. If this API version is incompatible then the .inc file will not be loaded.
+ 'path' => Where to find the file. Optional; if not specified it will be the module's directory.
+ 'file' => an alternative version of the filename. If not specified it will be $module.$api.inc
+ );
+ }
+}
+</pre>
+
+<p>This implementation must be in the .module file.</p>
+
+<p>Modules utilizing this can invole ctools_plugin_api_include() in order to ensure all modules that support the API will have their files loaded as necessary. It's usually easiest to create a small helper function like this:</p>
+
+<pre>
+define('MYMODULE_MINIMUM_VERSION', 1);
+define('MYMODULE_VERSION', 1);
+
+function mymodule_include_api() {
+ ctools_include('plugins');
+ return ctools_plugin_api_include('mymodule', 'myapi', MYMODULE_MINIMUM_VERSION, MYMODULE_VERSION);
+}
+</pre>
+
+<p>Using a define will ensure your use of version numbers is consistent and easy to update when you make API changes. You can then use the usual module_invoke type commands:</p>
+
+<pre>
+mymodule_include_api();
+module_invoke('myhook', $data);
+</pre>
+
+<p>If you need to pass references, this construct is standard:</p>
+
+<pre>
+foreach (mymodule_include_api() as $module => $info) {
+ $function = $module . '_hookname';
+ // Just because they implement the API and include a file does not guarantee they implemented
+ // a hook function!
+ if (!function_exists($function)) {
+ continue;
+ }
+
+ // Typically array_merge() is used below if data is returned.
+ $result = $function($data1, $data2, $data3);
+}
+</pre>
+
+<p>TODO: There needs to be a way to check API version without including anything, as a module may simply
+provide normal plugins and versioning could still matter.</p>
diff --git a/sites/all/modules/ctools/help/plugins-creating.html b/sites/all/modules/ctools/help/plugins-creating.html
new file mode 100644
index 000000000..23237058e
--- /dev/null
+++ b/sites/all/modules/ctools/help/plugins-creating.html
@@ -0,0 +1,203 @@
+There are two primary pieces to using plugins. The first is getting the data, and the second is using the data.
+
+<h2>Defining a plugin</h2>
+To define that you offer a plugin that modules can implement, you first must implement hook_ctools_plugin_type() to tell the plugin system about your plugin.
+
+<pre>
+/**
+ * Implements hook_ctools_plugin_type() to inform CTools about the layout plugin.
+ */
+function panels_ctools_plugin_type() {
+ $plugins['layouts'] = array(
+ 'load themes' => TRUE,
+ );
+
+ return $plugins;
+}
+</pre>
+
+The following information can be specified for each plugin type:
+<dl>
+<dt>cache</dt>
+<dd><em>Defaults to:</em> <strong>FALSE</strong></dd>
+<dd>If set to TRUE, the results of ctools_get_plugins will be cached in the 'cache' table (by default), thus preventing .inc files from being loaded. ctools_get_plugins looking for a specific plugin will always load the appropriate .inc file.</dd>
+<dt>cache table</dt>
+<dd><em>Defaults to:</em> <strong>'cache'</strong></dd>
+<dd>If 'cache' is TRUE, then this value specifies the cache table where the cached plugin information will be stored.</dd>
+<dt>classes</dt>
+<dd><em>Defaults to:</em> <strong>array()</strong></dd>
+<dd>An array of <em>class identifiers</em>(i.e. plugin array keys) which a plugin of this type uses to provide classes to the CTools autoloader. For example, if <strong>classes</strong> is set to array('class'), then CTools will search each <strong>$plugin['class']</strong> for a class to autoload. Depending of the plugin structure, a <em>class identifier</em> may be either:</dd>
+<dl>
+<dt>- a file name:</dt>
+<dd>the file which holds the class with the name structure as: <em>[filename].[class].php</em></dd>
+<dd>in this case the class name can be different than the <em>class identifier</em></dd>
+<dt>- the class name:</dt>
+<dd>if the class is in the same file as the $plugin</dd>
+<dd>the plugin <em>.inc</em> file can have a different name than the <em>class identifier</em></dd>
+</dl>
+<dt>defaults</dt>
+<dd><em>Defaults to:</em> <strong>array()</strong></dd>
+<dd>An array of defaults that should be added to each plugin; this can be used to ensure that every plugin has the basic data necessary. These defaults will not ovewrite data supplied by the plugin. This could also be a function name, in which case the callback will be used to provide defaults. NOTE, however, that the callback-based approach is deprecated as it is redundant with the 'process' callback, and as such will be removed in later versions. Consequently, you should only use the array form for maximum cross-version compatibility.</dd>
+<dt>load themes</dt>
+<dd><em>Defaults to:</em> <strong>FALSE</strong></dd>
+<dd>If set to TRUE, then plugins of this type can be supplied by themes as well as modules. If this is the case, all themes that are currently enabled will provide a plugin: NOTE: Due to a slight UI bug in Drupal, it is possible for the default theme to be active but not enabled. If this is the case, that theme will NOT provide plugins, so if you are using this feature, be sure to document that issue. Also, themes set via $custom_theme do not necessarily need to be enabled, but the system has no way of knowing what those themes are, so the enabled flag is the only true method of identifying which themes can provide layouts.</dd>
+<dt>hook</dt>
+<dd><em>Defaults to:</em> (dynamic value)</dd>
+<dd>The name of the hook used to collect data for this plugin. Normally this is <strong>$module . '_' . $type</strong> -- but this can be changed here. If you change this, you MUST be sure to document this for your plugin implementors as it will change the format of the specially named hook.
+<dt>process</dt>
+<dd><em>Defaults to:</em> <strong>''</strong></dd>
+<dd>An optional function callback to use for processing a plugin. This can be used to provide automated settings that must be calculated per-plugin instance (i.e., it is not enough to simply append an array via 'defaults'). The parameters on this callback are: <strong>callback(&$plugin, $info)</strong> where $plugin is a reference to the plugin as processed and $info is the fully processed result of hook_ctools_plugin_api_info().
+<dt>extension</dt>
+<dd><em>Defaults to:</em> <strong>'inc'</strong></dd>
+<dd>Can be used to change the extension on files containing plugins of this type. By default the extension will be "inc", though it will default to "info" if "info files" is set to true. Do not include the dot in the extension if changing it, that will be added automatically.</dd>
+<dt>info file</dt>
+<dd><em>Defaults to:</em> <strong>FALSE</strong></dd>
+<dd>If set to TRUE, then the plugin will look for a .info file instead of a .inc. Internally, this will look exactly the same, though obviously a .info file cannot contain functions. This can be good for styles that may not need to contain code.</dd>
+<dt>use hooks</dt>
+<dd><em>Defaults to:</em> <strong>TRUE</strong>*</dd>
+<dd>Use to enable support for plugin definition hooks instead of plugin definition files. NOTE: using a central plugin definition hook is less optimal for the plugins system, and as such this will default to FALSE in later versions.</dd>
+<dt>child plugins</dt>
+<dd><em>Defaults to:</em> <strong>FALSE</strong></dd>
+<dd>If set to TRUE, the plugin type can automatically have 'child plugins' meaning each plugin can actually provide multiple plugins. This is mostly used for plugins that store some of their information in the database, such as views, blocks or exportable custom versions of plugins.</dd>
+<dd>To implement, each plugin can have a 'get child' and 'get children' callback. Both of these should be implemented for performance reasons, since it is best to avoid getting all children if necessary, but if 'get child' is not implemented, it will fall back to 'get children' if it has to.</dd>
+<dd>Child plugins should be named parent:child, with the : being the separator, so that it knows which parent plugin to ask for the child. The 'get children' method should at least return the parent plugin as part of the list, unless it wants the parent plugin itself to not be a choosable option, which is not unheard of. </dd>
+<dd>'get children' arguments are ($plugin, $parent) and 'get child' arguments are ($plugin, $parent, $child).
+</dl>
+
+In addition, there is a 'module' and 'type' settings; these are for internal use of the plugin system and you should not change these.
+<h2>Getting the data</h2>
+To create a plugin, a module only has to execute ctools_get_plugins with the right data:
+
+<pre>
+ ctools_include('plugins');
+ ctools_get_plugins($module, $type, [$id])
+</pre>
+
+In the above example, $module should be your module's name and $type is the type of the plugin. It is typically best practice to provide some kind of wrapper function to make this easier. For example, Panels provides the following functions to implement the 'content_types' plugin:
+
+<pre>
+/**
+ * Fetch metadata on a specific content_type plugin.
+ *
+ * @param $content type
+ * Name of a panel content type.
+ *
+ * @return
+ * An array with information about the requested panel content type.
+ */
+function panels_get_content_type($content_type) {
+ ctools_include('context');
+ ctools_include('plugins');
+ return ctools_get_plugins('panels', 'content_types', $content_type);
+}
+
+/**
+ * Fetch metadata for all content_type plugins.
+ *
+ * @return
+ * An array of arrays with information about all available panel content types.
+ */
+function panels_get_content_types() {
+ ctools_include('context');
+ ctools_include('plugins');
+ return ctools_get_plugins('panels', 'content_types');
+}
+</pre>
+
+<h2>Using the data</h2>
+
+Each plugin returns a packet of data, which is added to with a few defaults. Each plugin is guaranteed to always have the following data:
+<dl>
+<dt>name</dt>
+<dd>The name of the plugin. This is also the key in the array, of the full list of plugins, and is placed here since that is not always available.</dd>
+<dt>module</dt>
+<dd>The module that supplied the plugin.</dd>
+<dt>file</dt>
+<dd>The actual file containing the plugin.</dd>
+<dt>path</dt>
+<dd>The path to the file containing the plugin. This is useful for using secondary files, such as templates, css files, images, etc, that may come with a plugin.</dd>
+</dl>
+
+<p>Any of the above items can be overridden by the plugin itself, though the most likely one to be modified is the 'path'.</p>
+
+<p>The most likely data (beyond simple printable data) for a plugin to provide is a callback. The plugin system provides a pair of functions to make it easy and consistent for these callbacks to be used. The first is ctools_plugin_get_function, which requires the full $plugin object.</p>
+
+<pre>
+/**
+ * Get a function from a plugin, if it exists. If the plugin is not already
+ * loaded, try ctools_plugin_load_function() instead.
+ *
+ * @param $plugin
+ * The loaded plugin type.
+ * @param $callback_name
+ * The identifier of the function. For example, 'settings form'.
+ *
+ * @return
+ * The actual name of the function to call, or NULL if the function
+ * does not exist.
+ */
+function ctools_plugin_get_function($plugin, $callback_name)
+</pre>
+
+<p>The second does not require the full $plugin object, and will load it:</p>
+
+<pre>
+/**
+ * Load a plugin and get a function name from it, returning success only
+ * if the function exists.
+ *
+ * @param $module
+ * The module that owns the plugin type.
+ * @param $type
+ * The type of plugin.
+ * @param $id
+ * The id of the specific plugin to load.
+ * @param $callback_name
+ * The identifier of the function. For example, 'settings form'.
+ *
+ * @return
+ * The actual name of the function to call, or NULL if the function
+ * does not exist.
+ */
+function ctools_plugin_load_function($module, $type, $id, $callback_name) {
+</pre>
+
+<p>Both of these functions will ensure any needed files are included. In fact, it allows each callback to specify alternative include files. The plugin implementation could include code like this:</p>
+
+<pre>
+ 'callback_name' => 'actual_name_of_function_here',
+</pre>
+
+<p>Or like this:</p>
+<pre>
+ 'callback_name' => array(
+ 'file' => 'filename',
+ 'path' => 'filepath', // optional, will use plugin path if absent
+ 'function' => 'actual_name_of_function_here',
+ ),
+</pre>
+
+<p>An example, for 'plugin_example' type</p>
+
+<pre>
+$plugin = array(
+ 'name' => 'my_plugin',
+ 'module' => 'my_module',
+ 'example_callback' => array(
+ 'file' => 'my_plugin.extrafile.inc',
+ 'function' => 'my_module_my_plugin_example_callback',
+ ),
+);
+</pre>
+
+<p>To utilize this callback on this plugin:</p>
+
+<pre>
+if ($function = ctools_plugin_get_function($plugin, 'example_callback')) {
+ $function($arg1, $arg2, $etc);
+}
+</pre>
+
+<h2>Document your plugins!</h2>
+
+<p>Since the data provided by your plugin tends to be specific to your plugin type, you really need to document what the data returned in the hook in the .inc file will be or nobody will figure it out. Use advanced help and document it there. If every system that utilizes plugins does this, then plugin implementors will quickly learn to expect the documentation to be in the advanced help.</p>
diff --git a/sites/all/modules/ctools/help/plugins-implementing.html b/sites/all/modules/ctools/help/plugins-implementing.html
new file mode 100644
index 000000000..c95e72d42
--- /dev/null
+++ b/sites/all/modules/ctools/help/plugins-implementing.html
@@ -0,0 +1,62 @@
+<p>There are two parts to implementing a plugin: telling the system where it is, and implementing one or more .inc files that contain the plugin data.</p>
+
+<h2>Telling the system where your plugins live</h2>
+<h3>How a module implements plugins</h3>
+<p>To implement any plugins at all, you must implement a single function for all plugins: <strong>hook_ctools_plugin_directory</strong>. Every time a module loads plugins, this hook will be called to see which modules implement those plugins and in what directory those plugins will live.</p>
+
+<pre>
+function hook_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'panels' && $plugin == 'content_types') {
+ return 'plugins/content_types';
+ }
+}
+</pre>
+
+<p>The directory returned should be relative to your module. Another common usage is to simply return that you implement all plugins owned by a given module (or modules):</p>
+
+<pre>
+function hook_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'panels') {
+ return 'plugins/' . $plugin;
+ }
+}
+</pre>
+
+<p>Typically, it is recommended that all plugins be placed into the 'plugins' directory for clarity and maintainability. Inside the directory, any number of subdirectories can be used. For plugins that require extra files, such as templates, css, javascript or image files, this is highly recommended:</p>
+<pre>
+mymodule.module
+mymodule.info
+plugins/
+ content_types/
+ my_content_type.inc
+ layouts/
+ my_layout.inc
+ my_layout.css
+ my_layout.tpl.php
+ my_layout_image.png
+</pre>
+
+<h3>How a theme implements plugins</h3>
+<p>Themes can implement plugins if the plugin owner specified that it's possible in its hook_ctools_plugin_type() call. If so, it is generally exactly the same as modules, except for one important difference: themes don't get hook_ctools_plugin_directory(). Instead, themes add a line to their .info file:</p>
+
+<pre>
+plugins[module][type] = directory
+</pre>
+
+<h2>How to structure the .inc file</h2>
+
+<p>The top of the .inc file should contain an array that defines the plugin. This array is simply defined in the global namespace of the file and does not need a function. Note that previous versions of this plugin system required a specially named function. While this function will still work, its use is now discouraged, as it is annoying to name properly.</p>
+
+<p>This array should look something like this:</p>
+
+<pre>
+$plugin = array(
+ 'key' => 'value',
+);
+</pre>
+
+<p>Several values will be filled in for you automatically, but you can override them if necessary. They include 'name', 'path', 'file' and 'module'. Additionally, the plugin owner can provide other defaults as well.</p>
+
+<p>There are no required keys by the plugin system itself. The only requirements in the $plugin array will be defined by the plugin type.</p>
+
+<p>After this array, if your plugin needs functions, they can be declared. Different plugin types have different needs here, so exactly what else will be needed will change from type to type.</p>
diff --git a/sites/all/modules/ctools/help/plugins.html b/sites/all/modules/ctools/help/plugins.html
new file mode 100644
index 000000000..1e64da095
--- /dev/null
+++ b/sites/all/modules/ctools/help/plugins.html
@@ -0,0 +1,5 @@
+<p>The plugins tool allows a module to allow <b>other</b> modules (and themes!) to provide plugins which provide some kind of functionality or some kind of task. For example, in Panels there are several types of plugins: Content types (which are like blocks), layouts (which are page layouts) and styles (which can be used to style a panel). Each plugin is represented by a .inc file, and the functionality they offer can differ wildly.</p>
+
+<p>A module which uses plugins can implement a hook describing the plugin (which is not necessary, as defaults will be filled in) and then calls a ctools function which loads either all the known plugins (used for listing/choosing) or a specific plugin (used when it's known which plugin is needed). From the perspective of the plugin system, a plugin is a packet of data, usually some printable info and a list of callbacks. It is up to the module implementing plugins to determine what that info means and what the callbacks do.</p>
+
+<p>A module which implements plugins must first implement the <strong>hook_ctools_plugin_directory</strong> function, which simply tells the system which plugins are supported and what directory to look in. Each plugin will then be in a separate .inc file in the directory supplied. The .inc file will contain a specially named hook which returns the data necessary to implement the plugin.</p>
diff --git a/sites/all/modules/ctools/help/wizard.html b/sites/all/modules/ctools/help/wizard.html
new file mode 100644
index 000000000..33fc45622
--- /dev/null
+++ b/sites/all/modules/ctools/help/wizard.html
@@ -0,0 +1,311 @@
+<p>Form wizards, or multi-step forms, are a process by which the user goes through or can use an arbitrary number of different forms to create a single object or perform a single task. Traditionally the multi-step form is difficult in Drupal because there is no easy place to put data in between forms. No longer! The form wizard tool allows a single entry point to easily set up a wizard of multiple forms, provide callbacks to handle data storage and updates between forms and when forms are completed. This tool pairs well with the <a href="&topic:ctools/object-cache&">object cache</a> tool for storage.</p>
+
+<h2>The form info array</h2>
+<p>The wizard starts with an array of data that describes all of the forms available to the wizard and sets options for how the wizard will present and control the flow. Here is an example of the $form_info array as used in the delegator module:</p>
+
+<pre>
+ $form_info = array(
+ 'id' => 'delegator_page',
+ 'path' => "admin/structure/pages/edit/$page_name/%step",
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'show return' => FALSE,
+ 'next callback' => 'delegator_page_add_subtask_next',
+ 'finish callback' => 'delegator_page_add_subtask_finish',
+ 'return callback' => 'delegator_page_add_subtask_finish',
+ 'cancel callback' => 'delegator_page_add_subtask_cancel',
+ 'order' => array(
+ 'basic' => t('Basic settings'),
+ 'argument' => t('Argument settings'),
+ 'access' => t('Access control'),
+ 'menu' => t('Menu settings'),
+ 'multiple' => t('Task handlers'),
+ ),
+ 'forms' => array(
+ 'basic' => array(
+ 'form id' => 'delegator_page_form_basic'
+ ),
+ 'access' => array(
+ 'form id' => 'delegator_page_form_access'
+ ),
+ 'menu' => array(
+ 'form id' => 'delegator_page_form_menu'
+ ),
+ 'argument' => array(
+ 'form id' => 'delegator_page_form_argument'
+ ),
+ 'multiple' => array(
+ 'form id' => 'delegator_page_argument_form_multiple'
+ ),
+ ),
+ );
+</pre>
+
+<p>The above array starts with an <strong>id</strong> which is used to identify the wizard in various places and a <strong>path</strong> which is needed to redirect to the next step between forms. It then creates some <strong>settings</strong> which control which pieces are displayed. In this case, it displays a form trail and a 'back' button, but not the 'return' button. Then there are the <strong>wizard callbacks</strong> which allow the wizard to act appropriately when forms are submitted. Finally it contains a <strong>list of forms</strong> and their <strong>order</strong> so that it knows which forms to use and what order to use them by default. Note that the keys in the order and list of forms match; that key is called the <strong>step</strong> and is used to identify each individual step of the wizard.</p>
+
+<p>Here is a full list of every item that can be in the form info array:</p>
+
+<dl>
+<dt>id</dt>
+<dd>An id for wizard. This is used like a hook to automatically name <strong>callbacks</strong>, as well as a form step's form building function. It is also used in trail theming.</dd>
+
+<dt>path</dt>
+<dd>The path to use when redirecting between forms. <strong>%step</strong> will be replaced with the key for the form.</dd>
+
+<dt>return path</dt>
+<dd>When a form is complete, this is the path to go to. This is required if the 'return' button is shown and not using AJAX. It is also used for the 'Finish' button. If it is not present and needed, the cancel path will also be checked.</dd>
+
+<dt>cancel path</dt>
+<dd>When a form is canceled, this is the path to go to. This is required if the 'cancel' is shown and not using AJAX.</dd>
+
+<dt>show trail</dt>
+<dd>If set to TRUE, the form trail will be shown like a breadcrumb at the top of each form. Defaults to FALSE.</dd>
+
+<dt>show back</dt>
+<dd>If set to TRUE, show a back button on each form. Defaults to FALSE.</dd>
+
+<dt>show return</dt>
+<dd>If set to TRUE, show a return button. Defaults to FALSE.</dd>
+
+<dt>show cancel</dt>
+<dd>If set to TRUE, show a cancel button. Defaults to FALSE.</dd>
+
+<dt>back text</dt>
+<dd>Set the text of the 'back' button. Defaults to t('Back').</dd>
+
+<dt>next text</dt>
+<dd>Set the text of the 'next' button. Defaults to t('Continue').</dd>
+
+<dt>return text</dt>
+<dd>Set the text of the 'return' button. Defaults to t('Update and return').</dd>
+
+<dt>finish text</dt>
+<dd>Set the text of the 'finish' button. Defaults to t('Finish').</dd>
+
+<dt>cancel text</dt>
+<dd>Set the text of the 'cancel' button. Defaults to t('Cancel').</dd>
+
+<dt>ajax</dt>
+<dd>Turn on AJAX capabilities, using CTools' ajax.inc. Defaults to FALSE.</dd>
+
+<dt>modal</dt>
+<dd>Put the wizard in the modal tool. The modal must already be open and called from an ajax button for this to work, which is easily accomplished using functions provided by the modal tool.</dd>
+
+<dt>ajax render</dt>
+<dd>A callback to display the rendered form via ajax. This is not required if using the modal tool, but is required otherwise since ajax by itself does not know how to render the results. Params: &$form_state, $output.</dd>
+
+<dt>finish callback</dt>
+<dd>The function to call when a form is complete and the finish button has been clicked. This function should finalize all data. Params: &$form_state.
+Defaults to $form_info['id']._finish if function exists.
+</dd>
+
+<dt>cancel callback</dt>
+<dd>The function to call when a form is canceled by the user. This function should clean up any data that is cached. Params: &$form_state.
+Defaults to $form_info['id']._cancel if function exists.</dd>
+
+<dt>return callback</dt>
+<dd>The function to call when a form is complete and the return button has been clicked. This is often the same as the finish callback. Params: &$form_state.
+Defaults to $form_info['id']._return if function exists.</dd>
+
+<dt>next callback</dt>
+<dd>The function to call when the next button has been clicked. This function should take the submitted data and cache it for later use by the finish callback. Params: &$form_state.
+Defaults to $form_info['id']._next if function exists.</dd>
+
+<dt>order</dt>
+<dd>An optional array of forms, keyed by the step, which represents the default order the forms will be displayed in. If not set, the forms array will control the order. Note that submit callbacks can override the order so that branching logic can be used.</dd>
+
+<dt>forms</dt>
+<dd>An array of form info arrays, keyed by step, describing every form available to the wizard. If order array isn't set, the wizard will use this to set the default order. Each array contains:
+ <dl>
+ <dt>form id</dt>
+ <dd>
+ The id of the form, as used in the Drupal form system. This is also the name of the function that represents the form builder.
+ Defaults to $form_info['id']._.$step._form.
+ </dd>
+
+ <dt>include</dt>
+ <dd>The name of a file to include which contains the code for this form. This makes it easy to include the form wizard in another file or set of files. This must be the full path of the file, so be sure to use drupal_get_path() when setting this. This can also be an array of files if multiple files need to be included.</dd>
+
+ <dt>title</dt>
+ <dd>The title of the form, to be optionally set via drupal_get_title. This is required when using the modal if $form_state['title'] is not set.</dd>
+ </dl>
+</dd>
+</dl>
+
+<h2>Invoking the form wizard</h2>
+<p>Your module should create a page callback via hook_menu, and this callback should contain an argument for the step. The path that leads to this page callback should be the same as the 'path' set in the $form_info array.</p>
+
+<p>The page callback should set up the $form_info, and figure out what the default step should be if no step is provided (note that the wizard does not do this for you; you MUST specify a step). Then invoke the form wizard:</p>
+
+<pre>
+ $form_state = array();
+ ctools_include('wizard');
+ $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
+</pre>
+
+<p>If using AJAX or the modal, This part is actually done! If not, you have one more small step:</p>
+
+<pre>
+ return $output;
+</pre>
+
+<h2>Forms and their callbacks</h2>
+<p>Each form within the wizard is a complete, independent form using Drupal's Form API system. It has a form builder callback as well as submit and validate callbacks and can be form altered. The primary difference between these forms and a normal Drupal form is that the submit handler should not save any data. Instead, it should make any changes to a cached object (usually placed on the $form_state) and only the _finish or _return handler should actually save any real data.</p>
+
+<p>How you handle this is completely up to you. The recommended best practice is to use the CTools Object cache, and a good way to do this is to write a couple of wrapper functions around the cache that look like these example functions:</p>
+
+<pre>
+/**
+ * Get the cached changes to a given task handler.
+ */
+function delegator_page_get_page_cache($name) {
+ ctools_include('object-cache');
+ $cache = ctools_object_cache_get('delegator_page', $name);
+ if (!$cache) {
+ $cache = delegator_page_load($name);
+ $cache->locked = ctools_object_cache_test('delegator_page', $name);
+ }
+
+ return $cache;
+}
+
+/**
+ * Store changes to a task handler in the object cache.
+ */
+function delegator_page_set_page_cache($name, $page) {
+ ctools_include('object-cache');
+ $cache = ctools_object_cache_set('delegator_page', $name, $page);
+}
+
+/**
+ * Remove an item from the object cache.
+ */
+function delegator_page_clear_page_cache($name) {
+ ctools_include('object-cache');
+ ctools_object_cache_clear('delegator_page', $name);
+}
+</pre>
+
+<p>Using these wrappers, when performing a get_cache operation, it defaults to loading the real object. It then checks to see if another user has this object cached using the ctools_object_cache_test() function, which automatically sets a lock (which can be used to prevent writes later on).</p>
+
+<p>With this set up, the _next, _finish and _cancel callbacks are quite simple:</p>
+
+<pre>
+/**
+ * Callback generated when the add page process is finished.
+ */
+function delegator_page_add_subtask_finish(&$form_state) {
+ $page = &$form_state['page'];
+
+ // Create a real object from the cache
+ delegator_page_save($page);
+
+ // Clear the cache
+ delegator_page_clear_page_cache($form_state['cache name']);
+}
+
+/**
+ * Callback generated when the 'next' button is clicked.
+ *
+ * All we do here is store the cache.
+ */
+function delegator_page_add_subtask_next(&$form_state) {
+ // Update the cache with changes.
+ delegator_page_set_page_cache($form_state['cache name'], $form_state['page']);
+}
+
+/**
+ * Callback generated when the 'cancel' button is clicked.
+ *
+ * All we do here is clear the cache.
+ */
+function delegator_page_add_subtask_cancel(&$form_state) {
+ // Update the cache with changes.
+ delegator_page_clear_page_cache($form_state['cache name']);
+}
+</pre>
+
+<p>All that's needed to tie this together is to understand how the changes made it into the cache in the first place. This happened in the various form _submit handlers, which made changes to $form_state['page'] based upon the values set in the form:</p>
+
+<pre>
+/**
+ * Store the values from the basic settings form.
+ */
+function delegator_page_form_basic_submit($form, &$form_state) {
+ if (!isset($form_state['page']->pid) && empty($form_state['page']->import)) {
+ $form_state['page']->name = $form_state['values']['name'];
+ }
+
+ $form_state['page']->admin_title = $form_state['values']['admin_title'];
+ $form_state['page']->path = $form_state['values']['path'];
+
+ return $form;
+}
+</pre>
+
+<p>No database operations were made during this _submit, and that's a very important distinction about this system.</p>
+
+<h3>Proper handling of back button</h3>
+<p>When using <strong>'show back' => TRUE</strong> the cached data should be assigned to the <em>#default_value</em> form property. Otherwise when the user goes back to the previous step the forms default values instead of his (cached) input is used.</p>
+
+<pre>
+/**
+ * Form builder function for wizard.
+ */
+function wizardid_step2_form($form, &$form_state) {
+ $form_state['my data'] = my_module_get_cache($form_state['cache name']);
+ $form['example'] = array(
+ '#type' => 'radios',
+ '#title' => t('Title'),
+ '#default_value' => $form_state['my data']->example ? $form_state['my data']->example : default,
+ '#options' => array(
+ 'default' => t('Default'),
+ 'setting1' => t('Setting1'),
+ ),
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler to prepare needed values for storing in cache.
+ */
+function wizardid_step2_form_submit($form, &$form_state) {
+ $form_state['my data']->example = $form_state['values']['example'];
+}
+</pre>
+
+<p>The data is stored in the <em>my data</em> object on submitting. If the user goes back to this step the cached <em>my data</em> is used as the default form value. The function <em>my_module_get_cache()</em> is like the cache functions explained above.</p>
+
+<h2>Required fields, cancel and back buttons</h2>
+<p>If you have required fields in your forms, the back and cancel buttons will not work as expected since validation of the form will fail. You can add the following code to the top of your form validation to avoid this problem:</p>
+<pre>
+/**
+ * Validation handler for step2 form
+ */
+function wizardid_step2_form_validate(&$form, &$form_state) {
+ // if the clicked button is anything but the normal flow
+ if ($form_state['clicked_button']['#next'] != $form_state['next']) {
+ drupal_get_messages('error');
+ form_set_error(NULL, '', TRUE);
+ return;
+ }
+ // you form validation goes here
+ // ...
+}
+</pre>
+
+<h2>Wizard for anonymous users</h2>
+<p>If you are creating a wizard which is be used by anonymous users, you might run into some issues with drupal's caching for anonymous users. You can circumvent this by using hook_init and telling drupal to not cache your wizard pages:</p>
+<pre>
+/**
+ * Implementation of hook init
+ */
+function mymodule_init() {
+ // if the path leads to the wizard
+ if (drupal_match_path($_GET['q'], 'path/to/your/wizard/*')) {
+ // set cache to false
+ $GLOBALS['conf']['cache'] = FALSE;
+ }
+}
+</pre>
diff --git a/sites/all/modules/ctools/images/arrow-active.png b/sites/all/modules/ctools/images/arrow-active.png
new file mode 100644
index 000000000..3bbd3c27f
--- /dev/null
+++ b/sites/all/modules/ctools/images/arrow-active.png
Binary files differ
diff --git a/sites/all/modules/ctools/images/collapsible-collapsed.png b/sites/all/modules/ctools/images/collapsible-collapsed.png
new file mode 100644
index 000000000..95a214a6e
--- /dev/null
+++ b/sites/all/modules/ctools/images/collapsible-collapsed.png
Binary files differ
diff --git a/sites/all/modules/ctools/images/collapsible-expanded.png b/sites/all/modules/ctools/images/collapsible-expanded.png
new file mode 100644
index 000000000..46f39ecb3
--- /dev/null
+++ b/sites/all/modules/ctools/images/collapsible-expanded.png
Binary files differ
diff --git a/sites/all/modules/ctools/images/expanded-options.png b/sites/all/modules/ctools/images/expanded-options.png
new file mode 100644
index 000000000..b7b755c0a
--- /dev/null
+++ b/sites/all/modules/ctools/images/expanded-options.png
Binary files differ
diff --git a/sites/all/modules/ctools/images/icon-close-window.png b/sites/all/modules/ctools/images/icon-close-window.png
new file mode 100644
index 000000000..5f0cf695b
--- /dev/null
+++ b/sites/all/modules/ctools/images/icon-close-window.png
Binary files differ
diff --git a/sites/all/modules/ctools/images/icon-configure.png b/sites/all/modules/ctools/images/icon-configure.png
new file mode 100644
index 000000000..e23d67cc0
--- /dev/null
+++ b/sites/all/modules/ctools/images/icon-configure.png
Binary files differ
diff --git a/sites/all/modules/ctools/images/icon-delete.png b/sites/all/modules/ctools/images/icon-delete.png
new file mode 100644
index 000000000..5f0cf695b
--- /dev/null
+++ b/sites/all/modules/ctools/images/icon-delete.png
Binary files differ
diff --git a/sites/all/modules/ctools/images/no-icon.png b/sites/all/modules/ctools/images/no-icon.png
new file mode 100644
index 000000000..fa78ec179
--- /dev/null
+++ b/sites/all/modules/ctools/images/no-icon.png
Binary files differ
diff --git a/sites/all/modules/ctools/images/status-active.gif b/sites/all/modules/ctools/images/status-active.gif
new file mode 100644
index 000000000..207e95c3f
--- /dev/null
+++ b/sites/all/modules/ctools/images/status-active.gif
Binary files differ
diff --git a/sites/all/modules/ctools/images/throbber.gif b/sites/all/modules/ctools/images/throbber.gif
new file mode 100644
index 000000000..8a084b844
--- /dev/null
+++ b/sites/all/modules/ctools/images/throbber.gif
Binary files differ
diff --git a/sites/all/modules/ctools/includes/action-links.theme.inc b/sites/all/modules/ctools/includes/action-links.theme.inc
new file mode 100644
index 000000000..3a2398a1f
--- /dev/null
+++ b/sites/all/modules/ctools/includes/action-links.theme.inc
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @file
+ * Theme function for wrapping menu local actions.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
+ */
+function ctools_action_links_theme(&$items) {
+ $items['ctools_menu_local_actions_wrapper'] = array(
+ 'render element' => 'links',
+ 'file' => 'includes/action-links.theme.inc',
+ );
+}
+
+/**
+ * Render a menu local actions wrapper.
+ *
+ * @param $links
+ * Local actions links.
+ * @param $attributes
+ * An array of attributes to append to the wrapper.
+ */
+function theme_ctools_menu_local_actions_wrapper($variables) {
+ $links = drupal_render($variables['links']);
+
+ if (empty($links)) {
+ return;
+ }
+
+ return '<ul class="action-links">' . $links . '</ul>';
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/includes/ajax.inc b/sites/all/modules/ctools/includes/ajax.inc
new file mode 100644
index 000000000..96f5068f3
--- /dev/null
+++ b/sites/all/modules/ctools/includes/ajax.inc
@@ -0,0 +1,157 @@
+<?php
+
+// Set this so we can tell that the file has been included at some point.
+define('CTOOLS_AJAX_INCLUDED', 1);
+
+/**
+ * @file
+ * Extend core AJAX with some of our own stuff.
+ */
+
+/**
+ * Render an image as a button link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * @param $image
+ * The path to an image to use that will be sent to theme('image') for rendering.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ */
+function ctools_ajax_image_button($image, $dest, $alt, $class = '') {
+ return ctools_ajax_text_button(theme('image', array('path' => $image)), $dest, $alt, $class);
+}
+
+/**
+ * Render text as a link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will
+ * not use user input so this is a very minor concern.
+ *
+ * @param $text
+ * The text that will be displayed as the link.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ * @param $type
+ * A type to use, in case a different behavior should be attached. Defaults
+ * to ctools-use-ajax.
+ */
+function ctools_ajax_text_button($text, $dest, $alt, $class = '', $type = 'use-ajax') {
+ drupal_add_library('system', 'drupal.ajax');
+ return l($text, $dest, array('html' => TRUE, 'attributes' => array('class' => array($type, $class), 'title' => $alt)));
+}
+
+/**
+ * Render an icon and related text as a link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will
+ * not use user input so this is a very minor concern.
+ *
+ * @param $text
+ * The text that will be displayed as the link.
+ * @param $image
+ * The icon image to include in the link.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The title text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ * @param $type
+ * A type to use, in case a different behavior should be attached. Defaults
+ * to ctools-use-ajax.
+ */
+function ctools_ajax_icon_text_button($text, $image, $dest, $alt, $class = '', $type = 'use-ajax') {
+ drupal_add_library('system', 'drupal.ajax');
+ $rendered_image = theme('image', array('path' => $image));
+ $link_content = $rendered_image . "<span>" . $text . "</span>";
+ return l($link_content, $dest, array('html' => TRUE, 'attributes' => array('class' => array($type, $class), 'title' => $alt)));
+}
+
+/**
+ * Set a single property to a value, on all matched elements.
+ *
+ * @param $selector
+ * The CSS selector. This can be any selector jquery uses in $().
+ * @param $name
+ * The name or key: of the data attached to this selector.
+ * @param $value
+ * The value of the data.
+ */
+function ctools_ajax_command_attr($selector, $name, $value) {
+ ctools_add_js('ajax-responder');
+ return array(
+ 'command' => 'attr',
+ 'selector' => $selector,
+ 'name' => $name,
+ 'value' => $value,
+ );
+ }
+
+/**
+ * Force a client-side redirect.
+ *
+ * @param $url
+ * The url to be redirected to. This can be an absolute URL or a
+ * Drupal path.
+ * @param $delay
+ * A delay before applying the redirection, in milliseconds.
+ * @param $options
+ * An array of options to pass to the url() function.
+ */
+function ctools_ajax_command_redirect($url, $delay = 0, $options = array()) {
+ ctools_add_js('ajax-responder');
+ return array(
+ 'command' => 'redirect',
+ 'url' => url($url, $options),
+ 'delay' => $delay,
+ );
+}
+
+/**
+ * Force a reload of the current page.
+ */
+function ctools_ajax_command_reload() {
+ ctools_add_js('ajax-responder');
+ return array(
+ 'command' => 'reload',
+ );
+}
+
+/**
+ * Submit a form.
+ *
+ * This is useful for submitting a parent form after a child form has finished
+ * processing in a modal overlay.
+ *
+ * @param $selector
+ * The CSS selector to identify the form for submission. This can be any
+ * selector jquery uses in $().
+ */
+function ctools_ajax_command_submit($selector) {
+ ctools_add_js('ajax-responder');
+ return array(
+ 'command' => 'submit',
+ 'selector' => $selector,
+ );
+}
+
+/**
+ * Send an error response back via AJAX and immediately exit.
+ */
+function ctools_ajax_render_error($error = '') {
+ $commands = array();
+ $commands[] = ajax_command_alert($error);
+ print ajax_render($commands);
+ exit;
+}
+
diff --git a/sites/all/modules/ctools/includes/cache.inc b/sites/all/modules/ctools/includes/cache.inc
new file mode 100644
index 000000000..3918683b0
--- /dev/null
+++ b/sites/all/modules/ctools/includes/cache.inc
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugins to handle cache-indirection.
+ *
+ * Simple plugin management to allow clients to more tightly control where
+ * object caches are stored.
+ *
+ * CTools provides an object cache mechanism, and it also provides a number
+ * of subsystems that are designed to plug into larger systems. When doing
+ * caching on multi-step forms (in particular during AJAX operations) these
+ * subsystems often need to operate their own cache. In reality, its best
+ * for everyone if they are able to piggyback off of the larger cache.
+ *
+ * This system allows this by registering plugins to control where caches
+ * are actually stored. For the most part, the subsystems could care less
+ * where the data is fetched from and stored to. All it needs to know is
+ * that it can 'get', 'set' and 'clear' caches. Additionally, some caches
+ * might need extra operations such as 'lock' and 'finalize', and other
+ * operations may be needed based upon the specific uses for the cache
+ * plugins.
+ *
+ * To utilize cache plugins, there are two pieces of data. First, there is
+ * the mechanism, which is simply the name of the plugin to use. CTools
+ * provides a 'simple' mechanism which goes straight through to the object
+ * cache. The second piece of data is the 'key' which is a unique identifier
+ * that can be used to find the data needed. Keys can be generated any number
+ * of ways, and the plugin must be agnostic about the key itself.
+ *
+ * That said, the 'mechanism' can be specified as pluginame::data and that
+ * data can be used to derive additional data. For example, it is often
+ * desirable to NOT store any cached data until original data (i.e, user
+ * input) has been received. The data can be used to derive this original
+ * data so that when a 'get' is called, if the cache is missed it can create
+ * the data needed. This can help prevent unwanted cache entries from
+ * building up just by visiting edit UIs without actually modifying anything.
+ *
+ * Modules wishing to implement cache indirection mechanisms need to implement
+ * a plugin of type 'cache' for the module 'ctools' and provide the .inc file.
+ * It should provide callbacks for 'cache set', 'cache get', and 'cache clear'.
+ * It can provide callbacks for 'break' and 'finalize' if these are relevant
+ * to the caching mechanism (i.e, for use with locking caches such as the page
+ * manager cache). Other operations may be utilized but at this time are not part
+ * of CTools.
+ */
+
+/**
+ * Fetch data from an indirect cache.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @param string $key
+ * The key used to identify the cache.
+ *
+ * @return mixed
+ * The cached data. This can be any format as the plugin does not necessarily
+ * have knowledge of what is being cached.
+ */
+function ctools_cache_get($mechanism, $key) {
+ return ctools_cache_operation($mechanism, $key, 'get');
+}
+
+/**
+ * Store data in an indirect cache.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @param string $key
+ * The key used to identify the cache.
+ *
+ * @param mixed $object
+ * The data to cache. This can be any format as the plugin does not
+ * necessarily have knowledge of what is being cached.
+ */
+function ctools_cache_set($mechanism, $key, $object) {
+ return ctools_cache_operation($mechanism, $key, 'set', $object);
+}
+
+/**
+ * Clear data from an indirect cache.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @param string $key
+ * The key used to identify the cache.
+ */
+function ctools_cache_clear($mechanism, $key) {
+ return ctools_cache_operation($mechanism, $key, 'clear');
+
+}
+
+/**
+ * Perform a secondary operation on an indirect cache.
+ *
+ * Additional operations, beyond get, set and clear may be items
+ * such as 'break' and 'finalize', which are needed to support cache
+ * locking. Other operations may be added by users of the indirect
+ * caching functions as needed.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @param string $key
+ * The key used to identify the cache.
+ *
+ * @param string $op
+ * The operation to call, such as 'break' or 'finalize'.
+ *
+ * @param mixed $object
+ * The cache data being operated on, in case it is necessary. This is
+ * optional so no references should be used.
+ *
+ * @return mixed
+ * The operation may or may not return a value.
+ */
+function ctools_cache_operation($mechanism, $key, $op, $object = NULL) {
+ list($plugin, $data) = ctools_cache_find_plugin($mechanism);
+ if (empty($plugin)) {
+ return;
+ }
+
+ $function = ctools_plugin_get_function($plugin, "cache $op");
+ if (empty($function)) {
+ return;
+ }
+
+ return $function($data, $key, $object, $op);
+}
+
+/**
+ * Take a mechanism and return a plugin and data.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @return array
+ * An array, the first element will be the plugin and the second element
+ * will be the data. If the plugin could not be found, the $plugin will
+ * be NULL.
+ */
+function ctools_cache_find_plugin($mechanism) {
+ if (strpos($mechanism, '::') !== FALSE) {
+ // use explode(2) to ensure that the data can contain double
+ // colons, just in case.
+ list($name, $data) = explode('::', $mechanism, 2);
+ }
+ else {
+ $name = $mechanism;
+ $data = '';
+ }
+
+ if (empty($name)) {
+ return array(NULL, $data);
+ }
+
+ ctools_include('plugins');
+ $plugin = ctools_get_plugins('ctools', 'cache', $name);
+ return array($plugin, $data);
+}
diff --git a/sites/all/modules/ctools/includes/cache.plugin-type.inc b/sites/all/modules/ctools/includes/cache.plugin-type.inc
new file mode 100644
index 000000000..1ab4cdd13
--- /dev/null
+++ b/sites/all/modules/ctools/includes/cache.plugin-type.inc
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @file
+ * Contains plugin type registration information for the cache tool.
+ */
+
+function ctools_cache_plugin_type(&$items) {
+ // There are no options to declare on this plugin at this time.
+ $items['cache'] = array();
+}
diff --git a/sites/all/modules/ctools/includes/cleanstring.inc b/sites/all/modules/ctools/includes/cleanstring.inc
new file mode 100644
index 000000000..56b3e36f4
--- /dev/null
+++ b/sites/all/modules/ctools/includes/cleanstring.inc
@@ -0,0 +1,204 @@
+<?php
+// $Id $
+
+/**
+ * @file
+ * Helper class to clean strings to make them URL safe and translatable.
+ *
+ * This was copied directly from pathauto and put here to be made available
+ * to all, because more things than just pathauto want URL safe strings.
+ *
+ * To use, simply:
+ * @code
+ * ctools_include('cleanstring');
+ * $output = ctools_cleanstring($string);
+ *
+ * You can add a variety of settings as an array in the second argument,
+ * including words to ignore, how to deal with punctuation, length
+ * limits, and more. See the function itself for options.
+ */
+
+/**
+ * Matches Unicode character classes.
+ *
+ * See: http://www.unicode.org/Public/UNIDATA/UCD.html#General_Category_Values
+ *
+ * The index only contains the following character classes:
+ * Lu Letter, Uppercase
+ * Ll Letter, Lowercase
+ * Lt Letter, Titlecase
+ * Lo Letter, Other
+ * Nd Number, Decimal Digit
+ * No Number, Other
+ *
+ * Copied from search.module's PREG_CLASS_SEARCH_EXCLUDE.
+ */
+define('CTOOLS_PREG_CLASS_ALNUM',
+'\x{0}-\x{2f}\x{3a}-\x{40}\x{5b}-\x{60}\x{7b}-\x{bf}\x{d7}\x{f7}\x{2b0}-' .
+'\x{385}\x{387}\x{3f6}\x{482}-\x{489}\x{559}-\x{55f}\x{589}-\x{5c7}\x{5f3}-' .
+'\x{61f}\x{640}\x{64b}-\x{65e}\x{66a}-\x{66d}\x{670}\x{6d4}\x{6d6}-\x{6ed}' .
+'\x{6fd}\x{6fe}\x{700}-\x{70f}\x{711}\x{730}-\x{74a}\x{7a6}-\x{7b0}\x{901}-' .
+'\x{903}\x{93c}\x{93e}-\x{94d}\x{951}-\x{954}\x{962}-\x{965}\x{970}\x{981}-' .
+'\x{983}\x{9bc}\x{9be}-\x{9cd}\x{9d7}\x{9e2}\x{9e3}\x{9f2}-\x{a03}\x{a3c}-' .
+'\x{a4d}\x{a70}\x{a71}\x{a81}-\x{a83}\x{abc}\x{abe}-\x{acd}\x{ae2}\x{ae3}' .
+'\x{af1}-\x{b03}\x{b3c}\x{b3e}-\x{b57}\x{b70}\x{b82}\x{bbe}-\x{bd7}\x{bf0}-' .
+'\x{c03}\x{c3e}-\x{c56}\x{c82}\x{c83}\x{cbc}\x{cbe}-\x{cd6}\x{d02}\x{d03}' .
+'\x{d3e}-\x{d57}\x{d82}\x{d83}\x{dca}-\x{df4}\x{e31}\x{e34}-\x{e3f}\x{e46}-' .
+'\x{e4f}\x{e5a}\x{e5b}\x{eb1}\x{eb4}-\x{ebc}\x{ec6}-\x{ecd}\x{f01}-\x{f1f}' .
+'\x{f2a}-\x{f3f}\x{f71}-\x{f87}\x{f90}-\x{fd1}\x{102c}-\x{1039}\x{104a}-' .
+'\x{104f}\x{1056}-\x{1059}\x{10fb}\x{10fc}\x{135f}-\x{137c}\x{1390}-\x{1399}' .
+'\x{166d}\x{166e}\x{1680}\x{169b}\x{169c}\x{16eb}-\x{16f0}\x{1712}-\x{1714}' .
+'\x{1732}-\x{1736}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17db}\x{17dd}' .
+'\x{17f0}-\x{180e}\x{1843}\x{18a9}\x{1920}-\x{1945}\x{19b0}-\x{19c0}\x{19c8}' .
+'\x{19c9}\x{19de}-\x{19ff}\x{1a17}-\x{1a1f}\x{1d2c}-\x{1d61}\x{1d78}\x{1d9b}-' .
+'\x{1dc3}\x{1fbd}\x{1fbf}-\x{1fc1}\x{1fcd}-\x{1fcf}\x{1fdd}-\x{1fdf}\x{1fed}-' .
+'\x{1fef}\x{1ffd}-\x{2070}\x{2074}-\x{207e}\x{2080}-\x{2101}\x{2103}-\x{2106}' .
+'\x{2108}\x{2109}\x{2114}\x{2116}-\x{2118}\x{211e}-\x{2123}\x{2125}\x{2127}' .
+'\x{2129}\x{212e}\x{2132}\x{213a}\x{213b}\x{2140}-\x{2144}\x{214a}-\x{2b13}' .
+'\x{2ce5}-\x{2cff}\x{2d6f}\x{2e00}-\x{3005}\x{3007}-\x{303b}\x{303d}-\x{303f}' .
+'\x{3099}-\x{309e}\x{30a0}\x{30fb}-\x{30fe}\x{3190}-\x{319f}\x{31c0}-\x{31cf}' .
+'\x{3200}-\x{33ff}\x{4dc0}-\x{4dff}\x{a015}\x{a490}-\x{a716}\x{a802}\x{a806}' .
+'\x{a80b}\x{a823}-\x{a82b}\x{e000}-\x{f8ff}\x{fb1e}\x{fb29}\x{fd3e}\x{fd3f}' .
+'\x{fdfc}-\x{fe6b}\x{feff}-\x{ff0f}\x{ff1a}-\x{ff20}\x{ff3b}-\x{ff40}\x{ff5b}-' .
+'\x{ff65}\x{ff70}\x{ff9e}\x{ff9f}\x{ffe0}-\x{fffd}');
+
+/**
+ * Clean up a string value provided by a module.
+ *
+ * Resulting string contains only alphanumerics and separators.
+ *
+ * @param $string
+ * A string to clean.
+ * @param $settings
+ * An optional array of settings to use.
+ * - 'clean slash': If set, slashes will be cleaned. Defaults to TRUE,
+ * so you have to explicitly set this to FALSE to not clean the
+ * slashes.
+ * - 'ignore words': Set to an array of words that will be removed
+ * rather than made safe. Defaults to an empty array.
+ * - 'separator': Change spaces and untranslatable characters to
+ * this character. Defaults to '-' .
+ * - 'replacements': An array of direct replacements to be made that will
+ * be implemented via strtr(). Defaults to an empty array.
+ * - 'transliterate': If set, use the transliteration replacements. If set
+ * to an array, use these replacements instead of the defaults in CTools.
+ * Defaults to FALSE.
+ * - 'reduce ascii': If set to TRUE further reduce to ASCII96 only. Defaults
+ * to TRUE.
+ * - 'max length': If set to a number, reduce the resulting string to this
+ * maximum length. Defaults to no maximum length.
+ * - 'lower case': If set to TRUE, convert the result to lower case.
+ * Defaults to false.
+ * These settings will be passed through drupal_alter.
+ *
+ * @return
+ * The cleaned string.
+ */
+function ctools_cleanstring($string, $settings = array()) {
+ $settings += array(
+ 'clean slash' => TRUE,
+ 'ignore words' => array(),
+ 'separator' => '-',
+ 'replacements' => array(),
+ 'transliterate' => FALSE,
+ 'reduce ascii' => TRUE,
+ 'max length' => FALSE,
+ 'lower case' => FALSE,
+ );
+
+ // Allow modules to make other changes to the settings.
+ if (isset($settings['clean id'])) {
+ drupal_alter('ctools_cleanstring_' . $settings['clean id'], $settings);
+ }
+
+ drupal_alter('ctools_cleanstring', $settings);
+
+ $output = $string;
+
+ // Do any replacements the user selected up front.
+ if (!empty($settings['replacements'])) {
+ $output = strtr($output, $settings['replacements']);
+ }
+
+ // Remove slashes if instructed to do so.
+ if ($settings['clean slash']) {
+ $output = str_replace('/', '', $output);
+ }
+
+ if (!empty($settings['transliterate']) && module_exists('transliteration')) {
+ $output = transliteration_get($output);
+ }
+
+ // Reduce to the subset of ASCII96 letters and numbers
+ if ($settings['reduce ascii']) {
+ $pattern = '/[^a-zA-Z0-9\/]+/';
+ $output = preg_replace($pattern, $settings['separator'], $output);
+ }
+
+ // Get rid of words that are on the ignore list
+ if (!empty($settings['ignore words'])) {
+ $ignore_re = '\b' . preg_replace('/,/', '\b|\b', $settings['ignore words']) . '\b';
+
+ if (function_exists('mb_eregi_replace')) {
+ $output = mb_eregi_replace($ignore_re, '', $output);
+ }
+ else {
+ $output = preg_replace("/$ignore_re/i", '', $output);
+ }
+ }
+
+ // Always replace whitespace with the separator.
+ $output = preg_replace('/\s+/', $settings['separator'], $output);
+
+ // In preparation for pattern matching,
+ // escape the separator if and only if it is not alphanumeric.
+ if (isset($settings['separator'])) {
+ if (preg_match('/^[^' . CTOOLS_PREG_CLASS_ALNUM . ']+$/uD', $settings['separator'])) {
+ $seppattern = $settings['separator'];
+ }
+ else {
+ $seppattern = '\\' . $settings['separator'];
+ }
+ // Trim any leading or trailing separators (note the need to
+ $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output);
+
+ // Replace multiple separators with a single one
+ $output = preg_replace("/$seppattern+/", $settings['separator'], $output);
+ }
+
+ // Enforce the maximum component length
+ if (!empty($settings['max length'])) {
+ $output = ctools_cleanstring_truncate($output, $settings['max length'], $settings['separator']);
+ }
+
+ if (!empty($settings['lower case'])) {
+ $output = drupal_strtolower($output);
+ }
+ return $output;
+}
+
+/**
+ * A friendly version of truncate_utf8.
+ *
+ * @param $string
+ * The string to be truncated.
+ * @param $length
+ * An integer for the maximum desired length.
+ * @param $separator
+ * A string which contains the word boundary such as - or _.
+ *
+ * @return
+ * The string truncated below the maxlength.
+ */
+function ctools_cleanstring_truncate($string, $length, $separator) {
+ if (drupal_strlen($string) > $length) {
+ $string = drupal_substr($string, 0, $length + 1); // leave one more character
+ if ($last_break = strrpos($string, $separator)) { // space exists AND is not on position 0
+ $string = substr($string, 0, $last_break);
+ }
+ else {
+ $string = drupal_substr($string, 0, $length);
+ }
+ }
+ return $string;
+}
diff --git a/sites/all/modules/ctools/includes/collapsible.theme.inc b/sites/all/modules/ctools/includes/collapsible.theme.inc
new file mode 100644
index 000000000..f7bbbb376
--- /dev/null
+++ b/sites/all/modules/ctools/includes/collapsible.theme.inc
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Theme function for the collapsible div tool.
+ *
+ * Call theme('ctools_collapsible', $handle, $content, $collapsed) to draw the
+ * div. The theme function is not necessary; you can add the classes, js and css
+ * yourself if you really want to.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
+ */
+function ctools_collapsible_theme(&$items) {
+ $items['ctools_collapsible'] = array(
+ 'variables' => array('handle' => NULL, 'content' => NULL, 'collapsed' => FALSE),
+ 'file' => 'includes/collapsible.theme.inc',
+ );
+ $items['ctools_collapsible_remembered'] = array(
+ 'variables' => array('id' => NULL, 'handle' => NULL, 'content' => NULL, 'collapsed' => FALSE),
+ 'file' => 'includes/collapsible.theme.inc',
+ );
+}
+
+/**
+ * Render a collapsible div.
+ *
+ * @param $handle
+ * Text to put in the handle/title area of the div.
+ * @param $content
+ * Text to put in the content area of the div, this is what will get
+ * collapsed
+ * @param $collapsed = FALSE
+ * If true, this div will start out collapsed.
+ */
+function theme_ctools_collapsible($vars) {
+ ctools_add_js('collapsible-div');
+ ctools_add_css('collapsible-div');
+
+ $class = $vars['collapsed'] ? ' ctools-collapsed' : '';
+ $output = '<div class="ctools-collapsible-container' . $class . '">';
+ $output .= '<div class="ctools-collapsible-handle">' . $vars['handle'] . '</div>';
+ $output .= '<div class="ctools-collapsible-content">' . $vars['content'] . '</div>';
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Render a collapsible div whose state will be remembered.
+ *
+ * @param $id
+ * The CSS id of the container. This is required.
+ * @param $handle
+ * Text to put in the handle/title area of the div.
+ * @param $content
+ * Text to put in the content area of the div, this is what will get
+ * collapsed
+ * @param $collapsed = FALSE
+ * If true, this div will start out collapsed.
+ */
+function theme_ctools_collapsible_remembered($vars) {
+ $id = $vars['id'];
+ $handle = $vars['handle'];
+ $content = $vars['content'];
+ $collapsed = $vars['collapsed'];
+ ctools_add_js('collapsible-div');
+ ctools_add_css('collapsible-div');
+
+ $class = $collapsed ? ' ctools-collapsed' : '';
+ $output = '<div id="' . $id . '" class="ctools-collapsible-remember ctools-collapsible-container' . $class . '">';
+ $output .= '<div class="ctools-collapsible-handle">' . $handle . '</div>';
+ $output .= '<div class="ctools-collapsible-content">' . $content . '</div>';
+ $output .= '</div>';
+
+ return $output;
+}
+
diff --git a/sites/all/modules/ctools/includes/content.inc b/sites/all/modules/ctools/includes/content.inc
new file mode 100644
index 000000000..ae1c6073d
--- /dev/null
+++ b/sites/all/modules/ctools/includes/content.inc
@@ -0,0 +1,853 @@
+<?php
+
+/**
+ * @file
+ * Contains the tools to handle pluggable content that can be used by other
+ * applications such as Panels or Dashboard.
+ *
+ * See the context-content.html file in advanced help for documentation
+ * of this tool.
+ */
+
+/**
+ * Provide defaults for a content type.
+ *
+ * Currently we check for automatically named callbacks to make life a little
+ * easier on the developer.
+ */
+function ctools_content_process(&$plugin, $info) {
+ $function_base = $plugin['module'] . '_' . $plugin['name'] . '_content_type_';
+
+ if (empty($plugin['render callback']) && function_exists($function_base . 'render')) {
+ $plugin['render callback'] = $function_base . 'render';
+ }
+
+ if (empty($plugin['admin title'])) {
+ if (function_exists($function_base . 'admin_title')) {
+ $plugin['admin title'] = $function_base . 'admin_title';
+ }
+ else {
+ $plugin['admin title'] = $plugin['title'];
+ }
+ }
+
+ if (empty($plugin['admin info']) && function_exists($function_base . 'admin_info')) {
+ $plugin['admin info'] = $function_base . 'admin_info';
+ }
+
+ if (!isset($plugin['edit form']) && function_exists($function_base . 'edit_form')) {
+ $plugin['edit form'] = $function_base . 'edit_form';
+ }
+
+ if (!isset($plugin['add form']) && function_exists($function_base . 'add_form')) {
+ $plugin['add form'] = $function_base . 'add_form';
+ }
+
+ if (!isset($plugin['add form']) && function_exists($function_base . 'edit_form')) {
+ $plugin['add form'] = $function_base . 'edit_form';
+ }
+
+ if (!isset($plugin['description'])) {
+ $plugin['description'] = '';
+ }
+
+ if (!isset($plugin['icon'])) {
+ $plugin['icon'] = ctools_content_admin_icon($plugin);
+ }
+
+ // Another ease of use check:
+ if (!isset($plugin['content types'])) {
+ // If a subtype plugin exists, try to use it. Otherwise assume single.
+ if (function_exists($function_base . 'content_types')) {
+ $plugin['content types'] = $function_base . 'content_types';
+ }
+ else {
+ $type = array(
+ 'title' => $plugin['title'],
+ 'description' => $plugin['description'],
+ 'icon' => ctools_content_admin_icon($plugin),
+ 'category' => $plugin['category'],
+ );
+
+ if (isset($plugin['required context'])) {
+ $type['required context'] = $plugin['required context'];
+ }
+ if (isset($plugin['top level'])) {
+ $type['top level'] = $plugin['top level'];
+ }
+ $plugin['content types'] = array($plugin['name'] => $type);
+ if (!isset($plugin['single'])) {
+ $plugin['single'] = TRUE;
+ }
+ }
+ }
+}
+
+/**
+ * Fetch metadata on a specific content_type plugin.
+ *
+ * @param $content type
+ * Name of a panel content type.
+ *
+ * @return
+ * An array with information about the requested panel content type.
+ */
+function ctools_get_content_type($content_type) {
+ ctools_include('context');
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'content_types', $content_type);
+}
+
+/**
+ * Fetch metadata for all content_type plugins.
+ *
+ * @return
+ * An array of arrays with information about all available panel content types.
+ */
+function ctools_get_content_types() {
+ ctools_include('context');
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'content_types');
+}
+
+/**
+ * Get all of the individual subtypes provided by a given content type. This
+ * would be all of the blocks for the block type, or all of the views for
+ * the view type.
+ *
+ * @param $type
+ * The content type to load.
+ *
+ * @return
+ * An array of all subtypes available.
+ */
+function ctools_content_get_subtypes($type) {
+ static $cache = array();
+
+ $subtypes = array();
+
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else {
+ $plugin = ctools_get_content_type($type);
+ }
+
+ if (empty($plugin) || empty($plugin['name'])) {
+ return;
+ }
+
+ if (isset($cache[$plugin['name']])) {
+ return $cache[$plugin['name']];
+ }
+
+ if (isset($plugin['content types'])) {
+ $function = $plugin['content types'];
+ if (is_array($function)) {
+ $subtypes = $function;
+ }
+ else if (function_exists($function)) {
+ // Cast to array to prevent errors from non-array returns.
+ $subtypes = (array) $function($plugin);
+ }
+ }
+
+ // Walk through the subtypes and ensure minimal settings are
+ // retained.
+ foreach ($subtypes as $id => $subtype) {
+ // Use exact name since this is a modify by reference.
+ ctools_content_prepare_subtype($subtypes[$id], $plugin);
+ }
+
+ $cache[$plugin['name']] = $subtypes;
+
+ return $subtypes;
+}
+
+/**
+ * Given a content type and a subtype id, return the information about that
+ * content subtype.
+ *
+ * @param $type
+ * The content type being fetched.
+ * @param $subtype_id
+ * The id of the subtype being fetched.
+ *
+ * @return
+ * An array of information describing the content subtype.
+ */
+function ctools_content_get_subtype($type, $subtype_id) {
+ $subtype = array();
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else {
+ $plugin = ctools_get_content_type($type);
+ }
+
+ $function = ctools_plugin_get_function($plugin, 'content type');
+ if ($function) {
+ $subtype = $function($subtype_id, $plugin);
+ }
+ else {
+ $subtypes = ctools_content_get_subtypes($type);
+ if (isset($subtypes[$subtype_id])) {
+ $subtype = $subtypes[$subtype_id];
+ }
+ // If there's only 1 and we somehow have the wrong subtype ID, do not
+ // care. Return the proper subtype anyway.
+ if (empty($subtype) && !empty($plugin['single'])) {
+ $subtype = current($subtypes);
+ }
+ }
+
+ if ($subtype) {
+ ctools_content_prepare_subtype($subtype, $plugin);
+ }
+ return $subtype;
+}
+
+/**
+ * Ensure minimal required settings on a content subtype exist.
+ */
+function ctools_content_prepare_subtype(&$subtype, $plugin) {
+ foreach (array('path', 'js', 'css') as $key) {
+ if (!isset($subtype[$key]) && isset($plugin[$key])) {
+ $subtype[$key] = $plugin[$key];
+ }
+ }
+
+ drupal_alter('ctools_content_subtype', $subtype, $plugin);
+}
+
+/**
+ * Get the content from a given content type.
+ *
+ * @param $type
+ * The content type. May be the name or an already loaded content type plugin.
+ * @param $subtype
+ * The name of the subtype being rendered.
+ * @param $conf
+ * The configuration for the content type.
+ * @param $keywords
+ * An array of replacement keywords that come from outside contexts.
+ * @param $args
+ * The arguments provided to the owner of the content type. Some content may
+ * wish to configure itself based on the arguments the panel or dashboard
+ * received.
+ * @param $context
+ * An array of context objects available for use.
+ * @param $incoming_content
+ * Any incoming content, if this display is a wrapper.
+ *
+ * @return
+ * The content as rendered by the plugin. This content should be an array
+ * with the following possible keys:
+ * - title: The safe to render title of the content.
+ * - title_heading: The title heading.
+ * - content: The safe to render HTML content.
+ * - links: An array of links associated with the content suitable for
+ * theme('links').
+ * - more: An optional 'more' link (destination only)
+ * - admin_links: Administrative links associated with the content, suitable
+ * for theme('links').
+ * - feeds: An array of feed icons or links associated with the content.
+ * Each member of the array is rendered HTML.
+ * - type: The content type.
+ * - subtype: The content subtype. These two may be used together as
+ * module-delta for block style rendering.
+ */
+function ctools_content_render($type, $subtype, $conf, $keywords = array(), $args = array(), $context = array(), $incoming_content = '') {
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else {
+ $plugin = ctools_get_content_type($type);
+ }
+
+ $subtype_info = ctools_content_get_subtype($plugin, $subtype);
+
+ $function = ctools_plugin_get_function($subtype_info, 'render callback');
+ if (!$function) {
+ $function = ctools_plugin_get_function($plugin, 'render callback');
+ }
+
+ if ($function) {
+ $pane_context = ctools_content_select_context($plugin, $subtype, $conf, $context);
+ if ($pane_context === FALSE) {
+ return;
+ }
+
+ $content = $function($subtype, $conf, $args, $pane_context, $incoming_content);
+
+ if (empty($content)) {
+ return;
+ }
+
+ // Set up some defaults and other massaging on the content before we hand
+ // it back to the caller.
+ if (!isset($content->type)) {
+ $content->type = $plugin['name'];
+ }
+
+ if (!isset($content->subtype)) {
+ $content->subtype = $subtype;
+ }
+
+ // Override the title if configured to
+ if (!empty($conf['override_title'])) {
+ // Give previous title as an available substitution here.
+ $keywords['%title'] = empty($content->title) ? '' : $content->title;
+ $content->original_title = $keywords['%title'];
+ $content->title = $conf['override_title_text'];
+ $content->title_heading = isset($conf['override_title_heading']) ? $conf['override_title_heading'] : 'h2';
+ }
+
+ if (!empty($content->title)) {
+ // Perform substitutions
+ if (!empty($keywords) || !empty($context)) {
+ $content->title = ctools_context_keyword_substitute($content->title, $keywords, $context);
+ }
+
+ // Sterilize the title
+ $content->title = filter_xss_admin($content->title);
+
+ // If a link is specified, populate.
+ if (!empty($content->title_link)) {
+ if (!is_array($content->title_link)) {
+ $url = array('href' => $content->title_link);
+ }
+ else {
+ $url = $content->title_link;
+ }
+ // set defaults so we don't bring up notices
+ $url += array('href' => '', 'attributes' => array(), 'query' => array(), 'fragment' => '', 'absolute' => NULL, 'html' => TRUE);
+ $content->title = l($content->title, $url['href'], $url);
+ }
+ }
+
+ return $content;
+ }
+}
+
+/**
+ * Determine if a content type can be edited or not.
+ *
+ * Some content types simply have their content and no options. This function
+ * lets a UI determine if it should display an edit link or not.
+ */
+function ctools_content_editable($type, $subtype, $conf) {
+ if (empty($type['edit form']) && empty($subtype['edit form'])) {
+ return FALSE;
+ }
+
+ $function = FALSE;
+
+ if (!empty($subtype['check editable'])) {
+ $function = ctools_plugin_get_function($subtype, 'check editable');
+ }
+ elseif (!empty($type['check editable'])) {
+ $function = ctools_plugin_get_function($type, 'check editable');
+ }
+
+ if ($function) {
+ return $function($type, $subtype, $conf);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Get the administrative title from a given content type.
+ *
+ * @param $type
+ * The content type. May be the name or an already loaded content type object.
+ * @param $subtype
+ * The subtype being rendered.
+ * @param $conf
+ * The configuration for the content type.
+ * @param $context
+ * An array of context objects available for use. These may be placeholders.
+ */
+function ctools_content_admin_title($type, $subtype, $conf, $context = NULL) {
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else if (is_string($type)) {
+ $plugin = ctools_get_content_type($type);
+ }
+ else {
+ return;
+ }
+
+ if ($function = ctools_plugin_get_function($plugin, 'admin title')) {
+ $pane_context = ctools_content_select_context($plugin, $subtype, $conf, $context);
+ if ($pane_context === FALSE) {
+ if ($plugin['name'] == $subtype) {
+ return t('@type will not display due to missing context', array('@type' => $plugin['name']));
+ }
+ return t('@type:@subtype will not display due to missing context', array('@type' => $plugin['name'], '@subtype' => $subtype));
+ }
+
+ return $function($subtype, $conf, $pane_context);
+ }
+ else if (isset($plugin['admin title'])) {
+ return $plugin['admin title'];
+ }
+ else if (isset($plugin['title'])) {
+ return $plugin['title'];
+ }
+}
+
+/**
+ * Get the proper icon path to use, falling back to default icons if no icon exists.
+ *
+ * $subtype
+ * The loaded subtype info.
+ */
+function ctools_content_admin_icon($subtype) {
+ $icon = '';
+
+ if (isset($subtype['icon'])) {
+ $icon = $subtype['icon'];
+ if (!file_exists($icon)) {
+ $icon = $subtype['path'] . '/' . $icon;
+ }
+ }
+
+ if (empty($icon) || !file_exists($icon)) {
+ $icon = ctools_image_path('no-icon.png');
+ }
+
+ return $icon;
+}
+
+/**
+ * Set up the default $conf for a new instance of a content type.
+ */
+function ctools_content_get_defaults($plugin, $subtype) {
+ if (isset($plugin['defaults'])) {
+ $defaults = $plugin['defaults'];
+ }
+ else if (isset($subtype['defaults'])) {
+ $defaults = $subtype['defaults'];
+ }
+ if (isset($defaults)) {
+ if (is_string($defaults) && function_exists($defaults)) {
+ if ($return = $defaults($pane)) {
+ return $return;
+ }
+ }
+ else if (is_array($defaults)) {
+ return $defaults;
+ }
+ }
+
+ return array();
+}
+
+/**
+ * Get the administrative title from a given content type.
+ *
+ * @param $type
+ * The content type. May be the name or an already loaded content type object.
+ * @param $subtype
+ * The subtype being rendered.
+ * @param $conf
+ * The configuration for the content type.
+ * @param $context
+ * An array of context objects available for use. These may be placeholders.
+ */
+function ctools_content_admin_info($type, $subtype, $conf, $context = NULL) {
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else {
+ $plugin = ctools_get_content_type($type);
+ }
+
+ if ($function = ctools_plugin_get_function($plugin, 'admin info')) {
+ $output = $function($subtype, $conf, $context);
+ }
+
+ if (empty($output) || !is_object($output)) {
+ $output = new stdClass();
+ // replace the _ with " " for a better output
+ $subtype = check_plain(str_replace("_", " ", $subtype));
+ $output->title = $subtype;
+ $output->content = t('No info available.');
+ }
+ return $output;
+}
+
+/**
+ * Add the default FAPI elements to the content type configuration form
+ */
+function ctools_content_configure_form_defaults($form, &$form_state) {
+ $plugin = $form_state['plugin'];
+ $subtype = $form_state['subtype'];
+ $contexts = isset($form_state['contexts']) ? $form_state['contexts'] : NULL;
+ $conf = $form_state['conf'];
+
+ $add_submit = FALSE;
+ if (!empty($subtype['required context']) && is_array($contexts)) {
+ $form['context'] = ctools_context_selector($contexts, $subtype['required context'], isset($conf['context']) ? $conf['context'] : array());
+ $add_submit = TRUE;
+ }
+
+ ctools_include('dependent');
+
+ // Unless we're not allowed to override the title on this content type, add this
+ // gadget to all panes.
+ if (empty($plugin['no title override']) && empty($subtype['no title override'])) {
+ $form['aligner_start'] = array(
+ '#markup' => '<div class="option-text-aligner clearfix">',
+ );
+ $form['override_title'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => isset($conf['override_title']) ? $conf['override_title'] : '',
+ '#title' => t('Override title'),
+ '#id' => 'override-title-checkbox',
+ );
+ $form['override_title_text'] = array(
+ '#type' => 'textfield',
+ '#default_value' => isset($conf['override_title_text']) ? $conf['override_title_text'] : '',
+ '#size' => 35,
+ '#id' => 'override-title-textfield',
+ '#dependency' => array('override-title-checkbox' => array(1)),
+ '#dependency_type' => 'hidden',
+ );
+ $form['override_title_heading'] = array(
+ '#type' => 'select',
+ '#default_value' => isset($conf['override_title_heading']) ? $conf['override_title_heading'] : 'h2',
+ '#options' => array(
+ 'h1' => t('h1'),
+ 'h2' => t('h2'),
+ 'h3' => t('h3'),
+ 'h4' => t('h4'),
+ 'h5' => t('h5'),
+ 'h6' => t('h6'),
+ 'div' => t('div'),
+ 'span' => t('span'),
+ ),
+ '#id' => 'override-title-heading',
+ '#dependency' => array('override-title-checkbox' => array(1)),
+ '#dependency_type' => 'hidden',
+ );
+
+ $form['aligner_stop'] = array(
+ '#markup' => '</div>',
+ );
+ if (is_array($contexts)) {
+ $form['override_title_markup'] = array(
+ '#prefix' => '<div class="description">',
+ '#suffix' => '</div>',
+ '#markup' => t('You may use %keywords from contexts, as well as %title to contain the original title.'),
+ );
+ }
+ $add_submit = TRUE;
+ }
+
+ if ($add_submit) {
+ // '#submit' is already set up due to the wizard.
+ $form['#submit'][] = 'ctools_content_configure_form_defaults_submit';
+ }
+ return $form;
+}
+
+/**
+ * Submit handler to store context/title override info.
+ */
+function ctools_content_configure_form_defaults_submit(&$form, &$form_state) {
+ if (isset($form_state['values']['context'])) {
+ $form_state['conf']['context'] = $form_state['values']['context'];
+ }
+ if (isset($form_state['values']['override_title'])) {
+ $form_state['conf']['override_title'] = $form_state['values']['override_title'];
+ $form_state['conf']['override_title_text'] = $form_state['values']['override_title_text'];
+ $form_state['conf']['override_title_heading'] = $form_state['values']['override_title_heading'];
+ }
+}
+
+/**
+ * Get the config form.
+ *
+ * The $form_info and $form_state need to be preconfigured with data you'll need
+ * such as whether or not you're using ajax, or the modal. $form_info will need
+ * your next/submit callbacks so that you can cache your data appropriately.
+ *
+ * @return
+ * If this function returns false, no form exists.
+ */
+function ctools_content_form($op, $form_info, &$form_state, $plugin, $subtype_name, $subtype, &$conf, $step = NULL) {
+ $form_state += array(
+ 'plugin' => $plugin,
+ 'subtype' => $subtype,
+ 'subtype_name' => $subtype_name,
+ 'conf' => &$conf,
+ 'op' => $op,
+ );
+
+ $form_info += array(
+ 'id' => 'ctools_content_form',
+ 'show back' => TRUE,
+ );
+
+ // Turn the forms defined in the plugin into the format the wizard needs.
+ if ($op == 'add') {
+ if (!empty($subtype['add form'])) {
+ _ctools_content_create_form_info($form_info, $subtype['add form'], $subtype, $subtype, $op);
+ }
+ else if (!empty($plugin['add form'])) {
+ _ctools_content_create_form_info($form_info, $plugin['add form'], $plugin, $subtype, $op);
+ }
+ }
+
+ if (empty($form_info['order'])) {
+ // Use the edit form for the add form if add form was completely left off.
+ if (!empty($subtype['edit form'])) {
+ _ctools_content_create_form_info($form_info, $subtype['edit form'], $subtype, $subtype, $op);
+ }
+ else if (!empty($plugin['edit form'])) {
+ _ctools_content_create_form_info($form_info, $plugin['edit form'], $plugin, $subtype, $op);
+ }
+ }
+
+ if (empty($form_info['order'])) {
+ return FALSE;
+ }
+
+ ctools_include('wizard');
+ return ctools_wizard_multistep_form($form_info, $step, $form_state);
+
+}
+
+function _ctools_content_create_form_info(&$form_info, $info, $plugin, $subtype, $op) {
+ if (is_string($info)) {
+ if (empty($subtype['title'])) {
+ $title = t('Configure');
+ }
+ else if ($op == 'add') {
+ $title = t('Configure new !subtype_title', array('!subtype_title' => $subtype['title']));
+ }
+ else {
+ $title = t('Configure !subtype_title', array('!subtype_title' => $subtype['title']));
+ }
+ $form_info['order'] = array('form' => $title);
+ $form_info['forms'] = array(
+ 'form' => array(
+ 'title' => $title,
+ 'form id' => $info,
+ 'wrapper' => 'ctools_content_configure_form_defaults',
+ ),
+ );
+ }
+ else if (is_array($info)) {
+ $form_info['order'] = array();
+ $form_info['forms'] = array();
+ $count = 0;
+ $base = 'step';
+ $wrapper = NULL;
+ foreach ($info as $form_id => $title) {
+ // @todo -- docs say %title can be used to sub for the admin title.
+ $step = $base . ++$count;
+ if (empty($wrapper)) {
+ $wrapper = $step;
+ }
+
+ if (is_array($title)) {
+ if (!empty($title['default'])) {
+ $wrapper = $step;
+ }
+ $title = $title['title'];
+ }
+
+ $form_info['order'][$step] = $title;
+ $form_info['forms'][$step] = array(
+ 'title' => $title,
+ 'form id' => $form_id,
+ );
+ }
+ if ($wrapper) {
+ $form_info['forms'][$wrapper]['wrapper'] = 'ctools_content_configure_form_defaults';
+ }
+ }
+}
+
+/**
+ * Get an array of all available content types that can be fed into the
+ * display editor for the add content list.
+ *
+ * @param $context
+ * If a context is provided, content that requires that context can apepar.
+ * @param $has_content
+ * Whether or not the display will have incoming content
+ * @param $allowed_types
+ * An array of allowed content types (pane types) keyed by content_type . '-' . sub_type
+ * @param $default_types
+ * A default allowed/denied status for content that isn't known about
+ */
+function ctools_content_get_available_types($contexts = NULL, $has_content = FALSE, $allowed_types = NULL, $default_types = NULL) {
+ $plugins = ctools_get_content_types();
+ $available = array();
+
+ foreach ($plugins as $id => $plugin) {
+ foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) {
+ // exclude items that require content if we're saying we don't
+ // provide it.
+ if (!empty($subtype['requires content']) && !$has_content) {
+ continue;
+ }
+
+ // Check to see if the content type can be used in this context.
+ if (!empty($subtype['required context'])) {
+ if (!ctools_context_match_requirements($contexts, $subtype['required context'])) {
+ continue;
+ }
+ }
+
+ // Check to see if the passed-in allowed types allows this content.
+ if ($allowed_types) {
+ $key = $id . '-' . $subtype_id;
+ if (!isset($allowed_types[$key])) {
+ $allowed_types[$key] = isset($default_types[$id]) ? $default_types[$id] : $default_types['other'];
+ }
+ if (!$allowed_types[$key]) {
+ continue;
+ }
+ }
+
+ // Check if the content type provides an access callback.
+ if (isset($subtype['create content access']) && function_exists($subtype['create content access']) && !$subtype['create content access']($plugin, $subtype)) {
+ continue;
+ }
+
+ // If we made it through all the tests, then we can use this content.
+ $available[$id][$subtype_id] = $subtype;
+ }
+ }
+ return $available;
+}
+
+/**
+ * Get an array of all content types that can be fed into the
+ * display editor for the add content list, regardless of
+ * availability.
+ *
+ */
+function ctools_content_get_all_types() {
+ $plugins = ctools_get_content_types();
+ $available = array();
+
+ foreach ($plugins as $id => $plugin) {
+ foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) {
+ // If we made it through all the tests, then we can use this content.
+ $available[$id][$subtype_id] = $subtype;
+ }
+ }
+ return $available;
+}
+
+/**
+ * Select the context to be used for a piece of content, based upon config.
+ *
+ * @param $plugin
+ * The content plugin
+ * @param $subtype
+ * The subtype of the content.
+ * @param $conf
+ * The configuration array that should contain the context.
+ * @param $contexts
+ * A keyed array of available contexts.
+ *
+ * @return
+ * The matching contexts or NULL if none or necessary, or FALSE if
+ * requirements can't be met.
+ */
+function ctools_content_select_context($plugin, $subtype, $conf, $contexts) {
+ // Identify which of our possible contexts apply.
+ if (empty($subtype)) {
+ return;
+ }
+
+ $subtype_info = ctools_content_get_subtype($plugin, $subtype);
+ if (empty($subtype_info)) {
+ return;
+ }
+
+ if (!empty($subtype_info['all contexts']) || !empty($plugin['all contexts'])) {
+ return $contexts;
+ }
+
+ // If the content requires a context, fetch it; if no context is returned,
+ // do not display the pane.
+ if (empty($subtype_info['required context'])) {
+ return;
+ }
+
+ // Deal with dynamic required contexts not getting updated in the panes.
+ // For example, Views let you dynamically change context info. While
+ // we cannot be perfect, one thing we can do is if no context at all
+ // was asked for, and then was later added but none is selected, make
+ // a best guess as to what context should be used. THis is right more
+ // than it's wrong.
+ if (is_array($subtype_info['required context'])) {
+ if (empty($conf['context']) || count($subtype_info['required context']) != count($conf['context'])) {
+ foreach ($subtype_info['required context'] as $index => $required) {
+ if (!isset($conf['context'][$index])) {
+ $filtered = ctools_context_filter($contexts, $required);
+ if ($filtered) {
+ $keys = array_keys($filtered);
+ $conf['context'][$index] = array_shift($keys);
+ }
+ }
+ }
+ }
+ }
+ else {
+ if (empty($conf['context'])) {
+ $filtered = ctools_context_filter($contexts, $subtype_info['required context']);
+ if ($filtered) {
+ $keys = array_keys($filtered);
+ $conf['context'] = array_shift($keys);
+ }
+ }
+ }
+
+ if (empty($conf['context'])) {
+ return;
+ }
+
+ $context = ctools_context_select($contexts, $subtype_info['required context'], $conf['context']);
+
+ return $context;
+}
+
+/**
+ * Fetch a piece of content from the addressable content system.
+ *
+ * @param $address
+ * A string or an array representing the address of the content.
+ * @param $type
+ * The type of content to return. The type is dependent on what
+ * the content actually is. The default, 'content' means a simple
+ * string representing the content. However, richer systems may
+ * offer more options. For example, Panels might allow the
+ * fetching of 'display' and 'pane' objects. Page Manager
+ * might allow for the fetching of 'task_handler' objects
+ * (AKA variants).
+ */
+function ctools_get_addressable_content($address, $type = 'content') {
+ if (!is_array($address)) {
+ $address = explode('::', $address);
+ }
+
+ if (!$address) {
+ return;
+ }
+
+ // This removes the module from the address so the
+ // implementor is not responsible for that part.
+ $module = array_shift($address);
+ return module_invoke($module, 'addressable_content', $address, $type);
+}
diff --git a/sites/all/modules/ctools/includes/content.menu.inc b/sites/all/modules/ctools/includes/content.menu.inc
new file mode 100644
index 000000000..f7f934067
--- /dev/null
+++ b/sites/all/modules/ctools/includes/content.menu.inc
@@ -0,0 +1,179 @@
+<?php
+
+/**
+ * @file
+ * Contains menu item registration for the content tool.
+ *
+ * The menu items registered are AJAX callbacks for the things like
+ * autocomplete and other tools needed by the content types.
+ */
+
+function ctools_content_menu(&$items) {
+ $base = array(
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/content.menu.inc',
+ );
+ $items['ctools/autocomplete/%'] = array(
+ 'page callback' => 'ctools_content_autocomplete_entity',
+ 'page arguments' => array(2),
+ ) + $base;
+}
+
+/**
+ * Helper function for autocompletion of entity titles.
+ */
+function ctools_content_autocomplete_entity($entity_type, $string = '') {
+ if ($string != '') {
+ $entity_info = entity_get_info($entity_type);
+
+ if (!module_exists('entity')) {
+ module_load_include('inc', 'ctools', 'includes/entity-access');
+ _ctools_entity_access($entity_info, $entity_type);
+ }
+
+ // We must query all ids, because if every one of the 10 don't have access
+ // the user may never be able to autocomplete a node title.
+ $preg_matches = array();
+ $matches = array();
+ $match = preg_match('/\[id: (\d+)\]/', $string, $preg_matches);
+ if (!$match) {
+ $match = preg_match('/^id: (\d+)/', $string, $preg_matches);
+ }
+ // If an ID match was found, use that ID rather than the whole string.
+ if ($match) {
+ $entity_id = $preg_matches[1];
+ $results = _ctools_getReferencableEntities($entity_type, $entity_info, $entity_id, '=', 1);
+ }
+ else {
+ // We cannot find results if the entity doesn't have a label to search.
+ if (!isset($entity_info['entity keys']['label'])) {
+ drupal_json_output(array("[id: NULL]" => '<span class="autocomplete_title">' . t('Entity Type !entity_type does not support autocomplete search.', array('!entity_type' => $entity_type)) . '</span>'));
+ return;
+ }
+ $results = _ctools_getReferencableEntities($entity_type, $entity_info, $string, 'LIKE', 10);
+ }
+ foreach ($results as $entity_id => $result) {
+ $matches[$result['label'] . " [id: $entity_id]"] = '<span class="autocomplete_title">' . check_plain($result['label']) . '</span>';
+ $matches[$result['label'] . " [id: $entity_id]"] .= isset($result['bundle']) ? ' <span class="autocomplete_bundle">(' . check_plain($result['bundle']) . ')</span>' : '';
+ }
+
+ drupal_json_output($matches);
+ }
+}
+
+/*
+ * Use well known/tested entity reference code to build our search query
+ * From EntityReference_SelectionHandler_Generic class
+ */
+function _ctools_buildQuery($entity_type, $entity_info, $match = NULL, $match_operator = 'CONTAINS') {
+ $base_table = $entity_info['base table'];
+ $label_key = $entity_info['entity keys']['label'];
+ $query = db_select($base_table)
+ ->fields($base_table, array($entity_info['entity keys']['id']));
+
+ if (isset($match)) {
+ if (isset($label_key)) {
+ $query->condition($base_table . '.' . $label_key, '%' . $match . '%', $match_operator);
+ }
+ // This should never happen, but double check just in case.
+ else {
+ return array();
+ }
+ }
+ // Add a generic entity access tag to the query.
+ $query->addTag('ctools');
+
+ // We have to perform two checks. First check is a query alter (with tags)
+ // in an attempt to only return results that have access. However, this is
+ // not full-proof since entities many not implement hook_access query tag.
+ // This is why we have a second check after entity load, before we display
+ // the label of an entity.
+ if ($entity_type == 'comment') {
+ // Adding the 'comment_access' tag is sadly insufficient for comments: core

+ // requires us to also know about the concept of 'published' and

+ // 'unpublished'.

+ if (!user_access('administer comments')) {
+ $query->condition('comment.status', COMMENT_PUBLISHED);
+ }
+
+ // Join to a node if the user does not have node access bypass permissions

+ // to obey node published permissions

+ if (!user_access('bypass node access')) {
+ $node_alias = $query->innerJoin('node', 'n', '%alias.nid = comment.nid');
+ $query->condition($node_alias . '.status', NODE_PUBLISHED);
+ }
+ $query->addTag('node_access');
+ }
+ else {
+ $query->addTag($entity_type . '_access');
+ }
+
+ // Add the sort option.
+ if (isset($label_key)) {
+ $query->orderBy($base_table . '.' . $label_key, 'ASC');
+ }
+
+ return $query;
+}
+
+/**
+ * Private function to get referencable entities. Based on code from the
+ * Entity Reference module.
+ */
+function _ctools_getReferencableEntities($entity_type, $entity_info, $match = NULL, $match_operator = 'LIKE', $limit = 0) {
+ global $user;
+ $account = $user;
+ $options = array();
+ // We're an entity ID, return the id
+ if (is_numeric($match) && $match_operator == '=') {
+ if ($entity = array_shift(entity_load($entity_type, array($match)))) {
+ if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) {
+ if ($entity_info['access callback']('view', $entity, $account, $entity_type)) {
+ $label = entity_label($entity_type, $entity);
+ return array(
+ $match => array(
+ 'label' => !empty($label) ? $label : $entity->{$entity_info['entity keys']['id']},
+ 'bundle' => !empty($entity_info['entity keys']['bundle']) ? check_plain($entity->{$entity_info['entity keys']['bundle']}) : NULL,
+ ),
+ );
+ }
+ }
+ }
+ // If you don't have access, or an access callback or a valid entity, just
+ // Return back the Entity ID.
+ return array(
+ $match => array(
+ 'label' => $match,
+ 'bundle' => NULL,
+ ),
+ );
+ }
+
+ // We have matches, build a query to fetch the result.
+ if ($query = _ctools_buildQuery($entity_type, $entity_info, $match, $match_operator)) {
+ if ($limit > 0) {
+ $query->range(0, $limit);
+ }
+
+ $results = $query->execute();
+
+ if (!empty($results)) {
+ foreach ($results as $record) {
+ $entities = entity_load($entity_type, array($record->{$entity_info['entity keys']['id']}));
+ $entity = array_shift($entities);
+ if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) {
+ if ($entity_info['access callback']('view', $entity, $account, $entity_type)) {
+ $label = entity_label($entity_type, $entity);
+ $options[$record->{$entity_info['entity keys']['id']}] = array(
+ 'label' => !empty($label) ? $label : $entity->{$entity_info['entity keys']['id']},
+ 'bundle' => !empty($entity_info['entity keys']['bundle']) ? check_plain($entity->{$entity_info['entity keys']['bundle']}) : NULL,
+ );
+ }
+ }
+ }
+ }
+ return $options;
+ }
+ return array();
+}
diff --git a/sites/all/modules/ctools/includes/content.plugin-type.inc b/sites/all/modules/ctools/includes/content.plugin-type.inc
new file mode 100644
index 000000000..a0debc3e5
--- /dev/null
+++ b/sites/all/modules/ctools/includes/content.plugin-type.inc
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains plugin type registration information for the content tool.
+ */
+
+function ctools_content_plugin_type(&$items) {
+ $items['content_types'] = array(
+ 'cache' => FALSE,
+ 'process' => array(
+ 'function' => 'ctools_content_process',
+ 'file' => 'content.inc',
+ 'path' => drupal_get_path('module', 'ctools') . '/includes',
+ ),
+ );
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/includes/content.theme.inc b/sites/all/modules/ctools/includes/content.theme.inc
new file mode 100644
index 000000000..ae4456aa7
--- /dev/null
+++ b/sites/all/modules/ctools/includes/content.theme.inc
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Contains theme registry and theme implementations for the content types.
+ */
+
+/**
+ * Implements hook_theme to load all content plugins and pass thru if
+ * necessary.
+ */
+function ctools_content_theme(&$theme) {
+ ctools_include('content');
+
+ $plugins = ctools_get_content_types();
+ foreach ($plugins as $plugin) {
+ if ($function = ctools_plugin_get_function($plugin, 'hook theme')) {
+ $function($theme, $plugin);
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/context-access-admin.inc b/sites/all/modules/ctools/includes/context-access-admin.inc
new file mode 100644
index 000000000..76643cf62
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context-access-admin.inc
@@ -0,0 +1,486 @@
+<?php
+
+/**
+ * @file
+ * Contains administrative screens for the access control plugins.
+ *
+ * Access control can be implemented by creating a list of 0 or more access
+ * plugins, each with settings. This list can be ANDed together or ORed
+ * together. When testing access, each plugin is tested until success
+ * or failure can be determined. We use short circuiting techniques to
+ * ensure we are as efficient as possible.
+ *
+ * Access plugins are part of the context system, and as such can require
+ * contexts to work. That allows the use of access based upon visibility
+ * of an object, or even more esoteric things such as node type, node language
+ * etc. Since a lot of access depends on the logged in user, the logged in
+ * user should always be provided as a context.
+ *
+ * In the UI, the user is presented with a table and a 'add access method' select.
+ * When added, the user will be presented with the config wizard and, when
+ * confirmed, table will be refreshed via AJAX to show the new access method.
+ * Each item in the table will have controls to change the settings or remove
+ * the item. Changing the settings will invoke the modal for update.
+ *
+ * Currently the modal is not degradable, but it could be with only a small
+ * amount of work.
+ *
+ * A simple radio
+ * control is used to let the user pick the and/or logic.
+ *
+ * Access control is stored in an array:
+ * @code
+ * array(
+ * 'plugins' => array(
+ * 0 => array(
+ * 'name' => 'name of access plugin',
+ * 'settings' => array(), // These will be set by the form
+ * ),
+ * // ... as many as needed
+ * ),
+ * 'logic' => 'AND', // or 'OR',
+ * ),
+ * @endcode
+ *
+ * To add this widget to your UI, you need to do a little bit of setup.
+ *
+ * The form will utilize two callbacks, one to get the cached version
+ * of the access settings, and one to store the cached version of the
+ * access settings. These will be used from AJAX forms, so they will
+ * be completely out of the context of this page load and will not have
+ * knowledge of anything sent to this form (the 'module' and 'argument'
+ * will be preserved through the URL only).
+ *
+ * The 'module' is used to determine the location of the callback. It
+ * does not strictly need to be a module, so that if your module defines
+ * multiple systems that use this callback, it can use anything within the
+ * module's namespace it likes.
+ *
+ * When retrieving the cache, the cache may not have already been set up;
+ * In order to efficiently use cache space, we want to cache the stored
+ * settings *only* when they have changed. Therefore, the get access cache
+ * callback should first look for cache, and if it finds nothing, return
+ * the original settings.
+ *
+ * The callbacks:
+ * - $module . _ctools_access_get($argument) -- get the 'access' settings
+ * from cache. Must return array($access, $contexts); This callback can
+ * perform access checking to make sure this URL is not being gamed.
+ * - $module . _ctools_access_set($argument, $access) -- set the 'access'
+ * settings in cache.
+ * - $module . _ctools_access_clear($argument) -- clear the cache.
+ *
+ * The ctools_object_cache is recommended for this purpose, but you can use
+ * any caching mechanism you like. An example:
+ *
+ * @code{
+ * ctools_include('object-cache');
+ * ctools_object_cache_set("$module:argument", $access);
+ * }
+ *
+ * To utilize this form:
+ * @code
+ * ctools_include('context-access-admin');
+ * $form_state = array(
+ * 'access' => $access,
+ * 'module' => 'module name',
+ * 'callback argument' => 'some string',
+ * 'contexts' => $contexts, // an array of contexts. Optional if no contexts.
+ * // 'logged-in-user' will be added if not present as the access system
+ * // requires this context.
+ * ),
+ * $output = drupal_build_form('ctools_access_admin_form', $form_state);
+ * if (!empty($form_state['executed'])) {
+ * // save $form_state['access'] however you like.
+ * }
+ * @endcode
+ *
+ * Additionally, you may add 'no buttons' => TRUE if you wish to embed this
+ * form into your own, and instead call
+ *
+ * @code{
+ * $form = ctools_access_admin_form($form, $form_state);
+ * }
+ *
+ * You'll be responsible for adding a submit button.
+ *
+ * You may use ctools_access($access, $contexts) which will return
+ * TRUE if access is passed or FALSE if access is not passed.
+ */
+
+/**
+ * Administrative form for access control.
+ */
+function ctools_access_admin_form($form, &$form_state) {
+ ctools_include('context');
+ $argument = isset($form_state['callback argument']) ? $form_state['callback argument'] : '';
+ $fragment = $form_state['module'];
+ if ($argument) {
+ $fragment .= '-' . $argument;
+ }
+
+ $contexts = isset($form_state['contexts']) ? $form_state['contexts'] : array();
+
+ $form['access_table'] = array(
+ '#markup' => ctools_access_admin_render_table($form_state['access'], $fragment, $contexts),
+ );
+
+ $form['add-button'] = array(
+ '#theme' => 'ctools_access_admin_add',
+ );
+ // This sets up the URL for the add access modal.
+ $form['add-button']['add-url'] = array(
+ '#attributes' => array('class' => array("ctools-access-add-url")),
+ '#type' => 'hidden',
+ '#value' => url("ctools/context/ajax/access/add/$fragment", array('absolute' => TRUE)),
+ );
+
+ $plugins = ctools_get_relevant_access_plugins($contexts);
+ $options = array();
+ foreach ($plugins as $id => $plugin) {
+ $options[$id] = $plugin['title'];
+ }
+
+ asort($options);
+
+ $form['add-button']['type'] = array(
+ // This ensures that the form item is added to the URL.
+ '#attributes' => array('class' => array("ctools-access-add-url")),
+ '#type' => 'select',
+ '#options' => $options,
+ '#required' => FALSE,
+ );
+
+ $form['add-button']['add'] = array(
+ '#type' => 'submit',
+ '#attributes' => array('class' => array('ctools-use-modal')),
+ '#id' => "ctools-access-add",
+ '#value' => t('Add'),
+ );
+
+ $form['logic'] = array(
+ '#type' => 'radios',
+ '#options' => array(
+ 'and' => t('All criteria must pass.'),
+ 'or' => t('Only one criteria must pass.'),
+ ),
+ '#default_value' => isset($form_state['access']['logic']) ? $form_state['access']['logic'] : 'and',
+ );
+
+ if (empty($form_state['no buttons'])) {
+ $form['buttons']['save'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#submit' => array('ctools_access_admin_form_submit'),
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Render the table. This is used both to render it initially and to rerender
+ * it upon ajax response.
+ */
+function ctools_access_admin_render_table($access, $fragment, $contexts) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ $rows = array();
+
+ if (empty($access['plugins'])) {
+ $access['plugins'] = array();
+ }
+
+ foreach ($access['plugins'] as $id => $test) {
+ $row = array();
+ $plugin = ctools_get_access_plugin($test['name']);
+ $title = isset($plugin['title']) ? $plugin['title'] : t('Broken/missing access plugin %plugin', array('%plugin' => $test['name']));
+
+ $row[] = array('data' => $title, 'class' => array('ctools-access-title'));
+
+ $description = ctools_access_summary($plugin, $contexts, $test);
+ $row[] = array('data' => $description, 'class' => array('ctools-access-description'));
+
+ $operations = ctools_modal_image_button(ctools_image_path('icon-configure.png'), "ctools/context/ajax/access/configure/$fragment/$id", t('Configure settings for this item.'));
+ $operations .= ctools_ajax_image_button(ctools_image_path('icon-delete.png'), "ctools/context/ajax/access/delete/$fragment/$id", t('Remove this item.'));
+
+ $row[] = array('data' => $operations, 'class' => array('ctools-access-operations'), 'align' => 'right');
+
+ $rows[] = $row;
+ }
+
+ $header = array(
+ array('data' => t('Title'), 'class' => array('ctools-access-title')),
+ array('data' => t('Description'), 'class' => array('ctools-access-description')),
+ array('data' => '', 'class' => array('ctools-access-operations'), 'align' => 'right'),
+ );
+
+ if (empty($rows)) {
+ $rows[] = array(array('data' => t('No criteria selected, this test will pass.'), 'colspan' => count($header)));
+ }
+
+ ctools_modal_add_js();
+ return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'ctools-access-table')));
+}
+
+/**
+ * Theme the 'add' portion of the access form into a table.
+ */
+function theme_ctools_access_admin_add($vars) {
+ $rows = array(array(drupal_render_children($vars['form'])));
+ $output = '<div class="container-inline">';
+ $output .= theme('table', array('rows' => $rows));
+ $output .= '</div>';
+ return $output;
+}
+
+function ctools_access_admin_form_submit($form, &$form_state) {
+ $form_state['access']['logic'] = $form_state['values']['logic'];
+
+ $function = $form_state['module'] . '_ctools_access_clear';
+ if (function_exists($function)) {
+ $function($form_state['callback argument']);
+ }
+}
+
+// --------------------------------------------------------------------------
+// AJAX menu entry points.
+
+/**
+ * AJAX callback to add a new access test to the list.
+ */
+function ctools_access_ajax_add($fragment = NULL, $name = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+
+ if (empty($fragment) || empty($name)) {
+ ctools_ajax_render_error();
+ }
+
+ $plugin = ctools_get_access_plugin($name);
+ if (empty($plugin)) {
+ ctools_ajax_render_error();
+ }
+
+ // Separate the fragment into 'module' and 'argument'
+ if (strpos($fragment, '-') === FALSE) {
+ $module = $fragment;
+ $argument = NULL;
+ }
+ else {
+ list($module, $argument) = explode('-', $fragment, 2);
+ }
+
+ $function = $module . '_ctools_access_get';
+ if (!function_exists($function)) {
+ ctools_ajax_render_error(t('Missing callback hooks.'));
+ }
+
+ list($access, $contexts) = $function($argument);
+
+ // Make sure we have the logged in user context
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ if (empty($access['plugins'])) {
+ $access['plugins'] = array();
+ }
+
+ $test = ctools_access_new_test($plugin);
+
+ $id = $access['plugins'] ? max(array_keys($access['plugins'])) + 1 : 0;
+ $access['plugins'][$id] = $test;
+
+ $form_state = array(
+ 'plugin' => $plugin,
+ 'id' => $id,
+ 'test' => &$access['plugins'][$id],
+ 'access' => &$access,
+ 'contexts' => $contexts,
+ 'title' => t('Add criteria'),
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ );
+
+ $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state);
+ if (!isset($output[0])) {
+ $function = $module . '_ctools_access_set';
+ if (function_exists($function)) {
+ $function($argument, $access);
+ }
+
+ $table = ctools_access_admin_render_table($access, $fragment, $contexts);
+ $output = array();
+ $output[] = ajax_command_replace('table#ctools-access-table', $table);
+ $output[] = ctools_modal_command_dismiss();
+ }
+
+ print ajax_render($output);
+}
+
+/**
+ * AJAX callback to edit an access test in the list.
+ */
+function ctools_access_ajax_edit($fragment = NULL, $id = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+
+ if (empty($fragment) || !isset($id)) {
+ ctools_ajax_render_error();
+ }
+
+ // Separate the fragment into 'module' and 'argument'
+ if (strpos($fragment, '-') === FALSE) {
+ $module = $fragment;
+ $argument = NULL;
+ }
+ else {
+ list($module, $argument) = explode('-', $fragment, 2);
+ }
+
+ $function = $module . '_ctools_access_get';
+ if (!function_exists($function)) {
+ ctools_ajax_render_error(t('Missing callback hooks.'));
+ }
+
+ list($access, $contexts) = $function($argument);
+
+ if (empty($access['plugins'][$id])) {
+ ctools_ajax_render_error();
+ }
+
+ // Make sure we have the logged in user context
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ $plugin = ctools_get_access_plugin($access['plugins'][$id]['name']);
+ $form_state = array(
+ 'plugin' => $plugin,
+ 'id' => $id,
+ 'test' => &$access['plugins'][$id],
+ 'access' => &$access,
+ 'contexts' => $contexts,
+ 'title' => t('Edit criteria'),
+ 'ajax' => TRUE,
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ );
+
+ $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state);
+ if (!isset($output[0])) {
+ $function = $module . '_ctools_access_set';
+ if (function_exists($function)) {
+ $function($argument, $access);
+ }
+
+ $table = ctools_access_admin_render_table($access, $fragment, $contexts);
+ $output = array();
+ $output[] = ajax_command_replace('table#ctools-access-table', $table);
+ $output[] = ctools_modal_command_dismiss();
+ }
+
+ print ajax_render($output);
+}
+
+/**
+ * Form to edit the settings of an access test.
+ */
+function ctools_access_ajax_edit_item($form, &$form_state) {
+ $test = &$form_state['test'];
+ $plugin = &$form_state['plugin'];
+ if (isset($plugin['required context'])) {
+ $form['context'] = ctools_context_selector($form_state['contexts'], $plugin['required context'], $test['context']);
+ }
+ $form['settings'] = array('#tree' => TRUE);
+ if ($function = ctools_plugin_get_function($plugin, 'settings form')) {
+ $form = $function($form, $form_state, $test['settings']);
+ }
+
+ $form['not'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Reverse (NOT)'),
+ '#default_value' => !empty($test['not']),
+ );
+
+ $form['save'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate handler for argument settings.
+ */
+function ctools_access_ajax_edit_item_validate($form, &$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form validate')) {
+ $function($form, $form_state);
+ }
+}
+
+/**
+ * Submit handler for argument settings.
+ */
+function ctools_access_ajax_edit_item_submit($form, &$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form submit')) {
+ $function($form, $form_state);
+ }
+
+ $form_state['test']['settings'] = $form_state['values']['settings'];
+ if (isset($form_state['values']['context'])) {
+ $form_state['test']['context'] = $form_state['values']['context'];
+ }
+ $form_state['test']['not'] = !empty($form_state['values']['not']);
+}
+
+/**
+ * AJAX command to remove an access control item.
+ */
+function ctools_access_ajax_delete($fragment = NULL, $id = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+
+ if (empty($fragment) || !isset($id)) {
+ ajax_render_error();
+ }
+
+ // Separate the fragment into 'module' and 'argument'
+ if (strpos($fragment, '-') === FALSE) {
+ $module = $fragment;
+ $argument = NULL;
+ }
+ else {
+ list($module, $argument) = explode('-', $fragment, 2);
+ }
+
+ $function = $module . '_ctools_access_get';
+ if (!function_exists($function)) {
+ ajax_render_error(t('Missing callback hooks.'));
+ }
+
+ list($access, $contexts) = $function($argument);
+
+ if (isset($access['plugins'][$id])) {
+ unset($access['plugins'][$id]);
+ }
+
+ // re-cache
+ $function = $module . '_ctools_access_set';
+ if (function_exists($function)) {
+ $function($argument, $access);
+ }
+
+ $table = ctools_access_admin_render_table($access, $fragment, $contexts);
+ $output = array();
+ $output[] = ajax_command_replace('table#ctools-access-table', $table);
+
+ print ajax_render($output);
+}
diff --git a/sites/all/modules/ctools/includes/context-admin.inc b/sites/all/modules/ctools/includes/context-admin.inc
new file mode 100644
index 000000000..821a5b32a
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context-admin.inc
@@ -0,0 +1,849 @@
+<?php
+
+/**
+ * @file includes/common-context.inc
+ * Provide API for adding contexts for modules that embed displays.
+ *
+ * Note that most of this code was directly copied from Panels 2, and as such
+ * a lot of this code is crusty. It could probably stand to be rewritten,
+ * and brought up to date, or at least better commented.
+ */
+
+/**
+ * Provide a list of the ways contexts can be embedded.
+ *
+ * This provides a full list of context types that the tool understands
+ * and can let modules utilize.
+ */
+function ctools_context_info($type = NULL) {
+ static $info = NULL;
+
+ // static doesn't work with functions like t().
+ if (empty($info)) {
+ $info = array(
+ 'argument' => array(
+ 'title' => t('Arguments'),
+ 'singular title' => t('argument'),
+ 'description' => '', // t("Arguments are parsed from the URL and translated into contexts that may be added to the display via the 'content' tab. These arguments are parsed in the order received, and you may use % in your URL to hold the place of an object; the rest of the arguments will come after the URL. For example, if the URL is node/%/panel and your user visits node/1/panel/foo, the first argument will be 1, and the second argument will be foo."),
+ 'add button' => t('Add argument'),
+ 'context function' => 'ctools_get_argument',
+ 'key' => 'arguments', // the key that data will be stored on an object, eg $panel_page
+ 'sortable' => TRUE,
+ 'settings' => 'argument_settings',
+ ),
+ 'relationship' => array(
+ 'title' => t('Relationships'),
+ 'singular title' => t('relationship'),
+ 'description' => '', // t('Relationships are contexts that are created from already existing contexts; the add relationship button will only appear once there is another context available. Relationships can load objects based upon how they are related to each other; for example, the author of a node, or a taxonomy term attached to a node, or the vocabulary of a taxonomy term.'),
+ 'add button' => t('Add relationship'),
+ 'context function' => 'ctools_get_relationship',
+ 'key' => 'relationships',
+ 'sortable' => FALSE,
+ 'settings' => 'relationship_settings',
+ ),
+ 'context' => array(
+ 'title' => t('Contexts'),
+ 'singular title' => t('context'),
+ 'description' => '', // t('Contexts are embedded directly into the panel; you generally must select an object in the panel. For example, you could select node 5, or the term "animals" or the user "administrator"'),
+ 'add button' => t('Add context'),
+ 'context function' => 'ctools_get_context',
+ 'key' => 'contexts',
+ 'sortable' => FALSE,
+ 'settings' => 'context_settings',
+ ),
+ 'requiredcontext' => array(
+ 'title' => t('Required contexts'),
+ 'singular title' => t('required context'),
+ 'description' => '', // t('Required contexts are passed in from some external source, such as a containing panel. If a mini panel has required contexts, it can only appear when that context is available, and therefore will not show up as a standard Drupal block.'),
+ 'add button' => t('Add required context'),
+ 'context function' => 'ctools_get_context',
+ 'key' => 'requiredcontexts',
+ 'sortable' => FALSE,
+ ),
+ );
+ }
+
+ if ($type === NULL) {
+ return $info;
+ }
+
+ return $info[$type];
+}
+
+
+/**
+ * Get the data belonging to a particular context.
+ */
+function ctools_context_get_plugin($type, $name) {
+ $info = ctools_context_info($type);
+ if (function_exists($info['context function'])) {
+ return $info['context function']($name);
+ }
+}
+
+/**
+ * Add the argument table plus gadget plus javascript to the form.
+ */
+function ctools_context_add_argument_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) {
+ if (empty($cache_key)) {
+ $cache_key = $object->name;
+ }
+
+ $form_location = array(
+ '#prefix' => '<div id="ctools-arguments-table">',
+ '#suffix' => '</div>',
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'argument',
+ '#ctools_context_module' => $module,
+ );
+
+ $args = ctools_get_arguments();
+ $choices = array();
+ foreach ($args as $name => $arg) {
+ if (empty($arg['no ui'])) {
+ $choices[$name] = $arg['title'];
+ }
+ }
+
+ asort($choices);
+
+ if (!empty($choices) || !empty($object->arguments)) {
+ ctools_context_add_item_table('argument', $form_location, $choices, $object->arguments);
+ }
+}
+
+function ctools_context_add_context_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) {
+ if (empty($cache_key)) {
+ $cache_key = $object->name;
+ }
+
+ $form_location = array(
+ '#prefix' => '<div id="ctools-contexts-table">',
+ '#suffix' => '</div>',
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'context',
+ '#ctools_context_module' => $module,
+ );
+
+ // Store the order the choices are in so javascript can manipulate it.
+ $form_location['markup'] = array(
+ '#markup' => '&nbsp;',
+ );
+
+ $choices = array();
+ foreach (ctools_get_contexts() as $name => $arg) {
+ if (empty($arg['no ui'])) {
+ $choices[$name] = $arg['title'];
+ }
+ }
+
+ asort($choices);
+
+ if (!empty($choices) || !empty($object->contexts)) {
+ ctools_context_add_item_table('context', $form_location, $choices, $object->contexts);
+ }
+
+}
+
+function ctools_context_add_required_context_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) {
+ if (empty($cache_key)) {
+ $cache_key = $object->name;
+ }
+
+ $form_location = array(
+ '#prefix' => '<div id="ctools-requiredcontexts-table">',
+ '#suffix' => '</div>',
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'requiredcontext',
+ '#ctools_context_module' => $module,
+ );
+
+ // Store the order the choices are in so javascript can manipulate it.
+ $form_location['markup'] = array(
+ '#value' => '&nbsp;',
+ );
+
+ $choices = array();
+ foreach (ctools_get_contexts() as $name => $arg) {
+ if (empty($arg['no required context ui'])) {
+ $choices[$name] = $arg['title'];
+ }
+ }
+
+ asort($choices);
+
+ if (!empty($choices) || !empty($object->contexts)) {
+ ctools_context_add_item_table('requiredcontext', $form_location, $choices, $object->requiredcontexts);
+ }
+}
+
+function ctools_context_add_relationship_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) {
+ if (empty($cache_key)) {
+ $cache_key = $object->name;
+ }
+
+ $form_location = array(
+ '#prefix' => '<div id="ctools-relationships-table">',
+ '#suffix' => '</div>',
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'relationship',
+ '#ctools_context_module' => $module,
+ );
+
+ // Store the order the choices are in so javascript can manipulate it.
+ $form_location['markup'] = array(
+ '#value' => '&nbsp;',
+ );
+
+ $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array();
+ $available_relationships = ctools_context_get_relevant_relationships(ctools_context_load_contexts($object, TRUE, $base_contexts));
+
+ ctools_context_add_item_table('relationship', $form_location, $available_relationships, $object->relationships);
+}
+
+/**
+ * Include all context administrative include files, css, javascript.
+ */
+function ctools_context_admin_includes() {
+ ctools_include('context');
+ ctools_include('modal');
+ ctools_include('ajax');
+ ctools_include('object-cache');
+ ctools_modal_add_js();
+ ctools_modal_add_plugin_js(ctools_get_contexts());
+ ctools_modal_add_plugin_js(ctools_get_relationships());
+}
+
+/**
+ * Add the context table to the page.
+ */
+function ctools_context_add_item_table($type, &$form, $available_contexts, $items) {
+ $form[$type] = array(
+ '#tree' => TRUE,
+ );
+
+ $module = $form['#ctools_context_module'];
+ $cache_key = $form['#cache_key'];
+
+ if (isset($items) && is_array($items)) {
+ foreach ($items as $position => $context) {
+ ctools_context_add_item_to_form($module, $type, $cache_key, $form[$type][$position], $position, $context);
+ }
+ }
+
+ $type_info = ctools_context_info($type);
+ $form['description'] = array(
+ '#prefix' => '<div class="description">',
+ '#suffix' => '</div>',
+ '#markup' => $type_info['description'],
+ );
+
+ ctools_context_add_item_table_buttons($type, $module, $form, $available_contexts);
+}
+
+function ctools_context_add_item_table_buttons($type, $module, &$form, $available_contexts) {
+ drupal_add_library('system', 'drupal.ajax');
+ $form['buttons'] = array(
+ '#tree' => TRUE,
+ );
+
+ if (!empty($available_contexts)) {
+ $type_info = ctools_context_info($type);
+
+ $module = $form['#ctools_context_module'];
+ $cache_key = $form['#cache_key'];
+
+ // The URL for this ajax button
+ $form['buttons'][$type]['add-url'] = array(
+ '#attributes' => array('class' => array("ctools-$type-add-url")),
+ '#type' => 'hidden',
+ '#value' => url("ctools/context/ajax/add/$module/$type/$cache_key", array('absolute' => TRUE)),
+ );
+
+ asort($available_contexts);
+ // This also will be in the URL.
+ $form['buttons'][$type]['item'] = array(
+ '#attributes' => array('class' => array("ctools-$type-add-url")),
+ '#type' => 'select',
+ '#options' => $available_contexts,
+ '#required' => FALSE,
+ );
+
+ $form['buttons'][$type]['add'] = array(
+ '#type' => 'submit',
+ '#attributes' => array('class' => array('ctools-use-modal')),
+ '#id' => "ctools-$type-add",
+ '#value' => $type_info['add button'],
+ );
+ }
+}
+
+/**
+ * Add a row to the form. Used both in the main form and by
+ * the ajax to add an item.
+ */
+function ctools_context_add_item_to_form($module, $type, $cache_key, &$form, $position, $item) {
+ // This is the single function way to load any plugin by variable type.
+ $info = ctools_context_get_plugin($type, $item['name']);
+ $form['title'] = array(
+ '#markup' => check_plain($item['identifier']),
+ );
+
+ // Relationships not sortable.
+ $type_info = ctools_context_info($type);
+
+ if (!empty($type_info['sortable'])) {
+ $form['position'] = array(
+ '#type' => 'weight',
+ '#default_value' => $position,
+ '#attributes' => array('class' => array('drag-position')),
+ );
+ }
+
+ $form['remove'] = array(
+ '#markup' => ctools_ajax_image_button(ctools_image_path('icon-delete.png'), "ctools/context/ajax/delete/$module/$type/$cache_key/$position", t('Remove this item.')),
+ );
+
+ $form['settings'] = array(
+ '#markup' => ctools_modal_image_button(ctools_image_path('icon-configure.png'), "ctools/context/ajax/configure/$module/$type/$cache_key/$position", t('Configure settings for this item.')),
+ );
+}
+
+
+// ---------------------------------------------------------------------------
+// AJAX forms and stuff.
+
+/**
+ * Ajax entry point to add an context
+ */
+function ctools_context_ajax_item_add($mechanism = NULL, $type = NULL, $cache_key = NULL, $name = NULL, $step = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+ ctools_include('cache');
+ ctools_include('plugins-admin');
+
+ if (!$name) {
+ return ctools_ajax_render_error();
+ }
+
+ // Load stored object from cache.
+ if (!($object = ctools_cache_get($mechanism, $cache_key))) {
+ ctools_ajax_render_error(t('Invalid object name.'));
+ }
+
+ // Get info about what we're adding, i.e, relationship, context, argument, etc.
+ $plugin_definition = ctools_context_get_plugin($type, $name);
+ if (empty($plugin_definition)) {
+ ctools_ajax_render_error(t('Invalid context type'));
+ }
+
+ // Set up the $conf array for this plugin
+ if (empty($step) || empty($object->temporary)) {
+ // Create the basis for our new context.
+ $conf = ctools_context_get_defaults($plugin_definition, $object, $type);
+ $object->temporary = &$conf;
+ }
+ else {
+ $conf = &$object->temporary;
+ }
+
+ // Load the contexts that may be used.
+ $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array();
+ $contexts = ctools_context_load_contexts($object, TRUE, $base_contexts);
+
+ $type_info = ctools_context_info($type);
+ $form_state = array(
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ 'object' => &$object,
+ 'conf' => &$conf,
+ 'plugin' => $plugin_definition,
+ 'type' => $type,
+ 'contexts' => $contexts,
+ 'title' => t('Add @type "@context"', array('@type' => $type_info['singular title'], '@context' => $plugin_definition['title'])),
+ 'type info' => $type_info,
+ 'op' => 'add',
+ 'step' => $step,
+ );
+
+ $form_info = array(
+ 'path' => "ctools/context/ajax/add/$mechanism/$type/$cache_key/$name/%step",
+ 'show cancel' => TRUE,
+ 'default form' => 'ctools_edit_context_form_defaults',
+ 'auto cache' => TRUE,
+ 'cache mechanism' => $mechanism,
+ 'cache key' => $cache_key,
+ // This is stating what the cache will be referred to in $form_state
+ 'cache location' => 'object',
+ );
+
+ if ($type == 'requiredcontext') {
+ $form_info += array(
+ 'add form name' => 'required context add form',
+ 'edit form name' => 'required context edit form',
+ );
+ }
+
+ $output = ctools_plugin_configure_form($form_info, $form_state);
+
+ if (!empty($form_state['cancel'])) {
+ $output = array(ctools_modal_command_dismiss());
+ }
+ else if (!empty($form_state['complete'])) {
+ // Successful submit -- move temporary data to location.
+
+ // Create a reference to the place our context lives. Since this is fairly
+ // generic, this is the easiest way to get right to the place of the
+ // object without knowing precisely what data we're poking at.
+ $ref = &$object->{$type_info['key']};
+
+ // Figure out the position for our new context.
+ $position = empty($ref) ? 0 : max(array_keys($ref)) + 1;
+
+ $conf['id'] = ctools_context_next_id($ref, $name);
+ $ref[$position] = $conf;
+
+ if (isset($object->temporary)) {
+ unset($object->temporary);
+ }
+
+ ctools_cache_operation($mechanism, $cache_key, 'finalize', $object);
+
+ // Very irritating way to update the form for our contexts.
+ $arg_form_state = form_state_defaults() + array(
+ 'values' => array(),
+ 'process_input' => FALSE,
+ 'complete form' => array(),
+ );
+
+ $rel_form_state = $arg_form_state;
+
+ $arg_form = array(
+ '#post' => array(),
+ '#programmed' => FALSE,
+ '#tree' => FALSE,
+ '#parents' => array(),
+ '#array_parents' => array(),
+ );
+
+ // Build a chunk of the form to merge into the displayed form
+ $arg_form[$type] = array(
+ '#tree' => TRUE,
+ );
+ $arg_form[$type][$position] = array(
+ '#tree' => TRUE,
+ );
+
+ ctools_context_add_item_to_form($mechanism, $type, $cache_key, $arg_form[$type][$position], $position, $ref[$position]);
+ $arg_form = form_builder('ctools_context_form', $arg_form, $arg_form_state);
+
+ // Build the relationships table so we can ajax it in.
+ // This is an additional thing that goes in here.
+ $rel_form = array(
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'relationship',
+ '#ctools_context_module' => $mechanism,
+ '#only_buttons' => TRUE,
+ '#post' => array(),
+ '#programmed' => FALSE,
+ '#tree' => FALSE,
+ '#parents' => array(),
+ '#array_parents' => array(),
+ );
+
+ $rel_form['relationship'] = array(
+ '#tree' => TRUE,
+ );
+
+ // Allow an object to set some 'base' contexts that come from elsewhere.
+ $rel_contexts = isset($object->base_contexts) ? $object->base_contexts : array();
+ $all_contexts = ctools_context_load_contexts($object, TRUE, $rel_contexts);
+ $available_relationships = ctools_context_get_relevant_relationships($all_contexts);
+
+ $output = array();
+ if (!empty($available_relationships)) {
+ ctools_context_add_item_table_buttons('relationship', $mechanism, $rel_form, $available_relationships);
+ $rel_form = form_builder('dummy_form_id', $rel_form, $rel_form_state);
+ $output[] = ajax_command_replace('div#ctools-relationships-table div.buttons', drupal_render($rel_form));
+ }
+
+ $theme_vars = array();
+ $theme_vars['type'] = $type;
+ $theme_vars['form'] = $arg_form[$type][$position];
+ $theme_vars['position'] = $position;
+ $theme_vars['count'] = $position;
+ $text = theme('ctools_context_item_row', $theme_vars);
+ $output[] = ajax_command_append('#' . $type . '-table tbody', $text);
+ $output[] = ajax_command_changed('#' . $type . '-row-' . $position, '.title');
+ $output[] = ctools_modal_command_dismiss();
+ }
+ else {
+ $output = ctools_modal_form_render($form_state, $output);
+ }
+ print ajax_render($output);
+ exit;
+}
+
+/**
+ * Ajax entry point to edit an item
+ */
+function ctools_context_ajax_item_edit($mechanism = NULL, $type = NULL, $cache_key = NULL, $position = NULL, $step = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+ ctools_include('cache');
+ ctools_include('plugins-admin');
+
+ if (!isset($position)) {
+ return ctools_ajax_render_error();
+ }
+
+ // Load stored object from cache.
+ if (!($object = ctools_cache_get($mechanism, $cache_key))) {
+ ctools_ajax_render_error(t('Invalid object name.'));
+ }
+
+ $type_info = ctools_context_info($type);
+
+ // Create a reference to the place our context lives. Since this is fairly
+ // generic, this is the easiest way to get right to the place of the
+ // object without knowing precisely what data we're poking at.
+ $ref = &$object->{$type_info['key']};
+
+ if (empty($step) || empty($object->temporary)) {
+ // Create the basis for our new context.
+ $conf = $object->{$type_info['key']}[$position];
+ $object->temporary = &$conf;
+ }
+ else {
+ $conf = &$object->temporary;
+ }
+
+ $name = $ref[$position]['name'];
+ if (empty($name)) {
+ ctools_ajax_render_error();
+ }
+
+ // load the plugin definition
+ $plugin_definition = ctools_context_get_plugin($type, $name);
+ if (empty($plugin_definition)) {
+ ctools_ajax_render_error(t('Invalid context type'));
+ }
+
+ // Load the contexts
+ $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array();
+ $contexts = ctools_context_load_contexts($object, TRUE, $base_contexts);
+
+ $form_state = array(
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ 'object' => &$object,
+ 'conf' => &$conf,
+ 'position' => $position,
+ 'plugin' => $plugin_definition,
+ 'type' => $type,
+ 'contexts' => $contexts,
+ 'title' => t('Edit @type "@context"', array('@type' => $type_info['singular title'], '@context' => $plugin_definition['title'])),
+ 'type info' => $type_info,
+ 'op' => 'add',
+ 'step' => $step,
+ );
+
+ $form_info = array(
+ 'path' => "ctools/context/ajax/configure/$mechanism/$type/$cache_key/$position/%step",
+ 'show cancel' => TRUE,
+ 'default form' => 'ctools_edit_context_form_defaults',
+ 'auto cache' => TRUE,
+ 'cache mechanism' => $mechanism,
+ 'cache key' => $cache_key,
+ // This is stating what the cache will be referred to in $form_state
+ 'cache location' => 'object',
+ );
+
+ if ($type == 'requiredcontext') {
+ $form_info += array(
+ 'add form name' => 'required context add form',
+ 'edit form name' => 'required context edit form',
+ );
+ }
+
+ $output = ctools_plugin_configure_form($form_info, $form_state);
+
+ if (!empty($form_state['cancel'])) {
+ $output = array(ctools_modal_command_dismiss());
+ }
+ else if (!empty($form_state['complete'])) {
+ // successful submit
+ $ref[$position] = $conf;
+ if (isset($object->temporary)) {
+ unset($object->temporary);
+ }
+
+ ctools_cache_operation($mechanism, $cache_key, 'finalize', $object);
+
+ $output = array();
+ $output[] = ctools_modal_command_dismiss();
+
+ $arg_form_state = form_state_defaults() + array(
+ 'values' => array(),
+ 'process_input' => FALSE,
+ 'complete form' => array(),
+ );
+
+ $arg_form = array(
+ '#post' => array(),
+ '#parents' => array(),
+ '#array_parents' => array(),
+ '#programmed' => FALSE,
+ '#tree' => FALSE,
+ );
+
+ // Build a chunk of the form to merge into the displayed form
+ $arg_form[$type] = array(
+ '#tree' => TRUE,
+ );
+ $arg_form[$type][$position] = array(
+ '#tree' => TRUE,
+ );
+
+ ctools_context_add_item_to_form($mechanism, $type, $cache_key, $arg_form[$type][$position], $position, $ref[$position]);
+ $arg_form = form_builder('ctools_context_form', $arg_form, $arg_form_state);
+
+ $theme_vars = array();
+ $theme_vars['type'] = $type;
+ $theme_vars['form'] = $arg_form[$type][$position];
+ $theme_vars['position'] = $position;
+ $theme_vars['count'] = $position;
+ $output[] = ajax_command_replace('#' . $type . '-row-' . $position, theme('ctools_context_item_row', $theme_vars));
+ $output[] = ajax_command_changed('#' . $type . '-row-' . $position, '.title');
+ }
+ else {
+ $output = ctools_modal_form_render($form_state, $output);
+ }
+ print ajax_render($output);
+ exit;
+}
+
+/**
+ * Get the defaults for a new instance of a context plugin.
+ *
+ * @param $plugin_definition
+ * The metadata definition of the plugin from ctools_get_plugins().
+ * @param $object
+ * The object the context plugin will be added to.
+ * @param $type
+ * The type of context plugin. i.e, context, requiredcontext, relationship
+ */
+function ctools_context_get_defaults($plugin_definition, $object, $type) {
+ // Fetch the potential id of the plugin so we can append
+ // title and keyword information for new ones.
+ $type_info = ctools_context_info($type);
+ $id = ctools_context_next_id($object->{$type_info['key']}, $plugin_definition['name']);
+
+ $conf = array(
+ 'identifier' => $plugin_definition['title'] . ($id > 1 ? ' ' . $id : ''),
+ 'keyword' => ctools_get_keyword($object, $plugin_definition['keyword']),
+ 'name' => $plugin_definition['name'],
+ );
+
+ if (isset($plugin_definition['defaults'])) {
+ $defaults = $plugin_definition['defaults'];
+ }
+ else if (isset($subtype['defaults'])) {
+ $defaults = $subtype['defaults'];
+ }
+
+ if (isset($defaults)) {
+ if (is_string($defaults) && function_exists($defaults)) {
+ if ($settings = $defaults($plugin_definition)) {
+ $conf += $settings;
+ }
+ }
+ else if (is_array($defaults)) {
+ $conf += $defaults;
+ }
+ }
+
+ return $conf;
+}
+
+/**
+ * Form wrapper for the edit context form.
+ *
+ * @todo: We should uncombine these.
+ */
+function ctools_edit_context_form_defaults($form, &$form_state) {
+ // Basic values required to orient ourselves
+ $object = $form_state['object'];
+ $plugin_definition = $form_state['plugin'];
+ $type_info = $form_state['type info'];
+ $contexts = $form_state['contexts'];
+ $conf = $form_state['conf'];
+
+ if ($type_info['key'] == 'arguments' && !isset($conf['default'])) {
+ $conf['default'] = 'ignore';
+ $conf['title'] = '';
+ }
+
+ $form['description'] = array(
+ '#prefix' => '<div class="description">',
+ '#suffix' => '</div>',
+ '#markup' => check_plain($plugin_definition['description']),
+ );
+
+ if ($type_info['key'] == 'relationships') {
+ $form['context'] = ctools_context_selector($contexts, $plugin_definition['required context'], isset($conf['context']) ? $conf['context'] : '');
+ }
+ if ($type_info['key'] == 'arguments') {
+ $form['default'] = array(
+ '#type' => 'select',
+ '#title' => t('Default'),
+ '#options' => array(
+ 'ignore' => t('Ignore it; content that requires this context will not be available.'),
+ '404' => t('Display page not found or display nothing at all.'),
+ ),
+ '#default_value' => $conf['default'],
+ '#description' => t('If the argument is missing or is not valid, select how this should behave.'),
+ );
+
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Title'),
+ '#default_value' => $conf['title'],
+ '#description' => t('Enter a title to use when this argument is present. You may use %KEYWORD substitution, where the keyword is specified below.'),
+ );
+ }
+
+ $form['identifier'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Identifier'),
+ '#description' => t('Enter a name to identify this !type on administrative screens.', array('!type' => t('context'))),
+ '#default_value' => $conf['identifier'],
+ );
+
+ $form['keyword'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Keyword'),
+ '#description' => t('Enter a keyword to use for substitution in titles.'),
+ '#default_value' => $conf['keyword'],
+ );
+
+ if ($type_info['key'] == 'requiredcontexts') {
+ $form['optional'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Context is optional'),
+ '#default_value' => !empty($form_state['conf']['optional']),
+ '#description' => t('This context need not be present for the component to function.'),
+ );
+ }
+
+ $form['#submit'][] = 'ctools_edit_context_form_defaults_submit';
+
+ return $form;
+}
+
+/**
+ * Submit handler to store context identifier and keyword info.
+ */
+function ctools_edit_context_form_defaults_submit(&$form, &$form_state) {
+ if ($form_state['type info']['key'] == 'relationships') {
+ $form_state['conf']['context'] = $form_state['values']['context'];
+ }
+ if ($form_state['type info']['key'] == 'arguments') {
+ $form_state['conf']['default'] = $form_state['values']['default'];
+ $form_state['conf']['title'] = $form_state['values']['title'];
+ }
+ if ($form_state['type info']['key'] == 'requiredcontexts') {
+ $form_state['conf']['optional'] = $form_state['values']['optional'];
+ }
+
+ $form_state['conf']['identifier'] = $form_state['values']['identifier'];
+ $form_state['conf']['keyword'] = $form_state['values']['keyword'];
+}
+
+/**
+ * Ajax entry point to edit an item
+ */
+function ctools_context_ajax_item_delete($mechanism = NULL, $type = NULL, $cache_key = NULL, $position = NULL) {
+ ctools_include('ajax');
+ ctools_include('context');
+ ctools_include('cache');
+
+ if (!isset($position)) {
+ return ctools_ajax_render_error();
+ }
+
+ // Load stored object from cache.
+ if (!($object = ctools_cache_get($mechanism, $cache_key))) {
+ ctools_ajax_render_error(t('Invalid object name.'));
+ }
+
+ $type_info = ctools_context_info($type);
+
+ // Create a reference to the place our context lives. Since this is fairly
+ // generic, this is the easiest way to get right to the place of the
+ // object without knowing precisely what data we're poking at.
+ $ref = &$object->{$type_info['key']};
+
+ if (!array_key_exists($position, $ref)) {
+ ctools_ajax_render_error(t('Unable to delete missing item!'));
+ }
+
+ unset($ref[$position]);
+ ctools_cache_operation($mechanism, $cache_key, 'finalize', $object);
+
+ $output = array();
+ $output[] = ajax_command_replace('#' . $type . '-row-' . $position, '');
+ $output[] = ajax_command_restripe("#$type-table");
+ print ajax_render($output);
+ exit;
+}
+
+// --- End of contexts
+
+function ctools_save_context($type, &$ref, $form_values) {
+ $type_info = ctools_context_info($type);
+
+ // Organize arguments
+ $new = array();
+ $order = array();
+
+ foreach ($ref as $id => $context) {
+ $position = $form_values[$type][$id]['position'];
+ $order[$position] = $id;
+ }
+
+ ksort($order);
+ foreach ($order as $id) {
+ $new[] = $ref[$id];
+ }
+ $ref = $new;
+}
+
+function ctools_get_keyword($page, $word) {
+ // Create a complete set of keywords
+ $keywords = array();
+ foreach (array('arguments', 'relationships', 'contexts', 'requiredcontexts') as $type) {
+ if (!empty($page->$type) && is_array($page->$type)) {
+ foreach ($page->$type as $info) {
+ $keywords[$info['keyword']] = TRUE;
+ }
+ }
+ }
+
+ $keyword = $word;
+ $count = 1;
+ while (!empty($keywords[$keyword])) {
+ $keyword = $word . '_' . ++$count;
+ }
+ return $keyword;
+}
+
diff --git a/sites/all/modules/ctools/includes/context-task-handler.inc b/sites/all/modules/ctools/includes/context-task-handler.inc
new file mode 100644
index 000000000..21ceea5dc
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context-task-handler.inc
@@ -0,0 +1,540 @@
+<?php
+
+/**
+ * @file
+ * Support for creating 'context' type task handlers.
+ *
+ * Context task handlers expect the task to provide 0 or more contexts. The
+ * task handler should use those contexts as selection rules, as well as
+ * rendering with them.
+ *
+ * The functions and forms in this file should be common to every context type
+ * task handler made.
+ *
+ * Forms:
+ * - ...
+ */
+
+/**
+ * Render a context type task handler given a list of handlers
+ * attached to a type.
+ *
+ * @param $task
+ * The $task object in use.
+ * @param $subtask
+ * The id of the subtask in use.
+ * @param $contexts
+ * The context objects in use.
+ * @param $args
+ * The raw arguments behind the contexts.
+ * @param $page
+ * If TRUE then this renderer owns the page and can use theme('page')
+ * for no blocks; if false, output is returned regardless of any no
+ * blocks settings.
+ * @return
+ * Either the output or NULL if there was output, FALSE if no handler
+ * accepted the task. If $page is FALSE then the $info block is returned instead.
+ */
+function ctools_context_handler_render($task, $subtask, $contexts, $args) {
+ // Load the landlers, choosing only enabled handlers.
+ $handlers = page_manager_load_sorted_handlers($task, $subtask ? $subtask['name'] : '', TRUE);
+
+ $id = ctools_context_handler_get_render_handler($task, $subtask, $handlers, $contexts, $args);
+ if ($id) {
+ return ctools_context_handler_render_handler($task, $subtask, $handlers[$id], $contexts, $args);
+ }
+
+ return FALSE;
+}
+
+/**
+ * Figure out which of the listed handlers should be used to render.
+ */
+function ctools_context_handler_get_render_handler($task, $subtask, $handlers, $contexts, $args) {
+ // Try each handler.
+ foreach ($handlers as $id => $handler) {
+ $plugin = page_manager_get_task_handler($handler->handler);
+ // First, see if the handler has a tester.
+ $function = ctools_plugin_get_function($plugin, 'test');
+ if ($function) {
+ $test = $function($handler, $contexts, $args);
+ if ($test) {
+ return $id;
+ }
+ }
+ else {
+ // If not, if it's a 'context' type handler, use the default tester.
+ if ($plugin['handler type'] == 'context') {
+ $test = ctools_context_handler_default_test($handler, $contexts, $args);
+ if ($test) {
+ return $id;
+ }
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Default test function to see if a task handler should be rendered.
+ *
+ * This tests against the standard selection criteria that most task
+ * handlers should be implementing.
+ */
+function ctools_context_handler_default_test($handler, $base_contexts, $args) {
+ ctools_include('context');
+ // Add my contexts
+ $contexts = ctools_context_handler_get_handler_contexts($base_contexts, $handler);
+
+ // Test.
+ return ctools_context_handler_select($handler, $contexts);
+}
+
+/**
+ * Render a task handler.
+ */
+function ctools_context_handler_render_handler($task, $subtask, $handler, $contexts, $args, $page = TRUE) {
+ $function = page_manager_get_renderer($handler);
+ if (!$function) {
+ return NULL;
+ }
+
+ if ($page) {
+ if ($subtask) {
+ $task_name = page_manager_make_task_name($task['name'], $subtask['name']);
+ }
+ else {
+ $task_name = $task['name'];
+ }
+
+ page_manager_get_current_page(array(
+ 'name' => $task_name,
+ 'task' => $task,
+ 'subtask' => $subtask,
+ 'contexts' => $contexts,
+ 'arguments' => $args,
+ 'handler' => $handler,
+ ));
+ }
+
+ $info = $function($handler, $contexts, $args);
+ if (!$info) {
+ return NULL;
+ }
+
+ $context = array(
+ 'args' => $args,
+ 'contexts' => $contexts,
+ 'task' => $task,
+ 'subtask' => $subtask,
+ 'handler' => $handler
+ );
+ drupal_alter('ctools_render', $info, $page, $context);
+
+ // If we don't own the page, let the caller deal with rendering.
+ if (!$page) {
+ return $info;
+ }
+
+ if (!empty($info['response code']) && $info['response code'] != 200) {
+ switch ($info['response code']) {
+ case 403:
+ return MENU_ACCESS_DENIED;
+ case 404:
+ return MENU_NOT_FOUND;
+ case 410:
+ drupal_add_http_header('Status', '410 Gone');
+ drupal_exit();
+ break;
+ case 301:
+ case 302:
+ case 303:
+ case 304:
+ case 305:
+ case 307:
+ $info += array(
+ 'query' => array(),
+ 'fragment' => '',
+ );
+ $options = array(
+ 'query' => $info['query'],
+ 'fragment' => $info['fragment'],
+ );
+ drupal_goto($info['destination'], $options, $info['response code']);
+ // @todo -- should other response codes be supported here?
+ }
+ }
+
+ $plugin = page_manager_get_task_handler($handler->handler);
+
+ if (module_exists('contextual') && user_access('access contextual links') && isset($handler->task)) {
+ // Provide a contextual link to edit this, if we can:
+ $callback = isset($plugin['contextual link']) ? $plugin['contextual link'] : 'ctools_task_handler_default_contextual_link';
+ if ($callback && function_exists($callback)) {
+ $links = $callback($handler, $plugin, $contexts, $args);
+ }
+
+ if (!empty($links) && is_array($links)) {
+ $build = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('contextual-links-region')),
+ );
+
+ if (!is_array($info['content'])) {
+ $build['content']['#markup'] = $info['content'];
+ }
+ else {
+ $build['content'] = $info['content'];
+ }
+
+ $build['contextual_links'] = array(
+ '#prefix' => '<div class="contextual-links-wrapper">',
+ '#suffix' => '</div>',
+ '#theme' => 'links__contextual',
+ '#links' => $links,
+ '#attributes' => array('class' => array('contextual-links')),
+ '#attached' => array(
+ 'library' => array(array('contextual', 'contextual-links')),
+ ),
+ );
+ $info['content'] = $build;
+ }
+ }
+
+ foreach (ctools_context_handler_get_task_arguments($task, $subtask) as $id => $argument) {
+ $plugin = ctools_get_argument($argument['name']);
+ $cid = ctools_context_id($argument, 'argument');
+ if (!empty($contexts[$cid]) && ($function = ctools_plugin_get_function($plugin, 'breadcrumb'))) {
+ $function($argument['settings'], $contexts[$cid]);
+ }
+ }
+
+ if (isset($info['title'])) {
+ drupal_set_title($info['title'], PASS_THROUGH);
+ }
+
+ // Only directly output if $page was set to true.
+ if (!empty($info['no_blocks'])) {
+ ctools_set_no_blocks(FALSE);
+ }
+ return $info['content'];
+}
+
+/**
+ * Default function to provide contextual link for a task as defined by the handler.
+ *
+ * This provides a simple link to th main content operation and is suitable
+ * for most normal handlers. Setting 'contextual link' to a function overrides
+ * this and setting it to FALSE will prevent a contextual link from appearing.
+ */
+function ctools_task_handler_default_contextual_link($handler, $plugin, $contexts, $args) {
+ if (!user_access('administer page manager')) {
+ return;
+ }
+
+ $task = page_manager_get_task($handler->task);
+
+ $title = !empty($task['tab title']) ? $task['tab title'] : t('Edit @type', array('@type' => $plugin['title']));
+ $trail = array();
+ if (!empty($plugin['tab operation'])) {
+ if (is_array($plugin['tab operation'])) {
+ $trail = $plugin['tab operation'];
+ }
+ else if (function_exists($plugin['tab operation'])) {
+ $trail = $plugin['tab operation']($handler, $contexts, $args);
+ }
+ }
+ $path = page_manager_edit_url(page_manager_make_task_name($handler->task, $handler->subtask), $trail);
+
+ $links = array(array(
+ 'href' => $path,
+ 'title' => $title,
+ 'query' => drupal_get_destination(),
+ ));
+
+ return $links;
+}
+
+/**
+ * Called to execute actions that should happen before a handler is rendered.
+ */
+function ctools_context_handler_pre_render($handler, $contexts, $args) { }
+
+/**
+ * Compare arguments to contexts for selection purposes.
+ *
+ * @param $handler
+ * The handler in question.
+ * @param $contexts
+ * The context objects provided by the task.
+ *
+ * @return
+ * TRUE if these contexts match the selection rules. NULL or FALSE
+ * otherwise.
+ */
+function ctools_context_handler_select($handler, $contexts) {
+ if (empty($handler->conf['access'])) {
+ return TRUE;
+ }
+
+ ctools_include('context');
+ return ctools_access($handler->conf['access'], $contexts);
+}
+
+/**
+ * Get the array of summary strings for the arguments.
+ *
+ * These summary strings are used to communicate to the user what
+ * arguments the task handlers are selecting.
+ *
+ * @param $task
+ * The loaded task plugin.
+ * @param $subtask
+ * The subtask id.
+ * @param $handler
+ * The handler to be checked.
+ */
+function ctools_context_handler_summary($task, $subtask, $handler) {
+ if (empty($handler->conf['access']['plugins'])) {
+ return array();
+ }
+
+ ctools_include('context');
+ $strings = array();
+ $contexts = ctools_context_handler_get_all_contexts($task, $subtask, $handler);
+
+ foreach ($handler->conf['access']['plugins'] as $test) {
+ $plugin = ctools_get_access_plugin($test['name']);
+ if ($string = ctools_access_summary($plugin, $contexts, $test)) {
+ $strings[] = $string;
+ }
+ }
+
+ return $strings;
+}
+
+// --------------------------------------------------------------------------
+// Tasks and Task handlers can both have their own sources of contexts.
+// Sometimes we need all of these contexts at once (when editing
+// the task handler, for example) but sometimes we need them separately
+// (when a task has contexts loaded and is trying out the task handlers,
+// for example). Therefore there are two paths we can take to getting contexts.
+
+/**
+ * Load the contexts for a task, using arguments.
+ *
+ * This creates the base array of contexts, loaded from arguments, suitable
+ * for use in rendering.
+ */
+function ctools_context_handler_get_task_contexts($task, $subtask, $args) {
+ $contexts = ctools_context_handler_get_base_contexts($task, $subtask);
+ $arguments = ctools_context_handler_get_task_arguments($task, $subtask);
+ ctools_context_get_context_from_arguments($arguments, $contexts, $args);
+
+ return $contexts;
+}
+
+/**
+ * Load the contexts for a task handler.
+ *
+ * This expands a base set of contexts passed in from a task with the
+ * contexts defined on the task handler. The contexts from the task
+ * must already have been loaded.
+ */
+function ctools_context_handler_get_handler_contexts($contexts, $handler) {
+ $object = ctools_context_handler_get_handler_object($handler);
+ return ctools_context_load_contexts($object, FALSE, $contexts);
+}
+
+/**
+ * Load the contexts for a task and task handler together.
+ *
+ * This pulls the arguments from a task and everything else from a task
+ * handler and loads them as a group. Since there is no data, this loads
+ * the contexts as placeholders.
+ */
+function ctools_context_handler_get_all_contexts($task, $subtask, $handler) {
+ $contexts = array();
+
+ $object = ctools_context_handler_get_task_object($task, $subtask, $handler);
+ $contexts = ctools_context_load_contexts($object, TRUE, $contexts);
+ ctools_context_handler_set_access_restrictions($task, $subtask, $handler, $contexts);
+ return $contexts;
+}
+
+/**
+ * Create an object suitable for use with the context system that kind of
+ * expects things in a certain, kind of clunky format.
+ */
+function ctools_context_handler_get_handler_object($handler) {
+ $object = new stdClass;
+ $object->name = $handler->name;
+ $object->contexts = isset($handler->conf['contexts']) ? $handler->conf['contexts'] : array();
+ $object->relationships = isset($handler->conf['relationships']) ? $handler->conf['relationships'] : array();
+
+ return $object;
+}
+
+/**
+ * Create an object suitable for use with the context system that kind of
+ * expects things in a certain, kind of clunky format. This one adds in
+ * arguments from the task.
+ */
+function ctools_context_handler_get_task_object($task, $subtask, $handler) {
+ $object = new stdClass;
+ $object->name = !empty($handler->name) ? $handler->name : 'temp';
+ $object->base_contexts = ctools_context_handler_get_base_contexts($task, $subtask, TRUE);
+ $object->arguments = ctools_context_handler_get_task_arguments($task, $subtask);
+ $object->contexts = isset($handler->conf['contexts']) ? $handler->conf['contexts'] : array();
+ $object->relationships = isset($handler->conf['relationships']) ? $handler->conf['relationships'] : array();
+
+ return $object;
+}
+
+/**
+ * Get base contexts from a task, if it has any.
+ *
+ * Tasks can get their contexts either from base contexts or arguments; base
+ * contexts extract their information from the environment.
+ */
+function ctools_context_handler_get_base_contexts($task, $subtask, $placeholders = FALSE) {
+ if ($function = ctools_plugin_get_function($task, 'get base contexts')) {
+ return $function($task, $subtask, $placeholders);
+ }
+
+ return array();
+}
+
+/**
+ * Get the arguments from a task that are used to load contexts.
+ */
+function ctools_context_handler_get_task_arguments($task, $subtask) {
+ if ($function = ctools_plugin_get_function($task, 'get arguments')) {
+ return $function($task, $subtask);
+ }
+
+ return array();
+}
+
+/**
+ * Set any access restrictions on the contexts for a handler.
+ *
+ * Both the task and the handler could add restrictions to the contexts
+ * based upon the access control. These restrictions might be useful
+ * to limit what kind of content appears in the add content dialog;
+ * for example, if we have an access item that limits a node context
+ * to only 'story' and 'page' types, there is no need for content that
+ * only applies to the 'poll' type to appear.
+ */
+function ctools_context_handler_set_access_restrictions($task, $subtask, $handler, &$contexts) {
+ // First, for the task:
+ if ($function = ctools_plugin_get_function($task, 'access restrictions')) {
+ $function($task, $subtask, $contexts);
+ }
+
+ // Then for the handler:
+ if (isset($handler->conf['access'])) {
+ ctools_access_add_restrictions($handler->conf['access'], $contexts);
+ }
+}
+
+/**
+ * Form to choose context based selection rules for a task handler.
+ *
+ * The configuration will be assumed to go simply in $handler->conf and
+ * will be keyed by the argument ID.
+ */
+function ctools_context_handler_edit_criteria($form, &$form_state) {
+ if (!isset($form_state['handler']->conf['access'])) {
+ $form_state['handler']->conf['access'] = array();
+ }
+
+ ctools_include('context');
+ ctools_include('modal');
+ ctools_include('ajax');
+ ctools_modal_add_plugin_js(ctools_get_access_plugins());
+ ctools_include('context-access-admin');
+ $form_state['module'] = (isset($form_state['module'])) ? $form_state['module'] : 'page_manager_task_handler';
+ // Encode a bunch of info into the argument so we can get our cache later
+ $form_state['callback argument'] = $form_state['task_name'] . '*' . $form_state['handler']->name;
+ $form_state['access'] = $form_state['handler']->conf['access'];
+ $form_state['no buttons'] = TRUE;
+ $form_state['contexts'] = ctools_context_handler_get_all_contexts($form_state['task'], $form_state['subtask'], $form_state['handler']);
+
+ $form['markup'] = array(
+ '#markup' => '<div class="description">' .
+ t('If there is more than one variant on a page, when the page is visited each variant is given an opportunity to be displayed. Starting from the first variant and working to the last, each one tests to see if its selection rules will pass. The first variant that meets its criteria (as specified below) will be used.') .
+ '</div>',
+ );
+ $form = ctools_access_admin_form($form, $form_state);
+ return $form;
+}
+
+/**
+ * Submit handler for rules selection
+ */
+function ctools_context_handler_edit_criteria_submit(&$form, &$form_state) {
+ $form_state['handler']->conf['access']['logic'] = $form_state['values']['logic'];
+}
+
+/**
+ * Edit contexts that go with this panel.
+ */
+function ctools_context_handler_edit_context($form, &$form_state) {
+ ctools_include('context-admin');
+ ctools_context_admin_includes();
+
+ $handler = $form_state['handler'];
+ $page = $form_state['page'];
+ $cache_name = $handler->name ? $handler->name : 'temp';
+ if (isset($page->context_cache[$cache_name])) {
+ $cache = $page->context_cache[$cache_name];
+ }
+ else {
+ $cache = ctools_context_handler_get_task_object($form_state['task'], $form_state['subtask'], $form_state['handler']);
+ $form_state['page']->context_cache[$cache_name] = $cache;
+ }
+
+ $form['right'] = array(
+ '#prefix' => '<div class="clearfix"><div class="right-container">',
+ '#suffix' => '</div>',
+ );
+
+ $form['left'] = array(
+ '#prefix' => '<div class="left-container">',
+ '#suffix' => '</div></div>',
+ );
+
+ $module = 'page_manager_context::' . $page->task_name;
+ ctools_context_add_context_form($module, $form, $form_state, $form['right']['contexts_table'], $cache);
+ ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $cache);
+
+ $theme_vars = array();
+ $theme_vars['object'] = $cache;
+ $theme_vars['header'] = t('Summary of contexts');
+ $form['left']['summary'] = array(
+ '#prefix' => '<div class="page-manager-contexts">',
+ '#suffix' => '</div>',
+ '#markup' => theme('ctools_context_list', $theme_vars),
+ );
+
+ $form_state['context_object'] = &$cache;
+ return $form;
+}
+
+/**
+ * Process submission of the context edit form.
+ */
+function ctools_context_handler_edit_context_submit(&$form, &$form_state) {
+ $handler = &$form_state['handler'];
+
+ $cache_name = $handler->name ? $handler->name : 'temp';
+
+ $handler->conf['contexts'] = $form_state['context_object']->contexts;
+ $handler->conf['relationships'] = $form_state['context_object']->relationships;
+ if (isset($form_state['page']->context_cache[$cache_name])) {
+ unset($form_state['page']->context_cache[$cache_name]);
+ }
+}
+
diff --git a/sites/all/modules/ctools/includes/context.inc b/sites/all/modules/ctools/includes/context.inc
new file mode 100644
index 000000000..1f9c1e457
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context.inc
@@ -0,0 +1,1602 @@
+<?php
+
+/**
+ * @file
+ *
+ * Contains code related to the ctools system of 'context'.
+ *
+ * Context, originally from Panels, is a method of packaging objects into
+ * a more generic bundle and providing a plugin system so that a UI can
+ * take advantage of them. The idea is that the context objects
+ * represent 'the context' that a given operation (usually a page view)
+ * is operating in or on.
+ *
+ * For example, when viewing a page, the 'context' is a node object. When
+ * viewing a user, the 'context' is a user object. Contexts can also
+ * have related contexts. For example, when viewing a 'node' you may need
+ * to know something about the node author. Therefore, the node author
+ * is a related context.
+ */
+
+/**
+ * The context object is largely a wrapper around some other object, with
+ * an interface to finding out what is contained and getting to both
+ * the object and information about the object.
+ *
+ * Each context object has its own information, but some things are very
+ * common, such as titles, data, keywords, etc. In particulare, the 'type'
+ * of the context is important.
+ */
+class ctools_context {
+ var $type = NULL;
+ var $data = NULL;
+ // The title of this object.
+ var $title = '';
+ // The title of the page if this object exists
+ var $page_title = '';
+ // The identifier (in the UI) of this object
+ var $identifier = '';
+ var $argument = NULL;
+ var $keyword = '';
+ var $original_argument = NULL;
+ var $restrictions = array();
+ var $empty = FALSE;
+
+ function ctools_context($type = 'none', $data = NULL) {
+ $this->type = $type;
+ $this->data = $data;
+ $this->title = t('Unknown context');
+ }
+
+ function is_type($type) {
+ if ($type == 'any' || $this->type == 'any') {
+ return TRUE;
+ }
+
+ $a = is_array($type) ? $type : array($type);
+ $b = is_array($this->type) ? $this->type : array($this->type);
+ return (bool) array_intersect($a, $b);
+ }
+
+ function get_argument() {
+ return $this->argument;
+ }
+
+ function get_original_argument() {
+ if (!is_null($this->original_argument)) {
+ return $this->original_argument;
+ }
+ return $this->argument;
+ }
+
+ function get_keyword() {
+ return $this->keyword;
+ }
+
+ function get_identifier() {
+ return $this->identifier;
+ }
+
+ function get_title() {
+ return $this->title;
+ }
+
+ function get_page_title() {
+ return $this->page_title;
+ }
+}
+
+/**
+ * Used to create a method of comparing if a list of contexts
+ * match a required context type.
+ */
+class ctools_context_required {
+ var $keywords = '';
+
+ /**
+ * If set, the title will be used in the selector to identify
+ * the context. This is very useful when multiple contexts
+ * are required to inform the user will be used for what.
+ */
+ var $title = NULL;
+
+ /**
+ * Test to see if this context is required.
+ */
+ var $required = TRUE;
+
+ /**
+ * If TRUE, skip the check in ctools_context_required::select()
+ * for contexts whose names may have changed.
+ */
+ var $skip_name_check = FALSE;
+
+ /**
+ *
+ * @param $title
+ * The first parameter should be the 'title' of the context for use
+ * in UYI selectors when multiple contexts qualify.
+ * @param ...
+ * One or more keywords to use for matching which contexts are allowed.
+ */
+ function ctools_context_required($title) {
+ $args = func_get_args();
+ $this->title = array_shift($args);
+
+ // If we have a boolean value at the end for $skip_name_check, store it
+ if (is_bool(end($args))) {
+ $this->skip_name_check = array_pop($args);
+ }
+
+ // If we were given restrictions at the end, store them.
+ if (count($args) > 1 && is_array(end($args))) {
+ $this->restrictions = array_pop($args);
+ }
+
+ if (count($args) == 1) {
+ $args = array_shift($args);
+ }
+ $this->keywords = $args;
+ }
+
+ function filter($contexts) {
+ $result = array();
+
+ // See which of these contexts are valid
+ foreach ((array) $contexts as $cid => $context) {
+ if ($context->is_type($this->keywords)) {
+ // Compare to see if our contexts were met.
+ if (!empty($this->restrictions) && !empty($context->restrictions)) {
+ foreach ($this->restrictions as $key => $values) {
+ // If we have a restriction, the context must either not have that
+ // restriction listed, which means we simply don't know what it is,
+ // or there must be an intersection of the restricted values on
+ // both sides.
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+ if (!empty($context->restrictions[$key]) && !array_intersect($values, $context->restrictions[$key])) {
+ continue 2;
+ }
+ }
+ }
+ $result[$cid] = $context;
+ }
+ }
+
+ return $result;
+ }
+
+ function select($contexts, $context) {
+ if (!is_array($contexts)) {
+ if (is_object($contexts) && $contexts instanceof ctools_context) {
+ $contexts = array($contexts->id => $contexts);
+ }
+ else {
+ $contexts = array($contexts);
+ }
+ }
+
+ // If we had requested a $context but that $context doesn't exist
+ // in our context list, there is a good chance that what happened
+ // is our context IDs changed. See if there's another context
+ // that satisfies our requirements.
+ if (!$this->skip_name_check && !empty($context) && !isset($contexts[$context])) {
+ $choices = $this->filter($contexts);
+
+ // If we got a hit, take the first one that matches.
+ if ($choices) {
+ $keys = array_keys($choices);
+ $context = reset($keys);
+ }
+ }
+
+ if (empty($context) || empty($contexts[$context])) {
+ return FALSE;
+ }
+ return $contexts[$context];
+ }
+}
+
+/**
+ * Used to compare to see if a list of contexts match an optional context. This
+ * can produce empty contexts to use as placeholders.
+ */
+class ctools_context_optional extends ctools_context_required {
+ var $required = FALSE;
+ function ctools_context_optional() {
+ $args = func_get_args();
+ call_user_func_array(array($this, 'ctools_context_required'), $args);
+ }
+
+ /**
+ * Add the 'empty' context which is possible for optional
+ */
+ function add_empty(&$contexts) {
+ $context = new ctools_context('any');
+ $context->title = t('No context');
+ $context->identifier = t('No context');
+ $contexts['empty'] = $context;
+ }
+
+ function filter($contexts) {
+ $this->add_empty($contexts);
+ return parent::filter($contexts);
+ }
+
+ function select($contexts, $context) {
+ $this->add_empty($contexts);
+ if (empty($context)) {
+ return $contexts['empty'];
+ }
+
+ $result = parent::select($contexts, $context);
+
+ // Don't flip out if it can't find the context; this is optional, put
+ // in an empty.
+ if ($result == FALSE) {
+ $result = $contexts['empty'];
+ }
+ return $result;
+ }
+}
+
+/**
+ * Return a keyed array of context that match the given 'required context'
+ * filters.
+ *
+ * Functions or systems that require contexts of a particular type provide a
+ * ctools_context_required or ctools_context_optional object. This function
+ * examines that object and an array of contexts to determine which contexts
+ * match the filter.
+ *
+ * Since multiple contexts can be required, this function will accept either
+ * an array of all required contexts, or just a single required context object.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts.
+ * @param $required
+ * A ctools_context_required or ctools_context_optional object, or an array
+ * of such objects.
+ *
+ * @return
+ * A keyed array of contexts that match the filter.
+ */
+function ctools_context_filter($contexts, $required) {
+ if (is_array($required)) {
+ $result = array();
+ foreach ($required as $r) {
+ $result = array_merge($result, _ctools_context_filter($contexts, $r));
+ }
+ return $result;
+ }
+
+ return _ctools_context_filter($contexts, $required);
+}
+
+function _ctools_context_filter($contexts, $required) {
+ $result = array();
+
+ if (is_object($required)) {
+ $result = $required->filter($contexts);
+ }
+
+ return $result;
+}
+
+/**
+ * Create a select box to choose possible contexts.
+ *
+ * This only creates a selector if there is actually a choice; if there
+ * is only one possible context, that one is silently assigned.
+ *
+ * If an array of required contexts is provided, one selector will be
+ * provided for each context.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts.
+ * @param $required
+ * The required context object or array of objects.
+ *
+ * @return
+ * A form element, or NULL if there are no contexts that satisfy the
+ * requirements.
+ */
+function ctools_context_selector($contexts, $required, $default) {
+ if (is_array($required)) {
+ $result = array('#tree' => TRUE);
+ $count = 1;
+ foreach ($required as $id => $r) {
+ $result[] = _ctools_context_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++);
+ }
+ return $result;
+ }
+
+ return _ctools_context_selector($contexts, $required, $default);
+}
+
+function _ctools_context_selector($contexts, $required, $default, $num = 0) {
+ $filtered = ctools_context_filter($contexts, $required);
+ $count = count($filtered);
+
+ $form = array();
+
+ if ($count >= 1) {
+ // If there's more than one to choose from, create a select widget.
+ foreach ($filtered as $cid => $context) {
+ $options[$cid] = $context->get_identifier();
+ }
+ if (!empty($required->title)) {
+ $title = $required->title;
+ }
+ else {
+ $title = $num ? t('Context %count', array('%count' => $num)) : t('Context');
+ }
+
+ $form = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => $title,
+ '#default_value' => $default,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Are there enough contexts for a plugin?
+ *
+ * Some plugins can have a 'required contexts' item which can either
+ * be a context requirement object or an array of them. When contexts
+ * are required, items that do not have enough contexts should not
+ * appear. This tests an item to see if it has enough contexts
+ * to actually appear.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts.
+ * @param $required
+ * The required context object or array of objects.
+ *
+ * @return
+ * TRUE if there are enough contexts, FALSE if there are not.
+ */
+function ctools_context_match_requirements($contexts, $required) {
+ if (!is_array($required)) {
+ $required = array($required);
+ }
+
+ // Get the keys to avoid bugs in PHP 5.0.8 with keys and loops.
+ // And use it to remove optional contexts.
+ $keys = array_keys($required);
+ foreach ($keys as $key) {
+ if (empty($required[$key]->required)) {
+ unset($required[$key]);
+ }
+ }
+
+ $count = count($required);
+ return (count(ctools_context_filter($contexts, $required)) >= $count);
+}
+
+/**
+ * Create a select box to choose possible contexts.
+ *
+ * This only creates a selector if there is actually a choice; if there
+ * is only one possible context, that one is silently assigned.
+ *
+ * If an array of required contexts is provided, one selector will be
+ * provided for each context.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts.
+ * @param $required
+ * The required context object or array of objects.
+ *
+ * @return
+ * A form element, or NULL if there are no contexts that satisfy the
+ * requirements.
+ */
+function ctools_context_converter_selector($contexts, $required, $default) {
+ if (is_array($required)) {
+ $result = array('#tree' => TRUE);
+ $count = 1;
+ foreach ($required as $id => $r) {
+ $result[] = _ctools_context_converter_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++);
+ }
+ return $result;
+ }
+
+ return _ctools_context_converter_selector($contexts, $required, $default);
+}
+
+function _ctools_context_converter_selector($contexts, $required, $default, $num = 0) {
+ $filtered = ctools_context_filter($contexts, $required);
+ $count = count($filtered);
+
+ $form = array();
+
+ if ($count > 1) {
+ // If there's more than one to choose from, create a select widget.
+ $options = array();
+ foreach ($filtered as $cid => $context) {
+ if ($context->type == 'any') {
+ $options[''] = t('No context');
+ continue;
+ }
+ $key = $context->get_identifier();
+ if ($converters = ctools_context_get_converters($cid . '.', $context)) {
+ $options[$key] = $converters;
+ }
+ }
+ if (empty($options)) {
+ return array(
+ '#type' => 'value',
+ '#value' => 'any',
+ );
+ }
+ if (!empty($required->title)) {
+ $title = $required->title;
+ }
+ else {
+ $title = $num ? t('Context %count', array('%count' => $num)) : t('Context');
+ }
+
+ return array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => $title,
+ '#description' => t('Please choose which context and how you would like it converted.'),
+ '#default_value' => $default,
+ );
+ }
+}
+
+/**
+ * Get a list of converters available for a given context.
+ */
+function ctools_context_get_converters($cid, $context) {
+ if (empty($context->plugin)) {
+ return array();
+ }
+
+ return _ctools_context_get_converters($cid, $context->plugin);
+}
+
+/**
+ * Get a list of converters available for a given context.
+ */
+function _ctools_context_get_converters($id, $plugin_name) {
+ $plugin = ctools_get_context($plugin_name);
+ if (empty($plugin['convert list'])) {
+ return array();
+ }
+
+ $converters = array();
+ if (is_array($plugin['convert list'])) {
+ $converters = $plugin['convert list'];
+ }
+ else if ($function = ctools_plugin_get_function($plugin, 'convert list')) {
+ $converters = (array) $function($plugin);
+ }
+
+ foreach (module_implements('ctools_context_convert_list_alter') as $module) {
+ $function = $module . '_ctools_context_convert_list_alter';
+ $function($plugin, $converters);
+ }
+
+ // Now, change them all to include the plugin:
+ $return = array();
+ foreach ($converters as $key => $title) {
+ $return[$id . $key] = $title;
+ }
+
+ natcasesort($return);
+ return $return;
+}
+
+/**
+ * Get a list of all contexts + converters available.
+ */
+function ctools_context_get_all_converters() {
+ $contexts = ctools_get_contexts();
+ $converters = array();
+ foreach ($contexts as $name => $context) {
+ if (empty($context['no required context ui'])) {
+ $context_converters = _ctools_context_get_converters($name . '.', $name);
+ if ($context_converters) {
+ $converters[$context['title']] = $context_converters;
+ }
+ }
+ }
+
+ return $converters;
+}
+
+/**
+ * Let the context convert an argument based upon the converter that was given.
+ *
+ * @param $context
+ * The context object
+ * @param $converter
+ * The converter to use, which should be a string provided by the converter list.
+ * @param $converter_options
+ * A n array of options to pass on to the generation function. For contexts
+ * that use token module, of particular use is 'sanitize' => FALSE which can
+ * get raw tokens. This should ONLY be used in values that will later be
+ * treated as unsafe user input since these values are by themselves unsafe.
+ * It is particularly useful to get raw values from Field API.
+ */
+function ctools_context_convert_context($context, $converter, $converter_options = array()) {
+ // Contexts without plugins might be optional placeholders.
+ if (empty($context->plugin)) {
+ return;
+ }
+
+ $value = $context->argument;
+ $plugin = ctools_get_context($context->plugin);
+ if ($function = ctools_plugin_get_function($plugin, 'convert')) {
+ $value = $function($context, $converter, $converter_options);
+ }
+
+ foreach (module_implements('ctools_context_converter_alter') as $module) {
+ $function = $module . '_ctools_context_converter_alter';
+ $function($context, $converter, $value, $converter_options);
+ }
+
+ return $value;
+}
+
+/**
+ * Choose a context or contexts based upon the selection made via
+ * ctools_context_filter.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts
+ * @param $required
+ * The required context object provided by the plugin
+ * @param $context
+ * The selection made using ctools_context_selector
+ */
+function ctools_context_select($contexts, $required, $context) {
+ if (is_array($required)) {
+ $result = array();
+ foreach ($required as $id => $r) {
+ if (empty($required[$id])) {
+ continue;
+ }
+
+ if (($result[] = _ctools_context_select($contexts, $r, $context[$id])) === FALSE) {
+ return FALSE;
+ }
+ }
+ return $result;
+ }
+
+ return _ctools_context_select($contexts, $required, $context);
+}
+
+function _ctools_context_select($contexts, $required, $context) {
+ if (!is_object($required)) {
+ return FALSE;
+ }
+
+ return $required->select($contexts, $context);
+}
+
+/**
+ * Create a new context object.
+ *
+ * @param $type
+ * The type of context to create; this loads a plugin.
+ * @param $data
+ * The data to put into the context.
+ * @param $empty
+ * Whether or not this context is specifically empty.
+ * @param $conf
+ * A configuration structure if this context was created via UI.
+ *
+ * @return
+ * A $context or NULL if one could not be created.
+ */
+function ctools_context_create($type, $data = NULL, $conf = FALSE) {
+ ctools_include('plugins');
+ $plugin = ctools_get_context($type);
+
+ if ($function = ctools_plugin_get_function($plugin, 'context')) {
+ return $function(FALSE, $data, $conf, $plugin);
+ }
+}
+
+/**
+ * Create an empty context object.
+ *
+ * Empty context objects are primarily used as placeholders in the UI where
+ * the actual contents of a context object may not be known. It may have
+ * additional text embedded to give the user clues as to how the context
+ * is used.
+ *
+ * @param $type
+ * The type of context to create; this loads a plugin.
+ *
+ * @return
+ * A $context or NULL if one could not be created.
+ */
+function ctools_context_create_empty($type) {
+ $plugin = ctools_get_context($type);
+ if ($function = ctools_plugin_get_function($plugin, 'context')) {
+ $context = $function(TRUE, NULL, FALSE, $plugin);
+ if (is_object($context)) {
+ $context->empty = TRUE;
+ }
+
+ return $context;
+ }
+}
+
+/**
+ * Perform keyword and context substitutions.
+ */
+function ctools_context_keyword_substitute($string, $keywords, $contexts, $converter_options = array()) {
+ // Ensure a default keyword exists:
+ $keywords['%%'] = '%';
+
+ // Match contexts to the base keywords:
+ $context_keywords = array();
+ foreach ($contexts as $context) {
+ if (isset($context->keyword)) {
+ $context_keywords[$context->keyword] = $context;
+ }
+ }
+
+ // Look for context matches we we only have to convert known matches.
+ $matches = array();
+ if (preg_match_all('/%(%|[a-zA-Z0-9_-]+(?:\:[a-zA-Z0-9_-]+)*)/us', $string, $matches)) {
+ foreach ($matches[1] as $keyword) {
+ // Ignore anything it finds with %%.
+ if ($keyword[0] == '%') {
+ continue;
+ }
+
+ // If the keyword is already set by something passed in, don't try to
+ // overwrite it.
+ if (!empty($keywords['%' . $keyword])) {
+ continue;
+ }
+
+ // Figure out our keyword and converter, if specified.
+ if (strpos($keyword, ':')) {
+ list($context, $converter) = explode(':', $keyword, 2);
+ }
+ else {
+ $context = $keyword;
+ if (isset($context_keywords[$keyword])) {
+ $plugin = ctools_get_context($context_keywords[$context]->plugin);
+
+ // Fall back to a default converter, if specified.
+ if ($plugin && !empty($plugin['convert default'])) {
+ $converter = $plugin['convert default'];
+ }
+ }
+ }
+
+ if (empty($context_keywords[$context]) || !empty($context_keywords[$context]->empty)) {
+ $keywords['%' . $keyword] = '';
+ }
+ else if (!empty($converter)) {
+ $keywords['%' . $keyword] = ctools_context_convert_context($context_keywords[$context], $converter, $converter_options);
+ }
+ else {
+ $keywords['%' . $keyword] = $context_keywords[$keyword]->title;
+ }
+ }
+ }
+ return strtr($string, $keywords);
+}
+
+/**
+ * Determine a unique context ID for a context
+ *
+ * Often contexts of many different types will be placed into a list. This
+ * ensures that even though contexts of multiple types may share IDs, they
+ * are unique in the final list.
+ */
+function ctools_context_id($context, $type = 'context') {
+ if (!$context['id']) {
+ $context['id'] = 1;
+ }
+
+ return $type . '_' . $context['name'] . '_' . $context['id'];
+}
+
+/**
+ * Get the next id available given a list of already existing objects.
+ *
+ * This finds the next id available for the named object.
+ *
+ * @param $objects
+ * A list of context descriptor objects, i.e, arguments, relationships, contexts, etc.
+ * @param $name
+ * The name being used.
+ */
+function ctools_context_next_id($objects, $name) {
+ $id = 0;
+ // Figure out which instance of this argument we're creating
+ if (!$objects) {
+ return $id + 1;
+ }
+
+ foreach ($objects as $object) {
+ if (isset($object['name']) && $object['name'] == $name) {
+ if ($object['id'] > $id) {
+ $id = $object['id'];
+ }
+ }
+ }
+
+ return $id + 1;
+}
+
+
+// ---------------------------------------------------------------------------
+// Functions related to contexts from arguments.
+
+/**
+ * Fetch metadata on a specific argument plugin.
+ *
+ * @param $argument
+ * Name of an argument plugin.
+ *
+ * @return
+ * An array with information about the requested argument plugin.
+ */
+function ctools_get_argument($argument) {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'arguments', $argument);
+}
+
+/**
+ * Fetch metadata for all argument plugins.
+ *
+ * @return
+ * An array of arrays with information about all available argument plugins.
+ */
+function ctools_get_arguments() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'arguments');
+}
+
+/**
+ * Get a context from an argument.
+ *
+ * @param $argument
+ * The configuration of an argument. It must contain the following data:
+ * - name: The name of the argument plugin being used.
+ * - argument_settings: The configuration based upon the plugin forms.
+ * - identifier: The human readable identifier for this argument, usually
+ * defined by the UI.
+ * - keyword: The keyword used for this argument for substitutions.
+ *
+ * @param $arg
+ * The actual argument received. This is expected to be a string from a URL but
+ * this does not have to be the only source of arguments.
+ * @param $empty
+ * If true, the $arg will not be used to load the context. Instead, an empty
+ * placeholder context will be loaded.
+ *
+ * @return
+ * A context object if one can be loaded.
+ */
+function ctools_context_get_context_from_argument($argument, $arg, $empty = FALSE) {
+ ctools_include('plugins');
+ if (empty($argument['name'])) {
+ return;
+ }
+
+ if ($function = ctools_plugin_load_function('ctools', 'arguments', $argument['name'], 'context')) {
+ // Backward compatibility: Merge old style settings into new style:
+ if (!empty($argument['settings'])) {
+ $argument += $argument['settings'];
+ unset($argument['settings']);
+ }
+
+ $context = $function($arg, $argument, $empty);
+
+ if (is_object($context)) {
+ $context->identifier = $argument['identifier'];
+ $context->page_title = isset($argument['title']) ? $argument['title'] : '';
+ $context->keyword = $argument['keyword'];
+ $context->id = ctools_context_id($argument, 'argument');
+ $context->original_argument = $arg;
+
+ if (!empty($context->empty)) {
+ $context->placeholder = array(
+ 'type' => 'argument',
+ 'conf' => $argument,
+ );
+ }
+ }
+ return $context;
+ }
+}
+
+/**
+ * Retrieve a list of empty contexts for all arguments.
+ */
+function ctools_context_get_placeholders_from_argument($arguments) {
+ $contexts = array();
+ foreach ($arguments as $argument) {
+ $context = ctools_context_get_context_from_argument($argument, NULL, TRUE);
+ if ($context) {
+ $contexts[ctools_context_id($argument, 'argument')] = $context;
+ }
+ }
+ return $contexts;
+}
+
+/**
+ * Load the contexts for a given list of arguments.
+ *
+ * @param $arguments
+ * The array of argument definitions.
+ * @param &$contexts
+ * The array of existing contexts. New contexts will be added to this array.
+ * @param $args
+ * The arguments to load.
+ *
+ * @return
+ * FALSE if an argument wants to 404.
+ */
+function ctools_context_get_context_from_arguments($arguments, &$contexts, $args) {
+ foreach ($arguments as $argument) {
+ // pull the argument off the list.
+ $arg = array_shift($args);
+ $id = ctools_context_id($argument, 'argument');
+
+ // For % arguments embedded in the URL, our context is already loaded.
+ // There is no need to go and load it again.
+ if (empty($contexts[$id])) {
+ if ($context = ctools_context_get_context_from_argument($argument, $arg)) {
+ $contexts[$id] = $context;
+ }
+ }
+ else {
+ $context = $contexts[$id];
+ }
+
+ if ((empty($context) || empty($context->data)) && !empty($argument['default']) && $argument['default'] == '404') {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+// ---------------------------------------------------------------------------
+// Functions related to contexts from relationships.
+
+/**
+ * Fetch metadata on a specific relationship plugin.
+ *
+ * @param $content type
+ * Name of a panel content type.
+ *
+ * @return
+ * An array with information about the requested relationship.
+ */
+function ctools_get_relationship($relationship) {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'relationships', $relationship);
+}
+
+/**
+ * Fetch metadata for all relationship plugins.
+ *
+ * @return
+ * An array of arrays with information about all available relationships.
+ */
+function ctools_get_relationships() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'relationships');
+}
+
+/**
+ *
+ * @param $relationship
+ * The configuration of a relationship. It must contain the following data:
+ * - name: The name of the relationship plugin being used.
+ * - relationship_settings: The configuration based upon the plugin forms.
+ * - identifier: The human readable identifier for this relationship, usually
+ * defined by the UI.
+ * - keyword: The keyword used for this relationship for substitutions.
+ *
+ * @param $source_context
+ * The context this relationship is based upon.
+ *
+ * @param $placeholders
+ * If TRUE, placeholders are acceptable.
+ *
+ * @return
+ * A context object if one can be loaded.
+ */
+function ctools_context_get_context_from_relationship($relationship, $source_context, $placeholders = FALSE) {
+ ctools_include('plugins');
+ if ($function = ctools_plugin_load_function('ctools', 'relationships', $relationship['name'], 'context')) {
+ // Backward compatibility: Merge old style settings into new style:
+ if (!empty($relationship['relationship_settings'])) {
+ $relationship += $relationship['relationship_settings'];
+ unset($relationship['relationship_settings']);
+ }
+
+ $context = $function($source_context, $relationship, $placeholders);
+ if ($context) {
+ $context->identifier = $relationship['identifier'];
+ $context->page_title = isset($relationship['title']) ? $relationship['title'] : '';
+ $context->keyword = $relationship['keyword'];
+ if (!empty($context->empty)) {
+ $context->placeholder = array(
+ 'type' => 'relationship',
+ 'conf' => $relationship,
+ );
+ }
+ return $context;
+ }
+ }
+}
+
+/**
+ * Fetch all relevant relationships.
+ *
+ * Relevant relationships are any relationship that can be created based upon
+ * the list of existing contexts. For example, the 'node author' relationship
+ * is relevant if there is a 'node' context, but makes no sense if there is
+ * not one.
+ *
+ * @param $contexts
+ * An array of contexts used to figure out which relationships are relevant.
+ *
+ * @return
+ * An array of relationship keys that are relevant for the given set of
+ * contexts.
+ */
+function ctools_context_get_relevant_relationships($contexts) {
+ $relevant = array();
+ $relationships = ctools_get_relationships();
+
+ // Go through each relationship
+ foreach ($relationships as $rid => $relationship) {
+ // For each relationship, see if there is a context that satisfies it.
+ if (empty($relationship['no ui']) && ctools_context_filter($contexts, $relationship['required context'])) {
+ $relevant[$rid] = $relationship['title'];
+ }
+ }
+
+ return $relevant;
+}
+
+/**
+ * Fetch all active relationships
+ *
+ * @param $relationships
+ * An keyed array of relationship data including:
+ * - name: name of relationship
+ * - context: context id relationship belongs to. This will be used to
+ * identify which context in the $contexts array to use to create the
+ * relationship context.
+ *
+ * @param $contexts
+ * A keyed array of contexts used to figure out which relationships
+ * are relevant. New contexts will be added to this.
+ *
+ * @param $placeholders
+ * If TRUE, placeholders are acceptable.
+ */
+function ctools_context_get_context_from_relationships($relationships, &$contexts, $placeholders = FALSE) {
+ $return = array();
+
+ foreach ($relationships as $rdata) {
+ if (!isset($rdata['context'])) {
+ continue;
+ }
+
+ if (is_array($rdata['context'])) {
+ $rcontexts = array();
+ foreach ($rdata['context'] as $cid) {
+ if (empty($contexts[$cid])) {
+ continue 2;
+ }
+ $rcontexts[] = $contexts[$cid];
+ }
+ }
+ else {
+ if (empty($contexts[$rdata['context']])) {
+ continue;
+ }
+ $rcontexts = $contexts[$rdata['context']];
+ }
+
+ $cid = ctools_context_id($rdata, 'relationship');
+ if ($context = ctools_context_get_context_from_relationship($rdata, $rcontexts)) {
+ $contexts[$cid] = $context;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Functions related to loading contexts from simple context definitions.
+
+/**
+ * Fetch metadata on a specific context plugin.
+ *
+ * @param $context
+ * Name of a context.
+ *
+ * @return
+ * An array with information about the requested panel context.
+ */
+function ctools_get_context($context) {
+ static $gate = array();
+ ctools_include('plugins');
+ $plugin = ctools_get_plugins('ctools', 'contexts', $context);
+ if (empty($gate['context']) && !empty($plugin['superceded by'])) {
+ // This gate prevents infinite loops.
+ $gate[$context] = TRUE;
+ $new_plugin = ctools_get_plugins('ctools', 'contexts', $plugin['superceded by']);
+ $gate[$context] = FALSE;
+
+ // If a new plugin was returned, return it. Otherwise fall through and
+ // return the original we fetched.
+ if ($new_plugin) {
+ return $new_plugin;
+ }
+ }
+
+ return $plugin;
+}
+
+/**
+ * Fetch metadata for all context plugins.
+ *
+ * @return
+ * An array of arrays with information about all available panel contexts.
+ */
+function ctools_get_contexts() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'contexts');
+}
+
+/**
+ *
+ * @param $context
+ * The configuration of a context. It must contain the following data:
+ * - name: The name of the context plugin being used.
+ * - context_settings: The configuration based upon the plugin forms.
+ * - identifier: The human readable identifier for this context, usually
+ * defined by the UI.
+ * - keyword: The keyword used for this context for substitutions.
+ * @param $type
+ * This is either 'context' which indicates the context will be loaded
+ * from data in the settings, or 'required_context' which means the
+ * context must be acquired from an external source. This is the method
+ * used to pass pure contexts from one system to another.
+ *
+ * @return
+ * A context object if one can be loaded.
+ */
+function ctools_context_get_context_from_context($context, $type = 'context', $argument = NULL) {
+ ctools_include('plugins');
+ $plugin = ctools_get_context($context['name']);
+ if ($function = ctools_plugin_get_function($plugin, 'context')) {
+ // Backward compatibility: Merge old style settings into new style:
+ if (!empty($context['context_settings'])) {
+ $context += $context['context_settings'];
+ unset($context['context_settings']);
+ }
+
+ if (isset($argument) && isset($plugin['placeholder name'])) {
+ $context[$plugin['placeholder name']] = $argument;
+ }
+
+ $return = $function($type == 'requiredcontext', $context, TRUE, $plugin);
+ if ($return) {
+ $return->identifier = $context['identifier'];
+ $return->page_title = isset($context['title']) ? $context['title'] : '';
+ $return->keyword = $context['keyword'];
+
+ if (!empty($context->empty)) {
+ $context->placeholder = array(
+ 'type' => 'context',
+ 'conf' => $context,
+ );
+ }
+
+ return $return;
+ }
+ }
+}
+
+/**
+ * Retrieve a list of base contexts based upon a simple 'contexts' definition.
+ *
+ * For required contexts this will always retrieve placeholders.
+ *
+ * @param $contexts
+ * The list of contexts defined in the UI.
+ * @param $type
+ * Either 'context' or 'requiredcontext', which indicates whether the contexts
+ * are loaded from internal data or copied from an external source.
+ * @param $placeholders
+ * If true, placeholders are acceptable.
+ */
+function ctools_context_get_context_from_contexts($contexts, $type = 'context', $placeholders = FALSE) {
+ $return = array();
+ foreach ($contexts as $context) {
+ $ctext = ctools_context_get_context_from_context($context, $type);
+ if ($ctext) {
+ if ($placeholders) {
+ $ctext->placeholder = TRUE;
+ }
+ $return[ctools_context_id($context, $type)] = $ctext;
+ }
+ }
+ return $return;
+}
+
+/**
+ * Match up external contexts to our required contexts.
+ *
+ * This function is used to create a list of contexts with proper
+ * IDs based upon a list of required contexts.
+ *
+ * These contexts passed in should match the numeric positions of the
+ * required contexts. The caller must ensure this has already happened
+ * correctly as this function will not detect errors here.
+ *
+ * @param $required
+ * A list of required contexts as defined by the UI.
+ * @param $contexts
+ * A list of matching contexts as passed in from the calling system.
+ */
+function ctools_context_match_required_contexts($required, $contexts) {
+ $return = array();
+ if (!is_array($required)) {
+ return $return;
+ }
+
+ foreach ($required as $r) {
+ $context = clone(array_shift($contexts));
+ $context->identifier = $r['identifier'];
+ $context->page_title = isset($r['title']) ? $r['title'] : '';
+ $context->keyword = $r['keyword'];
+ $return[ctools_context_id($r, 'requiredcontext')] = $context;
+ }
+
+ return $return;
+}
+
+/**
+ * Load a full array of contexts for an object.
+ *
+ * Not all of the types need to be supported by this object.
+ *
+ * This function is not used to load contexts from external data, but may
+ * be used to load internal contexts and relationships. Otherwise it can also
+ * be used to generate a full set of placeholders for UI purposes.
+ *
+ * @param $object
+ * An object that contains some or all of the following variables:
+ *
+ * - requiredcontexts: A list of UI configured contexts that are required
+ * from an external source. Since these require external data, they will
+ * only be added if $placeholders is set to TRUE, and empty contexts will
+ * be created.
+ * - arguments: A list of UI configured arguments that will create contexts.
+ * Since these require external data, they will only be added if $placeholders
+ * is set to TRUE.
+ * - contexts: A list of UI configured contexts that have no external source,
+ * and are essentially hardcoded. For example, these might configure a
+ * particular node or a particular taxonomy term.
+ * - relationships: A list of UI configured contexts to be derived from other
+ * contexts that already exist from other sources. For example, these might
+ * be used to get a user object from a node via the node author relationship.
+ * @param $placeholders
+ * If TRUE, this will generate placeholder objects for types this function
+ * cannot load.
+ * @param $contexts
+ * An array of pre-existing contexts that will be part of the return value.
+ */
+function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = array()) {
+ if (!empty($object->base_contexts)) {
+ $contexts += $object->base_contexts;
+ }
+
+ if ($placeholders) {
+ // This will load empty contexts as placeholders for arguments that come
+ // from external sources. If this isn't set, it's assumed these context
+ // will already have been matched up and loaded.
+ if (!empty($object->requiredcontexts) && is_array($object->requiredcontexts)) {
+ $contexts += ctools_context_get_context_from_contexts($object->requiredcontexts, 'requiredcontext', $placeholders);
+ }
+
+ if (!empty($object->arguments) && is_array($object->arguments)) {
+ $contexts += ctools_context_get_placeholders_from_argument($object->arguments);
+ }
+ }
+
+ if (!empty($object->contexts) && is_array($object->contexts)) {
+ $contexts += ctools_context_get_context_from_contexts($object->contexts, 'context', $placeholders);
+ }
+
+ // add contexts from relationships
+ if (!empty($object->relationships) && is_array($object->relationships)) {
+ ctools_context_get_context_from_relationships($object->relationships, $contexts, $placeholders);
+ }
+
+ return $contexts;
+}
+
+/**
+ * Return the first context with a form id from a list of contexts.
+ *
+ * This function is used to figure out which contexts represents 'the form'
+ * from a list of contexts. Only one contexts can actually be 'the form' for
+ * a given page, since the @code{<form>} tag can not be embedded within
+ * itself.
+ */
+function ctools_context_get_form($contexts) {
+ if (!empty($contexts)) {
+ foreach ($contexts as $id => $context) {
+ // if a form shows its id as being a 'required context' that means the
+ // the context is external to this display and does not count.
+ if (!empty($context->form_id) && substr($id, 0, 15) != 'requiredcontext') {
+ return $context;
+ }
+ }
+ }
+}
+
+/**
+ * Replace placeholders with real contexts using data extracted from a form
+ * for the purposes of previews.
+ *
+ * @param $contexts
+ * All of the contexts, including the placeholders.
+ * @param $arguments
+ * The arguments. These will be acquired from $form_state['values'] and the
+ * keys must match the context IDs.
+ *
+ * @return
+ * A new $contexts array containing the replaced contexts. Not all contexts
+ * may be replaced if, for example, an argument was unable to be converted
+ * into a context.
+ */
+function ctools_context_replace_placeholders($contexts, $arguments) {
+ foreach ($contexts as $cid => $context) {
+ if (empty($context->empty)) {
+ continue;
+ }
+
+ $new_context = NULL;
+ switch ($context->placeholder['type']) {
+ case 'relationship':
+ $relationship = $context->placeholder['conf'];
+ if (isset($contexts[$relationship['context']])) {
+ $new_context = ctools_context_get_context_from_relationship($relationship, $contexts[$relationship['context']]);
+ }
+ break;
+ case 'argument':
+ if (isset($arguments[$cid]) && $arguments[$cid] !== '') {
+ $argument = $context->placeholder['conf'];
+ $new_context = ctools_context_get_context_from_argument($argument, $arguments[$cid]);
+ }
+ break;
+ case 'context':
+ if (!empty($arguments[$cid])) {
+ $context_info = $context->placeholder['conf'];
+ $new_context = ctools_context_get_context_from_context($context_info, 'requiredcontext', $arguments[$cid]);
+ }
+ break;
+ }
+
+ if ($new_context && empty($new_context->empty)) {
+ $contexts[$cid] = $new_context;
+ }
+ }
+
+ return $contexts;
+}
+
+/**
+ * Provide a form array for getting data to replace placeholder contexts
+ * with real data.
+ */
+function ctools_context_replace_form(&$form, $contexts) {
+ foreach ($contexts as $cid => $context) {
+ if (empty($context->empty)) {
+ continue;
+ }
+
+ // Get plugin info from the context which should have been set when the
+ // empty context was created.
+ $info = NULL;
+ $plugin = NULL;
+ $settings = NULL;
+ switch ($context->placeholder['type']) {
+ case 'argument':
+ $info = $context->placeholder['conf'];
+ $plugin = ctools_get_argument($info['name']);
+ break;
+
+ case 'context':
+ $info = $context->placeholder['conf'];
+ $plugin = ctools_get_context($info['name']);
+ break;
+ }
+
+ // Ask the plugin where the form is.
+ if ($plugin && isset($plugin['placeholder form'])) {
+ if (is_array($plugin['placeholder form'])) {
+ $form[$cid] = $plugin['placeholder form'];
+ }
+ else if (function_exists($plugin['placeholder form'])) {
+ $widget = $plugin['placeholder form']($info);
+ if ($widget) {
+ $form[$cid] = $widget;
+ }
+ }
+
+ if (!empty($form[$cid])) {
+ $form[$cid]['#title'] = t('@identifier (@keyword)', array('@keyword' => '%' . $context->keyword, '@identifier' => $context->identifier));
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Functions related to loading access control plugins
+
+/**
+ * Fetch metadata on a specific access control plugin.
+ *
+ * @param $name
+ * Name of a plugin.
+ *
+ * @return
+ * An array with information about the requested access control plugin.
+ */
+function ctools_get_access_plugin($name) {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'access', $name);
+}
+
+/**
+ * Fetch metadata for all access control plugins.
+ *
+ * @return
+ * An array of arrays with information about all available access control plugins.
+ */
+function ctools_get_access_plugins() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'access');
+}
+
+/**
+ * Fetch a list of access plugins that are available for a given list of
+ * contexts.
+ *
+ * if 'logged-in-user' is not in the list of contexts, it will be added as
+ * this is required.
+ */
+function ctools_get_relevant_access_plugins($contexts) {
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ $all_plugins = ctools_get_access_plugins();
+ $plugins = array();
+ foreach ($all_plugins as $id => $plugin) {
+ if (!empty($plugin['required context']) && !ctools_context_match_requirements($contexts, $plugin['required context'])) {
+ continue;
+ }
+ $plugins[$id] = $plugin;
+ }
+
+ return $plugins;
+}
+
+/**
+ * Create a context for the logged in user.
+ */
+function ctools_access_get_loggedin_context() {
+ $context = ctools_context_create('entity:user', array('type' => 'current'), TRUE);
+ $context->identifier = t('Logged in user');
+ $context->keyword = 'viewer';
+ $context->id = 0;
+
+ return $context;
+}
+
+/**
+ * Get a summary of an access plugin's settings.
+ */
+function ctools_access_summary($plugin, $contexts, $test) {
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ $description = '';
+ if ($function = ctools_plugin_get_function($plugin, 'summary')) {
+ $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
+ $context = isset($test['context']) ? $test['context'] : array();
+ $description = $function($test['settings'], ctools_context_select($contexts, $required_context, $context), $plugin);
+ }
+
+ if (!empty($test['not'])) {
+ $description = "NOT ($description)";
+ }
+
+ return $description;
+}
+
+/**
+ * Get a summary of a group of access plugin's settings.
+ */
+function ctools_access_group_summary($access, $contexts) {
+ if (empty($access['plugins'])) {
+ return;
+ }
+
+ $descriptions = array();
+ foreach ($access['plugins'] as $id => $test) {
+ $plugin = ctools_get_access_plugin($test['name']);
+ $descriptions[] = ctools_access_summary($plugin, $contexts, $test);
+ }
+
+ $separator = (isset($access['logic']) && $access['logic'] == 'and') ? t(', and ') : t(', or ');
+ return implode($separator, $descriptions);
+}
+
+/**
+ * Determine if the current user has access via plugin.
+ *
+ * @param $settings
+ * An array of settings theoretically set by the user.
+ * @param $contexts
+ * An array of zero or more contexts that may be used to determine if
+ * the user has access.
+ *
+ * @return
+ * TRUE if access is granted, false if otherwise.
+ */
+function ctools_access($settings, $contexts = array()) {
+ if (empty($settings['plugins'])) {
+ return TRUE;
+ }
+
+ if (!isset($settings['logic'])) {
+ $settings['logic'] = 'and';
+ }
+
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ foreach ($settings['plugins'] as $test) {
+ $pass = FALSE;
+ $plugin = ctools_get_access_plugin($test['name']);
+ if ($plugin && $function = ctools_plugin_get_function($plugin, 'callback')) {
+ // Do we need just some contexts or all of them?
+ if (!empty($plugin['all contexts'])) {
+ $test_contexts = $contexts;
+ }
+ else {
+ $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
+ $context = isset($test['context']) ? $test['context'] : array();
+ $test_contexts = ctools_context_select($contexts, $required_context, $context);
+ }
+
+ $pass = $function($test['settings'], $test_contexts, $plugin);
+ if (!empty($test['not'])) {
+ $pass = !$pass;
+ }
+ }
+
+ if ($pass && $settings['logic'] == 'or') {
+ // Pass if 'or' and this rule passed.
+ return TRUE;
+ }
+ else if (!$pass && $settings['logic'] == 'and') {
+ // Fail if 'and' and this rule failed.
+ return FALSE;
+ }
+ }
+
+ // Return TRUE if logic was and, meaning all rules passed.
+ // Return FALSE if logic was or, meaning no rule passed.
+ return $settings['logic'] == 'and';
+}
+
+/**
+ * Create default settings for a new access plugin.
+ *
+ * @param $plugin
+ * The access plugin being used.
+ *
+ * @return
+ * A default configured test that should be placed in $access['plugins'];
+ */
+function ctools_access_new_test($plugin) {
+ $test = array(
+ 'name' => $plugin['name'],
+ 'settings' => array(),
+ );
+
+ // Set up required context defaults.
+ if (isset($plugin['required context'])) {
+ if (is_object($plugin['required context'])) {
+ $test['context'] = '';
+ }
+ else {
+ $test['context'] = array();
+ foreach ($plugin['required context'] as $required) {
+ $test['context'][] = '';
+ }
+ }
+ }
+
+
+ $default = NULL;
+ if (isset($plugin['default'])) {
+ $default = $plugin['default'];
+ }
+ elseif (isset($plugin['defaults'])) {
+ $default = $plugin['defaults'];
+ }
+
+ // Setup plugin defaults.
+ if (isset($default)) {
+ if (is_array($default)) {
+ $test['settings'] = $default;
+ }
+ else if (function_exists($default)) {
+ $test['settings'] = $default();
+ }
+ else {
+ $test['settings'] = array();
+ }
+ }
+
+ return $test;
+}
+
+/**
+ * Apply restrictions to contexts based upon the access control configured.
+ *
+ * These restrictions allow the UI to not show content that may not
+ * be relevant to all types of a particular context.
+ */
+function ctools_access_add_restrictions($settings, $contexts) {
+ if (empty($settings['plugins'])) {
+ return;
+ }
+
+ if (!isset($settings['logic'])) {
+ $settings['logic'] = 'and';
+ }
+
+ // We're not going to try to figure out restrictions on the or.
+ if ($settings['logic'] == 'or' && count($settings['plugins']) > 1) {
+ return;
+ }
+
+ foreach ($settings['plugins'] as $test) {
+ $plugin = ctools_get_access_plugin($test['name']);
+ if ($plugin && $function = ctools_plugin_get_function($plugin, 'restrictions')) {
+ $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
+ $context = isset($test['context']) ? $test['context'] : array();
+ $contexts = ctools_context_select($contexts, $required_context, $context);
+ if ($contexts !== FALSE) {
+ $function($test['settings'], $contexts);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/context.menu.inc b/sites/all/modules/ctools/includes/context.menu.inc
new file mode 100644
index 000000000..ee227cb74
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context.menu.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains menu item registration for the context tool.
+ *
+ * The menu items registered are AJAX callbacks for the context configuration
+ * popups. They are kept separately for organizational purposes.
+ */
+
+function ctools_context_menu(&$items) {
+ $base = array(
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/context-admin.inc',
+ 'theme callback' => 'ajax_base_page_theme',
+ );
+ $items['ctools/context/ajax/add'] = array(
+ 'page callback' => 'ctools_context_ajax_item_add',
+ ) + $base;
+ $items['ctools/context/ajax/configure'] = array(
+ 'page callback' => 'ctools_context_ajax_item_edit',
+ ) + $base;
+ $items['ctools/context/ajax/delete'] = array(
+ 'page callback' => 'ctools_context_ajax_item_delete',
+ ) + $base;
+
+ // For the access system
+ $base['file'] = 'includes/context-access-admin.inc';
+ $items['ctools/context/ajax/access/add'] = array(
+ 'page callback' => 'ctools_access_ajax_add',
+ ) + $base;
+ $items['ctools/context/ajax/access/configure'] = array(
+ 'page callback' => 'ctools_access_ajax_edit',
+ ) + $base;
+ $items['ctools/context/ajax/access/delete'] = array(
+ 'page callback' => 'ctools_access_ajax_delete',
+ ) + $base;
+
+}
diff --git a/sites/all/modules/ctools/includes/context.plugin-type.inc b/sites/all/modules/ctools/includes/context.plugin-type.inc
new file mode 100644
index 000000000..866def2e0
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context.plugin-type.inc
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains plugin type registration information for the context tool.
+ *
+ * Don't actually need to declare anything for these plugin types right now,
+ * apart from the fact that they exist. So, an empty array.
+ */
+
+function ctools_context_plugin_type(&$items) {
+ $items['contexts'] = array(
+ 'child plugins' => TRUE,
+ );
+ $items['arguments'] = array(
+ 'child plugins' => TRUE,
+ );
+ $items['relationships'] = array(
+ 'child plugins' => TRUE,
+ );
+ $items['access'] = array(
+ 'child plugins' => TRUE,
+ );
+}
diff --git a/sites/all/modules/ctools/includes/context.theme.inc b/sites/all/modules/ctools/includes/context.theme.inc
new file mode 100644
index 000000000..8f660b8c0
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context.theme.inc
@@ -0,0 +1,344 @@
+<?php
+
+/**
+ * @file
+ * Contains theme registry and theme implementations for the context tool.
+ */
+
+/**
+ * Implements hook_theme()
+ */
+function ctools_context_theme(&$theme) {
+ $theme['ctools_context_list'] = array(
+ 'variables' => array('object' => NULL),
+ 'file' => 'includes/context.theme.inc',
+ );
+ $theme['ctools_context_list_no_table'] = array(
+ 'variables' => array('object' => NULL),
+ 'file' => 'includes/context.theme.inc',
+ );
+ $theme['ctools_context_item_form'] = array(
+ 'render element' => 'form',
+// 'variables' => array('form' => NULL),
+ 'file' => 'includes/context.theme.inc',
+ );
+ $theme['ctools_context_item_row'] = array(
+ 'variables' => array('type' => NULL, 'form' => NULL, 'position' => NULL, 'count' => NULL, 'with_tr' => TRUE),
+ 'file' => 'includes/context.theme.inc',
+ );
+
+ // For the access plugin
+ $theme['ctools_access_admin_add'] = array(
+ 'render element' => 'form',
+ 'file' => 'includes/context-access-admin.inc',
+ );
+}
+
+/**
+ * Theme the form item for the context entry.
+ */
+function theme_ctools_context_item_row($vars) {
+ $type = $vars['type'];
+ $form = $vars['form'];
+ $position = $vars['position'];
+ $count = $vars['count'];
+ $with_tr = $vars['with_tr'];
+ $output = '<td class="title">&nbsp;' . render($form['title']) . '</td>';
+ if (!empty($form['position'])) {
+ $output .= '<td class="position">&nbsp;' . render($form['position']) . '</td>';
+ }
+ $output .= '<td class="operation">' . render($form['settings']);
+ $output .= render($form['remove']) . '</td>';
+
+ if ($with_tr) {
+ $output = '<tr id="' . $type . '-row-' . $position . '" class="draggable ' . $type . '-row ' . ($count % 2 ? 'even' : 'odd') . '">' . $output . '</tr>';
+ }
+ return $output;
+}
+
+/**
+ * Display the context item.
+ */
+function theme_ctools_context_item_form($vars) {
+ $form = $vars['form'];
+
+ $output = '';
+ $type = $form['#ctools_context_type'];
+ $module = $form['#ctools_context_module'];
+ $cache_key = $form['#cache_key'];
+
+ $type_info = ctools_context_info($type);
+
+ if (!empty($form[$type]) && empty($form['#only_buttons'])) {
+ $count = 0;
+ $rows = '';
+ foreach (array_keys($form[$type]) as $id) {
+ if (!is_numeric($id)) {
+ continue;
+ }
+ $theme_vars = array();
+ $theme_vars['type'] = $type;
+ $theme_vars['form'] = $form[$type][$id];
+ $theme_vars['position'] = $id;
+ $theme_vars['count'] = $count++;
+ $rows .= theme('ctools_context_item_row', $theme_vars);
+ }
+
+ $output .= '<table id="' . $type . '-table">';
+ $output .= '<thead>';
+ $output .= '<tr>';
+ $output .= '<th class="title">' . $type_info['title'] . '</th>';
+ if (!empty($type_info['sortable']) && $count) {
+ $output .= '<th class="position">' . t('Weight') . '</th>';
+ }
+ $output .= '<th class="operation">' . t('Operation') . '</th>';
+ $output .= '</tr>';
+ $output .= '</thead>';
+ $output .= '<tbody>';
+
+ $output .= $rows;
+
+ $output .= '</tbody>';
+ $output .= '</table>';
+ }
+
+ if (!empty($form['buttons'])) {
+ // Display the add context item.
+ $row = array();
+ $row[] = array('data' => render($form['buttons'][$type]['item']), 'class' => array('title'));
+ $row[] = array('data' => render($form['buttons'][$type]['add']), 'class' => array('add'), 'width' => "60%");
+ $output .= '<div class="buttons">';
+ $output .= render($form['buttons'][$type]);
+ $theme_vars = array();
+ $theme_vars['header'] = array();
+ $theme_vars['rows'] = array($row);
+ $theme_vars['attributes'] = array('id' => $type . '-add-table');
+ $output .= theme('table', $theme_vars);
+ $output .= '</div>';
+ }
+ if (!empty($form['description'])) {
+ $output .= render($form['description']);
+ }
+
+ if (!empty($type_info['sortable'])) {
+ drupal_add_tabledrag($type . '-table', 'order', 'sibling', 'drag-position');
+ }
+
+ return $output;
+}
+
+/**
+ * Create a visible list of all the contexts available on an object.
+ * Assumes arguments, relationships and context objects.
+ *
+ * Contexts must be preloaded.
+ */
+function theme_ctools_context_list($vars) {
+ $object = $vars['object'];
+ $header = $vars['header'];
+ $description = (!empty($vars['description'])) ? $vars['description'] : NULL;
+ $titles = array();
+ $output = '';
+ $count = 1;
+
+ $contexts = ctools_context_load_contexts($object);
+
+ // Describe 'built in' contexts.
+ if (!empty($object->base_contexts)) {
+ foreach ($object->base_contexts as $id => $context) {
+ $output .= '<tr>';
+ $output .= '<td valign="top"><em>' . t('Built in context') . '</em></td>';
+ $desc = check_plain($context->identifier);
+ if (isset($context->keyword)) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $context->keyword));
+ foreach (ctools_context_get_converters('%' . $context->keyword . ':', $context) as $keyword => $title) {
+ $desc .= '<br />' . t('@keyword --&gt; @title', array('@keyword' => $keyword, '@title' => $title));
+ }
+ $desc .= '</div>';
+
+ }
+ if (isset($context->description)) {
+ $desc .= '<div class="description">' . filter_xss_admin($context->description) . '</div>';
+ }
+ $output .= '<td>' . $desc . '</td>';
+ $output .= '</tr>';
+ $titles[$id] = $context->identifier;
+ }
+ }
+
+ // First, make a list of arguments. Arguments are pretty simple.
+ if (!empty($object->arguments)) {
+ foreach ($object->arguments as $argument) {
+ $output .= '<tr>';
+ $output .= '<td valign="top"><em>' . t('Argument @count', array('@count' => $count)) . '</em></td>';
+ $desc = check_plain($argument['identifier']);
+ if (isset($argument['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $argument['keyword']));
+ if (isset($contexts[ctools_context_id($argument, 'argument')])) {
+ foreach (ctools_context_get_converters('%' . $argument['keyword'] . ':', $contexts[ctools_context_id($argument, 'argument')]) as $keyword => $title) {
+ $desc .= '<br />' . t('@keyword --&gt; @title', array('@keyword' => $keyword, '@title' => $title));
+ }
+ }
+ $desc .= '</div>';
+ }
+ $output .= '<td>' . $desc . '</td>';
+ $output .= '</tr>';
+ $titles[ctools_context_id($argument, 'argument')] = $argument['identifier'];
+ $count++;
+ }
+ }
+
+ $count = 1;
+ // Then, make a nice list of contexts.
+ if (!empty($object->contexts)) {
+ foreach ($object->contexts as $context) {
+ $output .= '<tr>';
+ $output .= '<td valign="top"><em>' . t('Context @count', array('@count' => $count)) . '</em></td>';
+ $desc = check_plain($context['identifier']);
+ if (isset($context['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $context['keyword']));
+ foreach (ctools_context_get_converters('%' . $context['keyword'] . ':', $contexts[ctools_context_id($context, 'context')]) as $keyword => $title) {
+ $desc .= '<br />' . t('@keyword --&gt; @title', array('@keyword' => $keyword, '@title' => $title));
+ }
+ $desc .= '</div>';
+ }
+ $output .= '<td>' . $desc . '</td>';
+ $output .= '</tr>';
+ $titles[ctools_context_id($context)] = $context['identifier'];
+ $count++;
+ }
+ }
+
+ // And relationships
+ if (!empty($object->relationships)) {
+ foreach ($object->relationships as $relationship) {
+ $output .= '<tr>';
+ if (is_array($relationship['context'])) {
+ $rtitles = array();
+ foreach ($relationship['context'] as $cid) {
+ $rtitles[$cid] = $titles[$cid];
+ }
+ $title = implode(' + ', $rtitles);
+ }
+ else {
+ $title = $titles[$relationship['context']];
+ }
+ $output .= '<td valign="top"><em>' . t('From "@title"', array('@title' => $title)) . '</em></td>';
+ $desc = check_plain($relationship['identifier']);
+ if (isset($relationship['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $relationship['keyword']));
+ foreach (ctools_context_get_converters('%' . $relationship['keyword'] . ':', $contexts[ctools_context_id($relationship, 'relationship')]) as $keyword => $title) {
+ $desc .= '<br />' . t('@keyword --&gt; @title', array('@keyword' => $keyword, '@title' => $title));
+ }
+ $desc .= '</div>';
+ }
+ $output .= '<td>' . $desc . '</td>';
+ $output .= '</tr>';
+ $titles[ctools_context_id($relationship, 'relationship')] = $relationship['identifier'];
+ $count++;
+ }
+ }
+
+ $head = '';
+ if ($header) {
+ if ($description) {
+ $header .= '<div class="description">' . $description . '</div>';
+ }
+ $head .= '<thead><tr>';
+ $head .= '<th colspan="2">' . $header . '</th>';
+ $head .= '</tr></thead>';
+ }
+
+ return $output ? "<table>$head<tbody>$output</tbody></table>\n" : "<table>$head</table>\n";
+}
+
+/**
+ * ctools_context_list() but not in a table format because tabledrag
+ * won't let us have tables within tables and still drag.
+ */
+function theme_ctools_context_list_no_table($vars) {
+ $object = $vars['object'];
+ ctools_add_css('context');
+ $titles = array();
+ $output = '';
+ $count = 1;
+ // Describe 'built in' contexts.
+ if (!empty($object->base_contexts)) {
+ foreach ($object->base_contexts as $id => $context) {
+ $output .= '<div class="ctools-context-holder clearfix">';
+ $output .= '<div class="ctools-context-title">' . t('Built in context') . '</div>';
+ $desc = check_plain($context->identifier);
+ if (isset($context->keyword)) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $context->keyword)) . '</div>';
+ }
+ if (isset($context->description)) {
+ $desc .= '<div class="description">' . filter_xss_admin($context->description) . '</div>';
+ }
+ $output .= '<div class="ctools-context-content">' . $desc . '</div>';
+ $output .= '</div>';
+ $titles[$id] = $context->identifier;
+ $count++;
+ }
+ }
+
+ // First, make a list of arguments. Arguments are pretty simple.
+ if (!empty($object->arguments)) {
+ foreach ($object->arguments as $argument) {
+ $output .= '<div class="ctools-context-holder clearfix">';
+ $output .= '<div class="ctools-context-title">' . t('Argument @count', array('@count' => $count)) . '</div>';
+ $desc = check_plain($argument['identifier']);
+ if (isset($argument['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $argument['keyword'])) . '</div>';
+ }
+ $output .= '<div class="ctools-context-content">' . $desc . '</div>';
+ $output .= '</div>';
+ $titles[ctools_context_id($argument, 'argument')] = $argument['identifier'];
+ $count++;
+ }
+ }
+ $count = 1;
+ // Then, make a nice list of contexts.
+ if (!empty($object->contexts)) {
+ foreach ($object->contexts as $context) {
+ $output .= '<div class="ctools-context-holder clearfix">';
+ $output .= '<div class="ctools-context-title">' . t('Context @count', array('@count' => $count)) . '</div>';
+ $desc = check_plain($context['identifier']);
+ if (isset($context['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $context['keyword'])) . '</div>';
+ }
+ $output .= '<div class="ctools-context-content">' . $desc . '</div>';
+ $output .= '</div>';
+ $titles[ctools_context_id($context)] = $context['identifier'];
+ $count++;
+ }
+ }
+ // And relationships
+ if (!empty($object->relationships)) {
+ foreach ($object->relationships as $relationship) {
+ $output .= '<div class="ctools-context-holder clearfix">';
+ if (is_array($relationship['context'])) {
+ $rtitles = array();
+ foreach ($relationship['context'] as $cid) {
+ $rtitles[$cid] = $titles[$cid];
+ }
+ $title = implode(' + ', $rtitles);
+ }
+ else {
+ $title = $titles[$relationship['context']];
+ }
+
+ $output .= '<div class="ctools-context-title">' . t('From "@title"', array('@title' => $title)) . '</div>';
+ $desc = check_plain($relationship['identifier']);
+ if (isset($relationship['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $relationship['keyword'])) . '</div>';
+ }
+ $output .= '<div class="ctools-context-content">' . $desc . '</div>';
+ $output .= '</div>';
+ $titles[ctools_context_id($relationship, 'relationship')] = $relationship['identifier'];
+ $count++;
+ }
+ }
+
+ return $output;
+}
+
diff --git a/sites/all/modules/ctools/includes/css-cache.inc b/sites/all/modules/ctools/includes/css-cache.inc
new file mode 100644
index 000000000..d88160b5b
--- /dev/null
+++ b/sites/all/modules/ctools/includes/css-cache.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Custom cache implementation for the CTools CSS cache.
+ */
+
+class CToolsCssCache implements DrupalCacheInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear($cid = NULL, $wildcard = FALSE) {
+ // Only clear the caches if the wildcard is set, this ensures that the cache
+ // is only cleared when the full caches are cleared manually (eg by invoking
+ // drupal_flush_all_caches()), and not on a cron run.
+ // @see drupal_flush_all_caches()
+ // @see system_cron()
+ if ($wildcard) {
+ ctools_include('css');
+ ctools_css_flush_caches();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($cid) {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMultiple(&$cids) {
+ return array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isEmpty() {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($cid, $data, $expire = CACHE_PERMANENT) {
+ }
+
+}
diff --git a/sites/all/modules/ctools/includes/css.inc b/sites/all/modules/ctools/includes/css.inc
new file mode 100644
index 000000000..8cf5ed407
--- /dev/null
+++ b/sites/all/modules/ctools/includes/css.inc
@@ -0,0 +1,575 @@
+<?php
+
+/*
+ * @file
+ * CSS filtering functions. Contains a disassembler, filter, compressor, and
+ * decompressor.
+ *
+ * The general usage of this tool is:
+ *
+ * To simply filter CSS:
+ * @code
+ * $filtered_css = ctools_css_filter($css, TRUE);
+ * @endcode
+ *
+ * In the above, if the second argument is TRUE, the returned CSS will
+ * be compressed. Otherwise it will be returned in a well formatted
+ * syntax.
+ *
+ * To cache unfiltered CSS in a file, which will be filtered:
+ *
+ * @code
+ * $filename = ctools_css_cache($css, TRUE);
+ * @endcode
+ *
+ * In the above, if the second argument is FALSE, the CSS will not be filtered.
+ *
+ * This file will be cached within the Drupal files system. This system cannot
+ * detect when this file changes, so it is YOUR responsibility to remove and
+ * re-cache this file when the CSS is changed. Your system should also contain
+ * a backup method of re-generating the CSS cache in case it is removed, so
+ * that it is easy to force a re-cache by simply deleting the contents of the
+ * directory.
+ *
+ * Finally, if for some reason your application cannot store the filename
+ * (which is true of Panels where the style can't force the display to
+ * resave unconditionally) you can use the ctools storage mechanism. You
+ * simply have to come up with a unique Id:
+ *
+ * @code
+ * $filename = ctools_css_store($id, $css, TRUE);
+ * @endcode
+ *
+ * Then later on:
+ * @code
+ * $filename = ctools_css_retrieve($id);
+ * drupal_add_css($filename);
+ * @endcode
+ *
+ * The CSS that was generated will be stored in the database, so even if the
+ * file was removed the cached CSS will be used. If the CSS cache is
+ * cleared you may be required to regenerate your CSS. This will normally
+ * only be cleared by an administrator operation, not during normal usage.
+ *
+ * You may remove your stored CSS this way:
+ *
+ * @code
+ * ctools_css_clear($id);
+ * @endcode
+ */
+
+/**
+ * Store CSS with a given id and return the filename to use.
+ *
+ * This function associates a piece of CSS with an id, and stores the
+ * cached filename and the actual CSS for later use with
+ * ctools_css_retrieve.
+ */
+function ctools_css_store($id, $css, $filter = TRUE) {
+ $filename = db_query('SELECT filename FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchField();
+ if ($filename && file_exists($filename)) {
+ file_unmanaged_delete($filename);
+ }
+ // Remove any previous records.
+ db_delete('ctools_css_cache')
+ ->condition('cid', $id)
+ ->execute();
+
+ $filename = ctools_css_cache($css, $filter);
+
+ db_merge('ctools_css_cache')
+ ->key(array('cid' => $id))
+ ->fields(array(
+ 'filename' => $filename,
+ 'css' => $css,
+ 'filter' => intval($filter),
+ ))
+ ->execute();
+
+ return $filename;
+}
+
+/**
+ * Retrieve a filename associated with an id of previously cached CSS.
+ *
+ * This will ensure the file still exists and, if not, create it.
+ */
+function ctools_css_retrieve($id) {
+ $cache = db_query('SELECT * FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchObject();
+ if (!$cache) {
+ return;
+ }
+
+ if (!file_exists($cache->filename)) {
+ $filename = ctools_css_cache($cache->css, $cache->filter);
+ if ($filename != $cache->filename) {
+ db_update('ctools_css_cache')
+ ->fields(array('filename' => $filename))
+ ->condition('cid', $id)
+ ->execute();
+ $cache->filename = $filename;
+ }
+ }
+
+ return $cache->filename;
+}
+
+/**
+ * Remove stored CSS and any associated file.
+ */
+function ctools_css_clear($id) {
+ $cache = db_query('SELECT * FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchObject();
+ if (!$cache) {
+ return;
+ }
+
+ if (file_exists($cache->filename)) {
+ file_unmanaged_delete($cache->filename);
+ // If we remove an existing file, there may be cached pages that refer
+ // to it. We must get rid of them: FIXME same format in D7?
+ cache_clear_all();
+ }
+
+ db_delete('ctools_css_cache')
+ ->condition('cid', $id)
+ ->execute();
+}
+
+/**
+ * Write a chunk of CSS to a temporary cache file and return the file name.
+ *
+ * This function optionally filters the CSS (always compressed, if so) and
+ * generates a unique filename based upon md5. It returns that filename that
+ * can be used with drupal_add_css(). Note that as a cache file, technically
+ * this file is volatile so it should be checked before it is used to ensure
+ * that it exists.
+ *
+ * You can use file_exists() to test for the file and file_delete() to remove
+ * it if it needs to be cleared.
+ *
+ * @param $css
+ * A chunk of well-formed CSS text to cache.
+ * @param $filter
+ * If TRUE the css will be filtered. If FALSE the text will be cached
+ * as-is.
+ *
+ * @return $filename
+ * The filename the CSS will be cached in.
+ */
+function ctools_css_cache($css, $filter = TRUE) {
+ if ($filter) {
+ $css = ctools_css_filter($css);
+ }
+
+ // Create the css/ within the files folder.
+ $path = 'public://ctools/css';
+ if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+// if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
+ drupal_set_message(t('Unable to create CTools CSS cache directory. Check the permissions on your files directory.'), 'error');
+ return;
+ }
+
+ // @todo Is this slow? Does it matter if it is?
+ $filename = $path . '/' . md5($css) . '.css';
+
+ // Generally md5 is considered unique enough to sign file downloads.
+ // So this replaces already existing files based on the assumption that two
+ // files with the same hash are identical content wise.
+ // If we rename, the cache folder can potentially fill up with thousands of
+ // files with the same content.
+ $filename = file_unmanaged_save_data($css, $filename, FILE_EXISTS_REPLACE);
+
+ return $filename;
+}
+
+/**
+ * Filter a chunk of CSS text.
+ *
+ * This function disassembles the CSS into a raw format that makes it easier
+ * for our tool to work, then runs it through the filter and reassembles it.
+ * If you find that you want the raw data for some reason or another, you
+ * can use the disassemble/assemble functions yourself.
+ *
+ * @param $css
+ * The CSS text to filter.
+ * @param $compressed
+ * If true, generate compressed output; if false, generate pretty output.
+ * Defaults to TRUE.
+ */
+function ctools_css_filter($css, $compressed = TRUE) {
+ $css_data = ctools_css_disassemble($css);
+
+ // Note: By using this function yourself you can control the allowed
+ // properties and values list.
+ $filtered = ctools_css_filter_css_data($css_data);
+
+ return $compressed ? ctools_css_compress($filtered) : ctools_css_assemble($filtered);
+}
+
+/**
+ * Re-assemble a css string and format it nicely.
+ *
+ * @param array $css_data
+ * An array of css data, as produced by @see ctools_css_disassemble()
+ * disassembler and the @see ctools_css_filter_css_data() filter.
+ *
+ * @return string $css
+ * css optimized for human viewing.
+ */
+function ctools_css_assemble($css_data) {
+ // Initialize the output.
+ $css = '';
+ // Iterate through all the statements.
+ foreach ($css_data as $selector_str => $declaration) {
+ // Add the selectors, separating them with commas and line feeds.
+ $css .= strpos($selector_str, ',') === FALSE ? $selector_str : str_replace(", ", ",\n", $selector_str);
+ // Add the opening curly brace.
+ $css .= " {\n";
+ // Iterate through all the declarations.
+ foreach ($declaration as $property => $value) {
+ $css .= " " . $property . ": " . $value . ";\n";
+ }
+ // Add the closing curly brace.
+ $css .= "}\n\n";
+ }
+ // Return the output.
+ return $css;
+}
+
+/**
+ * Compress css data (filter it first!) to optimize for use on view.
+ *
+ * @param array $css_data
+ * An array of css data, as produced by @see ctools_css_disassemble()
+ * disassembler and the @see ctools_css_filter_css_data() filter.
+ *
+ * @return string $css
+ * css optimized for use.
+ */
+function ctools_css_compress($css_data) {
+ // Initialize the output.
+ $css = '';
+ // Iterate through all the statements.
+ foreach ($css_data as $selector_str => $declaration) {
+ if (empty($declaration)) {
+ // Skip this statement if filtering removed all parts of the declaration.
+ continue;
+ }
+ // Add the selectors, separating them with commas.
+ $css .= $selector_str;
+ // And, the opening curly brace.
+ $css .= "{";
+ // Iterate through all the statement properties.
+ foreach ($declaration as $property => $value) {
+ $css .= $property . ':' . $value . ';';
+ }
+ // Add the closing curly brace.
+ $css .= "}";
+ }
+ // Return the output.
+ return $css;
+}
+
+/**
+ * Disassemble the css string.
+ *
+ * Strip the css of irrelevant characters, invalid/malformed selectors and
+ * declarations, and otherwise prepare it for processing.
+ *
+ * @param string $css
+ * A string containing the css to be disassembled.
+ *
+ * @return array $disassembled_css
+ * An array of disassembled, slightly cleaned-up/formatted css statements.
+ */
+function ctools_css_disassemble($css) {
+ $disassembled_css = array();
+ // Remove comments.
+ $css = preg_replace("/\/\*(.*)?\*\//Usi", "", $css);
+ // Split out each statement. Match either a right curly brace or a semi-colon
+ // that precedes a left curly brace with no right curly brace separating them.
+ $statements = preg_split('/}|;(?=[^}]*{)/', $css);
+
+ // If we have any statements, parse them.
+ if (!empty($statements)) {
+ // Iterate through all of the statements.
+ foreach ($statements as $statement) {
+ // Get the selector(s) and declaration.
+ if (empty($statement) || !strpos($statement, '{')) {
+ continue;
+ }
+
+ list($selector_str, $declaration) = explode('{', $statement);
+
+ // If the selector exists, then disassemble it, check it, and regenerate
+ // the selector string.
+ $selector_str = empty($selector_str) ? FALSE : _ctools_css_disassemble_selector($selector_str);
+ if (empty($selector_str)) {
+ // No valid selectors. Bomb out and start the next item.
+ continue;
+ }
+
+ // Disassemble the declaration, check it and tuck it into an array.
+ if (!isset($disassembled_css[$selector_str])) {
+ $disassembled_css[$selector_str] = array();
+ }
+ $disassembled_css[$selector_str] += _ctools_css_disassemble_declaration($declaration);
+ }
+ }
+ return $disassembled_css;
+}
+
+function _ctools_css_disassemble_selector($selector_str) {
+ // Get all selectors individually.
+ $selectors = explode(",", trim($selector_str));
+ // Iterate through all the selectors, sanity check them and return if they
+ // pass. Note that this handles 0, 1, or more valid selectors gracefully.
+ foreach ($selectors as $key => $selector) {
+ // Replace un-needed characters and do a little cleanup.
+ $selector = preg_replace("/[\n|\t|\\|\s]+/", ' ', trim($selector));
+ // Make sure this is still a real selector after cleanup.
+ if (!empty($selector)) {
+ $selectors[$key] = $selector;
+ }
+ else {
+ // Selector is no good, so we scrap it.
+ unset($selectors[$key]);
+ }
+ }
+ // Check for malformed selectors; if found, we skip this declaration.
+ if (empty($selectors)) {
+ return FALSE;
+ }
+ return implode(', ', $selectors);
+}
+
+function _ctools_css_disassemble_declaration($declaration) {
+ $formatted_statement = array();
+ $propval_pairs = explode(";", $declaration);
+ // Make sure we actually have some properties to work with.
+ if (!empty($propval_pairs)) {
+ // Iterate through the remains and parse them.
+ foreach ($propval_pairs as $key => $propval_pair) {
+ // Check that we have a ':', otherwise it's an invalid pair.
+ if (strpos($propval_pair, ':') === FALSE) {
+ continue;
+ }
+ // Clean up the current property-value pair.
+ $propval_pair = preg_replace("/[\n|\t|\\|\s]+/", ' ', trim($propval_pair));
+ // Explode the remaining fragements some more, but clean them up first.
+ list($property, $value) = explode(':', $propval_pair, 2);
+ // If the property survived, toss it onto the stack.
+ if (!empty($property)) {
+ $formatted_statement[trim($property)] = trim($value);
+ }
+ }
+ }
+ return $formatted_statement;
+}
+
+/**
+ * Run disassembled $css through the filter.
+ *
+ * @param $css
+ * CSS code disassembled by ctools_dss_disassemble().
+ * @param $allowed_properties
+ * A list of properties that are allowed by the filter. If empty
+ * ctools_css_filter_default_allowed_properties() will provide the
+ * list.
+ * @param $allowed_values
+ * A list of values that are allowed by the filter. If empty
+ * ctools_css_filter_default_allowed_values() will provide the
+ * list.
+ *
+ * @return
+ * An array of disassembled, filtered CSS.
+ */
+function ctools_css_filter_css_data($css, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') {
+//function ctools_css_filter_css_data($css, &$filtered = NULL, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') {
+ // Retrieve the default list of allowed properties if none is provided.
+ $allowed_properties = !empty($allowed_properties) ? $allowed_properties : ctools_css_filter_default_allowed_properties();
+ // Retrieve the default list of allowed values if none is provided.
+ $allowed_values = !empty($allowed_values) ? $allowed_values : ctools_css_filter_default_allowed_values();
+ // Define allowed values regex if none is provided.
+ $allowed_values_regex = !empty($allowed_values_regex) ? $allowed_values_regex : '/(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)/';
+ // Define disallowed url() value contents, if none is provided.
+ // $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/[url|expression]\s*\(\s*[^\s)]+?\s*\)\s*/';
+ $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/(url|expression)/';
+
+ foreach ($css as $selector_str => $declaration) {
+ foreach ($declaration as $property => $value) {
+ if (!in_array($property, $allowed_properties)) {
+ // $filtered['properties'][$selector_str][$property] = $value;
+ unset($css[$selector_str][$property]);
+ continue;
+ }
+ $value = str_replace('!important', '', $value);
+ if (preg_match($disallowed_values_regex, $value) || !(in_array($value, $allowed_values) || preg_match($allowed_values_regex, $value))) {
+ // $filtered['values'][$selector_str][$property] = $value;
+ unset($css[$selector_str][$property]);
+ continue;
+ }
+ }
+ }
+ return $css;
+}
+
+/**
+ * Provide a deafult list of allowed properties by the filter.
+ */
+function ctools_css_filter_default_allowed_properties() {
+ return array(
+ 'azimuth',
+ 'background',
+ 'background-color',
+ 'background-image',
+ 'background-repeat',
+ 'background-attachment',
+ 'background-position',
+ 'border',
+ 'border-top-width',
+ 'border-right-width',
+ 'border-bottom-width',
+ 'border-left-width',
+ 'border-width',
+ 'border-top-color',
+ 'border-right-color',
+ 'border-bottom-color',
+ 'border-left-color',
+ 'border-color',
+ 'border-top-style',
+ 'border-right-style',
+ 'border-bottom-style',
+ 'border-left-style',
+ 'border-style',
+ 'border-top',
+ 'border-right',
+ 'border-bottom',
+ 'border-left',
+ 'clear',
+ 'color',
+ 'cursor',
+ 'direction',
+ 'display',
+ 'elevation',
+ 'float',
+ 'font',
+ 'font-family',
+ 'font-size',
+ 'font-style',
+ 'font-variant',
+ 'font-weight',
+ 'height',
+ 'letter-spacing',
+ 'line-height',
+ 'margin',
+ 'margin-top',
+ 'margin-right',
+ 'margin-bottom',
+ 'margin-left',
+ 'overflow',
+ 'padding',
+ 'padding-top',
+ 'padding-right',
+ 'padding-bottom',
+ 'padding-left',
+ 'pause',
+ 'pause-after',
+ 'pause-before',
+ 'pitch',
+ 'pitch-range',
+ 'richness',
+ 'speak',
+ 'speak-header',
+ 'speak-numeral',
+ 'speak-punctuation',
+ 'speech-rate',
+ 'stress',
+ 'text-align',
+ 'text-decoration',
+ 'text-indent',
+ 'text-transform',
+ 'unicode-bidi',
+ 'vertical-align',
+ 'voice-family',
+ 'volume',
+ 'white-space',
+ 'width',
+ 'fill',
+ 'fill-opacity',
+ 'fill-rule',
+ 'stroke',
+ 'stroke-width',
+ 'stroke-linecap',
+ 'stroke-linejoin',
+ 'stroke-opacity',
+ );
+}
+
+/**
+ * Provide a default list of allowed values by the filter.
+ */
+function ctools_css_filter_default_allowed_values() {
+ return array(
+ 'auto',
+ 'aqua',
+ 'black',
+ 'block',
+ 'blue',
+ 'bold',
+ 'both',
+ 'bottom',
+ 'brown',
+ 'capitalize',
+ 'center',
+ 'collapse',
+ 'dashed',
+ 'dotted',
+ 'fuchsia',
+ 'gray',
+ 'green',
+ 'italic',
+ 'inherit',
+ 'left',
+ 'lime',
+ 'lowercase',
+ 'maroon',
+ 'medium',
+ 'navy',
+ 'normal',
+ 'nowrap',
+ 'olive',
+ 'pointer',
+ 'purple',
+ 'red',
+ 'right',
+ 'solid',
+ 'silver',
+ 'teal',
+ 'top',
+ 'transparent',
+ 'underline',
+ 'uppercase',
+ 'white',
+ 'yellow',
+ );
+}
+
+/**
+ * Delegated implementation of hook_flush_caches()
+ */
+function ctools_css_flush_caches() {
+ // Remove all generated files.
+ // @see http://drupal.org/node/573292
+ // file_unmanaged_delete_recursive('public://render');
+ $filedir = file_default_scheme() . '://ctools/css';
+ if (drupal_realpath($filedir) && file_exists($filedir)) {
+ // We use the @ because it's possible that files created by the webserver
+ // cannot be deleted while using drush to clear the cache. We don't really
+ // care that much about that, to be honest, so we use the @ to suppress
+ // the error message.
+ @file_unmanaged_delete_recursive($filedir);
+ }
+
+ db_delete('ctools_css_cache')->execute();
+}
diff --git a/sites/all/modules/ctools/includes/dependent.inc b/sites/all/modules/ctools/includes/dependent.inc
new file mode 100644
index 000000000..74de91971
--- /dev/null
+++ b/sites/all/modules/ctools/includes/dependent.inc
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * @file
+ * Provide dependent checkboxes that can be easily used in forms.
+ *
+ * This system will ensure that form items are invisible if the dependency is
+ * not met. What this means is that you set the #dependency of an item to a
+ * list of form ids that must be set, and the list of values that qualify.
+ *
+ * For a simple use, setting an item to be dependent upon a select box, if
+ * any of the listed values are selected, the item will be visible. Otherwise,
+ * the item will be invisible.
+ *
+ * If dependent upon multiple items, use #dependency_count = X to set the
+ * number of items that must be set in order to make this item visible. This
+ * defaults to 1. If set to 2, then at least 2 form items in the list must
+ * have their items set for the item to become visible.
+ *
+ * When hiding checkboxes and radios you need to add their id in a div
+ * manually via #prefix and #suffix since they don't have their own id. You
+ * actually need to add TWO divs because it's the parent that gets hidden.
+ *
+ * Fieldsets can not be hidden by default. Adding '#input' => TRUE to the
+ * fieldset works around that.
+ *
+ * For radios, because they are selected a little bit differently, instead of
+ * using the CSS id, use: radio:NAME where NAME is the #name of the property.
+ * This can be quickly found by looking at the HTML of the generated form, but
+ * it is usually derived from the array which contains the item. For example,
+ * $form['menu']['type'] would have a name of menu[type]. This name is the same
+ * field that is used to determine where in $form_state['values'] you will find
+ * the value of the form.
+ *
+ * The item that is dependent on, should be set to #tree = TRUE.
+ *
+ * Usage:
+ *
+ * First, ensure this tool is loaded:
+ * @code { ctools_include('dependent'); }
+ *
+ * On any form item, add
+ * - @code '#dependency' => array('id-of-form-without-the-#' => array(list, of, values, that, make, this, gadget, visible)), @endcode
+ *
+ * A fuller example, that hides the menu title when no menu is selected:
+ * @code
+ *function ctools_dependent_example() {
+ * $form = array();
+ * $form['menu'] = array(
+ * '#type' => 'fieldset',
+ * '#title' => t('Menu settings'),
+ * '#tree' => TRUE,
+ * );
+ * $form['menu']['type'] = array(
+ * '#title' => t('Menu type'),
+ * '#type' => 'radios',
+ * '#options' => array(
+ * 'none' => t('No menu entry'),
+ * 'normal' => t('Normal menu entry'),
+ * 'tab' => t('Menu tab'),
+ * 'default tab' => t('Default menu tab'),
+ * ),
+ * '#default_value' => 'none',
+ * );
+ *
+ * $form['menu']['title'] = array(
+ * '#title' => t('Title'),
+ * '#type' => 'textfield',
+ * '#default_value' => '',
+ * '#description' => t('If set to normal or tab, enter the text to use for the menu item.'),
+ * '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab')),
+ * );
+ *
+ * return system_settings_form($form);
+ *}
+ * @endcode
+ *
+ * An example for hiding checkboxes using #prefix and #suffix:
+ * @code
+ *function ctools_dependent_example_checkbox() {
+ * $form = array();
+ * $form['object'] = array(
+ * '#type' => 'fieldset',
+ * '#title' => t('Select object type'),
+ * '#tree' => TRUE,
+ * );
+ * $form['object']['type'] = array(
+ * '#title' => t('Object type'),
+ * '#type' => 'radios',
+ * '#options' => array(
+ * 'view' => t('View'),
+ * 'node' => t('Node'),
+ * 'field' => t('Field'),
+ * 'term' => t('Term'),
+ * ),
+ * '#default_value' => 'view',
+ * );
+ *
+ * $form['object']['elements'] = array(
+ * '#title' => t('Select the elements to load from the node.'),
+ * '#type' => 'checkboxes',
+ * '#prefix' => '<div id="edit-elements-wrapper"><div id="edit-elements">',
+ * '#suffix' => '</div></div>',
+ * '#dependency' => array('radio:menu[type]' => array('node')),
+ * '#options' => array(
+ * 'body' => t('Body'),
+ * 'fields' => t('Fields'),
+ * 'taxonomy' => t('Taxonomy'),
+ * ),
+ * '#default_value' => array('body', 'fields'),
+ * );
+ *
+ * return system_settings_form($form);
+ *}
+ * @endcode
+ *
+ * Deprecated:
+ *
+ * You no longer use ctools_dependent_process(), and it should be removed
+ * completely.
+ *
+ * If you have a form element which isn't listed in ctools_dependent_element_info_alter
+ * you have to add [#pre_render'][] => 'ctools_dependent_pre_render' to your form.
+ */
+
+/**
+ * Process callback to add dependency to form items.
+ *
+ */
+function ctools_dependent_process($element, &$form_state, &$form) {
+ return $element;
+}
+
+function ctools_dependent_pre_render($element) {
+ // Preprocess only items with #dependency set.
+ if (isset($element['#dependency'])) {
+ if (!isset($element['#dependency_count'])) {
+ $element['#dependency_count'] = 1;
+ }
+ if (!isset($element['#dependency_type'])) {
+ $element['#dependency_type'] = 'hide';
+ }
+
+ $js = array(
+ 'values' => $element['#dependency'],
+ 'num' => $element['#dependency_count'],
+ 'type' => $element['#dependency_type'],
+ );
+
+ // Add a additional wrapper id around fieldsets, textareas to support depedency on it.
+ if (in_array($element['#type'], array('textarea', 'fieldset', 'text_format'))) {
+ $element['#theme_wrappers'][] = 'container';
+ $element['#attributes']['id'] = $element['#id'] . '-wrapper';
+ }
+
+ // Text formats need to unset the dependency on the textarea
+ // or it gets applied twice.
+ if ($element['#type'] == 'text_format') {
+ unset($element['value']['#dependency']);
+ }
+
+ $element['#attached']['js'][] = ctools_attach_js('dependent');
+ $options['CTools']['dependent'][$element['#id']] = $js;
+ $element['#attached']['js'][] = array('type' => 'setting', 'data' => $options);
+
+ }
+
+ return $element;
+}
+
+/**
+ * CTools alters the element_info to be able to add #process functions to
+ * every major form element to make it much more handy to use #dependency,
+ * because you don't have to add #process.
+ */
+function ctools_dependent_element_info_alter(&$type) {
+ $form_elements = array('checkbox', 'checkboxes', 'date', 'fieldset', 'item', 'machine_name', 'markup', 'radio', 'radios', 'select', 'textarea', 'textfield', 'text_format');
+ foreach ($form_elements as $element) {
+ $type[$element]['#pre_render'][] = 'ctools_dependent_pre_render';
+ }
+}
diff --git a/sites/all/modules/ctools/includes/dropbutton.theme.inc b/sites/all/modules/ctools/includes/dropbutton.theme.inc
new file mode 100644
index 000000000..fcdd5a37a
--- /dev/null
+++ b/sites/all/modules/ctools/includes/dropbutton.theme.inc
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Provide a javascript based dropbutton menu.
+ *
+ * An example are the edit/disable/delete links on the views listing page.
+ *
+ * The dropbutton menu will show up as a button with a clickable twisty pointer
+ * to the right. When clicked the button will expand, showing the list of links.
+ *
+ * The dropbutton will stay open until either the user has moved the mouse
+ * away from the box for > .5 seconds, or can be immediately closed by
+ * clicking the twisty again. The code is smart enough that if the mouse
+ * moves away and then back within the .5 second window, it will not
+ * re-close.
+ *
+ * Multiple dropbuttons can be placed per page.
+ *
+ * If only one link is passed to the theme function, the function will render
+ * a ctools-button with no twisty. The twisty is only rendered when 2 or more
+ * links are provided. The wrapping element will be classed with both
+ * ctools-button and ctools-dropbutton when a dropbutton is rendered.
+ *
+ * If the user does not have javascript enabled, the link will not appear,
+ * and instead by default the list of links will appear as a normal inline
+ * list.
+ *
+ * The menu is minimally styled by default, and to make it look different
+ * will require your own CSS. You can apply your own class to the
+ * dropbutton to help style it.
+ *
+ * The twisty is rendered as a border on a widthless and heightless element.
+ * There is no image for the twisty.
+ * The color of the twisty is the color of the link by default. To adjust the
+ * size of the twisty, adjust the border widths on .ctools-twisty. The adjust
+ * the color of the twisty, assign a new color to the .ctools-button class or
+ * assign a color to .ctools-twisty. You shouldn't need to adjust border-color.
+ *
+ * Use the theme() function to render dropbutton e.g.
+ * theme('links__ctools_dropbutton', array()) where array contains a renderable
+ * array of links.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
+ */
+function ctools_dropbutton_theme(&$items) {
+ $items['links__ctools_dropbutton'] = array(
+ 'variables' => array('title' => NULL, 'links' => NULL, 'image' => FALSE, 'class' => NULL),
+ 'file' => 'includes/dropbutton.theme.inc',
+ );
+}
+
+/**
+ * Create a dropbutton menu.
+ *
+ * @param $title
+ * The text to place in the clickable area to activate the dropbutton. This
+ * text is indented to -9999px by default.
+ * @param $links
+ * A list of links to provide within the dropbutton, suitable for use
+ * in via Drupal's theme('links').
+ * @param $image
+ * If true, the dropbutton link is an image and will not get extra decorations
+ * that a text dropbutton link will.
+ * @param $class
+ * An optional class to add to the dropbutton's container div to allow you
+ * to style a single dropbutton however you like without interfering with
+ * other dropbuttons.
+ */
+function theme_links__ctools_dropbutton($vars) {
+ // Check to see if the number of links is greater than 1;
+ // otherwise just treat this like a button.
+ if (!empty($vars['links'])) {
+ $is_drop_button = (count($vars['links']) > 1);
+
+ // Add needed files
+ if ($is_drop_button) {
+ ctools_add_js('dropbutton');
+ ctools_add_css('dropbutton');
+ }
+ ctools_add_css('button');
+
+ // Provide a unique identifier for every button on the page.
+ static $id = 0;
+ $id++;
+
+ // Wrapping div
+ $class = 'ctools-no-js';
+ $class .= ($is_drop_button) ? ' ctools-dropbutton' : '';
+ $class .= ' ctools-button';
+ if (!empty($vars['class'])) {
+ $class .= ($vars['class']) ? (' ' . implode(' ', $vars['class'])) : '';
+ }
+
+ $output = '';
+
+ $output .= '<div class="' . $class . '" id="ctools-button-' . $id . '">';
+
+ // Add a twisty if this is a dropbutton
+ if ($is_drop_button) {
+ $vars['title'] = ($vars['title'] ? check_plain($vars['title']) : t('open'));
+
+ $output .= '<div class="ctools-link">';
+ if ($vars['image']) {
+ $output .= '<a href="#" class="ctools-twisty ctools-image">' . $vars['title'] . '</a>';
+ }
+ else {
+ $output .= '<a href="#" class="ctools-twisty ctools-text">' . $vars['title'] . '</a>';
+ }
+ $output .= '</div>'; // ctools-link
+ }
+
+ // The button content
+ $output .= '<div class="ctools-content">';
+
+ // Check for attributes. theme_links expects an array().
+ $vars['attributes'] = (!empty($vars['attributes'])) ? $vars['attributes'] : array();
+
+ // Remove the inline and links classes from links if they exist.
+ // These classes are added and styled by Drupal core and mess up the default
+ // styling of any link list.
+ if (!empty($vars['attributes']['class'])) {
+ $classes = $vars['attributes']['class'];
+ foreach ($classes as $key => $class) {
+ if ($class === 'inline' || $class === 'links') {
+ unset($vars['attributes']['class'][$key]);
+ }
+ }
+ }
+
+ // Call theme_links to render the list of links.
+ $output .= theme_links(array('links' => $vars['links'], 'attributes' => $vars['attributes'], 'heading' => ''));
+ $output .= '</div>'; // ctools-content
+ $output .= '</div>'; // ctools-dropbutton
+ return $output;
+ }
+ else {
+ return '';
+ }
+}
+
diff --git a/sites/all/modules/ctools/includes/dropdown.theme.inc b/sites/all/modules/ctools/includes/dropdown.theme.inc
new file mode 100644
index 000000000..7e748f5e9
--- /dev/null
+++ b/sites/all/modules/ctools/includes/dropdown.theme.inc
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Provide a javascript based dropdown menu.
+ *
+ * An example are the dropdown settings in the panels ui, like for adding
+ * new panes.
+ *
+ * The dropdown menu will show up as a clickable link; when clicked,
+ * a small menu will slide down beneath it, showing the list of links.
+ *
+ * The dropdown will stay open until either the user has moved the mouse
+ * away from the box for > .5 seconds, or can be immediately closed by
+ * clicking the link again. The code is smart enough that if the mouse
+ * moves away and then back within the .5 second window, it will not
+ * re-close.
+ *
+ * Multiple dropdowns can be placed per page.
+ *
+ * If the user does not have javascript enabled, the link will not appear,
+ * and instead by default the list of links will appear as a normal inline
+ * list.
+ *
+ * The menu is heavily styled by default, and to make it look different
+ * will require a little bit of CSS. You can apply your own class to the
+ * dropdown to help ensure that your CSS can override the dropdown's CSS.
+ *
+ * In particular, the text, link, background and border colors may need to
+ * be changed. Please see dropdown.css for information about the existing
+ * styling.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
+ */
+function ctools_dropdown_theme(&$items) {
+ $items['ctools_dropdown'] = array(
+ 'variables' => array('title' => NULL, 'links' => NULL, 'image' => FALSE, 'class' => ''),
+ 'file' => 'includes/dropdown.theme.inc',
+ );
+}
+
+/**
+ * Create a dropdown menu.
+ *
+ * @param $title
+ * The text to place in the clickable area to activate the dropdown.
+ * @param $links
+ * A list of links to provide within the dropdown, suitable for use
+ * in via Drupal's theme('links').
+ * @param $image
+ * If true, the dropdown link is an image and will not get extra decorations
+ * that a text dropdown link will.
+ * @param $class
+ * An optional class to add to the dropdown's container div to allow you
+ * to style a single dropdown however you like without interfering with
+ * other dropdowns.
+ */
+function theme_ctools_dropdown($vars) {
+ // Provide a unique identifier for every dropdown on the page.
+ static $id = 0;
+ $id++;
+
+ $class = 'ctools-dropdown-no-js ctools-dropdown' . ($vars['class'] ? (' ' . $vars['class']) : '');
+
+ ctools_add_js('dropdown');
+ ctools_add_css('dropdown');
+
+ $output = '';
+
+ $output .= '<div class="' . $class . '" id="ctools-dropdown-' . $id . '">';
+ $output .= '<div class="ctools-dropdown-link-wrapper">';
+ if ($vars['image']) {
+ $output .= '<a href="#" class="ctools-dropdown-link ctools-dropdown-image-link">' . $vars['title'] . '</a>';
+ }
+ else {
+ $output .= '<a href="#" class="ctools-dropdown-link ctools-dropdown-text-link">' . check_plain($vars['title']) . '</a>';
+ }
+
+ $output .= '</div>'; // wrapper
+ $output .= '<div class="ctools-dropdown-container-wrapper">';
+ $output .= '<div class="ctools-dropdown-container">';
+ $output .= theme_links(array('links' => $vars['links'], 'attributes' => array(), 'heading' => ''));
+ $output .= '</div>'; // container
+ $output .= '</div>'; // container wrapper
+ $output .= '</div>'; // dropdown
+ return $output;
+}
+
diff --git a/sites/all/modules/ctools/includes/entity-access.inc b/sites/all/modules/ctools/includes/entity-access.inc
new file mode 100644
index 000000000..972cf13b0
--- /dev/null
+++ b/sites/all/modules/ctools/includes/entity-access.inc
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Provides various callbacks for the whole core module integration.
+ * This is a copy of Entity API's functionality for use when Entity API isn't
+ * Enabled, and only works on view functions.
+ */
+
+/**
+ * Core hack to include entity api-esque 'access callback' functions to core
+ * entities without needing to rely on entity api.
+ * Exception: We don't touch file entity. You must have entity API enabled to
+ * view files.
+ */
+function _ctools_entity_access(&$entity_info, $entity_type) {
+ // If the access callback is already set, don't change anything.
+ if (isset($entity_info['access callback'])) {
+ return;
+ }
+
+ switch ($entity_type) {
+ case 'node':
+ // Sad panda, we don't use Entity API, lets manually add access callbacks.
+ $entity_info['access callback'] = 'ctools_metadata_no_hook_node_access';
+ break;
+ case 'user':
+ $entity_info['access callback'] = 'ctools_metadata_user_access';
+ break;
+ case 'comment':
+ if (module_exists('comment')) {
+ $entity_info['access callback'] = 'ctools_metadata_comment_access';
+ }
+ break;
+ case 'taxonomy_term':
+ if (module_exists('taxonomy')) {
+ $entity_info['access callback'] = 'ctools_metadata_taxonomy_access';
+ }
+ break;
+ case 'taxonomy_vocabulary':
+ if (module_exists('taxonomy')) {
+ $entity_info['access callback'] = 'ctools_metadata_taxonomy_access';
+ }
+ break;
+ }
+}
+
+/**
+ * Access callback for the node entity.
+ *
+ * This function does not implement hook_node_access(), thus it may not be
+ * called ctools_metadata_node_access().
+ *
+ * @see entity_access()
+ *
+ * @param $op
+ * The operation being performed. One of 'view', 'update', 'create' or
+ * 'delete'.
+ * @param $node
+ * A node to check access for. Must be a node object. Must have nid,
+ * except in the case of 'create' operations.
+ * @param $account
+ * The user to check for. Leave it to NULL to check for the global user.
+ *
+ * @throws EntityMalformedException
+ *
+ * @return boolean
+ * TRUE if access is allowed, FALSE otherwise.
+ */
+function ctools_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) {
+ // First deal with the case where a $node is provided.
+ if (isset($node)) {
+ // If a non-default revision is given, incorporate revision access.
+ $default_revision = node_load($node->nid);
+ if ($node->vid !== $default_revision->vid) {
+ return _node_revision_access($node, $op, $account);
+ }
+ else {
+ return node_access($op, $node, $account);
+ }
+ }
+ // No node is provided. Check for access to all nodes.
+ if (user_access('bypass node access', $account)) {
+ return TRUE;
+ }
+ if (!user_access('access content', $account)) {
+ return FALSE;
+ }
+ if ($op == 'view' && node_access_view_all_nodes($account)) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the user entity.
+ */
+function ctools_metadata_user_access($op, $entity = NULL, $account = NULL, $entity_type) {
+ $account = isset($account) ? $account : $GLOBALS['user'];
+ // Grant access to the users own user account and to the anonymous one.
+ if (isset($entity) && $op != 'delete' && (($entity->uid == $account->uid && $entity->uid) || (!$entity->uid && $op == 'view'))) {
+ return TRUE;
+ }
+ if (user_access('administer users', $account) || user_access('access user profiles', $account) && $op == 'view' && $entity->status) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the comment entity.
+ */
+function ctools_metadata_comment_access($op, $entity = NULL, $account = NULL) {
+ // When determining access to a comment, if comment has an associated node,
+ // the user must be able to view the node in order to access the comment.
+ if (isset($entity->nid)) {
+ if (!node_access('view', node_load($entity->nid), $account)) {
+ return FALSE;
+ }
+ }
+
+ // Comment administrators are allowed to perform all operations on all
+ // comments.
+ if (user_access('administer comments', $account)) {
+ return TRUE;
+ }
+
+ // Unpublished comments can never be accessed by non-admins.
+ if (isset($entity->status) && $entity->status == COMMENT_NOT_PUBLISHED) {
+ return FALSE;
+ }
+
+ if (user_access('access comments', $account) && $op == 'view') {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the taxonomy entities.
+ */
+function ctools_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type) {
+ if ($entity_type == 'taxonomy_vocabulary') {
+ return user_access('administer taxonomy', $account);
+ }
+ if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') {
+ return TRUE;
+ }
+ return FALSE;
+}
diff --git a/sites/all/modules/ctools/includes/export-ui.inc b/sites/all/modules/ctools/includes/export-ui.inc
new file mode 100644
index 000000000..16e57d6ed
--- /dev/null
+++ b/sites/all/modules/ctools/includes/export-ui.inc
@@ -0,0 +1,475 @@
+<?php
+
+/**
+ * @file
+ * Provide a tool for creating UIs for exportable objects.
+ *
+ * See Advanced Help for documentation.
+ */
+
+/**
+ * Process an export-ui plugin to provide it with defaults.
+ */
+function ctools_export_ui_process(&$plugin, $info) {
+ ctools_include('export');
+
+ $plugin += array(
+ 'has menu' => TRUE,
+ 'title' => $plugin['name'],
+ 'export' => array(),
+ 'allowed operations' => array(),
+ 'menu' => array(),
+ 'redirect' => array(),
+ 'form' => array(),
+ 'strings' => array(),
+ 'list' => NULL,
+ 'access' => 'administer site configuration',
+ );
+
+ // Provide CRUD access defaults based on the base 'access' setting:
+ $plugin += array(
+ 'create access' => $plugin['access'],
+ 'delete access' => $plugin['access'],
+ );
+
+ if (empty($plugin['has menu'])) {
+ return;
+ }
+
+ // The following keys are required and the plugin cannot be processed
+ // without them.
+ $keys = array(
+ 'title singular',
+ 'title plural',
+ 'title singular proper',
+ 'title plural proper',
+ 'schema',
+ );
+
+ foreach ($keys as $key) {
+ if (empty($plugin[$key])) {
+ drupal_set_message(t('The plugin definition of @plugin is missing the %key key.', array('%key' => $key, '@plugin' => $plugin['name'])), 'error');
+ }
+ }
+
+ // If we're on the modules page and building a menu, there is a design flaw
+ // in Drupal core that causes modules to be installed but the schema does
+ // not become available until AFTER menu rebuild. This helps smooth that
+ // out. This is a HACK but it should work:
+ $schema = ctools_export_get_schema($plugin['schema']);
+
+ if (empty($schema)) {
+ // If we're updating the schema may not have been read yet, so don't report this error in that case.
+ if (!defined('MAINTENANCE_MODE')) {
+ drupal_set_message(t('The plugin definition of @plugin cannot locate schema %schema.', array('%schema' => $plugin['schema'], '@plugin' => $plugin['name'])), 'error');
+ }
+ return;
+ }
+
+ if (empty($schema['export'])) {
+ drupal_set_message(t('The plugin definition of @plugin uses %schema, but it has no export section.', array('%schema' => $plugin['schema'], '@plugin' => $plugin['name'])), 'error');
+ return;
+ }
+ $plugin['export'] += $schema['export'];
+
+ $plugin['export'] += array(
+ // Add the identifier key from the schema so we don't have to call
+ // ctools_export_get_schema() just for that.
+ 'key' => $schema['export']['key'],
+ );
+
+ // Add some default fields that appear often in exports
+ // If these use different keys they can easily be specified in the
+ // $plugin.
+
+ if (empty($plugin['export']['admin_title']) && !empty($schema['fields']['admin_title'])) {
+ $plugin['export']['admin_title'] = 'admin_title';
+ }
+ if (empty($plugin['export']['admin_description']) && !empty($schema['fields']['admin_description'])) {
+ $plugin['export']['admin_description'] = 'admin_description';
+ }
+
+ // Define allowed operations, and the name of the operations.
+ $plugin['allowed operations'] += array(
+ 'edit' => array('title' => t('Edit')),
+ 'enable' => array('title' => t('Enable'), 'ajax' => TRUE, 'token' => TRUE),
+ 'disable' => array('title' => t('Disable'), 'ajax' => TRUE, 'token' => TRUE),
+ 'revert' => array('title' => t('Revert')),
+ 'delete' => array('title' => t('Delete')),
+ 'clone' => array('title' => t('Clone')),
+ 'import' => array('title' => t('Import')),
+ 'export' => array('title' => t('Export')),
+ );
+
+ $plugin['menu'] += array(
+ 'menu item' => str_replace(' ', '-', $plugin['name']),
+ 'menu prefix' => 'admin/structure',
+ 'menu title' => $plugin['title'],
+ 'menu description' => '',
+ );
+ $base_path = ctools_export_ui_plugin_base_path($plugin);
+ $prefix_count = count(explode('/', $plugin['menu']['menu prefix']));
+
+ $plugin['menu'] += array(
+ // Default menu items that should be declared.
+ 'items' => array(),
+ );
+
+ $plugin['menu']['items'] += array(
+ 'list callback' => array(),
+ 'list' => array(),
+ 'add' => array(),
+ 'edit callback' => array(),
+ 'edit' => array(),
+ );
+
+ $plugin['menu']['items']['list callback'] += array(
+ 'path' => '',
+ // Menu items are translated by the menu system.
+ // TODO: We need more flexibility in title. The title of the admin page
+ // is not necessarily the title of the object, plus we need
+ // plural, singular, proper, not proper, etc.
+ 'title' => $plugin['menu']['menu title'],
+ 'description' => $plugin['menu']['menu description'],
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'list'),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'list'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+
+ $plugin['menu']['items']['list'] += array(
+ 'path' => 'list',
+ 'title' => 'List',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'list'),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'list'),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+
+ $plugin['menu']['items']['add'] += array(
+ 'path' => 'add',
+ 'title' => 'Add',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'add'),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'add'),
+ 'type' => MENU_LOCAL_ACTION,
+ );
+
+ $plugin['menu']['items']['edit callback'] += array(
+ 'path' => 'list/%ctools_export_ui',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'edit', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'edit', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+
+ $plugin['menu']['items']['edit'] += array(
+ 'path' => 'list/%ctools_export_ui/edit',
+ 'title' => 'Edit',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'edit', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'edit', $prefix_count + 2),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+
+ if ($plugin['allowed operations']['import']) {
+ $plugin['menu']['items'] += array('import' => array());
+ $plugin['menu']['items']['import'] += array(
+ 'path' => 'import',
+ 'title' => 'Import',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'import'),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'import'),
+ 'type' => MENU_LOCAL_ACTION,
+ );
+ }
+
+ if ($plugin['allowed operations']['export']) {
+ $plugin['menu']['items'] += array('export' => array());
+ $plugin['menu']['items']['export'] += array(
+ 'path' => 'list/%ctools_export_ui/export',
+ 'title' => 'Export',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'export', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'export', $prefix_count + 2),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ }
+
+ if ($plugin['allowed operations']['revert']) {
+ $plugin['menu']['items'] += array('revert' => array());
+ $plugin['menu']['items']['revert'] += array(
+ 'path' => 'list/%ctools_export_ui/revert',
+ 'title' => 'Revert',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ // Note: Yes, 'delete' op is correct.
+ 'page arguments' => array($plugin['name'], 'delete', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'revert', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if ($plugin['allowed operations']['delete']) {
+ $plugin['menu']['items'] += array('delete' => array());
+ $plugin['menu']['items']['delete'] += array(
+ 'path' => 'list/%ctools_export_ui/delete',
+ 'title' => 'Delete',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'delete', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'delete', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if ($plugin['allowed operations']['clone']) {
+ $plugin['menu']['items'] += array('clone' => array());
+ $plugin['menu']['items']['clone'] += array(
+ 'path' => 'list/%ctools_export_ui/clone',
+ 'title' => 'Clone',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'clone', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'clone', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if ($plugin['allowed operations']['enable']) {
+ $plugin['menu']['items'] += array('enable' => array());
+ $plugin['menu']['items']['enable'] += array(
+ 'path' => 'list/%ctools_export_ui/enable',
+ 'title' => 'Enable',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'enable', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'enable', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if ($plugin['allowed operations']['disable']) {
+ $plugin['menu']['items'] += array('disable' => array());
+ $plugin['menu']['items']['disable'] += array(
+ 'path' => 'list/%ctools_export_ui/disable',
+ 'title' => 'Disable',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'disable', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'disable', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ // Define some redirects that should happen after edit/add/clone/delete operations.
+ $plugin['redirect'] += array(
+ 'add' => $base_path,
+ 'clone' => $base_path,
+ 'edit' => $base_path,
+ 'delete' => $base_path,
+ 'revert' => $base_path,
+ 'import' => $base_path,
+ );
+
+ // Define form elements.
+ $plugin['form'] += array(
+ 'settings' => function_exists($plugin['name'] . '_form') ? $plugin['name'] . '_form' : '',
+ 'validate' => function_exists($plugin['name'] . '_form_validate') ? $plugin['name'] . '_form_validate' : '',
+ 'submit' => function_exists($plugin['name'] . '_form_submit') ? $plugin['name'] . '_form_submit' : '',
+ );
+
+ // Define strings.
+
+ // For all strings, %title may be filled in at a later time via str_replace
+ // since we do not know the title now.
+ $plugin['strings'] += array(
+ 'title' => array(),
+ 'confirmation' => array(),
+ 'help' => array(),
+ 'message' => array(),
+ );
+
+ // Strings used in drupal_set_title().
+ $plugin['strings']['title'] += array(
+ 'add' => t('Add a new @plugin', array('@plugin' => $plugin['title singular'])),
+ // The "%title" will be replaced in ctools_export_ui_form(), as in this
+ // stage we dont have the specific exportable object.
+ 'edit' => t('Edit @plugin %title', array('@plugin' => $plugin['title singular'])),
+ 'clone' => t('Clone @plugin %title', array('@plugin' => $plugin['title singular'])),
+
+ 'import' => t('Import @plugin', array('@plugin' => $plugin['title singular'])),
+ 'export' => t('Export @plugin %title', array('@plugin' => $plugin['title singular'])),
+ );
+
+ // Strings used in confirmation pages.
+ $plugin['strings']['confirmation'] += array(
+ 'revert' => array(),
+ 'delete' => array(),
+ 'add' => array(),
+ 'edit' => array(),
+ );
+
+ $plugin['strings']['confirmation']['revert'] += array(
+ 'question' => t('Are you sure you want to revert %title?'),
+ 'information' => t('This action will permanently remove any customizations made to this item.'),
+ 'success' => t('The item has been reverted.'),
+ );
+
+ $plugin['strings']['confirmation']['delete'] += array(
+ 'question' => t('Are you sure you want to delete %title?'),
+ 'information' => t('This action will permanently remove this item from your database..'),
+ 'success' => t('The item has been deleted.'),
+ );
+
+ $plugin['strings']['confirmation']['add'] += array(
+ 'success' => t('%title has been created.'),
+ 'fail' => t('%title could not be created.'),
+ );
+
+ $plugin['strings']['confirmation']['edit'] += array(
+ 'success' => t('%title has been updated.'),
+ 'fail' => t('%title could not be updated.'),
+ );
+
+ // Strings used in $forms.
+ $plugin['strings']['help'] += array(
+ 'import' => t('You can import an exported definition by pasting the exported object code into the field below.'),
+ );
+
+ // Strings used in drupal_set_message().
+ $plugin['strings']['message'] += array(
+ 'enable' => t('@plugin %title was enabled.', array('@plugin' => $plugin['title singular proper'])),
+ 'disable' => t('@plugin %title was disabled.', array('@plugin' => $plugin['title singular proper'])),
+ 'no items' => t('There are no @titles to display.', array('@titles' => $plugin['title plural'])),
+ );
+}
+
+/**
+ * Get the class to handle creating a list of exportable items.
+ *
+ * If a plugin does not define a lister class at all, then the default
+ * lister class will be used.
+ *
+ * @return
+ * Either the lister class or FALSE if one could not be had.
+ */
+function ctools_export_ui_get_handler($plugin) {
+ $cache = &drupal_static(__FUNCTION__, array());
+ if (empty($cache[$plugin['name']])) {
+ // If a list class is not specified by the plugin, fall back to the
+ // default ctools_export_ui plugin instead.
+ if (empty($plugin['handler'])) {
+ $default = ctools_get_export_ui('ctools_export_ui');
+ $class = ctools_plugin_get_class($default, 'handler');
+ }
+ else {
+ $class = ctools_plugin_get_class($plugin, 'handler');
+ }
+
+ if ($class) {
+ $cache[$plugin['name']] = new $class();
+ $cache[$plugin['name']]->init($plugin);
+ }
+ }
+ return !empty($cache[$plugin['name']]) ? $cache[$plugin['name']] : FALSE;
+}
+
+/**
+ * Get the base path from a plugin.
+ *
+ * @param $plugin
+ * The plugin.
+ *
+ * @return
+ * The menu path to the plugin's list.
+ */
+function ctools_export_ui_plugin_base_path($plugin) {
+ return $plugin['menu']['menu prefix'] . '/' . $plugin['menu']['menu item'];
+}
+
+/**
+ * Get the path to a specific menu item from a plugin.
+ *
+ * @param $plugin
+ * The plugin name.
+ * @param $item_id
+ * The id in the menu items from the plugin.
+ * @param $export_key
+ * The export key of the item being edited, if it exists.
+ * @return
+ * The menu path to the plugin's list.
+ */
+function ctools_export_ui_plugin_menu_path($plugin, $item_id, $export_key = NULL) {
+ $path = $plugin['menu']['items'][$item_id]['path'];
+ if ($export_key) {
+ $path = str_replace('%ctools_export_ui', $export_key, $path);
+ }
+ return ctools_export_ui_plugin_base_path($plugin) . '/' . $path;
+}
+
+/**
+ * Helper function to include CTools plugins and get an export-ui exportable.
+ *
+ * @param $plugin_name
+ * The plugin that should be laoded.
+ */
+function ctools_get_export_ui($plugin_name) {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'export_ui', $plugin_name);
+
+}
+
+/**
+ * Helper function to include CTools plugins and get all export-ui exportables.
+ */
+function ctools_get_export_uis() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'export_ui');
+}
+
+/**
+ * Main page callback to manipulate exportables.
+ *
+ * This simply loads the object defined in the plugin and hands it off to
+ * a method based upon the name of the operation in use. This can easily
+ * be used to add more ops.
+ */
+function ctools_export_ui_switcher_page($plugin_name, $op) {
+ $args = func_get_args();
+ $js = !empty($_REQUEST['js']);
+
+ // Load the $plugin information
+ $plugin = ctools_get_export_ui($plugin_name);
+ $handler = ctools_export_ui_get_handler($plugin);
+
+ if ($handler) {
+ $method = $op . '_page';
+ if (method_exists($handler, $method)) {
+ // replace the first two arguments:
+ $args[0] = $js;
+ $args[1] = $_POST;
+ return call_user_func_array(array($handler, $method), $args);
+ }
+ }
+ else {
+ return t('Configuration error. No handler found.');
+ }
+}
diff --git a/sites/all/modules/ctools/includes/export-ui.menu.inc b/sites/all/modules/ctools/includes/export-ui.menu.inc
new file mode 100644
index 000000000..d27bf157a
--- /dev/null
+++ b/sites/all/modules/ctools/includes/export-ui.menu.inc
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Delegated implementation of hook_menu().
+ */
+function ctools_export_ui_menu(&$items) {
+ ctools_include('export-ui');
+
+ // If a menu rebuild is triggered because of module enable/disable,
+ // this might be out of date. Reset the cache.
+ ctools_include('plugins');
+ ctools_get_plugins_reset();
+
+ foreach (ctools_get_export_uis() as $plugin) {
+ // We also need to make sure that the module hasn't been disabled. During
+ // the disable process, the module's plugins still still appear.
+ if ($plugin['has menu'] && module_exists($plugin['module'])) {
+ $handler = ctools_export_ui_get_handler($plugin);
+ if ($handler) {
+ $handler->hook_menu($items);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/export-ui.plugin-type.inc b/sites/all/modules/ctools/includes/export-ui.plugin-type.inc
new file mode 100644
index 000000000..f1a751095
--- /dev/null
+++ b/sites/all/modules/ctools/includes/export-ui.plugin-type.inc
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Contains plugin type registration information for the context tool.
+ *
+ * Don't actually need to declare anything for these plugin types right now,
+ * apart from the fact that they exist. So, an empty array.
+ */
+
+function ctools_export_ui_plugin_type(&$items) {
+ $items['export_ui'] = array(
+ 'process' => array(
+ 'function' => 'ctools_export_ui_process',
+ 'file' => 'export-ui.inc',
+ 'path' => drupal_get_path('module', 'ctools') . '/includes',
+ ),
+ 'classes' => array('handler'),
+ );
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/includes/export.inc b/sites/all/modules/ctools/includes/export.inc
new file mode 100644
index 000000000..0b85c2ef9
--- /dev/null
+++ b/sites/all/modules/ctools/includes/export.inc
@@ -0,0 +1,1267 @@
+<?php
+
+/**
+ * @file
+ * Contains code to make it easier to have exportable objects.
+ *
+ * Documentation for exportable objects is contained in help/export.html
+ */
+
+/**
+ * A bit flag used to let us know if an object is in the database.
+ */
+define('EXPORT_IN_DATABASE', 0x01);
+
+/**
+ * A bit flag used to let us know if an object is a 'default' in code.
+ */
+define('EXPORT_IN_CODE', 0x02);
+
+/**
+ * @defgroup export_crud CRUD functions for export.
+ * @{
+ * export.inc supports a small number of CRUD functions that should always
+ * work for every exportable object, no matter how complicated. These
+ * functions allow complex objects to provide their own callbacks, but
+ * in most cases, the default callbacks will be used.
+ *
+ * Note that defaults are NOT set in the $schema because it is presumed
+ * that a module's personalized CRUD functions will already know which
+ * $table to use and not want to clutter up the arguments with it.
+ */
+
+/**
+ * Create a new object for the given $table.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $set_defaults
+ * If TRUE, which is the default, then default values will be retrieved
+ * from schema fields and set on the object.
+ *
+ * @return
+ * The loaded object.
+ */
+function ctools_export_crud_new($table, $set_defaults = TRUE) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['create callback']) && function_exists($export['create callback'])) {
+ return $export['create callback']($set_defaults);
+ }
+ else {
+ return ctools_export_new_object($table, $set_defaults);
+ }
+}
+
+/**
+ * Load a single exportable object.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $name
+ * The unique ID to load. The field for this ID will be specified by
+ * the export key, which normally defaults to 'name'.
+ *
+ * @return
+ * The loaded object.
+ */
+function ctools_export_crud_load($table, $name) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['load callback']) && function_exists($export['load callback'])) {
+ return $export['load callback']($name);
+ }
+ else {
+ $result = ctools_export_load_object($table, 'names', array($name));
+ if (isset($result[$name])) {
+ return $result[$name];
+ }
+ }
+}
+
+/**
+ * Load multiple exportable objects.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $names
+ * An array of unique IDs to load. The field for these IDs will be specified
+ * by the export key, which normally defaults to 'name'.
+ *
+ * @return
+ * An array of the loaded objects.
+ */
+function ctools_export_crud_load_multiple($table, array $names) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ $results = array();
+ if (!empty($export['load multiple callback']) && function_exists($export['load multiple callback'])) {
+ $results = $export['load multiple callback']($names);
+ }
+ else {
+ $results = ctools_export_load_object($table, 'names', $names);
+ }
+
+ // Ensure no empty results are returned.
+ return array_filter($results);
+}
+
+/**
+ * Load all exportable objects of a given type.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $reset
+ * If TRUE, the static cache of all objects will be flushed prior to
+ * loading all. This can be important on listing pages where items
+ * might have changed on the page load.
+ * @return
+ * An array of all loaded objects, keyed by the unique IDs of the export key.
+ */
+function ctools_export_crud_load_all($table, $reset = FALSE) {
+ $schema = ctools_export_get_schema($table);
+ if (empty($schema['export'])) {
+ return array();
+ }
+
+ $export = $schema['export'];
+
+ if ($reset) {
+ ctools_export_load_object_reset($table);
+ }
+
+ if (!empty($export['load all callback']) && function_exists($export['load all callback'])) {
+ return $export['load all callback']($reset);
+ }
+ else {
+ return ctools_export_load_object($table, 'all');
+ }
+}
+
+/**
+ * Save a single exportable object.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to save.
+ *
+ * @return
+ * Failure to write a record will return FALSE. Otherwise SAVED_NEW or
+ * SAVED_UPDATED is returned depending on the operation performed. The
+ * $object parameter contains values for any serial fields defined by the $table
+ */
+function ctools_export_crud_save($table, &$object) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['save callback']) && function_exists($export['save callback'])) {
+ return $export['save callback']($object);
+ }
+ else {
+ // Objects should have a serial primary key. If not, simply fail to write.
+ if (empty($export['primary key'])) {
+ return FALSE;
+ }
+
+ $key = $export['primary key'];
+ if ($object->export_type & EXPORT_IN_DATABASE) {
+ // Existing record.
+ $update = array($key);
+ }
+ else {
+ // New record.
+ $update = array();
+ $object->export_type = EXPORT_IN_DATABASE;
+ }
+ return drupal_write_record($table, $object, $update);
+ }
+}
+
+/**
+ * Delete a single exportable object.
+ *
+ * This only deletes from the database, which means that if an item is in
+ * code, then this is actually a revert.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to delete, or the export key.
+ */
+function ctools_export_crud_delete($table, $object) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['delete callback']) && function_exists($export['delete callback'])) {
+ return $export['delete callback']($object);
+ }
+ else {
+ // If we were sent an object, get the export key from it. Otherwise
+ // assume we were sent the export key.
+ $value = is_object($object) ? $object->{$export['key']} : $object;
+ db_delete($table)
+ ->condition($export['key'], $value)
+ ->execute();
+ }
+}
+
+/**
+ * Get the exported code of a single exportable object.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to delete, or the export key.
+ * @param $indent
+ * Any indentation to apply to the code, in case this object is embedded
+ * into another, for example.
+ *
+ * @return
+ * A string containing the executable export of the object.
+ */
+function ctools_export_crud_export($table, $object, $indent = '') {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['export callback']) && function_exists($export['export callback'])) {
+ return $export['export callback']($object, $indent);
+ }
+ else {
+ return ctools_export_object($table, $object, $indent);
+ }
+}
+
+/**
+ * Turn exported code into an object.
+ *
+ * Note: If the code is poorly formed, this could crash and there is no
+ * way to prevent this.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $code
+ * The code to eval to create the object.
+ *
+ * @return
+ * An object created from the export. This object will NOT have been saved
+ * to the database. In the case of failure, a string containing all errors
+ * that the system was able to determine.
+ */
+function ctools_export_crud_import($table, $code) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['import callback']) && function_exists($export['import callback'])) {
+ return $export['import callback']($code);
+ }
+ else {
+ ob_start();
+ eval($code);
+ ob_end_clean();
+
+ if (empty(${$export['identifier']})) {
+ $errors = ob_get_contents();
+ if (empty($errors)) {
+ $errors = t('No item found.');
+ }
+ return $errors;
+ }
+
+ $item = ${$export['identifier']};
+
+ // Set these defaults just the same way that ctools_export_new_object sets
+ // them.
+ $item->export_type = NULL;
+ $item->{$export['export type string']} = t('Local');
+
+ return $item;
+ }
+}
+
+/**
+ * Change the status of a certain object.
+ *
+ * @param $table
+ * The name of the table to use to enable a certain object. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to enable, or the machine readable name.
+ * @param $status
+ * The status, in this case, is whether or not it is 'disabled'.
+ */
+function ctools_export_crud_set_status($table, $object, $status) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['status callback']) && function_exists($export['status callback'])) {
+ $export['status callback']($object, $status);
+ }
+ else {
+ if (is_object($object)) {
+ ctools_export_set_object_status($object, $status);
+ }
+ else {
+ ctools_export_set_status($table, $object, $status);
+ }
+ }
+
+}
+
+
+/**
+ * Enable a certain object.
+ *
+ * @param $table
+ * The name of the table to use to enable a certain object. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to enable, or the machine readable name.
+ */
+function ctools_export_crud_enable($table, $object) {
+ return ctools_export_crud_set_status($table, $object, FALSE);
+}
+
+/**
+ * Disable a certain object.
+ *
+ * @param $table
+ * The name of the table to use to disable a certain object. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to disable, or the machine readable name.
+ */
+function ctools_export_crud_disable($table, $object) {
+ return ctools_export_crud_set_status($table, $object, TRUE);
+}
+
+/**
+ * @}
+ */
+
+/**
+ * Load some number of exportable objects.
+ *
+ * This function will cache the objects, load subsidiary objects if necessary,
+ * check default objects in code and properly set them up. It will cache
+ * the results so that multiple calls to load the same objects
+ * will not cause problems.
+ *
+ * It attempts to reduce, as much as possible, the number of queries
+ * involved.
+ *
+ * @param $table
+ * The name of the table to be loaded from. Data is expected to be in the
+ * schema to make all this work.
+ * @param $type
+ * A string to notify the loader what the argument is
+ * - all: load all items. This is the default. $args is unused.
+ * - names: $args will be an array of specific named objects to load.
+ * - conditions: $args will be a keyed array of conditions. The conditions
+ * must be in the schema for this table or errors will result.
+ * @param $args
+ * An array of arguments whose actual use is defined by the $type argument.
+ */
+function ctools_export_load_object($table, $type = 'all', $args = array()) {
+ $cache = &drupal_static(__FUNCTION__);
+ $cache_table_exists = &drupal_static(__FUNCTION__ . '_table_exists', array());
+ $cached_database = &drupal_static('ctools_export_load_object_all');
+
+ if (!array_key_exists($table, $cache_table_exists)) {
+ $cache_table_exists[$table] = db_table_exists($table);
+ }
+
+ $schema = ctools_export_get_schema($table);
+ if (empty($schema) || !$cache_table_exists[$table]) {
+ return array();
+ }
+
+ $export = $schema['export'];
+
+ if (!isset($cache[$table])) {
+ $cache[$table] = array();
+ }
+
+ // If fetching all and cached all, we've done so and we are finished.
+ if ($type == 'all' && !empty($cached_database[$table])) {
+ return $cache[$table];
+ }
+
+ $return = array();
+
+ // Don't load anything we've already cached.
+ if ($type == 'names' && !empty($args)) {
+ foreach ($args as $id => $name) {
+ if (isset($cache[$table][$name])) {
+ $return[$name] = $cache[$table][$name];
+ unset($args[$id]);
+ }
+ }
+
+ // If nothing left to load, return the result.
+ if (empty($args)) {
+ return $return;
+ }
+ }
+
+ // Build the query
+ $query = db_select($table, 't__0')->fields('t__0');
+ $alias_count = 1;
+ if (!empty($schema['join'])) {
+ foreach ($schema['join'] as $join_key => $join) {
+ if ($join_schema = drupal_get_schema($join['table'])) {
+ $query->join($join['table'], 't__' . $alias_count, 't__0.' . $join['left_key'] . ' = ' . 't__' . $alias_count . '.' . $join['right_key']);
+ $query->fields('t__' . $alias_count);
+ $alias_count++;
+
+ // Allow joining tables to alter the query through a callback.
+ if (isset($join['callback']) && function_exists($join['callback'])) {
+ $join['callback']($query, $schema, $join_schema);
+ }
+ }
+ }
+ }
+
+ $conditions = array();
+ $query_args = array();
+
+ // If they passed in names, add them to the query.
+ if ($type == 'names') {
+ $query->condition($export['key'], $args, 'IN');
+ }
+ else if ($type == 'conditions') {
+ foreach ($args as $key => $value) {
+ if (isset($schema['fields'][$key])) {
+ $query->condition($key, $value);
+ }
+ }
+ }
+
+ $result = $query->execute();
+
+ $status = variable_get($export['status'], array());
+ // Unpack the results of the query onto objects and cache them.
+ foreach ($result as $data) {
+ if (isset($schema['export']['object factory']) && function_exists($schema['export']['object factory'])) {
+ $object = $schema['export']['object factory']($schema, $data);
+ }
+ else {
+ $object = _ctools_export_unpack_object($schema, $data, $export['object']);
+ }
+ $object->table = $table;
+ $object->{$export['export type string']} = t('Normal');
+ $object->export_type = EXPORT_IN_DATABASE;
+ // Determine if default object is enabled or disabled.
+ if (isset($status[$object->{$export['key']}])) {
+ $object->disabled = $status[$object->{$export['key']}];
+ }
+
+ $cache[$table][$object->{$export['key']}] = $object;
+ if ($type == 'conditions') {
+ $return[$object->{$export['key']}] = $object;
+ }
+ }
+
+ // Load subrecords.
+ if (isset($export['subrecords callback']) && function_exists($export['subrecords callback'])) {
+ $export['subrecords callback']($cache[$table]);
+ }
+
+ if ($type == 'names' && !empty($args) && !empty($export['cache defaults'])) {
+ $defaults = _ctools_export_get_some_defaults($table, $export, $args);
+ }
+ else {
+ $defaults = _ctools_export_get_defaults($table, $export);
+ }
+
+ if ($defaults) {
+ foreach ($defaults as $object) {
+ if ($type == 'conditions') {
+ // if this does not match all of our conditions, skip it.
+ foreach ($args as $key => $value) {
+ if (!isset($object->$key)) {
+ continue 2;
+ }
+ if (is_array($value)) {
+ if (!in_array($object->$key, $value)) {
+ continue 2;
+ }
+ }
+ else if ($object->$key != $value) {
+ continue 2;
+ }
+ }
+ }
+ else if ($type == 'names') {
+ if (!in_array($object->{$export['key']}, $args)) {
+ continue;
+ }
+ }
+
+ // Determine if default object is enabled or disabled.
+ if (isset($status[$object->{$export['key']}])) {
+ $object->disabled = $status[$object->{$export['key']}];
+ }
+
+ if (!empty($cache[$table][$object->{$export['key']}])) {
+ $cache[$table][$object->{$export['key']}]->{$export['export type string']} = t('Overridden');
+ $cache[$table][$object->{$export['key']}]->export_type |= EXPORT_IN_CODE;
+ $cache[$table][$object->{$export['key']}]->export_module = isset($object->export_module) ? $object->export_module : NULL;
+ if ($type == 'conditions') {
+ $return[$object->{$export['key']}] = $cache[$table][$object->{$export['key']}];
+ }
+ }
+ else {
+ $object->{$export['export type string']} = t('Default');
+ $object->export_type = EXPORT_IN_CODE;
+ $object->in_code_only = TRUE;
+ $object->table = $table;
+
+ $cache[$table][$object->{$export['key']}] = $object;
+ if ($type == 'conditions') {
+ $return[$object->{$export['key']}] = $object;
+ }
+ }
+ }
+ }
+
+ // If fetching all, we've done so and we are finished.
+ if ($type == 'all') {
+ $cached_database[$table] = TRUE;
+ return $cache[$table];
+ }
+
+ if ($type == 'names') {
+ foreach ($args as $name) {
+ if (isset($cache[$table][$name])) {
+ $return[$name] = $cache[$table][$name];
+ }
+ }
+ }
+
+ // For conditions,
+ return $return;
+}
+
+/**
+ * Reset all static caches in ctools_export_load_object() or static caches for
+ * a given table in ctools_export_load_object().
+ *
+ * @param $table
+ * String that is the name of a table. If not defined, all static caches in
+ * ctools_export_load_object() will be reset.
+ */
+function ctools_export_load_object_reset($table = NULL) {
+ // Reset plugin cache to make sure new include files are picked up.
+ ctools_include('plugins');
+ ctools_get_plugins_reset();
+ if (empty($table)) {
+ drupal_static_reset('ctools_export_load_object');
+ drupal_static_reset('ctools_export_load_object_all');
+ drupal_static_reset('_ctools_export_get_defaults');
+ }
+ else {
+ $cache = &drupal_static('ctools_export_load_object');
+ $cached_database = &drupal_static('ctools_export_load_object_all');
+ $cached_defaults = &drupal_static('_ctools_export_get_defaults');
+ unset($cache[$table]);
+ unset($cached_database[$table]);
+ unset($cached_defaults[$table]);
+ }
+}
+
+/**
+ * Get the default version of an object, if it exists.
+ *
+ * This function doesn't care if an object is in the database or not and
+ * does not check. This means that export_type could appear to be incorrect,
+ * because a version could exist in the database. However, it's not
+ * incorrect for this function as it is *only* used for the default
+ * in code version.
+ */
+function ctools_get_default_object($table, $name) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!$export['default hook']) {
+ return;
+ }
+
+ // Try to load individually from cache if this cache is enabled.
+ if (!empty($export['cache defaults'])) {
+ $defaults = _ctools_export_get_some_defaults($table, $export, array($name));
+ }
+ else {
+ $defaults = _ctools_export_get_defaults($table, $export);
+ }
+
+ $status = variable_get($export['status'], array());
+
+ if (!isset($defaults[$name])) {
+ return;
+ }
+
+ $object = $defaults[$name];
+
+ // Determine if default object is enabled or disabled.
+ if (isset($status[$object->{$export['key']}])) {
+ $object->disabled = $status[$object->{$export['key']}];
+ }
+
+ $object->{$export['export type string']} = t('Default');
+ $object->export_type = EXPORT_IN_CODE;
+ $object->in_code_only = TRUE;
+
+ return $object;
+}
+
+/**
+ * Call the hook to get all default objects of the given type from the
+ * export. If configured properly, this could include loading up an API
+ * to get default objects.
+ */
+function _ctools_export_get_defaults($table, $export) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ // If defaults may be cached, first see if we can load from cache.
+ if (!isset($cache[$table]) && !empty($export['cache defaults'])) {
+ $cache[$table] = _ctools_export_get_defaults_from_cache($table, $export);
+ }
+
+ if (!isset($cache[$table])) {
+ // If we're caching, attempt to get a lock. We will wait a short time
+ // on the lock, but not too long, because it's better to just rebuild
+ // and throw away results than wait too long on a lock.
+ if (!empty($export['cache defaults'])) {
+ for ($counter = 0; !($lock = lock_acquire('ctools_export:' . $table)) && $counter > 5; $counter++) {
+ lock_wait('ctools_export:' . $table, 1);
+ ++$counter;
+ }
+ }
+
+ $cache[$table] = array();
+
+ if ($export['default hook']) {
+ if (!empty($export['api'])) {
+ ctools_include('plugins');
+ $info = ctools_plugin_api_include($export['api']['owner'], $export['api']['api'],
+ $export['api']['minimum_version'], $export['api']['current_version']);
+ $modules = array_keys($info);
+ }
+ else {
+ $modules = module_implements($export['default hook']);
+ }
+
+ foreach ($modules as $module) {
+ $function = $module . '_' . $export['default hook'];
+ if (function_exists($function)) {
+ foreach ((array) $function($export) as $name => $object) {
+ // Record the module that provides this exportable.
+ $object->export_module = $module;
+
+ if (empty($export['api'])) {
+ $cache[$table][$name] = $object;
+ }
+ else {
+ // If version checking is enabled, ensure that the object can be used.
+ if (isset($object->api_version) &&
+ version_compare($object->api_version, $export['api']['minimum_version']) >= 0 &&
+ version_compare($object->api_version, $export['api']['current_version']) <= 0) {
+ $cache[$table][$name] = $object;
+ }
+ }
+ }
+ }
+ }
+
+ drupal_alter($export['default hook'], $cache[$table]);
+
+ // If we acquired a lock earlier, cache the results and release the
+ // lock.
+ if (!empty($lock)) {
+ // Cache the index.
+ $index = array_keys($cache[$table]);
+ cache_set('ctools_export_index:' . $table, $index, $export['default cache bin']);
+
+ // Cache each object.
+ foreach ($cache[$table] as $name => $object) {
+ cache_set('ctools_export:' . $table . ':' . $name, $object, $export['default cache bin']);
+ }
+ lock_release('ctools_export:' . $table);
+ }
+ }
+ }
+
+ return $cache[$table];
+}
+
+/**
+ * Attempt to load default objects from cache.
+ *
+ * We can be instructed to cache default objects by the schema. If so
+ * we cache them as an index which is a list of all default objects, and
+ * then each default object is cached individually.
+ *
+ * @return Either an array of cached objects, or NULL indicating a cache
+ * rebuild is necessary.
+ */
+function _ctools_export_get_defaults_from_cache($table, $export) {
+ $data = cache_get('ctools_export_index:' . $table, $export['default cache bin']);
+ if (!$data || !is_array($data->data)) {
+ return;
+ }
+
+ // This is the perfectly valid case where there are no default objects,
+ // and we have cached this state.
+ if (empty($data->data)) {
+ return array();
+ }
+
+ $keys = array();
+ foreach ($data->data as $name) {
+ $keys[] = 'ctools_export:' . $table . ':' . $name;
+ }
+
+ $data = cache_get_multiple($keys, $export['default cache bin']);
+
+ // If any of our indexed keys missed, then we have a fail and we need to
+ // rebuild.
+ if (!empty($keys)) {
+ return;
+ }
+
+ // Now, translate the returned cache objects to actual objects.
+ $cache = array();
+ foreach ($data as $cached_object) {
+ $cache[$cached_object->data->{$export['key']}] = $cached_object->data;
+ }
+
+ return $cache;
+}
+
+/**
+ * Get a limited number of default objects.
+ *
+ * This attempts to load the objects directly from cache. If it cannot,
+ * the cache is rebuilt. This does not disturb the general get defaults
+ * from cache object.
+ *
+ * This function should ONLY be called if default caching is enabled.
+ * It does not check, it is assumed the caller has already done so.
+ */
+function _ctools_export_get_some_defaults($table, $export, $names) {
+ foreach ($names as $name) {
+ $keys[] = 'ctools_export:' . $table . ':' . $name;
+ }
+
+ $data = cache_get_multiple($keys, $export['default cache bin']);
+
+ // Cache hits remove the $key from $keys by reference. Cache
+ // misses do not. A cache miss indicates we may have to rebuild.
+ if (!empty($keys)) {
+ return _ctools_export_get_defaults($table, $export);
+ }
+
+ // Now, translate the returned cache objects to actual objects.
+ $cache = array();
+ foreach ($data as $cached_object) {
+ $cache[$cached_object->data->{$export['key']}] = $cached_object->data;
+ }
+
+ return $cache;
+}
+
+/**
+ * Unpack data loaded from the database onto an object.
+ *
+ * @param $schema
+ * The schema from drupal_get_schema().
+ * @param $data
+ * The data as loaded from the database.
+ * @param $object
+ * If an object, data will be unpacked onto it. If a string
+ * an object of that type will be created.
+ */
+function _ctools_export_unpack_object($schema, $data, $object = 'stdClass') {
+ if (is_string($object)) {
+ if (class_exists($object)) {
+ $object = new $object;
+ }
+ else {
+ $object = new stdClass;
+ }
+ }
+
+ // Go through our schema and build correlations.
+ foreach ($schema['fields'] as $field => $info) {
+ if (isset($data->$field)) {
+ $object->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
+ }
+ else {
+ $object->$field = NULL;
+ }
+ }
+
+ if (isset($schema['join'])) {
+ foreach ($schema['join'] as $join_key => $join) {
+ $join_schema = ctools_export_get_schema($join['table']);
+ if (!empty($join['load'])) {
+ foreach ($join['load'] as $field) {
+ $info = $join_schema['fields'][$field];
+ $object->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
+ }
+ }
+ }
+ }
+
+ return $object;
+}
+
+/**
+ * Unpack data loaded from the database onto an object.
+ *
+ * @param $table
+ * The name of the table this object represents.
+ * @param $data
+ * The data as loaded from the database.
+ */
+function ctools_export_unpack_object($table, $data) {
+ $schema = ctools_export_get_schema($table);
+ return _ctools_export_unpack_object($schema, $data, $schema['export']['object']);
+}
+
+/**
+ * Export a field.
+ *
+ * This is a replacement for var_export(), allowing us to more nicely
+ * format exports. It will recurse down into arrays and will try to
+ * properly export bools when it can, though PHP has a hard time with
+ * this since they often end up as strings or ints.
+ */
+function ctools_var_export($var, $prefix = '') {
+ if (is_array($var)) {
+ if (empty($var)) {
+ $output = 'array()';
+ }
+ else {
+ $output = "array(\n";
+ foreach ($var as $key => $value) {
+ $output .= $prefix . " " . ctools_var_export($key) . " => " . ctools_var_export($value, $prefix . ' ') . ",\n";
+ }
+ $output .= $prefix . ')';
+ }
+ }
+ else if (is_object($var) && get_class($var) === 'stdClass') {
+ // var_export() will export stdClass objects using an undefined
+ // magic method __set_state() leaving the export broken. This
+ // workaround avoids this by casting the object as an array for
+ // export and casting it back to an object when evaluated.
+ $output = '(object) ' . ctools_var_export((array) $var, $prefix);
+ }
+ else if (is_bool($var)) {
+ $output = $var ? 'TRUE' : 'FALSE';
+ }
+ else {
+ $output = var_export($var, TRUE);
+ }
+
+ return $output;
+}
+
+/**
+ * Export an object into code.
+ */
+function ctools_export_object($table, $object, $indent = '', $identifier = NULL, $additions = array(), $additions2 = array()) {
+ $schema = ctools_export_get_schema($table);
+ if (!isset($identifier)) {
+ $identifier = $schema['export']['identifier'];
+ }
+
+ $output = $indent . '$' . $identifier . ' = new ' . get_class($object) . "();\n";
+
+ if ($schema['export']['can disable']) {
+ $output .= $indent . '$' . $identifier . '->disabled = FALSE; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n";
+ }
+ if (!empty($schema['export']['api']['current_version'])) {
+ $output .= $indent . '$' . $identifier . '->api_version = ' . $schema['export']['api']['current_version'] . ";\n";
+ }
+
+ // Put top additions here:
+ foreach ($additions as $field => $value) {
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
+ }
+
+ $fields = $schema['fields'];
+ if (!empty($schema['join'])) {
+ foreach ($schema['join'] as $join) {
+ if (!empty($join['load'])) {
+ foreach ($join['load'] as $join_field) {
+ $fields[$join_field] = $join['fields'][$join_field];
+ }
+ }
+ }
+ }
+
+ // Go through our schema and joined tables and build correlations.
+ foreach ($fields as $field => $info) {
+ if (!empty($info['no export'])) {
+ continue;
+ }
+ if (!isset($object->$field)) {
+ if (isset($info['default'])) {
+ $object->$field = $info['default'];
+ }
+ else {
+ $object->$field = '';
+ }
+ }
+
+ // Note: This is the *field* export callback, not the table one!
+ if (!empty($info['export callback']) && function_exists($info['export callback'])) {
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . $info['export callback']($object, $field, $object->$field, $indent) . ";\n";
+ }
+ else {
+ $value = $object->$field;
+ if ($info['type'] == 'int') {
+ if (isset($info['size']) && $info['size'] == 'tiny') {
+ $info['boolean'] = (!isset($info['boolean'])) ? $schema['export']['boolean'] : $info['boolean'];
+ $value = ($info['boolean']) ? (bool) $value : (int) $value;
+ }
+ else {
+ $value = (int) $value;
+ }
+ }
+
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
+ }
+ }
+
+ // And bottom additions here
+ foreach ($additions2 as $field => $value) {
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
+ }
+
+ return $output;
+}
+
+/**
+ * Get the schema for a given table.
+ *
+ * This looks for data the export subsystem needs and applies defaults so
+ * that it's easily available.
+ */
+function ctools_export_get_schema($table) {
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
+ }
+ $cache = &$drupal_static_fast['cache'];
+
+ if (empty($cache[$table])) {
+ $schema = drupal_get_schema($table);
+
+ // If our schema isn't loaded, it's possible we're in a state where it
+ // simply hasn't been cached. If we've been asked, let's force the
+ // issue.
+ if (!$schema || empty($schema['export'])) {
+ // force a schema reset:
+ $schema = drupal_get_schema($table, TRUE);
+ }
+
+ if (!isset($schema['export'])) {
+ return array();
+ }
+
+ if (empty($schema['module'])) {
+ return array();
+ }
+
+ // Add some defaults
+ $schema['export'] += array(
+ 'key' => 'name',
+ 'key name' => 'Name',
+ 'object' => 'stdClass',
+ 'status' => 'default_' . $table,
+ 'default hook' => 'default_' . $table,
+ 'can disable' => TRUE,
+ 'identifier' => $table,
+ 'primary key' => !empty($schema['primary key']) ? $schema['primary key'][0] : '',
+ 'bulk export' => TRUE,
+ 'list callback' => "$schema[module]_{$table}_list",
+ 'to hook code callback' => "$schema[module]_{$table}_to_hook_code",
+ 'cache defaults' => FALSE,
+ 'default cache bin' => 'cache',
+ 'export type string' => 'type',
+ 'boolean' => TRUE,
+ );
+
+ // If the export definition doesn't have the "primary key" then the CRUD
+ // save callback won't work.
+ if (empty($schema['export']['primary key']) && user_access('administer site configuration')) {
+ drupal_set_message(t('The export definition of @table is missing the "primary key" property.', array('@table' => $table)), 'error');
+ }
+
+ // Notes:
+ // The following callbacks may be defined to override default behavior
+ // when using CRUD functions:
+ //
+ // create callback
+ // load callback
+ // load multiple callback
+ // load all callback
+ // save callback
+ // delete callback
+ // export callback
+ // import callback
+ //
+ // See the appropriate ctools_export_crud function for details on what
+ // arguments these callbacks should accept. Please do not call these
+ // directly, always use the ctools_export_crud_* wrappers to ensure
+ // that default implementations are honored.
+ $cache[$table] = $schema;
+ }
+
+ return $cache[$table];
+}
+
+/**
+ * Gets the schemas for all tables with ctools object metadata.
+ */
+function ctools_export_get_schemas($for_export = FALSE) {
+ $export_tables = &drupal_static(__FUNCTION__);
+ if (is_null($export_tables)) {
+ $export_tables = array();
+ $schemas = drupal_get_schema();
+ foreach ($schemas as $table => $schema) {
+ if (!isset($schema['export'])) {
+ unset($schemas[$table]);
+ continue;
+ }
+ $export_tables[$table] = ctools_export_get_schema($table);
+ }
+ }
+ return $for_export ? array_filter($export_tables, '_ctools_export_filter_export_tables') : $export_tables;
+}
+
+function _ctools_export_filter_export_tables($schema) {
+ return !empty($schema['export']['bulk export']);
+}
+
+function ctools_export_get_schemas_by_module($modules = array(), $for_export = FALSE) {
+ $export_tables = array();
+ $list = ctools_export_get_schemas($for_export);
+ foreach ($list as $table => $schema) {
+ $export_tables[$schema['module']][$table] = $schema;
+ }
+ return empty($modules) ? $export_tables : array_keys($export_tables, $modules);
+}
+
+/**
+ * Set the status of a default $object as a variable.
+ *
+ * The status, in this case, is whether or not it is 'disabled'.
+ * This function does not check to make sure $object actually
+ * exists.
+ */
+function ctools_export_set_status($table, $name, $new_status = TRUE) {
+ $schema = ctools_export_get_schema($table);
+ $status = variable_get($schema['export']['status'], array());
+
+ $status[$name] = $new_status;
+ variable_set($schema['export']['status'], $status);
+}
+
+/**
+ * Set the status of a default $object as a variable.
+ *
+ * This is more efficient than ctools_export_set_status because it
+ * will actually unset the variable entirely if it's not necessary,
+ * this saving a bit of space.
+ */
+function ctools_export_set_object_status($object, $new_status = TRUE) {
+ $table = $object->table;
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+ $status = variable_get($export['status'], array());
+
+ // Compare
+ if (!$new_status && $object->export_type & EXPORT_IN_DATABASE) {
+ unset($status[$object->{$export['key']}]);
+ }
+ else {
+ $status[$object->{$export['key']}] = $new_status;
+ }
+
+ variable_set($export['status'], $status);
+}
+
+/**
+ * Provide a form for displaying an export.
+ *
+ * This is a simple form that should be invoked like this:
+ * @code
+ * $output = drupal_get_form('ctools_export_form', $code, $object_title);
+ * @endcode
+ */
+function ctools_export_form($form, &$form_state, $code, $title = '') {
+ $lines = substr_count($code, "\n");
+ $form['code'] = array(
+ '#type' => 'textarea',
+ '#title' => $title,
+ '#default_value' => $code,
+ '#rows' => $lines,
+ );
+
+ return $form;
+}
+
+/**
+ * Create a new object based upon schema values.
+ *
+ * Because 'default' has ambiguous meaning on some fields, we will actually
+ * use 'object default' to fill in default values if default is not set
+ * That's a little safer to use as it won't cause weird database default
+ * situations.
+ */
+function ctools_export_new_object($table, $set_defaults = TRUE) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ $object = new $export['object'];
+ foreach ($schema['fields'] as $field => $info) {
+ if (isset($info['object default'])) {
+ $object->$field = $info['object default'];
+ }
+ else if (isset($info['default'])) {
+ $object->$field = $info['default'];
+ }
+ else {
+ $object->$field = NULL;
+ }
+ }
+
+ if ($set_defaults) {
+ // Set some defaults so this data always exists.
+ // We don't set the export_type property here, as this object is not saved
+ // yet. We do give it NULL so we don't generate notices trying to read it.
+ $object->export_type = NULL;
+ $object->{$export['export type string']} = t('Local');
+ }
+ return $object;
+}
+
+/**
+ * Convert a group of objects to code based upon input and return this as a larger
+ * export.
+ */
+function ctools_export_to_hook_code(&$code, $table, $names = array(), $name = 'foo') {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+ // Use the schema-specified function for generating hook code, if one exists
+ if (function_exists($export['to hook code callback'])) {
+ $output = $export['to hook code callback']($names, $name);
+ }
+ // Otherwise, the following code generates basic hook code
+ else {
+ $output = ctools_export_default_to_hook_code($schema, $table, $names, $name);
+ }
+
+ if (!empty($output)) {
+ if (isset($export['api'])) {
+ if (isset($code[$export['api']['owner']][$export['api']['api']]['version'])) {
+ $code[$export['api']['owner']][$export['api']['api']]['version'] = max($code[$export['api']['owner']][$export['api']['api']]['version'], $export['api']['minimum_version']);
+ }
+ else {
+ $code[$export['api']['owner']][$export['api']['api']]['version'] = $export['api']['minimum_version'];
+ $code[$export['api']['owner']][$export['api']['api']]['code'] = '';
+ }
+ $code[$export['api']['owner']][$export['api']['api']]['code'] .= $output;
+ }
+ else {
+ if (empty($code['general'])) {
+ $code['general'] = '';
+ }
+ $code['general'] .= $output;
+ }
+ }
+}
+
+/**
+ * Default function to export objects to code.
+ *
+ * Note that if your module provides a 'to hook code callback' then it will
+ * receive only $names and $name as arguments. Your module is presumed to
+ * already know the rest.
+ */
+function ctools_export_default_to_hook_code($schema, $table, $names, $name) {
+ $export = $schema['export'];
+ $output = '';
+ $objects = ctools_export_crud_load_multiple($table, $names);
+ if ($objects) {
+ $output = "/**\n";
+ $output .= " * Implements hook_{$export['default hook']}().\n";
+ $output .= " */\n";
+ $output .= "function " . $name . "_{$export['default hook']}() {\n";
+ $output .= " \${$export['identifier']}s = array();\n\n";
+ foreach ($objects as $object) {
+ $output .= ctools_export_crud_export($table, $object, ' ');
+ $output .= " \${$export['identifier']}s['" . check_plain($object->$export['key']) . "'] = \${$export['identifier']};\n\n";
+ }
+ $output .= " return \${$export['identifier']}s;\n";
+ $output .= "}\n";
+ }
+
+ return $output;
+}
+/**
+ * Default function for listing bulk exportable objects.
+ */
+function ctools_export_default_list($table, $schema) {
+ $list = array();
+
+ $items = ctools_export_crud_load_all($table);
+ $export_key = $schema['export']['key'];
+ foreach ($items as $item) {
+ // Try a couple of possible obvious title keys:
+ $keys = array('admin_title', 'title');
+ if (isset($schema['export']['admin_title'])) {
+ array_unshift($keys, $schema['export']['admin_title']);
+ }
+
+ $string = '';
+ foreach ($keys as $key) {
+ if (!empty($item->$key)) {
+ $string = $item->$key . " (" . $item->$export_key . ")";
+ break;
+ }
+ }
+
+ if (empty($string)) {
+ $string = $item->$export_key;
+ }
+ $list[$item->$export_key] = check_plain($string);
+ }
+ return $list;
+}
diff --git a/sites/all/modules/ctools/includes/fields.inc b/sites/all/modules/ctools/includes/fields.inc
new file mode 100644
index 000000000..f379f5e9d
--- /dev/null
+++ b/sites/all/modules/ctools/includes/fields.inc
@@ -0,0 +1,357 @@
+<?php
+
+/**
+ * @file
+ * Extend core fields with some helper functions to reduce code complexity within views and ctools plugins.
+ */
+
+
+/**
+ * Fake an instance of a field.
+ *
+ * @param $field_name
+ * The unique name for this field no matter what entity/bundle it may be used on.
+ * @param $view_mode
+ * We're building a new view mode for this function. Defaults to ctools, but we expect developers to actually name this something meaningful.
+ * @param $formatter
+ * The formatter key selected from the options provided by field_ui_formatter_options().
+ * @param $formatter_settings
+ * An array of key value pairs. These will be used as #default_value for the form elements generated by a call to hook_field_formatter_settings_form() for this field type.
+ * Typically we'll pass an empty array to begin with and then pass this information back to ourselves on form submit so that we can set the values for later edit sessions.
+ */
+function ctools_fields_fake_field_instance($field_name, $view_mode = 'ctools', $formatter, $formatter_settings) {
+ $field = field_read_field($field_name);
+
+ $field_type = field_info_field_types($field['type']);
+
+ return array(
+ // Build a fake entity type and bundle.
+ 'field_name' => $field_name,
+ 'entity_type' => 'ctools',
+ 'bundle' => 'ctools',
+
+ // Use the default field settings for settings and widget.
+ 'settings' => field_info_instance_settings($field['type']),
+ 'widget' => array(
+ 'type' => $field_type['default_widget'],
+ 'settings' => array(),
+ ),
+
+ // Build a dummy display mode.
+ 'display' => array(
+ $view_mode => array(
+ 'type' => $formatter,
+ 'settings' => $formatter_settings,
+ ),
+ ),
+
+ // Set the other fields to their default values.
+ // @see _field_write_instance().
+ 'required' => FALSE,
+ 'label' => $field_name,
+ 'description' => '',
+ 'deleted' => 0,
+ );
+}
+
+/**
+ * Helper function for calling hook_field_formatter_settings_form() without needing to load an instance of the field.
+ *
+ * @param $field
+ * A fully loaded field.
+ * @param $formatter_type
+ * The formatter key selected from the options provided by field_ui_formatter_options().
+ * @param $form
+ * The full form from the function that's calling this function.
+ * @param $form_state
+ * The full form_state from the function that's calling this function.
+ * @param $view_mode
+ * We're passing a view mode from this function to the fake instance we're creating. Defaults to ctools, but we expect developers to actually name this something meaningful.
+ */
+function ctools_fields_get_field_formatter_settings_form($field, $formatter_type, &$form, $form_state, $view_mode = 'ctools') {
+ $conf = $form_state['conf'];
+ $formatter = field_info_formatter_types($formatter_type);
+ if (isset($formatter['settings'])) {
+ $conf['formatter_settings'] += $formatter['settings'];
+ }
+ $function = $formatter['module'] . '_field_formatter_settings_form';
+ if (function_exists($function)) {
+ $instance = ctools_fields_fake_field_instance($field['field_name'], $view_mode, $formatter_type, $conf['formatter_settings']);
+ $settings_form = $function($field, $instance, $view_mode, $form, $form_state);
+ if ($settings_form) {
+ // Allow other modules to alter the formatter settings form.
+ $context = array(
+ 'module' => $formatter['module'],
+ 'formatter' => $formatter,
+ 'field' => $field,
+ 'instance' => $instance,
+ 'view_mode' => $view_mode,
+ 'form' => $form,
+ 'form_state' => $form_state,
+ );
+ drupal_alter('field_formatter_settings_form', $settings_form, $context);
+
+ $settings_form['#tree'] = TRUE;
+ $form['ctools_field_list']['#value'][] = $field;
+ $form += $settings_form;
+ }
+ }
+
+ if (isset($field['cardinality']) && $field['cardinality'] != 1) {
+ list($prefix, $suffix) = explode('@count', t('Skip the first @count item(s)'));
+ $form['delta_offset'] = array(
+ '#type' => 'textfield',
+ '#size' => 5,
+ '#field_prefix' => $prefix,
+ '#field_suffix' => $suffix,
+ '#default_value' => isset($conf['delta_offset']) ? $conf['delta_offset'] : 0,
+ );
+
+ list($prefix, $suffix) = explode('@count', t('Then display at most @count item(s)'));
+ $form['delta_limit'] = array(
+ '#type' => 'textfield',
+ '#size' => 5,
+ '#field_prefix' => $prefix,
+ '#field_suffix' => $suffix,
+ '#description' => t('Enter 0 to display all items.'),
+ '#default_value' => isset($conf['delta_limit']) ? $conf['delta_limit'] : 0,
+ );
+
+ $form['delta_reversed'] = array(
+ '#title' => t('Display in reverse order'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['delta_reversed']),
+ '#description' => t('(start from last values)'),
+ );
+ }
+}
+
+/**
+ * Helper function for generating all the formatter information associated with
+ * any fields.
+ * Especially useful for determining the fields that will be added to form that
+ * executes hook_field_formatter_settings_form().
+ *
+ * @param $fields
+ * An array of fully loaded fields.
+ */
+function ctools_fields_get_field_formatter_info($fields) {
+ $info = array();
+ $field_info = module_invoke_all('field_formatter_info');
+ foreach ($fields as $field) {
+ foreach ($field_info as $format_name => $formatter_info) {
+ if (in_array($field['type'], $formatter_info['field types'])) {
+ $info += array($format_name => $formatter_info);
+ }
+ }
+ }
+ drupal_alter('field_formatter_info', $info);
+ return $info;
+}
+
+/**
+ * Returns the label of a certain field.
+ *
+ * Cribbed from Views.
+ */
+function ctools_field_label($field_name) {
+ $label_counter = array();
+ // Count the amount of instances per label per field.
+ $instances = field_info_instances();
+ foreach ($instances as $entity_type) {
+ foreach ($entity_type as $bundle) {
+ if (isset($bundle[$field_name])) {
+ $label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]['label']] : 1;
+ }
+ }
+ }
+ if (empty($label_counter)) {
+ return $field_name;
+ }
+ // Sort the field lables by it most used label and return the most used one.
+ arsort($label_counter);
+ $label_counter = array_keys($label_counter);
+ return $label_counter[0];
+}
+
+/**
+ * Replacement for core _field_invoke() to invoke on a single field.
+ *
+ * Core only allows invoking field hooks via a private function for all fields
+ * on an entire entity. However, we very often need to invoke our hooks on
+ * a single field as we take things apart and only use little bits.
+ *
+ * @param $field_name
+ * Either a field instance object or the name of the field.
+ * If the 'field' key is populated it will be used as the field
+ * settings.
+ * @param $op
+ * Possible operations include:
+ * - form
+ * - validate
+ * - presave
+ * - insert
+ * - update
+ * - delete
+ * - delete revision
+ * - view
+ * - prepare translation
+ * @param $entity_type
+ * The type of $entity; e.g. 'node' or 'user'.
+ * @param $entity
+ * The fully formed $entity_type entity.
+ * @param $a
+ * - The $form in the 'form' operation.
+ * - The value of $view_mode in the 'view' operation.
+ * - Otherwise NULL.
+ * @param $b
+ * - The $form_state in the 'submit' operation.
+ * - Otherwise NULL.
+ * @param $options
+ * An associative array of additional options, with the following keys:
+ * - 'field_name': The name of the field whose operation should be
+ * invoked. By default, the operation is invoked on all the fields
+ * in the entity's bundle. NOTE: This option is not compatible with
+ * the 'deleted' option; the 'field_id' option should be used
+ * instead.
+ * - 'field_id': The id of the field whose operation should be
+ * invoked. By default, the operation is invoked on all the fields
+ * in the entity's' bundles.
+ * - 'default': A boolean value, specifying which implementation of
+ * the operation should be invoked.
+ * - if FALSE (default), the field types implementation of the operation
+ * will be invoked (hook_field_[op])
+ * - If TRUE, the default field implementation of the field operation
+ * will be invoked (field_default_[op])
+ * Internal use only. Do not explicitely set to TRUE, but use
+ * _field_invoke_default() instead.
+ * - 'deleted': If TRUE, the function will operate on deleted fields
+ * as well as non-deleted fields. If unset or FALSE, only
+ * non-deleted fields are operated on.
+ * - 'language': A language code or an array of language codes keyed by field
+ * name. It will be used to narrow down to a single value the available
+ * languages to act on.
+ *
+ * @see _field_invoke()
+ */
+function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) {
+ if (is_array($field_name)) {
+ $instance = $field_name;
+ $field = empty($field_name['field']) ? field_info_field($instance['field_name']) : $field_name['field'];
+ $field_name = $instance['field_name'];
+ }
+ else {
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+ $instance = field_info_instance($entity_type, $field_name, $bundle);
+ }
+
+ if (empty($instance)) {
+ return;
+ }
+
+ // Merge default options.
+ $default_options = array(
+ 'default' => FALSE,
+ 'deleted' => FALSE,
+ 'language' => NULL,
+ );
+ $options += $default_options;
+
+ $return = array();
+
+ // Everything from here is unmodified code from _field_invoke() formerly
+ // inside a foreach loop over the instances.
+ $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
+ if (function_exists($function)) {
+ // Determine the list of languages to iterate on.
+ $available_languages = field_available_languages($entity_type, $field);
+ $languages = _field_language_suggestion($available_languages, $options['language'], $field_name);
+
+ foreach ($languages as $langcode) {
+ $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
+ $result = $function($entity_type, $entity, $field, $instance, $langcode, $items, $a, $b);
+ if (isset($result)) {
+ // For hooks with array results, we merge results together.
+ // For hooks with scalar results, we collect results in an array.
+ if (is_array($result)) {
+ $return = array_merge($return, $result);
+ }
+ else {
+ $return[] = $result;
+ }
+ }
+
+ // Populate $items back in the field values, but avoid replacing missing
+ // fields with an empty array (those are not equivalent on update).
+ if ($items !== array() || isset($entity->{$field_name}[$langcode])) {
+ $entity->{$field_name}[$langcode] = $items;
+ }
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Replacement for core _field_invoke_default() to invoke on a single field.
+ *
+ * @see ctools_field_invoke_field()
+ * @see _field_invoke_default()
+ */
+function ctools_field_invoke_field_default($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) {
+ $options['default'] = TRUE;
+ return ctools_field_invoke_field($field_name, $op, $entity_type, $entity, $a, $b, $options);
+}
+
+/**
+ * Returns a list of field definitions of a specified type.
+ *
+ * @param string $field_type
+ * A field type name; e.g. 'text' or 'date'.
+ *
+ * @return array
+ * An array of field definitions of the specified type, keyed by field name.
+ */
+function ctools_fields_get_fields_by_type($field_type) {
+ $fields = array();
+ foreach (field_info_fields() as $field_name => $field_info) {
+ if ($field_info['type'] == $field_type) {
+ $fields[$field_name] = $field_info;
+ }
+ }
+ return $fields;
+}
+
+/**
+ * Derive the foreign keys that a field provides.
+ *
+ * @param $field_name
+ * The name of the field.
+ *
+ * @return
+ * An array of foreign keys according to Schema API.
+ */
+function ctools_field_foreign_keys($field_name) {
+ $foreign_keys = &drupal_static(__FUNCTION__, array());
+ if (!isset($foreign_keys[$field_name])) {
+ $foreign_keys[$field_name] = array();
+ $field = field_info_field($field_name);
+
+ if (!empty($field['foreign keys'])) {
+ $foreign_keys[$field_name] = $field['foreign keys'];
+ }
+ else {
+ // try to fetch foreign keys from schema, as not everything
+ // stores foreign keys properly in the field info.
+ $module = $field['module'];
+
+ module_load_install($module);
+ $schema = module_invoke($module, 'field_schema', $field);
+ if (!empty($schema['foreign keys'])) {
+ $foreign_keys[$field_name] = $schema['foreign keys'];
+ }
+ }
+ }
+
+ return $foreign_keys[$field_name];
+}
diff --git a/sites/all/modules/ctools/includes/jump-menu.inc b/sites/all/modules/ctools/includes/jump-menu.inc
new file mode 100644
index 000000000..51f45982b
--- /dev/null
+++ b/sites/all/modules/ctools/includes/jump-menu.inc
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Provides a simple "jump menu".
+ *
+ * A jump menu is a select box and an optional 'go' button which can be removed
+ * if javascript is in use. Each item is keyed to the href that the button
+ * should go to. With javascript, the page is immediately redirected. Without
+ * javascript, the form is submitted and a drupal_goto() is given.
+ *
+ */
+
+/**
+ * Generate a jump menu form.
+ *
+ * This can either be used with drupal_get_form() or directly added to a
+ * form. The button provides its own submit handler so by default, other
+ * submit handlers will not be called.
+ *
+ * One note: Do not use #tree = TRUE or it will be unable to find the
+ * proper value.
+ *
+ * @code
+ * ctools_include('jump-menu');
+ * $output = drupal_get_form('ctools_jump_menu', $targets, $options);
+ * @endcode
+ *
+ * @param $select
+ * An array suitable for use as the #options. The keys will be the direct
+ * URLs that will be jumped to, so you absolutely must encode these using
+ * url() in order for them to work reliably.
+ *
+ * @param $options
+ * $options may be an array with the following options:
+ * - 'title': The text to display for the #title attribute.
+ * - 'description': The text to display for the #description attribute.
+ * - 'default_value': The text to display for the #default_value attribute.
+ * - 'hide': If TRUE the go button will be set to hide via javascript and
+ * will submit on change.
+ * - 'button': The text to display on the button.
+ * - 'image': If set, an image button will be used instead, and the image
+ * set to this.
+ * - 'inline': If set to TRUE (default) the display will be forced inline.
+ */
+function ctools_jump_menu($form, &$form_state, $select, $options = array()) {
+ $options += array(
+ 'button' => t('Go'),
+ 'choose' => t('- Choose -'),
+ 'inline' => TRUE,
+ 'hide' => TRUE,
+ );
+
+ $form['#attached']['js'][] = ctools_attach_js('jump-menu');
+
+ if (!empty($options['choose'])) {
+ $select = array('' => $options['choose']) + $select;
+ }
+
+ $form['jump'] = array(
+ '#type' => 'select',
+ '#options' => $select,
+ '#attributes' => array(
+ 'class' => array('ctools-jump-menu-select'),
+ ),
+ );
+
+ if (!empty($options['title'])) {
+ $form['jump']['#title'] = $options['title'];
+ }
+
+ if (!empty($options['description'])) {
+ $form['jump']['#description'] = $options['description'];
+ }
+
+ if (!empty($options['default_value'])) {
+ $form['jump']['#default_value'] = $options['default_value'];
+ }
+
+ if (isset($options['image'])) {
+ $form['go'] = array(
+ '#type' => 'image_button',
+ '#src' => $options['image'],
+ '#submit' => array('ctools_jump_menu_submit'),
+ '#attributes' => array(
+ 'class' => array('ctools-jump-menu-button'),
+ ),
+ );
+ }
+ else {
+ $form['go'] = array(
+ '#type' => 'submit',
+ '#value' => $options['button'],
+ '#submit' => array('ctools_jump_menu_submit'),
+ '#attributes' => array(
+ 'class' => array('ctools-jump-menu-button'),
+ ),
+ );
+ }
+
+ if ($options['inline']) {
+ $form['jump']['#prefix'] = '<div class="container-inline">';
+ $form['go']['#suffix'] = '</div>';
+ }
+
+ if ($options['hide']) {
+ $form['jump']['#attributes']['class'][] = 'ctools-jump-menu-change';
+ $form['go']['#attributes']['class'][] = 'ctools-jump-menu-hide';
+ }
+
+ return $form;
+}
+
+/**
+ * Submit handler for the jump menu.
+ *
+ * This is normally only invoked upon submit without javascript enabled.
+ */
+function ctools_jump_menu_submit($form, &$form_state) {
+ if ($form_state['values']['jump'] === '') {
+ // We have nothing to do when the user has not selected any value.
+ return;
+ }
+
+ // If the path we are redirecting to contains the string :: then treat the
+ // the string after the double colon as the path to redirect to.
+ // This allows duplicate paths to be used in jump menus for multiple options.
+ $redirect_array = explode("::", $form_state['values']['jump']);
+
+ if(isset($redirect_array[1]) && !empty($redirect_array[1])){
+ $redirect = $redirect_array[1];
+ }
+ else {
+ $redirect = $form_state['values']['jump'];
+ }
+
+ // If the path we are redirecting to starts with the base path (for example,
+ // "/somepath/node/1"), we need to strip the base path off before passing it
+ // to $form_state['redirect'].
+ $base_path = base_path();
+ if (strpos($redirect, $base_path) === 0) {
+ $redirect = substr($redirect, strlen($base_path));
+ }
+
+ // Parse the URL so that query strings and fragments are preserved in the
+ // redirect.
+ $redirect = drupal_parse_url($redirect);
+ $redirect['path'] = urldecode($redirect['path']);
+ $form_state['redirect'] = array($redirect['path'], $redirect);
+}
diff --git a/sites/all/modules/ctools/includes/language.inc b/sites/all/modules/ctools/includes/language.inc
new file mode 100644
index 000000000..9a7850b77
--- /dev/null
+++ b/sites/all/modules/ctools/includes/language.inc
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Returns array of language names.
+ *
+ * This is a one to one copy of locale_language_list because we can't rely on enabled locale module.
+ *
+ * @param $field
+ * 'name' => names in current language, localized
+ * 'native' => native names
+ * @param $all
+ * Boolean to return all languages or only enabled ones
+ *
+ * @see locale_language_list
+ */
+function ctools_language_list($field = 'name', $all = FALSE) {
+ if ($all) {
+ $languages = language_list();
+ }
+ else {
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+ }
+ $list = array();
+ foreach ($languages as $language) {
+ $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
+ }
+ return $list;
+}
+
+/**
+ * Returns an array of language names similar to ctools_language_list() except
+ * that additional choices have been added for ease of use.
+ */
+
+function ctools_language_list_all() {
+ $languages = array(
+ '***CURRENT_LANGUAGE***' => t("Current user's language"),
+ '***DEFAULT_LANGUAGE***' => t("Default site language"),
+ LANGUAGE_NONE => t('Language neutral'),
+ );
+ $languages = array_merge($languages, ctools_language_list());
+ return $languages;
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/includes/math-expr.inc b/sites/all/modules/ctools/includes/math-expr.inc
new file mode 100644
index 000000000..eeb184d86
--- /dev/null
+++ b/sites/all/modules/ctools/includes/math-expr.inc
@@ -0,0 +1,388 @@
+<?php
+
+/*
+================================================================================
+
+ctools_math_expr - PHP Class to safely evaluate math expressions
+Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
+
+================================================================================
+
+NAME
+ ctools_math_expr - safely evaluate math expressions
+
+SYNOPSIS
+ include('ctools_math_expr.class.php');
+ $m = new ctools_math_expr;
+ // basic evaluation:
+ $result = $m->evaluate('2+2');
+ // supports: order of operation; parentheses; negation; built-in functions
+ $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
+ // create your own variables
+ $m->evaluate('a = e^(ln(pi))');
+ // or functions
+ $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
+ // and then use them
+ $result = $m->evaluate('3*f(42,a)');
+
+DESCRIPTION
+ Use the ctools_math_expr class when you want to evaluate mathematical expressions
+ from untrusted sources. You can define your own variables and functions,
+ which are stored in the object. Try it, it's fun!
+
+METHODS
+ $m->evalute($expr)
+ Evaluates the expression and returns the result. If an error occurs,
+ prints a warning and returns false. If $expr is a function assignment,
+ returns true on success.
+
+ $m->e($expr)
+ A synonym for $m->evaluate().
+
+ $m->vars()
+ Returns an associative array of all user-defined variables and values.
+
+ $m->funcs()
+ Returns an array of all user-defined functions.
+
+PARAMETERS
+ $m->suppress_errors
+ Set to true to turn off warnings when evaluating expressions
+
+ $m->last_error
+ If the last evaluation failed, contains a string describing the error.
+ (Useful when suppress_errors is on).
+
+AUTHOR INFORMATION
+ Copyright 2005, Miles Kaufmann.
+
+LICENSE
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ 1 Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+class ctools_math_expr {
+ var $suppress_errors = false;
+ var $last_error = null;
+
+ var $v = array('e'=>2.71,'pi'=>3.14); // variables (and constants)
+ var $f = array(); // user-defined functions
+ var $vb = array('e', 'pi'); // constants
+ var $fb = array( // built-in functions
+ 'sin','sinh','arcsin','asin','arcsinh','asinh',
+ 'cos','cosh','arccos','acos','arccosh','acosh',
+ 'tan','tanh','arctan','atan','arctanh','atanh',
+ 'pow', 'exp',
+ 'sqrt','abs','ln','log',
+ 'time', 'ceil', 'floor', 'min', 'max', 'round');
+
+ function ctools_math_expr() {
+ // make the variables a little more accurate
+ $this->v['pi'] = pi();
+ $this->v['e'] = exp(1);
+ drupal_alter('ctools_math_expression_functions', $this->fb);
+ }
+
+ function e($expr) {
+ return $this->evaluate($expr);
+ }
+
+ function evaluate($expr) {
+ $this->last_error = null;
+ $expr = trim($expr);
+ if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
+ //===============
+ // is it a variable assignment?
+ if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
+ if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
+ return $this->trigger("cannot assign to constant '$matches[1]'");
+ }
+ if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
+ $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
+ return $this->v[$matches[1]]; // and return the resulting value
+ //===============
+ // is it a function assignment?
+ } elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
+ $fnn = $matches[1]; // get the function name
+ if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
+ return $this->trigger("cannot redefine built-in function '$matches[1]()'");
+ }
+ $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
+ if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
+ for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
+ $token = $stack[$i];
+ if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {
+ if (array_key_exists($token, $this->v)) {
+ $stack[$i] = $this->v[$token];
+ } else {
+ return $this->trigger("undefined variable '$token' in function definition");
+ }
+ }
+ }
+ $this->f[$fnn] = array('args'=>$args, 'func'=>$stack);
+ return true;
+ //===============
+ } else {
+ return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
+ }
+ }
+
+ function vars() {
+ $output = $this->v;
+ unset($output['pi']);
+ unset($output['e']);
+ return $output;
+ }
+
+ function funcs() {
+ $output = array();
+ foreach ($this->f as $fnn=>$dat)
+ $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
+ return $output;
+ }
+
+ //===================== HERE BE INTERNAL METHODS ====================\\
+
+ // Convert infix to postfix notation
+ function nfx($expr) {
+
+ $index = 0;
+ $stack = new ctools_math_expr_stack;
+ $output = array(); // postfix form of expression, to be passed to pfx()
+ $expr = trim(strtolower($expr));
+
+ $ops = array('+', '-', '*', '/', '^', '_');
+ $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative operator?
+ $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2); // operator precedence
+
+ $expecting_op = false; // we use this in syntax-checking the expression
+ // and determining when a - is a negation
+
+ if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr, $matches)) { // make sure the characters are all good
+ return $this->trigger("illegal character '{$matches[0]}'");
+ }
+
+ while(1) { // 1 Infinite Loop ;)
+ $op = substr($expr, $index, 1); // get the first character at the current index
+ // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
+ $ex = preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
+ //===============
+ if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
+ $stack->push('_'); // put a negation on the stack
+ $index++;
+ } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
+ return $this->trigger("illegal character '_'"); // but not in the input expression
+ //===============
+ } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
+ if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
+ $op = '*'; $index--; // it's an implicit multiplication
+ }
+ // heart of the algorithm:
+ while($stack->count > 0 and ($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
+ $output[] = $stack->pop(); // pop stuff off the stack into the output
+ }
+ // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
+ $stack->push($op); // finally put OUR operator onto the stack
+ $index++;
+ $expecting_op = false;
+ //===============
+ } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
+ while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
+ if (is_null($o2)) return $this->trigger("unexpected ')'");
+ else $output[] = $o2;
+ }
+ if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) { // did we just close a function?
+ $fnn = $matches[1]; // get the function name
+ $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
+ $output[] = $stack->pop(); // pop the function and push onto the output
+ if (in_array($fnn, $this->fb)) { // check the argument count
+ if($arg_count > 1)
+ return $this->trigger("too many arguments ($arg_count given, 1 expected)");
+ } elseif (array_key_exists($fnn, $this->f)) {
+ if ($arg_count != count($this->f[$fnn]['args']))
+ return $this->trigger("wrong number of arguments ($arg_count given, " . count($this->f[$fnn]['args']) . " expected)");
+ } else { // did we somehow push a non-function on the stack? this should never happen
+ return $this->trigger("internal error");
+ }
+ }
+ $index++;
+ //===============
+ } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
+ while (($o2 = $stack->pop()) != '(') {
+ if (is_null($o2)) return $this->trigger("unexpected ','"); // oops, never had a (
+ else $output[] = $o2; // pop the argument expression stuff and push onto the output
+ }
+ // make sure there was a function
+ if (!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches))
+ return $this->trigger("unexpected ','");
+ $stack->push($stack->pop()+1); // increment the argument count
+ $stack->push('('); // put the ( back on, we'll need to pop back to it again
+ $index++;
+ $expecting_op = false;
+ //===============
+ } elseif ($op == '(' and !$expecting_op) {
+ $stack->push('('); // that was easy
+ $index++;
+ $allow_neg = true;
+ //===============
+ } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
+ $expecting_op = true;
+ $val = $match[1];
+ if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
+ if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { // it's a func
+ $stack->push($val);
+ $stack->push(1);
+ $stack->push('(');
+ $expecting_op = false;
+ } else { // it's a var w/ implicit multiplication
+ $val = $matches[1];
+ $output[] = $val;
+ }
+ } else { // it's a plain old var or num
+ $output[] = $val;
+ }
+ $index += strlen($val);
+ //===============
+ } elseif ($op == ')') { // miscellaneous error checking
+ return $this->trigger("unexpected ')'");
+ } elseif (in_array($op, $ops) and !$expecting_op) {
+ return $this->trigger("unexpected operator '$op'");
+ } else { // I don't even want to know what you did to get here
+ return $this->trigger("an unexpected error occurred");
+ }
+ if ($index == strlen($expr)) {
+ if (in_array($op, $ops)) { // did we end with an operator? bad.
+ return $this->trigger("operator '$op' lacks operand");
+ } else {
+ break;
+ }
+ }
+ while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
+ $index++; // into implicit multiplication if no operator is there)
+ }
+
+ }
+ while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
+ if ($op == '(') return $this->trigger("expecting ')'"); // if there are (s on the stack, ()s were unbalanced
+ $output[] = $op;
+ }
+ return $output;
+ }
+
+ // evaluate postfix notation
+ function pfx($tokens, $vars = array()) {
+
+ if ($tokens == false) return false;
+
+ $stack = new ctools_math_expr_stack;
+
+ foreach ($tokens as $token) { // nice and easy
+ // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
+ if (in_array($token, array('+', '-', '*', '/', '^'))) {
+ if (is_null($op2 = $stack->pop())) return $this->trigger("internal error");
+ if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
+ switch ($token) {
+ case '+':
+ $stack->push($op1+$op2); break;
+ case '-':
+ $stack->push($op1-$op2); break;
+ case '*':
+ $stack->push($op1*$op2); break;
+ case '/':
+ if ($op2 == 0) return $this->trigger("division by zero");
+ $stack->push($op1/$op2); break;
+ case '^':
+ $stack->push(pow($op1, $op2)); break;
+ }
+ // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
+ } elseif ($token == "_") {
+ $stack->push(-1*$stack->pop());
+ // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
+ } elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a function!
+ $fnn = $matches[1];
+ if (in_array($fnn, $this->fb)) { // built-in function:
+ if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
+ $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
+ if ($fnn == 'ln') $fnn = 'log';
+ eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
+ } elseif (array_key_exists($fnn, $this->f)) { // user function
+ // get args
+ $args = array();
+ for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
+ if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger("internal error");
+ }
+ $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
+ }
+ // if the token is a number or variable, push it on the stack
+ } else {
+ if (is_numeric($token)) {
+ $stack->push($token);
+ } elseif (array_key_exists($token, $this->v)) {
+ $stack->push($this->v[$token]);
+ } elseif (array_key_exists($token, $vars)) {
+ $stack->push($vars[$token]);
+ } else {
+ return $this->trigger("undefined variable '$token'");
+ }
+ }
+ }
+ // when we're out of tokens, the stack should have a single element, the final result
+ if ($stack->count != 1) return $this->trigger("internal error");
+ return $stack->pop();
+ }
+
+ // trigger an error, but nicely, if need be
+ function trigger($msg) {
+ $this->last_error = $msg;
+ if (!$this->suppress_errors) trigger_error($msg, E_USER_WARNING);
+ return false;
+ }
+}
+
+// for internal use
+class ctools_math_expr_stack {
+
+ var $stack = array();
+ var $count = 0;
+
+ function push($val) {
+ $this->stack[$this->count] = $val;
+ $this->count++;
+ }
+
+ function pop() {
+ if ($this->count > 0) {
+ $this->count--;
+ return $this->stack[$this->count];
+ }
+ return null;
+ }
+
+ function last($n=1) {
+ return !empty($this->stack[$this->count-$n]) ? $this->stack[$this->count-$n] : NULL;
+ }
+}
+
diff --git a/sites/all/modules/ctools/includes/menu.inc b/sites/all/modules/ctools/includes/menu.inc
new file mode 100644
index 000000000..e725ea407
--- /dev/null
+++ b/sites/all/modules/ctools/includes/menu.inc
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * General menu helper functions.
+ */
+
+/**
+ * Dynamically add a tab to the current path.
+ *
+ * This function is a simplified interface for adding tabs to the current path.
+ * Some considerations when doing this:
+ *
+ * - First, if there is only 1 tab, Drupal will not show it. Therefore, if
+ * you are only adding one tab, you should find a way to make sure there is
+ * already tab, or instead add 2.
+ * - Second, the caller is responsible for providing access control to these
+ * links.
+ *
+ * @param $link
+ * An array describing this link. It must contain:
+ * - 'title': The printed title of the link.
+ * - 'href': The path of the link. This is an argument to l() so it has all
+ * of those features and limitations.
+ * - 'options': Any options that go to l, including query, fragment and html
+ * options necessary.
+ * - 'weight': The weight to use in ordering the tabs.
+ * - 'type': Optional. If set to MENU_DEFAULT_LOCAL_TASK this can be used to
+ * add a fake 'default' local task, which is useful if you have to add
+ * tabs to a page that has none.
+ */
+function ctools_menu_add_tab($link = NULL) {
+ $links = &drupal_static(__FUNCTION__, array());
+ if (isset($link)) {
+ $links[$link['href']] = $link;
+ }
+
+ return $links;
+}
+
+/**
+ * Re-sort menu items after we have modified them.
+ */
+function ctools_menu_sort($a, $b) {
+ $a_weight = (is_array($a) && isset($a['#link']['weight'])) ? $a['#link']['weight'] : 0;
+ $b_weight = (is_array($b) && isset($b['#link']['weight'])) ? $b['#link']['weight'] : 0;
+ if ($a_weight == $b_weight) {
+ $a_title = (is_array($a) && isset($a['#link']['title'])) ? $a['#link']['title'] : 0;
+ $b_title = (is_array($b) && isset($b['#link']['title'])) ? $b['#link']['title'] : 0;
+ if ($a_title == $b_title) {
+ return 0;
+ }
+
+ return ($a_title < $b_title) ? -1 : 1;
+ }
+
+ return ($a_weight < $b_weight) ? -1 : 1;
+}
+
+function _ctools_menu_add_dynamic_items(&$data, &$router_item, &$root_path) {
+ if ($additions = ctools_menu_add_tab()) {
+ // If none of the static local tasks are active allow one of the dynamic
+ // active tasks to be marked as such.
+ $has_active = FALSE;
+ if (!empty($data['tabs'][0]['output'])) {
+ foreach ($data['tabs'][0]['output'] as $element) {
+ if (!empty($element['#link']['#active'])) {
+ $has_active = TRUE;
+ }
+ }
+ }
+ foreach ($additions as $addition) {
+ $addition['localized_options'] = isset($addition['options']) ? $addition['options'] : array();
+ if (isset($addition['type']) && $addition['type'] == MENU_LOCAL_ACTION) {
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $addition,
+ );
+ }
+ else {
+ $data['tabs'][0]['output'][] = array(
+ '#theme' => 'menu_local_task',
+ '#link' => $addition,
+ '#active' => (!$has_active && $root_path === $addition['href']),
+ );
+ }
+ }
+ if (!empty($data['tabs'][0]['output'])) {
+ uasort($data['tabs'][0]['output'], 'ctools_menu_sort');
+ $data['tabs'][0]['count'] = count($data['tabs'][0]['output']);
+ }
+
+ if (!empty($data['actions']['output'])) {
+ uasort($data['actions']['output'], 'ctools_menu_sort');
+ $data['actions']['count'] = count($data['actions']['output']);
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/modal.inc b/sites/all/modules/ctools/includes/modal.inc
new file mode 100644
index 000000000..fc9901594
--- /dev/null
+++ b/sites/all/modules/ctools/includes/modal.inc
@@ -0,0 +1,262 @@
+<?php
+
+/**
+ * @file
+ * Implement a modal form using AJAX.
+ *
+ * The modal form is implemented primarily from mc.js; this contains the
+ * Drupal specific stuff to use it. The modal is fairly generic and can
+ * be activated mostly by setting up the right classes, but if you are
+ * using the modal you must include links to the images in settings,
+ * because the javascript does not inherently know where the images are.
+ *
+ * You can accomplish this with this PHP code:
+ * @code {
+ * ctools_include('modal');
+ * ctools_modal_add_js();
+ * }
+ *
+ * You can have links and buttons bound to use the modal by adding the
+ * class ctools-use-modal.
+ *
+ * For links, the href of the link will be the destination, with any
+ * appearance of /nojs/ converted to /ajax/.
+ *
+ * For submit buttons, however, the URL is found a different, slightly
+ * more complex way. The ID of the item is taken and -url is appended to
+ * it to derive a class name. Then, all form elements that contain that
+ * class name are founded and their values put together to form a URL.
+ *
+ * For example, let's say you have an 'add' button, and it has a select
+ * form item that tells your system what widget it is adding. If the id
+ * of the add button is edit-add, you would place a hidden input with
+ * the base of your URL in the form and give it a class of 'edit-add-url'.
+ * You would then add 'edit-add-url' as a class to the select widget
+ * allowing you to convert this value to the form without posting.
+ *
+ * If no URL is found, the action of the form will be used and the entire
+ * form posted to it.
+ */
+
+function ctools_modal_add_js() {
+ // Provide a gate so we only do this once.
+ static $done = FALSE;
+ if ($done) {
+ return;
+ }
+
+ $settings = array(
+ 'CToolsModal' => array(
+ 'loadingText' => t('Loading...'),
+ 'closeText' => t('Close Window'),
+ 'closeImage' => theme('image', array(
+ 'path' => ctools_image_path('icon-close-window.png'),
+ 'title' => t('Close window'),
+ 'alt' => t('Close window'),
+ )),
+ 'throbber' => theme('image', array(
+ 'path' => ctools_image_path('throbber.gif'),
+ 'title' => t('Loading...'),
+ 'alt' => t('Loading'),
+ )),
+ ),
+ );
+
+ drupal_add_js($settings, 'setting');
+ drupal_add_library('system', 'jquery.form');
+ drupal_add_library('system', 'drupal.progress');
+ drupal_add_library('system', 'drupal.ajax');
+ drupal_add_library('system', 'ui');
+ ctools_add_js('modal');
+
+ ctools_add_css('modal');
+ $done = TRUE;
+}
+
+/**
+ * @todo this is deprecated
+ */
+function ctools_modal_add_plugin_js($plugins) {
+ $css = array();
+ $js = array(drupal_get_path('module', 'ctools') . '/js/dependent.js' => TRUE);
+ foreach ($plugins as $subtype) {
+ if (isset($subtype['js'])) {
+ foreach ($subtype['js'] as $file) {
+ if (file_exists($file)) {
+ $js[$file] = TRUE;
+ }
+ else if (file(exists($subtype['path'] . '/' . $file))) {
+ $js[$subtype['path'] . '/' . $file] = TRUE;
+ }
+ }
+ }
+ if (isset($subtype['css'])) {
+ foreach ($subtype['css'] as $file) {
+ if (file_exists($file)) {
+ $css[$file] = TRUE;
+ }
+ else if (file(exists($subtype['path'] . '/' . $file))) {
+ $css[$subtype['path'] . '/' . $file] = TRUE;
+ }
+ }
+ }
+ }
+
+ foreach (array_keys($js) as $file) {
+ drupal_add_js($file);
+ }
+ foreach (array_keys($css) as $file) {
+ drupal_add_css($file);
+ }
+}
+
+/**
+ * Place HTML within the modal.
+ *
+ * @param $title
+ * The title of the modal.
+ * @param $html
+ * The html to place within the modal.
+ */
+function ctools_modal_command_display($title, $html) {
+ if (is_array($html)) {
+ $html = drupal_render($html);
+ }
+
+ return array(
+ 'command' => 'modal_display',
+ 'title' => $title,
+ 'output' => $html,
+ );
+}
+
+/**
+ * Dismiss the modal.
+ */
+function ctools_modal_command_dismiss() {
+ return array(
+ 'command' => 'modal_dismiss',
+ );
+}
+
+/**
+ * Display loading screen in the modal
+ */
+function ctools_modal_command_loading() {
+ return array(
+ 'command' => 'modal_loading',
+ );
+}
+
+/**
+ * Render an image as a button link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * @param $image
+ * The path to an image to use that will be sent to theme('image') for rendering.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ */
+function ctools_modal_image_button($image, $dest, $alt, $class = '') {
+ return ctools_ajax_text_button(theme('image', array('path' => $image)), $dest, $alt, $class, 'ctools-use-modal');
+}
+
+/**
+ * Render text as a link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will
+ * not use user input so this is a very minor concern.
+ *
+ * @param $text
+ * The text that will be displayed as the link.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ */
+function ctools_modal_text_button($text, $dest, $alt, $class = '') {
+ return ctools_ajax_text_button($text, $dest, $alt, $class, 'ctools-use-modal');
+}
+
+/**
+ * Wrap a form so that we can use it properly with AJAX. Essentially if the
+ * form wishes to render, it automatically does that, otherwise it returns
+ * the render array so we can see submission results.
+
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param array $form_state
+ * An associative array containing the current state of the form.
+ * If the 'reset_html_ids' key is set to TRUE, it will prevent HTML IDs in
+ * forms from being incremented.
+ *
+ * @return mixed
+ * The output of the form, if it was rendered. If $form_state['ajax']
+ * is set, this will use ctools_modal_form_render so it will be
+ * a $command object suitable for ajax_render already.
+ *
+ * If the form was not rendered, the raw render array will be returned.
+ *
+ * If ajax is set the form will never be redirected.
+ */
+function ctools_modal_form_wrapper($form_id, &$form_state) {
+ // Since this will run again on form rebuild while still in the modal, prevent
+ // form IDs from being incremented.
+ // @todo https://drupal.org/node/1305882
+ if (!empty($form_state['reset_html_ids']) && !empty($_POST['ajax_html_ids'])) {
+ unset($_POST['ajax_html_ids']);
+ }
+
+ // This won't override settings already in.
+ $form_state += array(
+ 're_render' => FALSE,
+ 'no_redirect' => !empty($form_state['ajax']),
+ );
+
+ $output = drupal_build_form($form_id, $form_state);
+ if (!empty($form_state['ajax']) && (!$form_state['executed'] || $form_state['rebuild'])) {
+ return ctools_modal_form_render($form_state, $output);
+ }
+
+ return $output;
+}
+
+/**
+ * Render a form into an AJAX display.
+ */
+function ctools_modal_form_render($form_state, $output) {
+ if (is_array($output)) {
+ $output = drupal_render($output);
+ }
+
+ $title = empty($form_state['title']) ? drupal_get_title() : $form_state['title'];
+
+ // If there are messages for the form, render them.
+ if ($messages = theme('status_messages')) {
+ $output = $messages . $output;
+ }
+
+ $commands = array();
+ // If the form has not yet been rendered, render it.
+ $commands[] = ctools_modal_command_display($title, $output);
+ return $commands;
+}
+
+/**
+ * Perform a simple modal render and immediately exit.
+ *
+ * This is primarily used for error displays, since usually modals will
+ * contain forms.
+ */
+function ctools_modal_render($title, $output) {
+ $commands = array();
+ $commands[] = ctools_modal_command_display($title, $output);
+ print ajax_render($commands);
+}
diff --git a/sites/all/modules/ctools/includes/object-cache.cron.inc b/sites/all/modules/ctools/includes/object-cache.cron.inc
new file mode 100644
index 000000000..99f2276ca
--- /dev/null
+++ b/sites/all/modules/ctools/includes/object-cache.cron.inc
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains cron hooks for the object cache tool.
+ *
+ * We use this to clean up old object caches.
+ */
+
+function ctools_object_cache_cron() {
+ if (variable_get('ctools_last_cron', 0) < time() - 86400) {
+ variable_set('ctools_last_cron', time());
+ ctools_include('object-cache');
+ ctools_object_cache_clean();
+ }
+}
diff --git a/sites/all/modules/ctools/includes/object-cache.inc b/sites/all/modules/ctools/includes/object-cache.inc
new file mode 100644
index 000000000..29225b05b
--- /dev/null
+++ b/sites/all/modules/ctools/includes/object-cache.inc
@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * @file
+ * The non-volatile object cache is used to store an object while it is
+ * being edited, so that we don't have to save until we're completely
+ * done. The cache should be 'cleaned' on a regular basis, meaning to
+ * remove old objects from the cache, but otherwise the data in this
+ * cache must remain stable, as it includes unsaved changes.
+ */
+
+/**
+ * Get an object from the non-volatile ctools cache.
+ *
+ * This function caches in memory as well, so that multiple calls to this
+ * will not result in multiple database reads.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being stored.
+ * @param $skip_cache
+ * Skip the memory cache, meaning this must be read from the db again.
+ * @param $sid
+ * The session id, allowing someone to use Session API or their own solution;
+ * defaults to session_id().
+ *
+ * @deprecated $skip_cache is deprecated in favor of drupal_static*
+ * @return
+ * The data that was cached.
+ */
+function ctools_object_cache_get($obj, $name, $skip_cache = FALSE, $sid = NULL) {
+ $cache = &drupal_static(__FUNCTION__, array());
+ $key = "$obj:$name";
+ if ($skip_cache) {
+ unset($cache[$key]);
+ }
+
+ if (!$sid) {
+ $sid = session_id();
+ }
+
+ if (!array_key_exists($key, $cache)) {
+ $data = db_query('SELECT * FROM {ctools_object_cache} WHERE sid = :session_id AND obj = :object AND name = :name', array(':session_id' => $sid, ':object' => $obj, ':name' => $name))
+ ->fetchObject();
+ if ($data) {
+ $cache[$key] = unserialize($data->data);
+ }
+ }
+ return isset($cache[$key]) ? $cache[$key] : NULL;
+}
+
+/**
+ * Store an object in the non-volatile ctools cache.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being stored.
+ * @param $cache
+ * The object to be cached. This will be serialized prior to writing.
+ * @param $sid
+ * The session id, allowing someone to use Session API or their own solution;
+ * defaults to session_id().
+ */
+function ctools_object_cache_set($obj, $name, $cache, $sid = NULL) {
+ // Store the CTools session id in the user session to force a
+ // session for anonymous users in Drupal 7 and Drupal 6 Pressflow.
+ // see http://drupal.org/node/562374, http://drupal.org/node/861778
+ if (empty($GLOBALS['user']->uid) && empty($_SESSION['ctools_session_id'])) {
+ $_SESSION['ctools_hold_session'] = TRUE;
+ }
+
+ ctools_object_cache_clear($obj, $name, $sid);
+
+ if (!$sid) {
+ $sid = session_id();
+ }
+
+ db_insert('ctools_object_cache')
+ ->fields(array(
+ 'sid' => $sid,
+ 'obj' => $obj,
+ 'name' => $name,
+ 'data' => serialize($cache),
+ 'updated' => REQUEST_TIME,
+ ))
+ ->execute();
+}
+
+/**
+ * Remove an object from the non-volatile ctools cache
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ * @param $sid
+ * The session id, allowing someone to use Session API or their own solution;
+ * defaults to session_id().
+ */
+function ctools_object_cache_clear($obj, $name, $sid = NULL) {
+
+ if (!$sid) {
+ $sid = session_id();
+ }
+
+ db_delete('ctools_object_cache')
+ ->condition('sid', $sid)
+ ->condition('obj', $obj)
+ ->condition('name', $name)
+ ->execute();
+ // Ensure the static cache is emptied of this obj:name set.
+ drupal_static_reset('ctools_object_cache_get');
+}
+
+
+/**
+ * Determine if another user has a given object cached.
+ *
+ * This is very useful for 'locking' objects so that only one user can
+ * modify them.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ * @param $sid
+ * The session id, allowing someone to use Session API or their own solution;
+ * defaults to session_id().
+ *
+ * @return
+ * An object containing the UID and updated date if found; NULL if not.
+ */
+function ctools_object_cache_test($obj, $name, $sid = NULL) {
+
+ if (!$sid) {
+ $sid = session_id();
+ }
+
+ return db_query('SELECT s.uid, c.updated FROM {ctools_object_cache} c INNER JOIN {sessions} s ON c.sid = s.sid WHERE s.sid <> :session_id AND c.obj = :obj AND c.name = :name ORDER BY c.updated ASC', array(':session_id' => $sid, ':obj' => $obj, ':name' => $name))
+ ->fetchObject();
+}
+
+/**
+ * Get the cache status of a group of objects.
+ *
+ * This is useful for displaying lock status when listing a number of objects
+ * an an administration UI.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $names
+ * An array of names of objects
+ *
+ * @return
+ * An array of objects containing the UID and updated date for each name found.
+ */
+function ctools_object_cache_test_objects($obj, $names) {
+ return db_query("SELECT c.name, s.uid, c.updated FROM {ctools_object_cache} c INNER JOIN {sessions} s ON c.sid = s.sid WHERE c.obj = :obj AND c.name IN (:names) ORDER BY c.updated ASC", array(':obj' => $obj, ':names' => $names))
+ ->fetchAllAssoc('name');
+}
+
+/**
+ * Remove an object from the non-volatile ctools cache for all session IDs.
+ *
+ * This is useful for clearing a lock.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ */
+function ctools_object_cache_clear_all($obj, $name) {
+ db_delete('ctools_object_cache')
+ ->condition('obj', $obj)
+ ->condition('name', $name)
+ ->execute();
+ // Ensure the static cache is emptied of this obj:name set.
+ $cache = &drupal_static('ctools_object_cache_get', array());
+ unset($cache["$obj:$name"]);
+}
+
+/**
+ * Remove all objects in the object cache that are older than the
+ * specified age.
+ *
+ * @param $age
+ * The minimum age of objects to remove, in seconds. For example, 86400 is
+ * one day. Defaults to 7 days.
+ */
+function ctools_object_cache_clean($age = NULL) {
+ if (empty($age)) {
+ $age = 86400 * 7; // 7 days
+ }
+ db_delete('ctools_object_cache')
+ ->condition('updated', REQUEST_TIME - $age, '<')
+ ->execute();
+}
diff --git a/sites/all/modules/ctools/includes/page-wizard.inc b/sites/all/modules/ctools/includes/page-wizard.inc
new file mode 100644
index 000000000..a211361b2
--- /dev/null
+++ b/sites/all/modules/ctools/includes/page-wizard.inc
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * Fetch metadata on a specific page_wizard plugin.
+ *
+ * @param $page_wizard
+ * Name of a panel page_wizard.
+ *
+ * @return
+ * An array with information about the requested panel page_wizard.
+ */
+function page_manager_get_page_wizard($page_wizard) {
+ ctools_include('plugins');
+ return ctools_get_plugins('page_manager', 'page_wizards', $page_wizard);
+}
+
+/**
+ * Fetch metadata for all page_wizard plugins.
+ *
+ * @return
+ * An array of arrays with information about all available panel page_wizards.
+ */
+function page_manager_get_page_wizards() {
+ ctools_include('plugins');
+ return ctools_get_plugins('page_manager', 'page_wizards');
+}
+
+/**
+ * Get the cached changes to a given wizard.
+ *
+ * @return
+ * A $cache object or a clean cache object if none could be loaded.
+ */
+function page_manager_get_wizard_cache($plugin) {
+ if (is_string($plugin)) {
+ $plugin = page_manager_get_page_wizard($plugin);
+ }
+
+ if (empty($plugin)) {
+ return;
+ }
+
+ ctools_include('object-cache');
+
+ // Since contexts might be cache, include this so they load.
+ ctools_include('context');
+ $cache = ctools_object_cache_get('page_manager_page_wizard', $plugin['name']);
+ if (!$cache) {
+ $cache = page_manager_make_wizard_cache($plugin);
+ }
+
+ return $cache;
+}
+
+function page_manager_make_wizard_cache($plugin) {
+ $cache = new stdClass;
+ $cache->plugin = $plugin;
+ if ($function = ctools_plugin_get_function($plugin, 'default cache')) {
+ $function($cache);
+ }
+
+ return $cache;
+}
+
+/**
+ * Store changes to a task handler in the object cache.
+ */
+function page_manager_set_wizard_cache($cache) {
+ ctools_include('object-cache');
+ ctools_object_cache_set('page_manager_page_wizard', $cache->plugin['name'], $cache);
+}
+
+/**
+ * Remove an item from the object cache.
+ */
+function page_manager_clear_wizard_cache($name) {
+ ctools_include('object-cache');
+ ctools_object_cache_clear('page_manager_page_wizard', $name);
+}
+
+/**
+ * Menu callback for the page wizard.
+ */
+function page_manager_page_wizard($name, $step = NULL) {
+ $plugin = page_manager_get_page_wizard($name);
+ if (!$plugin) {
+ return MENU_NOT_FOUND;
+ }
+
+ // Check for simple access string on plugin.
+ if (!empty($plugin['access']) && !user_access($plugin['access'])) {
+ return MENU_ACCESS_DENIED;
+ }
+
+ // Check for possibly more complex access callback on plugin.
+ if ($function = ctools_plugin_get_function($plugin, 'access callback') && !$function($plugin)) {
+ return MENU_ACCESS_DENIED;
+ }
+
+ // Create a basic wizard.in form info array and merge it with the
+ // plugin's.
+ $form_info = array(
+ 'id' => 'page_manager_page_wizard',
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'show return' => FALSE,
+ 'show cancel' => FALSE,
+ 'next callback' => 'page_manager_page_wizard_next',
+ 'finish callback' => 'page_manager_page_wizard_finish',
+
+ 'path' => "admin/structure/pages/wizard/$name/%step",
+ );
+
+ $form_info = array_merge_recursive($form_info, $plugin['form info']);
+
+ // If step is unset, go with the basic step.
+ if (!isset($step)) {
+ $step = current(array_keys($form_info['order']));
+ $cache = page_manager_make_wizard_cache($plugin);
+ }
+ else {
+ $cache = page_manager_get_wizard_cache($plugin);
+ }
+
+ ctools_include('wizard');
+ $form_state = array(
+ 'plugin' => $plugin,
+ 'wizard cache' => $cache,
+ 'type' => 'edit',
+ 'rerender' => TRUE,
+ 'step' => $step,
+ );
+
+ if (isset($plugin['page title'])) {
+ drupal_set_title($plugin['page title']);
+ }
+
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'start')) {
+ $function($form_info, $step, $form_state);
+ }
+
+ $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
+ return $output;
+}
+
+/**
+ * Callback generated when the add page process is finished.
+ */
+function page_manager_page_wizard_finish(&$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'finish')) {
+ $function($form_state);
+ }
+
+ page_manager_clear_wizard_cache($form_state['wizard cache']->plugin['name']);
+}
+
+/**
+ * Callback generated when the 'next' button is clicked.
+ *
+ * All we do here is store the cache.
+ */
+function page_manager_page_wizard_next(&$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'next')) {
+ $function($form_state);
+ }
+
+ page_manager_set_wizard_cache($form_state['wizard cache']);
+}
+
+/**
+ * Provide a simple administrative list of all wizards.
+ *
+ * This is called as a page callback, but can also be used by any module
+ * that wants to get a list of wizards for its type.
+ */
+function page_manager_page_wizard_list($type = NULL) {
+ $plugins = page_manager_get_page_wizards();
+ if (empty($plugins)) {
+ return '<p>' . t('There are no wizards available at this time.') . '</p>';
+ }
+
+ uasort($plugins, 'ctools_plugin_sort');
+
+ $output = '<dl class="page-manager-wizards">';
+ foreach ($plugins as $id => $plugin) {
+ if (!$type || (isset($plugin['type']) && $plugin['type'] == $type)) {
+ $output .= '<dt>' . l($plugin['title'], 'admin/structure/pages/wizard/' . $id) . '</dt>';
+ $output .= '<dd class="description">' . $plugin['description'] . '</dd>';
+ }
+ }
+ $output .= '</dl>';
+
+ return $output;
+}
diff --git a/sites/all/modules/ctools/includes/page-wizard.menu.inc b/sites/all/modules/ctools/includes/page-wizard.menu.inc
new file mode 100644
index 000000000..7ed932ed1
--- /dev/null
+++ b/sites/all/modules/ctools/includes/page-wizard.menu.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains menu item registration for the page manager page wizards tool.
+ */
+
+function ctools_page_wizard_menu(&$items) {
+ if (!module_exists('page_manager')) {
+ return;
+ }
+
+ $base = array(
+ 'access arguments' => array('use page manager'),
+ 'file' => 'includes/page-wizard.inc',
+ 'type' => MENU_CALLBACK,
+ );
+
+ $items['admin/structure/pages/wizard'] = array(
+ 'title' => 'Wizards',
+ 'page callback' => 'page_manager_page_wizard_list',
+ 'page arguments' => array(4),
+ 'weight' => -5,
+ 'type' => MENU_LOCAL_TASK,
+ ) + $base;
+
+ $items['admin/structure/pages/wizard/%'] = array(
+ 'title' => 'Wizard',
+ 'page callback' => 'page_manager_page_wizard',
+ 'page arguments' => array(4),
+ ) + $base;
+}
diff --git a/sites/all/modules/ctools/includes/plugins-admin.inc b/sites/all/modules/ctools/includes/plugins-admin.inc
new file mode 100644
index 000000000..d4ead0a48
--- /dev/null
+++ b/sites/all/modules/ctools/includes/plugins-admin.inc
@@ -0,0 +1,208 @@
+<?php
+
+/**
+ * @file
+ * Contains generic plugin administration functions.
+ *
+ * CTools includes the ability to (relatively) easily provide wizard based
+ * configuration for plugins, meaning plugins that need configuration can
+ * automatically allow multi-step forms.
+ *
+ * Implementing this
+ */
+/**
+ * Get a plugin configuration form.
+ *
+ * The $form_info and $form_state need to be preconfigured with data you'll need
+ * such as whether or not you're using ajax, or the modal. $form_info will need
+ * your next/submit callbacks so that you can cache your data appropriately.
+ *
+ * @param array $form_info
+ * This form_info *must* contain at least the path. next and finish callbacks
+ * are advisable but not necessary if using wizard's auto caching. Setting
+ * a cache mechanism is advisable. If not using auto caching, next and finish
+ * callbacks will be necessary.
+ *
+ * Also be sure to set up things such as AJAX settings and any temporary
+ * data you will need. Simply setting 'modal' => TRUE and
+ * 'modal return' => TRUE should make this system work well in the modal.
+ *
+ * In addition to standard wizard fields, this supports one extra field:
+ * - 'default form': A callback to a 'wrapper' that will be applied to either
+ * the first or a marked form. This is useful for adding global features that
+ * are applicable to all instances of a plugin, such as identifiers, or
+ * contexts, or the like.
+ *
+ * @param array &$form_state
+ * This is a standard form state array. This system imposes some requirements
+ * on what will be in the form state:
+ *
+ * - 'plugin': The plugin definition being edited.
+ * - 'conf': The configuration that is being edited, presumed to be an array.
+ * Ultimately at the end, this is where the modified config will be
+ * found.
+ * - 'op': The operation, either 'add' or 'edit'. This is used to derive form
+ * names and can also be used to derive default values from the plugin.
+ * - 'step': The current step. May be null if it is the 'first' step, but
+ * generally needs to be defined in the path.
+ *
+ * @param string $default_form
+ * An optional default form that can be added.
+ *
+ * @return
+ * If this function returns false, no form exists.
+ */
+function ctools_plugin_configure_form($form_info, &$form_state) {
+ // Turn the forms defined in the plugin into the format the wizard needs.
+ _ctools_plugin_configure_create_form_info($form_info, $form_state['plugin'], $form_state['op']);
+
+ if (empty($form_info['order'])) {
+ return FALSE;
+ }
+
+ ctools_include('wizard');
+ return ctools_wizard_multistep_form($form_info, $form_state['step'], $form_state);
+}
+
+function _ctools_plugin_configure_create_form_info(&$form_info, $plugin_definition, $op) {
+ // Provide a few extra defaults.
+ $form_info += array(
+ 'id' => 'ctools_plugin_configure_form',
+ 'show back' => TRUE,
+ );
+
+ $add_form = isset($form_info['add form name']) ? $form_info['add form name'] : 'add form';
+ $edit_form = isset($form_info['edit form name']) ? $form_info['edit form name'] : 'edit form';
+
+ // Figure out what the forms should actually be. Since they can be specified
+ // in a couple of different ways (in order to support simple declarations for
+ // the minimal forms but more complex declarations for powerful wizards).
+ if ($op == 'add') {
+ if (!empty($plugin_definition[$add_form])) {
+ $info = $plugin_definition[$add_form];
+ }
+ }
+ if (!isset($info) || $op == 'edit') {
+ // Use the edit form for the add form if add form was completely left off.
+ if (!empty($plugin_definition[$edit_form])) {
+ $info = $plugin_definition[$edit_form];
+ }
+ }
+
+ // If there is a default form wrapper, but no form is supplied,
+ // use the wrapper as the form.
+ if (empty($info) && !empty($form_info['default form'])) {
+ $info = $form_info['default form'];
+ }
+
+ // @todo we may want to make these titles more settable?
+ if (is_string($info)) {
+ if (empty($plugin_definition['title'])) {
+ $title = t('Configure');
+ }
+ else if ($op == 'add') {
+ $title = t('Configure new !plugin_title', array('!plugin_title' => $plugin_definition['title']));
+ }
+ else {
+ $title = t('Configure !plugin_title', array('!plugin_title' => $plugin_definition['title']));
+ }
+ if (empty($form_info['order'])) {
+ $form_info['order'] = array();
+ }
+ $form_info['order']['form'] = $title;
+
+ if (empty($form_info['forms'])) {
+ $form_info['forms'] = array();
+ }
+ $form_info['forms']['form'] = array(
+ 'title' => $title,
+ 'form id' => $info,
+ );
+
+ // Add the default form if one is specified.
+ if (!empty($form_info['default form']) && $form_info['forms']['form']['form id'] != $form_info['default form']) {
+ $form_info['forms']['form']['wrapper'] = $form_info['default form'];
+ }
+
+ // If no submit is supplied, supply the default submit which will do the
+ // most obvious task.
+ if (!function_exists($form_info['forms']['form']['form id'] . '_submit')) {
+ // Store the original wrapper so we can chain it.
+ if (!empty($form_info['forms']['form']['wrapper'])) {
+ $form_info['forms']['form']['original wrapper'] = $form_info['forms']['form']['wrapper'];
+ }
+ $form_info['forms']['form']['wrapper'] = 'ctools_plugins_default_form_wrapper';
+ }
+ }
+ else if (is_array($info)) {
+ if (empty($form_info['order'])) {
+ $form_info['order'] = array();
+ }
+ if (empty($form_info['forms'])) {
+ $form_info['forms'] = array();
+ }
+ $count = 0;
+ $base = 'step';
+ $wrapper = NULL;
+ foreach ($info as $form_id => $title) {
+ $step = $base . ++$count;
+ if (empty($wrapper)) {
+ $wrapper = $step;
+ }
+
+ if (is_array($title)) {
+ if (!empty($title['default'])) {
+ $wrapper = $step;
+ }
+ $title = $title['title'];
+ }
+
+ $form_info['order'][$step] = $title;
+ $form_info['forms'][$step] = array(
+ 'title' => $title,
+ 'form id' => $form_id,
+ );
+ }
+ if ($wrapper && !empty($form_info['default form'])) {
+ $form_info['forms'][$wrapper]['wrapper'] = $form_info['default form'];
+ }
+ }
+}
+
+/**
+ * A wrapper to provide a default submit so that plugins don't have to duplicate
+ * a whole bunch of code to do what most of them want to do anyway.
+ */
+function ctools_plugins_default_form_wrapper($form, &$form_state) {
+ $form_info = &$form_state['form_info'];
+ $info = $form_info['forms'][$form_state['step']];
+
+ if (isset($info['original wrapper']) && function_exists($info['original wrapper'])) {
+ $form = $info['original wrapper']($form, $form_state);
+ }
+
+ if (isset($form['buttons']['next'])) {
+ if (empty($form['buttons']['next']['#submit'])) {
+ $form['buttons']['next']['#submit'] = $form['#submit'];
+ }
+ $form['buttons']['next']['#submit'][] = 'ctools_plugins_default_form_wrapper_submit';
+ }
+ if (isset($form['buttons']['return'])) {
+ if (empty($form['buttons']['return']['#submit'])) {
+ $form['buttons']['return']['#submit'] = $form['#submit'];
+ }
+ $form['buttons']['return']['#submit'][] = 'ctools_plugins_default_form_wrapper_submit';
+ }
+ return $form;
+}
+
+/**
+ * Provide a default storage mechanism.
+ */
+function ctools_plugins_default_form_wrapper_submit(&$form, &$form_state) {
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ if (isset($form_state['values'][$key])) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/plugins.inc b/sites/all/modules/ctools/includes/plugins.inc
new file mode 100644
index 000000000..79a6087f3
--- /dev/null
+++ b/sites/all/modules/ctools/includes/plugins.inc
@@ -0,0 +1,917 @@
+<?php
+
+/**
+ * @file
+ *
+ * Contains routines to organize and load plugins. It allows a special
+ * variation of the hook system so that plugins can be kept in separate
+ * .inc files, and can be either loaded all at once or loaded only when
+ * necessary.
+ */
+
+/**
+ * Get an array of information about modules that support an API.
+ *
+ * This will ask each module if they support the given API, and if they do
+ * it will return an array of information about the modules that do.
+ *
+ * This function invokes hook_ctools_api. This invocation is statically
+ * cached, so feel free to call it as often per page run as you like, it
+ * will cost very little.
+ *
+ * This function can be used as an alternative to module_implements and can
+ * thus be used to find a precise list of modules that not only support
+ * a given hook (aka 'api') but also restrict to only modules that use
+ * the given version. This will allow multiple modules moving at different
+ * paces to still be able to work together and, in the event of a mismatch,
+ * either fall back to older behaviors or simply cease loading, which is
+ * still better than a crash.
+ *
+ * @param $owner
+ * The name of the module that controls the API.
+ * @param $api
+ * The name of the api. The api name forms the file name:
+ * $module.$api.inc
+ * @param $minimum_version
+ * The lowest version API that is compatible with this one. If a module
+ * reports its API as older than this, its files will not be loaded. This
+ * should never change during operation.
+ * @param $current_version
+ * The current version of the api. If a module reports its minimum API as
+ * higher than this, its files will not be loaded. This should never change
+ * during operation.
+ *
+ * @return
+ * An array of API information, keyed by module. Each module's information will
+ * contain:
+ * - 'version': The version of the API required by the module. The module
+ * should use the lowest number it can support so that the widest range
+ * of supported versions can be used.
+ * - 'path': If not provided, this will be the module's path. This is
+ * where the module will store any subsidiary files. This differs from
+ * plugin paths which are figured separately.
+ *
+ * APIs can request any other information to be placed here that they might
+ * need. This should be in the documentation for that particular API.
+ */
+function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version) {
+ $cache = &drupal_static(__FUNCTION__, array());
+ if (!isset($cache[$owner][$api])) {
+ $cache[$owner][$api] = array();
+
+ $hook = ctools_plugin_api_get_hook($owner, $api);
+
+ foreach (module_implements($hook) as $module) {
+ $function = $module . '_' . $hook;
+ $info = $function($owner, $api);
+ $version = NULL;
+ // This is added to make hook_views_api() compatible with this, since
+ // views used a different version key.
+ if (isset($info['version'])) {
+ $version = $info['version'];
+ }
+ else if (isset($info['api'])) {
+ $version = $info['api'];
+ }
+
+ if (!isset($version)) {
+ continue;
+ }
+
+ // Only process if version is between minimum and current, inclusive.
+ if (($version == $minimum_version) || ($version == $current_version)
+ || (version_compare($version, $minimum_version, '>=')
+ && version_compare($version, $current_version, '<='))) {
+ if (!isset($info['path'])) {
+ $info['path'] = drupal_get_path('module', $module);
+ }
+ $cache[$owner][$api][$module] = $info;
+ }
+ }
+
+ // And allow themes to implement these as well.
+ $themes = _ctools_list_themes();
+ foreach ($themes as $name => $theme) {
+ if (!empty($theme->info['api'][$owner][$api])) {
+ $info = $theme->info['api'][$owner][$api];
+ if (!isset($info['version'])) {
+ continue;
+ }
+
+ // Only process if version is between minimum and current, inclusive.
+ if (version_compare($info['version'], $minimum_version, '>=') && version_compare($info['version'], $current_version, '<=')) {
+ if (!isset($info['path'])) {
+ $info['path'] = '';
+ }
+ // Because themes can't easily specify full path, we add it here
+ // even though we do not for modules:
+ $info['path'] = drupal_get_path('theme', $name) . '/' . $info['path'];
+ $cache[$owner][$api][$name] = $info;
+ }
+ }
+ }
+
+ // Allow other modules to hook in.
+ drupal_alter($hook, $cache[$owner][$api], $owner, $api);
+ }
+
+ return $cache[$owner][$api];
+}
+
+/**
+ * Load a group of API files.
+ *
+ * This will ask each module if they support the given API, and if they do
+ * it will load the specified file name. The API and the file name
+ * coincide by design.
+ *
+ * @param $owner
+ * The name of the module that controls the API.
+ * @param $api
+ * The name of the api. The api name forms the file name:
+ * $module.$api.inc, though this can be overridden by the module's response.
+ * @param $minimum_version
+ * The lowest version API that is compatible with this one. If a module
+ * reports its API as older than this, its files will not be loaded. This
+ * should never change during operation.
+ * @param $current_version
+ * The current version of the api. If a module reports its minimum API as
+ * higher than this, its files will not be loaded. This should never change
+ * during operation.
+ *
+ * @return
+ * The API information, in case you need it.
+ */
+function ctools_plugin_api_include($owner, $api, $minimum_version, $current_version) {
+ static $already_done = array();
+
+ $info = ctools_plugin_api_info($owner, $api, $minimum_version, $current_version);
+ foreach ($info as $module => $plugin_info) {
+ if (!isset($already_done[$owner][$api][$module])) {
+ if (isset($plugin_info["$api file"])) {
+ $file = $plugin_info["$api file"];
+ }
+ else if (isset($plugin_info['file'])) {
+ $file = $plugin_info['file'];
+ }
+ else {
+ $file = "$module.$api.inc";
+ }
+
+ if (file_exists(DRUPAL_ROOT . "/$plugin_info[path]/$file")) {
+ require_once DRUPAL_ROOT . "/$plugin_info[path]/$file";
+ }
+ else if (file_exists(DRUPAL_ROOT . "/$file")) {
+ require_once DRUPAL_ROOT . "/$file";
+ }
+ $already_done[$owner][$api][$module] = TRUE;
+ }
+ }
+
+ return $info;
+}
+
+/**
+ * Find out what hook to use to determine if modules support an API.
+ *
+ * By default, most APIs will use hook_ctools_plugin_api, but some modules
+ * want sole ownership. This technique lets modules define what hook
+ * to use.
+ */
+function ctools_plugin_api_get_hook($owner, $api) {
+ // Allow modules to use their own hook for this. The only easy way to do
+ // this right now is with a magically named function.
+ if (function_exists($function = $owner . '_' . $api . '_hook_name')) {
+ $hook = $function();
+ }
+ else if (function_exists($function = $owner . '_ctools_plugin_api_hook_name')) {
+ $hook = $function();
+ }
+
+ // Do this last so that if the $function above failed to return, we have a
+ // sane default.
+ if (empty($hook)) {
+ $hook = 'ctools_plugin_api';
+ }
+
+ return $hook;
+}
+
+/**
+ * Fetch a group of plugins by name.
+ *
+ * @param $module
+ * The name of the module that utilizes this plugin system. It will be
+ * used to call hook_ctools_plugin_$plugin() to get more data about the plugin.
+ * @param $type
+ * The type identifier of the plugin.
+ * @param $id
+ * If specified, return only information about plugin with this identifier.
+ * The system will do its utmost to load only plugins with this id.
+ *
+ * @return
+ * An array of information arrays about the plugins received. The contents
+ * of the array are specific to the plugin.
+ */
+function ctools_get_plugins($module, $type, $id = NULL) {
+ // Store local caches of plugins and plugin info so we don't have to do full
+ // lookups every time.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['plugins'] = &drupal_static('ctools_plugins', array());
+ }
+ $plugins = &$drupal_static_fast['plugins'];
+
+ $info = ctools_plugin_get_plugin_type_info();
+
+ // Bail out noisily if an invalid module/type combination is requested.
+ if (!isset($info[$module][$type])) {
+ watchdog('ctools', 'Invalid plugin module/type combination requested: module @module and type @type', array('@module' => $module, '@type' => $type), WATCHDOG_ERROR);
+ return array();
+ }
+
+ // Make sure our plugins array is populated.
+ if (!isset($plugins[$module][$type])) {
+ $plugins[$module][$type] = array();
+ }
+
+ // Attempt to shortcut this whole piece of code if we already have
+ // the requested plugin:
+ if ($id && array_key_exists($id, $plugins[$module][$type])) {
+ return $plugins[$module][$type][$id];
+ }
+
+ // Store the status of plugin loading. If a module plugin type pair is true,
+ // then it is fully loaded and no searching or setup needs to be done.
+ $setup = &drupal_static('ctools_plugin_setup', array());
+
+ // We assume we don't need to build a cache.
+ $build_cache = FALSE;
+
+ // If the plugin info says this can be cached, check cache first.
+ if ($info[$module][$type]['cache'] && empty($setup[$module][$type])) {
+ $cache = cache_get("plugins:$module:$type", $info[$module][$type]['cache table']);
+
+ if (!empty($cache->data)) {
+ // Cache load succeeded so use the cached plugin list.
+ $plugins[$module][$type] = $cache->data;
+ // Set $setup to true so we know things where loaded.
+ $setup[$module][$type] = TRUE;
+ }
+ else {
+ // Cache load failed so store that we need to build and write the cache.
+ $build_cache = TRUE;
+ }
+ }
+
+ // Always load all hooks if we need them. Note we only need them now if the
+ // plugin asks for them. We can assume that if we have plugins we've already
+ // called the global hook.
+ if (!empty($info[$module][$type]['use hooks']) && empty($plugins[$module][$type])) {
+ $plugins[$module][$type] = ctools_plugin_load_hooks($info[$module][$type]);
+ }
+
+ // Then see if we should load all files. We only do this if we
+ // want a list of all plugins or there was a cache miss.
+ if (empty($setup[$module][$type]) && ($build_cache || !$id)) {
+ $setup[$module][$type] = TRUE;
+ $plugins[$module][$type] = array_merge($plugins[$module][$type], ctools_plugin_load_includes($info[$module][$type]));
+ // If the plugin can have child plugins, and we're loading all plugins,
+ // go through the list of plugins we have and find child plugins.
+ if (!$id && !empty($info[$module][$type]['child plugins'])) {
+ // If a plugin supports children, go through each plugin and ask.
+ $temp = array();
+ foreach ($plugins[$module][$type] as $name => $plugin) {
+ // The strpos ensures that we don't try to find children for plugins
+ // that are already children.
+ if (!empty($plugin['get children']) && function_exists($plugin['get children']) && strpos($name, ':') === FALSE) {
+ $temp = array_merge($plugin['get children']($plugin, $name), $temp);
+ }
+ else {
+ $temp[$name] = $plugin;
+ }
+ }
+ $plugins[$module][$type] = $temp;
+ }
+ }
+
+
+ // If we were told earlier that this is cacheable and the cache was
+ // empty, give something back.
+ if ($build_cache) {
+ cache_set("plugins:$module:$type", $plugins[$module][$type], $info[$module][$type]['cache table']);
+ }
+
+ // If no id was requested, we are finished here:
+ if (!$id) {
+ // Use array_filter because looking for unknown plugins could cause NULL
+ // entries to appear in the list later.
+ return array_filter($plugins[$module][$type]);
+ }
+
+ // Check to see if we need to look for the file
+ if (!array_key_exists($id, $plugins[$module][$type])) {
+ // If we can have child plugins, check to see if the plugin name is in the
+ // format of parent:child and break it up if it is.
+ if (!empty($info[$module][$type]['child plugins']) && strpos($id, ':') !== FALSE) {
+ list($parent, $child) = explode(':', $id, 2);
+ }
+ else {
+ $parent = $id;
+ }
+
+ if (!array_key_exists($parent, $plugins[$module][$type])) {
+ $result = ctools_plugin_load_includes($info[$module][$type], $parent);
+ // Set to either what was returned or NULL.
+ $plugins[$module][$type][$parent] = isset($result[$parent]) ? $result[$parent] : NULL;
+ }
+
+ // If we are looking for a child, and have the parent, ask the parent for the child.
+ if (!empty($child) && !empty($plugins[$module][$type][$parent]) && function_exists($plugins[$module][$type][$parent]['get child'])) {
+ $plugins[$module][$type][$id] = $plugins[$module][$type][$parent]['get child']($plugins[$module][$type][$parent], $parent, $child);
+ }
+ }
+
+ // At this point we should either have the plugin, or a NULL.
+ return $plugins[$module][$type][$id];
+}
+
+/**
+ * Return the full list of plugin type info for all plugin types registered in
+ * the current system.
+ *
+ * This function manages its own cache getting/setting, and should always be
+ * used as the way to initially populate the list of plugin types. Make sure you
+ * call this function to properly populate the ctools_plugin_type_info static
+ * variable.
+ *
+ * @return array
+ * A multilevel array of plugin type info, the outer array keyed on module
+ * name and each inner array keyed on plugin type name.
+ */
+function ctools_plugin_get_plugin_type_info($flush = FALSE) {
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['info_loaded'] = &drupal_static('ctools_plugin_type_info_loaded', FALSE);
+ $drupal_static_fast['all_type_info'] = &drupal_static('ctools_plugin_type_info', array());
+ }
+ $info_loaded = &$drupal_static_fast['info_loaded'];
+ $all_type_info = &$drupal_static_fast['all_type_info'];
+
+ // Only trigger info loading once.
+ if ($info_loaded && !$flush) {
+ return $all_type_info;
+ }
+ $info_loaded = TRUE;
+
+ $cache = cache_get('ctools_plugin_type_info');
+ if (!empty($cache->data) && !$flush) {
+ // Plugin type info cache is warm, use it.
+ $all_type_info = $cache->data;
+ }
+ else {
+ // Cache expired, refill it.
+ foreach (module_implements('ctools_plugin_type') as $module) {
+ $module_infos = array();
+ $function = $module . '_ctools_plugin_type';
+ $module_infos = $function();
+
+ foreach ($module_infos as $plugin_type_name => $plugin_type_info) {
+ // Apply defaults. Array addition will not overwrite pre-existing keys.
+ $plugin_type_info += array(
+ 'module' => $module,
+ 'type' => $plugin_type_name,
+ 'cache' => FALSE,
+ 'cache table' => 'cache',
+ 'classes' => array(),
+ 'use hooks' => FALSE,
+ 'defaults' => array(),
+ 'process' => '',
+ 'alterable' => TRUE,
+ 'extension' => 'inc',
+ 'info file' => FALSE,
+ 'hook' => $module . '_' . $plugin_type_name,
+ 'load themes' => FALSE,
+ );
+ $all_type_info[$module][$plugin_type_name] = $plugin_type_info;
+ }
+ }
+ cache_set('ctools_plugin_type_info', $all_type_info);
+ }
+
+ return $all_type_info;
+}
+
+/**
+ * Reset all static caches that affect the result of ctools_get_plugins().
+ */
+function ctools_get_plugins_reset() {
+ drupal_static_reset('ctools_plugins');
+ drupal_static_reset('ctools_plugin_setup');
+ drupal_static_reset('ctools_plugin_load_includes');
+ drupal_static_reset('ctools_plugin_api_info');
+}
+
+/**
+ * Load plugins from a directory.
+ *
+ * @param $info
+ * The plugin info as returned by ctools_plugin_get_info()
+ * @param $file
+ * The file to load if we're looking for just one particular plugin.
+ *
+ * @return
+ * An array of information created for this plugin.
+ */
+function ctools_plugin_load_includes($info, $filename = NULL) {
+ // Keep a static array so we don't hit file_scan_directory more than necessary.
+ $all_files = &drupal_static(__FUNCTION__, array());
+
+ // store static of plugin arrays for reference because they can't be reincluded.
+ static $plugin_arrays = array();
+
+ if (!isset($all_files[$info['module']][$info['type']])) {
+ $cache = cache_get("ctools_plugin_files:$info[module]:$info[type]");
+ if ($cache) {
+ $all_files[$info['module']][$info['type']] = $cache->data;
+ }
+ // Do not attempt any file scan even if the cached entry was empty.
+ // A NULL entry here would mean the plugin just does not exists, and we
+ // cannot afford to run file scan on production sites normal run.
+ elseif (!isset($all_files[$info['module']][$info['type']])) {
+ $all_files[$info['module']][$info['type']] = array();
+ // Load all our plugins.
+ $directories = ctools_plugin_get_directories($info);
+ $extension = (empty($info['info file']) || ($info['extension'] != 'inc')) ? $info['extension'] : 'info';
+
+ foreach ($directories as $module => $path) {
+ $all_files[$info['module']][$info['type']][$module] = file_scan_directory($path, '/\.' . $extension . '$/', array('key' => 'name'));
+ }
+
+ cache_set("ctools_plugin_files:$info[module]:$info[type]", $all_files[$info['module']][$info['type']]);
+ }
+ }
+ $file_list = $all_files[$info['module']][$info['type']];
+ $plugins = array();
+
+ // Iterate through all the plugin .inc files, load them and process the hook
+ // that should now be available.
+ foreach (array_filter($file_list) as $module => $files) {
+ if ($filename) {
+ $files = isset($files[$filename]) ? array($filename => $files[$filename]) : array();
+ }
+ foreach ($files as $file) {
+ if (!empty($info['info file'])) {
+ // Parse a .info file
+ $result = ctools_plugin_process_info($info, $module, $file);
+ }
+ else {
+ // Parse a hook.
+ $plugin = NULL; // ensure that we don't have something leftover from earlier.
+
+ if (isset($plugin_arrays[$file->uri])) {
+ $identifier = $plugin_arrays[$file->uri];
+ }
+ else {
+
+ require_once DRUPAL_ROOT . '/' . $file->uri;
+ // .inc files have a special format for the hook identifier.
+ // For example, 'foo.inc' in the module 'mogul' using the plugin
+ // whose hook is named 'borg_type' should have a function named (deep breath)
+ // mogul_foo_borg_type()
+
+ // If, however, the .inc file set the quasi-global $plugin array, we
+ // can use that and not even call a function. Set the $identifier
+ // appropriately and ctools_plugin_process() will handle it.
+ if (isset($plugin)) {
+ $plugin_arrays[$file->uri] = $plugin;
+ $identifier = $plugin;
+ }
+ else {
+ $identifier = $module . '_' . $file->name;
+ }
+ }
+
+ $result = ctools_plugin_process($info, $module, $identifier, dirname($file->uri), basename($file->uri), $file->name);
+ }
+ if (is_array($result)) {
+ $plugins = array_merge($plugins, $result);
+ }
+ }
+ }
+ return $plugins;
+}
+
+/**
+ * Get a list of directories to search for plugins of the given type.
+ *
+ * This utilizes hook_ctools_plugin_directory() to determine a complete list of
+ * directories. Only modules that implement this hook and return a string
+ * value will have their directories included.
+ *
+ * @param $info
+ * The $info array for the plugin as returned by ctools_plugin_get_info().
+ *
+ * @return array $directories
+ * An array of directories to search.
+ */
+function ctools_plugin_get_directories($info) {
+ $directories = array();
+
+ foreach (module_implements('ctools_plugin_directory') as $module) {
+ $function = $module . '_ctools_plugin_directory';
+ $result = $function($info['module'], $info['type']);
+ if ($result && is_string($result)) {
+ $directories[$module] = drupal_get_path('module', $module) . '/' . $result;
+ }
+ }
+
+ if (!empty($info['load themes'])) {
+ $themes = _ctools_list_themes();
+ foreach ($themes as $name => $theme) {
+ if (!empty($theme->info['plugins'][$info['module']][$info['type']])) {
+ $directories[$name] = drupal_get_path('theme', $name) . '/' . $theme->info['plugins'][$info['module']][$info['type']];
+ }
+ }
+ }
+ return $directories;
+}
+
+/**
+ * Helper function to build a ctools-friendly list of themes capable of
+ * providing plugins.
+ *
+ * @return array $themes
+ * A list of themes that can act as plugin providers, sorted parent-first with
+ * the active theme placed last.
+ */
+function _ctools_list_themes() {
+ static $themes;
+ if (is_null($themes)) {
+ $current = variable_get('theme_default', FALSE);
+ $themes = $active = array();
+ $all_themes = list_themes();
+ foreach ($all_themes as $name => $theme) {
+ // Only search from active themes
+ if (empty($theme->status) && $theme->name != $current) {
+ continue;
+ }
+ $active[$name] = $theme;
+ // Prior to drupal 6.14, $theme->base_themes does not exist. Build it.
+ if (!isset($theme->base_themes) && !empty($theme->base_theme)) {
+ $active[$name]->base_themes = ctools_find_base_themes($all_themes, $name);
+ }
+ }
+
+ // Construct a parent-first list of all themes
+ foreach ($active as $name => $theme) {
+ $base_themes = isset($theme->base_themes) ? $theme->base_themes : array();
+ $themes = array_merge($themes, $base_themes, array($name => $theme->info['name']));
+ }
+ // Put the actual theme info objects into the array
+ foreach (array_keys($themes) as $name) {
+ if (isset($all_themes[$name])) {
+ $themes[$name] = $all_themes[$name];
+ }
+ }
+
+ // Make sure the current default theme always gets the last word
+ if ($current_key = array_search($current, array_keys($themes))) {
+ $themes += array_splice($themes, $current_key, 1);
+ }
+ }
+ return $themes;
+}
+
+
+/**
+ * Find all the base themes for the specified theme.
+ *
+ * Themes can inherit templates and function implementations from earlier themes.
+ *
+ * NOTE: this is a verbatim copy of system_find_base_themes(), which was not
+ * implemented until 6.14. It is included here only as a fallback for outdated
+ * versions of drupal core.
+ *
+ * @param $themes
+ * An array of available themes.
+ * @param $key
+ * The name of the theme whose base we are looking for.
+ * @param $used_keys
+ * A recursion parameter preventing endless loops.
+ * @return
+ * Returns an array of all of the theme's ancestors; the first element's value
+ * will be NULL if an error occurred.
+ */
+function ctools_find_base_themes($themes, $key, $used_keys = array()) {
+ $base_key = $themes[$key]->info['base theme'];
+ // Does the base theme exist?
+ if (!isset($themes[$base_key])) {
+ return array($base_key => NULL);
+ }
+
+ $current_base_theme = array($base_key => $themes[$base_key]->info['name']);
+
+ // Is the base theme itself a child of another theme?
+ if (isset($themes[$base_key]->info['base theme'])) {
+ // Do we already know the base themes of this theme?
+ if (isset($themes[$base_key]->base_themes)) {
+ return $themes[$base_key]->base_themes + $current_base_theme;
+ }
+ // Prevent loops.
+ if (!empty($used_keys[$base_key])) {
+ return array($base_key => NULL);
+ }
+ $used_keys[$base_key] = TRUE;
+ return ctools_find_base_themes($themes, $base_key, $used_keys) + $current_base_theme;
+ }
+ // If we get here, then this is our parent theme.
+ return $current_base_theme;
+}
+
+
+/**
+ * Load plugin info for the provided hook; this is handled separately from
+ * plugins from files.
+ *
+ * @param $info
+ * The info array about the plugin as created by ctools_plugin_get_info()
+ *
+ * @return
+ * An array of info supplied by any hook implementations.
+ */
+function ctools_plugin_load_hooks($info) {
+ $hooks = array();
+ foreach (module_implements($info['hook']) as $module) {
+ $result = ctools_plugin_process($info, $module, $module, drupal_get_path('module', $module));
+ if (is_array($result)) {
+ $hooks = array_merge($hooks, $result);
+ }
+ }
+ return $hooks;
+}
+
+/**
+ * Process a single hook implementation of a ctools plugin.
+ *
+ * @param $info
+ * The $info array about the plugin as returned by ctools_plugin_get_info()
+ * @param $module
+ * The module that implements the plugin being processed.
+ * @param $identifier
+ * The plugin identifier, which is used to create the name of the hook
+ * function being called.
+ * @param $path
+ * The path where files utilized by this plugin will be found.
+ * @param $file
+ * The file that was loaded for this plugin, if it exists.
+ * @param $base
+ * The base plugin name to use. If a file was loaded for the plugin, this
+ * is the plugin to assume must be present. This is used to automatically
+ * translate the array to make the syntax more friendly to plugin
+ * implementors.
+ */
+function ctools_plugin_process($info, $module, $identifier, $path, $file = NULL, $base = NULL) {
+ if (is_array($identifier)) {
+ $result = $identifier;
+ }
+ else {
+ $function = $identifier . '_' . $info['hook'];
+ if (!function_exists($function)) {
+ return NULL;
+ }
+ $result = $function($info);
+ if (!isset($result) || !is_array($result)) {
+ return NULL;
+ }
+ }
+
+ // Automatically convert to the proper format that lets plugin implementations
+ // not nest arrays as deeply as they used to, but still support the older
+ // format where they do:
+ if ($base && (!isset($result[$base]) || !is_array($result[$base]))) {
+ $result = array($base => $result);
+ }
+
+ return _ctools_process_data($result, $info, $module, $path, $file);
+}
+
+/**
+ * Fill in default values and run hooks for data loaded for one or
+ * more plugins.
+ */
+function _ctools_process_data($result, $plugin_type_info, $module, $path, $file) {
+ // Fill in global defaults.
+ foreach ($result as $name => $plugin) {
+ $result[$name] += array(
+ 'module' => $module,
+ 'name' => $name,
+ 'path' => $path,
+ 'file' => $file,
+ 'plugin module' => $plugin_type_info['module'],
+ 'plugin type' => $plugin_type_info['type'],
+ );
+
+ // Fill in plugin-specific defaults, if they exist.
+ if (!empty($plugin_type_info['defaults'])) {
+ if (is_array($plugin_type_info['defaults'])) {
+ $result[$name] += $plugin_type_info['defaults'];
+ }
+ }
+
+ // Allow the plugin to be altered before processing.
+ if (!empty($plugin_type_info['alterable']) && $plugin_type_info['alterable']) {
+ drupal_alter('ctools_plugin_pre', $result[$name], $plugin_type_info);
+ }
+
+ // Allow the plugin owner to do additional processing.
+ if (!empty($plugin_type_info['process']) && $function = ctools_plugin_get_function($plugin_type_info, 'process')) {
+ $function($result[$name], $plugin_type_info);
+ }
+
+ // Allow the plugin to be altered after processing.
+ if (!empty($plugin_type_info['alterable']) && $plugin_type_info['alterable']) {
+ drupal_alter('ctools_plugin_post', $result[$name], $plugin_type_info);
+ }
+ }
+ return $result;
+}
+
+
+/**
+ * Process an info file for plugin information, rather than a hook.
+ */
+function ctools_plugin_process_info($info, $module, $file) {
+ $result = drupal_parse_info_file($file->uri);
+ if ($result) {
+ $result = array($file->name => $result);
+ return _ctools_process_data($result, $info, $module, dirname($file->uri), basename($file->uri));
+ }
+}
+
+/**
+ * Ask a module for info about a particular plugin type.
+ */
+function ctools_plugin_get_info($module, $type) {
+ $all_info = ctools_plugin_get_plugin_type_info();
+ return isset($all_info[$module][$type]) ? $all_info[$module][$type] : array();
+}
+
+/**
+ * Get a function from a plugin, if it exists. If the plugin is not already
+ * loaded, try ctools_plugin_load_function() instead.
+ *
+ * @param $plugin_definition
+ * The loaded plugin type.
+ * @param $function_name
+ * The identifier of the function. For example, 'settings form'.
+ *
+ * @return
+ * The actual name of the function to call, or NULL if the function
+ * does not exist.
+ */
+function ctools_plugin_get_function($plugin_definition, $function_name) {
+ // If cached the .inc file may not have been loaded. require_once is quite safe
+ // and fast so it's okay to keep calling it.
+ if (isset($plugin_definition['file'])) {
+ // Plugins that are loaded from info files have the info file as
+ // $plugin['file']. Don't try to run those.
+ $info = ctools_plugin_get_info($plugin_definition['plugin module'], $plugin_definition['plugin type']);
+ if (empty($info['info file'])) {
+ require_once DRUPAL_ROOT . '/' . $plugin_definition['path'] . '/' . $plugin_definition['file'];
+ }
+ }
+
+ if (!isset($plugin_definition[$function_name])) {
+ return;
+ }
+
+ if (is_array($plugin_definition[$function_name]) && isset($plugin_definition[$function_name]['function'])) {
+ $function = $plugin_definition[$function_name]['function'];
+ if (isset($plugin_definition[$function_name]['file'])) {
+ $file = $plugin_definition[$function_name]['file'];
+ if (isset($plugin_definition[$function_name]['path'])) {
+ $file = $plugin_definition[$function_name]['path'] . '/' . $file;
+ }
+ require_once DRUPAL_ROOT . '/' . $file;
+ }
+ }
+ else {
+ $function = $plugin_definition[$function_name];
+ }
+
+ if (function_exists($function)) {
+ return $function;
+ }
+}
+
+/**
+ * Load a plugin and get a function name from it, returning success only
+ * if the function exists.
+ *
+ * @param $module
+ * The module that owns the plugin type.
+ * @param $type
+ * The type of plugin.
+ * @param $id
+ * The id of the specific plugin to load.
+ * @param $function_name
+ * The identifier of the function. For example, 'settings form'.
+ *
+ * @return
+ * The actual name of the function to call, or NULL if the function
+ * does not exist.
+ */
+function ctools_plugin_load_function($module, $type, $id, $function_name) {
+ $plugin = ctools_get_plugins($module, $type, $id);
+ return ctools_plugin_get_function($plugin, $function_name);
+}
+
+/**
+ * Get a class from a plugin, if it exists. If the plugin is not already
+ * loaded, try ctools_plugin_load_class() instead.
+ *
+ * @param $plugin_definition
+ * The loaded plugin type.
+ * @param $class_name
+ * The identifier of the class. For example, 'handler'.
+ *
+ * @return
+ * The actual name of the class to call, or NULL if the class does not exist.
+ */
+function ctools_plugin_get_class($plugin_definition, $class_name) {
+ // If cached the .inc file may not have been loaded. require_once is quite safe
+ // and fast so it's okay to keep calling it.
+ if (isset($plugin_definition['file'])) {
+ // Plugins that are loaded from info files have the info file as
+ // $plugin['file']. Don't try to run those.
+ $info = ctools_plugin_get_info($plugin_definition['plugin module'], $plugin_definition['plugin type']);
+ if (empty($info['info file'])) {
+ require_once DRUPAL_ROOT . '/' . $plugin_definition['path'] . '/' . $plugin_definition['file'];
+ }
+ }
+
+ $return = FALSE;
+ if (!isset($plugin_definition[$class_name])) {
+ return;
+ }
+ else if (is_string($plugin_definition[$class_name])) {
+ // Plugin uses the string form shorthand.
+ $return = $plugin_definition[$class_name];
+ }
+ else if (isset($plugin_definition[$class_name]['class'])) {
+ // Plugin uses the verbose array form.
+ $return = $plugin_definition[$class_name]['class'];
+ }
+ // @todo consider adding an else {watchdog(...)} here
+
+ return ($return && class_exists($return)) ? $return : NULL;
+}
+
+/**
+ * Load a plugin and get a class name from it, returning success only if the
+ * class exists.
+ *
+ * @param $module
+ * The module that owns the plugin type.
+ * @param $type
+ * The type of plugin.
+ * @param $id
+ * The id of the specific plugin to load.
+ * @param $class_name
+ * The identifier of the class. For example, 'handler'.
+ *
+ * @return
+ * The actual name of the class to call, or NULL if the class does not exist.
+ */
+function ctools_plugin_load_class($module, $type, $id, $class_name) {
+ $plugin = ctools_get_plugins($module, $type, $id);
+ return ctools_plugin_get_class($plugin, $class_name);
+}
+
+/**
+ * Sort callback for sorting plugins naturally.
+ *
+ * Sort first by weight, then by title.
+ */
+function ctools_plugin_sort($a, $b) {
+ if (is_object($a)) {
+ $a = (array) $a;
+ }
+ if (is_object($b)) {
+ $b = (array) $b;
+ }
+
+ if (empty($a['weight'])) {
+ $a['weight'] = 0;
+ }
+
+ if (empty($b['weight'])) {
+ $b['weight'] = 0;
+ }
+
+ if ($a['weight'] == $b['weight']) {
+ return strnatcmp(strtolower($a['title']), strtolower($b['title']));
+ }
+ return ($a['weight'] < $b['weight']) ? -1 : 1;
+}
diff --git a/sites/all/modules/ctools/includes/registry.inc b/sites/all/modules/ctools/includes/registry.inc
new file mode 100644
index 000000000..9d4328e69
--- /dev/null
+++ b/sites/all/modules/ctools/includes/registry.inc
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ *
+ * Registry magic. In a separate file to minimize unnecessary code loading.
+ */
+
+/**
+ * Implements (via delegation) hook_registry_files_alter().
+ *
+ * Alter the registry of files to automagically include all classes in
+ * class-based plugins.
+ */
+function _ctools_registry_files_alter(&$files, $indexed_modules) {
+ ctools_include('plugins');
+
+ // Remap the passed indexed modules into a useful format.
+ $modules = array();
+ foreach ($indexed_modules as $module_object) {
+ $modules[$module_object->name] = $module_object;
+ }
+
+ $all_type_info = ctools_plugin_get_plugin_type_info(TRUE);
+ foreach ($all_type_info as $module => $plugin_types) {
+ foreach ($plugin_types as $plugin_type_name => $plugin_type_info) {
+ if (empty($plugin_type_info['classes'])) {
+ // This plugin type does not use classes, so skip it.
+ continue;
+ }
+
+ // Retrieve the full list of plugins of this type.
+ $plugins = ctools_get_plugins($module, $plugin_type_name);
+ foreach ($plugins as $plugin_name => $plugin_definition) {
+ foreach ($plugin_type_info['classes'] as $class_key) {
+ if (empty($plugin_definition[$class_key])) {
+ // Plugin doesn't provide a class for this class key, so skip it.
+ continue;
+ }
+
+ if (is_string($plugin_definition[$class_key])) {
+ // Plugin definition uses the shorthand for defining a class name
+ // and location; look for the containing file by following naming
+ // convention.
+ $path = $plugin_definition['path'] . '/' . $plugin_definition[$class_key] . '.class.php';
+ }
+ else {
+ // Plugin uses the verbose definition to indicate where its class
+ // files are.
+ $class = $plugin_definition[$class_key]['class'];
+ // Use the filename if it's explicitly set, else follow the naming
+ // conventions.
+ $filename = isset($plugin_definition[$class_key]['file']) ? $plugin_definition[$class_key]['file'] : $class . '.class.php';
+ $base_path = isset($plugin_definition[$class_key]['path']) ? $plugin_definition[$class_key]['path'] : $plugin_definition['path'];
+ $path = "$base_path/$filename";
+ }
+
+ if (file_exists($path)) {
+ // If the file exists, add it to the files for registry building.
+ $files[$path] = array('module' => $plugin_definition['module'], 'weight' => $modules[$plugin_definition['module']]->weight);
+ }
+ else {
+ // Else, watchdog that we've got some erroneous plugin info.
+ $args = array(
+ '@plugin' => $plugin_definition['name'],
+ '@owner' => $module,
+ '@type' => $plugin_type_name,
+ '@file' => $path,
+ '@class' => $class_key,
+ );
+ watchdog('ctools', 'Plugin @plugin of plugin type @owner:@type points to nonexistent file @file for class handler @class.', $args);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/stylizer.inc b/sites/all/modules/ctools/includes/stylizer.inc
new file mode 100644
index 000000000..5bc8450cb
--- /dev/null
+++ b/sites/all/modules/ctools/includes/stylizer.inc
@@ -0,0 +1,1654 @@
+<?php
+/**
+ * @file
+ * Create customized CSS and images from palettes created by user input.
+ */
+
+/**
+ * Fetch metadata on a specific style_base plugin.
+ *
+ * @param $content type
+ * Name of a panel content type.
+ *
+ * @return
+ * An array with information about the requested stylizer style base.
+ */
+function ctools_get_style_base($style_base) {
+ ctools_include('plugins');
+ return ctools_get_plugins('stylizer', 'style_bases', $style_base);
+}
+
+/**
+ * Fetch metadata for all style_base plugins.
+ *
+ * @return
+ * An array of arrays with information about all available styleizer style bases.
+ */
+function ctools_get_style_bases() {
+ ctools_include('plugins');
+ return ctools_get_plugins('stylizer', 'style_bases');
+}
+
+/**
+ * Fetch metadata about all of the style base types that are available.
+ */
+function ctools_get_style_base_types() {
+ $types = array();
+ foreach (module_implements('ctools_style_base_types') as $module) {
+ $types[$module] = module_invoke($module, 'ctools_style_base_types');
+ }
+
+ return $types;
+}
+
+/**
+ * Render the icon for a style base.
+ */
+function ctools_stylizer_print_style_icon($plugin, $print_title = TRUE) {
+ $file = $plugin['path'] . '/' . $plugin['icon'];
+ $title = $print_title ? $plugin['title'] : '';
+ return theme('ctools_style_icon', array('image' => theme('image', array('path' => $file)), 'title' => $title));
+}
+
+/**
+ * Theme the style icon image
+ */
+function theme_ctools_style_icon($vars) {
+ $image = $vars['image'];
+ ctools_add_css('stylizer');
+ ctools_add_js('stylizer');
+ $output = '<div class="ctools-style-icon">';
+ $output .= $vars['image'];
+ if ($vars['title']) {
+ $output .= '<div class="caption">' . $vars['title'] . '</div>';
+ }
+ $output .= '</div>';
+ return $output;
+}
+
+/**
+ * Add the necessary CSS for a stylizer plugin to the page.
+ *
+ * This will check to see if the images directory and the cached CSS
+ * exists and, if not, will regenerate everything needed.
+ */
+function ctools_stylizer_add_css($plugin, $settings) {
+ if (!file_exists(ctools_stylizer_get_image_path($plugin, $settings, FALSE))) {
+ ctools_stylizer_build_style($plugin, $settings, TRUE);
+ return;
+ }
+
+ ctools_include('css');
+ $filename = ctools_css_retrieve(ctools_stylizer_get_css_id($plugin, $settings));
+ if (!$filename) {
+ ctools_stylizer_build_style($plugin, $settings, TRUE);
+ }
+ else {
+ drupal_add_css($filename);
+ }
+}
+
+/**
+ * Build the files for a stylizer given the proper settings.
+ */
+function ctools_stylizer_build_style($plugin, $settings, $add_css = FALSE) {
+ $path = ctools_stylizer_get_image_path($plugin, $settings);
+ if (!$path) {
+ return;
+ }
+
+ $replacements = array();
+
+ // Set up palette conversions
+ foreach ($settings['palette'] as $key => $color) {
+ $replacements['%' . $key ] = $color;
+ }
+
+ // Process image actions:
+ if (!empty($plugin['actions'])) {
+ $processor = new ctools_stylizer_image_processor;
+ $processor->execute($path, $plugin, $settings);
+
+// @todo -- there needs to be an easier way to get at this.
+// dsm($processor->message_log);
+ // Add filenames to our conversions.
+ }
+
+ // Convert and write the CSS file.
+ $css = file_get_contents($plugin['path'] . '/' . $plugin['css']);
+
+ // Replace %style keyword with our generated class name.
+ // @todo We need one more unique identifier I think.
+ $class = ctools_stylizer_get_css_class($plugin, $settings);
+ $replacements['%style'] = '.' . $class;
+
+ if (!empty($processor) && !empty($processor->paths)) {
+ foreach ($processor->paths as $file => $image) {
+ $replacements[$file] = file_create_url($image);
+ }
+ }
+
+ if (!empty($plugin['build']) && function_exists($plugin['build'])) {
+ $plugin['build']($plugin, $settings, $css, $replacements);
+ }
+
+ $css = strtr($css, $replacements);
+ ctools_include('css');
+ $filename = ctools_css_store(ctools_stylizer_get_css_id($plugin, $settings), $css, FALSE);
+
+ if ($add_css) {
+ drupal_add_css($filename);
+ }
+}
+
+/**
+ * Clean up no longer used files.
+ *
+ * To prevent excess clutter in the files directory, this should be called
+ * whenever a style is going out of use. When being deleted, but also when
+ * the palette is being changed.
+ */
+function ctools_stylizer_cleanup_style($plugin, $settings) {
+ ctools_include('css');
+ $path = ctools_stylizer_get_image_path($plugin, $settings, FALSE);
+ if ($path) {
+ ctools_stylizer_recursive_delete($path);
+ }
+
+ ctools_css_clear(ctools_stylizer_get_css_id($plugin, $settings));
+}
+
+/**
+ * Recursively delete all files and folders in the specified filepath, then
+ * delete the containing folder.
+ *
+ * Note that this only deletes visible files with write permission.
+ *
+ * @param string $path
+ * A filepath relative to file_directory_path.
+ */
+function ctools_stylizer_recursive_delete($path) {
+ if (empty($path)) {
+ return;
+ }
+
+ $listing = $path . '/*';
+
+ foreach (glob($listing) as $file) {
+ if (is_file($file) === TRUE) {
+ @unlink($file);
+ }
+ elseif (is_dir($file) === TRUE) {
+ ctools_stylizer_recursive_delete($file);
+ }
+ }
+
+ @rmdir($path);
+}
+
+/**
+ * Get a safe name for the settings.
+ *
+ * This uses an md5 of the palette if the name is temporary so
+ * that multiple temporary styles on the same page can coexist
+ * safely.
+ */
+function ctools_stylizer_get_settings_name($settings) {
+ if ($settings['name'] != '_temporary') {
+ return $settings['name'];
+ }
+
+ return $settings['name'] . '-' . md5(serialize($settings['palette']));
+}
+
+/**
+ * Get the path where images will be stored for a given style plugin and settings.
+ *
+ * This function will make sure the path exists.
+ */
+function ctools_stylizer_get_image_path($plugin, $settings, $check = TRUE) {
+ $path = 'public://ctools/style/' . $settings['name'] . '/' . md5(serialize($settings['palette']));
+ if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ drupal_set_message(t('Unable to create CTools styles cache directory @path. Check the permissions on your files directory.', array('@path' => $path)), 'error');
+ return;
+ }
+
+ return $path;
+}
+
+/**
+ * Get the id used to cache CSS for a given style plugin and settings.
+ */
+function ctools_stylizer_get_css_id($plugin, $settings) {
+ return 'ctools-stylizer:' . $settings['name'] . ':' . md5(serialize($settings['palette']));
+}
+
+/**
+ * Get the class to use for a stylizer plugin.
+ */
+function ctools_stylizer_get_css_class($plugin, $settings) {
+ ctools_include('cleanstring');
+ return ctools_cleanstring($plugin['name'] . '-' . ctools_stylizer_get_settings_name($settings));
+}
+
+class ctools_stylizer_image_processor {
+ var $workspace = NULL;
+ var $name = NULL;
+
+ var $workspaces = array();
+
+ var $message_log = array();
+ var $error_log = array();
+
+ function execute($path, $plugin, $settings) {
+ $this->path = $path;
+ $this->plugin = $plugin;
+ $this->settings = $settings;
+ $this->palette = $settings['palette'];
+
+ if (is_string($plugin['actions']) && function_exists($plugin['actions'])) {
+ $actions = $plugin['actions']($plugin, $settings);
+ }
+ else if (is_array($plugin['actions'])) {
+ $actions = $plugin['actions'];
+ }
+
+ if (!empty($actions) && is_array($actions)) {
+ foreach ($plugin['actions'] as $action) {
+ $command = 'command_' . array_shift($action);
+ if (method_exists($this, $command)) {
+ call_user_func_array(array($this, $command), $action);
+ }
+ }
+ }
+
+ // Clean up buffers.
+ foreach ($this->workspaces as $name => $workspace) {
+ imagedestroy($this->workspaces[$name]);
+ }
+ }
+
+ function log($message, $type = 'normal') {
+ $this->message_log[] = $message;
+ if ($type == 'error') {
+ $this->error_log[] = $message;
+ }
+ }
+
+ function set_current_workspace($workspace) {
+ $this->log("Set current workspace: $workspace");
+ $this->workspace = &$this->workspaces[$workspace];
+ $this->name = $workspace;
+ }
+
+ /**
+ * Create a new workspace.
+ */
+ function command_new($name, $width, $height) {
+ $this->log("New workspace: $name ($width x $height)");
+ // Clean up if there was already a workspace there.
+ if (isset($this->workspaces[$name])) {
+ imagedestroy($this->workspaces[$name]);
+ }
+
+ $this->workspaces[$name] = imagecreatetruecolor($width, $height);
+ $this->set_current_workspace($name);
+
+ // Make sure the new workspace has a transparent color.
+
+ // Turn off transparency blending (temporarily)
+ imagealphablending($this->workspace, FALSE);
+
+ // Create a new transparent color for image
+ $color = imagecolorallocatealpha($this->workspace, 0, 0, 0, 127);
+
+ // Completely fill the background of the new image with allocated color.
+ imagefill($this->workspace, 0, 0, $color);
+
+ // Restore transparency blending
+ imagesavealpha($this->workspace, TRUE);
+
+ }
+
+ /**
+ * Create a new workspace a file.
+ *
+ * This will make the new workspace the current workspace.
+ */
+ function command_load($name, $file) {
+ $this->log("New workspace: $name (from $file)");
+ if (!file_exists($file)) {
+ // Try it relative to the plugin
+ $file = $this->plugin['path'] . '/' . $file;
+ if (!file_exists($file)) {
+ $this->log("Unable to open $file");
+ return;
+ }
+ }
+
+ // Clean up if there was already a workspace there.
+ if (isset($this->workspaces[$name])) {
+ imagedestroy($this->workspaces[$name]);
+ }
+
+ $this->workspaces[$name] = imagecreatefrompng($file);
+ $this->set_current_workspace($name);
+ }
+
+ /**
+ * Create a new workspace using the properties of an existing workspace
+ */
+ function command_new_from($name, $workspace) {
+ $this->log("New workspace: $name from existing $workspace");
+ if (empty($this->workspaces[$workspace])) {
+ $this->log("Workspace $name does not exist.", 'error');
+ return;
+ }
+
+ // Clean up if there was already a workspace there.
+ if (isset($this->workspaces[$name])) {
+ imagedestroy($this->workspaces[$name]);
+ }
+
+ $this->workspaces[$name] = $this->new_image($this->workspace[$workspace]);
+ $this->set_current_workspace($name);
+ }
+
+ /**
+ * Set the current workspace.
+ */
+ function command_workspace($name) {
+ $this->log("Set workspace: $name");
+ if (empty($this->workspaces[$name])) {
+ $this->log("Workspace $name does not exist.", 'error');
+ return;
+ }
+ $this->set_current_workspace($name);
+ }
+
+ /**
+ * Copy the contents of one workspace into the current workspace.
+ */
+ function command_merge_from($workspace, $x = 0, $y = 0) {
+ $this->log("Merge from: $workspace ($x, $y)");
+ if (empty($this->workspaces[$workspace])) {
+ $this->log("Workspace $name does not exist.", 'error');
+ return;
+ }
+
+ $this->merge($this->workspaces[$workspace], $this->workspace, $x, $y);
+ }
+
+ function command_merge_to($workspace, $x = 0, $y = 0) {
+ $this->log("Merge to: $workspace ($x, $y)");
+ if (empty($this->workspaces[$workspace])) {
+ $this->log("Workspace $name does not exist.", 'error');
+ return;
+ }
+
+ $this->merge($this->workspace, $this->workspaces[$workspace], $x, $y);
+ $this->set_current_workspace($workspace);
+ }
+
+ /**
+ * Blend an image into the current workspace.
+ */
+ function command_merge_from_file($file, $x = 0, $y = 0) {
+ $this->log("Merge from file: $file ($x, $y)");
+ if (!file_exists($file)) {
+ // Try it relative to the plugin
+ $file = $this->plugin['path'] . '/' . $file;
+ if (!file_exists($file)) {
+ $this->log("Unable to open $file");
+ return;
+ }
+ }
+
+ $source = imagecreatefrompng($file);
+
+ $this->merge($source, $this->workspace, $x, $y);
+
+ imagedestroy($source);
+ }
+
+ function command_fill($color, $x, $y, $width, $height) {
+ $this->log("Fill: $color ($x, $y, $width, $height)");
+ imagefilledrectangle($this->workspace, $x, $y, $x + $width, $y + $height, _color_gd($this->workspace, $this->palette[$color]));
+ }
+
+ function command_gradient($from, $to, $x, $y, $width, $height, $direction = 'down') {
+ $this->log("Gradient: $from to $to ($x, $y, $width, $height) $direction");
+
+ if ($direction == 'down') {
+ for ($i = 0; $i < $height; ++$i) {
+ $color = _color_blend($this->workspace, $this->palette[$from], $this->palette[$to], $i / ($height - 1));
+ imagefilledrectangle($this->workspace, $x, $y + $i, $x + $width, $y + $i + 1, $color);
+ }
+ }
+ else {
+ for ($i = 0; $i < $width; ++$i) {
+ $color = _color_blend($this->workspace, $this->palette[$from], $this->palette[$to], $i / ($width - 1));
+ imagefilledrectangle($this->workspace, $x + $i, $y, $x + $i + 1, $y + $height, $color);
+ }
+ }
+ }
+
+ /**
+ * Colorize the current workspace with the given location.
+ *
+ * This uses simple color blending to colorize the image.
+ *
+ * @todo it is possible that this colorize could allow different methods for
+ * determining how to blend colors?
+ */
+ function command_colorize($color, $x = NULL, $y = NULL, $width = NULL, $height = NULL) {
+ if (!isset($x)) {
+ $whole_image = TRUE;
+ $x = $y = 0;
+ $width = imagesx($this->workspace);
+ $height = imagesy($this->workspace);
+ }
+ $this->log("Colorize: $color ($x, $y, $width, $height)");
+
+ $c = _color_unpack($this->palette[$color]);
+
+ imagealphablending($this->workspace, FALSE);
+ imagesavealpha($this->workspace, TRUE);
+
+ // If PHP 5 use the nice imagefilter which is faster.
+ if (!empty($whole_image) && version_compare(phpversion(), '5.2.5', '>=') && function_exists('imagefilter')) {
+ imagefilter($this->workspace, IMG_FILTER_COLORIZE, $c[0], $c[1], $c[2]);
+ }
+ else {
+ // Otherwise we can do it the brute force way.
+ for ($j = 0; $j < $height; $j++) {
+ for ($i = 0; $i < $width; $i++) {
+ $current = imagecolorsforindex($this->workspace, imagecolorat($this->workspace, $i, $j));
+ $new_index = imagecolorallocatealpha($this->workspace, $c[0], $c[1], $c[2], $current['alpha']);
+ imagesetpixel($this->workspace, $i, $j, $new_index);
+ }
+ }
+ }
+ }
+
+ /**
+ * Colorize the current workspace with the given location.
+ *
+ * This uses a color replacement algorithm that retains luminosity but
+ * turns replaces all color with the specified color.
+ */
+ function command_hue($color, $x = NULL, $y = NULL, $width = NULL, $height = NULL) {
+ if (!isset($x)) {
+ $whole_image = TRUE;
+ $x = $y = 0;
+ $width = imagesx($this->workspace);
+ $height = imagesy($this->workspace);
+ }
+ $this->log("Hue: $color ($x, $y, $width, $height)");
+
+ list($red, $green, $blue) = _color_unpack($this->palette[$color]);
+
+ // We will create a monochromatic palette based on the input color
+ // which will go from black to white.
+
+ // Input color luminosity: this is equivalent to the position of the
+ // input color in the monochromatic palette
+ $luminosity_input = round(255 * ($red + $green + $blue) / 765); // 765 = 255 * 3
+
+ // We fill the palette entry with the input color at itscorresponding position
+ $palette[$luminosity_input]['red'] = $red;
+ $palette[$luminosity_input]['green'] = $green;
+ $palette[$luminosity_input]['blue'] = $blue;
+
+ // Now we complete the palette, first we'll do it to the black, and then to
+ // the white.
+
+ // From input to black
+ $steps_to_black = $luminosity_input;
+
+ // The step size for each component
+ if ($steps_to_black) {
+ $step_size_red = $red / $steps_to_black;
+ $step_size_green = $green / $steps_to_black;
+ $step_size_blue = $blue / $steps_to_black;
+
+ for ($i = $steps_to_black; $i >= 0; $i--) {
+ $palette[$steps_to_black-$i]['red'] = $red - round($step_size_red * $i);
+ $palette[$steps_to_black-$i]['green'] = $green - round($step_size_green * $i);
+ $palette[$steps_to_black-$i]['blue'] = $blue - round($step_size_blue * $i);
+ }
+ }
+
+ // From input to white
+ $steps_to_white = 255 - $luminosity_input;
+
+ if ($steps_to_white) {
+ $step_size_red = (255 - $red) / $steps_to_white;
+ $step_size_green = (255 - $green) / $steps_to_white;
+ $step_size_blue = (255 - $blue) / $steps_to_white;
+ }
+ else {
+ $step_size_red=$step_size_green=$step_size_blue=0;
+ }
+
+ // The step size for each component
+ for ($i = ($luminosity_input + 1); $i <= 255; $i++) {
+ $palette[$i]['red'] = $red + round($step_size_red * ($i - $luminosity_input));
+ $palette[$i]['green'] = $green + round($step_size_green * ($i - $luminosity_input));
+ $palette[$i]['blue']= $blue + round($step_size_blue * ($i - $luminosity_input));
+ }
+
+ // Go over the specified area of the image and update the colors.
+ for ($j = $x; $j < $height; $j++) {
+ for ($i = $y; $i < $width; $i++) {
+ $color = imagecolorsforindex($this->workspace, imagecolorat($this->workspace, $i, $j));
+ $luminosity = round(255 * ($color['red'] + $color['green'] + $color['blue']) / 765);
+ $new_color = imagecolorallocatealpha($this->workspace, $palette[$luminosity]['red'], $palette[$luminosity]['green'], $palette[$luminosity]['blue'], $color['alpha']);
+ imagesetpixel($this->workspace, $i, $j, $new_color);
+ }
+ }
+ }
+
+ /**
+ * Take a slice out of the current workspace and save it as an image.
+ */
+ function command_slice($file, $x = NULL, $y = NULL, $width = NULL, $height = NULL) {
+ if (!isset($x)) {
+ $x = $y = 0;
+ $width = imagesx($this->workspace);
+ $height = imagesy($this->workspace);
+ }
+
+ $this->log("Slice: $file ($x, $y, $width, $height)");
+
+ $base = basename($file);
+ $image = $this->path . '/' . $base;
+
+ $slice = $this->new_image($this->workspace, $width, $height);
+ imagecopy($slice, $this->workspace, 0, 0, $x, $y, $width, $height);
+
+ // Make sure alphas are saved:
+ imagealphablending($slice, FALSE);
+ imagesavealpha($slice, TRUE);
+
+ // Save image.
+ $temp_name = drupal_tempnam('temporary://', 'file');
+
+ imagepng($slice, drupal_realpath($temp_name));
+ file_unmanaged_move($temp_name, $image);
+ imagedestroy($slice);
+
+ // Set standard file permissions for webserver-generated files
+ @chmod(realpath($image), 0664);
+
+ $this->paths[$file] = $image;
+ }
+
+ /**
+ * Prepare a new image for being copied or worked on, preserving transparency.
+ */
+ function &new_image(&$source, $width = NULL, $height = NULL) {
+ if (!isset($width)) {
+ $width = imagesx($source);
+ }
+
+ if (!isset($height)) {
+ $height = imagesy($source);
+ }
+
+ $target = imagecreatetruecolor($width, $height);
+ imagealphablending($target, FALSE);
+ imagesavealpha($target, TRUE);
+
+ $transparency_index = imagecolortransparent($source);
+
+ // If we have a specific transparent color
+ if ($transparency_index >= 0) {
+ // Get the original image's transparent color's RGB values
+ $transparent_color = imagecolorsforindex($source, $transparency_index);
+
+ // Allocate the same color in the new image resource
+ $transparency_index = imagecolorallocate($target, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
+
+ // Completely fill the background of the new image with allocated color.
+ imagefill($target, 0, 0, $transparency_index);
+
+ // Set the background color for new image to transparent
+ imagecolortransparent($target, $transparency_index);
+ }
+ // Always make a transparent background color for PNGs that don't have one allocated already
+ else {
+ // Create a new transparent color for image
+ $color = imagecolorallocatealpha($target, 0, 0, 0, 127);
+
+ // Completely fill the background of the new image with allocated color.
+ imagefill($target, 0, 0, $color);
+ }
+
+ return $target;
+ }
+
+ /**
+ * Merge two images together, preserving alpha transparency.
+ */
+ function merge(&$from, &$to, $x, $y) {
+ // Blend over template.
+ $width = imagesx($from);
+ $height = imagesy($from);
+
+ // Re-enable alpha blending to make sure transparency merges.
+ imagealphablending($to, TRUE);
+ imagecopy($to, $from, $x, $y, 0, 0, $width, $height);
+ imagealphablending($to, FALSE);
+ }
+}
+
+/**
+ * Get the cached changes to a given task handler.
+ */
+function ctools_stylizer_get_settings_cache($name) {
+ ctools_include('object-cache');
+ return ctools_object_cache_get('ctools_stylizer_settings', $name);
+}
+
+/**
+ * Store changes to a task handler in the object cache.
+ */
+function ctools_stylizer_set_settings_cache($name, $settings) {
+ ctools_include('object-cache');
+ ctools_object_cache_set('ctools_stylizer_settings', $name, $settings);
+}
+
+/**
+ * Remove an item from the object cache.
+ */
+function ctools_stylizer_clear_settings_cache($name) {
+ ctools_include('object-cache');
+ ctools_object_cache_clear('ctools_stylizer_settings', $name);
+}
+
+/**
+ * Add a new style of the specified type.
+ */
+function ctools_stylizer_edit_style(&$info, $js, $step = NULL) {
+ $name = '::new';
+ $form_info = array(
+ 'id' => 'ctools_stylizer_edit_style',
+ 'path' => $info['path'],
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'show return' => FALSE,
+ 'next callback' => 'ctools_stylizer_edit_style_next',
+ 'finish callback' => 'ctools_stylizer_edit_style_finish',
+ 'return callback' => 'ctools_stylizer_edit_style_finish',
+ 'cancel callback' => 'ctools_stylizer_edit_style_cancel',
+ 'forms' => array(
+ 'choose' => array(
+ 'form id' => 'ctools_stylizer_edit_style_form_choose',
+ ),
+ ),
+ );
+
+ if (empty($info['settings'])) {
+ $form_info['order'] = array(
+ 'choose' => t('Select base style'),
+ );
+ if (empty($step)) {
+ $step = 'choose';
+ }
+
+ if ($step != 'choose') {
+ $cache = ctools_stylizer_get_settings_cache($name);
+ if (!$cache) {
+ $output = t('Missing settings cache.');
+ if ($js) {
+ return ctools_modal_form_render($form_state, $output);
+ }
+ else {
+ return $output;
+ }
+ }
+
+ if (!empty($cache['owner settings'])) {
+ $info['owner settings'] = $cache['owner settings'];
+ }
+ $settings = $cache['settings'];
+ }
+ else {
+ $settings = array(
+ 'name' => '_temporary',
+ 'style_base' => NULL,
+ 'palette' => array(),
+ );
+ ctools_stylizer_clear_settings_cache($name);
+ }
+ $op = 'add';
+ }
+ else {
+ $cache = ctools_stylizer_get_settings_cache($info['settings']['name']);
+
+ if (!empty($cache)) {
+ if (!empty($cache['owner settings'])) {
+ $info['owner settings'] = $cache['owner settings'];
+ }
+ $settings = $cache['settings'];
+ }
+ else {
+ $settings = $info['settings'];
+ }
+ $op = 'edit';
+ }
+
+ if (!empty($info['op'])) {
+ // Allow this to override. Necessary to allow cloning properly.
+ $op = $info['op'];
+ }
+
+ $plugin = NULL;
+ if (!empty($settings['style_base'])) {
+ $plugin = ctools_get_style_base($settings['style_base']);
+ $info['type'] = $plugin['type'];
+ ctools_stylizer_add_plugin_forms($form_info, $plugin, $op);
+ }
+ else {
+ // This is here so the 'finish' button does not show up, and because
+ // we don't have the selected style we don't know what the next form(s)
+ // will be.
+ $form_info['order']['next'] = t('Configure style');
+ }
+
+ if (count($form_info['order']) < 2 || $step == 'choose') {
+ $form_info['show trail'] = FALSE;
+ }
+
+ $form_state = array(
+ 'module' => $info['module'],
+ 'type' => $info['type'],
+ 'owner info' => &$info,
+ 'base_style_plugin' => $plugin,
+ 'name' => $name,
+ 'step' => $step,
+ 'settings' => $settings,
+ 'ajax' => $js,
+ 'op' => $op,
+ );
+
+ if (!empty($info['modal'])) {
+ $form_state['modal'] = TRUE;
+ $form_state['title'] = $info['modal'];
+ $form_state['modal return'] = TRUE;
+ }
+
+ ctools_include('wizard');
+ $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
+
+ if (!empty($form_state['complete'])) {
+ $info['complete'] = TRUE;
+ $info['settings'] = $form_state['settings'];
+ }
+
+ if ($js && !$output && !empty($form_state['clicked_button']['#next'])) {
+ // We have to do a separate redirect here because the formula that adds
+ // stuff to the wizard after being chosen hasn't happened. The wizard
+ // tried to go to the next step which did not exist.
+ return ctools_stylizer_edit_style($info, $js, $form_state['clicked_button']['#next']);
+ }
+
+ if ($js) {
+ return ctools_modal_form_render($form_state, $output);
+ }
+ else {
+ return $output;
+ }
+}
+
+/**
+ * Add wizard forms specific to a style base plugin.
+ *
+ * The plugin can store forms either as a simple 'edit form'
+ * => 'form callback' or if it needs the more complicated wizard
+ * functionality, it can set 'forms' and 'order' with values suitable
+ * for the wizard $form_info array.
+ *
+ * @param &$form_info
+ * The form info to modify.
+ * @param $plugin
+ * The plugin to use.
+ * @param $op
+ * Either 'add' or 'edit' so we can get the right forms.
+ */
+function ctools_stylizer_add_plugin_forms(&$form_info, $plugin, $op) {
+ if (empty($plugin['forms'])) {
+ if ($op == 'add' && isset($plugin['add form'])) {
+ $id = $plugin['add form'];
+ }
+ else if (isset($plugin['edit form'])) {
+ $id = $plugin['edit form'];
+ }
+ else {
+ $id = 'ctools_stylizer_edit_style_form_default';
+ }
+
+ $form_info['forms']['settings'] = array(
+ 'form id' => $id,
+ );
+ $form_info['order']['settings'] = t('Settings');
+ }
+ else {
+ $form_info['forms'] += $plugin['forms'];
+ $form_info['order'] += $plugin['order'];
+ }
+}
+
+/**
+ * Callback generated when the add style process is finished.
+ */
+function ctools_stylizer_edit_style_finish(&$form_state) {
+ $form_state['complete'] = TRUE;
+ ctools_stylizer_clear_settings_cache($form_state['name']);
+
+ if (isset($form_state['settings']['old_settings'])) {
+ unset($form_state['settings']['old_settings']);
+ }
+}
+
+/**
+ * Callback generated when the 'next' button is clicked.
+ */
+function ctools_stylizer_edit_style_next(&$form_state) {
+ $form_state['form_info']['path'] = str_replace('%name', $form_state['name'], $form_state['form_info']['path']);
+ $form_state['redirect'] = ctools_wizard_get_path($form_state['form_info'], $form_state['clicked_button']['#next']);
+
+ // Update the cache with changes.
+ $cache = array('settings' => $form_state['settings']);
+ if (!empty($form_state['owner info']['owner settings'])) {
+ $cache['owner settings'] = $form_state['owner info']['owner settings'];
+ }
+ ctools_stylizer_set_settings_cache($form_state['name'], $cache);
+}
+
+/**
+ * Callback generated when the 'cancel' button is clicked.
+ *
+ * We might have some temporary data lying around. We must remove it.
+ */
+function ctools_stylizer_edit_style_cancel(&$form_state) {
+ if (!empty($form_state['name'])) {
+ ctools_stylizer_clear_settings_cache($form_state['name']);
+ }
+}
+
+/**
+ * Choose which plugin to use to create a new style.
+ */
+function ctools_stylizer_edit_style_form_choose($form, &$form_state) {
+ $plugins = ctools_get_style_bases();
+ $options = array();
+
+ $categories = array();
+ foreach ($plugins as $name => $plugin) {
+ if ($form_state['module'] == $plugin['module'] && $form_state['type'] == $plugin['type']) {
+ $categories[$plugin['category']] = $plugin['category'];
+ $unsorted_options[$plugin['category']][$name] = ctools_stylizer_print_style_icon($plugin, TRUE);
+ }
+ }
+
+ asort($categories);
+
+ foreach ($categories as $category) {
+ $options[$category] = $unsorted_options[$category];
+ }
+
+ $form['style_base'] = array(
+ '#prefix' => '<div class="ctools-style-icons clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ ctools_include('cleanstring');
+ foreach ($options as $category => $radios) {
+ $cat = ctools_cleanstring($category);
+ $form['style_base'][$cat] = array(
+ '#prefix' => '<div class="ctools-style-category clearfix"><label>' . $category . '</label>',
+ '#suffix' => '</div>',
+ );
+
+ foreach ($radios as $key => $choice) {
+ // Generate the parents as the autogenerator does, so we will have a
+ // unique id for each radio button.
+ $form['style_base'][$cat][$key] = array(
+ '#type' => 'radio',
+ '#title' => $choice,
+ '#parents' => array('style_base'),
+ '#id' => drupal_clean_css_identifier('edit-style-base-' . $key),
+ '#return_value' => check_plain($key),
+ );
+ }
+ }
+
+ return $form;
+}
+
+function ctools_stylizer_edit_style_form_choose_submit($form, &$form_state) {
+ $form_state['settings']['style_base'] = $form_state['values']['style_base'];
+
+ // The 'next' form will show up as 'next' but that's not accurate now that
+ // we have a style. Figure out what next really is and update.
+ $plugin = ctools_get_style_base($form_state['settings']['style_base']);
+ if (empty($plugin['forms'])) {
+ $form_state['clicked_button']['#next'] = 'settings';
+ }
+ else {
+ $forms = array_keys($form_info['forms']);
+ $form_state['clicked_button']['#next'] = array_shift($forms);
+ }
+
+ // Fill in the defaults for the settings.
+ if (!empty($plugin['defaults'])) {
+ // @todo allow a callback
+ $form_state['settings'] += $plugin['defaults'];
+ }
+
+ return $form;
+}
+
+/**
+ * The default stylizer style editing form.
+ *
+ * Even when not using this, styles should call through to this form in
+ * their own edit forms.
+ */
+function ctools_stylizer_edit_style_form_default($form, &$form_state) {
+ ctools_add_js('stylizer');
+ ctools_add_css('stylizer');
+ drupal_add_library('system', 'farbtastic');
+
+ $plugin = &$form_state['base_style_plugin'];
+ $settings = &$form_state['settings'];
+
+ $form['top box'] = array(
+ '#prefix' => '<div id="ctools-stylizer-top-box" class="clearfix">',
+ '#suffix' => '</div>',
+ );
+ $form['top box']['left'] = array(
+ '#prefix' => '<div id="ctools-stylizer-left-box">',
+ '#suffix' => '</div>',
+ );
+ $form['top box']['preview'] = array(
+ // We have a copy of the $form_state on $form because form theme functions
+ // do not get $form_state.
+ '#theme' => 'ctools_stylizer_preview_form',
+ '#form_state' => &$form_state,
+ );
+
+ $form['top box']['preview']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Preview'),
+ );
+
+ if (!empty($plugin['palette'])) {
+ $form['top box']['color'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Color scheme'),
+ '#attributes' => array('id' => 'ctools_stylizer_color_scheme_form', 'class' => array('ctools-stylizer-color-edit')),
+ '#theme' => 'ctools_stylizer_color_scheme_form',
+ );
+
+ $form['top box']['color']['palette']['#tree'] = TRUE;
+
+ foreach ($plugin['palette'] as $key => $color) {
+ if (empty($settings['palette'][$key])) {
+ $settings['palette'][$key] = $color['default_value'];
+ }
+
+ $form['top box']['color']['palette'][$key] = array(
+ '#type' => 'textfield',
+ '#title' => $color['label'],
+ '#default_value' => $settings['palette'][$key],
+ '#size' => 8,
+ );
+ }
+ }
+
+ if (!empty($plugin['settings form']) && function_exists($plugin['settings form'])) {
+ $plugin['settings form']($form, $form_state);
+ }
+
+ if (!empty($form_state['owner info']['owner form']) && function_exists($form_state['owner info']['owner form'])) {
+ $form_state['owner info']['owner form']($form, $form_state);
+ }
+
+ return $form;
+}
+
+/**
+ * Theme the stylizer color scheme form.
+ */
+function theme_ctools_stylizer_color_scheme_form($vars) {
+ $form = &$vars['form'];
+ $output = '';
+
+ // Wrapper
+ $output .= '<div class="color-form clearfix">';
+
+ // Color schemes
+// $output .= drupal_render($form['scheme']);
+
+ // Palette
+ $output .= '<div id="palette" class="clearfix">';
+ foreach (element_children($form['palette']) as $name) {
+ $output .= render($form['palette'][$name]);
+ }
+ $output .= '</div>'; // palette
+
+ $output .= '</div>'; // color form
+
+ return $output;
+}
+
+/**
+ * Theme the stylizer preview form.
+ */
+function theme_ctools_stylizer_preview_form($vars) {
+ $form = &$vars['form'];
+
+ $plugin = $form['#form_state']['base_style_plugin'];
+ $settings = $form['#form_state']['settings'];
+
+ if (!empty($form['#form_state']['settings']['old_settings'])) {
+ ctools_stylizer_cleanup_style($plugin, $form['#form_state']['settings']['old_settings']);
+ }
+ $preview = '';
+ if (!empty($plugin['preview'])) {
+ $preview = $plugin['preview'];
+ }
+ else {
+ $base_types = ctools_get_style_base_types();
+ if (!empty($base_types[$plugin['module']][$plugin['type']]['preview'])) {
+ $preview = $base_types[$plugin['module']][$plugin['type']]['preview'];
+ }
+ }
+
+ if (!empty($preview) && function_exists($preview)) {
+ $output = '<fieldset id="preview"><legend>' . t('Preview') . '</legend>';
+ $output .= $preview($plugin, $settings);
+ $output .= drupal_render_children($form);
+ $output .= '</fieldset>';
+
+ return $output;
+ }
+}
+
+function ctools_stylizer_edit_style_form_default_validate($form, &$form_state) {
+ if (!empty($form_state['owner info']['owner form validate']) && function_exists($form_state['owner info']['owner form validate'])) {
+ $form_state['owner info']['owner form validate']($form, $form_state);
+ }
+
+ if (!empty($form_state['base_style_plugin']['settings form validate']) && function_exists($form_state['base_style_plugin']['settings form validate'])) {
+ $form_state['base_style_plugin']['settings form validate']($form, $form_state);
+ }
+}
+
+function ctools_stylizer_edit_style_form_default_submit($form, &$form_state) {
+ // Store old settings for the purposes of cleaning up.
+ $form_state['settings']['old_settings'] = $form_state['settings'];
+ $form_state['settings']['palette'] = $form_state['values']['palette'];
+
+ if (!empty($form_state['owner info']['owner form submit']) && function_exists($form_state['owner info']['owner form submit'])) {
+ $form_state['owner info']['owner form submit']($form, $form_state);
+ }
+
+ if (!empty($form_state['base_style_plugin']['settings form submit']) && function_exists($form_state['base_style_plugin']['settings form submit'])) {
+ $form_state['base_style_plugin']['settings form submit']($form, $form_state);
+ }
+
+ if ($form_state['clicked_button']['#value'] == t('Preview')) {
+ $form_state['rerender'] = TRUE;
+ // Update the cache with changes.
+ if (!empty($form_state['name'])) {
+ $cache = array('settings' => $form_state['settings']);
+ if (!empty($form_state['owner info']['owner settings'])) {
+ $cache['owner settings'] = $form_state['owner info']['owner settings'];
+ }
+ ctools_stylizer_set_settings_cache($form_state['name'], $cache);
+ }
+ }
+}
+
+// --------------------------------------------------------------------------
+// CSS forms and tools that plugins can use.
+
+/**
+ * Font selector form
+ */
+function ctools_stylizer_font_selector_form(&$form, &$form_state, $label, $settings) {
+ // Family
+ $form['#prefix'] = '<div class="ctools-stylizer-spacing-form clearfix">';
+ $form['#type'] = 'fieldset';
+ $form['#title'] = $label;
+ $form['#suffix'] = '</div>';
+ $form['#tree'] = TRUE;
+
+ $form['font'] = array(
+ '#title' => t('Font family'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['font']) ? $settings['font'] : '',
+ '#options' => array(
+ '' => '',
+ 'Arial, Helvetica, sans-serif' => t('Arial, Helvetica, sans-serif'),
+ 'Times New Roman, Times, serif' => t('Times New Roman, Times, serif'),
+ 'Courier New, Courier, monospace' => t('Courier New, Courier, monospace'),
+ 'Georgia, Times New Roman, Times, serif' => t('Georgia, Times New Roman, Times, serif'),
+ 'Verdana, Arial, Helvetica, sans-serif' => t('Verdana, Arial, Helvetica, sans-serif'),
+ 'Geneva, Arial, Helvetica, sans-serif' => t('Geneva, Arial, Helvetica, sans-serif'),
+ 'Trebuchet MS, Trebuchet, Verdana, sans-serif' => t('Trebuchet MS, Trebuchet, Verdana, sans-serif'),
+ ),
+ );
+
+ // size
+ $form['size'] = array(
+ '#title' => t('Size'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['size']) ? $settings['size'] : '',
+ '#options' => array(
+ '' => '',
+ 'xx-small' => t('XX-Small'),
+ 'x-small' => t('X-Small'),
+ 'small' => t('Small'),
+ 'medium' => t('Medium'),
+ 'large' => t('Large'),
+ 'x-large' => t('X-Large'),
+ 'xx-large' => t('XX-Large'),
+ ),
+ );
+
+ // letter spacing
+ $form['letter_spacing'] = array(
+ '#title' => t('Letter spacing'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['letter_spacing']) ? $settings['letter_spacing'] : '',
+ '#options' => array(
+ '' => '',
+ "-10px" => '10px',
+ "-9px" => '9px',
+ "-8px" => '8px',
+ "-7px" => '7px',
+ "-6px" => '6px',
+ "-5px" => '5px',
+ "-4px" => '4px',
+ "-3px" => '3px',
+ "-2px" => '2px',
+ "-1px" => '1px',
+ "0" => '0',
+ "1px" => '1px',
+ "2px" => '2px',
+ "3px" => '3px',
+ "4px" => '4px',
+ "5px" => '5px',
+ "6px" => '6px',
+ "7px" => '7px',
+ "8px" => '8px',
+ "9px" => '9px',
+ "10px" => '10px',
+ "11px" => '11px',
+ "12px" => '12px',
+ "13px" => '13px',
+ "14px" => '14px',
+ "15px" => '15px',
+ "16px" => '16px',
+ "17px" => '17px',
+ "18px" => '18px',
+ "19px" => '19px',
+ "20px" => '20px',
+ "21px" => '21px',
+ "22px" => '22px',
+ "23px" => '23px',
+ "24px" => '24px',
+ "25px" => '25px',
+ "26px" => '26px',
+ "27px" => '27px',
+ "28px" => '28px',
+ "29px" => '29px',
+ "30px" => '30px',
+ "31px" => '31px',
+ "32px" => '32px',
+ "33px" => '33px',
+ "34px" => '34px',
+ "35px" => '35px',
+ "36px" => '36px',
+ "37px" => '37px',
+ "38px" => '38px',
+ "39px" => '39px',
+ "40px" => '40px',
+ "41px" => '41px',
+ "42px" => '42px',
+ "43px" => '43px',
+ "44px" => '44px',
+ "45px" => '45px',
+ "46px" => '46px',
+ "47px" => '47px',
+ "48px" => '48px',
+ "49px" => '49px',
+ "50px" => '50px',
+ ),
+ );
+
+ // word space
+ $form['word_spacing'] = array(
+ '#title' => t('Word spacing'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['word_spacing']) ? $settings['word_spacing'] : '',
+ '#options' => array(
+ '' => '',
+ "-1em" => '-1em',
+ "-0.95em" => '-0.95em',
+ "-0.9em" => '-0.9em',
+ "-0.85em" => '-0.85em',
+ "-0.8em" => '-0.8em',
+ "-0.75em" => '-0.75em',
+ "-0.7em" => '-0.7em',
+ "-0.65em" => '-0.65em',
+ "-0.6em" => '-0.6em',
+ "-0.55em" => '-0.55em',
+ "-0.5em" => '-0.5em',
+ "-0.45em" => '-0.45em',
+ "-0.4em" => '-0.4em',
+ "-0.35em" => '-0.35em',
+ "-0.3em" => '-0.3em',
+ "-0.25em" => '-0.25em',
+ "-0.2em" => '-0.2em',
+ "-0.15em" => '-0.15em',
+ "-0.1em" => '-0.1em',
+ "-0.05em" => '-0.05em',
+ "normal" => 'normal',
+ "0.05em" => '0.05em',
+ "0.1em" => '0.1em',
+ "0.15em" => '0.15em',
+ "0.2em" => '0.2em',
+ "0.25em" => '0.25em',
+ "0.3em" => '0.3em',
+ "0.35em" => '0.35em',
+ "0.4em" => '0.4em',
+ "0.45em" => '0.45em',
+ "0.5em" => '0.5em',
+ "0.55em" => '0.55em',
+ "0.6em" => '0.6em',
+ "0.65em" => '0.65em',
+ "0.7em" => '0.7em',
+ "0.75em" => '0.75em',
+ "0.8em" => '0.8em',
+ "0.85em" => '0.85em',
+ "0.9em" => '0.9em',
+ "0.95em" => '0.95em',
+ "1em" => '1em',
+ ),
+ );
+
+ // decoration
+ $form['decoration'] = array(
+ '#title' => t('Decoration'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['decoration']) ? $settings['decoration'] : '',
+ '#options' => array(
+ '' => '',
+ 'none' => t('None'),
+ 'underline' => t('Underline'),
+ 'overline' => t('Overline'),
+ 'line-through' => t('Line-through'),
+ ),
+ );
+
+ // weight
+ $form['weight'] = array(
+ '#title' => t('Weight'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['weight']) ? $settings['weight'] : '',
+ '#options' => array(
+ '' => '',
+ 'normal' => t('Normal'),
+ 'bold' => t('Bold'),
+ 'bolder' => t('Bolder'),
+ 'lighter' => t('Lighter'),
+ ),
+ );
+
+ // style
+ $form['style'] = array(
+ '#title' => t('Style'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['style']) ? $settings['style'] : '',
+ '#options' => array(
+ '' => '',
+ 'normal' => t('Normal'),
+ 'italic' => t('Italic'),
+ 'oblique' => t('Oblique'),
+ ),
+ );
+
+ // variant
+ $form['variant'] = array(
+ '#title' => t('Variant'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['variant']) ? $settings['variant'] : '',
+ '#options' => array(
+ '' => '',
+ 'normal' => t('Normal'),
+ 'small-caps' => t('Small-caps'),
+ ),
+ );
+
+ // case
+ $form['case'] = array(
+ '#title' => t('Case'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['case']) ? $settings['case'] : '',
+ '#options' => array(
+ '' => '',
+ 'capitalize' => t('Capitalize'),
+ 'uppercase' => t('Uppercase'),
+ 'lowercase' => t('Lowercase'),
+ 'none' => t('None'),
+ ),
+ );
+
+ // alignment
+ $form['alignment'] = array(
+ '#title' => t('Align'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['alignment']) ? $settings['alignment'] : '',
+ '#options' => array(
+ '' => '',
+ 'justify' => t('Justify'),
+ 'left' => t('Left'),
+ 'right' => t('Right'),
+ 'center' => t('Center'),
+ ),
+ );
+}
+
+/**
+ * Copy font selector information into the settings
+ */
+function ctools_stylizer_font_selector_form_submit(&$form, &$form_state, &$values, &$settings) {
+ $settings = $values;
+}
+
+function ctools_stylizer_font_apply_style(&$stylesheet, $selector, $settings) {
+ $css = '';
+ if (isset($settings['font']) && $settings['font'] !== '') {
+ $css .= ' font-family: ' . $settings['font'] . ";\n";
+ }
+
+ if (isset($settings['size']) && $settings['size'] !== '') {
+ $css .= ' font-size: ' . $settings['size'] . ";\n";
+ }
+
+ if (isset($settings['weight']) && $settings['weight'] !== '') {
+ $css .= ' font-weight: ' . $settings['weight'] . ";\n";
+ }
+
+ if (isset($settings['style']) && $settings['style'] !== '') {
+ $css .= ' font-style: ' . $settings['style'] . ";\n";
+ }
+
+ if (isset($settings['variant']) && $settings['variant'] !== '') {
+ $css .= ' font-variant: ' . $settings['variant'] . ";\n";
+ }
+
+ if (isset($settings['case']) && $settings['case'] !== '') {
+ $css .= ' text-transform: ' . $settings['case'] . ";\n";
+ }
+
+ if (isset($settings['decoration']) && $settings['decoration'] !== '') {
+ $css .= ' text-decoration: ' . $settings['decoration'] . ";\n";
+ }
+
+ if (isset($settings['alignment']) && $settings['alignment'] !== '') {
+ $css .= ' text-align: ' . $settings['alignment'] . ";\n";
+ }
+
+ if (isset($settings['letter_spacing']) && $settings['letter_spacing'] !== '') {
+ $css .= ' letter-spacing: ' . $settings['letter_spacing'] . ";\n";
+ }
+
+ if (isset($settings['word_spacing']) && $settings['word_spacing'] !== '') {
+ $css .= ' word-spacing: ' . $settings['word_spacing'] . ";\n";
+ }
+
+ if ($css) {
+ $stylesheet .= $selector . " {\n" . $css . "}\n";
+ }
+}
+
+/**
+ * Border selector form
+ */
+function ctools_stylizer_border_selector_form(&$form, &$form_state, $label, $settings) {
+ // Family
+ $form['#prefix'] = '<div class="ctools-stylizer-spacing-form clearfix">';
+ $form['#type'] = 'fieldset';
+ $form['#title'] = $label;
+ $form['#suffix'] = '</div>';
+ $form['#tree'] = TRUE;
+
+ $form['thickness'] = array(
+ '#title' => t('Thickness'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['thickness']) ? $settings['thickness'] : '',
+ '#options' => array(
+ '' => '',
+ "none" => t('None'),
+ "1px" => '1px',
+ "2px" => '2px',
+ "3px" => '3px',
+ "4px" => '4px',
+ "5px" => '5px',
+ ),
+ );
+
+ $form['style'] = array(
+ '#title' => t('style'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['style']) ? $settings['style'] : '',
+ '#options' => array(
+ '' => '',
+ 'solid' => t('Solid'),
+ 'dotted' => t('Dotted'),
+ 'dashed' => t('Dashed'),
+ 'double' => t('Double'),
+ 'groove' => t('Groove'),
+ 'ridge' => t('Ridge'),
+ 'inset' => t('Inset'),
+ 'outset' => t('Outset'),
+ ),
+ );
+}
+
+/**
+ * Copy border selector information into the settings
+ */
+function ctools_stylizer_border_selector_form_submit(&$form, &$form_state, &$values, &$settings) {
+ $settings = $values;
+}
+
+function ctools_stylizer_border_apply_style(&$stylesheet, $selector, $settings, $color, $which = NULL) {
+ $border = 'border';
+ if ($which) {
+ $border .= '-' . $which;
+ }
+
+ $css = '';
+ if (isset($settings['thickness']) && $settings['thickness'] !== '') {
+ if ($settings['thickness'] == 'none') {
+ $css .= ' ' . $border . ': none';
+ }
+ else {
+ $css .= ' ' . $border . '-width: ' . $settings['thickness'] . ";\n";
+
+ if (isset($settings['style']) && $settings['style'] !== '') {
+ $css .= ' ' . $border . '-style: ' . $settings['style'] . ";\n";
+ }
+
+ $css .= ' ' . $border . '-color: ' . $color . ";\n";
+ }
+ }
+
+ if ($css) {
+ $stylesheet .= $selector . " {\n" . $css . "}\n";
+ }
+
+}
+
+/**
+ * padding selector form
+ */
+function ctools_stylizer_padding_selector_form(&$form, &$form_state, $label, $settings) {
+ // Family
+ $form['#prefix'] = '<div class="ctools-stylizer-spacing-form clearfix">';
+ $form['#type'] = 'fieldset';
+ $form['#title'] = $label;
+ $form['#suffix'] = '</div>';
+ $form['#tree'] = TRUE;
+
+ $options = array(
+ '' => '',
+ "0.05em" => '0.05em',
+ "0.1em" => '0.1em',
+ "0.15em" => '0.15em',
+ "0.2em" => '0.2em',
+ "0.25em" => '0.25em',
+ "0.3em" => '0.3em',
+ "0.35em" => '0.35em',
+ "0.4em" => '0.4em',
+ "0.45em" => '0.45em',
+ "0.5em" => '0.5em',
+ "0.55em" => '0.55em',
+ "0.6em" => '0.6em',
+ "0.65em" => '0.65em',
+ "0.7em" => '0.7em',
+ "0.75em" => '0.75em',
+ "0.8em" => '0.8em',
+ "0.85em" => '0.85em',
+ "0.9em" => '0.9em',
+ "0.95em" => '0.95em',
+ "1.0em" => '1.0em',
+ "1.05em" => '1.05em',
+ "1.1em" => '1.1em',
+ "1.15em" => '1.15em',
+ "1.2em" => '1.2em',
+ "1.25em" => '1.25em',
+ "1.3em" => '1.3em',
+ "1.35em" => '1.35em',
+ "1.4em" => '1.4em',
+ "1.45em" => '1.45em',
+ "1.5em" => '1.5em',
+ "1.55em" => '1.55em',
+ "1.6em" => '1.6em',
+ "1.65em" => '1.65em',
+ "1.7em" => '1.7em',
+ "1.75em" => '1.75em',
+ "1.8em" => '1.8em',
+ "1.85em" => '1.85em',
+ "1.9em" => '1.9em',
+ "1.95em" => '1.95em',
+ "2.0em" => '2.0em',
+ "2.05em" => '2.05em',
+ "2.1em" => '2.1em',
+ "2.15em" => '2.15em',
+ "2.2em" => '2.2em',
+ "2.25em" => '2.25em',
+ "2.3em" => '2.3em',
+ "2.35em" => '2.35em',
+ "2.4em" => '2.4em',
+ "2.45em" => '2.45em',
+ "2.5em" => '2.5em',
+ "2.55em" => '2.55em',
+ "2.6em" => '2.6em',
+ "2.65em" => '2.65em',
+ "2.7em" => '2.7em',
+ "2.75em" => '2.75em',
+ "2.8em" => '2.8em',
+ "2.85em" => '2.85em',
+ "2.9em" => '2.9em',
+ "2.95em" => '2.95em',
+ "3.0em" => '3.0em',
+ "3.05em" => '3.05em',
+ "3.1em" => '3.1em',
+ "3.15em" => '3.15em',
+ "3.2em" => '3.2em',
+ "3.25em" => '3.25em',
+ "3.3em" => '3.3em',
+ "3.35em" => '3.35em',
+ "3.4em" => '3.4em',
+ "3.45em" => '3.45em',
+ "3.5em" => '3.5em',
+ "3.55em" => '3.55em',
+ "3.6em" => '3.6em',
+ "3.65em" => '3.65em',
+ "3.7em" => '3.7em',
+ "3.75em" => '3.75em',
+ "3.8em" => '3.8em',
+ "3.85em" => '3.85em',
+ "3.9em" => '3.9em',
+ "3.95em" => '3.95em',
+ );
+
+ $form['top'] = array(
+ '#title' => t('Top'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['top']) ? $settings['top'] : '',
+ '#options' => $options,
+ );
+
+ $form['right'] = array(
+ '#title' => t('Right'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['right']) ? $settings['right'] : '',
+ '#options' => $options,
+ );
+
+ $form['bottom'] = array(
+ '#title' => t('Bottom'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['bottom']) ? $settings['bottom'] : '',
+ '#options' => $options,
+ );
+
+ $form['left'] = array(
+ '#title' => t('Left'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['left']) ? $settings['left'] : '',
+ '#options' => $options,
+ );
+}
+
+/**
+ * Copy padding selector information into the settings
+ */
+function ctools_stylizer_padding_selector_form_submit(&$form, &$form_state, &$values, &$settings) {
+ $settings = $values;
+}
+
+function ctools_stylizer_padding_apply_style(&$stylesheet, $selector, $settings) {
+ $css = '';
+
+ if (isset($settings['top']) && $settings['top'] !== '') {
+ $css .= ' padding-top: ' . $settings['top'] . ";\n";
+ }
+
+ if (isset($settings['right']) && $settings['right'] !== '') {
+ $css .= ' padding-right: ' . $settings['right'] . ";\n";
+ }
+
+ if (isset($settings['bottom']) && $settings['bottom'] !== '') {
+ $css .= ' padding-bottom: ' . $settings['bottom'] . ";\n";
+ }
+
+ if (isset($settings['left']) && $settings['left'] !== '') {
+ $css .= ' padding-left: ' . $settings['left'] . ";\n";
+ }
+
+ if ($css) {
+ $stylesheet .= $selector . " {\n" . $css . "}\n";
+ }
+
+}
diff --git a/sites/all/modules/ctools/includes/stylizer.theme.inc b/sites/all/modules/ctools/includes/stylizer.theme.inc
new file mode 100644
index 000000000..85346c155
--- /dev/null
+++ b/sites/all/modules/ctools/includes/stylizer.theme.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains theme registry and theme implementations for the content types.
+ */
+
+/**
+ * Implementation of hook_theme to load all content plugins and pass thru if
+ * necessary.
+ */
+function ctools_stylizer_theme(&$theme) {
+ $theme['ctools_stylizer_color_scheme_form'] = array(
+ 'render element' => 'form',
+ 'file' => 'includes/stylizer.inc',
+ );
+
+ $theme['ctools_stylizer_preview_form'] = array(
+ 'render element' => 'form',
+ 'file' => 'includes/stylizer.inc',
+ );
+
+ $theme['ctools_style_icon'] = array(
+ 'variables' => array('image' => NULL, 'title' => NULL),
+ 'file' => 'includes/stylizer.inc',
+ );
+}
+
diff --git a/sites/all/modules/ctools/includes/utility.inc b/sites/all/modules/ctools/includes/utility.inc
new file mode 100644
index 000000000..82fc1471a
--- /dev/null
+++ b/sites/all/modules/ctools/includes/utility.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains general utility functions for CTools that do not need to be
+ * in the module file.
+ *
+ * In particular, things that are only needed during hook_menu() and
+ * hook_theme() are placed here.
+ */
+
+/**
+ * Provide a hook passthrough to included files.
+ *
+ * To organize things neatly, each CTools tool gets its own toolname.$type.inc
+ * file. If it exists, it's loaded and ctools_$tool_$type() is executed.
+ * To save time we pass the $items array in so we don't need to do array
+ * addition. It modifies the array by reference and doesn't need to return it.
+ */
+function ctools_passthrough($module, $type, &$items) {
+ $files = file_scan_directory(drupal_get_path('module', $module) . '/includes', '/\.' . $type . '\.inc$/', array('key' => 'name'));
+ foreach ($files as $file) {
+ require_once DRUPAL_ROOT . '/' . $file->uri;
+ list($tool) = explode('.', $file->name, 2);
+
+ $function = $module . '_' . str_replace ('-', '_', $tool) . '_' . str_replace('-', '_', $type);
+ if (function_exists($function)) {
+ $function($items);
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/uuid.inc b/sites/all/modules/ctools/includes/uuid.inc
new file mode 100644
index 000000000..6e4c42c32
--- /dev/null
+++ b/sites/all/modules/ctools/includes/uuid.inc
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Enables ctools generated modules to use UUIDs without the UUID module enabled.
+ * Per the ctools.module, this file only gets included if UUID doesn't exist.
+ */
+
+/**
+ * Pattern for detecting a valid UUID.
+ */
+define('UUID_PATTERN', '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}');
+
+/**
+ * Generates a UUID using the Windows internal GUID generator.
+ *
+ * @see http://php.net/com_create_guid
+ */
+function _ctools_uuid_generate_com() {
+ // Remove {} wrapper and make lower case to keep result consistent.
+ return drupal_strtolower(trim(com_create_guid(), '{}'));
+}
+
+/**
+ * Generates an universally unique identifier using the PECL extension.
+ */
+function _ctools_uuid_generate_pecl() {
+ $uuid_type = UUID_TYPE_DEFAULT;
+ return uuid_create($uuid_type);
+}
+
+/**
+ * Generates a UUID v4 using PHP code.
+ *
+ * Based on code from @see http://php.net/uniqid#65879 , but corrected.
+ */
+function _ctools_uuid_generate_php() {
+ // The field names refer to RFC 4122 section 4.1.2.
+ return sprintf('%04x%04x-%04x-4%03x-%04x-%04x%04x%04x',
+ // 32 bits for "time_low".
+ mt_rand(0, 65535), mt_rand(0, 65535),
+ // 16 bits for "time_mid".
+ mt_rand(0, 65535),
+ // 12 bits after the 0100 of (version) 4 for "time_hi_and_version".
+ mt_rand(0, 4095),
+ bindec(substr_replace(sprintf('%016b', mt_rand(0, 65535)), '10', 0, 2)),
+ // 8 bits, the last two of which (positions 6 and 7) are 01, for "clk_seq_hi_res"
+ // (hence, the 2nd hex digit after the 3rd hyphen can only be 1, 5, 9 or d)
+ // 8 bits for "clk_seq_low" 48 bits for "node".
+ mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535)
+ );
+}
+
+// This is wrapped in an if block to avoid conflicts with PECL's uuid_is_valid().
+/**
+ * Check that a string appears to be in the format of a UUID.
+ *
+ * @param $uuid
+ * The string to test.
+ *
+ * @return
+ * TRUE if the string is well formed.
+ */
+if (!function_exists('uuid_is_valid')) {
+ function uuid_is_valid($uuid) {
+ return preg_match('/^' . UUID_PATTERN . '$/', $uuid);
+ }
+}
diff --git a/sites/all/modules/ctools/includes/views.inc b/sites/all/modules/ctools/includes/views.inc
new file mode 100644
index 000000000..4ef6439e7
--- /dev/null
+++ b/sites/all/modules/ctools/includes/views.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Generate new context classes by argument settings on the view.
+ */
+function ctools_views_get_argument_context($argument) {
+ if ($argument['type'] == 'context') {
+ if (strpos($argument['context'], '.')) {
+ list($context, $converter) = explode('.', $argument['context'], 2);
+ }
+ else {
+ // Backwards-compat for before we had a system for delimiting the data
+ // we retrieve out of context objects.
+ $context = $argument['context'];
+ }
+ if ($context == 'term' || $context == 'vocabulary') {
+ $context = 'entity:taxonomy_' . $context;
+ }
+ elseif ($entity = entity_get_info($context)) {
+ $context = 'entity:' . $context;
+ }
+ $class = 'ctools_context_' . (empty($argument['context_optional']) ? 'required' : 'optional');
+ $new_context = new $class($argument['label'], $context);
+ return $new_context;
+ }
+}
diff --git a/sites/all/modules/ctools/includes/wizard.inc b/sites/all/modules/ctools/includes/wizard.inc
new file mode 100644
index 000000000..1a821a586
--- /dev/null
+++ b/sites/all/modules/ctools/includes/wizard.inc
@@ -0,0 +1,534 @@
+<?php
+
+/**
+ * @file
+ * CTools' multi-step form wizard tool.
+ *
+ * This tool enables the creation of multi-step forms that go from one
+ * form to another. The forms themselves can allow branching if they
+ * like, and there are a number of configurable options to how
+ * the wizard operates.
+ *
+ * The wizard can also be friendly to ajax forms, such as when used
+ * with the modal tool.
+ *
+ * The wizard provides callbacks throughout the process, allowing the
+ * owner to control the flow. The general flow of what happens is:
+ *
+ * Generate a form
+ * submit a form
+ * based upon button clicked, 'finished', 'next form', 'cancel' or 'return'.
+ *
+ * Each action has its own callback, so cached objects can be modifed and or
+ * turned into real objects. Each callback can make decisions about where to
+ * go next if it wishes to override the default flow.
+ */
+
+/**
+ * Display a multi-step form.
+ *
+ * Aside from the addition of the $form_info which contains an array of
+ * information and configuration so the multi-step wizard can do its thing,
+ * this function works a lot like drupal_build_form.
+ *
+ * Remember that the form builders for this form will receive
+ * &$form, &$form_state, NOT just &$form_state and no additional args.
+ *
+ * @param $form_info
+ * An array of form info. @todo document the array.
+ * @param $step
+ * The current form step.
+ * @param &$form_state
+ * The form state array; this is a reference so the caller can get back
+ * whatever information the form(s) involved left for it.
+ */
+function ctools_wizard_multistep_form($form_info, $step, &$form_state) {
+ // Make sure 'wizard' always exists for the form when dealing
+ // with form caching.
+ ctools_form_include($form_state, 'wizard');
+
+ // allow order array to be optional
+ if (empty($form_info['order'])) {
+ foreach ($form_info['forms'] as $step_id => $params) {
+ $form_info['order'][$step_id] = $params['title'];
+ }
+ }
+
+ if (!isset($step)) {
+ $keys = array_keys($form_info['order']);
+ $step = array_shift($keys);
+ }
+
+ ctools_wizard_defaults($form_info);
+
+ // If automated caching is enabled, ensure that everything is as it
+ // should be.
+ if (!empty($form_info['auto cache'])) {
+ // If the cache mechanism hasn't been set, default to the simple
+ // mechanism and use the wizard ID to ensure uniqueness so cache
+ // objects don't stomp on each other.
+ if (!isset($form_info['cache mechanism'])) {
+ $form_info['cache mechanism'] = 'simple::wizard::' . $form_info['id'];
+ }
+
+ // If not set, default the cache key to the wizard ID. This is often
+ // a unique ID of the object being edited or similar.
+ if (!isset($form_info['cache key'])) {
+ $form_info['cache key'] = $form_info['id'];
+ }
+
+ // If not set, default the cache location to storage. This is often
+ // somnething like 'conf'.
+ if (!isset($form_info['cache location'])) {
+ $form_info['cache location'] = 'storage';
+ }
+
+ // If absolutely nothing was set for the cache area to work on
+ if (!isset($form_state[$form_info['cache location']])) {
+ ctools_include('cache');
+ $form_state[$form_info['cache location']] = ctools_cache_get($form_info['cache mechanism'], $form_info['cache key']);
+ }
+ }
+
+ $form_state['step'] = $step;
+ $form_state['form_info'] = $form_info;
+
+ // Ensure we have form information for the current step.
+ if (!isset($form_info['forms'][$step])) {
+ return;
+ }
+
+ // Ensure that whatever include file(s) were requested by the form info are
+ // actually included.
+ $info = $form_info['forms'][$step];
+
+ if (!empty($info['include'])) {
+ if (is_array($info['include'])) {
+ foreach ($info['include'] as $file) {
+ ctools_form_include_file($form_state, $file);
+ }
+ }
+ else {
+ ctools_form_include_file($form_state, $info['include']);
+ }
+ }
+
+ // This tells drupal_build_form to apply our wrapper to the form. It
+ // will give it buttons and the like.
+ $form_state['wrapper_callback'] = 'ctools_wizard_wrapper';
+ if (!isset($form_state['rerender'])) {
+ $form_state['rerender'] = FALSE;
+ }
+
+ $form_state['no_redirect'] = TRUE;
+
+ $output = drupal_build_form($info['form id'], $form_state);
+
+ if (empty($form_state['executed']) || !empty($form_state['rerender'])) {
+ if (empty($form_state['title']) && !empty($info['title'])) {
+ $form_state['title'] = $info['title'];
+ }
+
+ if (!empty($form_state['ajax render'])) {
+ // Any include files should already be included by this point:
+ return $form_state['ajax render']($form_state, $output);
+ }
+
+ // Automatically use the modal tool if set to true.
+ if (!empty($form_state['modal']) && empty($form_state['modal return'])) {
+ ctools_include('modal');
+
+ // This overwrites any previous commands.
+ $form_state['commands'] = ctools_modal_form_render($form_state, $output);
+ }
+ }
+
+ if (!empty($form_state['executed'])) {
+ // We use the plugins get_function format because it's powerful and
+ // not limited to just functions.
+ ctools_include('plugins');
+
+ if (isset($form_state['clicked_button']['#wizard type'])) {
+ $type = $form_state['clicked_button']['#wizard type'];
+ // If we have a callback depending upon the type of button that was
+ // clicked, call it.
+ if ($function = ctools_plugin_get_function($form_info, "$type callback")) {
+ $function($form_state);
+ }
+
+ // If auto-caching is on, we need to write the cache on next and
+ // clear the cache on finish.
+ if (!empty($form_info['auto cache'])) {
+ if ($type == 'next') {
+ ctools_include('cache');
+ ctools_cache_set($form_info['cache mechanism'], $form_info['cache key'], $form_state[$form_info['cache location']]);
+ }
+ elseif ($type == 'finish') {
+ ctools_include('cache');
+ ctools_cache_clear($form_info['cache mechanism'], $form_info['cache key']);
+ }
+ }
+
+ // Set a couple of niceties:
+ if ($type == 'finish') {
+ $form_state['complete'] = TRUE;
+ }
+
+ if ($type == 'cancel') {
+ $form_state['cancel'] = TRUE;
+ }
+
+ // If the modal is in use, some special code for it:
+ if (!empty($form_state['modal']) && empty($form_state['modal return'])) {
+ if ($type != 'next') {
+ // Automatically dismiss the modal if we're not going to another form.
+ ctools_include('modal');
+ $form_state['commands'][] = ctools_modal_command_dismiss();
+ }
+ }
+ }
+
+ if (empty($form_state['ajax'])) {
+ // redirect, if one is set.
+ if ($form_state['redirect']) {
+ if (is_array($form_state['redirect'])) {
+ call_user_func_array('drupal_goto', $form_state['redirect']);
+ }
+ else {
+ drupal_goto($form_state['redirect']);
+ }
+ }
+ }
+ else if (isset($form_state['ajax next'])) {
+ // Clear a few items off the form state so we don't double post:
+ $next = $form_state['ajax next'];
+ unset($form_state['ajax next']);
+ unset($form_state['executed']);
+ unset($form_state['post']);
+ unset($form_state['next']);
+ return ctools_wizard_multistep_form($form_info, $next, $form_state);
+ }
+
+ // If the callbacks wanted to do something besides go to the next form,
+ // it needs to have set $form_state['commands'] with something that can
+ // be rendered.
+ }
+
+ // Render ajax commands if we have any.
+ if (isset($form_state['ajax']) && isset($form_state['commands']) && empty($form_state['modal return'])) {
+ return ajax_render($form_state['commands']);
+ }
+
+ // Otherwise, return the output.
+ return $output;
+}
+
+/**
+ * Provide a wrapper around another form for adding multi-step information.
+ */
+function ctools_wizard_wrapper($form, &$form_state) {
+ $form_info = &$form_state['form_info'];
+ $info = $form_info['forms'][$form_state['step']];
+
+ // Determine the next form from this step.
+ // Create a form trail if we're supposed to have one.
+ $trail = array();
+ $previous = TRUE;
+ foreach ($form_info['order'] as $id => $title) {
+ if ($id == $form_state['step']) {
+ $previous = FALSE;
+ $class = 'wizard-trail-current';
+ }
+ elseif ($previous) {
+ $not_first = TRUE;
+ $class = 'wizard-trail-previous';
+ $form_state['previous'] = $id;
+ }
+ else {
+ $class = 'wizard-trail-next';
+ if (!isset($form_state['next'])) {
+ $form_state['next'] = $id;
+ }
+ if (empty($form_info['show trail'])) {
+ break;
+ }
+ }
+
+ if (!empty($form_info['show trail'])) {
+ if (!empty($form_info['free trail'])) {
+ // ctools_wizard_get_path() returns results suitable for
+ // $form_state['redirect] which can only be directly used in
+ // drupal_goto. We have to futz a bit with it.
+ $path = ctools_wizard_get_path($form_info, $id);
+ $options = array();
+ if (!empty($path[1])) {
+ $options = $path[1];
+ }
+ $title = l($title, $path[0], $options);
+ }
+ $trail[] = '<span class="' . $class . '">' . $title . '</span>';
+ }
+ }
+
+ // Display the trail if instructed to do so.
+ if (!empty($form_info['show trail'])) {
+ ctools_add_css('wizard');
+ $form['ctools_trail'] = array(
+ '#markup' => theme(array('ctools_wizard_trail__' . $form_info['id'], 'ctools_wizard_trail'), array('trail' => $trail, 'form_info' => $form_info)),
+ '#weight' => -1000,
+ );
+ }
+
+ if (empty($form_info['no buttons'])) {
+ // Ensure buttons stay on the bottom.
+ $form['buttons'] = array(
+ '#type' => 'actions',
+ '#weight' => 1000,
+ );
+
+ $button_attributes = array();
+ if (!empty($form_state['ajax']) && empty($form_state['modal'])) {
+ $button_attributes = array('class' => array('ctools-use-ajax'));
+ }
+
+ if (!empty($form_info['show back']) && isset($form_state['previous'])) {
+ $form['buttons']['previous'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['back text'],
+ '#next' => $form_state['previous'],
+ '#wizard type' => 'next',
+ '#weight' => -2000,
+ '#limit_validation_errors' => array(),
+ // hardcode the submit so that it doesn't try to save data.
+ '#submit' => array('ctools_wizard_submit'),
+ '#attributes' => $button_attributes,
+ );
+
+ if (isset($form_info['no back validate']) || isset($info['no back validate'])) {
+ $form['buttons']['previous']['#validate'] = array();
+ }
+ }
+
+ // If there is a next form, place the next button.
+ if (isset($form_state['next']) || !empty($form_info['free trail'])) {
+ $form['buttons']['next'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['next text'],
+ '#next' => !empty($form_info['free trail']) ? $form_state['step'] : $form_state['next'],
+ '#wizard type' => 'next',
+ '#weight' => -1000,
+ '#attributes' => $button_attributes,
+ );
+ }
+
+ // There are two ways the return button can appear. If this is not the
+ // end of the form list (i.e, there is a next) then it's "update and return"
+ // to be clear. If this is the end of the path and there is no next, we
+ // call it 'Finish'.
+
+ // Even if there is no direct return path (some forms may not want you
+ // leaving in the middle) the final button is always a Finish and it does
+ // whatever the return action is.
+ if (!empty($form_info['show return']) && !empty($form_state['next'])) {
+ $form['buttons']['return'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['return text'],
+ '#wizard type' => 'return',
+ '#attributes' => $button_attributes,
+ );
+ }
+ else if (empty($form_state['next']) || !empty($form_info['free trail'])) {
+ $form['buttons']['return'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['finish text'],
+ '#wizard type' => 'finish',
+ '#attributes' => $button_attributes,
+ );
+ }
+
+ // If we are allowed to cancel, place a cancel button.
+ if ((isset($form_info['cancel path']) && !isset($form_info['show cancel'])) || !empty($form_info['show cancel'])) {
+ $form['buttons']['cancel'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['cancel text'],
+ '#wizard type' => 'cancel',
+ // hardcode the submit so that it doesn't try to save data.
+ '#limit_validation_errors' => array(),
+ '#submit' => array('ctools_wizard_submit'),
+ '#attributes' => $button_attributes,
+ );
+ }
+
+ // Set up optional validate handlers.
+ $form['#validate'] = array();
+ if (function_exists($info['form id'] . '_validate')) {
+ $form['#validate'][] = $info['form id'] . '_validate';
+ }
+ if (isset($info['validate']) && function_exists($info['validate'])) {
+ $form['#validate'][] = $info['validate'];
+ }
+
+ // Set up our submit handler after theirs. Since putting something here will
+ // skip Drupal's autodetect, we autodetect for it.
+
+ // We make sure ours is after theirs so that they get to change #next if
+ // the want to.
+ $form['#submit'] = array();
+ if (function_exists($info['form id'] . '_submit')) {
+ $form['#submit'][] = $info['form id'] . '_submit';
+ }
+ if (isset($info['submit']) && function_exists($info['submit'])) {
+ $form['#submit'][] = $info['submit'];
+ }
+ $form['#submit'][] = 'ctools_wizard_submit';
+ }
+
+ if (!empty($form_state['ajax'])) {
+ $params = ctools_wizard_get_path($form_state['form_info'], $form_state['step']);
+ if (count($params) > 1) {
+ $url = array_shift($params);
+ $options = array();
+
+ $keys = array(0 => 'query', 1 => 'fragment');
+ foreach ($params as $key => $value) {
+ if (isset($keys[$key]) && isset($value)) {
+ $options[$keys[$key]] = $value;
+ }
+ }
+
+ $params = array($url, $options);
+ }
+ $form['#action'] = call_user_func_array('url', $params);
+ }
+
+ if (isset($info['wrapper']) && function_exists($info['wrapper'])) {
+ $form = $info['wrapper']($form, $form_state);
+ }
+
+ if (isset($form_info['wrapper']) && function_exists($form_info['wrapper'])) {
+ $form = $form_info['wrapper']($form, $form_state);
+ }
+ return $form;
+}
+
+/**
+ * On a submit, go to the next form.
+ */
+function ctools_wizard_submit(&$form, &$form_state) {
+ if (isset($form_state['clicked_button']['#wizard type'])) {
+ $type = $form_state['clicked_button']['#wizard type'];
+
+ // if AJAX enabled, we proceed slightly differently here.
+ if (!empty($form_state['ajax'])) {
+ if ($type == 'next') {
+ $form_state['ajax next'] = $form_state['clicked_button']['#next'];
+ }
+ }
+ else {
+ if ($type == 'cancel' && isset($form_state['form_info']['cancel path'])) {
+ $form_state['redirect'] = $form_state['form_info']['cancel path'];
+ }
+ else if ($type == 'next') {
+ $form_state['redirect'] = ctools_wizard_get_path($form_state['form_info'], $form_state['clicked_button']['#next']);
+ if (!empty($_GET['destination'])) {
+ // We don't want drupal_goto redirect this request
+ // back. ctools_wizard_get_path ensures that the destination is
+ // carried over on subsequent pages.
+ unset($_GET['destination']);
+ }
+ }
+ else if (isset($form_state['form_info']['return path'])) {
+ $form_state['redirect'] = $form_state['form_info']['return path'];
+ }
+ else if ($type == 'finish' && isset($form_state['form_info']['cancel path'])) {
+ $form_state['redirect'] = $form_state['form_info']['cancel path'];
+ }
+ }
+ }
+}
+
+/**
+ * Create a path from the form info and a given step.
+ */
+function ctools_wizard_get_path($form_info, $step) {
+ if (is_array($form_info['path'])) {
+ foreach ($form_info['path'] as $id => $part) {
+ $form_info['path'][$id] = str_replace('%step', $step, $form_info['path'][$id]);
+ }
+ $path = $form_info['path'];
+ }
+ else {
+ $path = array(str_replace('%step', $step, $form_info['path']));
+ }
+
+ // If destination is set, carry it over so it'll take effect when
+ // saving. The submit handler will unset destination to avoid drupal_goto
+ // redirecting us.
+ if (!empty($_GET['destination'])) {
+ // Ensure that options is an array.
+ if (!isset($path[1]) || !is_array($path[1])) {
+ $path[1] = array();
+ }
+ // Ensure that the query part of options is an array.
+ $path[1] += array('query' => array());
+ // Add the destination parameter, if not set already.
+ $path[1]['query'] += drupal_get_destination();
+ }
+
+ return $path;
+}
+
+/**
+ * Set default parameters and callbacks if none are given.
+ * Callbacks follows pattern:
+ * $form_info['id']_$hook
+ * $form_info['id']_$form_info['forms'][$step_key]_$hook
+ */
+function ctools_wizard_defaults(&$form_info) {
+ $hook = $form_info['id'];
+ $defaults = array(
+ 'show trail' => FALSE,
+ 'free trail' => FALSE,
+ 'show back' => FALSE,
+ 'show cancel' => FALSE,
+ 'show return' => FALSE,
+ 'next text' => t('Continue'),
+ 'back text' => t('Back'),
+ 'return text' => t('Update and return'),
+ 'finish text' => t('Finish'),
+ 'cancel text' => t('Cancel'),
+ );
+
+ if (!empty($form_info['free trail'])) {
+ $defaults['next text'] = t('Update');
+ $defaults['finish text'] = t('Save');
+ }
+
+ $form_info = $form_info + $defaults;
+ // set form callbacks if they aren't defined
+ foreach ($form_info['forms'] as $step => $params) {
+ if (!$params['form id']) {
+ $form_callback = $hook . '_' . $step . '_form';
+ $form_info['forms'][$step]['form id'] = $form_callback;
+ }
+ }
+
+ // set button callbacks
+ $callbacks = array(
+ 'back callback' => '_back',
+ 'next callback' => '_next',
+ 'return callback' => '_return',
+ 'cancel callback' => '_cancel',
+ 'finish callback' => '_finish',
+ );
+
+ foreach ($callbacks as $key => $callback) {
+ // never overwrite if explicity defined
+ if (empty($form_info[$key])) {
+ $wizard_callback = $hook . $callback;
+ if (function_exists($wizard_callback)) {
+ $form_info[$key] = $wizard_callback;
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/wizard.theme.inc b/sites/all/modules/ctools/includes/wizard.theme.inc
new file mode 100644
index 000000000..c1a26468d
--- /dev/null
+++ b/sites/all/modules/ctools/includes/wizard.theme.inc
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Themable for the wizard tool.
+ */
+
+function ctools_wizard_theme(&$theme) {
+ $theme['ctools_wizard_trail'] = array(
+ 'variables' => array('trail' => NULL, 'form_info' => NULL, 'divider' => ' » '),
+ 'file' => 'includes/wizard.theme.inc',
+ );
+}
+
+/**
+ * Themable display of the 'breadcrumb' trail to show the order of the forms.
+ */
+function theme_ctools_wizard_trail($vars) {
+ if (!empty($vars['trail'])) {
+ return '<div class="wizard-trail">' . implode($vars['divider'], $vars['trail']) . '</div>';
+ }
+}
diff --git a/sites/all/modules/ctools/js/ajax-responder.js b/sites/all/modules/ctools/js/ajax-responder.js
new file mode 100644
index 000000000..1cad618ef
--- /dev/null
+++ b/sites/all/modules/ctools/js/ajax-responder.js
@@ -0,0 +1,126 @@
+/**
+ * @file
+ *
+ * CTools flexible AJAX responder object.
+ */
+
+(function ($) {
+ Drupal.CTools = Drupal.CTools || {};
+ Drupal.CTools.AJAX = Drupal.CTools.AJAX || {};
+ /**
+ * Grab the response from the server and store it.
+ *
+ * @todo restore the warm cache functionality
+ */
+ Drupal.CTools.AJAX.warmCache = function () {
+ // Store this expression for a minor speed improvement.
+ $this = $(this);
+ var old_url = $this.attr('href');
+ // If we are currently fetching, or if we have fetched this already which is
+ // ideal for things like pagers, where the previous page might already have
+ // been seen in the cache.
+ if ($this.hasClass('ctools-fetching') || Drupal.CTools.AJAX.commandCache[old_url]) {
+ return false;
+ }
+
+ // Grab all the links that match this url and add the fetching class.
+ // This allows the caching system to grab each url once and only once
+ // instead of grabbing the url once per <a>.
+ var $objects = $('a[href="' + old_url + '"]')
+ $objects.addClass('ctools-fetching');
+ try {
+ url = old_url.replace(/\/nojs(\/|$)/g, '/ajax$1');
+ $.ajax({
+ type: "POST",
+ url: url,
+ data: { 'js': 1, 'ctools_ajax': 1},
+ global: true,
+ success: function (data) {
+ Drupal.CTools.AJAX.commandCache[old_url] = data;
+ $objects.addClass('ctools-cache-warmed').trigger('ctools-cache-warm', [data]);
+ },
+ complete: function() {
+ $objects.removeClass('ctools-fetching');
+ },
+ dataType: 'json'
+ });
+ }
+ catch (err) {
+ $objects.removeClass('ctools-fetching');
+ return false;
+ }
+
+ return false;
+ };
+
+ /**
+ * Cachable click handler to fetch the commands out of the cache or from url.
+ */
+ Drupal.CTools.AJAX.clickAJAXCacheLink = function () {
+ $this = $(this);
+ if ($this.hasClass('ctools-fetching')) {
+ $this.bind('ctools-cache-warm', function (event, data) {
+ Drupal.CTools.AJAX.respond(data);
+ });
+ return false;
+ }
+ else {
+ if ($this.hasClass('ctools-cache-warmed') && Drupal.CTools.AJAX.commandCache[$this.attr('href')]) {
+ Drupal.CTools.AJAX.respond(Drupal.CTools.AJAX.commandCache[$this.attr('href')]);
+ return false;
+ }
+ else {
+ return Drupal.CTools.AJAX.clickAJAXLink.apply(this);
+ }
+ }
+ };
+
+ /**
+ * Find a URL for an AJAX button.
+ *
+ * The URL for this gadget will be composed of the values of items by
+ * taking the ID of this item and adding -url and looking for that
+ * class. They need to be in the form in order since we will
+ * concat them all together using '/'.
+ */
+ Drupal.CTools.AJAX.findURL = function(item) {
+ var url = '';
+ var url_class = '.' + $(item).attr('id') + '-url';
+ $(url_class).each(
+ function() {
+ var $this = $(this);
+ if (url && $this.val()) {
+ url += '/';
+ }
+ url += $this.val();
+ });
+ return url;
+ };
+
+ // Hide these in a ready to ensure that Drupal.ajax is set up first.
+ $(function() {
+ Drupal.ajax.prototype.commands.attr = function(ajax, data, status) {
+ $(data.selector).attr(data.name, data.value);
+ };
+
+
+ Drupal.ajax.prototype.commands.redirect = function(ajax, data, status) {
+ if (data.delay > 0) {
+ setTimeout(function () {
+ location.href = data.url;
+ }, data.delay);
+ }
+ else {
+ location.href = data.url;
+ }
+ };
+
+ Drupal.ajax.prototype.commands.reload = function(ajax, data, status) {
+ location.reload();
+ };
+
+ Drupal.ajax.prototype.commands.submit = function(ajax, data, status) {
+ $(data.selector).submit();
+ }
+ });
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/auto-submit.js b/sites/all/modules/ctools/js/auto-submit.js
new file mode 100644
index 000000000..a3e9aa42a
--- /dev/null
+++ b/sites/all/modules/ctools/js/auto-submit.js
@@ -0,0 +1,100 @@
+(function($){
+/**
+ * To make a form auto submit, all you have to do is 3 things:
+ *
+ * ctools_add_js('auto-submit');
+ *
+ * On gadgets you want to auto-submit when changed, add the ctools-auto-submit
+ * class. With FAPI, add:
+ * @code
+ * '#attributes' => array('class' => array('ctools-auto-submit')),
+ * @endcode
+ *
+ * If you want to have auto-submit for every form element,
+ * add the ctools-auto-submit-full-form to the form. With FAPI, add:
+ * @code
+ * '#attributes' => array('class' => array('ctools-auto-submit-full-form')),
+ * @endcode
+ *
+ * If you want to exclude a field from the ctool-auto-submit-full-form auto submission,
+ * add the class ctools-auto-submit-exclude to the form element. With FAPI, add:
+ * @code
+ * '#attributes' => array('class' => array('ctools-auto-submit-exclude')),
+ * @endcode
+ *
+ * Finally, you have to identify which button you want clicked for autosubmit.
+ * The behavior of this button will be honored if it's ajaxy or not:
+ * @code
+ * '#attributes' => array('class' => array('ctools-use-ajax', 'ctools-auto-submit-click')),
+ * @endcode
+ *
+ * Currently only 'select', 'radio', 'checkbox' and 'textfield' types are supported. We probably
+ * could use additional support for HTML5 input types.
+ */
+
+Drupal.behaviors.CToolsAutoSubmit = {
+ attach: function(context) {
+ // 'this' references the form element
+ function triggerSubmit (e) {
+ var $this = $(this);
+ if (!$this.hasClass('ctools-ajaxing')) {
+ $this.find('.ctools-auto-submit-click').click();
+ }
+ }
+
+ // the change event bubbles so we only need to bind it to the outer form
+ $('form.ctools-auto-submit-full-form', context)
+ .add('.ctools-auto-submit', context)
+ .filter('form, select, input:not(:text, :submit)')
+ .once('ctools-auto-submit')
+ .change(function (e) {
+ // don't trigger on text change for full-form
+ if ($(e.target).is(':not(:text, :submit, .ctools-auto-submit-exclude)')) {
+ triggerSubmit.call(e.target.form);
+ }
+ });
+
+ // e.keyCode: key
+ var discardKeyCode = [
+ 16, // shift
+ 17, // ctrl
+ 18, // alt
+ 20, // caps lock
+ 33, // page up
+ 34, // page down
+ 35, // end
+ 36, // home
+ 37, // left arrow
+ 38, // up arrow
+ 39, // right arrow
+ 40, // down arrow
+ 9, // tab
+ 13, // enter
+ 27 // esc
+ ];
+ // Don't wait for change event on textfields
+ $('.ctools-auto-submit-full-form input:text, input:text.ctools-auto-submit', context)
+ .filter(':not(.ctools-auto-submit-exclude)')
+ .once('ctools-auto-submit', function () {
+ // each textinput element has his own timeout
+ var timeoutID = 0;
+ $(this)
+ .bind('keydown keyup', function (e) {
+ if ($.inArray(e.keyCode, discardKeyCode) === -1) {
+ timeoutID && clearTimeout(timeoutID);
+ }
+ })
+ .keyup(function(e) {
+ if ($.inArray(e.keyCode, discardKeyCode) === -1) {
+ timeoutID = setTimeout($.proxy(triggerSubmit, this.form), 500);
+ }
+ })
+ .bind('change', function (e) {
+ if ($.inArray(e.keyCode, discardKeyCode) === -1) {
+ timeoutID = setTimeout($.proxy(triggerSubmit, this.form), 500);
+ }
+ });
+ });
+ }
+}
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/collapsible-div.js b/sites/all/modules/ctools/js/collapsible-div.js
new file mode 100644
index 000000000..134151c3d
--- /dev/null
+++ b/sites/all/modules/ctools/js/collapsible-div.js
@@ -0,0 +1,241 @@
+/**
+ * @file
+ * Javascript required for a simple collapsible div.
+ *
+ * Creating a collapsible div with this doesn't take too much. There are
+ * three classes necessary:
+ *
+ * - ctools-collapsible-container: This is the overall container that will be
+ * collapsible. This must be a div.
+ * - ctools-collapsible-handle: This is the title area, and is what will be
+ * visible when it is collapsed. This can be any block element, such as div
+ * or h2.
+ * - ctools-collapsible-content: This is the ocntent area and will only be
+ * visible when expanded. This must be a div.
+ *
+ * Adding 'ctools-collapsible-remember' to the container class will cause the
+ * state of the container to be stored in a cookie, and remembered from page
+ * load to page load. This will only work if the container has a unique ID, so
+ * very carefully add IDs to your containers.
+ *
+ * If the class 'ctools-no-container' is placed on the container, the container
+ * will be the handle. The content will be found by appending '-content' to the
+ * id of the handle. The ctools-collapsible-handle and
+ * ctools-collapsible-content classes will not be required in that case, and no
+ * restrictions on what of data the container is are placed. Like
+ * ctools-collapsible-remember this requires an id to eist.
+ *
+ * The content will be 'open' unless the container class has 'ctools-collapsed'
+ * as a class, which will cause the container to draw collapsed.
+ */
+
+(function ($) {
+ // All CTools tools begin with this if they need to use the CTools namespace.
+ if (!Drupal.CTools) {
+ Drupal.CTools = {};
+ }
+
+ /**
+ * Object to store state.
+ *
+ * This object will remember the state of collapsible containers. The first
+ * time a state is requested, it will check the cookie and set up the variable.
+ * If a state has been changed, when the window is unloaded the state will be
+ * saved.
+ */
+ Drupal.CTools.Collapsible = {
+ state: {},
+ stateLoaded: false,
+ stateChanged: false,
+ cookieString: 'ctools-collapsible-state=',
+
+ /**
+ * Get the current collapsed state of a container.
+ *
+ * If set to 1, the container is open. If set to -1, the container is
+ * collapsed. If unset the state is unknown, and the default state should
+ * be used.
+ */
+ getState: function (id) {
+ if (!this.stateLoaded) {
+ this.loadCookie();
+ }
+
+ return this.state[id];
+ },
+
+ /**
+ * Set the collapsed state of a container for subsequent page loads.
+ *
+ * Set the state to 1 for open, -1 for collapsed.
+ */
+ setState: function (id, state) {
+ if (!this.stateLoaded) {
+ this.loadCookie();
+ }
+
+ this.state[id] = state;
+
+ if (!this.stateChanged) {
+ this.stateChanged = true;
+ $(window).unload(this.unload);
+ }
+ },
+
+ /**
+ * Check the cookie and load the state variable.
+ */
+ loadCookie: function () {
+ // If there is a previous instance of this cookie
+ if (document.cookie.length > 0) {
+ // Get the number of characters that have the list of values
+ // from our string index.
+ offset = document.cookie.indexOf(this.cookieString);
+
+ // If its positive, there is a list!
+ if (offset != -1) {
+ offset += this.cookieString.length;
+ var end = document.cookie.indexOf(';', offset);
+ if (end == -1) {
+ end = document.cookie.length;
+ }
+
+ // Get a list of all values that are saved on our string
+ var cookie = unescape(document.cookie.substring(offset, end));
+
+ if (cookie != '') {
+ var cookieList = cookie.split(',');
+ for (var i = 0; i < cookieList.length; i++) {
+ var info = cookieList[i].split(':');
+ this.state[info[0]] = info[1];
+ }
+ }
+ }
+ }
+
+ this.stateLoaded = true;
+ },
+
+ /**
+ * Turn the state variable into a string and store it in the cookie.
+ */
+ storeCookie: function () {
+ var cookie = '';
+
+ // Get a list of IDs, saparated by comma
+ for (i in this.state) {
+ if (cookie != '') {
+ cookie += ',';
+ }
+ cookie += i + ':' + this.state[i];
+ }
+
+ // Save this values on the cookie
+ document.cookie = this.cookieString + escape(cookie) + ';path=/';
+ },
+
+ /**
+ * Respond to the unload event by storing the current state.
+ */
+ unload: function() {
+ Drupal.CTools.Collapsible.storeCookie();
+ }
+ };
+
+ // Set up an array for callbacks.
+ Drupal.CTools.CollapsibleCallbacks = [];
+ Drupal.CTools.CollapsibleCallbacksAfterToggle = [];
+
+ /**
+ * Bind collapsible behavior to a given container.
+ */
+ Drupal.CTools.bindCollapsible = function () {
+ var $container = $(this);
+
+ // Allow the specification of the 'no container' class, which means the
+ // handle and the container can be completely independent.
+ if ($container.hasClass('ctools-no-container') && $container.attr('id')) {
+ // In this case, the container *is* the handle and the content is found
+ // by adding '-content' to the id. Obviously, an id is required.
+ var handle = $container;
+ var content = $('#' + $container.attr('id') + '-content');
+ }
+ else {
+ var handle = $container.children('.ctools-collapsible-handle');
+ var content = $container.children('div.ctools-collapsible-content');
+ }
+
+ if (content.length) {
+ // Create the toggle item and place it in front of the toggle.
+ var toggle = $('<span class="ctools-toggle"></span>');
+ handle.before(toggle);
+
+ // If the remember class is set, check to see if we have a remembered
+ // state stored.
+ if ($container.hasClass('ctools-collapsible-remember') && $container.attr('id')) {
+ var state = Drupal.CTools.Collapsible.getState($container.attr('id'));
+ if (state == 1) {
+ $container.removeClass('ctools-collapsed');
+ }
+ else if (state == -1) {
+ $container.addClass('ctools-collapsed');
+ }
+ }
+
+ // If we should start collapsed, do so:
+ if ($container.hasClass('ctools-collapsed')) {
+ toggle.toggleClass('ctools-toggle-collapsed');
+ content.hide();
+ }
+
+ var afterToggle = function () {
+ if (Drupal.CTools.CollapsibleCallbacksAfterToggle) {
+ for (i in Drupal.CTools.CollapsibleCallbacksAfterToggle) {
+ Drupal.CTools.CollapsibleCallbacksAfterToggle[i]($container, handle, content, toggle);
+ }
+ }
+ }
+
+ var clickMe = function () {
+ if (Drupal.CTools.CollapsibleCallbacks) {
+ for (i in Drupal.CTools.CollapsibleCallbacks) {
+ Drupal.CTools.CollapsibleCallbacks[i]($container, handle, content, toggle);
+ }
+ }
+
+ // If the container is a table element slideToggle does not do what
+ // we want, so use toggle() instead.
+ if ($container.is('table')) {
+ content.toggle(0, afterToggle);
+ }
+ else {
+ content.slideToggle(100, afterToggle);
+ }
+
+ $container.toggleClass('ctools-collapsed');
+ toggle.toggleClass('ctools-toggle-collapsed');
+
+ // If we're supposed to remember the state of this class, do so.
+ if ($container.hasClass('ctools-collapsible-remember') && $container.attr('id')) {
+ var state = toggle.hasClass('ctools-toggle-collapsed') ? -1 : 1;
+ Drupal.CTools.Collapsible.setState($container.attr('id'), state);
+ }
+
+ return false;
+ }
+
+ // Let both the toggle and the handle be clickable.
+ toggle.click(clickMe);
+ handle.click(clickMe);
+ }
+ };
+
+ /**
+ * Support Drupal's 'behaviors' system for binding.
+ */
+ Drupal.behaviors.CToolsCollapsible = {
+ attach: function(context) {
+ $('.ctools-collapsible-container', context).once('ctools-collapsible', Drupal.CTools.bindCollapsible);
+ }
+ }
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/dependent.js b/sites/all/modules/ctools/js/dependent.js
new file mode 100644
index 000000000..f74ec81ba
--- /dev/null
+++ b/sites/all/modules/ctools/js/dependent.js
@@ -0,0 +1,231 @@
+/**
+ * @file
+ * Provides dependent visibility for form items in CTools' ajax forms.
+ *
+ * To your $form item definition add:
+ * - '#process' => array('ctools_process_dependency'),
+ * - '#dependency' => array('id-of-form-item' => array(list, of, values, that,
+ * make, this, item, show),
+ *
+ * Special considerations:
+ * - Radios are harder. Because Drupal doesn't give radio groups individual IDs,
+ * use 'radio:name-of-radio'.
+ *
+ * - Checkboxes don't have their own id, so you need to add one in a div
+ * around the checkboxes via #prefix and #suffix. You actually need to add TWO
+ * divs because it's the parent that gets hidden. Also be sure to retain the
+ * 'expand_checkboxes' in the #process array, because the CTools process will
+ * override it.
+ */
+
+(function ($) {
+ Drupal.CTools = Drupal.CTools || {};
+ Drupal.CTools.dependent = {};
+
+ Drupal.CTools.dependent.bindings = {};
+ Drupal.CTools.dependent.activeBindings = {};
+ Drupal.CTools.dependent.activeTriggers = [];
+
+ Drupal.CTools.dependent.inArray = function(array, search_term) {
+ var i = array.length;
+ while (i--) {
+ if (array[i] == search_term) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ Drupal.CTools.dependent.autoAttach = function() {
+ // Clear active bindings and triggers.
+ for (i in Drupal.CTools.dependent.activeTriggers) {
+ $(Drupal.CTools.dependent.activeTriggers[i]).unbind('change.ctools-dependent');
+ }
+ Drupal.CTools.dependent.activeTriggers = [];
+ Drupal.CTools.dependent.activeBindings = {};
+ Drupal.CTools.dependent.bindings = {};
+
+ if (!Drupal.settings.CTools) {
+ return;
+ }
+
+ // Iterate through all relationships
+ for (id in Drupal.settings.CTools.dependent) {
+ // Test to make sure the id even exists; this helps clean up multiple
+ // AJAX calls with multiple forms.
+
+ // Drupal.CTools.dependent.activeBindings[id] is a boolean,
+ // whether the binding is active or not. Defaults to no.
+ Drupal.CTools.dependent.activeBindings[id] = 0;
+ // Iterate through all possible values
+ for(bind_id in Drupal.settings.CTools.dependent[id].values) {
+ // This creates a backward relationship. The bind_id is the ID
+ // of the element which needs to change in order for the id to hide or become shown.
+ // The id is the ID of the item which will be conditionally hidden or shown.
+ // Here we're setting the bindings for the bind
+ // id to be an empty array if it doesn't already have bindings to it
+ if (!Drupal.CTools.dependent.bindings[bind_id]) {
+ Drupal.CTools.dependent.bindings[bind_id] = [];
+ }
+ // Add this ID
+ Drupal.CTools.dependent.bindings[bind_id].push(id);
+ // Big long if statement.
+ // Drupal.settings.CTools.dependent[id].values[bind_id] holds the possible values
+
+ if (bind_id.substring(0, 6) == 'radio:') {
+ var trigger_id = "input[name='" + bind_id.substring(6) + "']";
+ }
+ else {
+ var trigger_id = '#' + bind_id;
+ }
+
+ Drupal.CTools.dependent.activeTriggers.push(trigger_id);
+
+ if ($(trigger_id).attr('type') == 'checkbox') {
+ $(trigger_id).siblings('label').addClass('hidden-options');
+ }
+
+ var getValue = function(item, trigger) {
+ if ($(trigger).size() == 0) {
+ return null;
+ }
+
+ if (item.substring(0, 6) == 'radio:') {
+ var val = $(trigger + ':checked').val();
+ }
+ else {
+ switch ($(trigger).attr('type')) {
+ case 'checkbox':
+ var val = $(trigger).attr('checked') ? true : false;
+
+ if (val) {
+ $(trigger).siblings('label').removeClass('hidden-options').addClass('expanded-options');
+ }
+ else {
+ $(trigger).siblings('label').removeClass('expanded-options').addClass('hidden-options');
+ }
+
+ break;
+ default:
+ var val = $(trigger).val();
+ }
+ }
+ return val;
+ }
+
+ var setChangeTrigger = function(trigger_id, bind_id) {
+ // Triggered when change() is clicked.
+ var changeTrigger = function() {
+ var val = getValue(bind_id, trigger_id);
+
+ if (val == null) {
+ return;
+ }
+
+ for (i in Drupal.CTools.dependent.bindings[bind_id]) {
+ var id = Drupal.CTools.dependent.bindings[bind_id][i];
+ // Fix numerous errors
+ if (typeof id != 'string') {
+ continue;
+ }
+
+ // This bit had to be rewritten a bit because two properties on the
+ // same set caused the counter to go up and up and up.
+ if (!Drupal.CTools.dependent.activeBindings[id]) {
+ Drupal.CTools.dependent.activeBindings[id] = {};
+ }
+
+ if (val != null && Drupal.CTools.dependent.inArray(Drupal.settings.CTools.dependent[id].values[bind_id], val)) {
+ Drupal.CTools.dependent.activeBindings[id][bind_id] = 'bind';
+ }
+ else {
+ delete Drupal.CTools.dependent.activeBindings[id][bind_id];
+ }
+
+ var len = 0;
+ for (i in Drupal.CTools.dependent.activeBindings[id]) {
+ len++;
+ }
+
+ var object = $('#' + id + '-wrapper');
+ if (!object.size()) {
+ // Some elements can't use the parent() method or they can
+ // damage things. They are guaranteed to have wrappers but
+ // only if dependent.inc provided them. This check prevents
+ // problems when multiple AJAX calls cause settings to build
+ // up.
+ var $original = $('#' + id);
+ if ($original.is('fieldset') || $original.is('textarea')) {
+ continue;
+ }
+
+ object = $('#' + id).parent();
+ }
+
+ if (Drupal.settings.CTools.dependent[id].type == 'disable') {
+ if (Drupal.settings.CTools.dependent[id].num <= len) {
+ // Show if the element if criteria is matched
+ object.attr('disabled', false);
+ object.addClass('dependent-options');
+ object.children().attr('disabled', false);
+ }
+ else {
+ // Otherwise hide. Use css rather than hide() because hide()
+ // does not work if the item is already hidden, for example,
+ // in a collapsed fieldset.
+ object.attr('disabled', true);
+ object.children().attr('disabled', true);
+ }
+ }
+ else {
+ if (Drupal.settings.CTools.dependent[id].num <= len) {
+ // Show if the element if criteria is matched
+ object.show(0);
+ object.addClass('dependent-options');
+ }
+ else {
+ // Otherwise hide. Use css rather than hide() because hide()
+ // does not work if the item is already hidden, for example,
+ // in a collapsed fieldset.
+ object.css('display', 'none');
+ }
+ }
+ }
+ }
+
+ $(trigger_id).bind('change.ctools-dependent', function() {
+ // Trigger the internal change function
+ // the attr('id') is used because closures are more confusing
+ changeTrigger(trigger_id, bind_id);
+ });
+ // Trigger initial reaction
+ changeTrigger(trigger_id, bind_id);
+ }
+ setChangeTrigger(trigger_id, bind_id);
+ }
+ }
+ }
+
+ Drupal.behaviors.CToolsDependent = {
+ attach: function (context) {
+ Drupal.CTools.dependent.autoAttach();
+
+ // Really large sets of fields are too slow with the above method, so this
+ // is a sort of hacked one that's faster but much less flexible.
+ $("select.ctools-master-dependent")
+ .once('ctools-dependent')
+ .bind('change.ctools-dependent', function() {
+ var val = $(this).val();
+ if (val == 'all') {
+ $('.ctools-dependent-all').show(0);
+ }
+ else {
+ $('.ctools-dependent-all').hide(0);
+ $('.ctools-dependent-' + val).show(0);
+ }
+ })
+ .trigger('change.ctools-dependent');
+ }
+ }
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/dropbutton.js b/sites/all/modules/ctools/js/dropbutton.js
new file mode 100644
index 000000000..f505550b6
--- /dev/null
+++ b/sites/all/modules/ctools/js/dropbutton.js
@@ -0,0 +1,94 @@
+/**
+ * @file
+ * Implement a simple, clickable dropbutton menu.
+ *
+ * See dropbutton.theme.inc for primary documentation.
+ *
+ * The javascript relies on four classes:
+ * - The dropbutton must be fully contained in a div with the class
+ * ctools-dropbutton. It must also contain the class ctools-no-js
+ * which will be immediately removed by the javascript; this allows for
+ * graceful degradation.
+ * - The trigger that opens the dropbutton must be an a tag wit hthe class
+ * ctools-dropbutton-link. The href should just be '#' as this will never
+ * be allowed to complete.
+ * - The part of the dropbutton that will appear when the link is clicked must
+ * be a div with class ctools-dropbutton-container.
+ * - Finally, ctools-dropbutton-hover will be placed on any link that is being
+ * hovered over, so that the browser can restyle the links.
+ *
+ * This tool isn't meant to replace click-tips or anything, it is specifically
+ * meant to work well presenting menus.
+ */
+
+(function ($) {
+ Drupal.behaviors.CToolsDropbutton = {
+ attach: function() {
+ // Process buttons. All dropbuttons are buttons.
+ $('.ctools-button')
+ .once('ctools-button')
+ .removeClass('ctools-no-js');
+
+ // Process dropbuttons. Not all buttons are dropbuttons.
+ $('.ctools-dropbutton').once('ctools-dropbutton', function() {
+ var $dropbutton = $(this);
+ var $button = $('.ctools-content', $dropbutton);
+ var $secondaryActions = $('li', $button).not(':first');
+ var $twisty = $(".ctools-link", $dropbutton);
+ var open = false;
+ var hovering = false;
+ var timerID = 0;
+
+ var toggle = function(close) {
+ // if it's open or we're told to close it, close it.
+ if (open || close) {
+ // If we're just toggling it, close it immediately.
+ if (!close) {
+ open = false;
+ $secondaryActions.slideUp(100);
+ $dropbutton.removeClass('open');
+ }
+ else {
+ // If we were told to close it, wait half a second to make
+ // sure that's what the user wanted.
+ // Clear any previous timer we were using.
+ if (timerID) {
+ clearTimeout(timerID);
+ }
+ timerID = setTimeout(function() {
+ if (!hovering) {
+ open = false;
+ $secondaryActions.slideUp(100);
+ $dropbutton.removeClass('open');
+ }}, 500);
+ }
+ }
+ else {
+ // open it.
+ open = true;
+ $secondaryActions.animate({height: "show", opacity: "show"}, 100);
+ $dropbutton.addClass('open');
+ }
+ }
+ // Hide the secondary actions initially.
+ $secondaryActions.hide();
+
+ $twisty.click(function() {
+ toggle();
+ return false;
+ });
+
+ $dropbutton.hover(
+ function() {
+ hovering = true;
+ }, // hover in
+ function() { // hover out
+ hovering = false;
+ toggle(true);
+ return false;
+ }
+ );
+ });
+ }
+ }
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/dropdown.js b/sites/all/modules/ctools/js/dropdown.js
new file mode 100644
index 000000000..c829ae2fe
--- /dev/null
+++ b/sites/all/modules/ctools/js/dropdown.js
@@ -0,0 +1,87 @@
+/**
+ * @file
+ * Implement a simple, clickable dropdown menu.
+ *
+ * See dropdown.theme.inc for primary documentation.
+ *
+ * The javascript relies on four classes:
+ * - The dropdown must be fully contained in a div with the class
+ * ctools-dropdown. It must also contain the class ctools-dropdown-no-js
+ * which will be immediately removed by the javascript; this allows for
+ * graceful degradation.
+ * - The trigger that opens the dropdown must be an a tag wit hthe class
+ * ctools-dropdown-link. The href should just be '#' as this will never
+ * be allowed to complete.
+ * - The part of the dropdown that will appear when the link is clicked must
+ * be a div with class ctools-dropdown-container.
+ * - Finally, ctools-dropdown-hover will be placed on any link that is being
+ * hovered over, so that the browser can restyle the links.
+ *
+ * This tool isn't meant to replace click-tips or anything, it is specifically
+ * meant to work well presenting menus.
+ */
+
+(function ($) {
+ Drupal.behaviors.CToolsDropdown = {
+ attach: function() {
+ $('div.ctools-dropdown').once('ctools-dropdown', function() {
+ var $dropdown = $(this);
+ var open = false;
+ var hovering = false;
+ var timerID = 0;
+
+ $dropdown.removeClass('ctools-dropdown-no-js');
+
+ var toggle = function(close) {
+ // if it's open or we're told to close it, close it.
+ if (open || close) {
+ // If we're just toggling it, close it immediately.
+ if (!close) {
+ open = false;
+ $("div.ctools-dropdown-container", $dropdown).slideUp(100);
+ }
+ else {
+ // If we were told to close it, wait half a second to make
+ // sure that's what the user wanted.
+ // Clear any previous timer we were using.
+ if (timerID) {
+ clearTimeout(timerID);
+ }
+ timerID = setTimeout(function() {
+ if (!hovering) {
+ open = false;
+ $("div.ctools-dropdown-container", $dropdown).slideUp(100);
+ }
+ }, 500);
+ }
+ }
+ else {
+ // open it.
+ open = true;
+ $("div.ctools-dropdown-container", $dropdown)
+ .animate({height: "show", opacity: "show"}, 100);
+ }
+ }
+ $("a.ctools-dropdown-link", $dropdown).click(function() {
+ toggle();
+ return false;
+ });
+
+ $dropdown.hover(
+ function() {
+ hovering = true;
+ }, // hover in
+ function() { // hover out
+ hovering = false;
+ toggle(true);
+ return false;
+ });
+ // @todo -- just use CSS for this noise.
+ $("div.ctools-dropdown-container a").hover(
+ function() { $(this).addClass('ctools-dropdown-hover'); },
+ function() { $(this).removeClass('ctools-dropdown-hover'); }
+ );
+ });
+ }
+ }
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/jump-menu.js b/sites/all/modules/ctools/js/jump-menu.js
new file mode 100644
index 000000000..7b0928a68
--- /dev/null
+++ b/sites/all/modules/ctools/js/jump-menu.js
@@ -0,0 +1,42 @@
+
+(function($) {
+ Drupal.behaviors.CToolsJumpMenu = {
+ attach: function(context) {
+ $('.ctools-jump-menu-hide')
+ .once('ctools-jump-menu')
+ .hide();
+
+ $('.ctools-jump-menu-change')
+ .once('ctools-jump-menu')
+ .change(function() {
+ var loc = $(this).val();
+ var urlArray = loc.split('::');
+ if (urlArray[1]) {
+ location.href = urlArray[1];
+ }
+ else {
+ location.href = loc;
+ }
+ return false;
+ });
+
+ $('.ctools-jump-menu-button')
+ .once('ctools-jump-menu')
+ .click(function() {
+ // Instead of submitting the form, just perform the redirect.
+
+ // Find our sibling value.
+ var $select = $(this).parents('form').find('.ctools-jump-menu-select');
+ var loc = $select.val();
+ var urlArray = loc.split('::');
+ if (urlArray[1]) {
+ location.href = urlArray[1];
+ }
+ else {
+ location.href = loc;
+ }
+ return false;
+ });
+ }
+ }
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/modal.js b/sites/all/modules/ctools/js/modal.js
new file mode 100644
index 000000000..c757ef274
--- /dev/null
+++ b/sites/all/modules/ctools/js/modal.js
@@ -0,0 +1,696 @@
+/**
+ * @file
+ *
+ * Implement a modal form.
+ *
+ * @see modal.inc for documentation.
+ *
+ * This javascript relies on the CTools ajax responder.
+ */
+
+(function ($) {
+ // Make sure our objects are defined.
+ Drupal.CTools = Drupal.CTools || {};
+ Drupal.CTools.Modal = Drupal.CTools.Modal || {};
+
+ /**
+ * Display the modal
+ *
+ * @todo -- document the settings.
+ */
+ Drupal.CTools.Modal.show = function(choice) {
+ var opts = {};
+
+ if (choice && typeof choice == 'string' && Drupal.settings[choice]) {
+ // This notation guarantees we are actually copying it.
+ $.extend(true, opts, Drupal.settings[choice]);
+ }
+ else if (choice) {
+ $.extend(true, opts, choice);
+ }
+
+ var defaults = {
+ modalTheme: 'CToolsModalDialog',
+ throbberTheme: 'CToolsModalThrobber',
+ animation: 'show',
+ animationSpeed: 'fast',
+ modalSize: {
+ type: 'scale',
+ width: .8,
+ height: .8,
+ addWidth: 0,
+ addHeight: 0,
+ // How much to remove from the inner content to make space for the
+ // theming.
+ contentRight: 25,
+ contentBottom: 45
+ },
+ modalOptions: {
+ opacity: .55,
+ background: '#fff'
+ },
+ modalClass: 'default'
+ };
+
+ var settings = {};
+ $.extend(true, settings, defaults, Drupal.settings.CToolsModal, opts);
+
+ if (Drupal.CTools.Modal.currentSettings && Drupal.CTools.Modal.currentSettings != settings) {
+ Drupal.CTools.Modal.modal.remove();
+ Drupal.CTools.Modal.modal = null;
+ }
+
+ Drupal.CTools.Modal.currentSettings = settings;
+
+ var resize = function(e) {
+ // When creating the modal, it actually exists only in a theoretical
+ // place that is not in the DOM. But once the modal exists, it is in the
+ // DOM so the context must be set appropriately.
+ var context = e ? document : Drupal.CTools.Modal.modal;
+
+ if (Drupal.CTools.Modal.currentSettings.modalSize.type == 'scale') {
+ var width = $(window).width() * Drupal.CTools.Modal.currentSettings.modalSize.width;
+ var height = $(window).height() * Drupal.CTools.Modal.currentSettings.modalSize.height;
+ }
+ else {
+ var width = Drupal.CTools.Modal.currentSettings.modalSize.width;
+ var height = Drupal.CTools.Modal.currentSettings.modalSize.height;
+ }
+
+ // Use the additionol pixels for creating the width and height.
+ $('div.ctools-modal-content', context).css({
+ 'width': width + Drupal.CTools.Modal.currentSettings.modalSize.addWidth + 'px',
+ 'height': height + Drupal.CTools.Modal.currentSettings.modalSize.addHeight + 'px'
+ });
+ $('div.ctools-modal-content .modal-content', context).css({
+ 'width': (width - Drupal.CTools.Modal.currentSettings.modalSize.contentRight) + 'px',
+ 'height': (height - Drupal.CTools.Modal.currentSettings.modalSize.contentBottom) + 'px'
+ });
+ }
+
+ if (!Drupal.CTools.Modal.modal) {
+ Drupal.CTools.Modal.modal = $(Drupal.theme(settings.modalTheme));
+ if (settings.modalSize.type == 'scale') {
+ $(window).bind('resize', resize);
+ }
+ }
+
+ resize();
+
+ $('span.modal-title', Drupal.CTools.Modal.modal).html(Drupal.CTools.Modal.currentSettings.loadingText);
+ Drupal.CTools.Modal.modalContent(Drupal.CTools.Modal.modal, settings.modalOptions, settings.animation, settings.animationSpeed, settings.modalClass);
+ $('#modalContent .modal-content').html(Drupal.theme(settings.throbberTheme)).addClass('ctools-modal-loading');
+
+ // Position autocomplete results based on the scroll position of the modal.
+ $('#modalContent .modal-content').delegate('input.form-autocomplete', 'keyup', function() {
+ $('#autocomplete').css('top', $(this).position().top + $(this).outerHeight() + $(this).offsetParent().filter('#modal-content').scrollTop());
+ });
+ };
+
+ /**
+ * Hide the modal
+ */
+ Drupal.CTools.Modal.dismiss = function() {
+ if (Drupal.CTools.Modal.modal) {
+ Drupal.CTools.Modal.unmodalContent(Drupal.CTools.Modal.modal);
+ }
+ };
+
+ /**
+ * Provide the HTML to create the modal dialog.
+ */
+ Drupal.theme.prototype.CToolsModalDialog = function () {
+ var html = ''
+ html += ' <div id="ctools-modal">'
+ html += ' <div class="ctools-modal-content">' // panels-modal-content
+ html += ' <div class="modal-header">';
+ html += ' <a class="close" href="#">';
+ html += Drupal.CTools.Modal.currentSettings.closeText + Drupal.CTools.Modal.currentSettings.closeImage;
+ html += ' </a>';
+ html += ' <span id="modal-title" class="modal-title">&nbsp;</span>';
+ html += ' </div>';
+ html += ' <div id="modal-content" class="modal-content">';
+ html += ' </div>';
+ html += ' </div>';
+ html += ' </div>';
+
+ return html;
+ }
+
+ /**
+ * Provide the HTML to create the throbber.
+ */
+ Drupal.theme.prototype.CToolsModalThrobber = function () {
+ var html = '';
+ html += ' <div id="modal-throbber">';
+ html += ' <div class="modal-throbber-wrapper">';
+ html += Drupal.CTools.Modal.currentSettings.throbber;
+ html += ' </div>';
+ html += ' </div>';
+
+ return html;
+ };
+
+ /**
+ * Figure out what settings string to use to display a modal.
+ */
+ Drupal.CTools.Modal.getSettings = function (object) {
+ var match = $(object).attr('class').match(/ctools-modal-(\S+)/);
+ if (match) {
+ return match[1];
+ }
+ }
+
+ /**
+ * Click function for modals that can be cached.
+ */
+ Drupal.CTools.Modal.clickAjaxCacheLink = function () {
+ Drupal.CTools.Modal.show(Drupal.CTools.Modal.getSettings(this));
+ return Drupal.CTools.AJAX.clickAJAXCacheLink.apply(this);
+ };
+
+ /**
+ * Handler to prepare the modal for the response
+ */
+ Drupal.CTools.Modal.clickAjaxLink = function () {
+ Drupal.CTools.Modal.show(Drupal.CTools.Modal.getSettings(this));
+ return false;
+ };
+
+ /**
+ * Submit responder to do an AJAX submit on all modal forms.
+ */
+ Drupal.CTools.Modal.submitAjaxForm = function(e) {
+ var $form = $(this);
+ var url = $form.attr('action');
+
+ setTimeout(function() { Drupal.CTools.AJAX.ajaxSubmit($form, url); }, 1);
+ return false;
+ }
+
+ /**
+ * Bind links that will open modals to the appropriate function.
+ */
+ Drupal.behaviors.ZZCToolsModal = {
+ attach: function(context) {
+ // Bind links
+ // Note that doing so in this order means that the two classes can be
+ // used together safely.
+ /*
+ * @todo remimplement the warm caching feature
+ $('a.ctools-use-modal-cache', context).once('ctools-use-modal', function() {
+ $(this).click(Drupal.CTools.Modal.clickAjaxCacheLink);
+ Drupal.CTools.AJAX.warmCache.apply(this);
+ });
+ */
+
+ $('area.ctools-use-modal, a.ctools-use-modal', context).once('ctools-use-modal', function() {
+ var $this = $(this);
+ $this.click(Drupal.CTools.Modal.clickAjaxLink);
+ // Create a drupal ajax object
+ var element_settings = {};
+ if ($this.attr('href')) {
+ element_settings.url = $this.attr('href');
+ element_settings.event = 'click';
+ element_settings.progress = { type: 'throbber' };
+ }
+ var base = $this.attr('href');
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ });
+
+ // Bind buttons
+ $('input.ctools-use-modal, button.ctools-use-modal', context).once('ctools-use-modal', function() {
+ var $this = $(this);
+ $this.click(Drupal.CTools.Modal.clickAjaxLink);
+ var button = this;
+ var element_settings = {};
+
+ // AJAX submits specified in this manner automatically submit to the
+ // normal form action.
+ element_settings.url = Drupal.CTools.Modal.findURL(this);
+ if (element_settings.url == '') {
+ element_settings.url = $(this).closest('form').attr('action');
+ }
+ element_settings.event = 'click';
+ element_settings.setClick = true;
+
+ var base = $this.attr('id');
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+
+ // Make sure changes to settings are reflected in the URL.
+ $('.' + $(button).attr('id') + '-url').change(function() {
+ Drupal.ajax[base].options.url = Drupal.CTools.Modal.findURL(button);
+ });
+ });
+
+ // Bind our custom event to the form submit
+ $('#modal-content form', context).once('ctools-use-modal', function() {
+ var $this = $(this);
+ var element_settings = {};
+
+ element_settings.url = $this.attr('action');
+ element_settings.event = 'submit';
+ element_settings.progress = { 'type': 'throbber' }
+ var base = $this.attr('id');
+
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ Drupal.ajax[base].form = $this;
+
+ $('input[type=submit], button', this).click(function(event) {
+ Drupal.ajax[base].element = this;
+ this.form.clk = this;
+ // Stop autocomplete from submitting.
+ if (Drupal.autocompleteSubmit && !Drupal.autocompleteSubmit()) {
+ return false;
+ }
+ // An empty event means we were triggered via .click() and
+ // in jquery 1.4 this won't trigger a submit.
+ if (event.bubbles == undefined) {
+ $(this.form).trigger('submit');
+ return false;
+ }
+ });
+ });
+
+ // Bind a click handler to allow elements with the 'ctools-close-modal'
+ // class to close the modal.
+ $('.ctools-close-modal', context).once('ctools-close-modal')
+ .click(function() {
+ Drupal.CTools.Modal.dismiss();
+ return false;
+ });
+ }
+ };
+
+ // The following are implementations of AJAX responder commands.
+
+ /**
+ * AJAX responder command to place HTML within the modal.
+ */
+ Drupal.CTools.Modal.modal_display = function(ajax, response, status) {
+ if ($('#modalContent').length == 0) {
+ Drupal.CTools.Modal.show(Drupal.CTools.Modal.getSettings(ajax.element));
+ }
+ $('#modal-title').html(response.title);
+ // Simulate an actual page load by scrolling to the top after adding the
+ // content. This is helpful for allowing users to see error messages at the
+ // top of a form, etc.
+ $('#modal-content').html(response.output).scrollTop(0);
+
+ // Attach behaviors within a modal dialog.
+ var settings = response.settings || ajax.settings || Drupal.settings;
+ Drupal.attachBehaviors('#modalContent', settings);
+
+ if ($('#modal-content').hasClass('ctools-modal-loading')) {
+ $('#modal-content').removeClass('ctools-modal-loading');
+ }
+ else {
+ // If the modal was already shown, and we are simply replacing its
+ // content, then focus on the first focusable element in the modal.
+ // (When first showing the modal, focus will be placed on the close
+ // button by the show() function called above.)
+ $('#modal-content :focusable:first').focus();
+ }
+ }
+
+ /**
+ * AJAX responder command to dismiss the modal.
+ */
+ Drupal.CTools.Modal.modal_dismiss = function(command) {
+ Drupal.CTools.Modal.dismiss();
+ $('link.ctools-temporary-css').remove();
+ }
+
+ /**
+ * Display loading
+ */
+ //Drupal.CTools.AJAX.commands.modal_loading = function(command) {
+ Drupal.CTools.Modal.modal_loading = function(command) {
+ Drupal.CTools.Modal.modal_display({
+ output: Drupal.theme(Drupal.CTools.Modal.currentSettings.throbberTheme),
+ title: Drupal.CTools.Modal.currentSettings.loadingText
+ });
+ }
+
+ /**
+ * Find a URL for an AJAX button.
+ *
+ * The URL for this gadget will be composed of the values of items by
+ * taking the ID of this item and adding -url and looking for that
+ * class. They need to be in the form in order since we will
+ * concat them all together using '/'.
+ */
+ Drupal.CTools.Modal.findURL = function(item) {
+ var url = '';
+ var url_class = '.' + $(item).attr('id') + '-url';
+ $(url_class).each(
+ function() {
+ var $this = $(this);
+ if (url && $this.val()) {
+ url += '/';
+ }
+ url += $this.val();
+ });
+ return url;
+ };
+
+
+ /**
+ * modalContent
+ * @param content string to display in the content box
+ * @param css obj of css attributes
+ * @param animation (fadeIn, slideDown, show)
+ * @param speed (valid animation speeds slow, medium, fast or # in ms)
+ * @param modalClass class added to div#modalContent
+ */
+ Drupal.CTools.Modal.modalContent = function(content, css, animation, speed, modalClass) {
+ // If our animation isn't set, make it just show/pop
+ if (!animation) {
+ animation = 'show';
+ }
+ else {
+ // If our animation isn't "fadeIn" or "slideDown" then it always is show
+ if (animation != 'fadeIn' && animation != 'slideDown') {
+ animation = 'show';
+ }
+ }
+
+ if (!speed) {
+ speed = 'fast';
+ }
+
+ // Build our base attributes and allow them to be overriden
+ css = jQuery.extend({
+ position: 'absolute',
+ left: '0px',
+ margin: '0px',
+ background: '#000',
+ opacity: '.55'
+ }, css);
+
+ // Add opacity handling for IE.
+ css.filter = 'alpha(opacity=' + (100 * css.opacity) + ')';
+ content.hide();
+
+ // If we already have modalContent, remove it.
+ if ($('#modalBackdrop').length) $('#modalBackdrop').remove();
+ if ($('#modalContent').length) $('#modalContent').remove();
+
+ // position code lifted from http://www.quirksmode.org/viewport/compatibility.html
+ if (self.pageYOffset) { // all except Explorer
+ var wt = self.pageYOffset;
+ } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
+ var wt = document.documentElement.scrollTop;
+ } else if (document.body) { // all other Explorers
+ var wt = document.body.scrollTop;
+ }
+
+ // Get our dimensions
+
+ // Get the docHeight and (ugly hack) add 50 pixels to make sure we dont have a *visible* border below our div
+ var docHeight = $(document).height() + 50;
+ var docWidth = $(document).width();
+ var winHeight = $(window).height();
+ var winWidth = $(window).width();
+ if( docHeight < winHeight ) docHeight = winHeight;
+
+ // Create our divs
+ $('body').append('<div id="modalBackdrop" class="backdrop-' + modalClass + '" style="z-index: 1000; display: none;"></div><div id="modalContent" class="modal-' + modalClass + '" style="z-index: 1001; position: absolute;">' + $(content).html() + '</div>');
+
+ // Get a list of the tabbable elements in the modal content.
+ var getTabbableElements = function () {
+ var tabbableElements = $('#modalContent :tabbable'),
+ radioButtons = tabbableElements.filter('input[type="radio"]');
+
+ // The list of tabbable elements from jQuery is *almost* right. The
+ // exception is with groups of radio buttons. The list from jQuery will
+ // include all radio buttons, when in fact, only the selected radio button
+ // is tabbable, and if no radio buttons in a group are selected, then only
+ // the first is tabbable.
+ if (radioButtons.length > 0) {
+ // First, build up an index of which groups have an item selected or not.
+ var anySelected = {};
+ radioButtons.each(function () {
+ var name = this.name;
+
+ if (typeof anySelected[name] === 'undefined') {
+ anySelected[name] = radioButtons.filter('input[name="' + name + '"]:checked').length !== 0;
+ }
+ });
+
+ // Next filter out the radio buttons that aren't really tabbable.
+ var found = {};
+ tabbableElements = tabbableElements.filter(function () {
+ var keep = true;
+
+ if (this.type == 'radio') {
+ if (anySelected[this.name]) {
+ // Only keep the selected one.
+ keep = this.checked;
+ }
+ else {
+ // Only keep the first one.
+ if (found[this.name]) {
+ keep = false;
+ }
+ found[this.name] = true;
+ }
+ }
+
+ return keep;
+ });
+ }
+
+ return tabbableElements.get();
+ };
+
+ // Keyboard and focus event handler ensures only modal elements gain focus.
+ modalEventHandler = function( event ) {
+ target = null;
+ if ( event ) { //Mozilla
+ target = event.target;
+ } else { //IE
+ event = window.event;
+ target = event.srcElement;
+ }
+
+ var parents = $(target).parents().get();
+ for (var i = 0; i < parents.length; ++i) {
+ var position = $(parents[i]).css('position');
+ if (position == 'absolute' || position == 'fixed') {
+ return true;
+ }
+ }
+
+ if ($(target).is('#modalContent, body') || $(target).filter('*:visible').parents('#modalContent').length) {
+ // Allow the event only if target is a visible child node
+ // of #modalContent.
+ return true;
+ }
+ else {
+ getTabbableElements()[0].focus();
+ }
+
+ event.preventDefault();
+ };
+ $('body').bind( 'focus', modalEventHandler );
+ $('body').bind( 'keypress', modalEventHandler );
+
+ // Keypress handler Ensures you can only TAB to elements within the modal.
+ // Based on the psuedo-code from WAI-ARIA 1.0 Authoring Practices section
+ // 3.3.1 "Trapping Focus".
+ modalTabTrapHandler = function (evt) {
+ // We only care about the TAB key.
+ if (evt.which != 9) {
+ return true;
+ }
+
+ var tabbableElements = getTabbableElements(),
+ firstTabbableElement = tabbableElements[0],
+ lastTabbableElement = tabbableElements[tabbableElements.length - 1],
+ singleTabbableElement = firstTabbableElement == lastTabbableElement,
+ node = evt.target;
+
+ // If this is the first element and the user wants to go backwards, then
+ // jump to the last element.
+ if (node == firstTabbableElement && evt.shiftKey) {
+ if (!singleTabbableElement) {
+ lastTabbableElement.focus();
+ }
+ return false;
+ }
+ // If this is the last element and the user wants to go forwards, then
+ // jump to the first element.
+ else if (node == lastTabbableElement && !evt.shiftKey) {
+ if (!singleTabbableElement) {
+ firstTabbableElement.focus();
+ }
+ return false;
+ }
+ // If this element isn't in the dialog at all, then jump to the first
+ // or last element to get the user into the game.
+ else if ($.inArray(node, tabbableElements) == -1) {
+ // Make sure the node isn't in another modal (ie. WYSIWYG modal).
+ var parents = $(node).parents().get();
+ for (var i = 0; i < parents.length; ++i) {
+ var position = $(parents[i]).css('position');
+ if (position == 'absolute' || position == 'fixed') {
+ return true;
+ }
+ }
+
+ if (evt.shiftKey) {
+ lastTabbableElement.focus();
+ }
+ else {
+ firstTabbableElement.focus();
+ }
+ }
+ };
+ $('body').bind('keydown', modalTabTrapHandler);
+
+ // Create our content div, get the dimensions, and hide it
+ var modalContent = $('#modalContent').css('top','-1000px');
+ var mdcTop = wt + ( winHeight / 2 ) - ( modalContent.outerHeight() / 2);
+ var mdcLeft = ( winWidth / 2 ) - ( modalContent.outerWidth() / 2);
+ $('#modalBackdrop').css(css).css('top', 0).css('height', docHeight + 'px').css('width', docWidth + 'px').show();
+ modalContent.css({top: mdcTop + 'px', left: mdcLeft + 'px'}).hide()[animation](speed);
+
+ // Bind a click for closing the modalContent
+ modalContentClose = function(){close(); return false;};
+ $('.close').bind('click', modalContentClose);
+
+ // Bind a keypress on escape for closing the modalContent
+ modalEventEscapeCloseHandler = function(event) {
+ if (event.keyCode == 27) {
+ close();
+ return false;
+ }
+ };
+
+ $(document).bind('keydown', modalEventEscapeCloseHandler);
+
+ // Per WAI-ARIA 1.0 Authoring Practices, initial focus should be on the
+ // close button, but we should save the original focus to restore it after
+ // the dialog is closed.
+ var oldFocus = document.activeElement;
+ $('.close').focus();
+
+ // Close the open modal content and backdrop
+ function close() {
+ // Unbind the events
+ $(window).unbind('resize', modalContentResize);
+ $('body').unbind( 'focus', modalEventHandler);
+ $('body').unbind( 'keypress', modalEventHandler );
+ $('body').unbind( 'keydown', modalTabTrapHandler );
+ $('.close').unbind('click', modalContentClose);
+ $('body').unbind('keypress', modalEventEscapeCloseHandler);
+ $(document).trigger('CToolsDetachBehaviors', $('#modalContent'));
+
+ // Set our animation parameters and use them
+ if ( animation == 'fadeIn' ) animation = 'fadeOut';
+ if ( animation == 'slideDown' ) animation = 'slideUp';
+ if ( animation == 'show' ) animation = 'hide';
+
+ // Close the content
+ modalContent.hide()[animation](speed);
+
+ // Remove the content
+ $('#modalContent').remove();
+ $('#modalBackdrop').remove();
+
+ // Restore focus to where it was before opening the dialog
+ $(oldFocus).focus();
+ };
+
+ // Move and resize the modalBackdrop and modalContent on window resize.
+ modalContentResize = function(){
+
+ // Reset the backdrop height/width to get accurate document size.
+ $('#modalBackdrop').css('height', '').css('width', '');
+
+ // Position code lifted from:
+ // http://www.quirksmode.org/viewport/compatibility.html
+ if (self.pageYOffset) { // all except Explorer
+ var wt = self.pageYOffset;
+ } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
+ var wt = document.documentElement.scrollTop;
+ } else if (document.body) { // all other Explorers
+ var wt = document.body.scrollTop;
+ }
+
+ // Get our heights
+ var docHeight = $(document).height();
+ var docWidth = $(document).width();
+ var winHeight = $(window).height();
+ var winWidth = $(window).width();
+ if( docHeight < winHeight ) docHeight = winHeight;
+
+ // Get where we should move content to
+ var modalContent = $('#modalContent');
+ var mdcTop = wt + ( winHeight / 2 ) - ( modalContent.outerHeight() / 2);
+ var mdcLeft = ( winWidth / 2 ) - ( modalContent.outerWidth() / 2);
+
+ // Apply the changes
+ $('#modalBackdrop').css('height', docHeight + 'px').css('width', docWidth + 'px').show();
+ modalContent.css('top', mdcTop + 'px').css('left', mdcLeft + 'px').show();
+ };
+ $(window).bind('resize', modalContentResize);
+ };
+
+ /**
+ * unmodalContent
+ * @param content (The jQuery object to remove)
+ * @param animation (fadeOut, slideUp, show)
+ * @param speed (valid animation speeds slow, medium, fast or # in ms)
+ */
+ Drupal.CTools.Modal.unmodalContent = function(content, animation, speed)
+ {
+ // If our animation isn't set, make it just show/pop
+ if (!animation) { var animation = 'show'; } else {
+ // If our animation isn't "fade" then it always is show
+ if (( animation != 'fadeOut' ) && ( animation != 'slideUp')) animation = 'show';
+ }
+ // Set a speed if we dont have one
+ if ( !speed ) var speed = 'fast';
+
+ // Unbind the events we bound
+ $(window).unbind('resize', modalContentResize);
+ $('body').unbind('focus', modalEventHandler);
+ $('body').unbind('keypress', modalEventHandler);
+ $('body').unbind( 'keydown', modalTabTrapHandler );
+ $('.close').unbind('click', modalContentClose);
+ $('body').unbind('keypress', modalEventEscapeCloseHandler);
+ $(document).trigger('CToolsDetachBehaviors', $('#modalContent'));
+
+ // jQuery magic loop through the instances and run the animations or removal.
+ content.each(function(){
+ if ( animation == 'fade' ) {
+ $('#modalContent').fadeOut(speed, function() {
+ $('#modalBackdrop').fadeOut(speed, function() {
+ $(this).remove();
+ });
+ $(this).remove();
+ });
+ } else {
+ if ( animation == 'slide' ) {
+ $('#modalContent').slideUp(speed,function() {
+ $('#modalBackdrop').slideUp(speed, function() {
+ $(this).remove();
+ });
+ $(this).remove();
+ });
+ } else {
+ $('#modalContent').remove();
+ $('#modalBackdrop').remove();
+ }
+ }
+ });
+ };
+
+$(function() {
+ Drupal.ajax.prototype.commands.modal_display = Drupal.CTools.Modal.modal_display;
+ Drupal.ajax.prototype.commands.modal_dismiss = Drupal.CTools.Modal.modal_dismiss;
+});
+
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/states-show.js b/sites/all/modules/ctools/js/states-show.js
new file mode 100644
index 000000000..88df58419
--- /dev/null
+++ b/sites/all/modules/ctools/js/states-show.js
@@ -0,0 +1,43 @@
+/**
+ * @file
+ * Custom state for handling visibility
+ */
+
+/**
+ * Add a new state to Drupal #states. We use this to toggle element-invisible
+ * to show/hidden #states elements. This allows elements to be visible to
+ * screen readers.
+ *
+ * To use:
+ * $form['my_form_field'] = array(
+ * ..
+ * // Only show this field if 'some_other_field' is checked.
+ * '#states => array(
+ * 'show' => array(
+ * 'some-other-field' => array('checked' => TRUE),
+ * ),
+ * ),
+ * ..
+ * // Required to load the 'show' state handler.
+ * '#attached' => array(
+ * 'js' => array(ctools_attach_js('states-show')),
+ * ),
+ * );
+ */
+
+(function ($) {
+ 'use strict';
+
+ Drupal.states.State.aliases.hidden = '!show';
+
+ // Show/hide form items by toggling the 'element-invisible' class. This is a
+ // more accessible option than the core 'visible' state.
+ $(document).bind('state:show', function(e) {
+ if (e.trigger) {
+ var element = $(e.target).closest('.form-item, .form-submit, .form-wrapper');
+ element.toggle(e.value);
+ e.value === true ? element.removeClass('element-invisible') : element.addClass('element-invisible');
+ }
+ });
+
+})(jQuery);
diff --git a/sites/all/modules/ctools/js/stylizer.js b/sites/all/modules/ctools/js/stylizer.js
new file mode 100644
index 000000000..16d6c49d5
--- /dev/null
+++ b/sites/all/modules/ctools/js/stylizer.js
@@ -0,0 +1,220 @@
+
+(function ($) {
+ Drupal.CTools = Drupal.CTools || {};
+ Drupal.CTools.Stylizer = {};
+
+ Drupal.CTools.Stylizer.addFarbtastic = function(context) {
+ // This behavior attaches by ID, so is only valid once on a page.
+ if ($('ctools_stylizer_color_scheme_form .color-form.Stylizer-processed').size()) {
+ return;
+ }
+
+ var form = $('.color-form', context);
+ var inputs = [];
+ var hooks = [];
+ var locks = [];
+ var focused = null;
+
+ // Add Farbtastic
+ $(form).prepend('<div id="placeholder"></div>').addClass('color-processed');
+ var farb = $.farbtastic('#placeholder');
+
+ // Decode reference colors to HSL
+ /*var reference = Drupal.settings.Stylizer.reference.clone();
+ for (i in reference) {
+ reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
+ } */
+
+ // Set up colorscheme selector
+ $('#edit-scheme', form).change(function () {
+ var colors = this.options[this.selectedIndex].value;
+ if (colors != '') {
+ colors = colors.split(',');
+ for (i in colors) {
+ callback(inputs[i], colors[i], false, true);
+ }
+ }
+ });
+
+ /**
+ * Shift a given color, using a reference pair (ref in HSL).
+ *
+ * This algorithm ensures relative ordering on the saturation and luminance
+ * axes is preserved, and performs a simple hue shift.
+ *
+ * It is also symmetrical. If: shift_color(c, a, b) == d,
+ * then shift_color(d, b, a) == c.
+ */
+ function shift_color(given, ref1, ref2) {
+ // Convert to HSL
+ given = farb.RGBToHSL(farb.unpack(given));
+
+ // Hue: apply delta
+ given[0] += ref2[0] - ref1[0];
+
+ // Saturation: interpolate
+ if (ref1[1] == 0 || ref2[1] == 0) {
+ given[1] = ref2[1];
+ }
+ else {
+ var d = ref1[1] / ref2[1];
+ if (d > 1) {
+ given[1] /= d;
+ }
+ else {
+ given[1] = 1 - (1 - given[1]) * d;
+ }
+ }
+
+ // Luminance: interpolate
+ if (ref1[2] == 0 || ref2[2] == 0) {
+ given[2] = ref2[2];
+ }
+ else {
+ var d = ref1[2] / ref2[2];
+ if (d > 1) {
+ given[2] /= d;
+ }
+ else {
+ given[2] = 1 - (1 - given[2]) * d;
+ }
+ }
+
+ return farb.pack(farb.HSLToRGB(given));
+ }
+
+ /**
+ * Callback for Farbtastic when a new color is chosen.
+ */
+ function callback(input, color, propagate, colorscheme) {
+ // Set background/foreground color
+ $(input).css({
+ backgroundColor: color,
+ 'color': farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
+ });
+
+ // Change input value
+ if (input.value && input.value != color) {
+ input.value = color;
+
+ // Update locked values
+ if (propagate) {
+ var i = input.i;
+ for (j = i + 1; ; ++j) {
+ if (!locks[j - 1] || $(locks[j - 1]).is('.unlocked')) break;
+ var matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
+ callback(inputs[j], matched, false);
+ }
+ for (j = i - 1; ; --j) {
+ if (!locks[j] || $(locks[j]).is('.unlocked')) break;
+ var matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
+ callback(inputs[j], matched, false);
+ }
+
+ }
+
+ // Reset colorscheme selector
+ if (!colorscheme) {
+ resetScheme();
+ }
+ }
+
+ }
+
+ /**
+ * Reset the color scheme selector.
+ */
+ function resetScheme() {
+ $('#edit-scheme', form).each(function () {
+ this.selectedIndex = this.options.length - 1;
+ });
+ }
+
+ // Focus the Farbtastic on a particular field.
+ function focus() {
+ var input = this;
+ // Remove old bindings
+ focused && $(focused).unbind('keyup', farb.updateValue)
+ .unbind('keyup', resetScheme)
+ .parent().removeClass('item-selected');
+
+ // Add new bindings
+ focused = this;
+ farb.linkTo(function (color) { callback(input, color, true, false); });
+ farb.setColor(this.value);
+ $(focused).keyup(farb.updateValue).keyup(resetScheme)
+ .parent().addClass('item-selected');
+ }
+
+ // Initialize color fields
+ $('#palette input.form-text', form)
+ .each(function () {
+ // Extract palette field name
+ this.key = this.id.substring(13);
+
+ // Link to color picker temporarily to initialize.
+ farb.linkTo(function () {}).setColor('#000').linkTo(this);
+
+ // Add lock
+ var i = inputs.length;
+ if (inputs.length) {
+ var lock = $('<div class="lock"></div>').toggle(
+ function () {
+ $(this).addClass('unlocked');
+ $(hooks[i - 1]).attr('class',
+ locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook up' : 'hook'
+ );
+ $(hooks[i]).attr('class',
+ locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook down' : 'hook'
+ );
+ },
+ function () {
+ $(this).removeClass('unlocked');
+ $(hooks[i - 1]).attr('class',
+ locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook both' : 'hook down'
+ );
+ $(hooks[i]).attr('class',
+ locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook both' : 'hook up'
+ );
+ }
+ );
+ $(this).after(lock);
+ locks.push(lock);
+ };
+
+ // Add hook
+ var $this = $(this);
+ var hook = $('<div class="hook"></div>');
+ $this.after(hook);
+ hooks.push(hook);
+
+ $this.parent().find('.lock').click();
+ this.i = i;
+ inputs.push(this);
+ })
+ .focus(focus);
+
+ $('#palette label', form);
+
+ // Focus first color
+ focus.call(inputs[0]);
+ };
+
+ Drupal.behaviors.CToolsColorSettings = {
+ attach: function() {
+ $('.ctools-stylizer-color-edit:not(.ctools-color-processed)')
+ .addClass('ctools-color-processed')
+ .each(function() {
+ Drupal.CTools.Stylizer.addFarbtastic('#' + $(this).attr('id'));
+ });
+
+ $('div.form-item div.ctools-style-icon:not(.ctools-color-processed)')
+ .addClass('ctools-color-processed')
+ .click(function() {
+ $widget = $('input', $(this).parent());
+ // Toggle if a checkbox, turn on if a radio.
+ $widget.attr('checked', !$widget.attr('checked') || $widget.is('input[type=radio]'));
+ });
+ }
+ }
+})(jQuery);
diff --git a/sites/all/modules/ctools/page_manager/css/page-manager.css b/sites/all/modules/ctools/page_manager/css/page-manager.css
new file mode 100644
index 000000000..1a7dd5e4b
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/css/page-manager.css
@@ -0,0 +1,372 @@
+body form#page-manager-list-pages-form {
+ margin: 0 0 20px 0;
+}
+
+#page-manager-list-pages-form .form-item {
+ padding-right: 1em; /* LTR */
+ float: left; /* LTR */
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#page-manager-list-pages {
+ width: 100%;
+}
+
+#edit-order-wrapper {
+ clear: left; /* LTR */
+}
+
+#edit-pages-apply,
+#edit-pages-reset {
+ margin-top: 1.65em;
+ float: left; /* LTR */
+}
+
+#page-manager-edit {
+ margin-top: 1em;
+}
+
+#page-manager-edit table {
+ width: 100%;
+}
+
+#page-manager-edit table tr.even {
+ background-color: #fafafa;
+}
+
+#page-manager-list-pages tr.page-manager-disabled td {
+ color: #999;
+}
+
+#page-manager-list-pages tr.page-manager-locked td.page-manager-page-name {
+ font-style: italic;
+ color: green;
+ padding-left: 22px; /* LTR */
+ background: url(../images/locked.png) no-repeat scroll left center; /* LTR */
+}
+
+#page-manager-list-pages tr.page-manager-locked-other td.page-manager-page-name {
+ font-style: italic;
+ color: red;
+ padding-left: 22px; /* LTR */
+ background: url(../images/locked-other.png) no-repeat scroll left center; /* LTR */
+}
+
+/* Force the background color to inherit so that the dropbuttons do not need
+ a specific background color. */
+#page-manager-list-pages td.page-manager-page-operations {
+ vertical-align: top;
+ position: relative;
+ text-align: right;
+}
+
+#page-manager-list-pages td.page-manager-page-operations .ctools-dropbutton {
+ text-align: left; /* LTR */
+}
+
+
+
+#page-manager-edit .page-manager-wrapper {
+ margin: 0;
+ padding: 0 0 0 149px;
+ color: #494949;
+}
+
+#page-manager-edit .page-manager-tabs {
+ border: 1px solid #aaa;
+}
+
+#page-manager-edit .page-manager-edit-operations {
+ float: left; /* LTR */
+ width: 150px;
+ margin-left: -150px;
+ margin-top: -1px;
+ height: 100%;
+ font-size: 90%;
+}
+
+#page-manager-edit .page-manager-edit-operations .inside {
+ border-top: 1px solid #aaa;
+ border-bottom: 1px solid #aaa;
+}
+
+#page-manager-edit .page-manager-edit-operations ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ padding: 0;
+ margin-left: 0;
+}
+
+#page-manager-edit .page-manager-edit-operations li {
+ list-style: none;
+ background: #F6F6F6;
+ border-top: 1px solid #aaa;
+ border-left: 1px solid #aaa;
+ border-right: 1px solid #aaa;
+ padding: 0 0 0 0;
+ margin: 0;
+ line-height: 2em;
+}
+
+#page-manager-edit .page-manager-edit-operations li.active,
+#page-manager-edit .page-manager-edit-operations li.active-group .page-manager-group-title {
+ background: #FFFFFF url(../images/arrow-active.png) no-repeat scroll right center;
+}
+
+#page-manager-edit .page-manager-edit-operations li.changed,
+#page-manager-edit .page-manager-edit-operations li.changed-group .page-manager-group-title {
+ background-color: #ffe;
+ font-weight: bold;
+}
+
+/** provide a reset for non active stray paths */
+#page-manager-edit .page-manager-edit-operations li.active-group li.not-active .page-manager-group-title,
+#page-manager-edit .page-manager-edit-operations li.changed-group li.not-changed .page-manager-group-title {
+ background: #F6F6F6;
+}
+
+#page-manager-edit .page-manager-edit-operations li.active {
+ border-right: 1px solid white;
+}
+
+#page-manager-edit .page-manager-edit-operations li.active a,
+#page-manager-edit .page-manager-edit-operations li.active a:hover {
+ background: #FFFFFF url(../images/arrow-active.png) no-repeat scroll right center;
+ color: #000000;
+ font-weight: bold;
+}
+
+#page-manager-edit .page-manager-edit-operations li.operation-first {
+ border-top: none;
+}
+
+#page-manager-edit .page-manager-edit-operations li li.operation-first {
+ border-top: 1px solid #aaa;
+}
+
+#page-manager-edit .page-manager-edit-operations li a {
+ display: block;
+ padding: 0 0 0 .5em;
+ color: #494949;
+}
+
+#page-manager-edit .page-manager-edit-operations li a:hover {
+ background-color: #eee;
+ text-decoration: none;
+}
+
+#page-manager-edit .ctools-collapsible-container {
+ display: inline-block;
+ position: relative;
+ *float: left; /* LTR */
+ width: 100%;
+}
+
+#page-manager-edit .page-manager-edit-operations li .ctools-collapsible-handle:hover {
+ background-color: #eee;
+}
+
+#page-manager-edit .page-manager-edit-operations li li {
+ border-right: none;
+ border-left: none;
+ margin-left: 1em;
+}
+
+#page-manager-edit .page-manager-edit-operations li ul {
+}
+
+#page-manager-edit .page-manager-group-title {
+ line-height: 2em;
+ font-weight: bold;
+ padding: 0 0 0 .5em;
+}
+
+/* Change the position of the arrow on the dropdown to look nicer with our defaults. */
+#page-manager-edit .page-manager-edit-operations .ctools-toggle {
+ background-position: 0 9px;
+ width: 10px;
+}
+
+#page-manager-edit .page-manager-ajax-pad {
+ float: left; /* LTR */
+ width: 100%;
+ border-left: none;
+ height: 100%;
+ background: white;
+}
+
+/** A riser to force the ajax pad to a minimum height. **/
+#page-manager-edit .page-manager-ajax-pad .page-manager-riser {
+ width: 1px;
+ float: right; /* LTR */
+ height: 400px;
+}
+
+#page-manager-edit .page-manager-ajax-pad .page-manager-riser span {
+ display: none;
+}
+
+#page-manager-edit .page-manager-ajax-pad .content-title {
+ font-weight: bold;
+ font-size: 120%;
+ background-color: #fafafa;
+ border-bottom: 1px solid #aaa;
+ border-left: 1px solid #aaa;
+ margin-left: -1px;
+ padding: 2px 5px 2px 20px;
+}
+
+#page-manager-edit .actions {
+ padding: 0 0 0 20px;
+}
+
+#page-manager-edit .primary-actions li {
+ border-top: 1px solid #aaa;
+}
+
+#page-manager-edit .secondary-actions {
+ border-bottom: 1px solid #aaa;
+}
+
+#page-manager-edit .handler-actions {
+ float: right; /* LTR */
+}
+
+#page-manager-edit .actions .page-manager-group-title {
+ float: left; /* LTR */
+ padding-left: 0;
+}
+
+#page-manager-edit .actions ul {
+ float: right; /* LTR */
+ text-align: right; /* LTR */
+ padding: 0;
+ margin: 0;
+ border-right: 1px solid #aaa;
+}
+
+#page-manager-edit .handler-title .actions ul {
+ border-right: none;
+}
+
+#page-manager-edit .actions li {
+ float: left; /* LTR */
+ background: none;
+ list-style: none;
+ border-left: 1px solid #aaa;
+ margin: 0;
+ padding: 0;
+}
+
+#page-manager-edit .actions ul li.operation-last {
+ border-right: none;
+}
+
+#page-manager-edit .actions li a:hover {
+ background-color: #eee;
+ text-decoration: none;
+}
+
+#page-manager-edit .actions li a {
+ display: block;
+ padding: 0.2em 0.5em;
+ color:#0062A0;
+ background-color: #F6F6F6;
+}
+
+#page-manager-edit .page-manager-changed {
+ float: right; /* LTR */
+ font-style: italic;
+ color: #f93;
+ padding-left: 1em;
+ padding-right: 22px;
+ background: url(../images/locked.png) no-repeat scroll right center;
+}
+
+#page-manager-edit .page-manager-ajax-pad .content-content {
+ padding: .5em 20px;
+}
+
+#page-manager-edit .page-manager-ajax-pad textarea {
+ width: 100%;
+}
+
+#page-manager-edit .changed-notification {
+ border: 1px solid #aaa;
+ background-color: #ffe;
+ color: #494949;
+ padding: 1em;
+ margin-top: 1em;
+}
+
+#page-manager-edit .ctools-locked {
+ margin-bottom: 2em;
+}
+
+#page-manager-page-summary .title {
+ font-weight: bold;
+ font-size: 160%;
+}
+
+#page-manager-page-summary .handler-summary {
+}
+
+#page-manager-page-summary .page-summary-operation {
+ text-align: right;
+}
+
+#page-manager-page-summary .page-summary-label {
+ width: 8em;
+ font-weight: bold;
+}
+
+
+.handler-summary dl {
+ margin: 0;
+}
+
+.handler-summary dt {
+ margin: 0;
+ padding: 0;
+}
+
+.handler-summary dd {
+ margin: 0;
+}
+
+.handler-summary ol {
+ margin: 0;
+}
+
+.handler-summary .handler-title {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ background: #fafafa;
+ padding: 0 0 0 5px;
+ margin-top: .5em;
+}
+
+.handler-summary .handler-title .title-label {
+ font-weight: bold;
+ font-size: 120%;
+ line-height: 1.75em;
+}
+
+
+/** Override that obnoxious float on throbber. **/
+#page-manager-edit .progress-disabled {
+ float: none;
+}
+
+#page-manager-edit .progress-disabled + .ajax-progress {
+ float: right;
+ position: relative;
+ top: -2em;
+}
+
+#page-manager-list-pages-form .progress-disabled + .ajax-progress {
+ position: relative;
+ top: 2em;
+ left: -.5em;
+}
diff --git a/sites/all/modules/ctools/page_manager/help/about.html b/sites/all/modules/ctools/page_manager/help/about.html
new file mode 100644
index 000000000..fa58acafb
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/about.html
@@ -0,0 +1,11 @@
+The Page Manager module creates and manages pages in your Drupal site. Pages are defined as items that have a path and provide output to the user. It is a complete round trip from getting user input to providing user output.
+
+There are two types of pages that the Page Manager currently supports:
+<dl>
+<dt>Custom pages</dt>
+<dd>Custom pages are defined completely by the administrator. Their path, access control and visible menu characteristics are completely arbitrary.</dd>
+<dt>System pages</dt>
+<dd>System pages are defined by Drupal and Drupal modules. They primarily override pre-existing pages to provide different functionality. They often do not allow such features as access control in favor of what already exists, and they will usually 'fall back' to default Drupal behavior.
+</dl>
+
+Both types of pages figure out what to show the user by using <strong>Variants</strong>. Variants are output handlers, and every page should have at least one. Most pages will simply have only one. Pages with multiple variants will choose one and only one Variant to display content to the user and will use the <strong>Selection Rules</strong> to figure out which Variant to display. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/api-task-handler.html b/sites/all/modules/ctools/page_manager/help/api-task-handler.html
new file mode 100644
index 000000000..4544a2a86
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/api-task-handler.html
@@ -0,0 +1,43 @@
+task handler definition:
+ title -- visible title of the task handler.
+ description -- description of the task handler.
+ task type -- The type of the task this handler can service.
+ render -- callback of the function to render the handler. The arguments to this callback are specific to the task type.
+ admin title -- callback to render the admin title as this handler is listed.
+ params: $handler, $task, $subtask_id
+ admin summary -- callback to render what's in the collapsible info as the handler is listed. Optional.
+ params: $handler, $task, $subtask_id
+ default conf -- either an array() of default conf data or a callback that returns an array.
+ params: $handler, $task, $subtask_id
+ save -- callback to call just prior to the task handler being saved so it can adjust its data.
+ params: &$handler, $update (as drupal_write_record would receive)
+ export -- callback to call just prior to the task being exported. It should return text to append to the export if necessary.
+ params: &$handler, $indent
+
+ forms => array(
+ 'id' => array(
+ 'form' => form callback (receives $form, $form_state)
+ 'submit' => submit callback
+ 'validate' => validate callback
+ 'include' => an optional file to include to get functionality for this form. Must include full path.
+ 'no return' => hide the 'return' button, meaning that the form requires extra steps if submitted
+ 'alternate next' => an alternate next form. Used for hidden edit forms that don't have tabs.
+ 'no blocks' => if TRUE, use Drupal's mechanism to not render blocks for this form.
+ )
+ )
+ ),
+
+ 'add forms' => array(
+ 'form1', => t('form title'),
+ 'form2', => t('form title'),
+ // ...etc.../
+),
+ 'edit forms' => array(
+ 'id' => t('tab name'),
+ 'id2' => t('tab name'),
+ ),
+
+ If a form name is blank it is a 'hidden' form -- it has no tab but can still be reached.
+
+
+Notes: Because #required validation cannot be skipped when clicking cancel, please don't use it. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/api-task-type.html b/sites/all/modules/ctools/page_manager/help/api-task-type.html
new file mode 100644
index 000000000..eb87265f6
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/api-task-type.html
@@ -0,0 +1,2 @@
+
+defines a task type, grouping tasks together and providing a common UI for them. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/api-task.html b/sites/all/modules/ctools/page_manager/help/api-task.html
new file mode 100644
index 000000000..cd6e3d0cb
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/api-task.html
@@ -0,0 +1,38 @@
+task definition:
+ title -- visible title of the task.
+ description -- description of the task.
+ hook menu -- function to delegate from hook_menu. Params: &$items, $task
+ hook menu alter -- function to delegate from hook_menu_alter. Params: &$items, $task
+ hook theme -- function to delegate from hook_theme. Params: &$items, $task
+
+ admin name -- if set an admin menu will appear in the delegator UI
+ admin description -- to describe the admin menu
+
+ admin access callback -- if set, the callback to use to determine administrative
+ access to this task. Defaults to user_access. Note that this is required even
+ if delegator isn't handling administration, since this gets used to on handler
+ edit forms.
+ admin access arguments -- If set, the arguments to use to determine administrative
+ access to this task. Defaults to array('administer delegator');
+
+ type -- The type of the task, used to determine which handlers can service it.
+
+ subtasks -- can be TRUE in which case it supports subtasks with the default
+ configuration or a string (array?) with callbacks to fetch subtask data.
+ subtask callback -- A callback which returns just one subtask. Param: $task, $subtask_id
+ subtasks callback -- A callback which returns an array of all subtasks.
+ This MUST return an array, even if it's empty.Param: $task
+
+ default handlers -- If the task contains any default handlers, they can be included here.
+
+task names must not contain a - as that is used to separate the task name from the subtask ID.
+
+subtasks implement data very similar to their parent task. In particular, they
+implement the following items exactly like their task:
+ hook menu
+ hook menu alter
+ description
+ admin name
+ admin description
+ admin access callback
+ admin access arguments
diff --git a/sites/all/modules/ctools/page_manager/help/custom-pages-access.html b/sites/all/modules/ctools/page_manager/help/custom-pages-access.html
new file mode 100644
index 000000000..a2643c28c
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/custom-pages-access.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528072">http://drupal.org/node/528072</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/custom-pages-arguments.html b/sites/all/modules/ctools/page_manager/help/custom-pages-arguments.html
new file mode 100644
index 000000000..516a4292b
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/custom-pages-arguments.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528058">http://drupal.org/node/528058</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/custom-pages-menu.html b/sites/all/modules/ctools/page_manager/help/custom-pages-menu.html
new file mode 100644
index 000000000..48cf9c397
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/custom-pages-menu.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528078">http://drupal.org/node/528078</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/custom-pages.html b/sites/all/modules/ctools/page_manager/help/custom-pages.html
new file mode 100644
index 000000000..18e66d4be
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/custom-pages.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528050">http://drupal.org/node/528050</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/getting-started-create.html b/sites/all/modules/ctools/page_manager/help/getting-started-create.html
new file mode 100644
index 000000000..a3d295e38
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/getting-started-create.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528038">http://drupal.org/node/528038</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/getting-started-custom-nodes.html b/sites/all/modules/ctools/page_manager/help/getting-started-custom-nodes.html
new file mode 100644
index 000000000..d62eb0f32
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/getting-started-custom-nodes.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528044">http://drupal.org/node/528044</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/getting-started-custom-vocabulary.html b/sites/all/modules/ctools/page_manager/help/getting-started-custom-vocabulary.html
new file mode 100644
index 000000000..7148cd96a
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/getting-started-custom-vocabulary.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528046">http://drupal.org/node/528046</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/getting-started-members.html b/sites/all/modules/ctools/page_manager/help/getting-started-members.html
new file mode 100644
index 000000000..87b21227c
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/getting-started-members.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528040">http://drupal.org/node/528040</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/getting-started-page-list.html b/sites/all/modules/ctools/page_manager/help/getting-started-page-list.html
new file mode 100644
index 000000000..d60bea4ad
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/getting-started-page-list.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528036">http://drupal.org/node/528036</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/getting-started.html b/sites/all/modules/ctools/page_manager/help/getting-started.html
new file mode 100644
index 000000000..4e4f24ae8
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/getting-started.html
@@ -0,0 +1,15 @@
+
+Note: this page is currently very preliminary. Please visit <a href="http://drupal.org/node/528034">http://drupal.org/node/528034</a> to help provide this documentation page!
+
+This is a quick summary:
+
+<ul>
+<li>Visit administer >> site building >> pages to get to the primary page manager interface.</li>
+<li>You can add custom pages for your basic landing pages, front pages, whatever you like for normal content display.</li>
+<li>You can use the system pages to create finer control of how taxonomy vocabularies, nodes and user profiles are displayed.</li>
+<li>When you add your first custom page, do not bother with the optional features. You will not need these until you get to more advanced tasks.</li>
+<li>The selection rules are the key to creating node displays for just one node type.</li>
+<li>Everything in this system is pluggable. A little PHP knowledge and exploration of the plugins directories can take you a long way.</li>
+</ul>
+
+
diff --git a/sites/all/modules/ctools/page_manager/help/page-task-type.html b/sites/all/modules/ctools/page_manager/help/page-task-type.html
new file mode 100644
index 000000000..c382c76ff
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/page-task-type.html
@@ -0,0 +1,4 @@
+
+Additional 'task' keys support:
+
+operations -- a list of operations suitable for theme('links') \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/help/page_manager.help.ini b/sites/all/modules/ctools/page_manager/help/page_manager.help.ini
new file mode 100644
index 000000000..05cadb482
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/page_manager.help.ini
@@ -0,0 +1,59 @@
+[advanced help settings]
+line break = TRUE
+
+[about]
+title = About Page Manager
+weight = -100
+
+[getting-started]
+title = Getting Started
+weight = -80
+
+[getting-started-page-list]
+title = The page list
+weight = -90
+parent = getting-started
+
+[getting-started-create]
+title = Creating a page
+weight = -80
+parent = getting-started
+
+[getting-started-members]
+title = Tutorial: Make a page that looks different for members
+weight = -70
+parent = getting-started
+
+[getting-started-custom-nodes]
+title = Tutorial: Customize the look of a single node type
+weight = -60
+parent = getting-started
+
+[getting-started-custom-vocabulary]
+title = Tutorial: Customize the look of a single taxonomy vocabulary
+weight = -50
+parent = getting-started
+
+[custom-pages]
+title = Custom pages
+weight = -50
+
+[custom-pages-arguments]
+title = Arguments
+weight = -100
+parent = custom-pages
+
+[custom-pages-access]
+title = Access control
+weight = -90
+parent = custom-pages
+
+[custom-pages-menu]
+title = Menu items
+weight = -80
+parent = custom-pages
+
+[variants]
+title = Variants
+weight = -40
+
diff --git a/sites/all/modules/ctools/page_manager/help/variants.html b/sites/all/modules/ctools/page_manager/help/variants.html
new file mode 100644
index 000000000..48cf9c397
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/help/variants.html
@@ -0,0 +1,2 @@
+
+Please visit <a href="http://drupal.org/node/528078">http://drupal.org/node/528078</a> to help provide this documentation page. \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/images/arrow-active.png b/sites/all/modules/ctools/page_manager/images/arrow-active.png
new file mode 100644
index 000000000..3bbd3c27f
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/images/arrow-active.png
Binary files differ
diff --git a/sites/all/modules/ctools/page_manager/images/locked-other.png b/sites/all/modules/ctools/page_manager/images/locked-other.png
new file mode 100644
index 000000000..b84a154e8
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/images/locked-other.png
Binary files differ
diff --git a/sites/all/modules/ctools/page_manager/images/locked.png b/sites/all/modules/ctools/page_manager/images/locked.png
new file mode 100644
index 000000000..2116eb1c8
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/images/locked.png
Binary files differ
diff --git a/sites/all/modules/ctools/page_manager/js/page-list.js b/sites/all/modules/ctools/page_manager/js/page-list.js
new file mode 100644
index 000000000..16b52911d
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/js/page-list.js
@@ -0,0 +1,44 @@
+
+/**
+ * Provide some extra responses for the page list so we can have automatic
+ * on change.
+ */
+
+Drupal.behaviors.PageManagerList = function() {
+ var timeoutID = 0;
+ $('form#page-manager-list-pages-form select:not(.pm-processed)')
+ .addClass('pm-processed')
+ .change(function() {
+ $('#edit-pages-apply').click();
+ });
+ $('form#page-manager-list-pages-form input[type=text]:not(.pm-processed)')
+ .addClass('pm-processed')
+ .keyup(function(e) {
+ switch (e.keyCode) {
+ case 16: // shift
+ case 17: // ctrl
+ case 18: // alt
+ case 20: // caps lock
+ case 33: // page up
+ case 34: // page down
+ case 35: // end
+ case 36: // home
+ case 37: // left arrow
+ case 38: // up arrow
+ case 39: // right arrow
+ case 40: // down arrow
+ case 9: // tab
+ case 13: // enter
+ case 27: // esc
+ return false;
+ default:
+ if (!$('#edit-pages-apply').hasClass('ctools-ajaxing')) {
+ if ((timeoutID)) {
+ clearTimeout(timeoutID);
+ }
+
+ timeoutID = setTimeout(function() { $('#edit-pages-apply').click(); }, 300);
+ }
+ }
+ });
+}
diff --git a/sites/all/modules/ctools/page_manager/page_manager.admin.inc b/sites/all/modules/ctools/page_manager/page_manager.admin.inc
new file mode 100644
index 000000000..0f164fe50
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/page_manager.admin.inc
@@ -0,0 +1,1904 @@
+<?php
+
+/**
+ * @file
+ * Administrative functions for the page manager.
+ *
+ * This provides the UI to list, create, edit and delete pages, though much
+ * of this is delegated through to individual tasks.
+ */
+
+/**
+ * Output a list of pages that are managed.
+ */
+function page_manager_list_page($js = NULL) {
+ // Prevent this page from showing up when random other links fail.
+ if ($js && $js != 'ajax' && $js != 'nojs') {
+ return MENU_NOT_FOUND;
+ }
+
+ // TRUE if 'ajax', FALSE if otherwise.
+ $js = $js == 'ajax';
+
+ // If we do any form rendering, it's to completely replace a form on the
+ // page, so don't let it force our ids to change.
+ if ($js && isset($_POST['ajax_html_ids'])) {
+ unset($_POST['ajax_html_ids']);
+ }
+
+ if (module_exists('advanced_help') && !$js) {
+ drupal_set_message(theme('advanced_help_topic', array(
+ 'module' => 'page_manager',
+ 'topic' => 'getting-started',
+ 'type' => t('See the getting started guide for more information.'),
+ )));
+ }
+
+ $tasks = page_manager_get_tasks_by_type('page');
+ $pages = array('operations' => array(), 'tasks' => array());
+
+ page_manager_get_pages($tasks, $pages);
+
+ // Add lock icon to all locked tasks.
+ global $user;
+
+ ctools_include('object-cache');
+ $locks = ctools_object_cache_test_objects('page_manager_page', $pages['tasks']);
+ foreach ($locks as $task_name => $lock) {
+ if ($lock->uid == $user->uid) {
+ $pages['rows'][$task_name]['class'][] = ' page-manager-locked';
+ $pages['rows'][$task_name]['title'] = t('This page is currently locked for editing by you. Nobody else may edit this page until these changes are saved or canceled.');
+ }
+ else {
+ $pages['rows'][$task_name]['class'][] = ' page-manager-locked-other';
+ $pages['rows'][$task_name]['title'] = t('This page is currently locked for editing by another user. You may not edit this page without breaking the lock.');
+ }
+ }
+
+ $input = $_POST;
+
+ // Respond to a reset command by clearing session and doing a drupal goto
+ // back to the base URL.
+ if (isset($input['op']) && $input['op'] == t('Reset')) {
+ unset($_SESSION['page_manager']['#admin']);
+ if (!$js) {
+ drupal_goto($_GET['q']);
+ }
+ // clear everything but form id, form build id and form token:
+ $keys = array_keys($input);
+ foreach ($keys as $id) {
+ if ($id != 'form_id' && $id != 'form_build_id' && $id != 'form_token') {
+ unset($input[$id]);
+ }
+ }
+ $replace_form = TRUE;
+ }
+ if (count($input) <= 1) {
+ if (isset($_SESSION['page_manager']['#admin']) && is_array($_SESSION['page_manager']['#admin'])) {
+ $input = $_SESSION['page_manager']['#admin'];
+ }
+ }
+ else {
+ $_SESSION['page_manager']['#admin'] = $input;
+ unset($_SESSION['page_manager']['#admin']['q']);
+ }
+
+ $form_state = array(
+ 'pages' => &$pages,
+ 'input' => $input,
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ );
+
+ // This form will sort and filter the pages.
+ $form = drupal_build_form('page_manager_list_pages_form', $form_state);
+
+ $header = array(
+ array('data' => t('Type'), 'class' => array('page-manager-page-type')),
+ array('data' => t('Module'), 'class' => array('page-manager-page-module')),
+ array('data' => t('Name'), 'class' => array('page-manager-page-name')),
+ array('data' => t('Title'), 'class' => array('page-manager-page-title')),
+ array('data' => t('Path'), 'class' => array('page-manager-page-path')),
+ array('data' => t('Storage'), 'class' => array('page-manager-page-storage')),
+ );
+
+ $header[] = array('data' => t('Operations'), 'class' => array('page-manager-page-operations'));
+ $table = theme('table', array('header' => $header, 'rows' => $pages['rows'], 'attributes' => array('id' => 'page-manager-list-pages')));
+
+ $operations = '<div id="page-manager-links" class="links">' . theme('links', array('links' => $pages['operations'])) . '</div>';
+
+ drupal_add_css(drupal_get_path('module', 'page_manager') . '/css/page-manager.css');
+
+
+ if (!$js) {
+ return array('#markup' => drupal_render($form) . $table . $operations);
+ }
+
+ ctools_include('ajax');
+ $commands = array();
+ $commands[] = ajax_command_replace('#page-manager-list-pages', $table);
+ if (!empty($replace_form)) {
+ $commands[] = ajax_command_replace('#page-manager-list-pages-form', drupal_render($form));
+ }
+ print ajax_render($commands);
+ ajax_footer();
+}
+
+/**
+ * Sort tasks into buckets based upon whether or not they have subtasks.
+ */
+function page_manager_get_pages($tasks, &$pages, $task_id = NULL) {
+ foreach ($tasks as $id => $task) {
+ if (empty($task_id) && !empty($task['page operations'])) {
+ $pages['operations'] = array_merge($pages['operations'], $task['page operations']);
+ }
+
+ // If a type has subtasks, add its subtasks in its own table.
+ if (!empty($task['subtasks'])) {
+ page_manager_get_pages(page_manager_get_task_subtasks($task), $pages, $task['name']);
+ continue;
+ }
+
+ if (isset($task_id)) {
+ $task_name = page_manager_make_task_name($task_id, $task['name']);
+ }
+ else {
+ $task_name = $task['name'];
+ }
+
+ $class = array('page-task-' . $id);
+ if (isset($task['row class'])) {
+ $class[] = $task['row class'];
+ }
+
+ if (!empty($task['disabled'])) {
+ $class[] = 'page-manager-disabled';
+ }
+
+ $path = array();
+ $visible_path = '';
+ if (!empty($task['admin path'])) {
+ foreach (explode('/', $task['admin path']) as $bit) {
+ if (isset($bit[0]) && $bit[0] != '!') {
+ $path[] = $bit;
+ }
+ }
+
+ $path = implode('/', $path);
+ if (empty($task['disabled']) && strpos($path, '%') === FALSE) {
+ $visible_path = l('/' . $task['admin path'], $path);
+ }
+ else {
+ $visible_path = '/' . $task['admin path'];
+ }
+ }
+
+ $row = array('data' => array(), 'class' => $class, 'title' => strip_tags($task['admin description']));
+
+ $type = isset($task['admin type']) ? $task['admin type'] : t('System');
+ if (isset($task['module'])) {
+ $module = $task['module'];
+ }
+ elseif (isset($task['subtask']->export_module)) {
+ $module = $task['subtask']->export_module;
+ }
+ else {
+ $module = '';
+ }
+ $pages['types'][$type] = $type;
+ $row['data']['type'] = array('data' => $type, 'class' => array('page-manager-page-type'));
+ $row['data']['module'] = array('data' => $module, 'class' => array('page-manager-page-module'));
+ $row['data']['name'] = array('data' => $task_name, 'class' => array('page-manager-page-name'));
+ $row['data']['title'] = array('data' => $task['admin title'], 'class' => array('page-manager-page-title'));
+ $row['data']['path'] = array('data' => $visible_path, 'class' => array('page-manager-page-path'));
+
+ $storage = isset($task['storage']) ? $task['storage'] : t('In code');
+ $pages['storages'][$storage] = $storage;
+ $row['data']['storage'] = array('data' => $storage, 'class' => array('page-manager-page-storage'));
+
+
+/*
+ if (empty($task['disabled'])) {
+ $item = menu_get_item($path);
+ if (empty($item)) {
+ dsm($path);
+ }
+ else {
+ dsm($item);
+ }
+ }
+*/
+ $operations = array(
+ array(
+ 'title' => t('Edit'),
+ 'href' => page_manager_edit_url($task_name),
+ ),
+ );
+
+ if (!empty($task['enable callback'])) {
+ if (!empty($task['disabled'])) {
+ array_unshift($operations, array(
+ 'title' => t('Enable'),
+ 'href' => 'admin/structure/pages/nojs/enable/' . $task_name,
+ 'query' => array('token' => drupal_get_token($task_name)),
+ ));
+ }
+ else {
+ $operations[] = array(
+ 'title' => t('Disable'),
+ 'href' => 'admin/structure/pages/nojs/disable/' . $task_name,
+ 'query' => array('token' => drupal_get_token($task_name)),
+ );
+ }
+ }
+
+ $ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
+
+ $row['data']['operations'] = array('data' => $ops, 'class' => array('page-manager-page-operations'));
+
+ $pages['disabled'][$task_name] = !empty($task['disabled']);
+ $pages['tasks'][] = $task_name;
+ $pages['rows'][$task_name] = $row;
+ }
+}
+
+/**
+ * Provide a form for sorting and filtering the list of pages.
+ */
+function page_manager_list_pages_form($form, &$form_state) {
+ // This forces the form to *always* treat as submitted which is
+ // necessary to make it work.
+ if (empty($_POST)) {
+ $form["#programmed"] = TRUE;
+ }
+ $form['#action'] = url('admin/structure/pages/nojs/', array('absolute' => TRUE));
+ if (!variable_get('clean_url', FALSE)) {
+ $form['q'] = array(
+ '#type' => 'hidden',
+ '#value' => $_GET['q'],
+ );
+ }
+
+ $all = array('all' => t('<All>'));
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Type'),
+ '#options' => $all + $form_state['pages']['types'],
+ '#default_value' => 'all',
+ );
+
+ $form['storage'] = array(
+ '#type' => 'select',
+ '#title' => t('Storage'),
+ '#options' => $all + $form_state['pages']['storages'],
+ '#default_value' => 'all',
+ );
+
+ $form['disabled'] = array(
+ '#type' => 'select',
+ '#title' => t('Enabled'),
+ '#options' => $all + array('0' => t('Enabled'), '1' => t('Disabled')),
+ '#default_value' => 'all',
+ );
+
+ $form['search'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Search'),
+ );
+
+ $form['order'] = array(
+ '#type' => 'select',
+ '#title' => t('Sort by'),
+ '#options' => array(
+ 'disabled' => t('Enabled, title'),
+ 'title' => t('Title'),
+ 'name' => t('Name'),
+ 'path' => t('Path'),
+ 'type' => t('Type'),
+ 'storage' => t('Storage'),
+ ),
+ '#default_value' => 'disabled',
+ );
+
+ $form['sort'] = array(
+ '#type' => 'select',
+ '#title' => t('Order'),
+ '#options' => array(
+ 'asc' => t('Up'),
+ 'desc' => t('Down'),
+ ),
+ '#default_value' => 'asc',
+ );
+
+ $form['submit'] = array(
+ '#name' => '', // so it won't in the $_GET args
+ '#type' => 'submit',
+ '#id' => 'edit-pages-apply',
+ '#value' => t('Apply'),
+ '#attributes' => array('class' => array('use-ajax-submit ctools-auto-submit-click')),
+ );
+
+ $form['reset'] = array(
+ '#type' => 'submit',
+ '#id' => 'edit-pages-reset',
+ '#value' => t('Reset'),
+ '#attributes' => array('class' => array('use-ajax-submit')),
+ );
+
+ $form['#attached']['js'] = array(ctools_attach_js('auto-submit'), ctools_attach_js('page-list', 'page_manager'));
+ $form['#attached']['library'][] = array('system', 'drupal.ajax');
+ $form['#attached']['library'][] = array('system', 'jquery.form');
+ $form['#prefix'] = '<div class="clearfix">';
+ $form['#suffix'] = '</div>';
+ $form['#attributes'] = array('class' => array('ctools-auto-submit-full-form'));
+
+ return $form;
+}
+
+/**
+ * Accept submission from the page manager sort/filter form and apply it
+ * to the list of pages.
+ */
+function page_manager_list_pages_form_submit(&$form, &$form_state) {
+ // Filter and re-sort the pages.
+
+ // This is a copy.
+ $rows = $form_state['pages']['rows'];
+
+ $sorts = array();
+ foreach ($rows as $name => $data) {
+ // Filter
+ if ($form_state['values']['type'] != 'all' && $form_state['values']['type'] != $data['data']['type']['data']) {
+ continue;
+ }
+
+ if ($form_state['values']['storage'] != 'all' && $form_state['values']['storage'] != $data['data']['storage']['data']) {
+ continue;
+ }
+
+ if ($form_state['values']['disabled'] != 'all' && $form_state['values']['disabled'] != $form_state['pages']['disabled'][$name]) {
+ continue;
+ }
+
+ if ($form_state['values']['search'] &&
+ strpos($data['data']['name']['data'], $form_state['values']['search']) === FALSE &&
+ strpos($data['data']['path']['data'], $form_state['values']['search']) === FALSE &&
+ strpos($data['data']['title']['data'], $form_state['values']['search']) === FALSE) {
+ continue;
+ }
+ // Set up sorting
+ switch ($form_state['values']['order']) {
+ case 'disabled':
+ $sorts[$name] = !$form_state['pages']['disabled'][$name] . $data['data']['title']['data'];
+ break;
+ case 'title':
+ $sorts[$name] = $data['data']['title']['data'];
+ break;
+ case 'name':
+ $sorts[$name] = $data['data']['name']['data'];
+ break;
+ case 'path':
+ $sorts[$name] = $data['data']['path']['data'];
+ break;
+ case 'type':
+ $sorts[$name] = $data['data']['type']['data'];
+ break;
+ case 'storage':
+ $sorts[$name] = $data['data']['storage']['data'];
+ break;
+ }
+ }
+
+ // Now actually sort
+ if ($form_state['values']['sort'] == 'desc') {
+ arsort($sorts);
+ }
+ else {
+ asort($sorts);
+ }
+
+ // Nuke the original.
+ $form_state['pages']['rows'] = array();
+ // And restore.
+ foreach ($sorts as $name => $title) {
+ $form_state['pages']['rows'][$name] = $rows[$name];
+ }
+
+}
+
+/**
+ * Render the edit page for a a page, custom or system.
+ */
+function page_manager_edit_page($page) {
+ drupal_set_title($page->subtask['admin title'], PASS_THROUGH);
+ // Provide and process the save page form before anything else.
+ $form_state = array('page' => &$page);
+ $built_form = drupal_build_form('page_manager_save_page_form', $form_state);
+ $form = drupal_render($built_form);
+
+ $operations = page_manager_get_operations($page);
+ $args = array('summary');
+ $rendered_operations = page_manager_render_operations($page, $operations, $args, array('class' => array('operations-main')), 'nav');
+ $content = page_manager_get_operation_content(FALSE, $page, $args, $operations);
+ $output = theme('page_manager_edit_page', array('page' => $page, 'save' => $form, 'operations' => $rendered_operations, 'content' => $content));
+ return array('#markup' => $output);
+}
+
+/**
+ * Entry point to edit a single operation for a page.
+ *
+ * @param $js
+ * Whether or not the page was called via javascript.
+ * @param $page
+ * The cached page that is being edited.
+ * @param ...
+ * A number of items used to drill down into the actual operation called.
+ */
+function page_manager_edit_page_operation() {
+ $args = func_get_args();
+ $js = array_shift($args);
+ $page = array_shift($args);
+
+ $operations = page_manager_get_operations($page);
+ $content = page_manager_get_operation_content($js, $page, $args, $operations);
+
+ // If the operation requested we go somewhere else afterward, oblige it.
+ if (isset($content['new trail'])) {
+ $args = $content['new trail'];
+ // Get operations again, for the operation may have changed their availability.
+ $operations = page_manager_get_operations($page);
+ $content = page_manager_get_operation_content($js, $page, $args, $operations);
+ }
+
+ // Rendering the content may have been a form submission that changed the
+ // operations, such as renaming or adding a handler. Thus we get a new set
+ // of operations.
+ $operations = page_manager_get_operations($page);
+ $rendered_operations = page_manager_render_operations($page, $operations, $args, array('class' => array('operations-main')), 'nav');
+
+ // Since this form should never be submitted to this page, process it late so
+ // that we can be sure it notices changes.
+ $form_state = array('page' => &$page);
+ $built_form = drupal_build_form('page_manager_save_page_form', $form_state);
+ $form = drupal_render($built_form);
+
+ $output = theme('page_manager_edit_page', array('page' => $page, 'save' => $form, 'operations' => $rendered_operations, 'content' => $content));
+
+ if ($js) {
+ $commands = array();
+ $commands[] = ajax_command_replace('#page-manager-edit', $output);
+
+ print ajax_render($commands);
+ ajax_footer();
+ return;
+ }
+
+ drupal_set_title($page->subtask['admin title'], PASS_THROUGH);
+ return $output;
+}
+
+/**
+ * Take the operations array from a task and expand it.
+ *
+ * This allows some of the operations to be dynamic, based upon settings
+ * on the task or the task's handlers. Each operation should have a type. In
+ * addition to all the types allowed in page_manager_render_operations, these
+ * types will be dynamically replaced with something else:
+ * - 'handlers': An automatically created group that contains all the task's
+ * handlers and appropriate links.
+ * - 'function': A callback (which will be placed in the 'function' parameter
+ * that should return an array of operations. This can be used to provide
+ * additional, dynamic links if needed.
+ */
+function page_manager_get_operations($page, $operations = NULL) {
+ if (!isset($operations)) {
+ // All tasks have at least these 2 ops:
+ $operations = array(
+ 'summary' => array(
+ 'title' => t('Summary'),
+ 'description' => t('Get a summary of the information about this page.'),
+ 'path' => 'admin/structure/pages/edit',
+ 'ajax' => FALSE,
+ 'no operations' => TRUE,
+ 'form info' => array(
+ 'no buttons' => TRUE,
+ ),
+ 'form' => 'page_manager_page_summary',
+ ),
+ 'actions' => array(
+ 'type' => 'group',
+ 'title' => '',
+ 'class' => array('operations-actions'),
+ 'location' => 'primary',
+ 'children' => array(),
+ ),
+ );
+
+ if (isset($page->subtask['operations'])) {
+ $operations += $page->subtask['operations'];
+ // add actions separately.
+ if (!empty($page->subtask['operations']['actions'])) {
+ $operations['actions']['children'] += $page->subtask['operations']['actions']['children'];
+ }
+ }
+ $operations['handlers'] = array('type' => 'handlers');
+ }
+
+ $result = array();
+ foreach ($operations as $id => $operation) {
+ if (empty($operation['type'])) {
+ $operation['type'] = 'operation';
+ }
+ switch ($operation['type']) {
+ case 'handlers':
+ $result[$id] = page_manager_get_handler_operations($page);
+ break;
+ case 'function':
+ if (function_exists($operation['function'])) {
+ $retval = $function($page, $operation);
+ if (is_array($retval)) {
+ $result[$id] = $retval;
+ }
+ }
+ break;
+ default:
+ $result[$id] = $operation;
+ }
+ }
+
+ if (!empty($page->subtask['enable callback']) && !empty($page->subtask['disabled']) && empty($result['actions']['children']['enable'])) {
+ $result['actions']['children']['enable'] = array(
+ 'title' => t('Enable'),
+ 'description' => t('Activate this page so that it will be in use in your system.'),
+ 'form' => 'page_manager_enable_form',
+ 'ajax' => FALSE,
+ 'silent' => TRUE,
+ 'no update and save' => TRUE,
+ 'form info' => array(
+ 'finish text' => t('Enable'),
+ ),
+ );
+ }
+
+ if (!empty($page->subtask['enable callback']) && empty($page->subtask['disabled']) && empty($result['actions']['children']['disable'])) {
+ $result['actions']['children']['disable'] = array(
+ 'title' => t('Disable'),
+ 'description' => t('De-activate this page. The data will remain but the page will not be in use on your system.'),
+ 'form' => 'page_manager_disable_form',
+ 'ajax' => FALSE,
+ 'silent' => TRUE,
+ 'no update and save' => TRUE,
+ 'form info' => array(
+ 'finish text' => t('Disable'),
+ ),
+ );
+ }
+
+ $result['actions']['children']['add'] = array(
+ 'title' => t('Add variant'),
+ 'description' => t('Add a new variant to this page.'),
+ 'form' => 'page_manager_handler_add',
+ 'ajax' => FALSE,
+ 'silent' => TRUE, // prevents a message about updating and prevents this item from showing as changed.
+ 'no update and save' => TRUE, // get rid of update and save button which is bad here.
+ 'form info' => array(
+ 'finish text' => t('Create variant'),
+ ),
+ );
+
+ // Restrict variant import due to security implications.
+ if (user_access('use ctools import')) {
+ $result['actions']['children']['import'] = array(
+ 'title' => t('Import variant'),
+ 'description' => t('Add a new variant to this page from code exported from another page.'),
+ 'form' => 'page_manager_handler_import',
+ );
+ }
+
+ if (count($page->handlers) > 1) {
+ $result['actions']['children']['rearrange'] = array(
+ 'title' => t('Reorder variants'),
+ 'ajax' => FALSE,
+ 'description' => t('Change the priority of the variants to ensure that the right one gets selected.'),
+ 'form' => 'page_manager_handler_rearrange',
+ );
+ }
+
+ // This is a special operation used to configure a new task handler before
+ // it is added.
+ if (isset($page->new_handler)) {
+ $plugin = page_manager_get_task_handler($page->new_handler->handler);
+ $result['actions']['children']['configure'] = array(
+ 'title' => t('Configure'),
+ 'description' => t('Configure a newly created variant prior to actually adding it to the page.'),
+ 'ajax' => FALSE,
+ 'no update and save' => TRUE, // get rid of update and save button which is bad here.
+ 'form info' => array(
+ // We use our own cancel and finish callback to handle the fun stuff.
+ 'finish callback' => 'page_manager_handler_add_finish',
+ 'cancel callback' => 'page_manager_handler_add_cancel',
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'finish text' => t('Create variant'),
+ ),
+ 'form' => array(
+ 'forms' => $plugin['forms'],
+ ),
+ );
+
+ foreach ($page->forms as $id) {
+ if (isset($plugin['add features'][$id])) {
+ $result['actions']['children']['configure']['form']['order'][$id] = $plugin['add features'][$id];
+ }
+ else if (isset($plugin['required forms'][$id])) {
+ $result['actions']['children']['configure']['form']['order'][$id] = $plugin['required forms'][$id];
+ }
+ }
+ }
+
+ if ($page->locked) {
+ $result['actions']['children']['break-lock'] = array(
+ 'title' => t('Break lock'),
+ 'description' => t('Break the lock on this page so that you can edit it.'),
+ 'form' => 'page_manager_break_lock',
+ 'ajax' => FALSE,
+ 'no update and save' => TRUE, // get rid of update and save button which is bad here.
+ 'form info' => array(
+ 'finish text' => t('Break lock'),
+ ),
+ 'even locked' => TRUE, // show button even if locked
+ 'silent' => TRUE,
+ );
+ }
+
+ drupal_alter('page_manager_operations', $result, $page);
+ return $result;
+}
+
+/**
+ * Collect all the operations related to task handlers (variants) and
+ * build a menu.
+ */
+function page_manager_get_handler_operations(&$page) {
+ ctools_include('export');
+ $group = array(
+ 'type' => 'group',
+ 'class' => array('operations-handlers'),
+ 'title' => t('Variants'),
+ );
+
+ $operations = array();
+
+ // If there is only one variant, let's not have it collapsible.
+ $collapsible = count($page->handler_info) != 1;
+ foreach ($page->handler_info as $id => $info) {
+ if ($info['changed'] & PAGE_MANAGER_CHANGED_DELETED) {
+ continue;
+ }
+ $handler = $page->handlers[$id];
+ $plugin = page_manager_get_task_handler($handler->handler);
+
+ $operations[$id] = array(
+ 'type' => 'group',
+ 'class' => array('operations-handlers-' . $id),
+ 'title' => page_manager_get_handler_title($plugin, $handler, $page->task, $page->subtask_id),
+ 'collapsible' => $collapsible,
+ 'children' => array(),
+ );
+
+ $operations[$id]['children']['actions'] = array(
+ 'type' => 'group',
+ 'class' => array('operations-handlers-actions-' . $id),
+ 'title' => t('Variant operations'),
+ 'children' => array(),
+ 'location' => $id,
+ );
+
+ // There needs to be a 'summary' item here for variants.
+ $operations[$id]['children']['summary'] = array(
+ 'title' => t('Summary'),
+ 'description' => t('Get a summary of the information about this variant.'),
+ 'form info' => array(
+ 'no buttons' => TRUE,
+ ),
+ 'form' => 'page_manager_handler_summary',
+ );
+
+ if ($plugin && isset($plugin['operations'])) {
+ $operations[$id]['children'] += $plugin['operations'];
+ }
+
+ $actions = &$operations[$id]['children']['actions']['children'];
+
+ $actions['clone'] = array(
+ 'title' => t('Clone'),
+ 'description' => t('Make an exact copy of this variant.'),
+ 'form' => 'page_manager_handler_clone',
+ );
+ $actions['export'] = array(
+ 'title' => t('Export'),
+ 'description' => t('Export this variant into code to import into another page.'),
+ 'form' => 'page_manager_handler_export',
+ );
+ if ($handler->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) {
+ $actions['delete'] = array(
+ 'title' => t('Revert'),
+ 'description' => t('Remove all changes to this variant and revert to the version in code.'),
+ 'form' => 'page_manager_handler_delete',
+ 'no update and save' => TRUE,
+ 'form info' => array(
+ 'finish text' => t('Revert'),
+ ),
+ );
+ }
+ else if ($handler->export_type != EXPORT_IN_CODE) {
+ $actions['delete'] = array(
+ 'title' => t('Delete'),
+ 'description' => t('Remove this variant from the page completely.'),
+ 'form' => 'page_manager_handler_delete',
+ 'form info' => array(
+ 'finish text' => t('Delete'),
+ 'save text' => t('Delete and save'),
+ ),
+ );
+ }
+ if (!empty($handler->disabled)) {
+ $actions['enable'] = array(
+ 'title' => t('Enable'),
+ 'description' => t('Activate this variant so that it will be in use in your system.'),
+ 'form' => 'page_manager_handler_enable',
+ 'silent' => TRUE,
+ 'form info' => array(
+ 'finish text' => t('Enable'),
+ 'save text' => t('Enable and save'),
+ ),
+ );
+ }
+ else {
+ $actions['disable'] = array(
+ 'title' => t('Disable'),
+ 'description' => t('De-activate this variant. The data will remain but the variant will not be in use on your system.'),
+ 'form' => 'page_manager_handler_disable',
+ 'silent' => TRUE,
+ 'form info' => array(
+ 'finish text' => t('Disable'),
+ 'save text' => t('Disable and save'),
+ ),
+ );
+ }
+
+ drupal_alter('page_manager_variant_operations', $operations[$id], $handler);
+ }
+ if (empty($operations)) {
+ $operations['empty'] = array(
+ 'type' => 'text',
+ 'title' => t('No variants'),
+ );
+ }
+
+ $group['children'] = $operations;
+ return $group;
+}
+
+/**
+ * Get an operation from a trail.
+ *
+ * @return array($operation, $active, $args)
+ */
+function page_manager_get_operation($operations, $trail) {
+ $args = $trail;
+ $stop = FALSE;
+ $active = array();
+ $titles = array();
+ // Drill down into operations array:
+ while (!$stop) {
+ $check = reset($args);
+ $stop = TRUE;
+ if (is_array($operations)) {
+ if (isset($operations[$check])) {
+ $active[] = $check;
+ $operation = array_shift($args);
+ // check to see if this operation has children. If so, we continue.
+ if (!isset($operations[$check]['children'])) {
+ $operations = $operations[$check];
+ }
+ else {
+ $titles[] = $operations[$check]['title'];
+ $operations = $operations[$check]['children'];
+ // continue only if the operation hs children.
+ $stop = FALSE;
+ }
+ }
+ }
+ }
+
+ return array($operations, $active, $args, $titles);
+}
+
+/**
+ * Fetch the content for an operation.
+ *
+ * First, this drills down through the arguments to find the operation, and
+ * turns whatever it finds into the active trail which is then used to
+ * hilite where we are when rendering the operation list.
+ *
+ * The arguments are discovered from the URL, and are an exact match for where
+ * the operation is in the hierarchy. For example, handlers/foo/settings will
+ * be the operation to edit the settings for the handler named foo. This comes
+ * in as an array ('handlers', 'foo', 'settings') and is used to find where the
+ * data for that operation is in the array.
+ */
+function page_manager_get_operation_content($js, &$page, $trail, $operations) {
+ list($operation, $active, $args, $titles) = page_manager_get_operation($operations, $trail);
+ // Once we've found the operation, send it off to render.
+ if ($operation) {
+ $content = _page_manager_get_operation_content($js, $page, $active, $operation, $titles, $args);
+ }
+
+ if (empty($content)) {
+ $content = _page_manager_get_operation_content($js, $page, array('summary'), $operations['summary']);
+ }
+
+ return $content;
+}
+
+/**
+ * Fetch the content for an operation, after it's been discovered from arguments.
+ *
+ * This system runs through the CTools form wizard. Each operation specifies a form
+ * or set of forms that it may use. Operations may also specify wrappers and can
+ * set their own next/finish handlers so that they can make additional things happen
+ * at the end.
+ */
+function _page_manager_get_operation_content($js, &$page, $active, $operation, $titles = array(), $args = array()) {
+ if (isset($operation['form'])) {
+ $form_info = array(
+ 'id' => 'page_manager_page',
+ 'finish text' => t('Update'),
+ 'show trail' => FALSE,
+ 'show back' => FALSE,
+ 'show return' => FALSE,
+ 'show cancel' => FALSE,
+ 'next callback' => 'page_manager_edit_page_next',
+ 'finish callback' => 'page_manager_edit_page_finish',
+ // Items specific to the 'edit' routines that will get moved over:
+ 'path' => page_manager_edit_url($page->task_name, $active) . "/%step",
+ // wrapper function to add an extra finish button.
+ 'wrapper' => 'page_manager_operation_wrapper',
+ );
+
+ // If $operation['form'] is simply a string, then it is the function
+ // name of the form.
+ if (!is_array($operation['form'])) {
+ $form_info['order'] = array(
+ 'form' => $operation['title'],
+ );
+ $form_info['forms'] = array(
+ 'form' => array('form id' => $operation['form']),
+ );
+ if (isset($operation['wrapper'])) {
+ $form_info['forms']['form']['wrapper'] = $operation['wrapper'];
+ }
+ }
+ // Otherwise it's the order and forms arrays directly.
+ else {
+ $form_info['order'] = $operation['form']['order'];
+ $form_info['forms'] = $operation['form']['forms'];
+ }
+
+ // Allow the operation to override any form info settings:
+ if (isset($operation['form info'])) {
+ foreach ($operation['form info'] as $key => $setting) {
+ $form_info[$key] = $setting;
+ }
+ }
+
+ if (!empty($page->subtask['operations include'])) {
+ // Quickly load any files necessary to display the forms.
+ $page->subtask['operations include']['function'] = 'nop';
+ ctools_plugin_get_function($page->subtask, 'operations include');
+ }
+
+ $step = array_shift($args);
+ // If step is unset, go with the basic step.
+ if (!isset($step)) {
+ $step = current(array_keys($form_info['order']));
+ }
+
+ // If it is locked, hide the buttonzzz!
+ if ($page->locked && empty($operation['even locked'])) {
+ $form_info['no buttons'] = TRUE;
+ }
+
+ ctools_include('wizard');
+ $form_state = array(
+ 'page' => $page,
+ 'type' => 'edit',
+ 'ajax' => $js && (!isset($operation['ajax']) || !empty($operation['ajax'])),
+ 'rerender' => TRUE,
+ 'trail' => $active,
+ 'task_name' => $page->task_name,
+ 'task_id' => $page->task_id,
+ 'task' => $page->task,
+ 'subtask_id' => $page->subtask_id,
+ 'subtask' => $page->subtask,
+ 'operation' => $operation,
+ );
+
+ if ($active && $active[0] == 'handlers' && isset($form_state['page']->handlers[$form_state['trail'][1]])) {
+ $form_state['handler_id'] = $form_state['trail'][1];
+ $form_state['handler'] = &$form_state['page']->handlers[$form_state['handler_id']];
+ }
+
+ if ($active && $active[0] == 'actions' && $active[1] == 'configure' && isset($form_state['page']->new_handler)) {
+ $form_state['handler_id'] = $form_state['page']->new_handler->name;
+ $form_state['handler'] = &$form_state['page']->new_handler;
+ }
+
+ $built_form = ctools_wizard_multistep_form($form_info, $step, $form_state);
+ $output = drupal_render($built_form);
+ $title = empty($form_state['title']) ? $operation['title'] : $form_state['title'];
+ $titles[] = $title;
+ $title = implode(' &raquo; ', array_filter($titles));
+
+ // If there are messages for the form, render them.
+ if ($messages = theme('status_messages')) {
+ $output = $messages . $output;
+ }
+
+ $description = isset($operation['admin description']) ? $operation['admin description'] : (isset($operation['description']) ? $operation['description'] : '');
+ $return = array(
+ 'title' => $title,
+ 'content' => $output,
+ 'description' => $description,
+ );
+
+ // Append any extra content, used for the preview which is submitted then
+ // rendered.
+ if (isset($form_state['content'])) {
+ $return['content'] .= $form_state['content'];
+ }
+
+ // If the form wanted us to go somewhere else next, pass that along.
+ if (isset($form_state['new trail'])) {
+ $return['new trail'] = $form_state['new trail'];
+ }
+ }
+ else {
+ $return = array(
+ 'title' => t('Error'),
+ 'content' => t('This operation trail does not exist.'),
+ );
+ }
+
+ $return['active'] = $active;
+ return $return;
+}
+
+function page_manager_operation_wrapper($form, &$form_state) {
+ if (empty($form_state['operation']['no update and save']) && !empty($form['buttons']['return']['#wizard type']) && $form['buttons']['return']['#wizard type']) {
+ $form['buttons']['save'] = array(
+ '#type' => 'submit',
+ '#value' => !empty($form_state['form_info']['save text']) ? $form_state['form_info']['save text'] : t('Update and save'),
+ '#wizard type' => 'finish',
+ '#attributes' => $form['buttons']['return']['#attributes'],
+ '#save' => TRUE,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Callback generated when the an operation edit finished.
+ */
+function page_manager_edit_page_finish(&$form_state) {
+ if (empty($form_state['operation']['silent'])) {
+ if (empty($form_state['clicked_button']['#save'])) {
+ drupal_set_message(t('The page has been updated. Changes will not be permanent until you save.'));
+ }
+ else {
+ drupal_set_message(t('The page has been updated and saved.'));
+ }
+ $path = array();
+ foreach ($form_state['trail'] as $operation) {
+ $path[] = $operation;
+ $form_state['page']->changes[implode('/', $path)] = TRUE;
+ }
+ }
+
+ // If a handler was modified, set it to changed so we know to overwrite it.
+ if (isset($form_state['handler_id'])) {
+ $form_state['page']->handler_info[$form_state['handler_id']]['changed'] |= PAGE_MANAGER_CHANGED_CACHED;
+ }
+
+ // While we make buttons go away on locked pages, it is still possible to
+ // have a lock a appear while you were editing, and have your changes
+ // disappear. This at least warns the user that this has happened.
+ if (!empty($page->locked)) {
+ drupal_set_message(t('Unable to update changes due to lock.'));
+ }
+
+ // If the 'Update and Save' button was selected, write our cache out.
+ if (!empty($form_state['clicked_button']['#save'])) {
+ page_manager_save_page_cache($form_state['page']);
+ page_manager_clear_page_cache($form_state['page']->task_name);
+ $form_state['page'] = page_manager_get_page_cache($form_state['page']->task_name);
+ }
+ else {
+ if (empty($form_state['do not cache'])) {
+ page_manager_set_page_cache($form_state['page']);
+ }
+ }
+
+ // We basically always want to force a rerender when the forms
+ // are finished, so make sure there is a new trail.
+ if (empty($form_state['new trail'])) {
+ // force a rerender to get rid of old form items that may have changed
+ // during save.
+ $form_state['new trail'] = $form_state['trail'];
+ }
+
+ if (isset($form_state['new trail']) && empty($form_state['ajax'])) {
+ $form_state['redirect'] = page_manager_edit_url($form_state['page']->task_name, $form_state['new trail']);
+ }
+
+ $form_state['complete'] = TRUE;
+}
+
+/**
+ * Callback generated when the 'next' button is clicked.
+ *
+ * All we do here is store the cache.
+ */
+function page_manager_edit_page_next(&$form_state) {
+ page_manager_set_page_cache($form_state['page']);
+}
+
+/**
+ * Callback generated when the 'cancel' button is clicked.
+ *
+ * All we do here is clear the cache.
+ */
+function page_manager_edit_page_cancel(&$form_state) {
+ $page = $form_state['page'];
+}
+
+/**
+ * Render an operations array.
+ *
+ * This renders an array of operations into a series of nested UL statements,
+ * with ajax automatically on unless specified otherwise. Operations will
+ * automatically have the URLs generated nested.
+ *
+ * Each operation should have a 'type', which tells the renderer how to deal
+ * with it:
+ * - 'operation': An AJAX link to render. This is the default and is
+ * assumed if a type is not specified. Other fields for the operation:
+ * - - 'title': The text to display. Can be an image. Must be pre-sanitized.
+ * - - 'description': Text to place in the hover box over the link using the
+ * title attribute.
+ * - - 'arguments': Anything optional to put at the end of the URL.
+ * - - 'path': If set, overrides the default path.
+ * - - 'no operations': If set, the path will not have operations appended.
+ * - - 'no task': If set, the path will not have the task id.
+ * - - 'no link': If set, this item will just be text, not a link.
+ * - - 'ajax': If set to TRUE, ajax will be used. The default is TRUE.
+ * - - 'class': An optional class to specify for the link.
+ * - - 'form': The form to display for this operation, if using a single form.
+ * - - 'forms': An array of forms that must be paired with 'order' of this
+ * operation uses multiple forms. See wizard tool for details.
+ * - - 'order': The form order to use for multiple forms. See wizard tool for
+ * details.
+ * - - 'form info': Form info overrides for the wizard. See the wizard tool
+ * for available settings
+ * - 'group':
+ * - - 'title': The title of the link. May be HTML.
+ * - - 'title class': A class to apply to the title.
+ * - - 'children': An array of more operations that this group represents.
+ * All operations within this group will have this group's ID as part
+ * of the AJAX url to make it easier to find.
+ * - - 'class': A class to apply to the UL of the children.
+ * - - 'collapsible': If TRUE the collapsible tool will be used.
+ */
+function page_manager_render_operations(&$page, $operations, $active_trail, $attributes, $location, $parents = array()) {
+ drupal_add_library('system', 'drupal.ajax');
+
+ if (!isset($output[$location])) {
+ $output[$location] = '';
+ }
+
+ $keys = array_keys($operations);
+ $first = array_shift($keys);
+ $last = array_pop($keys);
+
+ // Make sure the 'first' and 'last' operations are part of THIS nav tree:
+ while ($keys && isset($operations[$first]['location']) && $operations[$first]['location'] != $location) {
+ $first = array_shift($keys);
+ }
+ while ($keys && isset($operations[$last]['location']) && $operations[$last]['location'] != $location) {
+ $last = array_pop($keys);
+ }
+
+ $active = reset($active_trail);
+ foreach ($operations as $id => $operation) {
+ $current_path = '';
+ if ($parents) {
+ $current_path .= implode('/', $parents) . '/';
+ }
+ $current_path .= $id;
+
+ if (empty($operation['type'])) {
+ $operation['type'] = 'operation';
+ }
+
+ // We only render an li for things in the same nav tree.
+ if (empty($operation['location']) || $operation['location'] == $location) {
+ if (!is_array($attributes['class'])) {
+ $attributes['class'] = array($attributes['class']);
+ }
+
+ $class = empty($attributes['class']) || !is_array($attributes['class']) ? array() : $attributes['class'];
+
+ if ($id == $first) {
+ $class[] = 'operation-first';
+ }
+ else if ($id == $last) {
+ $class[] = 'operation-last';
+ }
+
+ if (empty($operation['silent']) && !empty($page->changes[$current_path])) {
+ $class[] = $operation['type'] == 'group' ? 'changed-group' : 'changed';
+ }
+ else {
+ $class[] = 'not-changed';
+ }
+
+ if ($active == $id) {
+ $class[] = $operation['type'] == 'group' ? 'active-group' : 'active';
+ }
+ else {
+ $class[] = 'not-active';
+ }
+
+ $output[$location] .= '<li class="' . implode(' ', $class) . '">';
+ }
+
+ switch ($operation['type']) {
+ case 'text':
+ $output[$location] .= $operation['title'];
+ break;
+ case 'operation':
+ $path = isset($operation['path']) ? $operation['path'] : 'admin/structure/pages/nojs/operation';
+ if (!isset($operation['no task'])) {
+ $path .= '/' . $page->task_name;
+ }
+
+ if (!isset($operation['no operations'])) {
+ $path .= '/' . $current_path;
+ if (isset($operation['arguments'])) {
+ $path .= '/' . $arguments;
+ }
+ }
+
+ $class = array('page-manager-operation');
+ if (!isset($operation['ajax']) || !empty($operation['ajax'])) {
+ $class[] = 'use-ajax';
+ }
+ if (!empty($operation['class'])) {
+ $class[] = $operation['class'];
+ }
+
+ $description = isset($operation['description']) ? $operation['description'] : '';
+ if (empty($operation['silent']) && !empty($page->changes[$current_path])) {
+ $description .= ' ' . t('This setting contains unsaved changes.');
+ }
+
+ $output[$location] .= l($operation['title'], $path, array('attributes' => array('id' => 'page-manager-operation-' . $id, 'class' => $class, 'title' => $description), 'html' => TRUE));
+ break;
+ case 'group':
+ if ($active == $id) {
+ $trail = $active_trail;
+ array_shift($trail);
+ }
+ else {
+ $trail = array();
+ }
+ $group_location = isset($operation['location']) ? $operation['location'] : $location;
+ $temp = page_manager_render_operations($page, $operation['children'], $trail, $operation, $group_location, array_merge($parents, array($id)));
+ if ($temp) {
+ foreach ($temp as $id => $text) {
+ if (empty($output[$id])) {
+ $output[$id] = '';
+ }
+ $output[$id] .= $text;
+ }
+ }
+ break;
+ }
+
+ if (empty($operation['location']) || $operation['location'] == $location) {
+ $output[$location] .= '</li>';
+ }
+ }
+
+ if ($output[$location]) {
+ $classes = isset($attributes['class']) && is_array($attributes['class']) ? $attributes['class'] : array();
+ $classes[] = 'page-manager-operations';
+
+ $output[$location] = '<ul class="' . implode(' ', $classes) . '">' . $output[$location] . '</ul>';
+
+ if (!empty($attributes['title'])) {
+ $class = '';
+ if (isset($attributes['title class'])) {
+ $class = $attributes['title class'];
+ }
+ $title = '<div class="page-manager-group-title' . $class . '">' . $attributes['title'] . '</div>';
+
+ if (!empty($attributes['collapsible'])) {
+ $output[$location] = theme('ctools_collapsible', array('handle' => $title, 'content' => $output[$location], 'collapsed' => empty($active_trail)));
+ }
+ else {
+ $output[$location] = $title . $output[$location];
+ }
+ }
+ return $output;
+ }
+}
+
+/**
+ * Provide a simple form for saving the page manager info out of the cache.
+ */
+function page_manager_save_page_form($form, &$form_state) {
+ if (!empty($form_state['page']->changed)) {
+ $form['markup'] = array(
+ '#markup' => '<div class="changed-notification">' . t('You have unsaved changes to this page. You must select Save to write them to the database, or Cancel to discard these changes. Please note that if you have changed any form, you must submit that form before saving.') . '</div>',
+ );
+
+ // Always make sure we submit back to the proper page.
+ $form['#action'] = url('admin/structure/pages/edit/' . $form_state['page']->task_name);
+ $form['save'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#submit' => array('page_manager_save_page_form_submit'),
+ );
+
+ $form['cancel'] = array(
+ '#type' => 'submit',
+ '#value' => t('Cancel'),
+ '#submit' => array('page_manager_save_page_form_cancel'),
+ );
+ return $form;
+ }
+}
+
+/**
+ * Save the page.
+ */
+function page_manager_save_page_form_submit(&$form, &$form_state) {
+ page_manager_save_page_cache($form_state['page']);
+}
+
+/**
+ * Discard changes to the page.
+ */
+function page_manager_save_page_form_cancel($form, &$form_state) {
+ drupal_set_message(t('All pending changes have been discarded, and the page is now unlocked.'));
+ page_manager_clear_page_cache($form_state['page']->task_name);
+
+ if (!empty($form_state['page']->new)) {
+ $form_state['redirect'] = 'admin/structure/pages';
+ }
+}
+
+// --------------------------------------------------------------------------
+// Handler (variant) related forms.
+
+/**
+ * Add a new task handler.
+ */
+function page_manager_handler_add($form, &$form_state) {
+ // Get a list of possible task handlers for this task.
+ return page_manager_handler_add_form($form, $form_state);
+}
+
+/**
+ * Handler related forms.
+ */
+function page_manager_handler_add_submit(&$form, &$form_state) {
+ $cache = $form_state['page'];
+ $plugin = page_manager_get_task_handler($form_state['values']['handler']);
+
+ // Create a new handler.
+ $handler = page_manager_new_task_handler($plugin);
+ if (!empty($form_state['values']['title'])) {
+ $handler->conf['title'] = $form_state['values']['title'];
+ }
+ else {
+ $handler->conf['title'] = $plugin['title'];
+ }
+ $handler->conf['name'] = $form_state['values']['name'];
+ $cache->new_handler = $handler;
+
+ // Figure out which forms to present them with
+ $cache->forms = array();
+
+ $features = $form_state['values']['features'];
+ if (isset($features[$form_state['values']['handler']])) {
+ $cache->forms = array_merge($cache->forms, array_keys(array_filter($features[$form_state['values']['handler']])));
+ }
+
+ if (isset($plugin['required forms'])) {
+ $cache->forms = array_merge($cache->forms, array_keys($plugin['required forms']));
+ }
+
+ $form_state['no_rerender'] = TRUE;
+ if (!empty($cache->forms)) {
+ // Tell the form to go to the config page.
+ drupal_set_message(t('Before this variant can be added, it must be configured. When you are finished, click "Create variant" at the end of this wizard to add this to your page.'));
+ $form_state['new trail'] = array('actions', 'configure');
+ }
+ else {
+ // It has no forms at all. Add the variant and go to its first operation.
+ page_manager_handler_add_finish($form_state);
+ }
+}
+
+/**
+ * Finish the add process and make the new handler official.
+ */
+function page_manager_handler_add_finish(&$form_state) {
+ $page = &$form_state['page'];
+ $handler = $page->new_handler;
+ page_manager_handler_add_to_page($page, $handler);
+
+ // Remove the temporary page.
+ unset($page->new_handler);
+ unset($page->forms);
+
+ // Set the new destination
+ $plugin = page_manager_get_task_handler($handler->handler);
+ if (!empty($plugin['add finish'])) {
+ $location = $plugin['add finish'];
+ }
+ else {
+ $keys = array_keys($plugin['operations']);
+ $location = reset($keys);
+ }
+
+ $form_state['new trail'] = array('handlers', $handler->name, $location);
+
+ // Pass through.
+ page_manager_edit_page_finish($form_state);
+}
+
+/**
+ * Throw away a new handler and return to the add form
+ */
+function page_manager_handler_add_cancel(&$form_state) {
+ $form_state['new trail'] = array('handlers', 'add');
+
+ // Remove the temporary page.
+ unset($page->new_handler);
+ unset($page->forms);
+}
+
+/**
+ * Provide a consistent UI for adding handlers.
+ */
+function page_manager_handler_add_form($form, $form_state, $features = array()) {
+ $task = $form_state['task'];
+ $task_handler_plugins = page_manager_get_task_handler_plugins($task);
+ if (empty($task_handler_plugins)) {
+ drupal_set_message(t('There are currently no variants available and a page may not be added. Perhaps you need to install the Panels module to get a variant?'), 'error');
+ $form['buttons']['return']['#disabled'] = TRUE;
+ return;
+ }
+
+ foreach ($task_handler_plugins as $id => $plugin) {
+ $options[$id] = $plugin['title'];
+ if (isset($plugin['add features'])) {
+ $features[$id] = $plugin['add features'];
+ }
+ }
+
+ if (!isset($form_state['type']) || $form_state['type'] != 'add') {
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Title'),
+ '#description' => t('Administrative title of this variant. If you leave blank it will be automatically assigned.'),
+ );
+
+ $form['name'] = array(
+ '#type' => 'machine_name',
+ '#title' => t('Machine name'),
+ '#required' => FALSE,
+ '#description' => t("A unique machine-readable name for this variant. It must only contain lowercase letters, numbers, and underscores. This name will be used when exporting the variant. If left empty the variant's name will be used instead."),
+ '#size' => 32,
+ '#maxlength' => 32,
+ '#machine_name' => array(
+ 'exists' => 'page_manager_handler_check_machine_name',
+ 'source' => array('title'),
+ ),
+ '#field_prefix' => '<span dir="ltr">' . $form_state['task_name'] . '__',
+ '#field_suffix' => '</span>&lrm;',
+ );
+ }
+
+ $form['handler'] = array(
+ '#title' => t('Variant type'),
+ '#type' => 'select',
+ '#options' => $options,
+ );
+
+ // This set of checkboxes is not dangerous at all.
+ $form['features'] = array(
+ '#type' => 'item',
+ '#title' => t('Optional features'),
+ '#description' => t('Check any optional features you need to be presented with forms for configuring them. If you do not check them here you will still be able to utilize these features once the new page is created. If you are not sure, leave these unchecked.'),
+ '#tree' => TRUE,
+ );
+
+ ctools_include('dependent');
+ foreach ($features as $plugin => $feature_list) {
+ foreach ($feature_list as $feature_id => $feature) {
+ $form['features'][$plugin][$feature_id] = array(
+ '#type' => 'checkbox',
+ '#title' => $feature,
+ );
+ if (!empty($form_state['page']->forms) && in_array($feature_id, $form_state['page']->forms)) {
+ $form['features'][$plugin][$feature_id]['#default_value'] = TRUE;
+ }
+
+ if ($plugin != 'default') {
+ $form['features'][$plugin][$feature_id] += array(
+ '#dependency' => array('edit-handler' => array($plugin)),
+ );
+ }
+ }
+ }
+
+ return $form;
+}
+
+/*
+ * Check if handler's machine-name is unique
+ */
+function page_manager_handler_check_machine_name($name, $element, $form_state) {
+ $name = $form_state['task_name'] . '__' . $name;
+
+ return count(ctools_export_load_object('page_manager_handlers', 'names', array($name)));
+}
+
+/**
+ * Rearrange the order of variants.
+ */
+function page_manager_handler_import($form, &$form_state) {
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Variant name'),
+ '#description' => t('Enter the name of the new variant.'),
+ );
+
+ if (user_access('use ctools import')) {
+ $form['object'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Paste variant code here'),
+ '#rows' => 15,
+ );
+ }
+ // Users ordinarily can't get here without the 'import' permission, due to
+ // security implications. In case they somehow do, though, disable the form
+ // widget for extra safety.
+ else {
+ $form['shoveoff'] = array(
+ '#markup' => '<div>' . t('You do not have sufficient permissions to perform this action.') . '</div>',
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Make sure that an import actually provides a handler.
+ */
+function page_manager_handler_import_validate($form, &$form_state) {
+ if (!user_access('use ctools import')) {
+ form_error($form['shoveoff'], t('You account permissions do not permit you to import.'));
+ return;
+ }
+ ob_start();
+ eval($form_state['values']['object']);
+ ob_end_clean();
+
+ if (empty($handler)) {
+ $errors = ob_get_contents();
+ if (empty($errors)) {
+ $errors = t('No variant found.');
+ }
+
+ form_error($form['object'], t('Unable to get a variant from the import. Errors reported: @errors', array('@errors' => $errors)));
+ }
+
+ $form_state['handler'] = $handler;
+}
+
+/**
+ * Clone an existing task handler into a new handler.
+ */
+function page_manager_handler_import_submit(&$form, &$form_state) {
+ $handler = $form_state['handler'];
+
+ page_manager_handler_add_to_page($form_state['page'], $handler, $form_state['values']['title']);
+
+ $plugin = page_manager_get_task_handler($handler->handler);
+ // It has no forms at all. Add the variant and go to its first operation.
+ $keys = array_keys($plugin['operations']);
+ $form_state['new trail'] = array('handlers', $handler->name, reset($keys));
+}
+
+/**
+ * Rearrange the order of variants.
+ */
+function page_manager_handler_rearrange($form, &$form_state) {
+ $page = $form_state['page'];
+
+ $form['handlers'] = array('#tree' => TRUE);
+
+ foreach ($page->handler_info as $id => $info) {
+ if ($info['changed'] & PAGE_MANAGER_CHANGED_DELETED) {
+ continue;
+ }
+ $handler = $page->handlers[$id];
+ $plugin = page_manager_get_task_handler($handler->handler);
+
+ $form['handlers'][$id]['title'] = array(
+ '#markup' => page_manager_get_handler_title($plugin, $handler, $page->task, $page->subtask_id),
+ );
+
+ $form['handlers'][$id]['weight'] = array(
+ '#type' => 'weight',
+ '#default_value' => $info['weight'],
+ '#delta' => 30,
+ );
+ }
+
+ return $form;
+}
+
+function page_manager_handler_rearrange_submit(&$form, &$form_state) {
+ $handler_info = &$form_state['page']->handler_info;
+
+ foreach ($form_state['values']['handlers'] as $id => $info) {
+ if ($handler_info[$id]['weight'] = $info['weight']) {
+ $handler_info[$id]['weight'] = $info['weight'];
+ $handler_info[$id]['changed'] |= PAGE_MANAGER_CHANGED_MOVED;
+ }
+ }
+
+ // Sort the new cache.
+ uasort($handler_info, '_page_manager_handler_sort');
+
+}
+
+/**
+ * Used as a callback to uasort to sort the task cache by weight.
+ *
+ * The 'name' field is used as a backup when weights are the same, which
+ * can happen when multiple modules put items out there at the same
+ * weight.
+ */
+function _page_manager_handler_sort($a, $b) {
+ if ($a['weight'] < $b['weight']) {
+ return -1;
+ }
+ elseif ($a['weight'] > $b['weight']) {
+ return 1;
+ }
+ elseif ($a['name'] < $b['name']) {
+ return -1;
+ }
+ elseif ($a['name'] > $b['name']) {
+ return 1;
+ }
+}
+
+/**
+ * Rearrange the order of variants.
+ */
+function page_manager_handler_delete($form, &$form_state) {
+ if ($form_state['handler']->type == t('Overridden')) {
+ $text = t('Reverting the variant will delete the variant that is in the database, reverting it to the original default variant. This deletion will not be made permanent until you click Save.');
+ }
+ else {
+ $text = t('Are you sure you want to delete this variant? This deletion will not be made permanent until you click Save.');
+ }
+ $form['markup'] = array(
+ '#markup' => '<p>' . $text . '</p>',
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler to delete a view.
+ */
+function page_manager_handler_delete_submit(&$form, &$form_state) {
+ $form_state['page']->handler_info[$form_state['handler_id']]['changed'] |= PAGE_MANAGER_CHANGED_DELETED;
+ $form_state['new trail'] = array('summary');
+}
+
+/**
+ * Entry point to export a page.
+ */
+function page_manager_handler_export($form, &$form_state) {
+ $export = page_manager_export_task_handler($form_state['handler']);
+
+ $lines = substr_count($export, "\n");
+ $form['code'] = array(
+ '#type' => 'textarea',
+ '#default_value' => $export,
+ '#rows' => $lines,
+ );
+
+ unset($form['buttons']);
+ return $form;
+}
+
+/**
+ * Rearrange the order of variants.
+ */
+function page_manager_handler_clone($form, &$form_state) {
+ // This provides its own button because it does something totally different.
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Variant name'),
+ '#description' => t('Enter the name of the new variant.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Clone an existing task handler into a new handler.
+ */
+function page_manager_handler_clone_submit(&$form, &$form_state) {
+ $export = page_manager_export_task_handler($form_state['handler']);
+ ob_start();
+ eval($export);
+ ob_end_clean();
+
+ page_manager_handler_add_to_page($form_state['page'], $handler, $form_state['values']['title']);
+
+ $plugin = page_manager_get_task_handler($handler->handler);
+ // It has no forms at all. Add the variant and go to its first operation.
+ $keys = array_keys($plugin['operations']);
+ $form_state['new trail'] = array('handlers', $handler->name, reset($keys));
+}
+
+/**
+ * Form to enable a handler.
+ */
+function page_manager_handler_enable($form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => t('This variant is currently disabled. Enabling it will make it available in your system. This will not take effect until you save this page.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Enable the page after it has been confirmed.
+ */
+function page_manager_handler_enable_submit(&$form, &$form_state) {
+ $form_state['handler']->disabled = FALSE;
+ $form_state['page']->handler_info[$form_state['handler_id']]['disabled'] = FALSE;
+ $form_state['page']->handler_info[$form_state['handler_id']]['changed'] |= PAGE_MANAGER_CHANGED_STATUS;
+ $form_state['new trail'] = array('handlers', $form_state['handler_id'], 'actions', 'disable');
+}
+
+/**
+ * Form to disable a page.
+ */
+function page_manager_handler_disable($form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => t('This variant is currently enabled. Disabling it will make it unavailable in your system, and it will not be used. This will not take effect until you save this page.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Form to disable a page.
+ */
+function page_manager_handler_summary($form, &$form_state) {
+ $handler = $form_state['handler'];
+ $page = $form_state['page'];
+ $plugin = page_manager_get_task_handler($handler->handler);
+
+ $form['markup'] = array(
+ '#markup' => page_manager_get_handler_summary($plugin, $handler, $page, FALSE),
+ );
+
+ return $form;
+}
+
+/**
+ * Disable the page after it has been confirmed.
+ */
+function page_manager_handler_disable_submit(&$form, &$form_state) {
+ $form_state['handler']->disabled = TRUE;
+ $form_state['page']->handler_info[$form_state['handler_id']]['disabled'] = TRUE;
+ $form_state['page']->handler_info[$form_state['handler_id']]['changed'] |= PAGE_MANAGER_CHANGED_STATUS;
+ $form_state['new trail'] = array('handlers', $form_state['handler_id'], 'actions', 'enable');
+}
+
+/**
+ * Break the lock on a page so that it can be edited.
+ */
+function page_manager_break_lock($form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => t('Breaking the lock on this page will <strong>discard</strong> any pending changes made by the locking user. Are you REALLY sure you want to do this?')
+ );
+
+ return $form;
+}
+
+/**
+ * Submit to break the lock on a page.
+ */
+function page_manager_break_lock_submit(&$form, &$form_state) {
+ $page = &$form_state['page'];
+ $form_state['page']->locked = FALSE;
+ ctools_object_cache_clear_all('page_manager_page', $page->task_name);
+ $form_state['do not cache'] = TRUE;
+ drupal_set_message(t('The lock has been cleared and all changes discarded. You may now make changes to this page.'));
+
+ $form_state['new trail'] = array('summary');
+}
+
+/**
+ * Form to enable a page.
+ */
+function page_manager_enable_form($form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => t('Enabling this page will immediately make it available in your system (there is no need to wait for a save.)'),
+ );
+
+ return $form;
+}
+
+/**
+ * Enable the page after it has been confirmed.
+ */
+function page_manager_enable_form_submit(&$form, &$form_state) {
+ $page = &$form_state['page'];
+ if ($function = ctools_plugin_get_function($page->subtask, 'enable callback')) {
+ $result = $function($page, FALSE);
+ menu_rebuild();
+ }
+ $form_state['new trail'] = array('actions', 'disable');
+
+ // We don't want to cause this to cache if it wasn't already. If it was
+ // cached, however, we want to update the enabled state.
+ if (empty($form_state['page']->changed)) {
+ $form_state['do not cache'] = TRUE;
+ }
+}
+
+/**
+ * Form to disable a page.
+ */
+function page_manager_disable_form($form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => t('Disabling this page will immediately make it unavailable in your system (there is no need to wait for a save.)'),
+ );
+
+ return $form;
+}
+
+/**
+ * Disable the page after it has been confirmed.
+ */
+function page_manager_disable_form_submit(&$form, &$form_state) {
+ $page = &$form_state['page'];
+ if ($function = ctools_plugin_get_function($page->subtask, 'enable callback')) {
+ $result = $function($page, TRUE);
+ menu_rebuild();
+ $form_state['new trail'] = array('actions', 'enable');
+
+ // We don't want to cause this to cache if it wasn't already. If it was
+ // cached, however, we want to update the enabled state.
+ if (empty($form_state['page']->changed)) {
+ $form_state['do not cache'] = TRUE;
+ }
+ }
+}
+
+/**
+ * Print the summary information for a page.
+ */
+function page_manager_page_summary($form, &$form_state) {
+ $page = $form_state['page'];
+
+ $output = '';
+
+/*
+ if (isset($form_state['subtask']['admin title'])) {
+ $form_state['title'] = $form_state['subtask']['admin title'];
+ }
+*/
+
+ if (isset($form_state['subtask']['admin description'])) {
+ $output .= '<div class="description">' . $form_state['subtask']['admin description'] . '</div>';
+ }
+
+ $output .= page_manager_get_page_summary($page->task, $page->subtask);
+
+ if (!empty($page->handlers)) {
+ foreach ($page->handler_info as $id => $info) {
+ if ($info['changed'] & PAGE_MANAGER_CHANGED_DELETED) {
+ continue;
+ }
+
+ $handler = $page->handlers[$id];
+ $plugin = page_manager_get_task_handler($handler->handler);
+
+ $output .= '<div class="handler-summary">';
+ $output .= page_manager_get_handler_summary($plugin, $handler, $page);
+ $output .= '</div>';
+
+ }
+ }
+ else {
+ $output .= '<p>' . t('This page has no variants and thus no output of its own.') . '</p>';
+ }
+
+ $links = array(
+ array(
+ 'title' => ' &raquo; ' . t('Add a new variant'),
+ 'href' => page_manager_edit_url($page->task_name, array('actions', 'add')),
+ 'html' => TRUE,
+ ),
+ );
+
+ $output .= '<div class="links">' . theme('links', array('links' => $links)) . '</div>';
+ $form['markup'] = array(
+ '#markup' => $output,
+ );
+
+ return $form;
+}
+
+/**
+ * Menu callback to enable or disable a page
+ */
+function page_manager_enable_page($disable, $js, $page) {
+ if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $page->task_name)) {
+ return MENU_ACCESS_DENIED;
+ }
+ if ($page->locked) {
+ if ($disable) {
+ drupal_set_message(t('Unable to disable due to lock.'));
+ }
+ else {
+ drupal_set_message(t('Unable to enable due to lock.'));
+ }
+ }
+ else {
+ if ($function = ctools_plugin_get_function($page->subtask, 'enable callback')) {
+ $result = $function($page, $disable);
+ menu_rebuild();
+
+ // We want to re-cache this if it's changed so that status is properly
+ // updated on the changed form.
+ if (!empty($page->changed)) {
+ page_manager_set_page_cache($page);
+ }
+ }
+ }
+
+ // For now $js is not actually in use on this.
+ drupal_goto('admin/structure/pages');
+}
diff --git a/sites/all/modules/ctools/page_manager/page_manager.api.php b/sites/all/modules/ctools/page_manager/page_manager.api.php
new file mode 100644
index 000000000..03e2e75f6
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/page_manager.api.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Describe hooks provided by the Page Manager module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * @todo.
+ *
+ * @param array $result
+ * @todo.
+ * @param object $page
+ * @todo.
+ */
+function hook_page_manager_operations_alter(&$result, &$page) {
+ // @todo.
+}
+
+/**
+ * @todo.
+ *
+ * @param array $operations
+ * @todo.
+ * @param object $handler
+ * @todo.
+ */
+function hook_page_manager_variant_operations_alter(&$operations, &$handler) {
+ // @todo.
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/sites/all/modules/ctools/page_manager/page_manager.info b/sites/all/modules/ctools/page_manager/page_manager.info
new file mode 100644
index 000000000..c7f4df3af
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/page_manager.info
@@ -0,0 +1,13 @@
+name = Page manager
+description = Provides a UI and API to manage pages within the site.
+core = 7.x
+dependencies[] = ctools
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/page_manager/page_manager.install b/sites/all/modules/ctools/page_manager/page_manager.install
new file mode 100644
index 000000000..b170ce721
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/page_manager.install
@@ -0,0 +1,204 @@
+<?php
+
+/**
+ * @file
+ * Installation routines for page manager module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function page_manager_schema() {
+ // This should always point to our 'current' schema. This makes it relatively easy
+ // to keep a record of schema as we make changes to it.
+ return page_manager_schema_1();
+}
+
+/**
+ * Schema version 1 for Panels in D6.
+ */
+function page_manager_schema_1() {
+ $schema['page_manager_handlers'] = array(
+ 'export' => array(
+ 'identifier' => 'handler',
+ 'bulk export' => TRUE,
+ 'export callback' => 'page_manager_export_task_handler',
+ 'load callback' => 'page_manager_export_task_handler_load',
+ 'delete callback' => 'page_manager_delete_task_handler',
+ 'primary key' => 'did',
+ 'api' => array(
+ 'owner' => 'page_manager',
+ 'api' => 'pages_default',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+ 'fields' => array(
+ 'did' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.',
+ 'no export' => TRUE,
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Unique ID for this task handler. Used to identify it programmatically.',
+ ),
+ 'task' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'description' => 'ID of the task this handler is for.',
+ ),
+ 'subtask' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'description' => 'ID of the subtask this handler is for.',
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'handler' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'description' => 'ID of the task handler being used.',
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'description' => 'The order in which this handler appears. Lower numbers go first.',
+ ),
+ 'conf' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Serialized configuration of the handler, if needed.',
+ 'not null' => TRUE,
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ ),
+ 'primary key' => array('did'),
+ 'unique keys' => array(
+ 'name' => array('name'),
+ ),
+ 'indexes' => array('fulltask' => array('task', 'subtask', 'weight')),
+ );
+
+ $schema['page_manager_weights'] = array(
+ 'description' => 'Contains override weights for page_manager handlers that are in code.',
+ 'fields' => array(
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Unique ID for this task handler. Used to identify it programmatically.',
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'description' => 'The order in which this handler appears. Lower numbers go first.',
+ ),
+ ),
+ 'primary key' => array('name'),
+ 'indexes' => array(
+ 'weights' => array('name', 'weight'),
+ ),
+ );
+
+ $schema['page_manager_pages'] = array(
+ 'description' => 'Contains page subtasks for implementing pages with arbitrary tasks.',
+ 'export' => array(
+ 'identifier' => 'page',
+ 'bulk export' => TRUE,
+ 'export callback' => 'page_manager_page_export',
+ 'api' => array(
+ 'owner' => 'page_manager',
+ 'api' => 'pages_default',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+ 'fields' => array(
+ 'pid' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.',
+ 'no export' => TRUE,
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Unique ID for this subtask. Used to identify it programmatically.',
+ ),
+ 'task' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'description' => 'What type of page this is, so that we can use the same mechanism for creating tighter UIs for targeted pages.',
+ 'default' => 'page',
+ ),
+ 'admin_title' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Human readable title for this page subtask.',
+ ),
+ 'admin_description' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Administrative description of this item.',
+ 'object default' => '',
+ ),
+ 'path' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'The menu path that will invoke this task.',
+ ),
+ 'access' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Access configuration for this path.',
+ 'not null' => TRUE,
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ 'menu' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Serialized configuration of Drupal menu visibility settings for this item.',
+ 'not null' => TRUE,
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ 'arguments' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Configuration of arguments for this menu item.',
+ 'not null' => TRUE,
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ 'conf' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Serialized configuration of the page, if needed.',
+ 'not null' => TRUE,
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ ),
+ ),
+ 'primary key' => array('pid'),
+ 'unique keys' => array(
+ 'name' => array('name'),
+ ),
+ 'indexes' => array('task' => array('task')),
+ );
+
+ return $schema;
+}
+
+/**
+ * Implements hook_install().
+ */
+function page_manager_install() {
+ db_update('system')
+ ->fields(array('weight' => 99))
+ ->condition('name', 'page_manager')
+ ->execute();
+}
diff --git a/sites/all/modules/ctools/page_manager/page_manager.module b/sites/all/modules/ctools/page_manager/page_manager.module
new file mode 100644
index 000000000..f3cb743e3
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/page_manager.module
@@ -0,0 +1,1349 @@
+<?php
+
+/**
+ * @file
+ * The page manager module provides a UI and API to manage pages.
+ *
+ * It defines pages, both for system pages, overrides of system pages, and
+ * custom pages using Drupal's normal menu system. It allows complex
+ * manipulations of these pages, their content, and their hierarchy within
+ * the site. These pages can be exported to code for superior revision
+ * control.
+ */
+
+/**
+ * Bit flag on the 'changed' value to tell us if an item was moved.
+ */
+define('PAGE_MANAGER_CHANGED_MOVED', 0x01);
+
+/**
+ * Bit flag on the 'changed' value to tell us if an item edited or added.
+ */
+define('PAGE_MANAGER_CHANGED_CACHED', 0x02);
+
+/**
+ * Bit flag on the 'changed' value to tell us if an item deleted.
+ */
+define('PAGE_MANAGER_CHANGED_DELETED', 0x04);
+
+/**
+ * Bit flag on the 'changed' value to tell us if an item has had its disabled status changed.
+ */
+define('PAGE_MANAGER_CHANGED_STATUS', 0x08);
+
+// --------------------------------------------------------------------------
+// Drupal hooks
+
+/**
+ * Implements hook_permission().
+ */
+function page_manager_permission() {
+ return array(
+ 'use page manager' => array(
+ 'title' => t('Use Page Manager'),
+ 'description' => t("Allows users to use most of Page Manager's features, though restricts some of the most powerful, potentially site-damaging features. Note that even the reduced featureset still allows for enormous control over your website."),
+ 'restrict access' => TRUE,
+ ),
+ 'administer page manager' => array(
+ 'title' => t('Administer Page Manager'),
+ 'description' => t('Allows complete control over Page Manager, i.e., complete control over your site. Grant with extreme caution.'),
+ 'restrict access' => TRUE,
+ ),
+ );
+
+}
+
+/**
+ * Implements hook_ctools_plugin_directory() to let the system know
+ * where our task and task_handler plugins are.
+ */
+function page_manager_ctools_plugin_directory($owner, $plugin_type) {
+ if ($owner == 'page_manager') {
+ return 'plugins/' . $plugin_type;
+ }
+ if ($owner == 'ctools' && $plugin_type == 'cache') {
+ return 'plugins/' . $plugin_type;
+ }
+}
+
+/**
+ * Implements hook_ctools_plugin_type() to inform the plugin system that Page
+ * Manager owns task, task_handler, and page_wizard plugin types.
+ *
+ * All of these are empty because the defaults all work.
+ */
+function page_manager_ctools_plugin_type() {
+ return array(
+ 'tasks' => array(),
+ 'task_handlers' => array(),
+ 'page_wizards' => array(),
+ );
+}
+
+/**
+ * Delegated implementation of hook_menu().
+ */
+function page_manager_menu() {
+ // For some reason, some things can activate modules without satisfying
+ // dependencies. I don't know how, but this helps prevent things from
+ // whitescreening when this happens.
+ if (!module_exists('ctools')) {
+ return;
+ }
+
+ $items = array();
+ $base = array(
+ 'access arguments' => array('use page manager'),
+ 'file' => 'page_manager.admin.inc',
+ 'theme callback' => 'ajax_base_page_theme',
+ );
+
+ $items['admin/structure/pages'] = array(
+ 'title' => 'Pages',
+ 'description' => 'Add, edit and remove overridden system pages and user defined pages from the system.',
+ 'page callback' => 'page_manager_list_page',
+ ) + $base;
+
+ $items['admin/structure/pages/list'] = array(
+ 'title' => 'List',
+ 'page callback' => 'page_manager_list_page',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ ) + $base;
+
+ $items['admin/structure/pages/edit/%page_manager_cache'] = array(
+ 'title' => 'Edit',
+ 'page callback' => 'page_manager_edit_page',
+ 'page arguments' => array(4),
+ 'type' => MENU_NORMAL_ITEM,
+ ) + $base;
+
+ $items['admin/structure/pages/%ctools_js/operation/%page_manager_cache'] = array(
+ 'page callback' => 'page_manager_edit_page_operation',
+ 'page arguments' => array(3, 5),
+ 'type' => MENU_NORMAL_ITEM,
+ ) + $base;
+
+ $items['admin/structure/pages/%ctools_js/enable/%page_manager_cache'] = array(
+ 'page callback' => 'page_manager_enable_page',
+ 'page arguments' => array(FALSE, 3, 5),
+ 'type' => MENU_CALLBACK,
+ ) + $base;
+
+ $items['admin/structure/pages/%ctools_js/disable/%page_manager_cache'] = array(
+ 'page callback' => 'page_manager_enable_page',
+ 'page arguments' => array(TRUE, 3, 5),
+ 'type' => MENU_CALLBACK,
+ ) + $base;
+
+ $tasks = page_manager_get_tasks();
+
+ // Provide menu items for each task.
+ foreach ($tasks as $task_id => $task) {
+ // Allow the task to add its own menu items.
+ if ($function = ctools_plugin_get_function($task, 'hook menu')) {
+ $function($items, $task);
+ }
+
+ // And for those that provide subtasks, provide menu items for them, as well.
+ foreach (page_manager_get_task_subtasks($task) as $subtask_id => $subtask) {
+ // Allow the task to add its own menu items.
+ if ($function = ctools_plugin_get_function($task, 'hook menu')) {
+ $function($items, $subtask);
+ }
+ }
+ }
+
+ return $items;
+}
+
+function page_manager_admin_paths() {
+ /* @todo FIX ME this is a major resource suck. */
+ return;
+
+ $items = array();
+ ctools_include('page', 'page_manager', 'plugins/tasks');
+ $pages = page_manager_page_load_all();
+ foreach ($pages as $page) {
+ // Make sure the page we're on is set to be an administrative path and that
+ // it is not set to be a frontpage path.
+ if ((isset($page->conf['admin_paths']) && $page->conf['admin_paths']) && (!isset($page->make_frontpage) || !$page->make_frontpage)) {
+ $path_parts = explode('/', $page->path);
+ foreach ($path_parts as $key => $part) {
+ if (strpos($part, '%') !== FALSE || strpos($part, '!') !== FALSE) {
+ $path_parts[$key] = '*';
+ }
+ }
+ $path = implode('/', $path_parts);
+ if ($page->menu['type'] == 'default tab') {
+ array_pop($path_parts);
+ $parent_path = implode('/', $path_parts);
+ $items[$parent_path] = TRUE;
+ }
+ $items[$path] = TRUE;
+ }
+ }
+ return $items;
+}
+
+/**
+ * Implements hook_menu_alter.
+ *
+ * Get a list of all tasks and delegate to them.
+ */
+function page_manager_menu_alter(&$items) {
+ // For some reason, some things can activate modules without satisfying
+ // dependencies. I don't know how, but this helps prevent things from
+ // whitescreening when this happens.
+ if (!module_exists('ctools')) {
+ return;
+ }
+
+ $tasks = page_manager_get_tasks();
+
+ foreach ($tasks as $task) {
+ if ($function = ctools_plugin_get_function($task, 'hook menu alter')) {
+ $function($items, $task);
+ }
+ // let the subtasks alter the menu items too.
+ foreach (page_manager_get_task_subtasks($task) as $subtask_id => $subtask) {
+ if ($function = ctools_plugin_get_function($subtask, 'hook menu alter')) {
+ $function($items, $subtask);
+ }
+ }
+ }
+
+ // Override the core node revisions display to use the configured Page
+ // display handler.
+ if (!variable_get('page_manager_node_view_disabled', TRUE) && isset($items['node/%node/revisions/%/view'])) {
+ // Abstract the basic settings.
+ $item = array(
+ // Handle the page arguments.
+ 'load arguments' => array(3),
+ 'page arguments' => array(1, TRUE),
+
+ // Replace the normal node_show call with Page Manager's node view.
+ 'page callback' => 'page_manager_node_view_page',
+
+ // Provide the correct path to the Page Manager file.
+ 'file' => 'node_view.inc',
+ 'file path' => drupal_get_path('module', 'page_manager') . '/plugins/tasks',
+ );
+ // Re-build the menu item using the normal values from node.module.
+ $items['node/%node/revisions/%/view'] = array(
+ 'title' => 'Revisions',
+ 'access callback' => '_node_revision_access',
+ 'access arguments' => array(1),
+ ) + $item;
+ }
+
+ return $items;
+}
+
+/*
+ * Implements hook_theme()
+ */
+function page_manager_theme() {
+ // For some reason, some things can activate modules without satisfying
+ // dependencies. I don't know how, but this helps prevent things from
+ // whitescreening when this happens.
+ if (!module_exists('ctools')) {
+ return;
+ }
+
+ $base = array(
+ 'path' => drupal_get_path('module', 'page_manager') . '/theme',
+ 'file' => 'page_manager.theme.inc',
+ );
+
+ $items = array(
+ 'page_manager_handler_rearrange' => array(
+ 'render element' => 'form',
+ ) + $base,
+ 'page_manager_edit_page' => array(
+ 'template' => 'page-manager-edit-page',
+ 'variables' => array('page' => NULL, 'save' => NULL, 'operations' => array(), 'content' => array()),
+ ) + $base,
+ 'page_manager_lock' => array(
+ 'variables' => array('page' => array()),
+ ) + $base,
+ 'page_manager_changed' => array(
+ 'variables' => array('text' => NULL, 'description' => NULL),
+ ) + $base,
+ );
+
+ // Allow task plugins to have theme registrations by passing through:
+ $tasks = page_manager_get_tasks();
+
+ // Provide menu items for each task.
+ foreach ($tasks as $task_id => $task) {
+ if ($function = ctools_plugin_get_function($task, 'hook theme')) {
+ $function($items, $task);
+ }
+ }
+
+ return $items;
+}
+
+// --------------------------------------------------------------------------
+// Page caching
+//
+// The page cache is used to store a page temporarily, using the ctools object
+// cache. When loading from the page cache, it will either load the cached
+// version, or if there is not one, load the real thing and create a cache
+// object which can then be easily stored.
+
+/**
+ * Get the cached changes to a given task handler.
+ */
+function page_manager_get_page_cache($task_name) {
+ $caches = drupal_static(__FUNCTION__, array());
+ if (!isset($caches[$task_name])) {
+ ctools_include('object-cache');
+ $cache = ctools_object_cache_get('page_manager_page', $task_name);
+ if (!$cache) {
+ $cache = new stdClass();
+ $cache->task_name = $task_name;
+ list($cache->task_id, $cache->subtask_id) = page_manager_get_task_id($cache->task_name);
+
+ $cache->task = page_manager_get_task($cache->task_id);
+ if (empty($cache->task)) {
+ return FALSE;
+ }
+
+ if ($cache->subtask_id) {
+ $cache->subtask = page_manager_get_task_subtask($cache->task, $cache->subtask_id);
+ if (empty($cache->subtask)) {
+ return FALSE;
+ }
+ }
+ else {
+ $cache->subtask = $cache->task;
+ $cache->subtask['name'] = '';
+ }
+
+ $cache->handlers = page_manager_load_sorted_handlers($cache->task, $cache->subtask_id);
+ $cache->handler_info = array();
+ foreach ($cache->handlers as $id => $handler) {
+ $cache->handler_info[$id] = array(
+ 'weight' => $handler->weight,
+ 'changed' => FALSE,
+ 'name' => $id,
+ );
+ }
+ }
+ else {
+ // ensure the task is loaded.
+ page_manager_get_task($cache->task_id);
+ }
+
+ if ($task_name != '::new') {
+ $cache->locked = ctools_object_cache_test('page_manager_page', $task_name);
+ }
+ else {
+ $cache->locked = FALSE;
+ }
+
+ $caches[$task_name] = $cache;
+ }
+
+ return $caches[$task_name];
+}
+
+/**
+ * Store changes to a task handler in the object cache.
+ */
+function page_manager_set_page_cache($page) {
+ if (!empty($page->locked)) {
+ return;
+ }
+
+ if (empty($page->task_name)) {
+ return;
+ }
+
+ ctools_include('object-cache');
+ $page->changed = TRUE;
+ $cache = ctools_object_cache_set('page_manager_page', $page->task_name, $page);
+}
+
+/**
+ * Remove an item from the object cache.
+ */
+function page_manager_clear_page_cache($name) {
+ ctools_include('object-cache');
+ ctools_object_cache_clear('page_manager_page', $name);
+}
+
+/**
+ * Write all changes from the page cache and clear it out.
+ */
+function page_manager_save_page_cache($cache) {
+ // Save the subtask:
+ if ($function = ctools_plugin_get_function($cache->task, 'save subtask callback')) {
+ $function($cache->subtask, $cache);
+ }
+
+ // Iterate through handlers and save/delete/update as necessary.
+ // Go through each of the task handlers, check to see if it needs updating,
+ // and update it if so.
+ foreach ($cache->handler_info as $id => $info) {
+ $handler = &$cache->handlers[$id];
+ // If it has been marked for deletion, delete it.
+
+ if ($info['changed'] & PAGE_MANAGER_CHANGED_DELETED) {
+ page_manager_delete_task_handler($handler);
+ }
+ // If it has been somehow edited (or added), write the cached version
+ elseif ($info['changed'] & PAGE_MANAGER_CHANGED_CACHED) {
+ // Make sure we get updated weight from the form for this.
+ $handler->weight = $info['weight'];
+ page_manager_save_task_handler($handler);
+ }
+ // Otherwise, check to see if it has moved and, if so, update the weight.
+ elseif ($info['weight'] != $handler->weight) {
+ // Theoretically we could only do this for in code objects, but since our
+ // load mechanism checks for all, this is less database work.
+ page_manager_update_task_handler_weight($handler, $info['weight']);
+ }
+
+ // Set enable/disabled status.
+ if ($info['changed'] & PAGE_MANAGER_CHANGED_STATUS) {
+ ctools_include('export');
+ ctools_export_set_object_status($cache->handlers[$id], $info['disabled']);
+ }
+ }
+
+ page_manager_clear_page_cache($cache->task_name);
+
+ if (!empty($cache->path_changed) || !empty($cache->new)) {
+ // Force a menu rebuild to make sure the menu entries are set.
+ menu_rebuild();
+ }
+ cache_clear_all();
+}
+
+/**
+ * Menu callback to load a page manager cache object for menu callbacks.
+ */
+function page_manager_cache_load($task_name) {
+ // load context plugin as there may be contexts cached here.
+ ctools_include('context');
+ return page_manager_get_page_cache($task_name);
+}
+
+/**
+ * Generate a unique name for a task handler.
+ *
+ * Task handlers need to be named but they aren't allowed to set their own
+ * names. Instead, they are named based upon their parent task and type.
+ */
+function page_manager_handler_get_name($task_name, $handlers, $handler) {
+ $base = str_replace('-', '_', $task_name);
+ $name = '';
+
+ // Optional machine name.
+ if (!empty($handler->conf['name'])) {
+ $name = $base . '__' . $handler->conf['name'];
+ if (count(ctools_export_load_object('page_manager_handlers', 'names', array($name)))) {
+ $name = '';
+ }
+ }
+
+ // If no machine name was provided or the name is in use, generate a unique name.
+ if (empty($name)) {
+ $base .= '__' . $handler->handler;
+
+ // Use the ctools uuid generator to generate a unique id.
+ $name = $base . '_' . ctools_uuid_generate();
+ }
+
+ return $name;
+}
+
+/**
+ * Import a handler into a page.
+ *
+ * This is used by both import and clone, since clone just exports the
+ * handler and immediately imports it.
+ */
+function page_manager_handler_add_to_page(&$page, &$handler, $title = NULL) {
+ $last = end($page->handler_info);
+ $handler->weight = $last ? $last['weight'] + 1 : 0;
+ $handler->task = $page->task_id;
+ $handler->subtask = $page->subtask_id;
+ $handler->export_type = EXPORT_IN_DATABASE;
+ $handler->type = t('Normal');
+
+ if ($title) {
+ $handler->conf['title'] = $title;
+ $handler->conf['name'] = trim(preg_replace('/[^a-z0-9_]+/', '-', strtolower($title)), '-');
+ }
+ else {
+ $handler->conf['name'] = '';
+ }
+
+ $name = page_manager_handler_get_name($page->task_name, $page->handlers, $handler);
+
+ $handler->name = $name;
+
+ $page->handlers[$name] = $handler;
+ $page->handler_info[$name] = array(
+ 'weight' => $handler->weight,
+ 'name' => $handler->name,
+ 'changed' => PAGE_MANAGER_CHANGED_CACHED,
+ );
+}
+
+// --------------------------------------------------------------------------
+// Database routines
+//
+// This includes fetching plugins and plugin info as well as specialized
+// fetch methods to get groups of task handlers per task.
+
+/**
+ * Load a single task handler by name.
+ *
+ * Handlers can come from multiple sources; either the database or by normal
+ * export method, which is handled by the ctools library, but handlers can
+ * also be bundled with task/subtask. We have to check there and perform
+ * overrides as appropriate.
+ *
+ * Handlers bundled with the task are of a higher priority than default
+ * handlers provided by normal code, and are of a lower priority than
+ * the database, so we have to check the source of handlers when we have
+ * multiple to choose from.
+ */
+function page_manager_load_task_handler($task, $subtask_id, $name) {
+ ctools_include('export');
+ $result = ctools_export_load_object('page_manager_handlers', 'names', array($name));
+ $handlers = page_manager_get_default_task_handlers($task, $subtask_id);
+ return page_manager_compare_task_handlers($result, $handlers, $name);
+}
+
+/**
+ * Load all task handlers for a given task/subtask.
+ */
+function page_manager_load_task_handlers($task, $subtask_id = NULL, $default_handlers = NULL) {
+ ctools_include('export');
+ $conditions = array(
+ 'task' => $task['name'],
+ );
+
+ if (isset($subtask_id)) {
+ $conditions['subtask'] = $subtask_id;
+ }
+
+ $handlers = ctools_export_load_object('page_manager_handlers', 'conditions', $conditions);
+ $defaults = isset($default_handlers) ? $default_handlers : page_manager_get_default_task_handlers($task, $subtask_id);
+ foreach ($defaults as $name => $default) {
+ $result = page_manager_compare_task_handlers($handlers, $defaults, $name);
+
+ if ($result) {
+ $handlers[$name] = $result;
+ // Ensure task and subtask are correct, because it's easy to change task
+ // names when editing a default and fail to do it on the associated handlers.
+ $result->task = $task['name'];
+ $result->subtask = $subtask_id;
+ }
+ }
+
+ // Override weights from the weight table.
+ if ($handlers) {
+ $names = array();
+ $placeholders = array();
+ foreach ($handlers as $handler) {
+ $names[] = $handler->name;
+ $placeholders[] = "'%s'";
+ }
+
+ $result = db_query('SELECT name, weight FROM {page_manager_weights} WHERE name IN (:names)', array(':names' => $names));
+ foreach ($result as $weight) {
+ $handlers[$weight->name]->weight = $weight->weight;
+ }
+ }
+
+ return $handlers;
+}
+
+/**
+ * Get the default task handlers from a task, if they exist.
+ *
+ * Tasks can contain 'default' task handlers which are provided by the
+ * default task. Because these can come from either the task or the
+ * subtask, the logic is abstracted to reduce code duplication.
+ */
+function page_manager_get_default_task_handlers($task, $subtask_id) {
+ // Load default handlers that are provied by the task/subtask itself.
+ $handlers = array();
+ if ($subtask_id) {
+ $subtask = page_manager_get_task_subtask($task, $subtask_id);
+ if (isset($subtask['default handlers'])) {
+ $handlers = $subtask['default handlers'];
+ }
+ }
+ else if (isset($task['default handlers'])) {
+ $handlers = $task['default handlers'];
+ }
+
+ return $handlers;
+}
+
+/**
+ * Compare a single task handler from two lists and provide the correct one.
+ *
+ * Task handlers can be gotten from multiple sources. As exportable objects,
+ * they can be provided by default hooks and the database. But also, because
+ * they are tightly bound to tasks, they can also be provided by default
+ * tasks. This function reconciles where to pick up a task handler between
+ * the exportables list and the defaults provided by the task itself.
+ *
+ * @param $result
+ * A list of handlers provided by export.inc
+ * @param $handlers
+ * A list of handlers provided by the default task.
+ * @param $name
+ * Which handler to compare.
+ * @return
+ * Which handler to use, if any. May be NULL.
+ */
+function page_manager_compare_task_handlers($result, $handlers, $name) {
+ // Compare our special default handler against the actual result, if
+ // any, and do the right thing.
+ if (!isset($result[$name]) && isset($handlers[$name])) {
+ $handlers[$name]->type = t('Default');
+ $handlers[$name]->export_type = EXPORT_IN_CODE;
+ return $handlers[$name];
+ }
+ else if (isset($result[$name]) && !isset($handlers[$name])) {
+ return $result[$name];
+ }
+ else if (isset($result[$name]) && isset($handlers[$name])) {
+ if ($result[$name]->export_type & EXPORT_IN_DATABASE) {
+ $result[$name]->type = t('Overridden');
+ $result[$name]->export_type = $result[$name]->export_type | EXPORT_IN_CODE;
+ return $result[$name];
+ }
+ else {
+ // In this case, our default is a higher priority than the standard default.
+ $handlers[$name]->type = t('Default');
+ $handlers[$name]->export_type = EXPORT_IN_CODE;
+ return $handlers[$name];
+ }
+ }
+}
+
+/**
+ * Load all task handlers for a given task and subtask and sort them.
+ */
+function page_manager_load_sorted_handlers($task, $subtask_id = NULL, $enabled = FALSE) {
+ $handlers = page_manager_load_task_handlers($task, $subtask_id);
+ if ($enabled) {
+ foreach ($handlers as $id => $handler) {
+ if (!empty($handler->disabled)) {
+ unset($handlers[$id]);
+ }
+ }
+ }
+ uasort($handlers, 'page_manager_sort_task_handlers');
+ return $handlers;
+}
+
+/**
+ * Callback for uasort to sort task handlers.
+ *
+ * Task handlers are sorted by weight then by name.
+ */
+function page_manager_sort_task_handlers($a, $b) {
+ if ($a->weight < $b->weight) {
+ return -1;
+ }
+ elseif ($a->weight > $b->weight) {
+ return 1;
+ }
+ elseif ($a->name < $b->name) {
+ return -1;
+ }
+ elseif ($a->name > $b->name) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Write a task handler to the database.
+ */
+function page_manager_save_task_handler(&$handler) {
+ $update = (isset($handler->did)) ? array('did') : array();
+ // Let the task handler respond to saves:
+ if ($function = ctools_plugin_load_function('page_manager', 'task_handlers', $handler->handler, 'save')) {
+ $function($handler, $update);
+ }
+
+ drupal_write_record('page_manager_handlers', $handler, $update);
+ db_delete('page_manager_weights')
+ ->condition('name', $handler->name)
+ ->execute();
+
+ // If this was previously a default handler, we may have to write task handlers.
+ if (!$update) {
+ // @todo wtf was I going to do here?
+ }
+ return $handler;
+}
+
+/**
+ * Remove a task handler.
+ */
+function page_manager_delete_task_handler($handler) {
+ // Let the task handler respond to saves:
+ if ($function = ctools_plugin_load_function('page_manager', 'task_handlers', $handler->handler, 'delete')) {
+ $function($handler);
+ }
+ db_delete('page_manager_handlers')
+ ->condition('name', $handler->name)
+ ->execute();
+ db_delete('page_manager_weights')
+ ->condition('name', $handler->name)
+ ->execute();
+}
+
+/**
+ * Export a task handler into code suitable for import or use as a default
+ * task handler.
+ */
+function page_manager_export_task_handler($handler, $indent = '') {
+ ctools_include('export');
+ ctools_include('plugins');
+ $handler = clone $handler;
+
+ $append = '';
+ if ($function = ctools_plugin_load_function('page_manager', 'task_handlers', $handler->handler, 'export')) {
+ $append = $function($handler, $indent);
+ }
+
+ $output = ctools_export_object('page_manager_handlers', $handler, $indent);
+ $output .= $append;
+
+ return $output;
+}
+
+/**
+ * Loads page manager handler for export.
+ *
+ * Callback to load page manager handler within ctools_export_crud_load().
+ *
+ * @param string $name
+ * The name of the handler to load.
+ *
+ * @return
+ * Loaded page manager handler object, extended with external properties.
+ */
+function page_manager_export_task_handler_load($name) {
+ $table = 'page_manager_handlers';
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ $result = ctools_export_load_object($table, 'names', array($name));
+ if (isset($result[$name])) {
+ $handler = $result[$name];
+
+ // Weight is stored in additional table so that in-code task handlers
+ // don't need to get written to the database just because they have their
+ // weight changed. Therefore, handler could have no correspondent database
+ // entry. Revert will not be performed for this handler and the weight
+ // will not be reverted. To make possible revert of the weight field
+ // export_type must simulate that the handler is stored in the database.
+ $handler->export_type = EXPORT_IN_DATABASE;
+
+ // Also, page manager handler weight should be overriden with correspondent
+ // weight from page_manager_weights table, if there is one.
+ $result = db_query('SELECT weight FROM {page_manager_weights} WHERE name = (:names)', array(':names' => $handler->name))->fetchField();
+ if (is_numeric($result)) {
+ $handler->weight = $result;
+ }
+ return $handler;
+ }
+}
+
+/**
+ * Create a new task handler object.
+ *
+ * @param $plugin
+ * The plugin this task handler is created from.
+ */
+function page_manager_new_task_handler($plugin) {
+ // Generate a unique name. Unlike most named objects, we don't let people choose
+ // names for task handlers because they mostly don't make sense.
+
+ // Create a new, empty handler object.
+ $handler = new stdClass;
+ $handler->title = $plugin['title'];
+ $handler->task = NULL;
+ $handler->subtask = NULL;
+ $handler->name = NULL;
+ $handler->handler = $plugin['name'];
+ $handler->weight = 0;
+ $handler->conf = array();
+
+ // These are provided by the core export API provided by ctools and we
+ // set defaults here so that we don't cause notices. Perhaps ctools should
+ // provide a way to do this for us so we don't have to muck with it.
+ $handler->export_type = EXPORT_IN_DATABASE;
+ $handler->type = t('Local');
+
+ if (isset($plugin['default conf'])) {
+ if (is_array($plugin['default conf'])) {
+ $handler->conf = $plugin['default conf'];
+ }
+ else if (function_exists($plugin['default conf'])) {
+ $handler->conf = $plugin['default conf']($handler);
+ }
+ }
+
+ return $handler;
+}
+
+/**
+ * Set an overidden weight for a task handler.
+ *
+ * We do this so that in-code task handlers don't need to get written
+ * to the database just because they have their weight changed.
+ */
+function page_manager_update_task_handler_weight($handler, $weight) {
+ db_delete('page_manager_weights')
+ ->condition('name', $handler->name)
+ ->execute();
+ db_insert('page_manager_weights')
+ ->fields(array(
+ 'name' => $handler->name,
+ 'weight' => $weight,
+ ))
+ ->execute();
+}
+
+
+/**
+ * Shortcut function to get task plugins.
+ */
+function page_manager_get_tasks() {
+ ctools_include('plugins');
+ return ctools_get_plugins('page_manager', 'tasks');
+}
+
+/**
+ * Shortcut function to get a task plugin.
+ */
+function page_manager_get_task($id) {
+ ctools_include('plugins');
+ return ctools_get_plugins('page_manager', 'tasks', $id);
+}
+
+/**
+ * Get all tasks for a given type.
+ */
+function page_manager_get_tasks_by_type($type) {
+ ctools_include('plugins');
+ $all_tasks = ctools_get_plugins('page_manager', 'tasks');
+ $tasks = array();
+ foreach ($all_tasks as $id => $task) {
+ if (isset($task['task type']) && $task['task type'] == $type) {
+ $tasks[$id] = $task;
+ }
+ }
+
+ return $tasks;
+}
+
+/**
+ * Fetch all subtasks for a page managertask.
+ *
+ * @param $task
+ * A loaded $task plugin object.
+ */
+function page_manager_get_task_subtasks($task) {
+ if (empty($task['subtasks'])) {
+ return array();
+ }
+
+ if ($function = ctools_plugin_get_function($task, 'subtasks callback')) {
+ $retval = $function($task);
+ if (is_array($retval)) {
+ return $retval;
+ }
+ }
+
+ return array();
+}
+
+/**
+ * Fetch all subtasks for a page managertask.
+ *
+ * @param $task
+ * A loaded $task plugin object.
+ * @param $subtask_id
+ * The subtask ID to load.
+ */
+function page_manager_get_task_subtask($task, $subtask_id) {
+ if (empty($task['subtasks'])) {
+ return;
+ }
+
+ if ($function = ctools_plugin_get_function($task, 'subtask callback')) {
+ return $function($task, $subtask_id);
+ }
+}
+
+/**
+ * Shortcut function to get task handler plugins.
+ */
+function page_manager_get_task_handlers() {
+ ctools_include('plugins');
+ return ctools_get_plugins('page_manager', 'task_handlers');
+}
+
+/**
+ * Shortcut function to get a task handler plugin.
+ */
+function page_manager_get_task_handler($id) {
+ ctools_include('plugins');
+ return ctools_get_plugins('page_manager', 'task_handlers', $id);
+}
+
+/**
+ * Retrieve a list of all applicable task handlers for a given task.
+ *
+ * This looks at the $task['handler type'] and compares that to $task_handler['handler type'].
+ * If the task has no type, the id of the task is used instead.
+ */
+function page_manager_get_task_handler_plugins($task, $all = FALSE) {
+ $type = isset($task['handler type']) ? $task['handler type'] : $task['name'];
+ $name = $task['name'];
+
+ $handlers = array();
+ $task_handlers = page_manager_get_task_handlers();
+ foreach ($task_handlers as $id => $handler) {
+ $task_type = is_array($handler['handler type']) ? $handler['handler type'] : array($handler['handler type']);
+ if (in_array($type, $task_type) || in_array($name, $task_type)) {
+ if ($all || !empty($handler['visible'])) {
+ $handlers[$id] = $handler;
+ }
+ }
+ }
+
+ return $handlers;
+}
+
+/**
+ * Get the title for a given handler.
+ *
+ * If the plugin has no 'admin title' function, the generic title of the
+ * plugin is used instead.
+ */
+function page_manager_get_handler_title($plugin, $handler, $task, $subtask_id) {
+ $function = ctools_plugin_get_function($plugin, 'admin title');
+ if ($function) {
+ return $function($handler, $task, $subtask_id);
+ }
+ else {
+ return $plugin['title'];
+ }
+}
+
+/**
+ * Get the admin summary (additional info) for a given handler.
+ */
+function page_manager_get_handler_summary($plugin, $handler, $page, $title = TRUE) {
+ if ($function = ctools_plugin_get_function($plugin, 'admin summary')) {
+ return $function($handler, $page->task, $page->subtask, $page, $title);
+ }
+}
+
+/**
+ * Get the admin summary (additional info) for a given page.
+ */
+function page_manager_get_page_summary($task, $subtask) {
+ if ($function = ctools_plugin_get_function($subtask, 'admin summary')) {
+ return $function($task, $subtask);
+ }
+}
+
+/**
+ * Split a task name into a task id and subtask id, if applicable.
+ */
+function page_manager_get_task_id($task_name) {
+ if (strpos($task_name, '-') !== FALSE) {
+ return explode('-', $task_name, 2);
+ }
+ else {
+ return array($task_name, NULL);
+ }
+}
+
+/**
+ * Turn a task id + subtask_id into a task name.
+ */
+function page_manager_make_task_name($task_id, $subtask_id) {
+ if ($subtask_id) {
+ return $task_id . '-' . $subtask_id;
+ }
+ else {
+ return $task_id;
+ }
+}
+
+/**
+ * Get the render function for a handler.
+ */
+function page_manager_get_renderer($handler) {
+ return ctools_plugin_load_function('page_manager', 'task_handlers', $handler->handler, 'render');
+}
+
+// --------------------------------------------------------------------------
+// Functions existing on behalf of tasks and task handlers
+
+
+/**
+ * Page manager arg load function because menu system will not load extra
+ * files for these; they must be in a .module.
+ */
+function pm_arg_load($value, $subtask, $argument) {
+ page_manager_get_task('page');
+ return _pm_arg_load($value, $subtask, $argument);
+}
+
+/**
+ * Special arg_load function to use %menu_tail like functionality to
+ * get everything after the arg together as a single value.
+ */
+function pm_arg_tail_load($value, $subtask, $argument, $map) {
+ $value = implode('/', array_slice($map, $argument));
+ page_manager_get_task('page');
+ return _pm_arg_load($value, $subtask, $argument);
+}
+
+/**
+ * Special menu _load() function for the user:uid argument.
+ *
+ * This is just the normal page manager argument. It only exists so that
+ * the to_arg can exist.
+ */
+function pm_uid_arg_load($value, $subtask, $argument) {
+ page_manager_get_task('page');
+ return _pm_arg_load($value, $subtask, $argument);
+}
+
+/**
+ * to_arg function for the user:uid argument to provide the arg for the
+ * current global user.
+ */
+function pm_uid_arg_to_arg($arg) {
+ return user_uid_optional_to_arg($arg);
+}
+
+/**
+ * Callback for access control ajax form on behalf of page.inc task.
+ *
+ * Returns the cached access config and contexts used.
+ */
+function page_manager_page_ctools_access_get($argument) {
+ $page = page_manager_get_page_cache($argument);
+
+ $contexts = array();
+
+ // Load contexts based on argument data:
+ if ($arguments = _page_manager_page_get_arguments($page->subtask['subtask'])) {
+ $contexts = ctools_context_get_placeholders_from_argument($arguments);
+ }
+
+ return array($page->subtask['subtask']->access, $contexts);
+}
+
+/**
+ * Callback for access control ajax form on behalf of page.inc task.
+ *
+ * Writes the changed access to the cache.
+ */
+function page_manager_page_ctools_access_set($argument, $access) {
+ $page = page_manager_get_page_cache($argument);
+ $page->subtask['subtask']->access = $access;
+ page_manager_set_page_cache($page);
+}
+
+/**
+ * Callback for access control ajax form on behalf of context task handler.
+ *
+ * Returns the cached access config and contexts used.
+ */
+function page_manager_task_handler_ctools_access_get($argument) {
+ list($task_name, $name) = explode('*', $argument);
+ $page = page_manager_get_page_cache($task_name);
+ if (empty($name)) {
+ $handler = &$page->new_handler;
+ }
+ else {
+ $handler = &$page->handlers[$name];
+ }
+
+ if (!isset($handler->conf['access'])) {
+ $handler->conf['access'] = array();
+ }
+
+ ctools_include('context-task-handler');
+
+ $contexts = ctools_context_handler_get_all_contexts($page->task, $page->subtask, $handler);
+
+ return array($handler->conf['access'], $contexts);
+}
+
+/**
+ * Callback for access control ajax form on behalf of context task handler.
+ *
+ * Writes the changed access to the cache.
+ */
+function page_manager_task_handler_ctools_access_set($argument, $access) {
+ list($task_name, $name) = explode('*', $argument);
+ $page = page_manager_get_page_cache($task_name);
+ if (empty($name)) {
+ $handler = &$page->new_handler;
+ }
+ else {
+ $handler = &$page->handlers[$name];
+ }
+
+ $handler->conf['access'] = $access;
+ page_manager_set_page_cache($page);
+}
+
+/**
+ * Form a URL to edit a given page given the trail.
+ */
+function page_manager_edit_url($task_name, $trail = array()) {
+ if (!is_array($trail)) {
+ $trail = array($trail);
+ }
+
+ if (empty($trail) || $trail == array('summary')) {
+ return "admin/structure/pages/edit/$task_name";
+ }
+
+ return 'admin/structure/pages/nojs/operation/' . $task_name . '/' . implode('/', $trail);
+}
+
+/**
+ * Watch menu links during the menu rebuild, and re-parent things if we need to.
+ */
+function page_manager_menu_link_alter(&$item) {
+ return;
+/** -- disabled, concept code --
+ static $mlids = array();
+ // Keep an array of mlids as links are saved that we can use later.
+ if (isset($item['mlid'])) {
+ $mlids[$item['path']] = $item['mlid'];
+ }
+
+ if (isset($item['parent_path'])) {
+ if (isset($mlids[$item['parent_path']])) {
+ $item['plid'] = $mlids[$item['parent_path']];
+ }
+ else {
+ // Since we didn't already see an mlid, let's check the database for one.
+ $mlid = db_query('SELECT mlid FROM {menu_links} WHERE router_path = :path', array('path' => $item['parent_path']))->fetchField();
+ if ($mlid) {
+ $item['plid'] = $mlid;
+ }
+ }
+ }
+ */
+}
+
+/**
+ * Callback to list handlers available for export.
+ */
+function page_manager_page_manager_handlers_list() {
+ $list = $types = array();
+ $tasks = page_manager_get_tasks();
+ foreach ($tasks as $type => $info) {
+ if (empty($info['non-exportable'])) {
+ $types[] = $type;
+ }
+ }
+
+ $handlers = ctools_export_load_object('page_manager_handlers');
+ foreach ($handlers as $handler) {
+ if (in_array($handler->task, $types)) {
+ $plugin = page_manager_get_task_handler($handler->handler);
+ $title = page_manager_get_handler_title($plugin, $handler, $tasks[$handler->task], $handler->subtask);
+
+ if ($title) {
+ $list[$handler->name] = check_plain("$handler->task: $title ($handler->name)");
+ }
+ else {
+ $list[$handler->name] = check_plain("$handler->task: ($handler->name)");
+ }
+ }
+ }
+ return $list;
+}
+
+/**
+ * Callback to bulk export page manager pages.
+ */
+function page_manager_page_manager_pages_to_hook_code($names = array(), $name = 'foo') {
+ $schema = ctools_export_get_schema('page_manager_pages');
+ $export = $schema['export'];
+ $objects = ctools_export_load_object('page_manager_pages', 'names', array_values($names));
+ if ($objects) {
+ $code = "/**\n";
+ $code .= " * Implements hook_{$export['default hook']}()\n";
+ $code .= " */\n";
+ $code .= "function " . $name . "_{$export['default hook']}() {\n";
+ foreach ($objects as $object) {
+ // Have to implement our own because this export func sig requires it
+ $code .= $export['export callback']($object, TRUE, ' ');
+ $code .= " \${$export['identifier']}s['" . check_plain($object->$export['key']) . "'] = \${$export['identifier']};\n\n";
+ }
+ $code .= " return \${$export['identifier']}s;\n";
+ $code .= "}\n";
+ return $code;
+ }
+}
+
+/**
+ * Get the current page information.
+ *
+ * @return $page
+ * An array containing the following information.
+ *
+ * - 'name': The name of the page as used in the page manager admin UI.
+ * - 'task': The plugin for the task in use. If this is a system page it
+ * will contain information about that page, such as what functions
+ * it uses.
+ * - 'subtask': The plugin for the subtask. If this is a custom page, this
+ * will contain information about that custom page. See 'subtask' in this
+ * array to get the actual page object.
+ * - 'handler': The actual handler object used. If using panels, see
+ * $page['handler']->conf['display'] for the actual panels display
+ * used to render.
+ * - 'contexts': The context objects used to render this page.
+ * - 'arguments': The raw arguments from the URL used on this page.
+ */
+function page_manager_get_current_page($page = NULL) {
+ static $current = array();
+ if (isset($page)) {
+ $current = $page;
+ }
+
+ return $current;
+}
+
+/**
+ * Implementation of hook_panels_dashboard_blocks().
+ *
+ * Adds page information to the Panels dashboard.
+ */
+function page_manager_panels_dashboard_blocks(&$vars) {
+ $vars['links']['page_manager'] = array(
+ 'weight' => -100,
+ 'title' => l(t('Panel page'), 'admin/structure/pages/add'),
+ 'description' => t('Panel pages can be used as landing pages. They have a URL path, accept arguments and can have menu entries.'),
+ );
+
+ module_load_include('inc', 'page_manager', 'page_manager.admin');
+ $tasks = page_manager_get_tasks_by_type('page');
+ $pages = array('operations' => array());
+
+ page_manager_get_pages($tasks, $pages);
+ $count = 0;
+ $rows = array();
+ foreach ($pages['rows'] as $id => $info) {
+ $rows[] = array(
+ 'data' => array(
+ $info['data']['title'],
+ $info['data']['operations'],
+ ),
+ 'class' => $info['class'],
+ );
+
+ // Only show 10.
+ if (++$count >= 10) {
+ break;
+ }
+ }
+
+ $vars['blocks']['page_manager'] = array(
+ 'weight' => -100,
+ 'title' => t('Manage pages'),
+ 'link' => l(t('Go to list'), 'admin/structure/pages'),
+ 'content' => theme('table', array('header' => array(), 'rows' => $rows, 'attributes' => array('class' => 'panels-manage'))),
+ 'class' => 'dashboard-pages',
+ 'section' => 'right',
+ );
+}
+
+/**
+ * Implement pseudo-hook to fetch addressable content.
+ *
+ * For Page Manager, the address will be an array. The first
+ * element will be the $task and the second element will be the
+ * $task_handler. The third elements will be the arguments
+ * provided.
+ */
+function page_manager_addressable_content($address, $type) {
+ if (count($address) < 3) {
+ return;
+ }
+
+ $task_name = array_shift($address);
+ $subtask_name = array_shift($address);
+ $handler_name = array_shift($address);
+ if ($address) {
+ $arguments = array_shift($address);
+ }
+
+ // Since $arguments is an array of arbitrary size, we need to implode it:
+ if (!empty($arguments)) {
+ // The only choices we have for separators since :: is already
+ // used involve ., - or _. Since - and _ are more common than .
+ // in URLs, let's try .. as an argument separator.
+ $arguments = explode('..', $arguments);
+ }
+ else {
+ // implode does not return an empty array on an empty
+ // string so do it specifically.
+ $arguments = array();
+ }
+
+ $task = page_manager_get_task($task_name);
+ if (!$task) {
+ return;
+ }
+
+ $handler = page_manager_load_task_handler($task, $subtask_name, $handler_name);
+ if (!$handler) {
+ return;
+ }
+
+ $handler_plugin = page_manager_get_task_handler($handler->handler);
+ if (!$handler_plugin) {
+ return;
+ }
+
+ // Load the contexts for the task.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, $subtask_name, $arguments);
+
+ // With contexts loaded, ensure the task is accessible. Tasks without a callback
+ // are automatically accessible.
+ $function = ctools_plugin_get_function($task, 'access callback');
+ if ($function && !$function($task, $subtask_name, $contexts)) {
+ return;
+ }
+
+ $function = ctools_plugin_get_function($handler_plugin, 'addressable callback');
+ if ($function) {
+ return $function($task, $subtask_name, $handler, $address, $contexts, $arguments, $type);
+ }
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/cache/page_manager_context.inc b/sites/all/modules/ctools/page_manager/plugins/cache/page_manager_context.inc
new file mode 100644
index 000000000..2f01b5603
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/cache/page_manager_context.inc
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * A page_manager cache indirection mechanism that just attaches context
+ * caching to the larger page cache.
+ */
+
+$plugin = array(
+ // cache plugins are the rare plugin types that have no real UI but
+ // we're providing a title just in case.
+ 'title' => t('Page manager context'),
+ 'cache get' => 'page_manager_cache_page_manager_context_cache_get',
+ 'cache set' => 'page_manager_cache_page_manager_context_cache_set',
+ 'cache finalize' => 'page_manager_cache_page_manager_context_cache_finalize',
+
+ // We don't support a clear because the general uses of clear have no effect
+ // on us.
+);
+
+function page_manager_cache_page_manager_context_cache_get($data, $key) {
+ $page = page_manager_get_page_cache($data);
+ if ($page) {
+ if (!empty($page->context_cache[$key])) {
+ return $page->context_cache[$key];
+ }
+ else {
+ ctools_include('context-task-handler');
+ if ($key == 'temp') {
+ $handler = $page->new_handler;
+ }
+ else {
+ $handler = $page->handlers[$key];
+ }
+ return ctools_context_handler_get_task_object($page->task, $page->subtask, $handler);
+ }
+ }
+}
+
+function page_manager_cache_page_manager_context_cache_set($data, $key, $object) {
+ $page = page_manager_get_page_cache($data);
+ if ($page) {
+ $page->context_cache[$key] = $object;
+ return page_manager_set_page_cache($page);
+ }
+}
+
+/**
+ * Copy temporary data from the page manager cache
+ */
+function page_manager_cache_page_manager_context_cache_finalize($data, $key, $object) {
+ // Statically cached so there shouldn't be any worries. It's an object so
+ // referencing ensures that we'll get the right one.
+ $page = page_manager_get_page_cache($data);
+ if ($page) {
+ if ($key == 'temp') {
+ $handler = $page->new_handler;
+ }
+ else {
+ $handler = $page->handlers[$key];
+ }
+ $handler->conf['contexts'] = $object->contexts;
+ $handler->conf['relationships'] = $object->relationships;
+
+ if (isset($page->context_cache[$key])) {
+ unset($page->context_cache[$key]);
+ }
+ return page_manager_set_page_cache($page);
+ }
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/task_handlers/http_response.inc b/sites/all/modules/ctools/page_manager/plugins/task_handlers/http_response.inc
new file mode 100644
index 000000000..c4eba8e20
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/task_handlers/http_response.inc
@@ -0,0 +1,332 @@
+<?php
+
+/**
+ * @file
+ *
+ * This is the task handler plugin to handle generating 403, 404 and 301 response codes.
+ */
+
+// Plugin definition
+$plugin = array(
+ // is a 'context' handler type, meaning it supports the API of the
+ // context handlers provided by ctools context plugins.
+ 'handler type' => 'context',
+ 'visible' => TRUE, // may be added up front.
+
+ // Administrative fields.
+ 'title' => t('HTTP response code'),
+ 'admin summary' => 'page_manager_http_response_admin_summary',
+ 'admin title' => 'page_manager_http_response_title',
+ 'operations' => array(
+ 'settings' => array(
+ 'title' => t('General'),
+ 'description' => t('Change general settings for this variant.'),
+ 'form' => 'page_manager_http_response_edit_settings',
+ ),
+ 'criteria' => array(
+ 'title' => t('Selection rules'),
+ 'description' => t('Control the criteria used to decide whether or not this variant is used.'),
+ 'ajax' => FALSE,
+ 'form' => array(
+ 'order' => array(
+ 'form' => t('Selection rules'),
+ ),
+ 'forms' => array(
+ 'form' => array(
+ 'include' => drupal_get_path('module', 'ctools') . '/includes/context-task-handler.inc',
+ 'form id' => 'ctools_context_handler_edit_criteria',
+ ),
+ ),
+ ),
+ ),
+ 'context' => array(
+ 'title' => t('Contexts'),
+ 'ajax' => FALSE,
+ 'description' => t('Add additional context objects to this variant that can be used by the content.'),
+ 'form' => array(
+ 'order' => array(
+ 'form' => t('Context'),
+ ),
+ 'forms' => array(
+ 'form' => array(
+ 'include' => drupal_get_path('module', 'ctools') . '/includes/context-task-handler.inc',
+ 'form id' => 'ctools_context_handler_edit_context',
+ ),
+ ),
+ ),
+ ),
+ ),
+
+ // Callback to render the data.
+ 'render' => 'page_manager_http_response_render',
+
+ 'add features' => array(
+ 'criteria' => t('Selection rules'),
+ 'context' => t('Contexts'),
+ ),
+ // Where to go when finished.
+ 'add finish' => 'settings',
+
+ 'required forms' => array(
+ 'settings' => t('Panel settings'),
+ ),
+
+ 'edit forms' => array(
+ 'criteria' => t('Selection rules'),
+ 'settings' => t('General'),
+ 'context' => t('Contexts'),
+ ),
+ 'forms' => array(
+ 'settings' => array(
+ 'form id' => 'page_manager_http_response_edit_settings',
+ ),
+ 'context' => array(
+ 'include' => drupal_get_path('module', 'ctools') . '/includes/context-task-handler.inc',
+ 'form id' => 'ctools_context_handler_edit_context',
+ ),
+ 'criteria' => array(
+ 'include' => drupal_get_path('module', 'ctools') . '/includes/context-task-handler.inc',
+ 'form id' => 'ctools_context_handler_edit_criteria',
+ ),
+ ),
+ 'default conf' => array(
+ 'title' => t('HTTP response code'),
+ 'contexts' => array(),
+ 'relationships' => array(),
+ 'code' => '404',
+ 'destination' => '',
+ ),
+);
+
+/**
+ * Provide a list of the response codes we support.
+ *
+ * Abstracted so it can be more readily used both on input and output.
+ */
+function page_manager_http_response_codes() {
+ return array(
+ 403 => t('403 Access denied'),
+ 404 => t('404 Page not found'),
+ 410 => t('410 Gone'),
+ 301 => t('301 Redirect'),
+ );
+}
+
+function page_manager_http_response_admin_summary($handler, $task, $subtask, $page, $show_title = TRUE) {
+ $task_name = page_manager_make_task_name($task['name'], $subtask['name']);
+ $output = '';
+
+ ctools_include('context');
+ ctools_include('context-task-handler');
+
+ // Get the operations
+ $operations = page_manager_get_operations($page);
+
+ // Get operations for just this handler.
+ $operations = $operations['handlers']['children'][$handler->name]['children']['actions']['children'];
+ $args = array('handlers', $handler->name, 'actions');
+ $rendered_operations = page_manager_render_operations($page, $operations, array(), array('class' => array('actions')), 'actions', $args);
+
+ $plugin = page_manager_get_task_handler($handler->handler);
+
+ $object = ctools_context_handler_get_task_object($task, $subtask, $handler);
+ $context = ctools_context_load_contexts($object, TRUE);
+
+ $access = ctools_access_group_summary(!empty($handler->conf['access']) ? $handler->conf['access'] : array(), $context);
+ if ($access) {
+ $access = t('This panel will be selected if @conditions.', array('@conditions' => $access));
+ }
+ else {
+ $access = t('This panel will always be selected.');
+ }
+
+ $rows = array();
+
+ $type = $handler->type == t('Default') ? t('In code') : $handler->type;
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Storage')),
+ array('class' => array('page-summary-data'), 'data' => $type),
+ array('class' => array('page-summary-operation'), 'data' => ''),
+ );
+
+ if (!empty($handler->disabled)) {
+ $link = l(t('Enable'), page_manager_edit_url($task_name, array('handlers', $handler->name, 'actions', 'enable')));
+ $text = t('Disabled');
+ }
+ else {
+ $link = l(t('Disable'), page_manager_edit_url($task_name, array('handlers', $handler->name, 'actions', 'disable')));
+ $text = t('Enabled');
+ }
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Status')),
+ array('class' => array('page-summary-data'), 'data' => $text),
+ array('class' => array('page-summary-operation'), 'data' => $link),
+ );
+
+ $link = l(t('Edit'), page_manager_edit_url($task_name, array('handlers', $handler->name, 'criteria')));
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Selection rule')),
+ array('class' => array('page-summary-data'), 'data' => $access),
+ array('class' => array('page-summary-operation'), 'data' => $link),
+ );
+
+ $link = l(t('Edit'), page_manager_edit_url($task_name, array('handlers', $handler->name, 'settings')));
+ $codes = page_manager_http_response_codes();
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Response code')),
+ array('class' => array('page-summary-data'), 'data' => $codes[$handler->conf['code']]),
+ array('class' => array('page-summary-operation'), 'data' => $link),
+ );
+
+ $info = theme('table', array('header' => array(), 'rows' => $rows, 'attributes' => array('class' => array('page-manager-handler-summary'))));
+
+ $title = $handler->conf['title'];
+ if ($title != t('Panel')) {
+ $title = t('Panel: @title', array('@title' => $title));
+ }
+
+ $output .= '<div class="clearfix">';
+ if ($show_title) {
+ $output .= '<div class="handler-title clearfix">';
+ $output .= '<div class="actions handler-actions">' . $rendered_operations['actions'] . '</div>';
+ $output .= '<span class="title-label">' . $title . '</span>';
+ }
+
+ $output .= '</div>';
+ $output .= $info;
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Set up a title for the panel based upon the selection rules.
+ */
+function page_manager_http_response_title($handler, $task, $subtask) {
+ if (isset($handler->conf['title'])) {
+ return check_plain($handler->conf['title']);
+ }
+ else {
+ return t('HTTP response code');
+ }
+}
+
+/**
+ * General settings for the panel
+ */
+function page_manager_http_response_edit_settings($form, &$form_state) {
+ ctools_include('page_manager.admin', 'page_manager', '');
+ ctools_include('export', 'ctools');
+
+ $conf = $form_state['handler']->conf;
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['title'],
+ '#title' => t('Administrative title'),
+ '#description' => t('Administrative title of this variant.'),
+ );
+
+ $name = isset($conf['name']) ? $conf['name'] : FALSE;
+ $form['name'] = array(
+ '#type' => 'machine_name',
+ '#title' => t('Machine name'),
+ '#required' => FALSE,
+ '#default_value' => $name,
+ '#description' => t("A unique machine-readable name for this variant. It must only contain lowercase letters, numbers, and underscores. This name will be used when exporting the variant. If left empty the variant's name will be used instead."),
+ '#size' => 32,
+ '#maxlength' => 32,
+ '#machine_name' => array(
+ 'exists' => 'page_manager_handler_check_machine_name',
+ 'source' => array('title'),
+ ),
+ '#field_prefix' => '<span dir="ltr">' . $form_state['task_name'] . '__',
+ '#field_suffix' => '</span>&lrm;',
+ );
+
+ $form['code'] = array(
+ '#title' => t('Response code'),
+ '#type' => 'select',
+ '#options' => page_manager_http_response_codes(),
+ '#default_value' => $conf['code'],
+ );
+
+ ctools_include('dependent');
+ $form['destination'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Redirect destination'),
+ '#default_value' => $conf['destination'],
+ '#dependency' => array('edit-code' => array(301)),
+ '#description' => t('Enter the path to redirect to. You may use keyword substitutions from contexts. You can use external urls (http://www.example.com/foo) or internal urls (node/1).'),
+ );
+
+ return $form;
+}
+
+function page_manager_http_response_edit_settings_submit($form, &$form_state) {
+ $machine_name = $form_state['handler']->name;
+ $name = $form_state['task_name'] . '__' . $form_state['values']['name'];
+
+ // If new name doesn't equal machine name, we need to update and redirect.
+ if ($machine_name !== $name) {
+ $form_state['handler']->name = $name;
+ // If there's a trail, we need to replace it for redirection.
+ if (isset($form_state['trail'])) {
+ $form_state['new trail'] = $form_state['trail'];
+ $delta = array_search($machine_name, $form_state['new trail']);
+ $form_state['new trail'][$delta] = $name;
+ }
+ // If handler id is set, replace it.
+ if ($form_state['handler_id']) {
+ $form_state['handler_id'] = $name;
+ }
+ // If we're defining a new custom handler, move page handler to new name.
+ if (isset($form_state['page']->handlers[$machine_name]) && isset($form_state['page']->handler_info[$machine_name])) {
+ $form_state['page']->handlers[$name] = $form_state['page']->handlers[$machine_name];
+ unset($form_state['page']->handlers[$machine_name]);
+ $form_state['page']->handler_info[$name] = $form_state['page']->handler_info[$machine_name];
+ unset($form_state['page']->handler_info[$machine_name]);
+ }
+ }
+
+ $form_state['handler']->conf['title'] = $form_state['values']['title'];
+ $form_state['handler']->conf['name'] = $form_state['values']['name'];
+ $form_state['handler']->conf['code'] = $form_state['values']['code'];
+ $form_state['handler']->conf['destination'] = $form_state['values']['destination'];
+}
+
+function page_manager_http_response_render($handler, $base_contexts, $args, $test = TRUE) {
+ // Go through arguments and see if they match.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+
+ // Add my contexts
+ $contexts = ctools_context_handler_get_handler_contexts($base_contexts, $handler);
+
+ // Test.
+ if ($test && !ctools_context_handler_select($handler, $contexts)) {
+ return;
+ }
+
+ if (isset($handler->handler)) {
+ ctools_context_handler_pre_render($handler, $contexts, $args);
+ }
+
+ $info['response code'] = $handler->conf['code'];
+ if ($info['response code'] == 301) {
+ $path = ctools_context_keyword_substitute($handler->conf['destination'], array(), $contexts);
+ $url = parse_url($path);
+ if (isset($url['query'])) {
+ $path = strtr($path, array('?' . $url['query'] => ''));
+ $info['query'] = drupal_get_query_array($url['query']);
+ }
+ if (isset($url['fragment'])) {
+ $path = strtr($path, array('#' . $url['fragment'] => ''));
+ $info['fragment'] = $url['fragment'];
+ }
+
+ $info['destination'] = rtrim($path, '?');
+ }
+
+ return $info;
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/blog.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/blog.inc
new file mode 100644
index 000000000..bab2dd28d
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/blog.inc
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_blog_page_manager_tasks() {
+ if (!module_exists('blog')) {
+ return;
+ }
+
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+
+ 'title' => t('All blogs'),
+ 'admin title' => t('All blogs'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for the all blogs at <em>/blog</em>. If no variant is selected, the default Drupal most recent blog posts will be shown.'),
+ 'admin path' => 'blog',
+
+ // Menu hooks so that we can alter the node/%node menu entry to point to us.
+ 'hook menu alter' => 'page_manager_blog_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_blog_disabled', TRUE),
+ 'enable callback' => 'page_manager_blog_enable',
+ 'access callback' => 'page_manager_blog_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_blog_page_manager_tasks().
+ *
+ * Alter the node edit input so that node edit comes to us rather than the
+ * normal node edit process.
+ */
+function page_manager_blog_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_blog_disabled', TRUE)) {
+ return;
+ }
+
+ $callback = $items['blog']['page callback'];
+ // Override the node edit handler for our purpose.
+ if ($callback == 'blog_page_last' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['blog']['page callback'] = 'page_manager_blog';
+ $items['blog']['file path'] = $task['path'];
+ $items['blog']['file'] = $task['file'];
+ }
+ else {
+ variable_set('page_manager_blog_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_blog'])) {
+ drupal_set_message(t('Page manager module is unable to enable blog because some other module already has overridden with %callback.', array('%callback' => $callback)), 'warning');
+ }
+ return;
+ }
+
+}
+
+/**
+ * Entry point for our overridden node edit.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * node edit, which is node_page_edit().
+ */
+function page_manager_blog() {
+ // Load my task plugin
+ $task = page_manager_get_task('blog');
+
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $output = ctools_context_handler_render($task, '', array(), array());
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ module_load_include('inc', 'blog', 'blog.pages');
+ $function = 'blog_page_last';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('blog')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+ return $function();
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_blog_enable($cache, $status) {
+ variable_set('page_manager_blog_disabled', $status);
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_blog'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_blog_access_check($task, $subtask_id, $contexts) {
+ return user_access('access content');
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/blog_user.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/blog_user.inc
new file mode 100644
index 000000000..351e4de09
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/blog_user.inc
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_blog_user_page_manager_tasks() {
+ if (!module_exists('blog')) {
+ return;
+ }
+
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+ 'title' => t('User blog'),
+ 'admin title' => t('User blog'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying user blogs at <em>blog/%user</em>. If no variant is selected, the default Drupal user blog will be used.'),
+ 'admin path' => 'blog/%user',
+
+ // Callback to add items to the page managertask administration form:
+ 'task admin' => 'page_manager_blog_user_task_admin',
+
+ 'hook menu alter' => 'page_manager_blog_user_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context', // handler type -- misnamed
+ 'get arguments' => 'page_manager_blog_user_get_arguments',
+ 'get context placeholders' => 'page_manager_blog_user_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_blog_user_disabled', TRUE),
+ 'enable callback' => 'page_manager_blog_user_enable',
+ 'access callback' => 'page_manager_blog_user_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_blog_user_page_manager_tasks().
+ *
+ * Alter the user view input so that user view comes to us rather than the
+ * normal user view process.
+ */
+function page_manager_blog_user_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_blog_user_disabled', TRUE)) {
+ return;
+ }
+
+ // Override the user view handler for our purpose.
+ if ($items['blog/%user_uid_optional']['page callback'] == 'blog_page_user' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['blog/%user_uid_optional']['page callback'] = 'page_manager_blog_user';
+ $items['blog/%user_uid_optional']['file path'] = $task['path'];
+ $items['blog/%user_uid_optional']['file'] = $task['file'];
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('page_manager_blog_user_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_blog_user'])) {
+ drupal_set_message(t('Page manager module is unable to enable blog/%user because some other module already has overridden with %callback.', array('%callback' => $items['blog/%user']['page callback'])), 'error');
+ }
+ }
+}
+
+/**
+ * Entry point for our overridden user view.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * user view, which is user_page_view().
+ */
+function page_manager_blog_user($account) {
+ // Load my task plugin:
+ $task = page_manager_get_task('blog_user');
+
+ // Load the account into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($account));
+
+ $output = ctools_context_handler_render($task, '', $contexts, array($account->uid));
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ module_load_include('inc', 'blog', 'blog.pages');
+ $function = 'blog_page_user';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('blog_user')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+ return $function($account);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the node view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_blog_user_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'user',
+ 'identifier' => t('User being viewed'),
+ 'id' => 1,
+ 'name' => 'uid',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_blog_user_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_blog_user_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_blog_user_enable($cache, $status) {
+ variable_set('page_manager_blog_user_disabled', $status);
+
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_blog_user'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_blog_user_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return blog_page_user_access($context->data);
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/comment_reply.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/comment_reply.inc
new file mode 100644
index 000000000..ffbafe462
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/comment_reply.inc
@@ -0,0 +1,162 @@
+<?php
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_comment_reply_page_manager_tasks() {
+ if (!module_exists('comment')) {
+ return;
+ }
+
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+
+ 'title' => t('Comment Reply page'),
+ 'admin title' => t('Comment Reply page'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for the site contact page at <em>/contact</em>. If no variant is selected, the default Drupal contact form will be used.'),
+ 'admin path' => 'comment/reply/%node',
+
+ // Menu hooks so that we can alter the node/%node menu entry to point to us.
+ 'hook menu alter' => 'page_manager_comment_reply_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+ 'get arguments' => 'page_manager_comment_reply_get_arguments',
+ 'get context placeholders' => 'page_manager_comment_reply_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_comment_reply_disabled', TRUE),
+ 'enable callback' => 'page_manager_comment_reply_enable',
+ 'access callback' => 'page_manager_comment_reply_check',
+ );
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_comment_reply_enable($cache, $status) {
+ variable_set('page_manager_comment_reply_disabled', $status);
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_comment_reply'] = TRUE;
+ }
+}
+
+
+/**
+ * Entry point for our overridden comment.
+ *
+ */
+function page_manager_comment_reply_page($node, $pid = NULL){
+ // Load my task plugin
+ $task = page_manager_get_task('comment_reply');
+
+ // Load the node into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($node, $pid));
+
+ if (array_key_exists('argument_cid_3', $contexts) && $contexts['argument_cid_3']->data->nid != $node->nid) {
+ // Attempting to reply to a comment not belonging to the current nid.
+ drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
+ drupal_goto("node/$node->nid");
+ }
+
+ $output = ctools_context_handler_render($task, '', $contexts, array($node, $pid));
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ $function = 'comment_reply';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('comment_reply')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ module_load_include('inc', 'comment', 'comment.pages');
+ return $function($node, $pid);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the node view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_comment_reply_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'node',
+ 'identifier' => t('Node being commented on'),
+ 'id' => 2,
+ 'name' => 'entity_id:node',
+ 'settings' => array(),
+ ),
+ array(
+ 'keyword' => 'comment',
+ 'identifier' => t('Comment being replied to'),
+ 'id' => 3,
+ 'name' => 'entity_id:comment',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_comment_reply_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_comment_reply_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback defined by page_manager_node_view_page_manager_tasks().
+ *
+ * Alter the node view input so that node view comes to us rather than the
+ * normal node view process.
+ */
+function page_manager_comment_reply_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_comment_reply_disabled', TRUE)) {
+ return;
+ }
+ // Override the node view handler for our purpose.
+ $callback = $items['comment/reply/%node']['page callback'];
+ if ($callback == 'comment_reply' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['comment/reply/%node']['page callback'] = 'page_manager_comment_reply_page';
+ $items['comment/reply/%node']['file path'] = $task['path'];
+ $items['comment/reply/%node']['file'] = $task['file'];
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('page_manager_comment_reply_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_comment_reply'])) {
+ drupal_set_message(t('Page manager module is unable to enable comment/reply/%node because some other module already has overridden with %callback.', array('%callback' => $callback)), 'error');
+ }
+ }
+
+ // @todo override node revision handler as well?
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_comment_reply_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return TRUE;
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/contact_site.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/contact_site.inc
new file mode 100644
index 000000000..f8718697c
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/contact_site.inc
@@ -0,0 +1,129 @@
+<?php
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_contact_site_page_manager_tasks() {
+ if (!module_exists('contact')) {
+ return;
+ }
+
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+
+ 'title' => t('Site contact page'),
+ 'admin title' => t('Site contact page'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for the site contact page at <em>/contact</em>. If no variant is selected, the default Drupal contact form will be used.'),
+ 'admin path' => 'contact',
+
+ // Menu hooks so that we can alter the node/%node menu entry to point to us.
+ 'hook menu alter' => 'page_manager_contact_site_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_contact_site_disabled', TRUE),
+ 'enable callback' => 'page_manager_contact_site_enable',
+ 'access callback' => 'page_manager_contact_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_contact_site_page_manager_tasks().
+ *
+ * Alter the node edit input so that node edit comes to us rather than the
+ * normal node edit process.
+ */
+function page_manager_contact_site_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_contact_site_disabled', TRUE)) {
+ return;
+ }
+
+ $callback = $items['contact']['page callback'];
+ if ($callback == 'drupal_get_form') {
+ $callback = $items['contact']['page arguments'][0];
+ }
+
+ // Override the node edit handler for our purpose.
+ if ($callback == 'contact_site_form' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['contact']['page callback'] = 'page_manager_contact_site';
+ $items['contact']['file path'] = $task['path'];
+ $items['contact']['file'] = $task['file'];
+ }
+ else {
+ variable_set('page_manager_contact_site_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_contact_site'])) {
+ drupal_set_message(t('Page manager module is unable to enable contact because some other module already has overridden with %callback.', array('%callback' => $callback)), 'warning');
+ }
+ return;
+ }
+
+}
+
+/**
+ * Entry point for our overridden site contact.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * node edit, which is node_page_edit().
+ */
+function page_manager_contact_site() {
+ // Load my task plugin
+ $task = page_manager_get_task('contact_site');
+
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $output = ctools_context_handler_render($task, '', array(), array());
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ module_load_include('inc', 'contact', 'contact.pages');
+ $function = 'contact_site_form';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('contact_site')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+ if ($function == 'contact_site_form') {
+ return drupal_get_form($function);
+ }
+
+ return $function();
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_contact_site_enable($cache, $status) {
+ variable_set('page_manager_contact_site_disabled', $status);
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_contact_site'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_contact_access_check($task, $subtask_id, $contexts) {
+ return user_access('access site-wide contact form');
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/contact_user.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/contact_user.inc
new file mode 100644
index 000000000..5c868161b
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/contact_user.inc
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_contact_user_page_manager_tasks() {
+ if (!module_exists('contact')) {
+ return;
+ }
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+ 'title' => t('User contact'),
+ 'admin title' => t('User contact'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying the user contact form at <em>user/%user/contact</em>. If no variant is selected, the default Drupal user contact form will be used.'),
+ 'admin path' => 'user/%user/contact',
+
+ // Callback to add items to the page managertask administration form:
+ 'task admin' => 'page_manager_contact_user_task_admin',
+
+ 'hook menu alter' => 'page_manager_contact_user_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context', // handler type -- misnamed
+ 'get arguments' => 'page_manager_contact_user_get_arguments',
+ 'get context placeholders' => 'page_manager_contact_user_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_contact_user_disabled', TRUE),
+ 'enable callback' => 'page_manager_contact_user_enable',
+ 'access callback' => 'page_manager_contact_user_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_contact_user_page_manager_tasks().
+ *
+ * Alter the user view input so that user view comes to us rather than the
+ * normal user view process.
+ */
+function page_manager_contact_user_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_contact_user_disabled', TRUE)) {
+ return;
+ }
+ $callback = $items['user/%user/contact']['page callback'];
+ if ($callback == 'drupal_get_form') {
+ $callback = $items['user/%user/contact']['page arguments'][0];
+ }
+
+ // Override the user view handler for our purpose.
+ if ($callback == 'contact_personal_form' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['user/%user/contact']['page callback'] = 'page_manager_contact_user';
+ $items['user/%user/contact']['file path'] = $task['path'];
+ $items['user/%user/contact']['file'] = $task['file'];
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('page_manager_contact_user_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_contact_user'])) {
+ drupal_set_message(t('Page manager module is unable to enable user/%user/contact because some other module already has overridden with %callback.', array('%callback' => $callback)), 'error');
+ }
+ }
+}
+
+/**
+ * Entry point for our overridden user view.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * user view, which is user_page_view().
+ */
+function page_manager_contact_user($form_id, $account) {
+ // Load my task plugin:
+ $task = page_manager_get_task('contact_user');
+
+ // Load the account into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($account));
+
+ $output = ctools_context_handler_render($task, '', $contexts, array($account->uid));
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ module_load_include('inc', 'contact', 'contact.pages');
+ $function = 'contact_personal_form';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('contact_user')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+ return drupal_get_form($function, $account);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the node view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_contact_user_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'user',
+ 'identifier' => t('User being viewed'),
+ 'id' => 1,
+ 'name' => 'uid',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_contact_user_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_contact_user_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_contact_user_enable($cache, $status) {
+ variable_set('page_manager_contact_user_disabled', $status);
+
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_contact_user'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_blog_contact_user_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return _contact_personal_tab_access($context->data);
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc
new file mode 100644
index 000000000..61ef13ac4
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_node_edit_page_manager_tasks() {
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+
+ 'title' => t('Node add/edit form'),
+ 'admin title' => t('Node add/edit form'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for adding or edit nodes at <em>node/%node/edit</em> and <em>node/add/%node_type</em>. If you add variants, you may use selection criteria such as node type or language or user access to provide different edit forms for nodes. If no variant is selected, the default Drupal node edit will be used.'),
+ 'admin path' => 'node/%node/edit',
+
+ // Menu hooks so that we can alter the node/%node menu entry to point to us.
+ 'hook menu' => 'page_manager_node_edit_menu',
+ 'hook menu alter' => 'page_manager_node_edit_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+ 'get arguments' => 'page_manager_node_edit_get_arguments',
+ 'get context placeholders' => 'page_manager_node_edit_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_node_edit_disabled', TRUE),
+ 'enable callback' => 'page_manager_node_edit_enable',
+ 'access callback' => 'page_manager_node_edit_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_node_edit_page_manager_tasks().
+ *
+ * Alter the node edit input so that node edit comes to us rather than the
+ * normal node edit process.
+ */
+function page_manager_node_edit_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_node_edit_disabled', TRUE)) {
+ return;
+ }
+
+ $callback = $items['node/%node/edit']['page callback'];
+ // Override the node edit handler for our purpose.
+ if ($callback == 'node_page_edit' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['node/%node/edit']['page callback'] = 'page_manager_node_edit';
+ $items['node/%node/edit']['file path'] = $task['path'];
+ $items['node/%node/edit']['file'] = $task['file'];
+ }
+ else {
+ variable_set('page_manager_node_edit_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_node_edit'])) {
+ drupal_set_message(t('Page manager module is unable to enable node/%node/edit because some other module already has overridden with %callback.', array('%callback' => $callback)), 'warning');
+ }
+ return;
+ }
+
+ // Also catch node/add handling:
+ foreach (node_type_get_types() as $type) {
+ $path = 'node/add/' . str_replace('_', '-', $type->type);
+ if ($items[$path]['page callback'] != 'node_add') {
+ if (!empty($GLOBALS['page_manager_enabling_node_edit'])) {
+ drupal_set_message(t('Page manager module is unable to override @path because some other module already has overridden with %callback. Node edit will be enabled but that edit path will not be overridden.', array('@path' => $path, '%callback' => $items[$path]['page callback'])), 'warning');
+ }
+ continue;
+ }
+
+ $items[$path]['page callback'] = 'page_manager_node_add';
+ $items[$path]['file path'] = $task['path'];
+ $items[$path]['file'] = $task['file'];
+ // Why str_replace things back?
+ $items[$path]['page arguments'] = array($type->type);
+ }
+}
+
+/**
+ * Entry point for our overridden node edit.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * node edit, which is node_page_edit().
+ */
+function page_manager_node_edit($node) {
+ // Load my task plugin
+ $task = page_manager_get_task('node_edit');
+
+ // Load the node into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($node));
+
+ $arg = array(isset($node->nid) ? $node->nid : $node->type);
+ $output = ctools_context_handler_render($task, '', $contexts, $arg);
+ if ($output === FALSE) {
+ // Fall back!
+ // We've already built the form with the context, so we can't build it again, or
+ // form_clean_id will mess up our ids. But we don't really need to, either:
+ $context = reset($contexts);
+ $output = $context->form;
+ }
+
+ return $output;
+}
+
+/**
+ * Callback to handle the process of adding a node.
+ *
+ * This creates a basic $node and passes that off to page_manager_node_edit().
+ * It is modelled after Drupal's node_add() function.
+ *
+ * Unlike node_add() we do not need to check node_access because that was
+ * already checked by the menu system.
+ */
+function page_manager_node_add($type) {
+ global $user;
+
+ $types = node_type_get_types();
+
+ // Initialize settings:
+ $node = (object) array(
+ 'uid' => $user->uid,
+ 'name' => (isset($user->name) ? $user->name : ''),
+ 'type' => $type,
+ 'language' => LANGUAGE_NONE,
+ );
+
+ drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)), PASS_THROUGH);
+ return page_manager_node_edit($node);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the node edit and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_node_edit_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'node',
+ 'identifier' => t('Node being edited'),
+ 'id' => 1,
+ 'name' => 'node_edit',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_node_edit_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_node_edit_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_node_edit_enable($cache, $status) {
+ variable_set('page_manager_node_edit_disabled', $status);
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_node_edit'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_node_edit_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return node_access('update', $context->data);
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/node_view.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/node_view.inc
new file mode 100644
index 000000000..89a291287
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/node_view.inc
@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * @file
+ * Handle the 'node view' override task.
+ *
+ * This plugin overrides node/%node and reroutes it to the page manager, where
+ * a list of tasks can be used to service this request based upon criteria
+ * supplied by access plugins.
+ */
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_node_view_page_manager_tasks() {
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+
+ 'title' => t('Node template'),
+
+ 'admin title' => t('Node template'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying nodes at <em>node/%node</em>. If you add variants, you may use selection criteria such as node type or language or user access to provide different views of nodes. If no variant is selected, the default Drupal node view will be used. This page only affects nodes viewed as pages, it will not affect nodes viewed in lists or at other locations. Also please note that if you are using pathauto, aliases may make a node to be somewhere else, but as far as Drupal is concerned, they are still at node/%node.'),
+ 'admin path' => 'node/%node',
+
+ // Menu hooks so that we can alter the node/%node menu entry to point to us.
+ 'hook menu' => 'page_manager_node_view_menu',
+ 'hook menu alter' => 'page_manager_node_view_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+ 'get arguments' => 'page_manager_node_view_get_arguments',
+ 'get context placeholders' => 'page_manager_node_view_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_node_view_disabled', TRUE),
+ 'enable callback' => 'page_manager_node_view_enable',
+ 'access callback' => 'page_manager_node_view_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_node_view_page_manager_tasks().
+ *
+ * Alter the node view input so that node view comes to us rather than the
+ * normal node view process.
+ */
+function page_manager_node_view_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_node_view_disabled', TRUE)) {
+ return;
+ }
+
+ // Override the node view handler for our purpose.
+ $callback = $items['node/%node']['page callback'];
+ if ($callback == 'node_page_view' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['node/%node']['page callback'] = 'page_manager_node_view_page';
+ $items['node/%node']['file path'] = $task['path'];
+ $items['node/%node']['file'] = $task['file'];
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('page_manager_node_view_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_node_view'])) {
+ drupal_set_message(t('Page manager module is unable to enable node/%node because some other module already has overridden with %callback.', array('%callback' => $callback)), 'error');
+ }
+ }
+
+ // @todo override node revision handler as well?
+}
+
+/**
+ * Entry point for our overridden node view.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * node view, which is node_page_view().
+ */
+function page_manager_node_view_page($node) {
+ // Load my task plugin
+ $task = page_manager_get_task('node_view');
+
+ // Load the node into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+
+ // Load all contexts.
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($node));
+
+ // Build the full output using the configured CTools plugin.
+ $output = ctools_context_handler_render($task, '', $contexts, array($node->nid));
+ if ($output !== FALSE) {
+ node_tag_new($node);
+ return $output;
+ }
+
+ // Try loading an override plugin.
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('node_view')) && function_exists($rc)) {
+ return $rc($node);
+ }
+ }
+
+ // Prepare the node to be displayed so all of the regular hooks are triggered.
+ $default_output = node_page_view($node);
+
+ // Otherwise, fall back to the default output generated by node_page_view().
+ return $default_output;
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the node view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_node_view_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'node',
+ 'identifier' => t('Node being viewed'),
+ 'id' => 1,
+ 'name' => 'entity_id:node',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_node_view_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_node_view_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_node_view_enable($cache, $status) {
+ variable_set('page_manager_node_view_disabled', $status);
+
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_node_view'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_node_view_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return node_access('view', $context->data);
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/page.admin.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/page.admin.inc
new file mode 100644
index 000000000..97bd37bc4
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/page.admin.inc
@@ -0,0 +1,1521 @@
+<?php
+
+/**
+ * @file
+ * Administrative functions for the page subtasks.
+ *
+ * These are attached to the menu system in page.inc via the hook_menu
+ * delegation. They are included here so that this code is loaded
+ * only when needed.
+ */
+
+/**
+ * Delegated implementation of hook_menu().
+ */
+function page_manager_page_menu(&$items, $task) {
+ // Set up access permissions.
+ $access_callback = isset($task['admin access callback']) ? $task['admin access callback'] : 'user_access';
+ $access_arguments = isset($task['admin access arguments']) ? $task['admin access arguments'] : array('administer page manager');
+
+ $base = array(
+ 'access callback' => $access_callback,
+ 'access arguments' => $access_arguments,
+ 'file' => 'plugins/tasks/page.admin.inc',
+ );
+
+ $items['admin/structure/pages/add'] = array(
+ 'title' => 'Add custom page',
+ 'page callback' => 'page_manager_page_add_subtask',
+ 'page arguments' => array(),
+ 'type' => MENU_LOCAL_ACTION,
+ ) + $base;
+
+ $items['admin/structure/pages/import'] = array(
+ 'title' => 'Import page',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('page_manager_page_import_subtask', 'page'),
+ 'type' => MENU_LOCAL_ACTION,
+ ) + $base;
+ if ($access_callback == 'user_access') {
+ $items['admin/structure/pages/import']['access callback'] = 'ctools_access_multiperm';
+ $items['admin/structure/pages/import']['access arguments'][] = 'use ctools import';
+ }
+
+ // AJAX callbacks for argument modal.
+ $items['admin/structure/pages/argument'] = array(
+ 'page callback' => 'page_manager_page_subtask_argument_ajax',
+ 'type' => MENU_CALLBACK,
+ ) + $base;
+
+ // Add menu entries for each subtask
+ foreach (page_manager_page_load_all() as $subtask_id => $subtask) {
+ if (!empty($subtask->disabled)) {
+ continue;
+ }
+
+ if (!isset($subtask->access['type'])) {
+ $subtask->access['type'] = 'none';
+ }
+ if (!isset($subtask->access['settings'])) {
+ $subtask->access['settings'] = NULL;
+ }
+
+ $path = array();
+ $page_arguments = array((string) $subtask_id);
+ $access_arguments = array($subtask->access);
+ $load_arguments = array($subtask_id, '%index', '%map');
+
+ // Replace named placeholders with our own placeholder to load contexts.
+ $position = 0;
+
+ foreach (explode('/', $subtask->path) as $bit) {
+ // Remove things like double slashes completely.
+ if (!isset($bit) || $bit === '') {
+ continue;
+ }
+
+ if ($bit[0] == '%' && $bit != '%') {
+ $placeholder = '%pm_arg';
+
+ // Chop off that %.
+ $name = substr($bit, 1);
+
+ // Check to see if the argument plugin wants to use a different
+ // placholder. This will allow to_args.
+ if (!empty($subtask->arguments[$name])) {
+ ctools_include('context');
+ if (!empty($subtask->arguments[$name]['name'])) {
+ $plugin = ctools_get_argument($subtask->arguments[$name]['name']);
+ if (isset($plugin['path placeholder'])) {
+ if (function_exists($plugin['path placeholder'])) {
+ $placeholder = $plugin['path placeholder']($subtask->arguments[$name]);
+ }
+ else {
+ $placeholder = $plugin['path placeholder'];
+ }
+ }
+ }
+ }
+ // If an argument, swap it out with our argument loader and make sure
+ // the argument gets passed through to the page callback.
+ $path[] = $placeholder;
+ $page_arguments[] = $position;
+ $access_arguments[] = $position;
+ }
+ else if ($bit[0] != '!') {
+ $path[] = $bit;
+ }
+
+ // Increment position. We do it like this to skip empty items that
+ // could happen from erroneous paths like: this///that
+ $position++;
+ }
+
+ $menu_path = implode('/', $path);
+
+ $items[$menu_path] = page_manager_page_menu_item($task, $subtask->menu, $access_arguments, $page_arguments, $load_arguments);
+
+ // Add a parent menu item if one is configured.
+ if (isset($subtask->menu['type']) && $subtask->menu['type'] == 'default tab') {
+ array_pop($path);
+ $parent_path = implode('/', $path);
+ $items[$parent_path] = page_manager_page_menu_item($task, $subtask->menu['parent'], $access_arguments, $page_arguments, $load_arguments);
+ }
+ }
+}
+
+/**
+ * Create a menu item for page manager pages.
+ *
+ * @param $menu
+ * The configuration to use. It will contain a type, and depending on the
+ * type may also contain weight, title and name. These are presumed to have
+ * been configured from the UI.
+ * @param $access_arguments
+ * Arguments that go with ctools_access_menu; it should be loaded with
+ * the access plugin type, settings, and positions of any arguments that
+ * may produce contexts.
+ * @param $page_arguments
+ * This should be seeded with the subtask name for easy loading and like
+ * the access arguments above should contain positions of arguments so
+ * that the menu system passes contexts through.
+ * @param $load_arguments
+ * Arguments to send to the arg loader; should be the subtask id and '%index'.
+ */
+function page_manager_page_menu_item($task, $menu, $access_arguments, $page_arguments, $load_arguments) {
+ $item = array(
+ 'access callback' => 'ctools_access_menu',
+ 'access arguments' => $access_arguments,
+ 'page callback' => 'page_manager_page_execute',
+ 'page arguments' => $page_arguments,
+ 'load arguments' => $load_arguments,
+ 'file' => 'plugins/tasks/page.inc',
+ );
+
+ if (isset($menu['title'])) {
+ $item['title'] = $menu['title'];
+ }
+ if (isset($menu['weight'])) {
+ $item['weight'] = $menu['weight'];
+ }
+
+ if (empty($menu['type'])) {
+ $menu['type'] = 'none';
+ }
+
+ switch ($menu['type']) {
+ case 'none':
+ default:
+ $item['type'] = MENU_CALLBACK;
+ break;
+
+ case 'normal':
+ $item['type'] = MENU_NORMAL_ITEM;
+ // Insert item into the proper menu
+ $item['menu_name'] = $menu['name'];
+ break;
+
+ case 'tab':
+ $item['type'] = MENU_LOCAL_TASK;
+ break;
+
+ case 'action':
+ $item['type'] = MENU_LOCAL_ACTION;
+ break;
+
+ case 'default tab':
+ $item['type'] = MENU_DEFAULT_LOCAL_TASK;
+ break;
+ }
+
+ return $item;
+}
+
+/**
+ * Page callback to add a subtask.
+ */
+function page_manager_page_add_subtask($task_name = NULL, $step = NULL) {
+ ctools_include('context');
+ $task = page_manager_get_task('page');
+ $task_handler_plugins = page_manager_get_task_handler_plugins($task);
+ if (empty($task_handler_plugins)) {
+ drupal_set_message(t('There are currently no variants available and a page may not be added. Perhaps you need to install the Panels module to get a variant?'), 'error');
+ return ' ';
+ }
+
+ $form_info = array(
+ 'id' => 'page_manager_add_page',
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'show return' => FALSE,
+ 'next callback' => 'page_manager_page_add_subtask_next',
+ 'finish callback' => 'page_manager_page_add_subtask_finish',
+ 'return callback' => 'page_manager_page_add_subtask_finish',
+ 'cancel callback' => 'page_manager_page_add_subtask_cancel',
+ 'add order' => array(
+ 'basic' => t('Basic settings'),
+ 'argument' => t('Argument settings'),
+ 'access' => t('Access control'),
+ 'menu' => t('Menu settings'),
+ ),
+ 'forms' => array(
+ 'basic' => array(
+ 'form id' => 'page_manager_page_form_basic',
+ ),
+ 'access' => array(
+ 'form id' => 'page_manager_page_form_access',
+ ),
+ 'menu' => array(
+ 'form id' => 'page_manager_page_form_menu',
+ ),
+ 'argument' => array(
+ 'form id' => 'page_manager_page_form_argument',
+ ),
+ ),
+ );
+
+ if ($task_name) {
+ $page = page_manager_get_page_cache($task_name);
+ if (empty($page)) {
+ return MENU_NOT_FOUND;
+ }
+
+ $form_info['path'] = "admin/structure/pages/add/$task_name/%step";
+ }
+ else {
+ $new_page = page_manager_page_new();
+ $new_page->name = NULL;
+
+ $page = new stdClass();
+ page_manager_page_new_page_cache($new_page, $page);
+ $form_info['path'] = 'admin/structure/pages/add/%task_name/%step';
+ }
+
+ if ($step && $step != 'basic') {
+ $handler_plugin = page_manager_get_task_handler($page->handler);
+
+ $form_info['forms'] += $handler_plugin['forms'];
+
+ if (isset($page->forms)) {
+ foreach ($page->forms as $id) {
+ if (isset($form_info['add order'][$id])) {
+ $form_info['order'][$id] = $form_info['add order'][$id];
+ }
+ else if (isset($handler_plugin['add features'][$id])) {
+ $form_info['order'][$id] = $handler_plugin['add features'][$id];
+ }
+ else if (isset($handler_plugin['required forms'][$id])) {
+ $form_info['order'][$id] = $handler_plugin['required forms'][$id];
+ }
+ }
+ }
+ else {
+ $form_info['order'] = $form_info['add order'];
+ }
+
+ // This means we just submitted our form from the default list
+ // of steps, which we've traded in for a newly generated list of
+ // steps above. We need to translate this 'next' step into what
+ // our questions determined would be next.
+ if ($step == 'next') {
+ $keys = array_keys($form_info['order']);
+ // get rid of 'basic' from the list of forms.
+ array_shift($keys);
+ $step = array_shift($keys);
+
+ // If $step == 'basic' at this point, we were not presented with any
+ // additional forms at all. Let's just save and go!
+ if ($step == 'basic') {
+ page_manager_save_page_cache($page);
+ // Redirect to the new page's task handler editor.
+ drupal_goto(page_manager_edit_url($page->task_name));
+ }
+ }
+ }
+ else {
+ $form_info['show trail'] = FALSE;
+ $form_info['order'] = array(
+ 'basic' => t('Basic settings'),
+ 'next' => t('A meaningless second page'),
+ );
+ }
+
+ ctools_include('wizard');
+ $form_state = array(
+ 'task' => $task,
+ 'subtask' => $page->subtask,
+ 'page' => &$page,
+ 'type' => 'add',
+ 'task_id' => 'page',
+ 'task_name' => $page->task_name,
+ 'creating' => TRUE,
+ );
+
+ if (!empty($page->handlers)) {
+ $keys = array_keys($page->handlers);
+ $key = array_shift($keys);
+ $form_state['handler'] = &$page->handlers[$key];
+ $form_state['handler_id'] = $key;
+ }
+
+ $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
+
+ if (!$output) {
+ // redirect.
+ drupal_redirect_form(array(), $form_state['redirect']);
+ }
+
+ return $output;
+}
+
+/**
+ * Callback generated when the add page process is finished.
+ */
+function page_manager_page_add_subtask_finish(&$form_state) {
+ $page = &$form_state['page'];
+ // Update the cache with changes.
+ page_manager_set_page_cache($page);
+
+ $handler = $form_state['handler'];
+ $handler_plugin = page_manager_get_task_handler($handler->handler);
+
+ // Redirect to the new page's task handler editor.
+ if (isset($handler_plugin['add finish'])) {
+ $form_state['redirect'] = page_manager_edit_url($page->task_name, array('handlers', $handler->name, $handler_plugin['add finish']));
+ }
+ else {
+ $form_state['redirect'] = page_manager_edit_url($page->task_name);
+ }
+ return;
+}
+
+/**
+ * Callback generated when the 'next' button is clicked.
+ *
+ * All we do here is store the cache.
+ */
+function page_manager_page_add_subtask_next(&$form_state) {
+ if (empty($form_state['task_name']) || $form_state['task_name'] == 'page') {
+ // We may not have known the path to go next, because we didn't yet know the
+ // task name. This fixes that.
+ $form_state['form_info']['path'] = str_replace('%task_name', $form_state['page']->task_name, $form_state['form_info']['path']);
+
+ $form_state['redirect'] = ctools_wizard_get_path($form_state['form_info'], $form_state['clicked_button']['#next']);
+ }
+
+ // Update the cache with changes.
+ page_manager_set_page_cache($form_state['page']);
+}
+
+/**
+ * Callback generated when the 'cancel' button is clicked.
+ *
+ * All we do here is clear the cache.
+ */
+function page_manager_page_add_subtask_cancel(&$form_state) {
+ // Wipe all our stored changes.
+ if (isset($form_state['page']->task_name)) {
+ page_manager_clear_page_cache($form_state['page']->task_name);
+ }
+}
+
+/**
+ * Basic settings form for a page manager page.
+ */
+function page_manager_page_form_basic($form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+ $task = $form_state['task'];
+
+ $form['admin_title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Administrative title'),
+ '#description' => t('The name of this page. This will appear in the administrative interface to easily identify it.'),
+ '#default_value' => $page->admin_title,
+ );
+
+ $form['name'] = array(
+ '#type' => 'machine_name',
+ '#title' => t('Machine name'),
+ '#machine_name' => array(
+ 'exists' => 'page_manager_page_load',
+ 'source' => array('admin_title'),
+ ),
+ '#description' => t('The machine readable name of this page. It must be unique, and it must contain only alphanumeric characters and underscores. Once created, you will not be able to change this value!'),
+ '#default_value' => $page->name,
+ );
+
+ if (isset($page->pid) || empty($form_state['creating'])) {
+ $form['name']['#disabled'] = TRUE;
+ $form['name']['#value'] = $page->name;
+ }
+
+ $form['admin_description'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Administrative description'),
+ '#description' => t('A description of what this page is, does or is for, for administrative use.'),
+ '#default_value' => $page->admin_description,
+ );
+
+ // path
+ $form['path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Path'),
+ '#description' => t('The URL path to get to this page. You may create named placeholders for variable parts of the path by using %name for required elements and !name for optional elements. For example: "node/%node/foo", "forum/%forum" or "dashboard/!input". These named placeholders can be turned into contexts on the arguments form.'),
+ '#default_value' => $page->path,
+ '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
+ );
+
+ $frontpage = variable_get('site_frontpage', 'node');
+
+ $path = array();
+ if ($page->path) {
+ foreach (explode('/', $page->path) as $bit) {
+ if ($bit[0] != '!') {
+ $path[] = $bit;
+ }
+ }
+ }
+
+ $path = implode('/', $path);
+
+ if (empty($path) || $path != $frontpage) {
+ $form['frontpage'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($page->make_frontpage),
+ '#title' => t('Make this your site home page.'),
+ '#description' => t('To set this panel as your home page you must create a unique path name with no % placeholders in the path. The site home page is currently set to %homepage on the !siteinfo configuration form.', array('!siteinfo' => l(t('Site Information'), 'admin/config/system/site-information'), '%homepage' => '/' . $frontpage)),
+ );
+ $form['admin_paths'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($page->conf['admin_paths']),
+ '#title' => t('Use this page in an admin overlay.'),
+ '#description' => t('Admin overlays are used in many places in Drupal 7 and administrative custom pages should probably utilize this feature.'),
+ );
+ }
+ else if ($path == $frontpage) {
+ $form['frontpage_markup'] = array(
+ '#value' => '<b>' . t('This page is currently set to be your site home page. This can be modified on the !siteinfo configuration form.', array('!siteinfo' => l(t('Site Information'), 'admin/settings/site-information'))) . '</b>',
+ );
+
+ $form['frontpage'] = array(
+ '#type' => 'value',
+ '#value' => TRUE,
+ );
+ }
+
+ if (!isset($page->pid) && !empty($form_state['creating'])) {
+ $features['default'] = array(
+ 'access' => t('Access control'),
+ 'menu' => t('Visible menu item'),
+ );
+
+ module_load_include('inc', 'page_manager', 'page_manager.admin');
+ $form = page_manager_handler_add_form($form, $form_state, $features);
+ }
+
+ return $form;
+}
+
+function page_manager_page_form_basic_validate_filter($value) {
+ return $value === -1;
+}
+
+/**
+ * Validate the basic form.
+ */
+function page_manager_page_form_basic_validate(&$form, &$form_state) {
+ // Ensure path is unused by other pages.
+ $page = $form_state['page']->subtask['subtask'];
+ $name = !empty($form_state['values']['name']) ? $form_state['values']['name'] : $page->name;
+ if (empty($name)) {
+ form_error($form['name'], t('Name is required.'));
+ }
+
+ // If this is new, make sure the name is unique:
+ if (empty($page->name)) {
+ $test = page_manager_page_load($name);
+ if ($test) {
+ form_error($form['name'], t('That name is used by another page: @page', array('@page' => $test->admin_title)));
+ }
+
+ // Ensure name fits the rules:
+ if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
+ form_error($form['name'], t('Page name must be alphanumeric or underscores only.'));
+ }
+ }
+
+ $pages = page_manager_page_load_all();
+ foreach ($pages as $test) {
+ if ($test->name != $name && $test->path == $form_state['values']['path'] && empty($test->disabled)) {
+ form_error($form['path'], t('That path is used by another page: @page', array('@page' => $test->admin_title)));
+ }
+ }
+
+ // Ensure path is unused by things NOT pages. We do the double check because
+ // we're checking against our page callback.
+ $path = array();
+ if (empty($form_state['values']['path'])) {
+ form_error($form['path'], t('Path is required.'));
+ // stop processing here if there is no path.
+ return;
+ }
+
+ $found = FALSE;
+ $error = FALSE;
+ foreach (explode('/', $form_state['values']['path']) as $position => $bit) {
+ if (!isset($bit) || $bit === '') {
+ continue;
+ }
+
+ if ($bit == '%' || $bit == '!') {
+ form_error($form['path'], t('You cannot have an unnamed placeholder (% or ! by itself). Please name your placeholder by adding a short piece of descriptive text to the % or !, such as %user or %node.'));
+ }
+
+ if ($bit[0] == '%') {
+ if ($found) {
+ form_error($form['path'], t('You cannot have a dynamic path element after an optional path element.'));
+ }
+
+ if ($position == 0) {
+ form_error($form['path'], t('The first element in a path may not be dynamic.'));
+ }
+
+ $path[] = '%';
+ }
+ else if ($bit[0] == '!') {
+ $found = TRUE;
+ }
+ else {
+ if ($found) {
+ form_error($form['path'], t('You cannot have a static path element after an optional path element.'));
+ }
+ $path[] = $bit;
+ }
+ }
+
+ // Check to see if something that isn't a page manager page is using the path.
+ $path = implode('/', $path);
+ $result = db_query('SELECT * FROM {menu_router} WHERE path = :path', array(':path' => $path));
+ foreach ($result as $router) {
+ if ($router->page_callback != 'page_manager_page_execute') {
+ form_error($form['path'], t('That path is already in use. This system cannot override existing paths.'));
+ }
+ }
+
+ // Ensure the path is not already an alias to something else.
+ if (strpos($path, '%') === FALSE) {
+ $alias = db_query('SELECT alias, source FROM {url_alias} WHERE alias = :path', array(':path' => $path))->fetchObject();
+ if ($alias) {
+ form_error($form['path'], t('That path is currently assigned to be an alias for @alias. This system cannot override existing aliases.', array('@alias' => $alias->source)));
+ }
+ }
+ else {
+ if (!empty($form_state['values']['frontpage'])) {
+ form_error($form['path'], t('You cannot make this page your site home page if it uses % placeholders.'));
+ }
+ }
+
+ // Ensure path is properly formed.
+ $args = page_manager_page_get_named_arguments($form_state['values']['path']);
+ if ($invalid_args = array_filter($args, 'page_manager_page_form_basic_validate_filter')) {
+ foreach ($invalid_args as $arg => $position) {
+ form_error($form['path'], t('Duplicated argument %arg', array('%arg' => $arg)));
+ }
+ }
+
+ if (isset($args['%'])) {
+ form_error($form['path'], t('Invalid arg <em>%</em>. All arguments must be named with keywords.'));
+ }
+
+ $form_state['arguments'] = $args;
+}
+
+/**
+ * Store the values from the basic settings form.
+ */
+function page_manager_page_form_basic_submit(&$form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+ $cache = &$form_state['page'];
+
+ // If this is a new thing, then we have to do a bunch of setup to create
+ // the cache record with the right ID and some basic data that we could
+ // not know until we asked the user some questions.
+ if (!isset($page->pid) && !empty($form_state['creating'])) {
+ // Update the data with our new name.
+ $page->name = $form_state['values']['name'];
+ $form_state['page']->task_name = page_manager_make_task_name($form_state['task_id'], $page->name);
+ $cache->handler = $form_state['values']['handler'];
+ $cache->subtask_id = $page->name;
+ $plugin = page_manager_get_task_handler($cache->handler);
+
+ // If they created and went back, there might be old, dead handlers
+ // that are not going to be added.
+ //
+ // Remove them:
+ $cache->handlers = array();
+ $cache->handler_info = array();
+
+ // Create a new handler.
+ $handler = page_manager_new_task_handler($plugin);
+ $title = !empty($form_state['values']['title']) ? $form_state['values']['title'] : $plugin['title'];
+ page_manager_handler_add_to_page($cache, $handler, $title);
+
+ // Figure out which forms to present them with
+ $cache->forms = array();
+ $cache->forms[] = 'basic'; // This one is always there.
+ if (!empty($form_state['arguments'])) {
+ $cache->forms[] = 'argument';
+ }
+
+ $features = $form_state['values']['features'];
+ $cache->forms = array_merge($cache->forms, array_keys(array_filter($features['default'])));
+ if (isset($features[$form_state['values']['handler']])) {
+ $cache->forms = array_merge($cache->forms, array_keys(array_filter($features[$form_state['values']['handler']])));
+ }
+
+ if (isset($plugin['required forms'])) {
+ $cache->forms = array_merge($cache->forms, array_keys($plugin['required forms']));
+ }
+ }
+
+ $page->admin_title = $form_state['values']['admin_title'];
+ $cache->subtask['admin title'] = check_plain($form_state['values']['admin_title']);
+
+ $page->admin_description = $form_state['values']['admin_description'];
+ $cache->subtask['admin description'] = filter_xss_admin($form_state['values']['admin_description']);
+
+ if ($page->path != $form_state['values']['path']) {
+ $page->path = $form_state['values']['path'];
+ page_manager_page_recalculate_arguments($page);
+ $cache->path_changed = TRUE;
+ }
+
+ $page->make_frontpage = !empty($form_state['values']['frontpage']);
+ $page->conf['admin_paths'] = !empty($form_state['values']['admin_paths']);
+}
+
+/**
+ * Form to handle menu item controls.
+ */
+function page_manager_page_form_menu($form, &$form_state) {
+ ctools_include('dependent');
+ $form['menu'] = array(
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+
+ $menu = $form_state['page']->subtask['subtask']->menu;
+ if (empty($menu)) {
+ $menu = array(
+ 'type' => 'none',
+ 'title' => '',
+ 'weight' => 0,
+ 'name' => 'navigation',
+ 'parent' => array(
+ 'type' => 'none',
+ 'title' => '',
+ 'weight' => 0,
+ 'name' => 'navigation',
+ ),
+ );
+ }
+
+ $form['menu']['type'] = array(
+ '#title' => t('Type'),
+ '#type' => 'radios',
+ '#options' => array(
+ 'none' => t('No menu entry'),
+ 'normal' => t('Normal menu entry'),
+ 'tab' => t('Menu tab'),
+ 'default tab' => t('Default menu tab'),
+ 'action' => t('Local action'),
+ ),
+ '#default_value' => $menu['type'],
+ );
+
+ $form['menu']['title'] = array(
+ '#title' => t('Title'),
+ '#type' => 'textfield',
+ '#default_value' => $menu['title'],
+ '#description' => t('If set to normal or tab, enter the text to use for the menu item.'),
+ '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab', 'action')),
+ );
+
+ list($major, $minor) = explode('.', VERSION, 2);
+
+ // Only display the menu selector if menu module is enabled.
+ if (module_exists('menu')) {
+ $form['menu']['name'] = array(
+ '#title' => t('Menu'),
+ '#type' => 'select',
+ '#options' => menu_get_menus(),
+ '#default_value' => $menu['name'],
+ '#description' => t('Insert item into an available menu.'),
+ '#dependency' => array('radio:menu[type]' => array('normal')),
+ );
+ }
+ else {
+ $form['menu']['name'] = array(
+ '#type' => 'value',
+ '#value' => $menu['name'],
+ );
+ $form['menu']['markup'] = array(
+ '#value' => t('Menu selection requires the activation of menu module.'),
+ );
+ }
+ $form['menu']['weight'] = array(
+ '#title' => t('Weight'),
+ '#type' => 'textfield',
+ '#default_value' => isset($menu['weight']) ? $menu['weight'] : 0,
+ '#description' => t('The lower the weight the higher/further left it will appear.'),
+ '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab', 'action')),
+ );
+
+ $form['menu']['parent']['type'] = array(
+ '#prefix' => '<div id="edit-menu-parent-type-wrapper">',
+ '#suffix' => '</div>',
+ '#title' => t('Parent menu item'),
+ '#type' => 'radios',
+ '#options' => array('none' => t('No menu entry'), 'normal' => t('Normal menu item'), 'tab' => t('Menu tab')),
+ '#default_value' => $menu['parent']['type'],
+ '#description' => t('When providing a menu item as a default tab, Drupal needs to know what the parent menu item of that tab will be. Sometimes the parent will already exist, but other times you will need to have one created. The path of a parent item will always be the same path with the last part left off. i.e, if the path to this view is <em>foo/bar/baz</em>, the parent path would be <em>foo/bar</em>.'),
+ '#dependency' => array('radio:menu[type]' => array('default tab')),
+ );
+ $form['menu']['parent']['title'] = array(
+ '#title' => t('Parent item title'),
+ '#type' => 'textfield',
+ '#default_value' => $menu['parent']['title'],
+ '#description' => t('If creating a parent menu item, enter the title of the item.'),
+ '#dependency' => array('radio:menu[type]' => array('default tab'), 'radio:menu[parent][type]' => array('normal', 'tab')),
+ '#dependency_count' => 2,
+ );
+ // Only display the menu selector if menu module is enabled.
+ if (module_exists('menu')) {
+ $form['menu']['parent']['name'] = array(
+ '#title' => t('Parent item menu'),
+ '#type' => 'select',
+ '#options' => menu_get_menus(),
+ '#default_value' => $menu['parent']['name'],
+ '#description' => t('Insert item into an available menu.'),
+ '#dependency' => array('radio:menu[type]' => array('default tab'), 'radio:menu[parent][type]' => array('normal')),
+ '#dependency_count' => 2,
+ );
+ }
+ else {
+ $form['menu']['parent']['name'] = array(
+ '#type' => 'value',
+ '#value' => $menu['parent']['name'],
+ );
+ }
+ $form['menu']['parent']['weight'] = array(
+ '#title' => t('Parent weight'),
+ '#type' => 'textfield',
+ '#default_value' => $menu['parent']['weight'],
+ '#size' => 5,
+ '#description' => t('Enter the weight of the parent item. The lower the number, the more to the left it will be.'),
+ '#dependency' => array('radio:menu[type]' => array('default tab'), 'radio:menu[parent][type]' => array('tab', 'normal')),
+ '#dependency_count' => 2,
+ );
+
+ return $form;
+}
+
+/**
+ * Validate handler for the menu form for add/edit page task.
+ */
+function page_manager_page_form_menu_validate(&$form, &$form_state) {
+ // If setting a 'normal' menu entry, make sure that any placeholders
+ // support the to_arg stuff.
+
+ if ($form_state['values']['menu']['type'] == 'normal') {
+ $page = $form_state['page']->subtask['subtask'];
+
+ foreach (explode('/', $page->path) as $bit) {
+ if (!isset($bit) || $bit === '') {
+ continue;
+ }
+
+ if ($bit[0] == '%') {
+ // Chop off that %.
+ $name = substr($bit, 1);
+
+ // Check to see if the argument plugin allows to arg:
+ if (!empty($page->arguments[$name])) {
+ ctools_include('context');
+ $plugin = ctools_get_argument($page->arguments[$name]['name']);
+ if (!empty($plugin['path placeholder to_arg'])) {
+ continue;
+ }
+ }
+
+ form_error($form['menu']['type'], t('Paths with non optional placeholders cannot be used as normal menu items unless the selected argument handler provides a default argument to use for the menu item.'));
+ return;
+ }
+ }
+ }
+}
+
+/**
+ * Submit handler for the menu form for add/edit page task.
+ */
+function page_manager_page_form_menu_submit(&$form, &$form_state) {
+ $form_state['page']->subtask['subtask']->menu = $form_state['values']['menu'];
+ $form_state['page']->path_changed = TRUE;
+}
+
+/**
+ * Form to handle menu item controls.
+ */
+function page_manager_page_form_access($form, &$form_state) {
+ ctools_include('context');
+ $form_state['module'] = 'page_manager_page';
+ $form_state['callback argument'] = $form_state['page']->task_name;
+ $form_state['access'] = $form_state['page']->subtask['subtask']->access;
+ $form_state['no buttons'] = TRUE;
+ $form_state['contexts'] = array();
+
+ // Load contexts based on argument data:
+ if ($arguments = _page_manager_page_get_arguments($form_state['page']->subtask['subtask'])) {
+ $form_state['contexts'] = ctools_context_get_placeholders_from_argument($arguments);
+ }
+
+ ctools_include('context-access-admin');
+ $form = ctools_access_admin_form($form, $form_state);
+
+ return $form;
+}
+
+/**
+ * Submit handler to deal with access control changes.
+ */
+function page_manager_page_form_access_submit(&$form, &$form_state) {
+ $form_state['page']->subtask['subtask']->access['logic'] = $form_state['values']['logic'];
+ $form_state['page']->path_changed = TRUE;
+}
+
+/**
+ * Form to handle assigning argument handlers to named arguments.
+ */
+function page_manager_page_form_argument($form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+ $path = $page->path;
+
+ $arguments = page_manager_page_get_named_arguments($path);
+
+ $form['table'] = array(
+ '#theme' => 'page_manager_page_form_argument_table',
+ '#page-manager-path' => $path,
+ 'argument' => array(),
+ );
+
+ $task_name = $form_state['page']->task_name;
+ foreach ($arguments as $keyword => $position) {
+ $conf = array();
+
+ if (isset($page->temporary_arguments[$keyword]) && !empty($form_state['allow temp'])) {
+ $conf = $page->temporary_arguments[$keyword];
+ }
+ else if (isset($page->arguments[$keyword])) {
+ $conf = $page->arguments[$keyword];
+ }
+
+ $context = t('No context assigned');
+
+ $plugin = array();
+ if ($conf && isset($conf['name'])) {
+ ctools_include('context');
+ $plugin = ctools_get_argument($conf['name']);
+
+ if (isset($plugin['title'])) {
+ $context = $plugin['title'];
+ }
+ }
+
+ $form['table']['argument'][$keyword]['#keyword'] = $keyword;
+ $form['table']['argument'][$keyword]['#position'] = $position;
+ $form['table']['argument'][$keyword]['#context'] = $context;
+
+ // The URL for this ajax button
+ $form['table']['argument'][$keyword]['change-url'] = array(
+ '#attributes' => array('class' => array("page-manager-context-$keyword-change-url")),
+ '#type' => 'hidden',
+ '#value' => url("admin/structure/pages/argument/change/$task_name/$keyword", array('absolute' => TRUE)),
+ );
+ $form['table']['argument'][$keyword]['change'] = array(
+ '#type' => 'submit',
+ '#value' => t('Change'),
+ '#attributes' => array('class' => array('ctools-use-modal')),
+ '#id' => "page-manager-context-$keyword-change",
+ );
+
+ $form['table']['argument'][$keyword]['settings'] = array();
+
+ // Only show the button if this has a settings form available:
+ if (!empty($plugin)) {
+ // The URL for this ajax button
+ $form['table']['argument'][$keyword]['settings-url'] = array(
+ '#attributes' => array('class' => array("page-manager-context-$keyword-settings-url")),
+ '#type' => 'hidden',
+ '#value' => url("admin/structure/pages/argument/settings/$task_name/$keyword", array('absolute' => TRUE)),
+ );
+ $form['table']['argument'][$keyword]['settings'] = array(
+ '#type' => 'submit',
+ '#value' => t('Settings'),
+ '#attributes' => array('class' => array('ctools-use-modal')),
+ '#id' => "page-manager-context-$keyword-settings",
+ );
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Theme the table for this form.
+ */
+function theme_page_manager_page_form_argument_table($vars) {
+ $form = $vars['form'];
+ $header = array(
+ array('data' => t('Argument'), 'class' => array('page-manager-argument')),
+ array('data' => t('Position in path'), 'class' => array('page-manager-position')),
+ array('data' => t('Context assigned'), 'class' => array('page-manager-context')),
+ array('data' => t('Operations'), 'class' => array('page-manager-operations')),
+ );
+
+ $rows = array();
+
+ ctools_include('modal');
+ ctools_modal_add_js();
+ foreach (element_children($form['argument']) as $key) {
+ $row = array();
+ $row[] = '%' . check_plain($form['argument'][$key]['#keyword']);
+ $row[] = check_plain($form['argument'][$key]['#position']);
+ $row[] = $form['argument'][$key]['#context'] . ' &nbsp; ' . drupal_render($form['argument'][$key]['change']);;
+ $row[] = drupal_render($form['argument'][$key]['settings']) . drupal_render($form['argument'][$key]);
+
+ $rows[] = array('data' => $row);
+ }
+
+ if (!$rows) {
+ $rows[] = array(array('data' => t('The path %path has no arguments to configure.', array('%path' => $form['#page-manager-path'])), 'colspan' => 4));
+ }
+
+ $attributes = array(
+ 'id' => 'page-manager-argument-table',
+ );
+
+ $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => $attributes));
+ return $output;
+}
+
+/**
+ * Ajax entry point to edit an item
+ */
+function page_manager_page_subtask_argument_ajax($step = NULL, $task_name = NULL, $keyword = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+ ctools_include('wizard');
+
+ if (!$step) {
+ return ctools_ajax_render_error();
+ }
+
+ if (!$cache = page_manager_get_page_cache($task_name)) {
+ return ctools_ajax_render_error(t('Invalid object name.'));
+ }
+
+ $page = &$cache->subtask['subtask'];
+ $path = $page->path;
+ $arguments = page_manager_page_get_named_arguments($path);
+
+ // Load stored object from cache.
+ if (!isset($arguments[$keyword])) {
+ return ctools_ajax_render_error(t('Invalid keyword.'));
+ }
+
+ // Set up wizard info
+ $form_info = array(
+ 'id' => 'page_manager_page_argument',
+ 'path' => "admin/structure/pages/argument/%step/$task_name/$keyword",
+ 'show cancel' => TRUE,
+ 'next callback' => 'page_manager_page_argument_next',
+ 'finish callback' => 'page_manager_page_argument_finish',
+ 'cancel callback' => 'page_manager_page_argument_cancel',
+ 'order' => array(
+ 'change' => t('Change context type'),
+ 'settings' => t('Argument settings'),
+ ),
+ 'forms' => array(
+ 'change' => array(
+ 'title' => t('Change argument'),
+ 'form id' => 'page_manager_page_argument_form_change',
+ ),
+ 'settings' => array(
+ 'title' => t('Argument settings'),
+ 'form id' => 'page_manager_page_argument_form_settings',
+ ),
+ ),
+ );
+
+ $form_state = array(
+ 'page' => $cache,
+ 'keyword' => $keyword,
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ 'commands' => array(),
+ );
+
+ $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
+ if (!empty($form_state['cancel'])) {
+ $commands = array(ctools_modal_command_dismiss());
+ }
+ else if (!empty($form_state['complete'])) {
+ if (isset($page->temporary_arguments[$keyword])) {
+ $page->arguments[$keyword] = $page->temporary_arguments[$keyword];
+ }
+
+ if (isset($page->temporary_arguments)) {
+ unset($page->temporary_arguments);
+ }
+
+ // Update the cache with changes.
+ page_manager_set_page_cache($cache);
+
+ // Rerender the table so we can ajax it back in.
+ // Go directly to the form and retrieve it using a blank form and
+ // a clone of our current form state. This is an abbreviated
+ // drupal_get_form that is halted prior to render and is never
+ // fully processed, but is guaranteed to produce the same form we
+ // started with so we don't have to do crazy stuff to rerender
+ // just part of it.
+
+ // @todo should there be a tool to do this?
+
+ $clone_state = $form_state;
+ $clone_state['allow temp'] = TRUE;
+ $form = drupal_build_form('page_manager_page_form_argument', $form_state);
+
+ // Render just the table portion.
+ $output = drupal_render($form['table']);
+ $commands = array(
+ ajax_command_replace('#page-manager-argument-table', $output),
+ ctools_modal_command_dismiss(),
+ );
+ }
+ else {
+ $commands = ctools_modal_form_render($form_state, $output);
+ }
+ print ajax_render($commands);
+ ajax_footer();
+ exit;
+}
+
+/**
+ * Callback generated when the add page process is finished.
+ */
+function page_manager_page_argument_finish(&$form_state) {
+}
+
+/**
+ * Callback generated when the 'next' button is clicked.
+ *
+ * All we do here is store the cache.
+ */
+function page_manager_page_argument_next(&$form_state) {
+ // Update the cache with changes.
+ page_manager_set_page_cache($form_state['page']);
+}
+
+/**
+ * Callback generated when the 'cancel' button is clicked.
+ *
+ * We might have some temporary data lying around. We must remove it.
+ */
+function page_manager_page_argument_cancel(&$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+ if (isset($page->temporary_arguments)) {
+ unset($page->temporary_arguments);
+ // Update the cache with changes.
+ page_manager_set_page_cache($page);
+ }
+}
+
+/**
+ * Basic settings form for a page manager page.
+ */
+function page_manager_page_argument_form_change($form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+ $keyword = &$form_state['keyword'];
+
+ ctools_include('context');
+ $plugins = ctools_get_arguments();
+
+ $options = array();
+ foreach ($plugins as $id => $plugin) {
+ if (empty($plugin['no ui'])) {
+ $options[$id] = $plugin['title'];
+ }
+ }
+
+ asort($options);
+
+ $options = array('' => t('No context selected')) + $options;
+
+ $argument = '';
+ if (isset($page->arguments[$keyword]) && isset($page->arguments[$keyword]['name'])) {
+ $argument = $page->arguments[$keyword]['name'];
+ }
+
+ $form['argument'] = array(
+ '#type' => 'radios',
+ '#options' => $options,
+ '#default_value' => $argument,
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler to change an argument.
+ */
+function page_manager_page_argument_form_change_submit(&$form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+ $keyword = &$form_state['keyword'];
+ $argument = $form_state['values']['argument'];
+
+ // If the argument is not changing, we do not need to do anything.
+ if (isset($page->arguments[$keyword]['name']) && $page->arguments[$keyword]['name'] == $argument) {
+ // Set the task to cancel since no change means do nothing:
+ $form_state['clicked_button']['#wizard type'] = 'cancel';
+ return;
+ }
+
+ ctools_include('context');
+
+ // If switching to the no context, just wipe out the old data.
+ if (empty($argument)) {
+ $form_state['clicked_button']['#wizard type'] = 'finish';
+ $page->temporary_arguments[$keyword] = array(
+ 'settings' => array(),
+ 'identifier' => t('No context'),
+ );
+ return;
+ }
+
+ $plugin = ctools_get_argument($argument);
+
+ // Acquire defaults.
+ $settings = array();
+
+ if (isset($plugin['default'])) {
+ if (is_array($plugin['default'])) {
+ $settings = $plugin['default'];
+ }
+ else if (function_exists($plugin['default'])) {
+ $settings = $plugin['default']();
+ }
+ }
+
+ $id = ctools_context_next_id($page->arguments, $argument);
+ $title = isset($plugin['title']) ? $plugin['title'] : t('No context');
+
+ // Set the new argument in a temporary location.
+ $page->temporary_arguments[$keyword] = array(
+ 'id' => $id,
+ 'identifier' => $title . ($id > 1 ? ' ' . $id : ''),
+ 'name' => $argument,
+ 'settings' => $settings,
+ );
+}
+
+/**
+ * Basic settings form for a page manager page.
+ */
+function page_manager_page_argument_form_settings($form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+ $keyword = &$form_state['keyword'];
+
+ if (isset($page->temporary_arguments[$keyword])) {
+ $conf = $page->temporary_arguments[$keyword];
+ }
+ else if (isset($page->arguments[$keyword])) {
+ $conf = $page->temporary_arguments[$keyword] = $page->arguments[$keyword];
+ }
+
+ if (!isset($conf)) {
+ // This should be impossible and thus never seen.
+ $form['error'] = array('#value' => t('Error: missing argument.'));
+ return;
+ }
+
+ ctools_include('context');
+ $plugin = ctools_get_argument($conf['name']);
+
+ $form['settings'] = array(
+ '#tree' => TRUE,
+ );
+
+ $form['identifier'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Context identifier'),
+ '#description' => t('This is the title of the context used to identify it later in the administrative process. This will never be shown to a user.'),
+ '#default_value' => $conf['identifier'],
+ );
+
+ if (!$plugin) {
+ // This should be impossible and thus never seen.
+ $form['error'] = array('#value' => t('Error: missing or invalid argument plugin %argument.', array('%argument', $argument)));
+ return;
+ }
+
+ if ($function = ctools_plugin_get_function($plugin, 'settings form')) {
+ $function($form, $form_state, $conf['settings']);
+ }
+
+ $form_state['plugin'] = $plugin;
+ return $form;
+}
+
+/**
+ * Validate handler for argument settings.
+ */
+function page_manager_page_argument_form_settings_validate(&$form, &$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form validate')) {
+ $function($form, $form_state);
+ }
+}
+
+/**
+ * Submit handler for argument settings.
+ */
+function page_manager_page_argument_form_settings_submit(&$form, &$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form submit')) {
+ $function($form, $form_state);
+ }
+
+ $page = &$form_state['page']->subtask['subtask'];
+ $keyword = &$form_state['keyword'];
+ // Copy the form to our temporary location which will get moved again when
+ // finished. Yes, finished is always next but finish can happen from other
+ // locations so we funnel through that path rather than duplicate.
+ $page->temporary_arguments[$keyword]['identifier'] = $form_state['values']['identifier'];
+ if (isset($form_state['values']['settings'])) {
+ $page->temporary_arguments[$keyword]['settings'] = $form_state['values']['settings'];
+ }
+ else {
+ $page->temporary_arguments[$keyword]['settings'] = array();
+ }
+}
+
+/**
+ * Import a task handler from cut & paste
+ */
+function page_manager_page_import_subtask($form, &$form_state, $task_name) {
+ $form_state['task'] = page_manager_get_task($task_name);
+
+ drupal_set_title(t('Import page'));
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Page name'),
+ '#description' => t('Enter the name to use for this page if it is different from the source page. Leave blank to use the original name of the page.'),
+ );
+
+ $form['path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Path'),
+ '#description' => t('Enter the path to use for this page if it is different from the source page. Leave blank to use the original path of the page.'),
+ );
+
+ $form['overwrite'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow overwrite of an existing page'),
+ '#description' => t('If the name you selected already exists in the database, this page will be allowed to overwrite the existing page.'),
+ );
+
+ $form['object'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Paste page code here'),
+ '#rows' => 15,
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import'),
+ );
+ return $form;
+}
+
+/**
+ * Ensure we got a valid page.
+ */
+function page_manager_page_import_subtask_validate(&$form, &$form_state) {
+ ob_start();
+ eval($form_state['values']['object']);
+ ob_end_clean();
+
+ if (!isset($page) || !is_object($page)) {
+ $errors = ob_get_contents();
+ if (empty($errors)) {
+ $errors = t('No handler found.');
+ }
+ form_error($form['object'], t('Unable to get a page from the import. Errors reported: @errors', array('@errors' => $errors)));
+ }
+
+ if (empty($form_state['values']['name'])) {
+ $form_state['values']['name'] = $page->name;
+ }
+
+ $task_name = page_manager_make_task_name('page', $form_state['values']['name']);
+ $form_state['cache'] = page_manager_get_page_cache($task_name);
+
+ if ($form_state['cache'] && $form_state['cache']->locked) {
+ form_error($form['name'], t('That page name is in use and locked by another user. You must <a href="!break">break the lock</a> on that page before proceeding, or choose a different name.', array('!break' => url(page_manager_edit_url($task_name, array('actions', 'break-lock'))))));
+ return;
+ }
+
+ if (empty($form_state['values']['path'])) {
+ $form_state['values']['path'] = $page->path;
+ }
+
+ if (empty($form_state['values']['overwrite'])) {
+ $page->name = NULL;
+ }
+
+ $form_state['page'] = new stdClass();
+ $form_state['page']->subtask['subtask'] = $page;
+ page_manager_page_form_basic_validate($form, $form_state);
+}
+
+/**
+ * Submit the import page to create the new page and redirect.
+ */
+function page_manager_page_import_subtask_submit($form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+ $page->name = $form_state['values']['name'];
+ $page->path = $form_state['values']['path'];
+
+ $task_name = page_manager_make_task_name('page', $page->name);
+ $cache = page_manager_get_page_cache($task_name);
+ if (!$cache) {
+ $cache = new stdClass();
+ }
+
+ page_manager_page_new_page_cache($page, $cache);
+ page_manager_set_page_cache($cache);
+
+ $form_state['redirect'] = page_manager_edit_url($task_name);
+}
+
+/**
+ * Entry point to export a page.
+ */
+function page_manager_page_form_export($form, &$form_state) {
+ $page = $form_state['page']->subtask['subtask'];
+
+ $export = page_manager_page_export($page, $form_state['page']->handlers);
+
+ $lines = substr_count($export, "\n");
+ $form['code'] = array(
+ '#type' => 'textarea',
+ '#default_value' => $export,
+ '#rows' => $lines,
+ );
+
+ unset($form['buttons']);
+ return $form;
+}
+
+/**
+ * Entry point to clone a page.
+ */
+function page_manager_page_form_clone($form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+
+ // This provides its own button because it does something totally different.
+ unset($form['buttons']);
+
+ $form['admin_title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Administrative title'),
+ '#description' => t('The name of this page. This will appear in the administrative interface to easily identify it.'),
+ '#default_value' => $page->admin_title,
+ );
+
+ $form['name'] = array(
+ '#type' => 'machine_name',
+ '#title' => t('Page name'),
+ '#machine_name' => array(
+ 'exists' => 'page_manager_page_load',
+ 'source' => array('admin_title'),
+ ),
+ '#description' => t('Enter the name to the new page It must be unique and contain only alphanumeric characters and underscores.'),
+ );
+
+ // path
+ $form['path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Path'),
+ '#description' => t('The URL path to get to this page. You may create named placeholders for variable parts of the path by using %name for required elements and !name for optional elements. For example: "node/%node/foo", "forum/%forum" or "dashboard/!input". These named placeholders can be turned into contexts on the arguments form. You cannot use the same path as the original page.'),
+ '#default_value' => $page->path,
+ );
+
+ $form['handlers'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Clone variants'),
+ '#description' => t('If checked all variants associated with the page will be cloned as well. If not checked the page will be cloned without variants.'),
+ '#default_value' => TRUE,
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Clone'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate clone page form.
+ */
+function page_manager_page_form_clone_validate(&$form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+
+ $page->old_name = $page->name;
+ $page->name = NULL;
+ page_manager_page_form_basic_validate($form, $form_state);
+}
+
+/**
+ * submit clone page form.
+ *
+ * Load the page, change the name(s) to protect the innocent, and if
+ * requested, load all the task handlers so that they get saved properly too.
+ */
+function page_manager_page_form_clone_submit(&$form, &$form_state) {
+ $original = $form_state['page']->subtask['subtask'];
+
+ $original->name = $form_state['values']['name'];
+ $original->admin_title = $form_state['values']['admin_title'];
+ $original->path = $form_state['values']['path'];
+
+ $handlers = !empty($form_state['values']['handlers']) ? $form_state['page']->handlers : FALSE;
+ // Export the handler, which is a fantastic way to clean database IDs out of it.
+ $export = page_manager_page_export($original, $handlers);
+ ob_start();
+ eval($export);
+ ob_end_clean();
+
+ $task_name = page_manager_make_task_name('page', $page->name);
+ $cache = new stdClass();
+
+ page_manager_page_new_page_cache($page, $cache);
+ page_manager_set_page_cache($cache);
+
+ $form_state['redirect'] = page_manager_edit_url($task_name);
+}
+
+/**
+ * Entry point to export a page.
+ */
+function page_manager_page_form_delete($form, &$form_state) {
+ $page = &$form_state['page']->subtask['subtask'];
+
+ if ($page->type == t('Overridden')) {
+ $text = t('Reverting the page will delete the page that is in the database, reverting it to the original default page. Any changes you have made will be lost and cannot be recovered.');
+ }
+ else {
+ $text = t('Are you sure you want to delete this page? Deleting a page cannot be undone.');
+ }
+ $form['markup'] = array(
+ '#value' => '<p>' . $text . '</p>',
+ );
+
+ if (empty($form_state['page']->locked)) {
+ unset($form['buttons']);
+ $form['delete'] = array(
+ '#type' => 'submit',
+ '#value' => $page->type == t('Overridden') ? t('Revert') : t('Delete'),
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Submit handler to delete a view.
+ */
+function page_manager_page_form_delete_submit(&$form, &$form_state) {
+ $page = $form_state['page']->subtask['subtask'];
+ page_manager_page_delete($page);
+ if ($page->type != t('Overridden')) {
+ $form_state['redirect'] = 'admin/structure/pages';
+ drupal_set_message(t('The page has been deleted.'));
+ }
+ else {
+ $form_state['redirect'] = page_manager_edit_url($form_state['page']->task_name, array('summary'));
+ drupal_set_message(t('The page has been reverted.'));
+ }
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/page.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/page.inc
new file mode 100644
index 000000000..6a8545d1a
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/page.inc
@@ -0,0 +1,787 @@
+<?php
+
+/**
+ * @file
+ * Handle the 'page' task, which creates pages with arbitrary tasks and lets
+ * handlers decide how they will be rendered.
+ *
+ * This creates subtasks and stores them in the page_manager_pages table. These
+ * are exportable objects, too.
+ *
+ * The render callback for this task type has $handler, $page, $contexts as
+ * parameters.
+ */
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_page_page_manager_tasks() {
+ return array(
+ 'title' => t('Custom pages'),
+ 'description' => t('Administrator created pages that have a URL path, access control and entries in the Drupal menu system.'),
+ 'non-exportable' => TRUE,
+ 'subtasks' => TRUE,
+ 'subtask callback' => 'page_manager_page_subtask',
+ 'subtasks callback' => 'page_manager_page_subtasks',
+ 'save subtask callback' => 'page_manager_page_save_subtask',
+ 'access callback' => 'page_manager_page_access_check',
+ 'hook menu' => array(
+ 'file' => 'page.admin.inc',
+ 'path' => drupal_get_path('module', 'page_manager') . '/plugins/tasks',
+ 'function' => 'page_manager_page_menu',
+ ),
+ 'hook theme' => 'page_manager_page_theme',
+ // page only items
+ 'task type' => 'page',
+ 'page operations' => array(
+ array(
+ 'title' => ' &raquo; ' . t('Create a new page'),
+ 'href' => 'admin/structure/pages/add',
+ 'html' => TRUE,
+ ),
+ ),
+ 'columns' => array(
+ 'storage' => array(
+ 'label' => t('Storage'),
+ 'class' => 'page-manager-page-storage',
+ ),
+ ),
+ 'page type' => 'custom',
+
+ // context only items
+ 'handler type' => 'context',
+ 'get arguments' => array(
+ 'file' => 'page.admin.inc',
+ 'path' => drupal_get_path('module', 'page_manager') . '/plugins/tasks',
+ 'function' => 'page_manager_page_get_arguments',
+ ),
+ 'get context placeholders' => 'page_manager_page_get_contexts',
+ 'access restrictions' => 'page_manager_page_access_restrictions',
+ 'uses handlers' => TRUE,
+ );
+}
+
+/**
+ * Task callback to get all subtasks.
+ *
+ * Return a list of all subtasks.
+ */
+function page_manager_page_subtasks($task) {
+ $pages = page_manager_page_load_all($task['name']);
+ $return = array();
+ foreach ($pages as $name => $page) {
+ $return[$name] = page_manager_page_build_subtask($task, $page);
+ }
+
+ return $return;
+}
+
+/**
+ * Callback to return a single subtask.
+ */
+function page_manager_page_subtask($task, $subtask_id) {
+ $page = page_manager_page_load($subtask_id);
+ if ($page) {
+ return page_manager_page_build_subtask($task, $page);
+ }
+}
+
+/**
+ * Call back from the administrative system to save a page.
+ *
+ * We get the $subtask as created by page_manager_page_build_subtask.
+ */
+function page_manager_page_save_subtask($subtask) {
+ $page = &$subtask['subtask'];
+
+ // Ensure $page->arguments contains only real arguments:
+ $arguments = page_manager_page_get_named_arguments($page->path);
+ $args = array();
+ foreach ($arguments as $keyword => $position) {
+ if (isset($page->arguments[$keyword])) {
+ $args[$keyword] = $page->arguments[$keyword];
+ }
+ else {
+ $args[$keyword] = array(
+ 'id' => '',
+ 'identifier' => '',
+ 'argument' => '',
+ 'settings' => array(),
+ );
+ }
+ }
+ page_manager_page_recalculate_arguments($page);
+ // Create a real object from the cache
+ page_manager_page_save($page);
+
+ // Check to see if we should make this the site frontpage.
+ if (!empty($page->make_frontpage)) {
+ $path = array();
+ foreach (explode('/', $page->path) as $bit) {
+ if ($bit[0] != '!') {
+ $path[] = $bit;
+ }
+ }
+
+ $path = implode('/', $path);
+ $front = variable_get('site_frontpage', 'node');
+ if ($path != $front) {
+ variable_set('site_frontpage', $path);
+ }
+ }
+}
+
+/**
+ * Build a subtask array for a given page.
+ */
+function page_manager_page_build_subtask($task, $page) {
+ $operations = array();
+ $operations['settings'] = array(
+ 'type' => 'group',
+ 'class' => array('operations-settings'),
+ 'title' => t('Settings'),
+ 'children' => array(),
+ );
+
+ $settings = &$operations['settings']['children'];
+
+ $settings['basic'] = array(
+ 'title' => t('Basic'),
+ 'description' => t('Edit name, path and other basic settings for the page.'),
+ 'form' => 'page_manager_page_form_basic',
+ );
+
+ $arguments = page_manager_page_get_named_arguments($page->path);
+ if ($arguments) {
+ $settings['argument'] = array(
+ 'title' => t('Arguments'),
+ 'description' => t('Set up contexts for the arguments on this page.'),
+ 'form' => 'page_manager_page_form_argument',
+ );
+ }
+
+ $settings['access'] = array(
+ 'title' => t('Access'),
+ 'description' => t('Control what users can access this page.'),
+ 'admin description' => t('Access rules are used to test if the page is accessible and any menu items associated with it are visible.'),
+ 'form' => 'page_manager_page_form_access',
+ );
+
+ $settings['menu'] = array(
+ 'title' => t('Menu'),
+ 'description' => t('Provide this page a visible menu or a menu tab.'),
+ 'form' => 'page_manager_page_form_menu',
+ );
+
+ $operations['actions']['children']['clone'] = array(
+ 'title' => t('Clone'),
+ 'description' => t('Make a copy of this page'),
+ 'form' => 'page_manager_page_form_clone',
+ );
+ $operations['actions']['children']['export'] = array(
+ 'title' => t('Export'),
+ 'description' => t('Export this page as code that can be imported or embedded into a module.'),
+ 'form' => 'page_manager_page_form_export',
+ );
+ if ($page->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) {
+ $operations['actions']['children']['delete'] = array(
+ 'title' => t('Revert'),
+ 'description' => t('Remove all changes to this page and revert to the version in code.'),
+ 'form' => 'page_manager_page_form_delete',
+ );
+ }
+ else if ($page->export_type != EXPORT_IN_CODE) {
+ $operations['actions']['children']['delete'] = array(
+ 'title' => t('Delete'),
+ 'description' => t('Remove this page from your system completely.'),
+ 'form' => 'page_manager_page_form_delete',
+ );
+ }
+
+ $subtask = array(
+ 'name' => $page->name,
+ 'admin title' => check_plain($page->admin_title),
+ 'admin description' => filter_xss_admin($page->admin_description),
+ 'admin summary' => 'page_manager_page_admin_summary',
+ 'admin path' => $page->path,
+ 'admin type' => t('Custom'),
+ 'subtask' => $page,
+ 'operations' => $operations,
+ 'operations include' => array(
+ 'file' => 'page.admin.inc',
+ 'path' => drupal_get_path('module', 'page_manager') . '/plugins/tasks',
+ ),
+ 'single task' => empty($page->multiple),
+ 'row class' => empty($page->disabled) ? 'page-manager-enabled' : 'page-manager-disabled',
+ 'storage' => $page->type == t('Default') ? t('In code') : $page->type,
+ 'disabled' => !empty($page->disabled),
+ // This works for both enable AND disable
+ 'enable callback' => 'page_manager_page_enable',
+ );
+
+ // default handlers may appear from a default subtask.
+ if (isset($page->default_handlers)) {
+ $subtask['default handlers'] = $page->default_handlers;
+ }
+ return $subtask;
+}
+
+/**
+ * Delegated implementation of hook_theme().
+ */
+function page_manager_page_theme(&$items, $task) {
+ $base = array(
+ 'file' => 'page.admin.inc',
+ 'path' => drupal_get_path('module', 'page_manager') . '/plugins/tasks',
+ );
+ $items['page_manager_page_form_argument_table'] = $base + array(
+ 'render element' => 'form',
+ );
+ $items['page_manager_page_lock'] = $base + array(
+ 'variables' => array('lock' => array(), 'task_name' => NULL),
+ );
+ $items['page_manager_page_changed'] = $base + array(
+ 'variables' => array(),
+ );
+}
+
+// --------------------------------------------------------------------------
+// Page execution functions
+
+/**
+ * Execute a page task.
+ *
+ * This is the callback to entries in the Drupal menu system created by the
+ * page task.
+ *
+ * @param $subtask_id
+ * The name of the page task used.
+ * @param ...
+ * A number of context objects as specified by the user when
+ * creating named arguments in the path.
+ */
+function page_manager_page_execute($subtask_id) {
+ $page = page_manager_page_load($subtask_id);
+ $task = page_manager_get_task($page->task);
+ $subtask = page_manager_get_task_subtask($task, $subtask_id);
+
+ // Turn the contexts into a properly keyed array.
+ $contexts = array();
+ $args = array();
+ foreach (func_get_args() as $count => $arg) {
+ if (is_object($arg) && get_class($arg) == 'ctools_context') {
+ $contexts[$arg->id] = $arg;
+ $args[] = $arg->original_argument;
+ }
+ else if ($count) {
+ $args[] = $arg;
+ }
+ }
+
+ $count = 0;
+ $names = page_manager_page_get_named_arguments($page->path);
+ $bits = explode('/', $page->path);
+
+ if ($page->arguments) {
+ foreach ($page->arguments as $name => $argument) {
+ // Optional arguments must be converted to contexts too, if they exist.
+ if ($bits[$names[$name]][0] == '!') {
+ ctools_include('context');
+ $argument['keyword'] = $name;
+ if (isset($args[$count])) {
+ // Hack: use a special argument config variable to learn if we need
+ // to use menu_tail style behavior:
+ if (empty($argument['settings']['use_tail'])) {
+ $value = $args[$count];
+ }
+ else {
+ $value = implode('/', array_slice($args, $count));
+ }
+
+ $context = ctools_context_get_context_from_argument($argument, $value);
+ }
+ else {
+ // make sure there is a placeholder context for missing optional contexts.
+ $context = ctools_context_get_context_from_argument($argument, NULL, TRUE);
+ // Force the title to blank for replacements
+ }
+ if ($context) {
+ $contexts[$context->id] = $context;
+ }
+ }
+ $count++;
+ }
+ }
+
+ if ($function = ctools_plugin_get_function($task, 'page callback')) {
+ return call_user_func_array($function, array($page, $contexts, $args));
+ }
+
+ ctools_include('context-task-handler');
+ $output = ctools_context_handler_render($task, $subtask, $contexts, $args);
+ if ($output === FALSE) {
+ return MENU_NOT_FOUND;
+ }
+
+ return $output;
+}
+
+// --------------------------------------------------------------------------
+// Context type callbacks
+
+/**
+ * Return a list of arguments used by this task.
+ */
+function page_manager_page_get_arguments($task, $subtask) {
+ return _page_manager_page_get_arguments($subtask['subtask']);
+}
+
+function _page_manager_page_get_arguments($page) {
+ $arguments = array();
+ if (!empty($page->arguments)) {
+ foreach ($page->arguments as $keyword => $argument) {
+ if (isset($argument['name'])) {
+ $argument['keyword'] = $keyword;
+ $arguments[$keyword] = $argument;
+ }
+ }
+ }
+ return $arguments;
+}
+
+/**
+ * Get a group of context placeholders for the arguments.
+ */
+function page_manager_page_get_contexts($task, $subtask) {
+ ctools_include('context');
+ return ctools_context_get_placeholders_from_argument(page_manager_page_get_arguments($task, $subtask));
+}
+
+/**
+ * Return a list of arguments used by this task.
+ */
+function page_manager_page_access_restrictions($task, $subtask, $contexts) {
+ $page = $subtask['subtask'];
+ return ctools_access_add_restrictions($page->access, $contexts);
+}
+
+// --------------------------------------------------------------------------
+// Page task database info.
+
+/**
+ * Create a new page with defaults appropriately set from schema.
+ */
+function page_manager_page_new() {
+ ctools_include('export');
+ return ctools_export_new_object('page_manager_pages');
+}
+
+/**
+ * Load a single page subtask.
+ */
+function page_manager_page_load($name) {
+ ctools_include('export');
+ $result = ctools_export_load_object('page_manager_pages', 'names', array($name));
+ if (isset($result[$name])) {
+ return $result[$name];
+ }
+}
+
+/**
+ * Load all page subtasks.
+ */
+function page_manager_page_load_all($task = NULL) {
+ ctools_include('export');
+
+ if (empty($task)) {
+ return ctools_export_load_object('page_manager_pages');
+ }
+ else {
+ return ctools_export_load_object('page_manager_pages', 'conditions', array('task' => $task));
+ }
+}
+
+/**
+ * Write a page subtask to the database.
+ */
+function page_manager_page_save(&$page) {
+ $update = (isset($page->pid)) ? array('pid') : array();
+ $task = page_manager_get_task($page->task);
+
+ if ($function = ctools_plugin_get_function($task, 'save')) {
+ $function($page, $update);
+ }
+ drupal_write_record('page_manager_pages', $page, $update);
+
+ // If this was a default page we may need to write default task
+ // handlers that we provided as well.
+ if (!$update && isset($page->default_handlers)) {
+ $handlers = page_manager_load_task_handlers(page_manager_get_task('page'), $page->name);
+ foreach ($page->default_handlers as $name => $handler) {
+ if (!isset($handlers[$name]) || !($handlers[$name]->export_type & EXPORT_IN_DATABASE)) {
+ // Make sure this is right, as exports can wander a bit.
+ $handler->subtask = $page->name;
+ page_manager_save_task_handler($handler);
+ }
+ }
+ }
+ return $page;
+}
+
+/**
+ * Remove a page subtask.
+ */
+function page_manager_page_delete($page) {
+ $task = page_manager_get_task($page->task);
+ if ($function = ctools_plugin_get_function($task, 'delete')) {
+ $function($page);
+ }
+ if (!empty($task['uses handlers'])) {
+ $handlers = page_manager_load_task_handlers($task, $page->name);
+ foreach ($handlers as $handler) {
+ page_manager_delete_task_handler($handler);
+ }
+ }
+ db_delete('page_manager_pages')
+ ->condition('name', $page->name)
+ ->execute();
+ // Make sure that the cache is reset so that the menu rebuild does not
+ // rebuild this page again.
+ ctools_include('export');
+ ctools_export_load_object_reset('page_manager_pages');
+ menu_rebuild();
+}
+
+/**
+ * Export a page subtask.
+ */
+function page_manager_page_export($page, $with_handlers = FALSE, $indent = '') {
+ $task = page_manager_get_task($page->task);
+ $append = '';
+
+ if ($function = ctools_plugin_get_function($task, 'export')) {
+ $append = $function($page, $indent);
+ }
+
+ ctools_include('export');
+ $output = ctools_export_object('page_manager_pages', $page, $indent);
+ $output .= $append;
+
+ if ($with_handlers) {
+ if (is_array($with_handlers)) {
+ $handlers = $with_handlers;
+ }
+ else {
+ $handlers = page_manager_load_task_handlers(page_manager_get_task('page'), $page->name);
+ }
+ $output .= $indent . '$page->default_handlers = array();' . "\n";
+ foreach ($handlers as $handler) {
+ $output .= page_manager_export_task_handler($handler, $indent);
+ $output .= $indent . '$page->default_handlers[$handler->name] = $handler;' . "\n";
+ }
+ }
+ return $output;
+}
+
+/**
+ * Get a list of named arguments in a page manager path.
+ *
+ * @param $path
+ * A normal Drupal path.
+ *
+ * @return
+ * An array of % marked variable arguments, keyed by the argument's name.
+ * The value will be the position of the argument so that it can easily
+ * be found. Items with a position of -1 have multiple positions.
+ */
+function page_manager_page_get_named_arguments($path) {
+ $arguments = array();
+ $bits = explode('/', $path);
+ foreach ($bits as $position => $bit) {
+ if ($bit && ($bit[0] == '%' || $bit[0] == '!')) {
+ // special handling for duplicate path items and substr to remove the %
+ $arguments[substr($bit, 1)] = isset($arguments[$bit]) ? -1 : $position;
+ }
+ }
+
+ return $arguments;
+}
+
+/**
+ * Load a context from an argument for a given page task.
+ *
+ * Helper function for pm_arg_load(), which is in page_manager.module because
+ * drupal's menu system does not allow loader functions to reside in separate
+ * files.
+ *
+ * @param $value
+ * The incoming argument value.
+ * @param $subtask
+ * The subtask id.
+ * @param $argument
+ * The numeric position of the argument in the path, counting from 0.
+ *
+ * @return
+ * A context item if one is configured, the argument if one is not, or
+ * FALSE if restricted or invalid.
+ */
+function _pm_arg_load($value, $subtask, $argument) {
+ $page = page_manager_page_load($subtask);
+ if (!$page) {
+ return FALSE;
+ }
+
+ $path = explode('/', $page->path);
+ if (empty($path[$argument])) {
+ return FALSE;
+ }
+
+ $keyword = substr($path[$argument], 1);
+ if (empty($page->arguments[$keyword])) {
+ return $value;
+ }
+
+ $page->arguments[$keyword]['keyword'] = $keyword;
+
+ ctools_include('context');
+ $context = ctools_context_get_context_from_argument($page->arguments[$keyword], $value);
+
+ // convert false equivalents to false.
+ return $context ? $context : FALSE;
+}
+
+/**
+ * Provide a nice administrative summary of the page so an admin can see at a
+ * glance what this page does and how it is configured.
+ */
+function page_manager_page_admin_summary($task, $subtask) {
+ $task_name = page_manager_make_task_name($task['name'], $subtask['name']);
+ $page = $subtask['subtask'];
+ $output = '';
+
+ $rows = array();
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Storage')),
+ array('class' => array('page-summary-data'), 'data' => $subtask['storage']),
+ array('class' => array('page-summary-operation'), 'data' => ''),
+ );
+
+ if (!empty($page->disabled)) {
+ $link = l(t('Enable'), page_manager_edit_url($task_name, array('handlers', $page->name, 'actions', 'enable')));
+ $text = t('Disabled');
+ }
+ else {
+ $link = l(t('Disable'), page_manager_edit_url($task_name, array('handlers', $page->name, 'actions', 'disable')));
+ $text = t('Enabled');
+ }
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Status')),
+ array('class' => array('page-summary-data'), 'data' => $text),
+ array('class' => array('page-summary-operation'), 'data' => $link),
+ );
+
+
+ $path = array();
+ foreach (explode('/', $page->path) as $bit) {
+ if ($bit[0] != '!') {
+ $path[] = $bit;
+ }
+ }
+
+ $path = implode('/', $path);
+ $front = variable_get('site_frontpage', 'node');
+
+ $link = l(t('Edit'), page_manager_edit_url($task_name, array('settings', 'basic')));
+ $message = '';
+ if ($path == $front) {
+ $message = t('This is your site home page.');
+ }
+ else if (!empty($page->make_frontpage)) {
+ $message = t('This page is set to become your site home page.');
+ }
+
+ if ($message) {
+ $rows[] = array(
+ array('class' => array('page-summary-data'), 'data' => $message, 'colspan' => 2),
+ array('class' => array('page-summary-operation'), 'data' => $link),
+ );
+ }
+
+ if (strpos($path, '%') === FALSE) {
+ $path = l('/' . $page->path, $path);
+ }
+ else {
+ $path = '/' . $page->path;
+ }
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Path')),
+ array('class' => array('page-summary-data'), 'data' => $path),
+ array('class' => array('page-summary-operation'), 'data' => $link),
+ );
+
+ if (empty($access['plugins'])) {
+ $access['plugins'] = array();
+ }
+
+ $contexts = page_manager_page_get_contexts($task, $subtask);
+ $access = ctools_access_group_summary($page->access, $contexts);
+ if ($access) {
+ $access = t('Accessible only if @conditions.', array('@conditions' => $access));
+ }
+ else {
+ $access = t('This page is publicly accessible.');
+ }
+
+ $link = l(t('Edit'), page_manager_edit_url($task_name, array('settings', 'access')));
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Access')),
+ array('class' => array('page-summary-data'), 'data' => $access),
+ array('class' => array('page-summary-operation'), 'data' => $link),
+ );
+
+ $menu_options = array(
+ 'none' => t('No menu entry.'),
+ 'normal' => t('Normal menu entry.'),
+ 'tab' => t('Menu tab.'),
+ 'default tab' => t('Default menu tab.'),
+ 'action' => t('Local action'),
+ );
+
+ if (!empty($page->menu)) {
+ $menu = $menu_options[$page->menu['type']];
+ if ($page->menu['type'] != 'none') {
+ $menu .= ' ' . t('Title: %title.', array('%title' => $page->menu['title']));
+ switch ($page->menu['type']) {
+ case 'default tab':
+ $menu .= ' ' . t('Parent title: %title.', array('%title' => $page->menu['parent']['title']));
+ break;
+ case 'normal':
+ if (module_exists('menu')) {
+ $menus = menu_get_menus();
+ $menu .= ' ' . t('Menu block: %title.', array('%title' => $menus[$page->menu['name']]));
+ }
+ break;
+ }
+ }
+ }
+ else {
+ $menu = t('No menu entry');
+ }
+
+ $link = l(t('Edit'), page_manager_edit_url($task_name, array('settings', 'menu')));
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Menu')),
+ array('class' => array('page-summary-data'), 'data' => $menu),
+ array('class' => array('page-summary-operation'), 'data' => $link),
+ );
+
+ $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'page-manager-page-summary')));
+ return $output;
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_page_enable(&$cache, $status) {
+ $page = &$cache->subtask['subtask'];
+ ctools_include('export');
+ ctools_export_set_object_status($page, $status);
+
+ $page->disabled = FALSE;
+}
+
+/**
+ * Recalculate the arguments when something like the path changes.
+ */
+function page_manager_page_recalculate_arguments(&$page) {
+ // Ensure $page->arguments contains only real arguments:
+ $arguments = page_manager_page_get_named_arguments($page->path);
+ $args = array();
+ foreach ($arguments as $keyword => $position) {
+ if (isset($page->arguments[$keyword])) {
+ $args[$keyword] = $page->arguments[$keyword];
+ }
+ else {
+ $args[$keyword] = array(
+ 'id' => '',
+ 'identifier' => '',
+ 'argument' => '',
+ 'settings' => array(),
+ );
+ }
+ }
+ $page->arguments = $args;
+}
+
+/**
+ * When adding or cloning a new page, this creates a new page cache
+ * and adds our page to it.
+ *
+ * This does not check to see if the existing cache is already locked.
+ * This must be done beforehand.
+ *
+ * @param &$page
+ * The page to create.
+ * @param &$cache
+ * The cache to use. If the cache has any existing task handlers,
+ * they will be marked for deletion. This may be a blank object.
+ */
+function page_manager_page_new_page_cache(&$page, &$cache) {
+ // Does a page already exist? If so, we are overwriting it so
+ // take its pid.
+ if (!empty($cache->subtask) && !empty($cache->subtask['subtask']) && !empty($cache->subtask['subtask']->pid)) {
+ $page->pid = $cache->subtask['subtask']->pid;
+ }
+ else {
+ $cache->new = TRUE;
+ }
+
+ $cache->task_name = page_manager_make_task_name('page', $page->name);
+ $cache->task_id = 'page';
+ $cache->task = page_manager_get_task('page');
+ $cache->subtask_id = $page->name;
+ $page->export_type = EXPORT_IN_DATABASE;
+ $page->type = t('Normal');
+ $cache->subtask = page_manager_page_build_subtask($cache->task, $page);
+
+ if (isset($cache->handlers)) {
+ foreach($cache->handlers as $id => $handler) {
+ $cache->handler_info[$id]['changed'] = PAGE_MANAGER_CHANGED_DELETED;
+ }
+ }
+ else {
+ $cache->handlers = array();
+ $cache->handler_info = array();
+ }
+
+ if (!empty($page->default_handlers)) {
+ foreach ($page->default_handlers as $id => $handler) {
+ page_manager_handler_add_to_page($cache, $handler);
+ }
+ }
+
+ $cache->locked = FALSE;
+ $cache->changed = TRUE;
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_page_access_check($task, $subtask_id, $contexts) {
+ $page = page_manager_page_load($subtask_id);
+ return ctools_access($page->access, $contexts);
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/poll.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/poll.inc
new file mode 100644
index 000000000..073ee0c60
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/poll.inc
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_poll_page_manager_tasks() {
+ if (!module_exists('poll')) {
+ return;
+ }
+
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+
+ 'title' => t('All polls'),
+ 'admin title' => t('All polls'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for the polls at <em>/poll</em>. If no variant is selected, the default Drupal most recent polls will be shown.'),
+ 'admin path' => 'poll',
+
+ // Menu hooks so that we can alter the node/%node menu entry to point to us.
+ 'hook menu alter' => 'page_manager_poll_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_poll_disabled', TRUE),
+ 'enable callback' => 'page_manager_poll_enable',
+ 'access callback' => 'page_manager_poll_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_poll_page_manager_tasks().
+ *
+ * Alter the node edit input so that node edit comes to us rather than the
+ * normal node edit process.
+ */
+function page_manager_poll_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_poll_disabled', TRUE)) {
+ return;
+ }
+
+ $callback = $items['poll']['page callback'];
+ // Override the node edit handler for our purpose.
+ if ($callback == 'poll_page' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['poll']['page callback'] = 'page_manager_poll';
+ $items['poll']['file path'] = $task['path'];
+ $items['poll']['file'] = $task['file'];
+ }
+ else {
+ variable_set('page_manager_poll_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_poll'])) {
+ drupal_set_message(t('Page manager module is unable to enable poll because some other module already has overridden with %callback.', array('%callback' => $callback)), 'warning');
+ }
+ return;
+ }
+
+}
+
+/**
+ * Entry point for our overridden node edit.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * node edit, which is node_page_edit().
+ */
+function page_manager_poll() {
+ // Load my task plugin
+ $task = page_manager_get_task('poll');
+
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $output = ctools_context_handler_render($task, '', array(), array());
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ module_load_include('inc', 'poll', 'poll.pages');
+ $function = 'poll_page';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('poll')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+ return $function();
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_poll_enable($cache, $status) {
+ variable_set('page_manager_poll_disabled', $status);
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_poll'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_poll_access_check($task, $subtask_id, $contexts) {
+ return user_access('access content');
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/search.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/search.inc
new file mode 100644
index 000000000..efd7415c4
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/search.inc
@@ -0,0 +1,249 @@
+<?php
+
+/**
+ * @file
+ * Handle the 'node view' override task.
+ *
+ * This plugin overrides node/%node and reroutes it to the page manager, where
+ * a list of tasks can be used to service this request based upon criteria
+ * supplied by access plugins.
+ */
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_search_page_manager_tasks() {
+ if (!module_exists('search')) {
+ return;
+ }
+
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+ 'title' => t('Search'),
+
+ // There are multiple search pages, let's override each of them
+ // separately.
+ 'subtasks' => TRUE,
+ 'subtask callback' => 'page_manager_search_subtask',
+ 'subtasks callback' => 'page_manager_search_subtasks',
+
+ // Menu hooks so that we can alter the node/%node menu entry to point to us.
+ 'hook menu alter' => 'page_manager_search_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+ 'get arguments' => 'page_manager_search_get_arguments',
+ 'get context placeholders' => 'page_manager_search_get_contexts',
+ 'access callback' => 'page_manager_search_access_check',
+
+ );
+}
+
+/**
+ * Callback defined by page_manager_search_page_manager_tasks().
+ *
+ * Alter the search tabs to work with page manager. The search flow is
+ * quite odd, and tracing through the code takes hours to realize
+ * that the tab you click on does not normally actually handle
+ * the search. This tries to account for that.
+ *
+ * Note to module authors: This tends to work a lot better with modules
+ * that override their own search pages if their _alter runs *before*
+ * this one.
+ */
+function page_manager_search_menu_alter(&$items, $task) {
+ // We are creating two sets of tabs. One set is for searching without
+ // keywords. A second set is for searching *with* keywords. This
+ // is necessary because search/node/% and search/node need to be
+ // different due to the way the search menu items function.
+
+ $default_info = search_get_default_module_info();
+ if (empty($default_info)) {
+ // Nothing to do.
+ return;
+ }
+
+ // Go through each search module item.
+ foreach (search_get_info() as $module => $info) {
+ if (variable_get('page_manager_search_disabled_' . $module, TRUE)) {
+ continue;
+ }
+
+ $path = 'search/' . $info['path'];
+ $callback = $items["$path/%menu_tail"]['page callback'];
+
+ if ($callback == 'search_view' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items["$path"]['page callback'] = 'page_manager_search_page';
+ $items["$path"]['file path'] = $task['path'];
+ $items["$path"]['file'] = $task['file'];
+
+ $items["$path/%menu_tail"]['page callback'] = 'page_manager_search_page';
+ $items["$path/%menu_tail"]['file path'] = $task['path'];
+ $items["$path/%menu_tail"]['file'] = $task['file'];
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('page_manager_search_disabled_' . $module, TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_search'])) {
+ drupal_set_message(t('Page manager module is unable to enable @path because some other module already has overridden with %callback.', array('%callback' => $callback, '@path' => $path)), 'error');
+ }
+ }
+ }
+}
+
+/**
+ * Entry point for our overridden search page.
+ *
+ */
+function page_manager_search_page($type) {
+ ctools_include('menu');
+// menu_set_active_trail(ctools_get_menu_trail('search/' . $type));
+
+ // Get the arguments and construct a keys string out of them.
+ $args = func_get_args();
+
+ // We have to remove the $type.
+ array_shift($args);
+
+ // And implode() it all back together.
+ $keys = $args ? implode('/', $args) : '';
+
+ // Allow other modules to alter the search keys
+ drupal_alter(array('search_keys', 'search_'. $type .'_keys'), $keys);
+
+ // Load my task plugin
+ $task = page_manager_get_task('search');
+ $subtask = page_manager_get_task_subtask($task, $type);
+
+ // Load the node into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, $subtask, array($keys));
+
+ $output = ctools_context_handler_render($task, $subtask, $contexts, array($keys));
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ $function = 'search_view';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('search')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+
+ // Put the $type back on the arguments.
+ module_load_include('inc', 'search', 'search.pages');
+ array_unshift($args, $type);
+ return call_user_func_array($function, $args);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the node view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_search_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'keywords',
+ 'identifier' => t('Keywords'),
+ 'id' => 1,
+ 'name' => 'string',
+ 'settings' => array('use_tail' => TRUE),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_search_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_search_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_search_enable($cache, $status) {
+ variable_set('page_manager_search_disabled_' . $cache->subtask_id, $status);
+
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_search'] = TRUE;
+ }
+}
+
+/**
+ * Task callback to get all subtasks.
+ *
+ * Return a list of all subtasks.
+ */
+function page_manager_search_subtasks($task) {
+ $return = array();
+ foreach (search_get_info() as $module => $info) {
+ if ($info['path']) {
+ // We don't pass the $info because the subtask build could be called
+ // singly without the $info when just the subtask needs to be built.
+ $return[$module] = page_manager_search_build_subtask($task, $module);
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Callback to return a single subtask.
+ */
+function page_manager_search_subtask($task, $subtask_id) {
+ return page_manager_search_build_subtask($task, $subtask_id);
+}
+
+/**
+ * Build a subtask array for a given page.
+ */
+function page_manager_search_build_subtask($task, $module) {
+ $search_info = search_get_info();
+ $info = $search_info[$module];
+ $path = 'search/' . $info['path'];
+ $subtask = array(
+ 'name' => $module,
+ 'admin title' => $info['title'],
+ 'admin path' => "$path/!keywords",
+ 'admin description' => t('Search @type', array('@type' => $info['title'])),
+ 'admin type' => t('System'),
+ 'row class' => empty($page->disabled) ? 'page-manager-enabled' : 'page-manager-disabled',
+ 'storage' => t('In code'),
+ 'disabled' => variable_get('page_manager_search_disabled_' . $module, TRUE),
+ // This works for both enable AND disable
+ 'enable callback' => 'page_manager_search_enable',
+ );
+
+ return $subtask;
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_search_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return _search_menu_access($context->data);
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc
new file mode 100644
index 000000000..37259b95b
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc
@@ -0,0 +1,377 @@
+<?php
+
+/**
+ * @file
+ * Handle the 'term view' override task.
+ *
+ * This plugin overrides term/%term and reroutes it to the page manager, where
+ * a list of tasks can be used to service this request based upon criteria
+ * supplied by access plugins.
+ */
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_term_view_page_manager_tasks() {
+ if (module_exists('taxonomy')) {
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+
+ 'title' => t('Taxonomy term template'),
+ 'admin title' => t('Taxonomy term template'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying taxonomy terms at <em>taxonomy/term/%term</em>. If you add variants, you may use selection criteria such as vocabulary or user access to provide different displays of the taxonomy term and associated nodes. If no variant is selected, the default Drupal taxonomy term display will be used. This page only affects items actually displayed ad taxonomy/term/%term. Some taxonomy terms, such as forums, have their displays moved elsewhere. Also please note that if you are using pathauto, aliases may make a taxonomy terms appear somewhere else, but as far as Drupal is concerned, they are still at taxonomy/term/%term.'),
+ 'admin path' => 'taxonomy/term/%taxonomy_term',
+ 'admin summary' => 'page_manager_term_view_admin_summary',
+
+ // Menu hooks so that we can alter the term/%term menu entry to point to us.
+ 'hook menu' => 'page_manager_term_view_menu',
+ 'hook menu alter' => 'page_manager_term_view_menu_alter',
+
+ // Provide a setting to the primary settings UI for Panels
+ 'admin settings' => 'page_manager_term_view_admin_settings',
+ // Even though we don't have subtasks, this allows us to save our settings.
+ 'save subtask callback' => 'page_manager_term_view_save',
+
+ // Callback to add items to the page manager task administration form:
+ 'task admin' => 'page_manager_term_view_task_admin',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+ 'get arguments' => 'page_manager_term_view_get_arguments',
+ 'get context placeholders' => 'page_manager_term_view_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_term_view_disabled', TRUE),
+ 'enable callback' => 'page_manager_term_view_enable',
+ 'access callback' => 'page_manager_term_view_access_check',
+
+ // Allow additional operations
+ 'operations' => array(
+ 'settings' => array(
+ 'title' => t('Settings'),
+ 'description' => t('Edit name, path and other basic settings for the page.'),
+ 'form' => 'page_manager_term_view_settings',
+ ),
+ ),
+ );
+ }
+}
+
+/**
+ * Callback defined by page_manager_term_view_page_manager_tasks().
+ *
+ * Alter the term view input so that term view comes to us rather than the
+ * normal term view process.
+ */
+function page_manager_term_view_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_term_view_disabled', TRUE)) {
+ return;
+ }
+
+ // Override the term view handler for our purpose, but only if someone else
+ // has not already done so.
+ if (isset($items['taxonomy/term/%taxonomy_term']) && $items['taxonomy/term/%taxonomy_term']['page callback'] == 'taxonomy_term_page' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['taxonomy/term/%taxonomy_term']['page callback'] = 'page_manager_term_view_page';
+ $items['taxonomy/term/%taxonomy_term']['file path'] = $task['path'];
+ $items['taxonomy/term/%taxonomy_term']['file'] = $task['file'];
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('page_manager_term_view_disabled', TRUE);
+
+ if (isset($items['taxonomy/term/%taxonomy_term']['page callback'])) {
+ $callback = $items['taxonomy/term/%taxonomy_term']['page callback'];
+ }
+ // Because Views changes %taxonomy_term to %views_arg, check to see if that
+ // is why we can't enable:
+ else if (isset($items['taxonomy/term/%views_arg']['page callback'])) {
+ $callback = $items['taxonomy/term/%views_arg']['page callback'];
+ }
+ else {
+ $callback = t('an unknown callback');
+ }
+ if (!empty($GLOBALS['page_manager_enabling_term_view'])) {
+ drupal_set_message(t('Page manager module is unable to enable taxonomy/term/%taxonomy_term because some other module already has overridden with %callback.', array('%callback' => $callback)), 'error');
+ }
+ }
+}
+
+/**
+ * Entry point for our overridden term view.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * term view, which is term_page_view().
+ */
+function page_manager_term_view_page($term, $depth = NULL) {
+ // Prep the term to be displayed so all of the regular hooks are triggered.
+ // Rather than calling taxonomy_term_page() directly, as it that would
+ // potentially load nodes that were not necessary, execute some of the code
+ // prior to identifying the correct CTools or Page Manager task handler and
+ // only proceed with the rest of the code if necessary.
+
+ // Assign the term name as the page title.
+ drupal_set_title($term->name);
+
+ // If there is a menu link to this term, the link becomes the last part
+ // of the active trail, and the link name becomes the page title.
+ // Thus, we must explicitly set the page title to be the node title.
+ $uri = entity_uri('taxonomy_term', $term);
+
+ // Set the term path as the canonical URL to prevent duplicate content.
+ drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
+ // Set the non-aliased path as a default shortlink.
+ drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
+
+ // Trigger the main
+ $build = taxonomy_term_show($term);
+
+ // Load my task plugin
+ $task = page_manager_get_task('term_view');
+
+ // Load the term into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($term, $depth));
+
+ if (empty($contexts)) {
+ return MENU_NOT_FOUND;
+ }
+
+ // Build the full output using the configured CTools plugin.
+ $output = ctools_context_handler_render($task, '', $contexts, array($term->tid));
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ // Try loading an override plugin.
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('term_view')) && function_exists($rc)) {
+ return $rc($term, $depth);
+ }
+ }
+
+ // Otherwise, fall back to replicating the output normally generated by
+ // taxonomy_term_page().
+
+ // Build breadcrumb based on the hierarchy of the term.
+ $current = (object) array(
+ 'tid' => $term->tid,
+ );
+ // @todo This overrides any other possible breadcrumb and is a pure hard-coded
+ // presumption. Make this behavior configurable per vocabulary or term.
+ $breadcrumb = array();
+ while ($parents = taxonomy_get_parents($current->tid)) {
+ $current = array_shift($parents);
+ $breadcrumb[] = l($current->name, 'taxonomy/term/' . $current->tid);
+ }
+ $breadcrumb[] = l(t('Home'), NULL);
+ $breadcrumb = array_reverse($breadcrumb);
+ drupal_set_breadcrumb($breadcrumb);
+ drupal_add_feed('taxonomy/term/' . $term->tid . '/feed', 'RSS - ' . $term->name);
+
+ if ($nids = taxonomy_select_nodes($term->tid, TRUE, variable_get('default_nodes_main', 10))) {
+ $nodes = node_load_multiple($nids);
+ $build += node_view_multiple($nodes);
+ $build['pager'] = array(
+ '#theme' => 'pager',
+ '#weight' => 5,
+ );
+ }
+ else {
+ $build['no_content'] = array(
+ '#prefix' => '<p>',
+ '#markup' => t('There is currently no content classified with this term.'),
+ '#suffix' => '</p>',
+ );
+ }
+ return $build;
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the term view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_term_view_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'term',
+ 'identifier' => page_manager_term_view_get_type() == 'multiple' ? t('Term(s) being viewed') : t('Term being viewed'),
+ 'id' => 1,
+ 'name' => page_manager_term_view_get_type() == 'multiple' ? 'terms' : 'term',
+ 'settings' => array('input_form' => 'tid', 'breadcrumb' => variable_get('page_manager_taxonomy_breadcrumb', TRUE)),
+ 'default' => '404',
+ ),
+ array(
+ 'keyword' => 'depth',
+ 'identifier' => t('Depth'),
+ 'id' => 1,
+ 'name' => 'string',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_term_view_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_term_view_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Settings page for this item.
+ */
+function page_manager_term_view_settings($form, &$form_state) {
+ // This passes thru because the setting can also appear on the main Panels
+ // settings form. If $settings is an array it will just pick up the default.
+ $settings = isset($form_state->update_values) ? $form_state->update_values : array();
+ return page_manager_term_view_admin_settings($form, $settings);
+}
+
+/**
+ * Copy form values into the page cache.
+ */
+function page_manager_term_view_settings_submit(&$form, &$form_state) {
+ $form_state['page']->update_values = $form_state['values'];
+}
+
+/**
+ * Save when the page cache is saved.
+ */
+function page_manager_term_view_save($subtask, $cache) {
+ if (isset($cache->update_values)) {
+ variable_set('page_manager_term_view_type', $cache->update_values['page_manager_term_view_type']);
+ variable_set('page_manager_taxonomy_breadcrumb', $cache->update_values['page_manager_taxonomy_breadcrumb']);
+ }
+}
+
+/**
+ * Provide a setting to the Panels administrative form.
+ */
+function page_manager_term_view_admin_settings($form, $settings = array()) {
+ if (empty($settings)) {
+ $settings = array(
+ 'page_manager_term_view_type' => page_manager_term_view_get_type(),
+ 'page_manager_taxonomy_breadcrumb' => variable_get('page_manager_taxonomy_breadcrumb', TRUE),
+ );
+ }
+
+ $form['page_manager_term_view_type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Allow multiple terms on taxonomy/term/%term'),
+ '#options' => array('single' => t('Single term'), 'multiple' => t('Multiple terms')),
+ '#description' => t('By default, Drupal allows multiple terms as an argument by separating them with commas or plus signs. If you set this to single, that feature will be disabled.') . ' ' . t('This feature does not currently work and is disabled.'),
+ '#default_value' => $settings['page_manager_term_view_type'],
+ // @todo -- fix this
+ '#disabled' => TRUE,
+ );
+ $form['page_manager_taxonomy_breadcrumb'] = array(
+ '#title' => t('Inject hierarchy of first term into breadcrumb trail'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['page_manager_taxonomy_breadcrumb'],
+ '#description' => t('If checked, taxonomy term parents will appear in the breadcrumb trail.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_term_view_enable($cache, $status) {
+ variable_set('page_manager_term_view_disabled', $status);
+
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_term_view'] = TRUE;
+ }
+}
+
+function page_manager_term_view_get_type() {
+// $view_type = variable_get('page_manager_term_view_type', 'multiple');
+ // Revert to just allowing single.
+ $view_type = 'single';
+
+ return $view_type;
+}
+
+/**
+ * Provide a nice administrative summary of the page so an admin can see at a
+ * glance what this page does and how it is configured.
+ */
+function page_manager_term_view_admin_summary($task, $subtask) {
+ $task_name = page_manager_make_task_name($task['name'], $subtask['name']);
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Path')),
+ array('class' => array('page-summary-data'), 'data' => 'taxonomy/term/%term'),
+ array('class' => array('page-summary-operation'), 'data' => ''),
+ );
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Access')),
+ array('class' => array('page-summary-data'), 'data' => t('This page is publicly accessible.')),
+ array('class' => array('page-summary-operation'), 'data' => ''),
+ );
+
+ $menu = t('No menu entry');
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Menu')),
+ array('class' => array('page-summary-data'), 'data' => $menu),
+ array('class' => array('page-summary-operation'), 'data' => ''),
+ );
+
+ if (page_manager_term_view_get_type() == 'multiple') {
+ $message = t('Multiple terms may be used, separated by , or +.');
+ }
+ else {
+ $message = t('Only a single term may be used.');
+ }
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('%term')),
+ array('class' => array('page-summary-data'), 'data' => $message),
+ array('class' => array('page-summary-operation'), 'data' => ''),
+ );
+
+ if (variable_get('page_manager_taxonomy_breadcrumb', TRUE)) {
+ $message = t('Breadcrumb trail will contain taxonomy term hierarchy');
+ }
+ else {
+ $message = t('Breadcrumb trail will not contain taxonomy term hiearchy.');
+ }
+
+ $rows[] = array(
+ array('class' => array('page-summary-label'), 'data' => t('Breadcrumb')),
+ array('class' => array('page-summary-data'), 'data' => $message),
+ array('class' => array('page-summary-operation'), 'data' => ''),
+ );
+
+ $output = theme('table', array(), $rows, array('id' => 'page-manager-page-summary'));
+ return $output;
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_term_view_access_check($task, $subtask_id, $contexts) {
+ return user_access('access content');
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/user_edit.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/user_edit.inc
new file mode 100644
index 000000000..0b11bf017
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/user_edit.inc
@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * @file
+ * Overrides the user profile display at user/%user.
+ *
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_user_edit_page_manager_tasks() {
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+ 'title' => t('User Edit Template'),
+ 'admin title' => t('User edit template'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying user edit form at <em>user/%user/edit</em>.'),
+ 'admin path' => 'user/%user/edit',
+
+ // Callback to add items to the page managertask administration form:
+ 'task admin' => 'page_manager_user_edit_task_admin',
+
+ 'hook menu' => 'page_manager_user_edit_menu',
+ 'hook menu alter' => 'page_manager_user_edit_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context', // handler type -- misnamed
+ 'get arguments' => 'page_manager_user_edit_get_arguments',
+ 'get context placeholders' => 'page_manager_user_edit_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_user_edit_disabled', TRUE),
+ 'enable callback' => 'page_manager_user_edit_enable',
+ 'access callback' => 'page_manager_user_edit_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_user_view_page_manager_tasks().
+ *
+ * Alter the user view input so that user view comes to us rather than the
+ * normal user view process.
+ */
+function page_manager_user_edit_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_user_edit_disabled', TRUE)) {
+ return;
+ }
+
+ // Override the user view handler for our purpose.
+ if ($items['user/%user/edit']['page callback'] == 'drupal_get_form' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['user/%user/edit']['page callback'] = 'page_manager_user_edit_page';
+ $items['user/%user/edit']['page arguments'] = array(1);
+ $items['user/%user/edit']['file path'] = $task['path'];
+ $items['user/%user/edit']['file'] = $task['file'];
+ if (($categories = _user_categories()) && (count($categories) > 1)) {
+ foreach ($categories as $key => $category) {
+ // 'account' is already handled by the MENU_DEFAULT_LOCAL_TASK.
+ if ($category['name'] != 'account') {
+ $items['user/%user_category/edit/' . $category['name']]['page callback'] = 'page_manager_user_edit_page';
+ $items['user/%user_category/edit/' . $category['name']]['page arguments'] = array(1, 3);
+ $items['user/%user_category/edit/' . $category['name']]['file path'] = $task['path'];
+ $items['user/%user_category/edit/' . $category['name']]['file'] = $task['file'];
+ }
+ }
+ }
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('page_manager_user_edit_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_user_edit'])) {
+ drupal_set_message(t('Page manager module is unable to enable user/%user/edit because some other module already has overridden with %callback.', array('%callback' => $items['user/%user']['page callback'])), 'error');
+ }
+ }
+}
+
+/**
+ * Entry point for our overridden user view.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * user edit, which is drupal_get_form('user_profile_form',$account).
+ */
+function page_manager_user_edit_page($account, $category = 'account') {
+ // Store the category on the user for later usage.
+ $account->user_category = $category;
+
+ // Load my task plugin:
+ $task = page_manager_get_task('user_edit');
+
+ // Load the account into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($account));
+ // Build content. @todo -- this may not be right.
+ user_build_content($account);
+
+ $output = ctools_context_handler_render($task, '', $contexts, array($account->uid));
+ if (is_array($output)) {
+ $output = drupal_render($output);
+ }
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ $function = 'drupal_get_form';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('user_edit')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+ if ($function == 'drupal_get_form') {
+
+ //In order to ajax fields to work we need to run form_load_include.
+ //Hence we eschew drupal_get_form and manually build the info and
+ //call drupal_build_form.
+ $form_state = array();
+ $form_id = 'user_profile_form';
+ $args = array($account, $category);
+ $form_state['build_info']['args'] = $args;
+ form_load_include($form_state, 'inc', 'user', 'user.pages');
+ $output = drupal_build_form($form_id, $form_state);
+ return $output;
+ }
+ //fire off "view" op so that triggers still work
+ // @todo -- this doesn't work anymore, and the alternatives seem bad.
+ // will have to figure out how to fix this.
+ // user_module_invoke('view', $array = array(), $account);
+ return $function($account);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the node view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_user_edit_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'user',
+ 'identifier' => t('User being edited'),
+ 'id' => 1,
+ 'name' => 'user_edit',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_user_edit_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_user_edit_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_user_edit_enable($cache, $status) {
+ variable_set('page_manager_user_edit_disabled', $status);
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_user_edit'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_user_edit_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return user_edit_access($context->data);
+}
diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/user_view.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/user_view.inc
new file mode 100644
index 000000000..c428384a1
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/plugins/tasks/user_view.inc
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * @file
+ * Overrides the user profile display at user/%user.
+ *
+ * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
+ * more information.
+ */
+function page_manager_user_view_page_manager_tasks() {
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+ 'title' => t('User profile template'),
+ 'admin title' => t('User profile template'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying user profiles at <em>user/%user</em>. If you add variants, you may use selection criteria such as roles or user access to provide different views of user profiles. If no variant is selected, the default Drupal user view will be used. Please note that if you are using pathauto, aliases may make a node to be somewhere else, but as far as Drupal is concerned, they are still at user/%user.'),
+ 'admin path' => 'user/%user',
+
+ // Callback to add items to the page managertask administration form:
+ 'task admin' => 'page_manager_user_view_task_admin',
+
+ 'hook menu' => 'page_manager_user_view_menu',
+ 'hook menu alter' => 'page_manager_user_view_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context', // handler type -- misnamed
+ 'get arguments' => 'page_manager_user_view_get_arguments',
+ 'get context placeholders' => 'page_manager_user_view_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('page_manager_user_view_disabled', TRUE),
+ 'enable callback' => 'page_manager_user_view_enable',
+ 'access callback' => 'page_manager_user_view_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_user_view_page_manager_tasks().
+ *
+ * Alter the user view input so that user view comes to us rather than the
+ * normal user view process.
+ */
+function page_manager_user_view_menu_alter(&$items, $task) {
+ if (variable_get('page_manager_user_view_disabled', TRUE)) {
+ return;
+ }
+
+ // Override the user view handler for our purpose.
+ if ($items['user/%user']['page callback'] == 'user_view_page' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['user/%user']['page callback'] = 'page_manager_user_view_page';
+ $items['user/%user']['file path'] = $task['path'];
+ $items['user/%user']['file'] = $task['file'];
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('page_manager_user_view_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_user_view'])) {
+ drupal_set_message(t('Page manager module is unable to enable user/%user because some other module already has overridden with %callback.', array('%callback' => $items['user/%user']['page callback'])), 'error');
+ }
+ }
+}
+
+/**
+ * Entry point for our overridden user view.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to Drupal core's
+ * user view, which is user_page_view().
+ */
+function page_manager_user_view_page($account) {
+ // Load my task plugin:
+ $task = page_manager_get_task('user_view');
+
+ // Load the account into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($account));
+
+ // Build content. @todo -- this may not be right.
+ user_build_content($account);
+
+ $output = ctools_context_handler_render($task, '', $contexts, array($account->uid));
+ if ($output !== FALSE) {
+ return $output;
+ }
+
+ $function = 'user_view';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('user_view')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+ if ($function == 'user_view') {
+ module_load_include('inc', 'user', 'user.pages');
+ }
+ //fire off "view" op so that triggers still work
+ // @todo -- this doesn't work anymore, and the alternatives seem bad.
+ // will have to figure out how to fix this.
+// user_module_invoke('view', $array = array(), $account);
+ return $function($account);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the node view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function page_manager_user_view_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'user',
+ 'identifier' => t('User being viewed'),
+ 'id' => 1,
+ 'name' => 'entity_id:user',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function page_manager_user_view_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_user_view_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function page_manager_user_view_enable($cache, $status) {
+ variable_set('page_manager_user_view_disabled', $status);
+
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_user_view'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_user_view_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return user_view_access($context->data);
+}
diff --git a/sites/all/modules/ctools/page_manager/theme/page-manager-edit-page.tpl.php b/sites/all/modules/ctools/page_manager/theme/page-manager-edit-page.tpl.php
new file mode 100644
index 000000000..85510cd93
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/theme/page-manager-edit-page.tpl.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @file
+ * Template for the page manager page editor.
+ *
+ * Variables available:
+ * -
+ *
+ * For javascript purposes the id must not change.
+ */
+?>
+<div id="page-manager-edit">
+ <?php print $locked; ?>
+ <div class="page-manager-wrapper">
+ <?php if (isset($operations['primary'])): ?>
+ <div class="primary-actions clearfix actions">
+ <?php print $operations['primary']; ?>
+ </div>
+ <?php endif; ?>
+ <div class="page-manager-tabs clearfix">
+ <div class="page-manager-edit-operations">
+ <div class="inside">
+ <?php print $operations['nav']; ?>
+ </div>
+ </div>
+ <div class="page-manager-ajax-pad">
+ <div class="inside">
+ <div class="content-header">
+ <div class="content-title">
+ <?php print $changed; ?>
+ <?php print $content['title']; ?>
+ </div>
+ <?php if (isset($operations['secondary'])): ?>
+ <div class="secondary-actions clearfix actions">
+ <?php print $operations['secondary']; ?>
+ </div>
+ <?php endif; ?>
+ </div>
+
+ <div class="content-content">
+ <?php if (!empty($content['description'])): ?>
+ <div class="description">
+ <?php print $content['description']; ?>
+ </div>
+ <?php endif; ?>
+ <?php print $content['content']; ?>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <?php print $save; ?>
+</div> \ No newline at end of file
diff --git a/sites/all/modules/ctools/page_manager/theme/page_manager.theme.inc b/sites/all/modules/ctools/page_manager/theme/page_manager.theme.inc
new file mode 100644
index 000000000..6435d1ce5
--- /dev/null
+++ b/sites/all/modules/ctools/page_manager/theme/page_manager.theme.inc
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Preprocess functions for page manager editing templates.
+ */
+
+/**
+ * Preprocess the page manager edit page.
+ */
+function template_preprocess_page_manager_edit_page(&$vars) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_modal_add_js();
+ ctools_add_js('dependent');
+
+ ctools_add_css('page-manager', 'page_manager');
+ ctools_add_css('wizard');
+
+ $task = $vars['page']->task;
+
+ $page = &$vars['page'];
+
+ $vars['locked'] = '';
+ $vars['changed'] = '';
+ if (!empty($page->locked)) {
+ $vars['locked'] = theme('page_manager_lock', array('page' => $page));
+ $vars['changed'] = theme('page_manager_changed', array('text' => t('Locked'), 'description' => t('This page is being edited by another user and you cannot make changes to it.')));
+ }
+ else if (!empty($page->new)) {
+ $vars['changed'] = theme('page_manager_changed', array('text' => t('New'), 'description' => t('This page is newly created and has not yet been saved to the database. It will not be available until you save it.')));
+ }
+ else if (!empty($page->changed)) {
+ $vars['changed'] = theme('page_manager_changed', array('text' => t('Changed'), 'description' => t('This page has been modified, but these modifications are not yet live. While modifying this page, it is locked from modification by other users.')));
+ }
+
+ $form_state = array(
+ 'page' => &$vars['page'],
+ );
+
+ $active = $vars['content']['active'];
+ if ($active[0] == 'handlers' && isset($vars['operations'][$active[1]])) {
+ $vars['operations']['secondary'] = $vars['operations'][$active[1]];
+ }
+}
+
+/**
+ * Turn the rearrange form into a table with tablesorting on.
+ */
+function theme_page_manager_handler_rearrange($vars) {
+ $form = &$vars['form'];
+ // Assemble the data for a table from everything in $form['handlers']
+ foreach (element_children($form['handlers']) as $id) {
+ // provide a reference shortcut.
+ $element = &$form['handlers'][$id];
+ if (isset($element['title'])) {
+ $row = array();
+
+ $row[] = array(
+ 'data' => render($element['title']),
+ 'class' => array('page-manager-handler'),
+ );
+
+ $element['weight']['#attributes']['class'][] = 'weight';
+ $row[] = render($element['weight']);
+
+ $rows[] = array('data' => $row, 'class' => array('draggable'), 'id' => 'page-manager-row-' . $id);
+ }
+ }
+
+ if (empty($rows)) {
+ $rows[] = array(array('data' => t('No task handlers are defined for this task.'), 'colspan' => '5'));
+ }
+
+ $header = array(
+ array('data' => t('Variant'), 'class' => array('page-manager-handler')),
+ t('Weight'),
+ );
+
+ drupal_add_tabledrag('page-manager-arrange-handlers', 'order', 'sibling', 'weight');
+
+ $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'page-manager-arrange-handlers')));
+ $output .= drupal_render_children($form);
+ return $output;
+}
+
+/**
+ * Draw the "this task is locked from editing" box.
+ */
+function theme_page_manager_lock($vars) {
+ $page = $vars['page'];
+
+ $account = user_load($page->locked->uid);
+ $name = theme('username', array('account' => $account));
+ $lock_age = format_interval(REQUEST_TIME - $page->locked->updated);
+ $break = url(page_manager_edit_url($page->task_name, array('actions', 'break-lock')));
+
+ ctools_add_css('ctools');
+ $output = '<div class="ctools-locked">';
+ $output .= t('This page is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => $name, '!age' => $lock_age, '!break' => $break));
+ $output .= '</div>';
+ return $output;
+}
+
+/**
+ * Draw the "you have unsaved changes and this task is locked." message.
+ */
+function theme_page_manager_changed($vars) {
+ $text = $vars['text'];
+ $description = $vars['description'];
+
+ ctools_add_css('ctools');
+ $output = '<div class="page-manager-changed" title="' . $description . '">';
+ $output .= $text;
+ $output .= '</div>';
+
+ return $output;
+}
diff --git a/sites/all/modules/ctools/plugins/access/book.inc b/sites/all/modules/ctools/plugins/access/book.inc
new file mode 100644
index 000000000..4b4f7eaae
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/book.inc
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based on whether a node belongs to a book.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+if (module_exists('book')) {
+ $plugin = array(
+ 'title' => t("Book: node is in a book"),
+ 'description' => t('Control access based upon a node belonging to a book.'),
+ 'callback' => 'ctools_book_node_ctools_access_check',
+ 'default' => array('book' => array()),
+ 'settings form' => 'ctools_book_node_ctools_access_settings',
+ 'settings form submit' => 'ctools_book_node_ctools_access_settings_submit',
+ 'summary' => 'ctools_book_node_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ );
+}
+
+/**
+ * Settings form for the 'by book_node' access plugin.
+ */
+function ctools_book_node_ctools_access_settings($form, &$form_state, $conf) {
+ $options = array(
+ 'any' => t('In any book'),
+ );
+ $books = book_get_books();
+ foreach ($books as $bid => $book) {
+ $options[$bid] = $book['title'];
+ }
+ $form['settings']['book'] = array(
+ '#title' => t('Book'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('Pass only if the node belongs to one of the selected books'),
+ '#default_value' => $conf['book'],
+ '#required' => TRUE,
+ );
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_book_node_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || empty($context->data->book)) {
+ return FALSE;
+ }
+
+ if ($conf['book']['any']) {
+ return !empty($context->data->book);
+ }
+
+ foreach ($conf['book'] as $bid => $value) {
+ if ($bid == 'any') {
+ continue;
+ }
+ if ($value && ($bid == $context->data->book['bid'])) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Provide a summary description based upon the checked node_languages.
+ */
+function ctools_book_node_ctools_access_summary($conf, $context) {
+ if ($conf['book']['any']) {
+ return t('@identifier belongs to a book', array('@identifier' => $context->identifier));
+ }
+
+ $books = array();
+ foreach ($conf['book'] as $bid => $value) {
+ if ($value) {
+ $node = node_load($bid);
+ $books[] = $node->title;
+ }
+ }
+
+ if (count($books) == 1) {
+ return t('@identifier belongs to the book "@book"', array('@book' => $books[0], '@identifier' => $context->identifier));
+ }
+
+ return t('@identifier belongs in multiple books', array('@identifier' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/access/compare_users.inc b/sites/all/modules/ctools/plugins/access/compare_users.inc
new file mode 100644
index 000000000..c271ff4e3
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/compare_users.inc
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Ctools access plugin to provide access/visiblity if two user contexts are equal.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("User: compare"),
+ 'description' => t('Compare two users (logged-in user and user being viewed, for example)'),
+ 'callback' => 'ctools_compare_users_access_check',
+ 'default' => array(
+ 'equality' => 1,
+ ),
+ 'settings form' => 'ctools_compare_users_settings',
+ 'summary' => 'ctools_compare_users_ctools_access_summary',
+ 'required context' => array(
+ new ctools_context_required(t('First User'), 'user'),
+ new ctools_context_required(t("Second User"), 'user')
+ ),
+);
+
+/**
+ * Settings form for the 'by perm' access plugin
+ */
+function ctools_compare_users_settings($form, &$form_state, $conf) {
+
+ $form['settings']['helptext'] = array(
+ '#type' => 'markup',
+ '#value' => '<div>' . t('Grant access based on comparison of the two user contexts. For example, to grant access to a user to view their own profile, choose "logged in user" and "user being viewed" and say "grant access if equal". When they\'re the same, access will be granted.') . '</div>',
+ );
+
+ $form['settings']['equality'] = array(
+ '#type' => 'radios',
+ '#title' => t('Grant access if user contexts are'),
+ '#options' => array(1 => t('Equal'), 0 => t('Not equal')),
+ '#default_value' => $conf['equality'],
+ );
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_compare_users_access_check($conf, $context) {
+
+ if (empty($context) || count($context) != 2 || empty($context[0]->data) || empty($context[1]->data)) {
+ return FALSE;
+ }
+ $account1 = $context[0]->data;
+ $account2 = $context[1]->data;
+
+ // xor returns false if the two bools are the same, and true if they are not.
+ // i.e, if we asked for equality and they are equal, return true.
+ // If we asked for inequality and they are equal, return false.
+ return ($account1->uid == $account2->uid) xor empty($conf['equality']);
+}
+
+/**
+ * Describe an instance of this plugin.
+ */
+function ctools_compare_users_ctools_access_summary($conf, $context) {
+ $comparison = !empty($conf['equality']) ? "is" : 'is not';
+
+ return t('@id1 @comp @id2', array('@comp' => $comparison, '@id1' => $context[0]->identifier, '@id2' => $context[1]->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/access/context_exists.inc b/sites/all/modules/ctools/plugins/access/context_exists.inc
new file mode 100644
index 000000000..aabc62dd9
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/context_exists.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control/visibility based on existence of a specified context
+ */
+
+$plugin = array(
+ 'title' => t("Context exists"),
+ 'description' => t('Control access by whether or not a context exists and contains data.'),
+ 'callback' => 'ctools_context_exists_ctools_access_check',
+ 'settings form' => 'ctools_context_exists_ctools_access_settings',
+ 'summary' => 'ctools_context_exists_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Context'), 'any', TRUE),
+ 'defaults' => array('exists' => TRUE),
+);
+
+/**
+ * Settings form
+ */
+function ctools_context_exists_ctools_access_settings($form, &$form_state, $conf) {
+ $form['settings']['exists'] = array(
+ '#type' => 'radios',
+ '#description' => t("Check to see if the context exists (contains data) or does not exist (contains no data). For example, if a context is optional and the path does not contain an argument for that context, it will not exist."),
+ '#options' => array(TRUE => t('Exists'), FALSE => t("Doesn't exist")),
+ '#default_value' => $conf['exists'],
+ );
+ return $form;
+}
+
+/**
+ * Check for access
+ */
+function ctools_context_exists_ctools_access_check($conf, $context) {
+ // xor returns false if the two bools are the same, and true if they are not.
+ // i.e, if we asked for context_exists and it does, return true.
+ // If we asked for context does not exist and it does, return false.
+ return (empty($context->data) xor !empty($conf['exists']));
+}
+
+/**
+ * Provide a summary description based upon the specified context
+ */
+function ctools_context_exists_ctools_access_summary($conf, $context) {
+ if (!empty($conf['exists'])) {
+ return t('@identifier exists', array('@identifier' => $context->identifier));
+ }
+ else {
+ return t('@identifier does not exist', array('@identifier' => $context->identifier));
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/access/entity_bundle.inc b/sites/all/modules/ctools/plugins/access/entity_bundle.inc
new file mode 100644
index 000000000..e07a048de
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/entity_bundle.inc
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon entity bundle.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Entity: bundle"),
+ 'description' => t('Control access by entity bundle.'),
+ 'callback' => 'ctools_entity_bundle_ctools_access_check',
+ 'default' => array('type' => array()),
+ 'settings form' => 'ctools_entity_bundle_ctools_access_settings',
+ 'settings form submit' => 'ctools_entity_bundle_ctools_access_settings_submit',
+ 'summary' => 'ctools_entity_bundle_ctools_access_summary',
+ 'restrictions' => 'ctools_entity_bundle_ctools_access_restrictions',
+ 'get child' => 'ctools_entity_bundle_ctools_access_get_child',
+ 'get children' => 'ctools_entity_bundle_ctools_access_get_children',
+);
+
+function ctools_entity_bundle_ctools_access_get_child($plugin, $parent, $child) {
+ $plugins = ctools_entity_bundle_ctools_access_get_children($plugin, $parent);
+ return $plugins[$parent . ':' . $child];
+}
+
+function ctools_entity_bundle_ctools_access_get_children($plugin, $parent) {
+ $entities = entity_get_info();
+ $plugins = array();
+ foreach ($entities as $entity_type => $entity) {
+ $plugin['title'] = t('@entity: Bundle', array('@entity' => $entity['label']));
+ $plugin['keyword'] = $entity_type;
+ $plugin['description'] = t('Control access by @entity entity bundle.', array('@entity' => $entity_type));
+ $plugin['name'] = $parent . ':' . $entity_type;
+ $plugin['required context'] = new ctools_context_required(t(ucfirst($entity_type)), $entity_type);
+ $plugins[$parent . ':' . $entity_type] = $plugin;
+ }
+
+ return $plugins;
+}
+
+/**
+ * Settings form for the 'by entity_bundle' access plugin
+ */
+function ctools_entity_bundle_ctools_access_settings($form, &$form_state, $conf) {
+ $plugin = $form_state['plugin'];
+ $entity_type = explode(':', $plugin['name']);
+ $entity_type = $entity_type[1];
+ $entity = entity_get_info($entity_type);
+ foreach ($entity['bundles'] as $type => $info) {
+ $options[$type] = check_plain($info['label']);
+ }
+
+ $form['settings']['type'] = array(
+ '#title' => t('Entity Bundle'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('Only the checked entity bundles will be valid.'),
+ '#default_value' => $conf['type'],
+ );
+ return $form;
+}
+
+/**
+ * Compress the entity bundles allowed to the minimum.
+ */
+function ctools_entity_bundle_ctools_access_settings_submit($form, &$form_state) {
+ $form_state['values']['settings']['type'] = array_filter($form_state['values']['settings']['type']);
+}
+
+/**
+ * Check for access.
+ */
+function ctools_entity_bundle_ctools_access_check($conf, $context, $plugin) {
+ list($plugin_name, $entity_type) = explode(':', $plugin['name']);
+ if (!$entity_type) {
+ return FALSE;
+ };
+
+ $entity = entity_get_info($entity_type);
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || empty($context->data->{$entity['entity keys']['bundle']})) {
+ return FALSE;
+ }
+
+ if (array_filter($conf['type']) && empty($conf['type'][$context->data->{$entity['entity keys']['bundle']}])) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Inform the UI that we've eliminated a bunch of possibilities for this
+ * context.
+ */
+function ctools_entity_bundle_ctools_access_restrictions($conf, &$context) {
+ if (isset($context->restrictions['type'])) {
+ $context->restrictions['type'] = array_unique(array_merge($context->restrictions['type'], array_keys(array_filter($conf['type']))));
+ }
+ else {
+ $context->restrictions['type'] = array_keys(array_filter($conf['type']));
+ }
+}
+
+/**
+ * Provide a summary description based upon the checked entity_bundle.
+ */
+function ctools_entity_bundle_ctools_access_summary($conf, $context, $plugin) {
+ if (!isset($conf['type'])) {
+ $conf['type'] = array();
+ }
+
+ list($plugin_name, $entity_type) = explode(':', $plugin['name']);
+ if (!$entity_type) {
+ return t('Error, misconfigured entity_bundle access plugin');
+ };
+
+ $entity = entity_get_info($entity_type);
+
+ $names = array();
+ foreach (array_filter($conf['type']) as $type) {
+ $names[] = check_plain($entity['bundles'][$type]['label']);
+ }
+
+ if (empty($names)) {
+ return t('@identifier is any bundle', array('@identifier' => $context->identifier));
+ }
+
+ return format_plural(count($names), '@identifier is bundle "@types"', '@identifier bundle is one of "@types"', array('@types' => implode(', ', $names), '@identifier' => $context->identifier));
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/entity_field_value.inc b/sites/all/modules/ctools/plugins/access/entity_field_value.inc
new file mode 100644
index 000000000..fa94a4818
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/entity_field_value.inc
@@ -0,0 +1,410 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon entity bundle.
+ */
+
+$plugin = array(
+ 'title' => t("(Custom) Entity: Field Value"),
+ 'description' => t('Control access by entity field value.'),
+ 'callback' => 'ctools_entity_field_value_ctools_access_check',
+ 'default' => array('type' => array()),
+ 'settings form' => 'ctools_entity_field_value_ctools_access_settings',
+ 'settings form submit' => 'ctools_entity_field_value_ctools_access_settings_submit',
+ 'summary' => 'ctools_entity_field_value_ctools_access_summary',
+ 'get child' => 'ctools_entity_field_value_ctools_access_get_child',
+ 'get children' => 'ctools_entity_field_value_ctools_access_get_children',
+);
+
+function ctools_entity_field_value_ctools_access_get_child($plugin, $parent, $child) {
+ $plugins = &drupal_static(__FUNCTION__, array());
+ if (empty($plugins[$parent . ':' . $child])) {
+ list($entity_type, $bundle_type, $field_name) = explode(':', $child);
+ $plugins[$parent . ':' . $child] = _ctools_entity_field_value_ctools_access_get_child($plugin, $parent, $entity_type, $bundle_type, $field_name);
+ }
+
+ return $plugins[$parent . ':' . $child];
+}
+
+function ctools_entity_field_value_ctools_access_get_children($plugin, $parent) {
+ $plugins = &drupal_static(__FUNCTION__, array());
+ if (!empty($plugins)) {
+ return $plugins;
+ }
+ $entities = entity_get_info();
+ foreach ($entities as $entity_type => $entity) {
+ foreach ($entity['bundles'] as $bundle_type => $bundle) {
+ foreach (field_info_instances($entity_type, $bundle_type) as $field_name => $field) {
+ if (!isset($plugins[$parent . ':' . $entity_type . ':' . $bundle_type . ':' . $field_name])) {
+ $plugin = _ctools_entity_field_value_ctools_access_get_child($plugin, $parent, $entity_type, $bundle_type, $field_name, $entity, $bundle, $field);
+ $plugins[$parent . ':' . $entity_type . ':' . $bundle_type . ':' . $field_name] = $plugin;
+ }
+ }
+ }
+ }
+
+ return $plugins;
+}
+
+function _ctools_entity_field_value_ctools_access_get_child($plugin, $parent, $entity_type, $bundle_type, $field_name, $entity = NULL, $bundle = NULL, $field = NULL) {
+ // check that the entity, bundle and field arrays have a value.
+ // If not, load theme using machine names.
+ if (empty($entity)) {
+ $entity = entity_get_info($entity_type);
+ }
+
+ if (empty($bundle)) {
+ $bundle = $entity['bundles'][$bundle_type];
+ }
+
+ if (empty($field)) {
+ $field_instances = field_info_instances($entity_type, $bundle_type);
+ $field = $field_instances[$field_name];
+ }
+
+ $plugin['title'] = t('@entity @type: @field Field', array('@entity' => $entity['label'], '@type' => $bundle_type, '@field' => $field['label']));
+ $plugin['keyword'] = $entity_type;
+ $plugin['description'] = t('Control access by @entity entity bundle.', array('@entity' => $entity_type));
+ $plugin['name'] = $parent . ':' . $entity_type . ':' . $bundle_type . ':' . $field_name;
+ $plugin['required context'] = new ctools_context_required(t(ucfirst($entity_type)), $entity_type, array(
+ 'type' => $bundle_type,
+ ));
+
+ return $plugin;
+}
+
+/**
+ * Settings form for the 'by entity_bundle' access plugin
+ */
+function ctools_entity_field_value_ctools_access_settings($form, &$form_state, $conf) {
+ $plugin = $form_state['plugin'];
+ list($parent, $entity_type, $bundle_type, $field_name) = explode(':', $plugin['name']);
+ $entity_info = entity_get_info($entity_type);
+ $instances = field_info_instances($entity_type, $bundle_type);
+ $instance = $instances[$field_name];
+ $field = field_info_field_by_id($instance['field_id']);
+ foreach ($field['columns'] as $column => $attributes) {
+ $columns[$column] = _field_sql_storage_columnname($field_name, $column);
+ }
+ ctools_include('fields');
+ $entity = (object)array(
+ $entity_info['entity keys']['bundle'] => $bundle_type,
+ );
+
+ foreach ($columns as $column => $sql_column) {
+ if (isset($conf[$sql_column])) {
+ if (is_array($conf[$sql_column])) {
+ foreach ($conf[$sql_column] as $delta => $conf_value) {
+ if (is_numeric($delta)) {
+ if (is_array($conf_value)) {
+ $entity->{$field_name}[LANGUAGE_NONE][$delta][$column] = $conf_value[$column];
+ }
+ else {
+ $entity->{$field_name}[LANGUAGE_NONE][$delta][$column] = $conf_value;
+ }
+ }
+ }
+ }
+ else {
+ $entity->{$field_name}[LANGUAGE_NONE][0][$column] = $conf[$sql_column];
+ }
+ }
+ }
+
+ $form['#parents'] = array('settings');
+ $langcode = field_valid_language(NULL);
+ $form['settings'] += (array) ctools_field_invoke_field($instance, 'form', $entity_type, $entity, $form, $form_state, array('default' => TRUE, 'language' => $langcode));
+ // weight is really not important once this is populated and will only interfere with the form layout.
+ foreach (element_children($form['settings']) as $element) {
+ unset($form['settings'][$element]['#weight']);
+ }
+
+ return $form;
+}
+
+function ctools_entity_field_value_ctools_access_settings_submit($form, &$form_state) {
+ $plugin = $form_state['plugin'];
+ list($parent, $entity_type, $bundle_type, $field_name) = explode(':', $plugin['name']);
+ $langcode = field_valid_language(NULL);
+ $langcode = isset($form_state['input']['settings'][$field_name][$langcode]) ? $langcode : LANGUAGE_NONE;
+ $instances = field_info_instances($entity_type, $bundle_type);
+ $instance = $instances[$field_name];
+ $field = field_info_field_by_id($instance['field_id']);
+ foreach ($field['columns'] as $column => $attributes) {
+ $columns[$column] = _field_sql_storage_columnname($field_name, $column);
+ }
+ $items = _ctools_entity_field_value_get_proper_form_items($field, $form_state['values']['settings'][$field_name][$langcode], array_keys($columns));
+ foreach ($columns as $column => $sql_column) {
+ $column_items = _ctools_entity_field_value_filter_items_by_column($items, $column);
+ $form_state['values']['settings'][$sql_column] = $column_items;
+ }
+ $form_state['values']['settings'][$field_name][$langcode] = $items;
+}
+
+function _ctools_entity_field_value_get_proper_form_items($field, $form_items, $columns) {
+ $items = array();
+
+ if (!is_array($form_items)) { // Single value item.
+ foreach ($columns as $column) {
+ $items[0][$column] = $form_items;
+ }
+ return $items;
+ }
+
+ foreach ($form_items as $delta => $value) {
+ $item = array();
+ if (is_numeric($delta)) { // Array of field values.
+ if (!is_array($value)) { // Single value in array.
+ foreach ($columns as $column) {
+ $item[$column] = $value;
+ }
+ }
+ else { // Value has colums.
+ foreach ($columns as $column) {
+ $item[$column] = isset($value[$column]) ? $value[$column] : '';
+ }
+ }
+ }
+ $items[] = $item;
+ }
+
+ // Check if $form_items is an array of columns.
+ $item = array();
+ $has_columns = FALSE;
+ foreach ($columns as $column) {
+ if (isset($form_items[$column])) {
+ $has_columns = TRUE;
+ $item[$column] = $form_items[$column];
+ }
+ else {
+ $item[$column] = '';
+ }
+ }
+ if ($has_columns) {
+ $items[] = $item;
+ }
+
+ // Remove empty values.
+ $items = _field_filter_items($field, $items);
+ return $items;
+}
+
+function _ctools_entity_field_value_filter_items_by_column($items, $column) {
+ $column_items = array();
+ foreach ($items as $delta => $values) {
+ $column_items[$delta] = isset($values[$column]) ? $values[$column] : '';
+ }
+ return $column_items;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_entity_field_value_ctools_access_check($conf, $context, $plugin) {
+ if ((!is_object($context)) || (empty($context->data))) {
+ // If the context doesn't exist -- for example, a newly added entity
+ // reference is used as a pane visibility criteria -- we deny access.
+ return FALSE;
+ }
+
+ list($parent, $entity_type, $bundle_type, $field_name) = explode(':', $plugin['name']);
+
+ if ($field_items = field_get_items($entity_type, $context->data, $field_name)) {
+ $langcode = field_language($entity_type, $context->data, $field_name);
+ // Get field storage columns.
+ $instance = field_info_instance($entity_type, $field_name, $bundle_type);
+ $field = field_info_field_by_id($instance['field_id']);
+ $columns = array();
+ foreach ($field['columns'] as $column => $attributes) {
+ $columns[$column] = _field_sql_storage_columnname($field_name, $column);
+ }
+
+ if (isset($conf[$field_name])) {
+ // We have settings for this field.
+ $conf_value_array = _ctools_entity_field_value_ctools_access_get_conf_field_values($conf[$field_name], $langcode);
+ if (empty($conf_value_array)) {
+ return FALSE;
+ }
+
+ // Check field value.
+ foreach ($field_items as $field_value) {
+ // Iterate through config values.
+ foreach ($conf_value_array as $conf_value) {
+ $match = FALSE;
+ foreach ($field_value as $field_column => $value) {
+ // Check access only for stored in config column values.
+ if (isset($conf_value[$field_column])) {
+ if ($value == $conf_value[$field_column]) {
+ $match = TRUE;
+ }
+ else {
+ $match = FALSE;
+ break;
+ }
+ }
+ }
+ if ($match) {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+function _ctools_entity_field_value_ctools_access_get_conf_field_values($values, $langcode = LANGUAGE_NONE) {
+ if (!is_array($values) || !isset($values[$langcode])) {
+ return NULL;
+ }
+ $conf_values = array();
+
+ foreach ($values[$langcode] as $delta => $value) {
+ $conf_values[$delta] = $value;
+ }
+
+ return $conf_values;
+}
+
+/**
+ * Provide a summary description based upon the checked entity_bundle.
+ */
+function ctools_entity_field_value_ctools_access_summary($conf, $context, $plugin) {
+ list($parent, $entity_type, $bundle_type, $field_name) = explode(':', $plugin['name']);
+ $instances = field_info_instances($entity_type, $bundle_type);
+ $instance = $instances[$field_name];
+ $field = field_info_field_by_id($instance['field_id']);
+ $entity_info = entity_get_info($entity_type);
+ $entity = (object)array(
+ $entity_info['entity keys']['bundle'] => $bundle_type,
+ );
+ $keys = array();
+ $value_keys = array();
+ $keyed_elements = array();
+ foreach ($field['columns'] as $column => $attributes) {
+ $conf_key = _field_sql_storage_columnname($field_name, $column);
+ $keyed_elements["@{$column}_value"] = array();
+
+ if (isset($conf[$conf_key])) {
+ if (is_array($conf[$conf_key])) {
+ $i = 0;
+ foreach ($conf[$conf_key] as $conf_value) {
+ if (!is_array($conf_value)) {
+ $entity->{$field_name}[LANGUAGE_NONE][$i][$column] = $conf_value;
+ $keyed_elements["@{$column}_value"][$i] = array('#markup' => $conf_value);
+ }
+ elseif (isset($conf_value[$column])) {
+ $entity->{$field_name}[LANGUAGE_NONE][$i][$column] = $conf_value[$column];
+ $keyed_elements["@{$column}_value"][$i] = array('#markup' => $conf_value[$column]);
+ }
+ $i++;
+ }
+ }
+ else {
+ $entity->{$field_name}[LANGUAGE_NONE][0][$column] = $conf[$conf_key];
+ $keyed_elements["@{$column}_value"][0] = array('#markup' => $conf[$conf_key]);
+ }
+ }
+
+ $keys['@' . $column] = $column;
+ $value_keys[] = "@{$column}_value";
+ }
+ $elements = array();
+ $items = isset($entity->{$field_name}[LANGUAGE_NONE]) ? $entity->{$field_name}[LANGUAGE_NONE] : array();
+ $view_mode = 'full';
+ ctools_include('fields');
+ $display = field_get_display($instance, $view_mode, $entity);
+ if (!isset($display['module'])) {
+ $display['module'] = $field['module'];
+ }
+ if (isset($display['module'])) {
+ // Choose simple formatter for well known cases.
+ switch ($display['module']) {
+ case 'text':
+ $display['type'] = 'text_default';
+ break;
+
+ case 'list':
+ $display['type'] = 'list_default';
+ if ($field['type'] == 'list_boolean') {
+ $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity);
+ foreach ($items as $item) {
+ if (isset($allowed_values[$item['value']])) {
+ if ($allowed_values[$item['value']] == '') {
+ $display['type'] = 'list_key';
+ break;
+ }
+ }
+ else {
+ $display['type'] = 'list_key';
+ }
+ }
+ }
+ break;
+
+ case 'taxonomy':
+ $display['type'] = 'taxonomy_term_reference_plain';
+ break;
+
+ case 'entityreference':
+ $display['type'] = 'entityreference_label';
+ break;
+
+ default :
+ // Use field instance formatter setting.
+ break;
+ }
+
+ $function = $display['module'] . '_field_formatter_view';
+ if (function_exists($function)) {
+ $entity_group = array(0 => $entity);
+ $item_group = array(0 => $items);
+ $instance_group = array(0 => $instance);
+ field_default_prepare_view($entity_type, $entity_group, $field, $instance_group, LANGUAGE_NONE, $item_group, $display);
+ $elements = $function($entity_type, $entity, $field, $instance, LANGUAGE_NONE, $item_group[0], $display);
+ }
+ }
+ if (count($elements) > 0) {
+ foreach ($field['columns'] as $column => $attributes) {
+ if (count($field['columns']) == 1) {
+ $keyed_elements["@{$column}_value"] = $elements;
+ }
+ }
+ }
+ $values = array();
+ foreach ($value_keys as $key) {
+ $output = array();
+ $elements = $keyed_elements[$key];
+ if (is_array($elements)) {
+ foreach ($elements as $element_key => $element) {
+ if (is_numeric($element_key)) {
+ $value_str= strip_tags(drupal_render($element));
+ if (strlen($value_str) > 0) {
+ $output[] = $value_str;
+ }
+ }
+ }
+ }
+ else {
+ $value_str = strip_tags(drupal_render($elements));
+ if (strlen($value_str) > 0) {
+ $output[] = $value_str;
+ }
+ }
+ $value = implode(', ', $output);
+ if ($value !== '') {
+ $values[$key] = implode(', ', $output);
+ }
+ }
+ $string = '';
+ $value_count = count($values);
+ foreach ($keys as $key_name => $column) {
+ if (isset($values[$key_name . '_value'])) {
+ $string .= ($value_count > 1) ? " @{$column} = @{$column}_value" : "@{$column}_value";
+ }
+ }
+ return t('@field is set to "!value"', array('@field' => $instance['label'], '!value' => format_string($string, array_merge($keys, $values))));
+}
diff --git a/sites/all/modules/ctools/plugins/access/front.inc b/sites/all/modules/ctools/plugins/access/front.inc
new file mode 100644
index 000000000..1bbc6e057
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/front.inc
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based on drupal_is_front_page.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Front page'),
+ 'description' => t('Is this the front page.'),
+ 'callback' => 'ctools_front_ctools_access_check',
+ 'default' => array('negate' => 0),
+ 'settings form' => 'ctools_front_ctools_access_settings',
+ 'summary' => 'ctools_front_ctools_access_summary',
+);
+
+/**
+ * Settings form for the 'by parent term' access plugin
+ */
+function ctools_front_ctools_access_settings($form, &$form_state, $conf) {
+ // No additional configuration necessary.
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_front_ctools_access_check($conf, $context) {
+ if (drupal_is_front_page()) {
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Provide a summary description based upon the checked terms.
+ */
+function ctools_front_ctools_access_summary($conf, $context) {
+ return t('The front page');
+}
diff --git a/sites/all/modules/ctools/plugins/access/node.inc b/sites/all/modules/ctools/plugins/access/node.inc
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/node.inc
diff --git a/sites/all/modules/ctools/plugins/access/node_access.inc b/sites/all/modules/ctools/plugins/access/node_access.inc
new file mode 100644
index 000000000..fcd275d94
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/node_access.inc
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon node type.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node: accessible"),
+ 'description' => t('Control access with built in Drupal node access test.'),
+ 'callback' => 'ctools_node_access_ctools_access_check',
+ 'default' => array('type' => 'view'),
+ 'settings form' => 'ctools_node_access_ctools_access_settings',
+ 'settings form submit' => 'ctools_node_access_ctools_access_settings_submit',
+ 'summary' => 'ctools_node_access_ctools_access_summary',
+ 'required context' => array(
+ new ctools_context_required(t('User'), 'user'),
+ new ctools_context_required(t('Node'), 'node'),
+ ),
+);
+
+/**
+ * Settings form for the 'by node_access' access plugin
+ */
+function ctools_node_access_ctools_access_settings($form, &$form_state, $conf) {
+ $form['settings']['type'] = array(
+ '#title' => t('Operation'),
+ '#type' => 'radios',
+ '#options' => array(
+ 'view' => t('View'),
+ 'update' => t('Update'),
+ 'delete' => t('Delete'),
+ 'create' => t('Create nodes of the same type'),
+ ),
+ '#description' => t('Using built in Drupal node access rules, determine if the user can perform the selected operation on the node.'),
+ '#default_value' => $conf['type'],
+ );
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_node_access_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ list($user_context, $node_context) = $context;
+ if (empty($node_context) || empty($node_context->data) || empty($node_context->data->type)) {
+ return FALSE;
+ }
+
+ if (empty($user_context) || empty($user_context->data)) {
+ return FALSE;
+ }
+
+ if ($conf['type'] == 'create') {
+ return node_access('create', $node_context->data->type, $user_context->data);
+ }
+ else {
+ return node_access($conf['type'], $node_context->data, $user_context->data);
+ }
+}
+
+/**
+ * Provide a summary description based upon the checked node_accesss.
+ */
+function ctools_node_access_ctools_access_summary($conf, $context) {
+ list($user_context, $node_context) = $context;
+ $replacement = array('@user' => $user_context->identifier, '@node' => $node_context->identifier);
+
+ switch ($conf['type']) {
+ case 'view':
+ return t('@user can view @node.', $replacement);
+
+ case 'update':
+ return t('@user can edit @node.', $replacement);
+
+ case 'delete':
+ return t('@user can delete @node.', $replacement);
+
+ case 'create':
+ return t('@user can create nodes of the same type as @node.', $replacement);
+ }
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/node_comment.inc b/sites/all/modules/ctools/plugins/access/node_comment.inc
new file mode 100644
index 000000000..915ee20e2
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/node_comment.inc
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @file
+ * Plugin to provide access control based upon node comment status.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node: comments are open"),
+ 'description' => t('Control access by the nodes comment status.'),
+ 'callback' => 'ctools_node_comment_ctools_access_check',
+ 'summary' => 'ctools_node_comment_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+);
+
+/**
+ * Checks for access.
+ */
+function ctools_node_comment_ctools_access_check($conf, $context) {
+ return (!empty($context->data) && $context->data->comment == 2);
+}
+
+/**
+ * Provides a summary description based upon the checked node_status.
+ */
+function ctools_node_comment_ctools_access_summary($conf, $context) {
+ return t('Returns true if the nodes comment status is "open".');
+}
diff --git a/sites/all/modules/ctools/plugins/access/node_language.inc b/sites/all/modules/ctools/plugins/access/node_language.inc
new file mode 100644
index 000000000..0fdcfc66a
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/node_language.inc
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon node type.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+if (module_exists('locale')) {
+ $plugin = array(
+ 'title' => t("Node: language"),
+ 'description' => t('Control access by node language.'),
+ 'callback' => 'ctools_node_language_ctools_access_check',
+ 'default' => array('language' => array()),
+ 'settings form' => 'ctools_node_language_ctools_access_settings',
+ 'settings form submit' => 'ctools_node_language_ctools_access_settings_submit',
+ 'summary' => 'ctools_node_language_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ );
+}
+
+/**
+ * Settings form for the 'by node_language' access plugin
+ */
+function ctools_node_language_ctools_access_settings($form, &$form_state, $conf) {
+ $options = array(
+ 'current' => t('Current site language'),
+ 'default' => t('Default site language'),
+ 'no_language' => t('No language'),
+ );
+ $options = array_merge($options, locale_language_list());
+ $form['settings']['language'] = array(
+ '#title' => t('Language'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('Pass only if the node is in one of the selected languages.'),
+ '#default_value' => $conf['language'],
+ );
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_node_language_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || !isset($context->data->language)) {
+ return FALSE;
+ }
+
+ global $language;
+
+ // Specialcase: if 'no language' is checked, return TRUE if the language field is
+ // empty.
+ if (!empty($conf['language']['no_language'])) {
+ if (empty($context->data->language)) {
+ return TRUE;
+ }
+ }
+
+ // Specialcase: if 'current' is checked, return TRUE if the current site language
+ // matches the node language.
+ if (!empty($conf['language']['current'])) {
+ if ($context->data->language == $language->language) {
+ return TRUE;
+ }
+ }
+
+ // Specialcase: If 'default' is checked, return TRUE if the default site language
+ // matches the node language.
+ if (!empty($conf['language']['default'])) {
+ if ($context->data->language == language_default('language')) {
+ return TRUE;
+ }
+ }
+
+ if (array_filter($conf['language']) && empty($conf['language'][$context->data->language])) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Provide a summary description based upon the checked node_languages.
+ */
+function ctools_node_language_ctools_access_summary($conf, $context) {
+ $languages = array(
+ 'current' => t('Current site language'),
+ 'default' => t('Default site language'),
+ 'no_language' => t('No language'),
+ );
+ $languages = array_merge($languages, locale_language_list());
+
+ if (!isset($conf['language'])) {
+ $conf['language'] = array();
+ }
+
+ $names = array();
+ foreach (array_filter($conf['language']) as $language) {
+ $names[] = $languages[$language];
+ }
+
+ if (empty($names)) {
+ return t('@identifier is in any language', array('@identifier' => $context->identifier));
+ }
+
+ return format_plural(count($names), '@identifier language is "@languages"', '@identifier language is one of "@languages"', array('@languages' => implode(', ', $names), '@identifier' => $context->identifier));
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/node_status.inc b/sites/all/modules/ctools/plugins/access/node_status.inc
new file mode 100644
index 000000000..ad5ba4009
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/node_status.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon node (un)published status.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node: (un)published"),
+ 'description' => t('Control access by the nodes published status.'),
+ 'callback' => 'ctools_node_status_ctools_access_check',
+ 'summary' => 'ctools_node_status_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+);
+
+/**
+ * Check for access.
+ */
+function ctools_node_status_ctools_access_check($conf, $context) {
+ return (!empty($context->data) && $context->data->status);
+}
+
+/**
+ * Provide a summary description based upon the checked node_statuss.
+ */
+function ctools_node_status_ctools_access_summary($conf, $context) {
+ return t('Returns true if the nodes status is "published".');
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/node_type.inc b/sites/all/modules/ctools/plugins/access/node_type.inc
new file mode 100644
index 000000000..23a38453a
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/node_type.inc
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon node type.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node: type"),
+ 'description' => t('Control access by node_type.'),
+ 'callback' => 'ctools_node_type_ctools_access_check',
+ 'default' => array('type' => array()),
+ 'settings form' => 'ctools_node_type_ctools_access_settings',
+ 'settings form submit' => 'ctools_node_type_ctools_access_settings_submit',
+ 'summary' => 'ctools_node_type_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'restrictions' => 'ctools_node_type_ctools_access_restrictions',
+);
+
+/**
+ * Settings form for the 'by node_type' access plugin
+ */
+function ctools_node_type_ctools_access_settings($form, &$form_state, $conf) {
+ $types = node_type_get_types();
+ foreach ($types as $type => $info) {
+ $options[$type] = check_plain($info->name);
+ }
+
+ $form['settings']['type'] = array(
+ '#title' => t('Node type'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('Only the checked node types will be valid.'),
+ '#default_value' => $conf['type'],
+ );
+ return $form;
+}
+
+/**
+ * Compress the node_types allowed to the minimum.
+ */
+function ctools_node_type_ctools_access_settings_submit($form, &$form_state) {
+ $form_state['values']['settings']['type'] = array_filter($form_state['values']['settings']['type']);
+}
+
+/**
+ * Check for access.
+ */
+function ctools_node_type_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || empty($context->data->type)) {
+ return FALSE;
+ }
+
+ if (array_filter($conf['type']) && empty($conf['type'][$context->data->type])) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Inform the UI that we've eliminated a bunch of possibilities for this
+ * context.
+ */
+function ctools_node_type_ctools_access_restrictions($conf, &$context) {
+ if (isset($context->restrictions['type'])) {
+ $context->restrictions['type'] = array_unique(array_merge($context->restrictions['type'], array_keys(array_filter($conf['type']))));
+ }
+ else {
+ $context->restrictions['type'] = array_keys(array_filter($conf['type']));
+ }
+}
+
+/**
+ * Provide a summary description based upon the checked node_types.
+ */
+function ctools_node_type_ctools_access_summary($conf, $context) {
+ if (!isset($conf['type'])) {
+ $conf['type'] = array();
+ }
+ $types = node_type_get_types();
+
+ $names = array();
+ // If a node type doesn't exist, let the user know, but prevent a notice.
+ $missing_types = array();
+
+ foreach (array_filter($conf['type']) as $type) {
+ if (!empty($types[$type])) {
+ $names[] = check_plain($types[$type]->name);
+ }
+ else {
+ $missing_types[] = check_plain($type);
+ }
+ }
+
+ if (empty($names) && empty($missing_types)) {
+ return t('@identifier is any node type', array('@identifier' => $context->identifier));
+ }
+
+ if (!empty($missing_types)) {
+ $output = array();
+ if (!empty($names)) {
+ $output[] = format_plural(count($names), '@identifier is type "@types"', '@identifier type is one of "@types"', array('@types' => implode(', ', $names), '@identifier' => $context->identifier));
+ }
+ $output[] = format_plural(count($missing_types), 'Missing/ deleted type "@types"', 'Missing/ deleted type is one of "@types"', array('@types' => implode(', ', $missing_types)));
+ return implode(' | ', $output);
+ }
+
+ return format_plural(count($names), '@identifier is type "@types"', '@identifier type is one of "@types"', array('@types' => implode(', ', $names), '@identifier' => $context->identifier));
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/path_visibility.inc b/sites/all/modules/ctools/plugins/access/path_visibility.inc
new file mode 100644
index 000000000..60b86124e
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/path_visibility.inc
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control/visibility based on path.
+ */
+
+$plugin = array(
+ 'title' => t('String: URL path'),
+ 'description' => t('Control access by the current path.'),
+ 'callback' => 'ctools_path_visibility_ctools_access_check',
+ 'settings form' => 'ctools_path_visibility_ctools_access_settings',
+ 'summary' => 'ctools_path_visibility_ctools_access_summary',
+ 'required context' => new ctools_context_optional(t('Path'), 'string'),
+ 'default' => array('visibility_setting' => 1, 'paths' => ''),
+);
+
+/**
+ * Settings form
+ */
+function ctools_path_visibility_ctools_access_settings($form, &$form_state, $conf) {
+ $form['settings']['note'] = array(
+ '#value' => '<div class="description">' . t('Note: if no context is chosen, the current page path will be used.') . '</div>',
+ );
+
+ $form['settings']['visibility_setting'] = array(
+ '#type' => 'radios',
+ '#options' => array(
+ 1 => t('Allow access on the following pages'),
+ 0 => t('Allow access on all pages except the following pages'),
+ ),
+ '#default_value' => $conf['visibility_setting'],
+ );
+
+ $form['settings']['paths'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Paths'),
+ '#default_value' => $conf['paths'],
+ '#description' => t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>')),
+ );
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_path_visibility_ctools_access_check($conf, $context) {
+ if (isset($context->data)) {
+ $base_path = $context->data;
+ }
+ else {
+ $base_path = $_GET['q'];
+ }
+
+ $path = drupal_get_path_alias($base_path);
+ $page_match = drupal_match_path($path, $conf['paths']);
+
+ // If there's a path alias, we may still be at the un-aliased path
+ // so check that as well.
+ if (!isset($context->data) && $path != $base_path) {
+ $page_match = $page_match || drupal_match_path($base_path, $conf['paths']);
+ }
+
+ // When $conf['visibility_setting'] has a value of 0, the block is displayed
+ // on all pages except those listed in $block->pages. When set to 1, it
+ // is displayed only on those pages listed in $block->pages.
+ $page_match = !($conf['visibility_setting'] xor $page_match);
+
+ return $page_match;
+}
+
+/**
+ * Provide a summary description.
+ */
+function ctools_path_visibility_ctools_access_summary($conf, $context) {
+ $paths = array();
+ foreach (explode("\n", $conf['paths']) as $path) {
+ $paths[] = check_plain($path);
+ }
+
+ $identifier = $context->type == 'any' ? t('Current path') : $context->identifier;
+ if ($conf['visibility_setting']) {
+ return format_plural(count($paths), '@identifier is "@paths"', '@identifier type is one of "@paths"', array('@paths' => implode(', ', $paths), '@identifier' => $identifier));
+ }
+ else {
+ return format_plural(count($paths), '@identifier is not "@paths"', '@identifier type is not one of "@paths"', array('@paths' => implode(', ', $paths), '@identifier' => $identifier));
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/access/perm.inc b/sites/all/modules/ctools/plugins/access/perm.inc
new file mode 100644
index 000000000..67516faf6
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/perm.inc
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based on user permission strings.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("User: permission"),
+ 'description' => t('Control access by permission string.'),
+ 'callback' => 'ctools_perm_ctools_access_check',
+ 'default' => array('perm' => 'access content'),
+ 'settings form' => 'ctools_perm_ctools_access_settings',
+ 'summary' => 'ctools_perm_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+);
+
+/**
+ * Settings form for the 'by perm' access plugin
+ */
+function ctools_perm_ctools_access_settings($form, &$form_state, $conf) {
+ $perms = array();
+ // Get list of permissions
+ foreach (module_list(FALSE, FALSE, TRUE) as $module) {
+ // By keeping them keyed by module we can use optgroups with the
+ // 'select' type.
+ if ($permissions = module_invoke($module, 'permission')) {
+ foreach ($permissions as $id => $permission) {
+ $perms[$module][$id] = $permission['title'];
+ }
+ }
+ }
+
+ $form['settings']['perm'] = array(
+ '#type' => 'select',
+ '#options' => $perms,
+ '#title' => t('Permission'),
+ '#default_value' => $conf['perm'],
+ '#description' => t('Only users with the selected permission flag will be able to access this.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_perm_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data)) {
+ return FALSE;
+ }
+
+ return user_access($conf['perm'], $context->data);
+}
+
+/**
+ * Provide a summary description based upon the checked roles.
+ */
+function ctools_perm_ctools_access_summary($conf, $context) {
+ if (!isset($conf['perm'])) {
+ return t('Error, unset permission');
+ }
+
+ $permissions = module_invoke_all('permission');
+ return t('@identifier has "@perm"', array('@identifier' => $context->identifier, '@perm' => $permissions[$conf['perm']]['title']));
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/php.inc b/sites/all/modules/ctools/plugins/access/php.inc
new file mode 100644
index 000000000..35da86d9a
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/php.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based on evaluated PHP.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("PHP Code"),
+ 'description' => t('Control access through arbitrary PHP code.'),
+ 'callback' => 'ctools_php_ctools_access_check',
+ 'default' => array('description' => '', 'php' => ''),
+ 'settings form' => 'ctools_php_ctools_access_settings',
+ 'summary' => 'ctools_php_ctools_access_summary',
+ 'all contexts' => TRUE,
+);
+
+/**
+ * Settings form for the 'by perm' access plugin
+ *
+ * @todo Need a way to provide a list of all available contexts to be used by
+ * the eval-ed PHP.
+ */
+function ctools_php_ctools_access_settings($form, &$form_state, $conf) {
+ $perms = array();
+
+ $form['settings']['description'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Administrative desc'),
+ '#default_value' => $conf['description'],
+ '#description' => t('A description for this test for administrative purposes.'),
+ );
+ $form['settings']['php'] = array(
+ '#type' => 'textarea',
+ '#title' => t('PHP Code'),
+ '#default_value' => $conf['php'],
+ '#description' => t('Access will be granted if the following PHP code returns <code>TRUE</code>. Do not include &lt;?php ?&gt;. Note that executing incorrect PHP-code can break your Drupal site. All contexts will be available in the <em>$contexts</em> variable.'),
+ );
+ if (!user_access('use PHP for settings')) {
+ $form['settings']['php']['#disabled'] = TRUE;
+ $form['settings']['php']['#value'] = $conf['php'];
+ $form['settings']['php']['#description'] .= ' ' . t('You do not have sufficient permissions to edit PHP code.');
+ }
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_php_ctools_access_check($__conf, $contexts) {
+ $access = eval($__conf['php']);
+ return $access;
+}
+
+/**
+ * Provide a summary description based upon the checked roles.
+ */
+function ctools_php_ctools_access_summary($conf, $contexts) {
+ return !empty($conf['description']) ? check_plain($conf['description']) : t('No description');
+}
diff --git a/sites/all/modules/ctools/plugins/access/role.inc b/sites/all/modules/ctools/plugins/access/role.inc
new file mode 100644
index 000000000..b6332544f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/role.inc
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon role membership.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("User: role"),
+ 'description' => t('Control access by role.'),
+ 'callback' => 'ctools_role_ctools_access_check',
+ 'default' => array('rids' => array()),
+ 'settings form' => 'ctools_role_ctools_access_settings',
+ 'settings form submit' => 'ctools_role_ctools_access_settings_submit',
+ 'summary' => 'ctools_role_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+);
+
+/**
+ * Settings form for the 'by role' access plugin
+ */
+function ctools_role_ctools_access_settings($form, &$form_state, $conf) {
+ $form['settings']['rids'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Role'),
+ '#default_value' => $conf['rids'],
+ '#options' => ctools_get_roles(),
+ '#description' => t('Only the checked roles will be granted access.'),
+ );
+ return $form;
+}
+
+/**
+ * Compress the roles allowed to the minimum.
+ */
+function ctools_role_ctools_access_settings_submit($form, &$form_state) {
+ $form_state['values']['settings']['rids'] = array_keys(array_filter($form_state['values']['settings']['rids']));
+}
+
+/**
+ * Check for access.
+ */
+function ctools_role_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || !isset($context->data->roles)) {
+ return FALSE;
+ }
+
+ $roles = array_keys($context->data->roles);
+ $roles[] = $context->data->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
+ return (bool) array_intersect($conf['rids'], $roles);
+}
+
+/**
+ * Provide a summary description based upon the checked roles.
+ */
+function ctools_role_ctools_access_summary($conf, $context) {
+ if (!isset($conf['rids'])) {
+ $conf['rids'] = array();
+ }
+ $roles = ctools_get_roles();
+
+ $names = array();
+ foreach (array_filter($conf['rids']) as $rid) {
+ $names[] = check_plain($roles[$rid]);
+ }
+
+ if (empty($names)) {
+ return t('@identifier can have any role', array('@identifier' => $context->identifier));
+ }
+
+ return format_plural(count($names), '@identifier has role "@roles"', '@identifier has one of "@roles"', array('@roles' => implode(', ', $names), '@identifier' => $context->identifier));
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/site_language.inc b/sites/all/modules/ctools/plugins/access/site_language.inc
new file mode 100644
index 000000000..9ff2f70c8
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/site_language.inc
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon node type.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+if (module_exists('locale')) {
+ $plugin = array(
+ 'title' => t("User: language"),
+ 'description' => t('Control access by the language the user or site currently uses.'),
+ 'callback' => 'ctools_site_language_ctools_access_check',
+ 'default' => array('language' => array()),
+ 'settings form' => 'ctools_site_language_ctools_access_settings',
+ 'settings form submit' => 'ctools_site_language_ctools_access_settings_submit',
+ 'summary' => 'ctools_site_language_ctools_access_summary',
+ );
+}
+
+/**
+ * Settings form for the 'by site_language' access plugin
+ */
+function ctools_site_language_ctools_access_settings($form, &$form_state, $conf) {
+ $options = array(
+ 'default' => t('Default site language'),
+ );
+ $options = array_merge($options, locale_language_list());
+ $form['settings']['language'] = array(
+ '#title' => t('Language'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('Pass only if the current site language is one of the selected languages.'),
+ '#default_value' => $conf['language'],
+ );
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_site_language_ctools_access_check($conf, $context) {
+ global $language;
+
+ // Specialcase: If 'default' is checked, return TRUE if the default site language
+ // matches the node language.
+ if (!empty($conf['language']['default'])) {
+ if ($language->language == language_default('language')) {
+ return TRUE;
+ }
+ }
+
+ if (array_filter($conf['language']) && empty($conf['language'][$language->language])) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Provide a summary description based upon the checked site_languages.
+ */
+function ctools_site_language_ctools_access_summary($conf, $context) {
+ $languages = array(
+ 'default' => t('Default site language'),
+ );
+ $languages = array_merge($languages, locale_language_list());
+
+ if (!isset($conf['language'])) {
+ $conf['language'] = array();
+ }
+
+ $names = array();
+ foreach (array_filter($conf['language']) as $language) {
+ $names[] = $languages[$language];
+ }
+
+ if (empty($names)) {
+ return t('Site language is any language');
+ }
+
+ return format_plural(count($names), 'Site language is "@languages"', 'Site language is one of "@languages"', array('@languages' => implode(', ', $names)));
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/string_equal.inc b/sites/all/modules/ctools/plugins/access/string_equal.inc
new file mode 100644
index 000000000..ad1c88d82
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/string_equal.inc
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control/visibility based on specified context string matching user-specified string
+ */
+
+$plugin = array(
+ 'title' => t("String: comparison"),
+ 'description' => t('Control access by string match.'),
+ 'callback' => 'ctools_string_equal_ctools_access_check',
+ 'settings form' => 'ctools_string_equal_ctools_access_settings',
+ 'summary' => 'ctools_string_equal_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('String'), 'string'),
+ 'defaults' => array('operator' => '=', 'value' => '', 'case' => FALSE),
+);
+
+/**
+ * Settings form
+ */
+function ctools_string_equal_ctools_access_settings($form, &$form_state, $conf) {
+ $form['settings']['operator'] = array(
+ '#type' => 'radios',
+ '#title' => t('Operator'),
+ '#options' => array(
+ '=' => t('Equal'),
+ '!=' => t('Not equal'),
+ 'regex' => t('Regular expression'),
+ '!regex' => t('Not equal to regular expression'),
+ ),
+ '#default_value' => $conf['operator'],
+ '#description' => t('If using a regular expression, you should enclose the pattern in slashes like so: <em>/foo/</em>. If you need to compare against slashes you can use another character to enclose the pattern, such as @. See <a href="http://www.php.net/manual/en/reference.pcre.pattern.syntax.php">PHP regex documentation</a> for more.'),
+ );
+
+ $form['settings']['value'] = array(
+ '#type' => 'textfield',
+ '#title' => t('String'),
+ '#default_value' => $conf['value'],
+ );
+
+ $form['settings']['case'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Case sensitive'),
+ '#default_value' => $conf['case'],
+ );
+ return $form;
+}
+
+/**
+ * Check for access
+ */
+function ctools_string_equal_ctools_access_check($conf, $context) {
+ if (empty($context) || empty($context->data)) {
+ $string = '';
+ }
+ else {
+ $string = $context->data;
+ }
+
+ $value = $conf['value'];
+ if (empty($conf['case'])) {
+ $string = drupal_strtolower($string);
+ $value = drupal_strtolower($value);
+ }
+
+ switch ($conf['operator']) {
+ case '=':
+ return $string === $value;
+ case '!=':
+ return $string !== $value;
+ case 'regex':
+ return preg_match($value, $string);
+ case '!regex':
+ return !preg_match($value, $string);
+ }
+}
+
+/**
+ * Provide a summary description based upon the specified context
+ */
+function ctools_string_equal_ctools_access_summary($conf, $context) {
+ $values = array('@identifier' => $context->identifier, '@value' => $conf['value']);
+ switch ($conf['operator']) {
+ case '=':
+ return t('@identifier is "@value"', $values);
+ case '!=':
+ return t('@identifier is not "@value"', $values);
+ case 'regex':
+ return t('@identifier matches "@value"', $values);
+ case '!regex':
+ return t('@identifier does not match "@value"', $values);
+ }
+}
+
diff --git a/sites/all/modules/ctools/plugins/access/string_length.inc b/sites/all/modules/ctools/plugins/access/string_length.inc
new file mode 100644
index 000000000..91abf2276
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/string_length.inc
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control/visibility based on length of
+ * a string context.
+ */
+
+$plugin = array(
+ 'title' => t("String: length"),
+ 'description' => t('Control access by length of string context.'),
+ 'callback' => 'ctools_string_length_ctools_access_check',
+ 'settings form' => 'ctools_string_length_ctools_access_settings',
+ 'summary' => 'ctools_string_length_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('String'), 'string'),
+ 'defaults' => array('operator' => '=', 'length' => 0),
+);
+
+/**
+ * Settings form for the 'by role' access plugin.
+ */
+function ctools_string_length_ctools_access_settings($form, &$form_state, $conf) {
+ $form['settings']['operator'] = array(
+ '#type' => 'radios',
+ '#title' => t('Operator'),
+ '#options' => array(
+ '>' => t('Greater than'),
+ '>=' => t('Greater than or equal to'),
+ '=' => t('Equal to'),
+ '!=' => t('Not equal to'),
+ '<' => t('Less than'),
+ '<=' => t('Less than or equal to'),
+ ),
+ '#default_value' => $conf['operator'],
+ );
+ $form['settings']['length'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Length of string'),
+ '#size' => 3,
+ '#default_value' => $conf['length'],
+ '#description' => t('Access/visibility will be granted based on string context length.'),
+ );
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_string_length_ctools_access_check($conf, $context) {
+ if (empty($context) || empty($context->data)) {
+ $length = 0;
+ }
+ else {
+ $length = drupal_strlen($context->data);
+ }
+
+ switch ($conf['operator']) {
+ case '<':
+ return $length < $conf['length'];
+ case '<=':
+ return $length <= $conf['length'];
+ case '=':
+ return $length == $conf['length'];
+ case '!=':
+ return $length != $conf['length'];
+ case '>':
+ return $length > $conf['length'];
+ case '>=':
+ return $length >= $conf['length'];
+ }
+ // Invalid Operator sent, return FALSE.
+ return FALSE;
+}
+
+/**
+ * Provide a summary description based upon the checked roles.
+ */
+function ctools_string_length_ctools_access_summary($conf, $context) {
+ return t('@identifier must be @comp @length characters', array('@identifier' => $context->identifier, '@comp' => $conf['operator'], '@length' => $conf['length']));
+}
diff --git a/sites/all/modules/ctools/plugins/access/term.inc b/sites/all/modules/ctools/plugins/access/term.inc
new file mode 100644
index 000000000..36e70de47
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/term.inc
@@ -0,0 +1,129 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon specific terms.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy: term"),
+ 'description' => t('Control access by a specific term.'),
+ 'callback' => 'ctools_term_ctools_access_check',
+ 'default' => array('vids' => array()),
+ 'settings form' => 'ctools_term_ctools_access_settings',
+ 'settings form validation' => 'ctools_term_ctools_access_settings_validate',
+ 'settings form submit' => 'ctools_term_ctools_access_settings_submit',
+ 'summary' => 'ctools_term_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Term'), array('taxonomy_term', 'terms')),
+);
+
+/**
+ * Settings form for the 'by term' access plugin
+ */
+function ctools_term_ctools_access_settings($form, &$form_state, $conf) {
+ // If no configuration was saved before, set some defaults.
+ if (empty($conf)) {
+ $conf = array(
+ 'vid' => 0,
+ );
+ }
+ if (!isset($conf['vid'])) {
+ $conf['vid'] = 0;
+ }
+
+ $form['settings']['vid'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'select',
+ '#options' => array(),
+ '#description' => t('Select the vocabulary for this form.'),
+ '#id' => 'ctools-select-vid',
+ '#default_value' => $conf['vid'],
+ '#required' => TRUE,
+ );
+
+ ctools_include('dependent');
+ $options = array();
+
+ // A note: Dependency works strangely on these forms as they have never been
+ // updated to a more modern system so they are not individual forms of their
+ // own like the content types.
+
+ $form['settings']['#tree'] = TRUE;
+
+ // Loop over each of the configured vocabularies.
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ $form['settings'][$vocabulary->vid] = array(
+ '#title' => t('Terms'),
+ '#description' => t('Select a term or terms from @vocabulary.', array('@vocabulary' => $vocabulary->name)), //. $description,
+ '#dependency' => array('ctools-select-vid' => array($vocabulary->vid)),
+ '#default_value' => !empty($conf[$vid]) ? $conf[$vid] : '',
+ '#multiple' => TRUE,
+ );
+
+ $terms = array();
+ foreach (taxonomy_get_tree($vocabulary->vid) as $tid => $term) {
+ $terms[$term->tid] = str_repeat('-', $term->depth) . ($term->depth ? ' ' : '') . $term->name;
+ }
+ $form['settings'][$vocabulary->vid]['#type'] = 'select';
+ $form['settings'][$vocabulary->vid]['#options'] = $terms;
+ unset($terms);
+ }
+ $form['settings']['vid']['#options'] = $options;
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_term_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || empty($context->data->vid) || empty($context->data->tid)) {
+ return FALSE;
+ }
+
+ // Get the $vid.
+ if (!isset($conf['vid'])) {
+ return FALSE;
+ }
+ $vid = $conf['vid'];
+
+ // Get the terms.
+ if (!isset($conf[$vid])) {
+ return FALSE;
+ }
+
+ $return = FALSE;
+
+ $terms = array_filter($conf[$vid]);
+ // For multi-term if any terms coincide, let's call that good enough:
+ if (isset($context->tids)) {
+ return (bool) array_intersect($terms, $context->tids);
+ }
+ else {
+ return in_array($context->data->tid, $terms);
+ }
+}
+
+/**
+ * Provide a summary description based upon the checked terms.
+ */
+function ctools_term_ctools_access_summary($conf, $context) {
+ $vid = $conf['vid'];
+ $terms = array();
+ foreach ($conf[$vid] as $tid) {
+ $term = taxonomy_term_load($tid);
+ $terms[] = $term->name;
+ }
+
+ return format_plural(count($terms),
+ '@term can be the term "@terms"',
+ '@term can be one of these terms: @terms',
+ array('@terms' => implode(', ', $terms),
+ '@term' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/access/term_has_parent.inc b/sites/all/modules/ctools/plugins/access/term_has_parent.inc
new file mode 100644
index 000000000..a079e92af
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/term_has_parent.inc
@@ -0,0 +1,172 @@
+<?php
+/**
+ * @file
+ * Plugin to provide access control based upon a parent term.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy: term has parent(s)"),
+ 'description' => t('Control access if a term belongs to a specific parent term.'),
+ 'callback' => 'ctools_term_has_parent_ctools_access_check',
+ 'default' => array('vid' => array(), 'negate' => 0),
+ 'settings form' => 'ctools_term_has_parent_ctools_access_settings',
+ 'settings form submit' => 'ctools_term_has_parent_ctools_access_settings_submit',
+ 'summary' => 'ctools_term_has_parent_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Term'), array('taxonomy_term', 'terms')),
+);
+
+/**
+ * Settings form for the 'by parent term' access plugin
+ */
+function ctools_term_has_parent_ctools_access_settings($form, &$form_state, $conf) {
+ // If no configuration was saved before, set some defaults.
+ if (empty($conf)) {
+ $conf = array(
+ 'vid' => 0,
+ );
+ }
+ if (!isset($conf['vid'])) {
+ $conf['vid'] = 0;
+ }
+
+ $form['settings']['vid'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'select',
+ '#options' => array(),
+ '#description' => t('Select the vocabulary for this form.'),
+ '#id' => 'ctools-select-vid',
+ '#default_value' => $conf['vid'],
+ '#required' => TRUE,
+ );
+
+ ctools_include('dependent');
+ $options = array();
+
+ // A note: Dependency works strangely on these forms as they have never been
+ // updated to a more modern system so they are not individual forms of their
+ // own like the content types.
+
+ $form['settings']['#tree'] = TRUE;
+
+ // Loop over each of the configured vocabularies.
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ $form['settings']['vid_' . $vid] = array(
+ '#title' => t('Terms'),
+ '#description' => t('Select a term or terms from @vocabulary.', array('@vocabulary' => $vocabulary->name)),
+ '#dependency' => array('ctools-select-vid' => array($vocabulary->vid)),
+ '#default_value' => !empty($conf['vid_' . $vid]) ? $conf['vid_' . $vid] : '',
+ '#size' => 10,
+ '#multiple' => TRUE,
+ //@todo: Remove the following workaround when the following patch is in core. {@see:http://drupal.org/node/1117526}
+ '#name' => sprintf("settings[%u][]", $vid),
+ '#attributes' => array('multiple' => 'multiple'),
+ );
+
+ $terms = array();
+ foreach (taxonomy_get_tree($vocabulary->vid) as $term) {
+ $terms[$term->tid] = str_repeat('-', $term->depth) . ($term->depth ? ' ' : '') . $term->name;
+ }
+ //$form['settings']['vid_' . $vid]['#type'] = 'select';
+ $form['settings']['vid_' . $vid]['#type'] = 'checkboxes';
+ $form['settings']['vid_' . $vid]['#options'] = $terms;
+ unset($terms);
+ }
+ $form['settings']['vid']['#options'] = $options;
+ $form['settings']['include_self'] = array(
+ '#title' => t('Include these term(s) as candidates?'),
+ '#description' => t('When this rule is evaluated, should the term(s) you select be included as candidates for access?'),
+ '#default_value' => !empty($conf['include_self']) ? $conf['include_self'] : FALSE,
+ '#type' => 'checkbox',
+ );
+ return $form;
+}
+
+/**
+ * Filters values to store less.
+ */
+function ctools_term_has_parent_ctools_access_settings_submit($form, &$form_state) {
+ foreach ($form_state['values']['settings'] as $key => $value) {
+ if (strpos($key, 'vid_') === 0) {
+ $form_state['values']['settings'][$key] = array_filter($form_state['values']['settings'][$key]);
+ }
+ }
+}
+
+/**
+ * Check for access.
+ */
+function ctools_term_has_parent_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || empty($context->data->vid) || empty($context->data->tid)) {
+ return FALSE;
+ }
+
+ // Get the $vid.
+ if (!isset($conf['vid'])) {
+ return FALSE;
+ }
+ $vid = $conf['vid'];
+
+ // we'll start looking up the hierarchy from our context term id.
+ $current_term = $context->data->tid;
+
+ $term='';
+
+ // scan up the tree.
+ while (true) {
+ // select parent as term_parent to avoid PHP5 complications with the parent keyword
+ //@todo: Find a way to reduce the number of queries required for really deep hierarchies.
+ $term = db_query("SELECT parent AS term_parent, tid AS tid FROM {taxonomy_term_hierarchy} th WHERE th.tid = :tid", array(':tid'=>$current_term))->fetchObject();
+
+ // if no term is found, get out of the loop
+ if (!$term || empty($term->tid)) {
+ break;
+ }
+
+ // check the term selected, if the user asked it to.
+ if (!empty($conf['include_self']) && isset($conf['vid_' . $vid][$term->tid])) {
+ return TRUE;
+ }
+
+ // did we find the parent TID we were looking for?
+ if (isset($conf['vid_' . $vid][$term->tid])) {
+ // YES, we're done!
+ return TRUE;
+ }
+ // Nope, we didn't find it.
+
+ // If this is the top of the hierarchy, stop scanning.
+ if ($term->term_parent==0) {
+ break;
+ }
+
+ // update the parent, and keep scanning.
+ $current_term = $term->term_parent;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Provide a summary description based upon the checked terms.
+ */
+function ctools_term_has_parent_ctools_access_summary($conf, $context) {
+ $vid = (int)$conf['vid'];
+ $terms = array();
+ foreach ($conf['vid_' . $vid] as $tid) {
+ $term = taxonomy_term_load($tid);
+ $terms[] = $term->name;
+ }
+
+ return format_plural(count($terms),
+ '@term can have the parent "@terms"',
+ '@term can have one of these parents: @terms',
+ array('@terms' => implode(', ', $terms),
+ '@term' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/access/term_parent.inc b/sites/all/modules/ctools/plugins/access/term_parent.inc
new file mode 100644
index 000000000..acbaf8720
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/term_parent.inc
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon a parent term.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy: parent term"),
+ 'description' => t('Control access by existence of a parent term.'),
+ 'callback' => 'ctools_term_parent_ctools_access_check',
+ 'default' => array('vid' => array(), 'negate' => 0),
+ 'settings form' => 'ctools_term_parent_ctools_access_settings',
+ 'settings form validation' => 'ctools_term_parent_ctools_access_settings_validate',
+ 'settings form submit' => 'ctools_term_parent_ctools_access_settings_submit',
+ 'summary' => 'ctools_term_parent_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Term'), array('taxonomy_term', 'terms')),
+);
+
+/**
+ * Settings form for the 'by parent term' access plugin
+ */
+function ctools_term_parent_ctools_access_settings($form, &$form_state, $conf) {
+ // If no configuration was saved before, set some defaults.
+ if (empty($conf)) {
+ $conf = array(
+ 'vid' => 0,
+ );
+ }
+ if (!isset($conf['vid'])) {
+ $conf['vid'] = 0;
+ }
+
+ $form['settings']['vid'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'select',
+ '#options' => array(),
+ '#description' => t('Select the vocabulary for this form. If there exists a parent term in that vocabulary, this access check will succeed.'),
+ '#id' => 'ctools-select-vid',
+ '#default_value' => $conf['vid'],
+ '#required' => TRUE,
+ );
+
+ $options = array();
+
+ // Loop over each of the configured vocabularies.
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ }
+ $form['settings']['vid']['#options'] = $options;
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_term_parent_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || empty($context->data->vid) || empty($context->data->tid)) {
+ return FALSE;
+ }
+
+ // Get the $vid.
+ if (!isset($conf['vid'])) {
+ return FALSE;
+ }
+ $vid = $conf['vid'];
+
+ $count = db_query('SELECT COUNT(*) FROM {taxonomy_term_hierarchy} th INNER JOIN {taxonomy_term_data} td ON th.parent = td.tid WHERE th.tid = :tid AND td.vid = :vid', array(':tid' => $context->data->tid, ':vid' => $vid))->fetchField();
+
+ return $count ? TRUE : FALSE;
+}
+
+/**
+ * Provide a summary description based upon the checked terms.
+ */
+function ctools_term_parent_ctools_access_summary($conf, $context) {
+ $vocab = taxonomy_vocabulary_load($conf['vid']);
+
+ return t('"@term" has parent in vocabulary "@vocab"', array('@term' => $context->identifier, '@vocab' => $vocab->name));
+}
diff --git a/sites/all/modules/ctools/plugins/access/term_vocabulary.inc b/sites/all/modules/ctools/plugins/access/term_vocabulary.inc
new file mode 100644
index 000000000..b003138df
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/term_vocabulary.inc
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon term vocabulary
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy: vocabulary"),
+ 'description' => t('Control access by vocabulary.'),
+ 'callback' => 'ctools_term_vocabulary_ctools_access_check',
+ 'default' => array('vids' => array()),
+ 'settings form' => 'ctools_term_vocabulary_ctools_access_settings',
+ 'settings form submit' => 'ctools_term_vocabulary_ctools_access_settings_submit',
+ 'summary' => 'ctools_term_vocabulary_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Vocabulary'), array(
+ 'taxonomy_term',
+ 'terms',
+ 'taxonomy_vocabulary'
+ )),
+);
+
+/**
+ * Settings form for the 'by term_vocabulary' access plugin
+ */
+function ctools_term_vocabulary_ctools_access_settings($form, &$form_state, $conf) {
+ $options = array();
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($vocabularies as $voc) {
+ $options[$voc->machine_name] = check_plain($voc->name);
+ }
+
+ _ctools_term_vocabulary_ctools_access_map_vids($conf);
+
+ $form['settings']['machine_name'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Vocabularies'),
+ '#options' => $options,
+ '#description' => t('Only the checked vocabularies will be valid.'),
+ '#default_value' => $conf['machine_name'],
+ );
+ return $form;
+}
+
+/**
+ * Compress the term_vocabularys allowed to the minimum.
+ */
+function ctools_term_vocabulary_ctools_access_settings_submit($form, &$form_state) {
+ $form_state['values']['settings']['machine_name'] = array_filter($form_state['values']['settings']['machine_name']);
+}
+
+/**
+ * Check for access.
+ */
+function ctools_term_vocabulary_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || empty($context->data->vocabulary_machine_name)) {
+ return FALSE;
+ }
+
+ _ctools_term_vocabulary_ctools_access_map_vids($conf);
+
+ if (array_filter($conf['machine_name']) && empty($conf['machine_name'][$context->data->vocabulary_machine_name])) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Provide a summary description based upon the checked term_vocabularys.
+ */
+function ctools_term_vocabulary_ctools_access_summary($conf, $context) {
+ if (!isset($conf['type'])) {
+ $conf['type'] = array();
+ }
+ $vocabularies = taxonomy_get_vocabularies();
+
+ _ctools_term_vocabulary_ctools_access_map_vids($conf);
+
+ $names = array();
+ if (!empty($conf['machine_name'])) {
+ foreach (array_filter($conf['machine_name']) as $machine_name) {
+ foreach ($vocabularies as $vocabulary) {
+ if ($vocabulary->machine_name === $machine_name) {
+ $names[] = check_plain($vocabulary->name);
+ continue;
+ }
+ }
+ }
+ }
+
+ if (empty($names)) {
+ return t('@identifier is any vocabulary', array('@identifier' => $context->identifier));
+ }
+
+ return format_plural(count($names), '@identifier vocabulary is "@machine_names"', '@identifier vocabulary is one of "@machine_names"', array(
+ '@machine_names' => implode(', ', $names),
+ '@identifier' => $context->identifier
+ ));
+}
+
+/**
+ * Helper function to map the vids from old features to the new machine_name.
+ *
+ * Add the machine_name key to $conf if the vids key exist.
+ *
+ * @param array $conf
+ * The configuration of this plugin.
+ */
+function _ctools_term_vocabulary_ctools_access_map_vids(&$conf) {
+ if (!empty($conf['vids'])) {
+ $conf['machine_name'] = array();
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($conf['vids'] as $vid) {
+ $machine_name = $vocabularies[$vid]->machine_name;
+ $conf['machine_name'][$machine_name] = $vocabularies[$vid]->machine_name;
+ }
+ }
+}
+
+
diff --git a/sites/all/modules/ctools/plugins/access/theme.inc b/sites/all/modules/ctools/plugins/access/theme.inc
new file mode 100644
index 000000000..4f4be6ded
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/access/theme.inc
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based on user themeission strings.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Current theme"),
+ 'description' => t('Control access by checking which theme is in use.'),
+ 'callback' => 'ctools_theme_ctools_access_check',
+ 'default' => array('theme' => variable_get('theme_default', 'garland')),
+ 'settings form' => 'ctools_theme_ctools_access_settings',
+ 'summary' => 'ctools_theme_ctools_access_summary',
+);
+
+/**
+ * Settings form for the 'by theme' access plugin
+ */
+function ctools_theme_ctools_access_settings($form, &$form_state, $conf) {
+ $themes = array();
+ foreach (list_themes() as $key => $theme) {
+ $themes[$key] = $theme->info['name'];
+ }
+
+ $form['settings']['theme'] = array(
+ '#type' => 'select',
+ '#options' => $themes,
+ '#title' => t('Themes'),
+ '#default_value' => $conf['theme'],
+ '#description' => t('This will only be accessed if the current theme is the selected theme.'),
+ );
+ return $form;
+}
+
+/**
+ * Check for access.
+ */
+function ctools_theme_ctools_access_check($conf, $context) {
+ if (!empty($GLOBALS['theme'])) {
+ $theme = $GLOBALS['theme'];
+ }
+ else if (!empty($GLOBALS['custom_theme'])) {
+ $theme = $GLOBALS['custom_theme'];
+ }
+ else if (!empty($GLOBALS['user']->theme)) {
+ $theme = $GLOBALS['user']->theme;
+ }
+ else {
+ $theme = variable_get('theme_default', 'garland');
+ }
+
+ return $conf['theme'] == $theme;
+}
+
+/**
+ * Provide a summary description based upon the checked roles.
+ */
+function ctools_theme_ctools_access_summary($conf, $context) {
+ if (!isset($conf['theme'])) {
+ return t('Error, unset theme');
+ }
+ $themes = list_themes();
+
+ return t('Current theme is "@theme"', array('@theme' => $themes[$conf['theme']]->info['name']));
+}
diff --git a/sites/all/modules/ctools/plugins/arguments/entity_id.inc b/sites/all/modules/ctools/plugins/arguments/entity_id.inc
new file mode 100644
index 000000000..3063fefd2
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/entity_id.inc
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for all entity ids.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Entity: ID"),
+ 'description' => t('Creates an entity context from an entity ID argument.'),
+ 'context' => 'ctools_argument_entity_id_context',
+ 'get child' => 'ctools_argument_entity_id_get_child',
+ 'get children' => 'ctools_argument_entity_id_get_children',
+ 'default' => array(
+ 'entity_id' => '',
+ ),
+ 'placeholder form' => 'ctools_argument_entity_id_ctools_argument_placeholder',
+);
+
+function ctools_argument_entity_id_get_child($plugin, $parent, $child) {
+ $plugins = ctools_argument_entity_id_get_children($plugin, $parent);
+ return $plugins[$parent . ':' . $child];
+}
+
+function ctools_argument_entity_id_get_children($original_plugin, $parent) {
+ $entities = entity_get_info();
+ $plugins = array();
+ foreach ($entities as $entity_type => $entity) {
+ $plugin = $original_plugin;
+ $plugin['title'] = t('@entity: ID', array('@entity' => $entity['label']));
+ $plugin['keyword'] = $entity_type;
+ $plugin['description'] = t('Creates @entity context from an ID argument.', array('@entity' => $entity_type));
+ $plugin['name'] = $parent . ':' . $entity_type;
+ $plugin_id = $parent . ':' . $entity_type;
+ drupal_alter('ctools_entity_context', $plugin, $entity, $plugin_id);
+ $plugins[$plugin_id] = $plugin;
+ }
+ drupal_alter('ctools_entity_contexts', $plugins);
+
+ return $plugins;
+}
+
+/**
+ * Discover if this argument gives us the entity we crave.
+ */
+function ctools_argument_entity_id_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ $entity_type = explode(':', $conf['name']);
+ $entity_type = $entity_type[1];
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('entity:' . $entity_type);
+ }
+
+ // We can accept either an entity object or a pure id.
+ if (is_object($arg)) {
+ return ctools_context_create('entity:' . $entity_type, $arg);
+ }
+
+ // Trim spaces and other garbage.
+ $arg = trim($arg);
+
+ if (!is_numeric($arg)) {
+ $preg_matches = array();
+ $match = preg_match('/\[id: (\d+)\]/', $arg, $preg_matches);
+ if (!$match) {
+ $match = preg_match('/^id: (\d+)/', $arg, $preg_matches);
+ }
+
+ if ($match) {
+ $id = $preg_matches[1];
+ }
+ if (isset($id) && is_numeric($id)) {
+ return ctools_context_create('entity:' . $entity_type, $id);
+ }
+ return FALSE;
+ }
+
+ $entities = entity_load($entity_type, array($arg));
+ if (empty($entities)) {
+ return FALSE;
+ }
+
+ return ctools_context_create('entity:' . $entity_type, reset($entities));
+}
+
+function ctools_argument_entity_id_settings_form(&$form, &$form_state, $conf) {
+ $plugin = &$form_state['plugin'];
+
+ $form['settings']['entity'] = array(
+ '#title' => t('Enter the title or ID of a @entity entity', array('@entity' => $plugin['keyword'])),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#autocomplete_path' => 'ctools/autocomplete/' . $plugin['keyword'],
+ '#weight' => -10,
+ );
+
+ if (!empty($conf['entity_id'])) {
+ $info = entity_load($plugin['keyword'], array($conf['entity_id']));
+ $info = $info[$conf['entity_id']];
+ if ($info) {
+ $entity = entity_get_info($plugin['keyword']);
+ $uri = entity_uri($plugin['keyword'], $info);
+ if (is_array($uri) && $entity['entity keys']['label']) {
+ $link = l(t("'%title' [%type id %id]", array('%title' => $info->{$entity['entity keys']['label']}, '%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), $uri['path'], array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ }
+ elseif (is_array($uri)) {
+ $link = l(t("[%type id %id]", array('%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), $uri['path'], array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ }
+ elseif ($entity['entity keys']['label']) {
+ $link = l(t("'%title' [%type id %id]", array('%title' => $info->{$entity['entity keys']['label']}, '%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), file_create_url($uri), array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ }
+ else {
+ $link = t("[%type id %id]", array('%type' => $plugin['keyword'], '%id' => $conf['entity_id']));
+ }
+ $form['settings']['entity']['#description'] = t('Currently set to !link', array('!link' => $link));
+ }
+ }
+
+ $form['settings']['entity_id'] = array(
+ '#type' => 'value',
+ '#value' => isset($conf['entity_id']) ? $conf['entity_id'] : '',
+ );
+
+ $form['settings']['entity_type'] = array(
+ '#type' => 'value',
+ '#value' => $plugin['keyword'],
+ );
+
+ return $form;
+}
+
+function ctools_argument_entity_id_ctools_argument_placeholder($conf) {
+ $conf = array(
+ '#title' => t('Enter the title or ID of a @entity entity', array('@entity' => $conf['keyword'])),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#autocomplete_path' => 'ctools/autocomplete/' . $conf['keyword'],
+ '#weight' => -10,
+ );
+
+ return $conf;
+}
diff --git a/sites/all/modules/ctools/plugins/arguments/nid.inc b/sites/all/modules/ctools/plugins/arguments/nid.inc
new file mode 100644
index 000000000..9aaec0e1f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/nid.inc
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a node id
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node: ID"),
+ 'keyword' => 'node',
+ 'description' => t('Creates a node context from a node ID argument.'),
+ 'context' => 'ctools_argument_nid_context',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the node ID of a node for this argument'),
+ ),
+ 'no ui' => TRUE,
+);
+
+/**
+ * Discover if this argument gives us the node we crave.
+ */
+function ctools_argument_nid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('node');
+ }
+
+ // We can accept either a node object or a pure nid.
+ if (is_object($arg)) {
+ return ctools_context_create('node', $arg);
+ }
+
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+
+ $node = node_load($arg);
+ if (!$node) {
+ return FALSE;
+ }
+
+ return ctools_context_create('node', $node);
+}
+
diff --git a/sites/all/modules/ctools/plugins/arguments/node_add.inc b/sites/all/modules/ctools/plugins/arguments/node_add.inc
new file mode 100644
index 000000000..c811311b0
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/node_add.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a Node add form
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node add form: node type"),
+ // keyword to use for %substitution
+ 'keyword' => 'node_type',
+ 'description' => t('Creates a node add form context from a node type argument.'),
+ 'context' => 'ctools_node_add_context',
+);
+
+/**
+ * Discover if this argument gives us the node we crave.
+ */
+function ctools_node_add_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if (!isset($arg)) {
+ return ctools_context_create_empty('node_add_form');
+ }
+
+ return ctools_context_create('node_add_form', $arg);
+}
+
diff --git a/sites/all/modules/ctools/plugins/arguments/node_edit.inc b/sites/all/modules/ctools/plugins/arguments/node_edit.inc
new file mode 100644
index 000000000..c7cdf29e8
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/node_edit.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a Node edit form
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node edit form: node ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'node',
+ 'description' => t('Creates a node edit form context from a node ID argument.'),
+ 'context' => 'ctools_node_edit_context',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the node ID of a node for this argument'),
+ ),
+);
+
+/**
+ * Discover if this argument gives us the node we crave.
+ */
+function ctools_node_edit_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('node_edit_form');
+ }
+
+ // We can accept either a node object or a pure nid.
+ if (is_object($arg)) {
+ return ctools_context_create('node_edit_form', $arg);
+ }
+
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+
+ $node = node_load($arg);
+ if (!$node) {
+ return NULL;
+ }
+
+ // This will perform a node_access check, so we don't have to.
+ return ctools_context_create('node_edit_form', $node);
+}
+
diff --git a/sites/all/modules/ctools/plugins/arguments/rid.inc b/sites/all/modules/ctools/plugins/arguments/rid.inc
new file mode 100644
index 000000000..2661153b2
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/rid.inc
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a node revision id
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Revision: ID"),
+ 'keyword' => 'revision',
+ 'description' => t('Creates a node context from a revision ID argument.'),
+ 'context' => 'ctools_argument_rid_context',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the revision ID of a node for this argument'),
+ ),
+);
+
+/**
+ * Discover if this argument gives us the node we crave.
+ */
+function ctools_argument_rid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('node');
+ }
+
+ // We can accept either a node object or a pure nid.
+ if (is_object($arg)) {
+ return ctools_context_create('node', $arg);
+ }
+
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+
+ $nid = db_query('SELECT nid FROM {node_revision} WHERE vid = :vid', array(':vid' => $arg))->fetchField();
+ $node = node_load($nid, $arg);
+ if (!$node) {
+ return FALSE;
+ }
+
+ return ctools_context_create('node', $node);
+}
+
diff --git a/sites/all/modules/ctools/plugins/arguments/string.inc b/sites/all/modules/ctools/plugins/arguments/string.inc
new file mode 100644
index 000000000..ed4ffbb63
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/string.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a raw string
+ */
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("String"),
+ // keyword to use for %substitution
+ 'keyword' => 'string',
+ 'description' => t('A string is a minimal context that simply holds a string that can be used for some other purpose.'),
+ 'settings form' => 'ctools_string_settings_form',
+ 'context' => 'ctools_string_context',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter a value for this argument'),
+ ),
+ 'path placeholder' => 'ctools_string_path_placeholder', // This is in pagemanager.
+);
+
+/**
+ * Discover if this argument gives us the term we crave.
+ */
+function ctools_string_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('string');
+ }
+
+ $context = ctools_context_create('string', $arg);
+ $context->original_argument = $arg;
+
+ return $context;
+}
+
+/**
+ * Settings form for the argument
+ */
+function ctools_string_settings_form(&$form, &$form_state, $conf) {
+ $form['settings']['use_tail'] = array(
+ '#title' => t('Get all arguments after this one'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['use_tail']),
+ '#description' => t('If checked, this string will include all arguments. For example, if the path is "path/%" and the user visits "path/foo/bar", if this is not checked the string will be "foo". If it is checked the string will be "foo/bar".'),
+ );
+// return $form;
+}
+
+/**
+ * Switch the placeholder based upon user settings.
+ */
+function ctools_string_path_placeholder($argument) {
+ if (empty($argument['settings']['use_tail'])) {
+ return '%pm_arg';
+ }
+ else {
+ return '%pm_arg_tail';
+ }
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/plugins/arguments/term.inc b/sites/all/modules/ctools/plugins/arguments/term.inc
new file mode 100644
index 000000000..868c8aa5e
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/term.inc
@@ -0,0 +1,163 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a Taxonomy term
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy term: ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'term',
+ 'description' => t('Creates a single taxonomy term from a taxonomy ID or taxonomy term name.'),
+ 'context' => 'ctools_term_context',
+ 'default' => array('input_form' => 'tid', 'breadcrumb' => TRUE, 'transform' => FALSE),
+ 'settings form' => 'ctools_term_settings_form',
+ 'placeholder form' => 'ctools_term_ctools_argument_placeholder',
+ 'breadcrumb' => 'ctools_term_breadcrumb',
+);
+
+/**
+ * Discover if this argument gives us the term we crave.
+ */
+function ctools_term_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('entity:taxonomy_term');
+ }
+
+ if (is_object($arg)) {
+ $term = $arg;
+ }
+ else {
+ switch ($conf['input_form']) {
+ case 'tid':
+ default:
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+ $term = taxonomy_term_load($arg);
+ break;
+
+ case 'term':
+ if (!empty($conf['transform'])) {
+ $arg = strtr($arg, '-', ' ');
+ }
+
+ $terms = taxonomy_get_term_by_name($arg);
+
+ $conf['vids'] = is_array($conf['vids']) ? array_filter($conf['vids']) : NULL;
+ if ((count($terms) > 1) && isset($conf['vids'])) {
+ foreach ($terms as $potential) {
+ foreach ($conf['vids'] as $vid => $active) {
+ if ($active && $potential->vid == $vid) {
+ $term = $potential;
+ // break out of the foreaches AND the case
+ break 3;
+ }
+ }
+ }
+ }
+ $term = array_shift($terms);
+ break;
+ }
+
+ if (empty($term)) {
+ return NULL;
+ }
+ }
+
+ if (!empty($conf['vids']) && array_filter($conf['vids']) && empty($conf['vids'][$term->vid])) {
+ return NULL;
+ }
+
+ $context = ctools_context_create('entity:taxonomy_term', $term);
+ $context->original_argument = $arg;
+ return $context;
+}
+
+/**
+ * Settings form for the argument
+ */
+function ctools_term_settings_form(&$form, &$form_state, $conf) {
+ // @todo allow synonym use like Views does.
+ $form['settings']['input_form'] = array(
+ '#title' => t('Argument type'),
+ '#type' => 'radios',
+ '#options' => array('tid' => t('Term ID'), 'term' => t('Term name')),
+ '#default_value' => $conf['input_form'],
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ $vocabularies = taxonomy_get_vocabularies();
+ $options = array();
+ foreach ($vocabularies as $vid => $vocab) {
+ $options[$vid] = $vocab->name;
+ }
+ $form['settings']['vids'] = array(
+ '#title' => t('Limit to these vocabularies'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#default_value' => !empty($conf['vids']) ? $conf['vids'] : array(),
+ '#description' => t('If no vocabularies are checked, terms from all vocabularies will be accepted.'),
+ );
+
+ $form['settings']['breadcrumb'] = array(
+ '#title' => t('Inject hierarchy into breadcrumb trail'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['breadcrumb']),
+ '#description' => t('If checked, taxonomy term parents will appear in the breadcrumb trail.'),
+ );
+
+ $form['settings']['transform'] = array(
+ '#title' => t('Transform dashes in URL to spaces in term name filter values'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['transform']),
+ );
+// return $form;
+}
+
+/**
+ * Form fragment to get an argument to convert a placeholder for preview.
+ */
+function ctools_term_ctools_argument_placeholder($conf) {
+ switch ($conf['input_form']) {
+ case 'tid':
+ default:
+ return array(
+ '#type' => 'textfield',
+ '#description' => t('Enter a taxonomy term ID.'),
+ );
+ case 'term':
+ return array(
+ '#type' => 'textfield',
+ '#description' => t('Enter a taxonomy term name.'),
+ );
+ }
+}
+
+/**
+ * Inject the breadcrumb trail if necessary.
+ */
+function ctools_term_breadcrumb($conf, $context) {
+ if (empty($conf['breadcrumb']) || empty($context->data) || empty($context->data->tid)) {
+ return;
+ }
+
+ $breadcrumb = array();
+ $current = new stdClass();
+ $current->tid = $context->data->tid;
+ while ($parents = taxonomy_get_parents($current->tid)) {
+ $current = array_shift($parents);
+ $breadcrumb[] = l($current->name, 'taxonomy/term/' . $current->tid);
+ }
+
+ $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
+ drupal_set_breadcrumb($breadcrumb);
+}
diff --git a/sites/all/modules/ctools/plugins/arguments/terms.inc b/sites/all/modules/ctools/plugins/arguments/terms.inc
new file mode 100644
index 000000000..4298ea91d
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/terms.inc
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a Taxonomy term
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy term (multiple): ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'term',
+ 'description' => t('Creates a group of taxonomy terms from a list of tids separated by a comma or a plus sign. In general the first term of the list will be used for panes.'),
+ 'context' => 'ctools_terms_context',
+ 'default' => array('breadcrumb' => TRUE),
+ 'settings form' => 'ctools_terms_settings_form',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter a term ID or a list of term IDs separated by a + or a ,'),
+ ),
+ 'breadcrumb' => 'ctools_terms_breadcrumb',
+);
+
+/**
+ * Discover if this argument gives us the term we crave.
+ */
+function ctools_terms_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('terms');
+ }
+
+ $terms = ctools_break_phrase($arg);
+ if (empty($terms->value) || !empty($terms->invalid_input)) {
+ return FALSE;
+ }
+
+ $context = ctools_context_create('terms', $terms);
+ $context->original_argument = $arg;
+ return $context;
+}
+
+/**
+ * Settings form for the argument
+ */
+function ctools_terms_settings_form(&$form, &$form_state, $conf) {
+ $form['settings']['breadcrumb'] = array(
+ '#title' => t('Inject hierarchy of first term into breadcrumb trail'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['breadcrumb']),
+ '#description' => t('If checked, taxonomy term parents will appear in the breadcrumb trail.'),
+ );
+// return $form;
+}
+
+/**
+ * Inject the breadcrumb trail if necessary.
+ */
+function ctools_terms_breadcrumb($conf, $context) {
+ if (empty($conf['breadcrumb'])) {
+ return;
+ }
+
+ $current->tid = $context->tids[0];
+ $breadcrumb = array();
+ while ($parents = taxonomy_get_parents($current->tid)) {
+ $current = array_shift($parents);
+ $breadcrumb[] = l($current->name, 'taxonomy/term/' . $current->tid);
+ }
+
+ $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
+ drupal_set_breadcrumb($breadcrumb);
+}
diff --git a/sites/all/modules/ctools/plugins/arguments/uid.inc b/sites/all/modules/ctools/plugins/arguments/uid.inc
new file mode 100644
index 000000000..f9d5315cc
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/uid.inc
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a user id
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("User: ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'user',
+ 'description' => t('Creates a user context from a user ID argument.'),
+ 'context' => 'ctools_argument_uid_context',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the user ID of a user for this argument'),
+ ),
+ 'default' => array('to_arg' => TRUE),
+ 'path placeholder' => '%pm_uid_arg', // This is in pagemanager.
+ 'path placeholder to_arg' => TRUE,
+ 'no ui' => TRUE,
+);
+
+/**
+ * Discover if this argument gives us the user we crave.
+ */
+function ctools_argument_uid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('user');
+ }
+
+ // We can accept either a node object or a pure nid.
+ if (is_object($arg)) {
+ return ctools_context_create('user', $arg);
+ }
+
+ if (!is_numeric($arg)) {
+ return NULL;
+ }
+
+ $account = user_load($arg);
+ if (!$account) {
+ return NULL;
+ }
+
+ return ctools_context_create('user', $account);
+}
diff --git a/sites/all/modules/ctools/plugins/arguments/user_edit.inc b/sites/all/modules/ctools/plugins/arguments/user_edit.inc
new file mode 100644
index 000000000..32b2b812a
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/user_edit.inc
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a Taxonomy term
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("User edit form: User ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'user',
+ 'description' => t('Creates a user edit form context from a user ID argument.'),
+ 'context' => 'ctools_user_edit_context',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the user ID for this argument.'),
+ ),
+);
+
+/**
+ * Discover if this argument gives us the term we crave.
+ */
+function ctools_user_edit_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('user_edit_form');
+ }
+ if(is_object($arg)){
+ return ctools_context_create('user_edit_form', $arg);
+ }
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+
+ $account= user_load($arg);
+ if (!$account) {
+ return NULL;
+ }
+
+ // This will perform a node_access check, so we don't have to.
+ return ctools_context_create('user_edit_form', $account);
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/plugins/arguments/user_name.inc b/sites/all/modules/ctools/plugins/arguments/user_name.inc
new file mode 100644
index 000000000..f6f3b4635
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/user_name.inc
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a username
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("User: name"),
+ // keyword to use for %substitution
+ 'keyword' => 'user',
+ 'description' => t('Creates a user context from a user name.'),
+ 'context' => 'ctools_argument_user_name_context',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the username of a user for this argument'),
+ ),
+);
+
+/**
+ * Discover if this argument gives us the user we crave.
+ */
+function ctools_argument_user_name_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('user');
+ }
+
+ // We can accept either a node object or a pure nid.
+ if (is_object($arg)) {
+ return ctools_context_create('user', $arg);
+ }
+
+ $account = user_load_by_name($arg);
+ if (!$account) {
+ return NULL;
+ }
+ return ctools_context_create('user', $account);
+}
+
+
+
diff --git a/sites/all/modules/ctools/plugins/arguments/vid.inc b/sites/all/modules/ctools/plugins/arguments/vid.inc
new file mode 100644
index 000000000..064b22d0a
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/arguments/vid.inc
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide an argument handler for a vocabulary id
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Vocabulary: ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'vocabulary',
+ 'description' => t('Creates a vocabulary context from a vocabulary ID argument.'),
+ 'context' => 'ctools_vid_context',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the vocabulary ID for this argument'),
+ ),
+ 'no ui' => TRUE,
+);
+
+/**
+ * Discover if this argument gives us the vocabulary we crave.
+ */
+function ctools_vid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return ctools_context_create_empty('entity:taxonomy_vocabulary');
+ }
+
+ if (!is_numeric($arg)) {
+ return NULL;
+ }
+
+ $vocabulary = taxonomy_vocabulary_load($arg);
+ if (!$vocabulary) {
+ return NULL;
+ }
+
+ return ctools_context_create('vocabulary', $vocabulary);
+}
+
diff --git a/sites/all/modules/ctools/plugins/cache/export_ui.inc b/sites/all/modules/ctools/plugins/cache/export_ui.inc
new file mode 100644
index 000000000..53483a535
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/cache/export_ui.inc
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * A caching mechanism for use with subsystems that use the export ui.
+ */
+
+$plugin = array(
+ // cache plugins are the rare plugin types that have no real UI but
+ // we're providing a title just in case.
+ 'title' => t('Export UI wizard cache'),
+ 'cache get' => 'ctools_cache_export_ui_cache_get',
+ 'cache set' => 'ctools_cache_export_ui_cache_set',
+ // Some operations use a 'finalize' but that really just means set
+ // for us, since we're not using temporary storage for subsystems.
+ 'cache finalize' => 'ctools_cache_export_ui_cache_set',
+);
+
+function ctools_cache_export_ui_cache_get($plugin_name, $key) {
+ ctools_include('export-ui');
+ $plugin = ctools_get_export_ui($plugin_name);
+ $handler = ctools_export_ui_get_handler($plugin);
+ if ($handler) {
+ $item = $handler->edit_cache_get($key);
+ if (!$item) {
+ $item = ctools_export_crud_load($handler->plugin['schema'], $key);
+ }
+ return $item;
+ }
+}
+
+function ctools_cache_export_ui_cache_set($plugin_name, $key, $item) {
+ ctools_include('export-ui');
+ $plugin = ctools_get_export_ui($plugin_name);
+ $handler = ctools_export_ui_get_handler($plugin);
+ if ($handler) {
+ return $handler->edit_cache_set_key($item, $key);
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/cache/simple.inc b/sites/all/modules/ctools/plugins/cache/simple.inc
new file mode 100644
index 000000000..570398ba0
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/cache/simple.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * A simple cache indirection mechanism that just uses the basic object cache.
+ */
+
+$plugin = array(
+ // cache plugins are the rare plugin types that have no real UI but
+ // we're providing a title just in case.
+ 'title' => t('Simple'),
+ 'cache get' => 'ctools_cache_simple_cache_get',
+ 'cache set' => 'ctools_cache_simple_cache_set',
+ 'cache clear' => 'ctools_cache_simple_cache_clear',
+);
+
+function ctools_cache_simple_cache_get($data, $key) {
+ ctools_include('object-cache');
+
+ // Ensure that if there is somehow no data, we at least don't stomp on other
+ // people's caches.
+ if (empty($data)) {
+ $data = 'simple_cache_plugin';
+ }
+
+ return ctools_object_cache_get($data, $key);
+}
+
+function ctools_cache_simple_cache_set($data, $key, $object) {
+ ctools_include('object-cache');
+
+ // Ensure that if there is somehow no data, we at least don't stomp on other
+ // people's caches.
+ if (empty($data)) {
+ $data = 'simple_cache_plugin';
+ }
+
+ return ctools_object_cache_set($data, $key, $object);
+}
+
+function ctools_cache_simple_cache_clear($data, $key) {
+ ctools_include('object-cache');
+
+ // Ensure that if there is somehow no data, we at least don't stomp on other
+ // people's caches.
+ if (empty($data)) {
+ $data = 'simple_cache_plugin';
+ }
+
+ return ctools_object_cache_clear($data, $key);
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/block/block.inc b/sites/all/modules/ctools/plugins/content_types/block/block.inc
new file mode 100644
index 000000000..4d4c31c31
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/block.inc
@@ -0,0 +1,565 @@
+<?php
+
+/**
+ * @file
+ * Provide Drupal blocks as content.
+ *
+ * Since blocks don't provide all of the features we do, we have to do a little
+ * extra work, including providing icons and categories for core blocks. Blocks
+ * from contrib modules get to provide their own stuff, or get relegated to
+ * the old "Miscellaneous" category.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ // And this is just the administrative title.
+ // All our callbacks are named according to the standard pattern and can be deduced.
+ 'title' => t('Block'),
+ 'content type' => 'ctools_block_content_type_content_type',
+);
+
+/**
+ * Return the block content types with the specified $subtype_id.
+ */
+function ctools_block_content_type_content_type($subtype_id) {
+ list($module, $delta) = explode('-', $subtype_id, 2);
+ $module_blocks = module_invoke($module, 'block_info');
+ if (isset($module_blocks[$delta])) {
+ return _ctools_block_content_type_content_type($module, $delta, $module_blocks[$delta]);
+ }
+}
+
+/**
+ * Return all block content types available.
+ *
+ * Modules wanting to make special adjustments the way that CTools handles their blocks
+ * can implement an extension to the hook_block() family, where the function name is
+ * of the form "$module . '_ctools_block_info'".
+ */
+function ctools_block_content_type_content_types() {
+ $types = &drupal_static(__FUNCTION__);
+ if (isset($types)) {
+ return $types;
+ }
+
+ $types = array();
+ foreach (module_implements('block_info') as $module) {
+ $module_blocks = module_invoke($module, 'block_info');
+ if ($module_blocks) {
+ foreach ($module_blocks as $delta => $block) {
+ $info = _ctools_block_content_type_content_type($module, $delta, $block);
+ // this check means modules can remove their blocks; particularly useful
+ // if they offer the block some other way (like we do for views)
+ if ($info) {
+ $types["$module-$delta"] = $info;
+ }
+ }
+ }
+ }
+ return $types;
+}
+
+/**
+ * Return an info array for a specific block.
+ */
+function _ctools_block_content_type_content_type($module, $delta, $block) {
+ // strip_tags used because it goes through check_plain and that
+ // just looks bad.
+ $info = array(
+ 'title' => strip_tags($block['info']),
+ );
+
+ // Ask around for further information by invoking the hook_block() extension.
+ $function = $module . '_ctools_block_info';
+ if (!function_exists($function)) {
+ $function = 'ctools_default_block_info';
+ }
+ $function($module, $delta, $info);
+
+ return $info;
+}
+
+/**
+ * Load block info from the database.
+ *
+ * This is copied from _block_load_blocks(). It doesn't use that
+ * function because _block_load_blocks sorts by region, and it
+ * doesn't cache its results anyway.
+ */
+function _ctools_block_load_blocks() {
+ if (!module_exists('block')) {
+ return array();
+ }
+
+ $blocks = &drupal_static(__FUNCTION__, NULL);
+ if (!isset($blocks)) {
+ global $theme_key;
+
+ $query = db_select('block', 'b');
+ $result = $query
+ ->fields('b')
+ ->condition('b.theme', $theme_key)
+ ->orderBy('b.region')
+ ->orderBy('b.weight')
+ ->orderBy('b.module')
+ ->addTag('block_load')
+ ->addTag('translatable')
+ ->execute();
+
+ $block_info = $result->fetchAllAssoc('bid');
+ // Allow modules to modify the block list.
+ drupal_alter('block_list', $block_info);
+
+ $blocks = array();
+ foreach ($block_info as $block) {
+ $blocks["{$block->module}_{$block->delta}"] = $block;
+ }
+ }
+
+ return $blocks;
+}
+
+/**
+ * Fetch the stored info for a block.
+ *
+ * The primary reason to use this is so that modules which perform alters
+ * can have their alters make it to the block.
+ */
+function _ctools_get_block_info($module, $delta) {
+ $blocks = _ctools_block_load_blocks();
+
+ $key = $module . '_' . $delta;
+ if (isset($blocks[$key])) {
+ return $blocks[$key];
+ }
+}
+
+/**
+ * Output function for the 'block' content type. Outputs a block
+ * based on the module and delta supplied in the configuration.
+ */
+function ctools_block_content_type_render($subtype, $conf) {
+ list($module, $delta) = _ctools_block_get_module_delta($subtype, $conf);
+
+ $info = _ctools_get_block_info($module, $delta);
+ $block = module_invoke($module, 'block_view', $delta);
+
+ if (!empty($info)) {
+ // Valid PHP function names cannot contain hyphens.
+ $block_delta = str_replace('-', '_', $delta);
+
+ // Allow modules to modify the block before it is viewed, via either
+ // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter().
+ drupal_alter(array('block_view', "block_view_{$module}_{$block_delta}"), $block, $info);
+ }
+
+ if (empty($block)) {
+ return;
+ }
+
+ $block = (object) $block;
+ $block->module = $module;
+ $block->delta = $delta;
+
+ if (!isset($block->title)) {
+ if ($module == 'block' && !empty($info) && isset($info->title)) {
+ $block->title = $info->title;
+ }
+ else if (isset($block->subject)) {
+ $block->title = $block->subject;
+ }
+ else {
+ $block->title = NULL;
+ }
+ }
+
+ if (module_exists('block') && user_access('administer blocks')) {
+ $block->admin_links = array(
+ array(
+ 'title' => t('Configure block'),
+ 'href' => "admin/structure/block/manage/$module/$delta/configure",
+ 'query' => drupal_get_destination(),
+ ),
+ );
+ }
+
+ return $block;
+}
+
+/**
+ * Empty form so we can have the default override title.
+ */
+function ctools_block_content_type_edit_form($form, &$form_state) {
+ // Does nothing!
+ return $form;
+}
+
+/**
+ * Submit function to fix the subtype for really old panel panes.
+ */
+function ctools_block_content_type_edit_form_submit($form, &$form_state) {
+ if (empty($form_state['subtype']) && isset($form_state['pane'])) {
+ $form_state['pane']->subtype = $form_state['conf']['module'] . '-' . $form_state['conf']['delta'];
+ unset($form_state['conf']['module']);
+ unset($form_state['conf']['delta']);
+ }
+}
+
+/**
+ * Returns an edit form for a block.
+ */
+//function ctools_block_content_type_edit_form($id, $parents, $conf) {
+// if (user_access('administer advanced pane settings')) {
+// $form['block_visibility'] = array(
+// '#type' => 'checkbox',
+// '#title' => t('Use block visibility settings (see block config)'),
+// '#default_value' => !empty($conf['block_visibility']),
+// '#description' => t('If checked, the block visibility settings for this block will apply to this block.'),
+// );
+// // Module-specific block configurations.
+// if ($settings = module_invoke($module, 'block', 'configure', $delta)) {
+// // Specifically modify a couple of core block forms.
+// if ($module == 'block') {
+// unset($settings['submit']);
+// $settings['info']['#type'] = 'value';
+// $settings['info']['#value'] = $settings['info']['#default_value'];
+// }
+// ctools_admin_fix_block_tree($settings);
+// $form['block_settings'] = array(
+// '#type' => 'fieldset',
+// '#title' => t('Block settings'),
+// '#description' => t('Settings in this section are global and are for all blocks of this type, anywhere in the system.'),
+// '#tree' => FALSE,
+// );
+//
+//
+// $form['block_settings'] += $settings;
+// }
+// }
+//
+// return $form;
+//}
+
+//function ctools_admin_submit_block(&$form_values) {
+// if (!empty($form_values['block_settings'])) {
+// module_invoke($form_values['module'], 'block', 'save', $form_values['delta'], $form_values['block_settings']);
+// }
+//}
+//
+///**
+// * Because form api cannot collapse just part of a tree, and the block settings
+// * assume no tree, we have to collapse the tree ourselves.
+// */
+//function ctools_admin_fix_block_tree(&$form, $key = NULL) {
+// if ($key) {
+// if (!empty($form['#parents'])) {
+// $form['#parents'] = array_merge(array('configuration', 'block_settings'), $form['#parents']);
+// }
+// else if (empty($form['#tree'])) {
+// $form['#parents'] = array('configuration', 'block_settings', $key);
+// }
+// }
+//
+// if (isset($form['#type']) && $form['#type'] == 'textarea' && !empty($form['#rows']) && $form['#rows'] > 10) {
+// $form['#rows'] = 10;
+// }
+//
+// foreach (element_children($form) as $key) {
+// ctools_admin_fix_block_tree($form[$key], $key);
+// }
+//}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_block_content_type_admin_title($subtype, $conf) {
+ list($module, $delta) = _ctools_block_get_module_delta($subtype, $conf);
+ $block = module_invoke($module, 'block_info');
+ if (empty($block) || empty($block[$delta])) {
+ return t('Deleted/missing block @module-@delta', array('@module' => $module, '@delta' => $delta));
+ }
+
+ // The block description reported by hook_block() is plain text, but the title
+ // reported by this hook should be HTML.
+ $title = check_plain($block[$delta]['info']);
+ return $title;
+}
+
+/**
+ * Output function for the 'block' content type. Outputs a block
+ * based on the module and delta supplied in the configuration.
+ */
+function ctools_block_content_type_admin_info($subtype, $conf) {
+ list($module, $delta) = _ctools_block_get_module_delta($subtype, $conf);
+ $block = (object) module_invoke($module, 'block_view', $delta);
+
+ if (!empty($block)) {
+ // Sanitize the block because <script> tags can hose javascript up:
+ if (!empty($block->content)) {
+ $block->content = filter_xss_admin(render($block->content));
+ }
+
+ if (!empty($block->subject)) {
+ $block->title = $block->subject;
+ }
+ elseif (empty($block->title)) {
+ $block->title = t('No title');
+ }
+ return $block;
+ }
+}
+
+function _ctools_block_get_module_delta($subtype, $conf) {
+ if (strpos($subtype, '-')) {
+ return explode('-', $subtype, 2);
+ }
+ else {
+ return array($conf['module'], $conf['delta']);
+ }
+}
+
+/**
+ * Provide default icon and categories for blocks when modules don't do this
+ * for us.
+ */
+function ctools_default_block_info($module, $delta, &$info) {
+ $core_modules = array('aggregator', 'block', 'blog', 'blogapi', 'book', 'color', 'comment', 'contact', 'drupal', 'filter', 'forum', 'help', 'legacy', 'locale', 'menu', 'node', 'path', 'ping', 'poll', 'profile', 'search', 'statistics', 'taxonomy', 'throttle', 'tracker', 'upload', 'user', 'watchdog', 'system');
+
+ if (in_array($module, $core_modules)) {
+ $info['icon'] = 'icon_core_block.png';
+ $info['category'] = t('Miscellaneous');
+ }
+ else {
+ $info['icon'] = 'icon_contrib_block.png';
+ $info['category'] = t('Miscellaneous');
+ }
+}
+
+// These are all on behalf of modules that don't implement ctools but that
+// we care about.
+function menu_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_block_menu.png';
+ $info['category'] = t('Menus');
+ if ($delta == 'primary-links' || $delta == 'secondary-links') {
+ $info['icon'] = 'icon_core_primarylinks.png';
+ }
+}
+
+function forum_ctools_block_info($module, $delta, &$info) {
+ $info['category'] = t('Activity');
+ switch ($delta) {
+ case 'active':
+ $info['icon'] = 'icon_core_activeforumtopics.png';
+ break;
+
+ case 'new':
+ $info['icon'] = 'icon_core_newforumtopics.png';
+ break;
+
+ default:
+ // safety net
+ ctools_default_block_info($module, $delta, $info);
+ }
+}
+
+function profile_ctools_block_info($module, $delta, &$info) {
+ // Hide the author information block which isn't as rich as what we can
+ // do with context.
+ $info = NULL;
+}
+
+function book_ctools_block_info($module, $delta, &$info) {
+ // Hide the book navigation block which isn't as rich as what we can
+ // do with context.
+ $info = NULL;
+}
+
+function blog_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_recentblogposts.png';
+ $info['category'] = t('Activity');
+}
+
+function poll_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_recentpoll.png';
+ $info['category'] = t('Activity');
+}
+
+function comment_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_recentcomments.png';
+ $info['category'] = t('Activity');
+}
+
+function search_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_searchform.png';
+ $info['category'] = t('Widgets');
+}
+
+function node_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_syndicate.png';
+ $info['category'] = t('Widgets');
+}
+
+function aggregator_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_syndicate.png';
+ $info['category'] = t('Feeds');
+}
+
+function block_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_block_empty.png';
+ $info['category'] = t('Custom blocks');
+
+ // The title of custom blocks from the block module is stored in the
+ // {block} table. Look for it in the default theme as a reasonable
+ // default value for the title.
+ $block_info_cache = drupal_static(__FUNCTION__);
+ if (!isset($block_info_cache)) {
+ $block_info_cache = db_select('block', 'b')
+ ->fields('b')
+ ->condition('b.module', 'block')
+ ->condition('b.theme', variable_get('theme_default', 'bartik'))
+ ->addTag('block_load')
+ ->addTag('translatable')
+ ->execute()
+ ->fetchAllAssoc('delta');
+ }
+
+ if (isset($block_info_cache[$delta])) {
+ $info['defaults'] = array(
+ 'override_title' => TRUE,
+ 'override_title_text' => $block_info_cache[$delta]->title,
+ );
+ }
+}
+
+function user_ctools_block_info($module, $delta, &$info) {
+ $info['category'] = t('Activity');
+ switch ($delta) {
+ case 'login':
+ $info['icon'] = 'icon_core_userlogin.png';
+ $info['category'] = t('Widgets');
+ // Provide a custom render callback, because the default login block
+ // will not render on /user, /user/login, or any other URL beginning
+ // /user (unless it's a user-specific page such as /user/123).
+ $info['render callback'] = 'ctools_user_login_pane_render';
+ break;
+
+ case 'new':
+ $info['icon'] = 'icon_core_whosnew.png';
+ break;
+
+ case 'online':
+ $info['icon'] = 'icon_core_whosonline.png';
+ break;
+
+ default:
+ // safety net
+ ctools_default_block_info($module, $delta, $info);
+ }
+}
+
+function locale_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_languageswitcher.png';
+ $info['category'] = t('Widgets');
+}
+
+function statistics_ctools_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_popularcontent.png';
+ $info['category'] = t('Activity');
+}
+
+function system_ctools_block_info($module, $delta, &$info) {
+ // Remove the main content fake block.
+ if ($delta == 'main') {
+ $info = NULL;
+ return;
+ }
+
+ $menus = array('main-menu', 'management', 'navigation', 'user-menu');
+
+ if (in_array($delta, $menus)) {
+ $info['icon'] = 'icon_core_block_menu.png';
+ $info['category'] = t('Menus');
+
+ if ($delta == 'navigation') {
+ $info['icon'] = 'icon_core_navigation.png';
+ }
+
+ return;
+ }
+
+ $info['icon'] = 'icon_core_drupal.png';
+ if ($delta == 'help') {
+ $info['category'] = t('Page elements');
+ return;
+ }
+
+ $info['category'] = t('Widgets');
+}
+
+function ctools_user_login_pane_render($subtype, $conf, $panel_args, $contexts) {
+ list($module, $delta) = _ctools_block_get_module_delta($subtype, $conf);
+
+ // The login form is only visible to anonymous users.
+ global $user;
+ if ($user->uid) {
+ return;
+ }
+
+ $info = new stdClass;
+ $info->module = $module;
+ $info->delta = $delta;
+
+ $block = array();
+ $block['subject'] = t('User login');
+ // Manually set the content (rather than invoking block_view) because the
+ // block implementation won't render on certain URLs.
+ $block['content'] = drupal_get_form('user_login_block');
+
+ // Allow modules to modify the block before it is viewed, via either
+ // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter().
+ drupal_alter(array('block_view', "block_view_{$module}_{$delta}"), $block, $info);
+ $block = (object) $block;
+
+ if (empty($block)) {
+ return;
+ }
+
+ $block->module = $module;
+ $block->delta = $delta;
+
+ // $block->title is not set for the blocks returned by block_block() (the
+ // Block module adds the title in block_list() instead), so we look it up
+ // manually, unless the title is overridden and does not use the %title
+ // placeholder.
+ if ($module == 'block') {
+ $block->title = $info->title;
+ }
+ else if (isset($block->subject)) {
+ $block->title = $block->subject;
+ }
+ else {
+ $block->title = NULL;
+ }
+
+ if (isset($block->subject)) {
+ $block->title = $block->subject;
+ }
+ else {
+ $block->title = NULL;
+ }
+
+ if (user_access('administer blocks')) {
+ $block->admin_links = array(
+ array(
+ 'title' => t('Configure block'),
+ 'href' => "admin/structure/block/manage/$module/$delta/configure",
+ 'query' => drupal_get_destination(),
+ ),
+ );
+ }
+
+ return $block;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_block.png b/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_block.png
new file mode 100644
index 000000000..fa78ec179
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_block.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_block_empty.png b/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_block_empty.png
new file mode 100644
index 000000000..6d0891b03
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_block_empty.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_menu.png b/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_menu.png
new file mode 100644
index 000000000..38cf72090
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_menu.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_page.png b/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_page.png
new file mode 100644
index 000000000..4a2fa51d3
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_contrib_page.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_activeforumtopics.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_activeforumtopics.png
new file mode 100644
index 000000000..8414a8f88
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_activeforumtopics.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_authorinformation.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_authorinformation.png
new file mode 100644
index 000000000..ab248f3f1
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_authorinformation.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_block.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_block.png
new file mode 100644
index 000000000..b0d9628ad
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_block.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_block_empty.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_block_empty.png
new file mode 100644
index 000000000..da08c64c6
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_block_empty.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_block_menu.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_block_menu.png
new file mode 100644
index 000000000..84594431b
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_block_menu.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_booknavigation.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_booknavigation.png
new file mode 100644
index 000000000..52dfca536
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_booknavigation.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_languageswitcher.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_languageswitcher.png
new file mode 100644
index 000000000..dc4521fff
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_languageswitcher.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_navigation.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_navigation.png
new file mode 100644
index 000000000..fb4c1f84f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_navigation.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_newforumtopics.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_newforumtopics.png
new file mode 100644
index 000000000..70bbde26b
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_newforumtopics.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_page.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_page.png
new file mode 100644
index 000000000..f0417cb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_page.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_popularcontent.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_popularcontent.png
new file mode 100644
index 000000000..70bbde26b
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_popularcontent.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_primarylinks.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_primarylinks.png
new file mode 100644
index 000000000..6dafb99ed
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_primarylinks.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentblogposts.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentblogposts.png
new file mode 100644
index 000000000..785207ac4
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentblogposts.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentcomments.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentcomments.png
new file mode 100644
index 000000000..ba96e32a3
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentcomments.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentpoll.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentpoll.png
new file mode 100644
index 000000000..c23fa23e6
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_recentpoll.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_searchform.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_searchform.png
new file mode 100644
index 000000000..3ad1deb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_searchform.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_syndicate.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_syndicate.png
new file mode 100644
index 000000000..27c54bf00
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_syndicate.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_userlogin.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_userlogin.png
new file mode 100644
index 000000000..dc4521fff
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_userlogin.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_whosnew.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_whosnew.png
new file mode 100644
index 000000000..51303e7fa
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_whosnew.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/block/icon_core_whosonline.png b/sites/all/modules/ctools/plugins/content_types/block/icon_core_whosonline.png
new file mode 100644
index 000000000..a5896e3a5
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/block/icon_core_whosonline.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/comment/comment_created.inc b/sites/all/modules/ctools/plugins/content_types/comment/comment_created.inc
new file mode 100644
index 000000000..62944e712
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/comment/comment_created.inc
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Comment created date'),
+ 'icon' => 'icon_comment.png',
+ 'description' => t('The date the referenced comment was created.'),
+ 'required context' => new ctools_context_required(t('Comment'), 'entity:comment'),
+ 'category' => t('Comment'),
+ 'defaults' => array(
+ 'format' => 'small',
+ ),
+);
+
+/**
+ * Render the custom content type.
+ */
+function ctools_comment_created_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Get a shortcut to the comment.
+ $comment = $context->data;
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'comment_created';
+ $block->title = t('Created date');
+ $block->content = format_date($comment->created, $conf['format']);
+ $block->delta = $comment->cid;
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_comment_created_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $date_types = array();
+
+ foreach (system_get_date_types() as $date_type => $definition) {
+ $date_types[$date_type] = format_date(REQUEST_TIME, $date_type);
+ }
+ $form['format'] = array(
+ '#title' => t('Date format'),
+ '#type' => 'select',
+ '#options' => $date_types,
+ '#default_value' => $conf['format'],
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the custom type settings form.
+ */
+function ctools_comment_created_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_comment_created_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" created date', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/comment/comment_links.inc b/sites/all/modules/ctools/plugins/content_types/comment/comment_links.inc
new file mode 100644
index 000000000..c8fefccab
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/comment/comment_links.inc
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Comment links'),
+ 'icon' => 'icon_comment.png',
+ 'description' => t('Comment links of the referenced comment.'),
+ 'required context' => new ctools_context_required(t('Comment'), 'entity:comment'),
+ 'category' => t('Comment'),
+ 'defaults' => array(
+ 'override_title' => FALSE,
+ 'override_title_text' => '',
+ 'build_mode' => '',
+ ),
+);
+
+/**
+ * Output function for the comment links.
+ */
+function ctools_comment_links_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (!empty($context) && empty($context->data)) {
+ return;
+ }
+
+ $comment = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'comment';
+ $block->delta = $comment->cid;
+
+ if (empty($comment)) {
+ $block->delta = 'placeholder';
+ $block->subject = t('Comment subject.');
+ $block->content = t('Comment links go here.');
+ }
+ else {
+ $node = node_load($comment->nid);
+ $block->subject = $comment->subject;
+ comment_build_content($comment, $node, $conf['build_mode']);
+ $block->content = $comment->content['links'];
+ }
+ return $block;
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function ctools_comment_links_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $entity = entity_get_info('comment');
+ $build_mode_options = array();
+ foreach ($entity['view modes'] as $mode => $option) {
+ $build_mode_options[$mode] = $option['label'];
+ }
+
+ $form['build_mode'] = array(
+ '#title' => t('Build mode'),
+ '#type' => 'select',
+ '#description' => t('Select a build mode for this comment.'),
+ '#options' => $build_mode_options,
+ '#default_value' => $conf['build_mode'],
+ );
+
+ return $form;
+}
+
+function ctools_comment_links_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+function ctools_comment_links_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" links', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/comment/comment_reply_form.inc b/sites/all/modules/ctools/plugins/content_types/comment/comment_reply_form.inc
new file mode 100644
index 000000000..c05effb60
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/comment/comment_reply_form.inc
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Ctools content-type plugin to provide a comment-reply form (replying either
+ * to a node or to another comment).
+ */
+
+// Only provide the plugin in the comment module is enabled.
+if (module_exists('comment')) {
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Comment Reply Form'),
+ 'icon' => 'icon_comment.png',
+ 'description' => t('A form to add a new comment reply.'),
+ 'required context' => array(
+ new ctools_context_required(t('Node'), 'node'),
+ new ctools_context_optional(t('Comment'), 'comment'),
+ ),
+ 'category' => t('Comment'),
+ 'render callback' => 'ctools_comment_reply_form_content_type_render',
+ 'defaults' => array('anon_links' => false),
+ );
+}
+
+function ctools_comment_reply_form_content_type_render($subtype, $conf, $panel_args, $context) {
+
+ $comment = ($context[1]->identifier == t('No context')) ? NULL : clone($context[1]->data);
+ $block = new stdClass();
+ $block->module = 'comments';
+ if ($comment) $block->delta = $comment->cid;
+ $block->title = t('Add comment');
+ $node = $context[0]->data;
+
+ module_load_include('inc', 'comment', 'comment.pages');
+ $block->content = comment_reply($node, ($comment ? $comment->cid : NULL));
+
+ return $block;
+}
+
+function ctools_comment_reply_form_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" comment form', array('@s' => $context[0]->identifier));
+}
+
+function ctools_comment_reply_form_content_type_edit_form($form, &$form_state) {
+ return $form;
+}
+
+function ctools_comment_reply_form_content_type_edit_form_submit($form, &$form_state) {
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/contact/contact.inc b/sites/all/modules/ctools/plugins/content_types/contact/contact.inc
new file mode 100644
index 000000000..63283f598
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/contact/contact.inc
@@ -0,0 +1,60 @@
+<?php
+
+if (module_exists('contact')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Contact form'),
+ 'icon' => 'icon_contact.png',
+ 'description' => t('The site contact form that allows users to send a message to site administrators.'),
+ 'category' => t('Widgets'),
+ );
+}
+
+/**
+ * Render the custom content type.
+ */
+function ctools_contact_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (!user_access('access site-wide contact form')) {
+ return;
+ }
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'contact';
+ $block->delta = 'form';
+ $block->title = t('Contact');
+
+ module_load_include('inc', 'contact', 'contact.pages');
+ $block->content = drupal_get_form('contact_site_form');
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_contact_content_type_edit_form($form, &$form_state) {
+ // Empty so that we can have title override.
+ return $form;
+}
+
+/**
+ * Submit handler for contact form.
+ */
+function ctools_contact_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+/*
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+*/
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_contact_content_type_admin_title($subtype, $conf, $context) {
+ return t('Contact form');
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/contact/icon_contact.png b/sites/all/modules/ctools/plugins/content_types/contact/icon_contact.png
new file mode 100644
index 000000000..ab248f3f1
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/contact/icon_contact.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/contact/user_contact.inc b/sites/all/modules/ctools/plugins/content_types/contact/user_contact.inc
new file mode 100644
index 000000000..9b3726ab8
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/contact/user_contact.inc
@@ -0,0 +1,66 @@
+<?php
+
+if (module_exists('contact')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('User contact form'),
+ 'icon' => 'icon_contact.png',
+ 'description' => t('The site contact form that allows users to contact other users.'),
+ 'category' => t('User'),
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+ );
+}
+
+/**
+ * Render the custom content type.
+ */
+function ctools_user_contact_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ if (!_contact_personal_tab_access($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'contact';
+ $block->delta = 'form';
+ $block->title = t('Contact @name', array('@name' => $context->data->name));
+
+ module_load_include('inc', 'contact', 'contact.pages');
+ $block->content = drupal_get_form('contact_personal_form', $context->data);
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_user_contact_content_type_edit_form($form, &$form_state) {
+ // Empty so that we can have title override.
+ return $form;
+}
+
+/**
+ * Submit handler for contact form.
+ */
+function ctools_user_contact_content_type_edit_form_submit(&$form, &$form_state) {
+ // Copy everything from our defaults.
+/*
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+*/
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_user_contact_content_type_admin_title($subtype, $conf, $context) {
+ return t('User contact form');
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/custom/custom.inc b/sites/all/modules/ctools/plugins/content_types/custom/custom.inc
new file mode 100644
index 000000000..ac2f2a3ff
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/custom/custom.inc
@@ -0,0 +1,434 @@
+<?php
+
+/**
+ * @file
+ * Custom content type.
+ *
+ * This content type is nothing more than a title and a body that is entered
+ * by the user and run through standard filters. The information is stored
+ * right in the config, so each custom content is unique.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Custom content'),
+ 'no title override' => TRUE,
+ 'defaults' => array('admin_title' => '', 'title' => '', 'body' => '', 'format' => filter_default_format(), 'substitute' => TRUE),
+ 'js' => array('misc/autocomplete.js', 'misc/textarea.js', 'misc/collapse.js'),
+ // Make sure the edit form is only used for some subtypes.
+ 'edit form' => '',
+ 'add form' => '',
+ 'edit text' => t('Edit'),
+ 'all contexts' => TRUE,
+);
+
+/**
+ * Return the custom content types with the specified $subtype_id.
+ */
+function ctools_custom_content_type_content_type($subtype_id) {
+ if ($subtype_id == 'custom') {
+ return _ctools_default_content_type_content_type();
+ }
+ elseif (module_exists('ctools_custom_content')) {
+ ctools_include('export');
+ $content = ctools_export_crud_load('ctools_custom_content', $subtype_id);
+ if ($content) {
+ return _ctools_custom_content_type_content_type($content);
+ }
+ }
+}
+
+/**
+ * Return all custom content types available.
+ */
+function ctools_custom_content_type_content_types() {
+ $types = &drupal_static(__FUNCTION__);
+ if (isset($types)) {
+ return $types;
+ }
+
+ ctools_include('export');
+ $types = array();
+ $types['custom'] = _ctools_default_content_type_content_type();
+
+ if (module_exists('ctools_custom_content')) {
+ foreach (ctools_export_crud_load_all('ctools_custom_content') as $name => $content) {
+ $types[$name] = _ctools_custom_content_type_content_type($content);
+ }
+ }
+
+ return $types;
+}
+
+/**
+ * Settings for the default custom content type.
+ *
+ * The default is the one that allows the user to actually create a type.
+ */
+function _ctools_default_content_type_content_type() {
+ $info = array(
+ 'name' => 'custom',
+ 'title' => t('New custom content'),
+ 'top level' => TRUE,
+ 'category' => t('Custom'),
+ 'description' => t('Create a completely custom piece of HTML content.'),
+ 'edit form' => 'ctools_custom_content_type_edit_form',
+ 'all contexts' => TRUE,
+ 'check editable' => 'ctools_custom_content_type_editable',
+ );
+
+ return $info;
+}
+
+/**
+ * Return an info array for a specific custom content type.
+ */
+function _ctools_custom_content_type_content_type($content) {
+ $info = array(
+ 'name' => $content->name,
+ 'title' => check_plain($content->admin_title),
+ 'description' => check_plain($content->admin_description),
+ 'category' => $content->category ? check_plain($content->category) : t('Miscellaneous'),
+ 'all contexts' => TRUE,
+ 'icon' => 'icon_block_custom.png',
+ // Store this here to make it easy to access.
+ 'content' => $content,
+ );
+
+ return $info;
+}
+
+/**
+ * Given a subtype and a $conf, return the actual settings to use.
+ *
+ * The actual settings may be stored directly in the pane or this may
+ * be a pointer to re-usable content that may be in the database or in
+ * an export. We have to determine from the subtype whether or not it
+ * is local or shared custom content.
+ */
+function ctools_custom_content_type_get_conf($subtype, $conf) {
+ if ($subtype['name'] != 'custom') {
+ $settings = $subtype['content']->settings;
+ $settings['custom_type'] = 'fixed';
+ $settings['content'] = $subtype['content'];
+ }
+ else {
+ // This means they created it as custom content and then set it as
+ // reusable. Since we're not allowed to change the subtype, we're
+ // still stored as though we are local, but are pointing off to
+ // non-local.
+ if (!empty($conf['name']) && module_exists('ctools_custom_content')) {
+ ctools_include('export');
+ $content = ctools_export_crud_load('ctools_custom_content', $conf['name']);
+ if ($content) {
+ $settings = $content->settings;
+ $settings['custom_type'] = 'fixed';
+ $settings['content'] = $content;
+ $settings['admin_title'] = $content->admin_title;
+ }
+ else {
+ $content = ctools_export_crud_new('ctools_custom_content');
+ $content->name = $conf['name'];
+ $settings = array(
+ 'admin_title' => t('Missing/deleted content'),
+ 'title' => '',
+ 'body' => '',
+ 'format' => filter_default_format(),
+ 'substitute' => TRUE,
+ 'custom_type' => 'fixed',
+ 'content' => $content,
+ );
+ }
+ }
+ // This means that it is created as custom and has not been set to
+ // reusable.
+ else {
+ $settings = $conf;
+ $settings['custom_type'] = 'local';
+ }
+ }
+
+ // Correct for an error that came in because filter format changed.
+ if (is_array($settings['body'])) {
+ $settings['format'] = $settings['body']['format'];
+ $settings['body'] = $settings['body']['value'];
+ }
+
+ return $settings;
+}
+
+function ctools_custom_content_type_editable($content_type, $subtype, $conf) {
+ if ($subtype['name'] == 'custom' && !empty($conf['name'])) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Output function for the 'custom' content type. Outputs a custom
+ * based on the module and delta supplied in the configuration.
+ */
+function ctools_custom_content_type_render($subtype, $conf, $args, $contexts) {
+ $settings = ctools_custom_content_type_get_conf(ctools_custom_content_type_content_type($subtype), $conf);
+
+ static $delta = 0;
+
+ $block = new stdClass();
+ $block->subtype = ++$delta;
+ $block->title = filter_xss_admin($settings['title']);
+
+ // Add keyword substitutions if we were configured to do so.
+ $content = $settings['body'];
+ if (!empty($contexts) && !empty($settings['substitute'])) {
+ $content = ctools_context_keyword_substitute($content, array(), $contexts);
+ }
+
+ $block->content = check_markup($content, $settings['format']);
+ if ($settings['custom_type'] == 'fixed' && user_access('administer custom content')) {
+ $block->admin_links = array(
+ array(
+ 'title' => t('Configure content pane'),
+ 'alt' => t("Configure this pane in administer >> structure >> custom content panes"),
+ 'href' => 'admin/structure/ctools-content/list/' . $settings['content']->name . '/edit',
+ 'query' => drupal_get_destination(),
+ ),
+ );
+ }
+
+ return $block;
+}
+
+/**
+ * Callback to provide the administrative title of the custom content.
+ */
+function ctools_custom_content_type_admin_title($subtype, $conf) {
+ $settings = ctools_custom_content_type_get_conf(ctools_custom_content_type_content_type($subtype), $conf);
+
+ $output = t('Custom');
+ $title = !empty($settings['admin_title']) ? $settings['admin_title'] : $settings['title'];
+ if ($title) {
+ if ($settings['custom_type'] != 'fixed') {
+ $output = t('Custom: @title', array('@title' => $title));
+ }
+ else {
+ $output = $title;
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Callback to provide administrative info. In this case we'll render the
+ * content as long as it's not PHP, which is too risky to render here.
+ */
+function ctools_custom_content_type_admin_info($subtype, $conf) {
+ $settings = ctools_custom_content_type_get_conf(ctools_custom_content_type_content_type($subtype), $conf);
+
+ $block = new stdClass();
+ $block->title = filter_xss_admin($settings['title']);
+ // We don't want to render php output on preview here, because if something is
+ // wrong the whole display will be borked. So we check to see if the php
+ // evaluator filter is being used, and make a temporary change to the filter
+ // so that we get the printed php, not the eval'ed php.
+ $php_filter = FALSE;
+ foreach (filter_list_format($settings['format']) as $filter) {
+ if ($filter->module == 'php') {
+ $php_filter = TRUE;
+ break;
+ }
+ }
+ // If a php filter is active, just print the source, but only if the current
+ // user has access to the actual filter.
+ if ($php_filter) {
+ $filter = filter_format_load($settings['format']);
+ if (!filter_access($filter)) {
+ return NULL;
+ }
+ $block->content = '<pre>' . check_plain($settings['body']) . '</pre>';
+ }
+ else {
+ // We also need to filter through XSS admin because <script> tags can
+ // cause javascript which will interfere with our ajax.
+ $block->content = filter_xss_admin(check_markup($settings['body'], $settings['format']));
+ }
+ return $block;
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function ctools_custom_content_type_edit_form($form, &$form_state) {
+ $settings = ctools_custom_content_type_get_conf($form_state['subtype'], $form_state['conf']);
+ $form_state['settings'] = $settings;
+
+ if ($settings['custom_type'] == 'fixed') {
+ return $form; // no form for this case.
+ }
+
+ $form['admin_title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => isset($settings['admin_title']) ? $settings['admin_title'] : '',
+ '#title' => t('Administrative title'),
+ '#description' => t('This title will be used administratively to identify this pane. If blank, the regular title will be used.'),
+ );
+
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $settings['title'],
+ '#title' => t('Title'),
+ );
+
+ $form['body'] = array(
+ '#type' => 'text_format',
+ '#title' => t('Body'),
+ '#default_value' => $settings['body'],
+ '#format' => $settings['format'],
+ );
+
+ if (!empty($form_state['contexts'])) {
+ // Set extended description if both CCK and Token modules are enabled, notifying of unlisted keywords
+ if (module_exists('content') && module_exists('token')) {
+ $description = t('If checked, context keywords will be substituted in this content. Note that CCK fields may be used as keywords using patterns like <em>%node:field_name-formatted</em>.');
+ }
+ elseif (!module_exists('token')) {
+ $description = t('If checked, context keywords will be substituted in this content. More keywords will be available if you install the Token module, see http://drupal.org/project/token.');
+ }
+ else {
+ $description = t('If checked, context keywords will be substituted in this content.');
+ }
+
+ $form['substitute'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use context keywords'),
+ '#description' => $description,
+ '#default_value' => !empty($settings['substitute']),
+ );
+ $form['contexts'] = array(
+ '#title' => t('Substitutions'),
+ '#type' => 'fieldset',
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+
+ $rows = array();
+ foreach ($form_state['contexts'] as $context) {
+ foreach (ctools_context_get_converters('%' . check_plain($context->keyword) . ':', $context) as $keyword => $title) {
+ $rows[] = array(
+ check_plain($keyword),
+ t('@identifier: @title', array('@title' => $title, '@identifier' => $context->identifier)),
+ );
+ }
+ }
+ $header = array(t('Keyword'), t('Value'));
+ $form['contexts']['context'] = array('#markup' => theme('table', array('header' => $header, 'rows' => $rows)));
+ }
+
+ if (!user_access('administer custom content') || !module_exists('ctools_custom_content')) {
+ return $form;
+ }
+
+ // Make the other form items dependent upon it.
+ ctools_include('dependent');
+ ctools_add_js('dependent');
+
+ $form['reusable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Make this content reusable'),
+ '#default_value' => FALSE,
+ );
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Machine name'),
+ '#description' => t('The machine readable name of this content. It must be unique, and it must contain only alphanumeric characters and underscores. Once created, you will not be able to change this value!'),
+ '#dependency' => array('edit-reusable' => array(1)),
+ );
+
+ $form['category'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Category'),
+ '#description' => t('What category this content should appear in. If left blank the category will be "Miscellaneous".'),
+ '#dependency' => array('edit-reusable' => array(1)),
+ );
+
+ $form['admin_description'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Administrative description'),
+ '#description' => t('A description of what this content is, does or is for, for administrative use.'),
+ '#dependency' => array('edit-reusable' => array(1)),
+ );
+ return $form;
+}
+
+function _ctools_custom_content_type_edit_save(&$content, $form_state) {
+ // Apply updates to the content object.
+ $content->category = $form_state['values']['category'];
+ $content->admin_title = $form_state['values']['admin_title'];
+ $content->admin_description = $form_state['values']['admin_description'];
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ if (isset($form_state['values'][$key])) {
+ $content->settings[$key] = $form_state['values'][$key];
+ }
+ }
+
+ ctools_export_crud_save('ctools_custom_content', $content);
+}
+
+/**
+ * The validate form to ensure the custom content data is okay.
+ */
+function ctools_custom_content_type_edit_form_validate(&$form, &$form_state) {
+ if ($form_state['settings']['custom_type'] != 'fixed' && !empty($form_state['values']['reusable'])) {
+ if (empty($form_state['values']['name'])) {
+ form_error($form['name'], t('Name is required.'));
+ }
+
+ // Check for string identifier sanity
+ if (!preg_match('!^[a-z0-9_]+$!', $form_state['values']['name'])) {
+ form_error($form['name'], t('The name can only consist of lowercase letters, underscores, and numbers.'));
+ return;
+ }
+
+ if (!module_exists('ctools_custom_content')) {
+ return;
+ }
+
+ // Check for name collision
+ if ($form_state['values']['name'] == 'custom' || (ctools_export_crud_load('ctools_custom_content', $form_state['values']['name']))) {
+ form_error($form['name'], t('Content with this name already exists. Please choose another name or delete the existing item before creating a new one.'));
+ }
+ }
+}
+
+/**
+ * The submit form stores the data in $conf.
+ */
+function ctools_custom_content_type_edit_form_submit($form, &$form_state) {
+ // Because of changes in filter form, these two keys are out of position:
+ $form_state['values']['format'] = $form_state['values']['body']['format'];
+ $form_state['values']['body'] = $form_state['values']['body']['value'];
+
+ if ($form_state['settings']['custom_type'] == 'fixed') {
+ _ctools_custom_content_type_edit_save($form_state['settings']['content'], $form_state);
+ }
+ // If the 'reusable' checkbox was checked, we will create a new
+ // custom content and give it the proper values.
+ else if (!empty($form_state['values']['reusable'])) {
+ $content = ctools_export_crud_new('ctools_custom_content');
+ $content->name = $form_state['values']['name'];
+ _ctools_custom_content_type_edit_save($content, $form_state);
+ $form_state['conf']['name'] = $content->name;
+ }
+ else {
+ // Otherwise, just save values into $conf normally.
+
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = isset($form_state['values'][$key]) ? $form_state['values'][$key] : $form_state['plugin']['defaults'][$key];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/custom/icon_block_custom.png b/sites/all/modules/ctools/plugins/content_types/custom/icon_block_custom.png
new file mode 100644
index 000000000..bbde4bd57
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/custom/icon_block_custom.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/entity_context/entity_field.inc b/sites/all/modules/ctools/plugins/content_types/entity_context/entity_field.inc
new file mode 100644
index 000000000..65367a026
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/entity_context/entity_field.inc
@@ -0,0 +1,281 @@
+<?php
+
+/**
+ * @file
+ * Handle rendering entity fields as panes.
+ */
+
+$plugin = array(
+ 'title' => t('Entity field'),
+ 'defaults' => array('label' => 'title', 'formatter' => '', 'delta_limit' => 0, 'delta_offset' => '0', 'delta_reversed' => FALSE),
+ 'content type' => 'ctools_entity_field_content_type_content_type',
+);
+
+/**
+ * Just one subtype.
+ *
+ * Ordinarily this function is meant to get just one subtype. However, we are
+ * using it to deal with the fact that we have changed the subtype names. This
+ * lets us translate the name properly.
+ */
+function ctools_entity_field_content_type_content_type($subtype) {
+ $types = ctools_entity_field_content_type_content_types();
+ if (isset($types[$subtype])) {
+ return $types[$subtype];
+ }
+}
+
+/**
+ * Return all field content types available.
+ */
+function ctools_entity_field_content_type_content_types() {
+ $types = &drupal_static(__FUNCTION__, array());
+ if (!empty($types)) {
+ return $types;
+ }
+
+ $cache_key = 'ctools_entity_field_content_type_content_types';
+ if ($cache = cache_get($cache_key)) {
+ $types = $cache->data;
+ if (!empty($types)) {
+ return $types;
+ }
+ }
+
+ // This will hold all the individual field content types.
+ $context_types = array();
+ $entities = entity_get_info();
+
+ $description = t('Field on the referenced entity.');
+ $styles = t('Formatter Styles');
+ $categories = array();
+ foreach ($entities as $entity_type => $entity) {
+ $category = t(ucfirst($entity_type));
+ $categories[$entity_type] = $category;
+ foreach ($entity['bundles'] as $type => $bundle) {
+ foreach (field_info_instances($entity_type, $type) as $field_name => $field) {
+ if (!isset($types[$entity_type . ':' . $field_name])) {
+ $label = t($field['label']);
+ $types[$entity_type . ':' . $field_name] = array(
+ 'category' => $category,
+ 'icon' => 'icon_field.png',
+ 'title' => t('Field: @widget_label (@field_name)', array(
+ '@widget_label' => $label,
+ '@field_name' => $field_name,
+ )),
+ 'description' => $description,
+ 'edit form' => array(
+ 'ctools_entity_field_content_type_formatter_options' => array(
+ 'default' => TRUE,
+ 'title' => t('Formatter options for: @widget_label (@field_name)', array(
+ '@widget_label' => $label,
+ '@field_name' => $field_name,
+ )),
+ ),
+ 'ctools_entity_field_content_type_formatter_styles' => $styles,
+ ),
+ );
+ }
+ $context_types[$entity_type . ':' . $field_name]['types'][$type] = $bundle['label'];
+ }
+ }
+ }
+
+ // Create the required context for each field related to the bundle types.
+ foreach ($types as $key => $field_content_type) {
+ list($entity_type, $field_name) = explode(':', $key, 2);
+ $types[$key]['required context'] = new ctools_context_required($categories[$entity_type], $entity_type, array(
+ 'type' => array_keys($context_types[$key]['types']),
+ ));
+ unset($context_types[$key]['types']);
+ }
+
+ cache_set($cache_key, $types);
+
+ return $types;
+}
+
+/**
+* Render the custom content type.
+*/
+function ctools_entity_field_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Get a shortcut to the entity.
+ $entity = $context->data;
+ list($entity_type, $field_name) = explode(':', $subtype, 2);
+
+ // Load the entity type's information for this field.
+ $ids = entity_extract_ids($entity_type, $entity);
+ $field = field_info_instance($entity_type, $field_name, $ids[2]);
+
+ // Do not render if the entity type does not have this field.
+ if (empty($field)) {
+ return;
+ }
+ $language = field_language($entity_type, $entity, $field_name);
+
+ if (empty($conf['label']) || $conf['label'] == 'title') {
+ $label = 'hidden';
+ $conf['label'] = 'title';
+ }
+ else {
+ $label = $conf['label'];
+ }
+
+ $field_settings = array(
+ 'label' => $label,
+ 'type' => $conf['formatter'],
+ // Pass all entity field panes settings to field display settings.
+ 'pane_settings' => $conf,
+ );
+
+ // Get the field output, and the title.
+ if (!empty($conf['formatter_settings'])) {
+ $field_settings['settings'] = $conf['formatter_settings'];
+ }
+
+ $all_values = field_get_items($entity_type, $entity, $field_name, $language);
+ if (!is_array($all_values)) {
+ // Do not render if the field is empty.
+ return;
+ }
+
+ // Reverse values.
+ if (isset($conf['delta_reversed']) && $conf['delta_reversed']) {
+ $all_values = array_reverse($all_values, TRUE);
+ }
+
+ if (isset($conf['delta_limit'])) {
+ $offset = intval($conf['delta_offset']);
+ $limit = !empty($conf['delta_limit']) ? $conf['delta_limit'] : NULL;
+ $all_values = array_slice($all_values, $offset, $limit, TRUE);
+ }
+
+ $clone = clone $entity;
+ $clone->{$field_name}[$language] = $all_values;
+ $field_output = field_view_field($entity_type, $clone, $field_name, $field_settings, $language);
+
+ if (!empty($field_output) && !empty($conf['override_title'])) {
+ $field_output['#title'] = filter_xss_admin($conf['override_title_text']);
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'entity_field';
+ if ($conf['label'] == 'title' && isset($field_output['#title'])) {
+ $block->title = $field_output['#title'];
+ }
+
+ $block->content = $field_output;
+ $block->delta = $ids[0];
+
+ return $block;
+}
+
+/**
+* Returns an edit form for custom type settings.
+*/
+function ctools_entity_field_content_type_formatter_options($form, &$form_state) {
+ if (empty($form_state['conf']['formatter_settings'])) {
+ $form_state['conf']['formatter_settings'] = array();
+ }
+ $conf = $form_state['conf'];
+ $subtype = $form_state['subtype_name'];
+ list($entity_type, $field_name) = explode(':', $subtype, 2);
+
+ $field = field_info_field($field_name);
+ module_load_include('inc', 'field_ui', 'field_ui.admin');
+ $formatter_options = field_ui_formatter_options($field['type']);
+
+ $field_label_options = array(
+ 'title' => t('Pane title'),
+ 'above' => t('Above'),
+ 'inline' => t('Inline'),
+ 'hidden' => t('Hidden'),
+ );
+
+ $form['label'] = array(
+ '#type' => 'select',
+ '#title' => t('Label'),
+ '#options' => $field_label_options,
+ '#default_value' => $conf['label'],
+ );
+
+ $form['formatter'] = array(
+ '#type' => 'select',
+ '#title' => t('Select a formatter'),
+ '#options' => $formatter_options,
+ '#default_value' => $conf['formatter'],
+ );
+
+ return $form;
+}
+
+function ctools_entity_field_content_type_formatter_options_submit($form, &$form_state) {
+ $form_state['conf']['formatter'] = $form_state['values']['formatter'];
+ $form_state['conf']['label'] = $form_state['values']['label'];
+}
+
+function ctools_entity_field_content_type_formatter_styles($form, &$form_state) {
+ if (!$form_state['conf']['formatter_settings']) {
+ $form_state['conf']['formatter_settings'] = array();
+ }
+ $conf = $form_state['conf'];
+ $subtype = $form_state['subtype_name'];
+ list($entity_type, $field_name) = explode(':', $subtype, 2);
+ $field = field_info_field($field_name);
+
+ ctools_form_include($form_state, 'field_ui.admin', 'field_ui', '');
+ ctools_form_include($form_state, 'fields');
+
+ $form['ctools_field_list'] = array(
+ '#type' => 'value',
+ '#value' => array(),
+ );
+
+ ctools_fields_get_field_formatter_settings_form($field, $conf['formatter'], $form, $form_state);
+ return $form;
+}
+
+function ctools_entity_field_content_type_formatter_styles_submit($form, &$form_state) {
+ $fields = $form_state['values']['ctools_field_list'];
+ $formatter_info = ctools_fields_get_field_formatter_info($fields);
+ foreach ($formatter_info as $info) {
+ if (!empty($info['settings'])) {
+ foreach ($info['settings'] as $field_name => $value) {
+ if (isset($form_state['values'][$field_name])) {
+ $form_state['conf']['formatter_settings'][$field_name] = $form_state['values'][$field_name];
+ }
+ }
+ }
+ }
+
+ if (isset($form_state['values']['delta_limit'])) {
+ $form_state['conf']['delta_limit'] = $form_state['values']['delta_limit'];
+ $form_state['conf']['delta_offset'] = $form_state['values']['delta_offset'];
+ $form_state['conf']['delta_reversed'] = $form_state['values']['delta_reversed'];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_entity_field_content_type_admin_title($subtype, $conf, $context) {
+ list($bundle, $field_name) = explode(':', $subtype);
+ ctools_include('fields');
+ if (is_object($context) && isset($context->identifier)) {
+ $identifier = $context->identifier;
+ }
+ else {
+ $type = 'ctools_entity_field_content_type_admin_title';
+ $message = t('Context is missing for field: @name', array('@name' => $subtype));
+ $variables = array($subtype, $conf, $context);
+ watchdog($type, $message, $variables, $severity = WATCHDOG_NOTICE);
+ $identifier = t('Unknown');
+ }
+
+ return t('"@s" @field', array('@s' => $identifier, '@field' => ctools_field_label($field_name)));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/entity_context/entity_field_extra.inc b/sites/all/modules/ctools/plugins/content_types/entity_context/entity_field_extra.inc
new file mode 100644
index 000000000..6a59ed4c6
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/entity_context/entity_field_extra.inc
@@ -0,0 +1,133 @@
+<?php
+
+$plugin = array(
+ 'title' => t('Entity extra field'),
+ 'defaults' => array('view_mode' => NULL),
+ 'content type' => 'ctools_entity_field_extra_content_type_content_type',
+);
+
+/**
+ * Just one subtype.
+ *
+ * Ordinarily this function is meant to get just one subtype. However, we are
+ * using it to deal with the fact that we have changed the subtype names. This
+ * lets us translate the name properly.
+ */
+function ctools_entity_field_extra_content_type_content_type($subtype) {
+ $types = ctools_entity_field_extra_content_type_content_types();
+ if (isset($types[$subtype])) {
+ return $types[$subtype];
+ }
+}
+
+/**
+ * Return all extra field content types available.
+ */
+function ctools_entity_field_extra_content_type_content_types() {
+ // This will hold all the individual field content types.
+ $types = &drupal_static(__FUNCTION__);
+ if (isset($types)) {
+ return $types;
+ }
+
+ $types = array();
+ $context_types = array();
+ $entities = entity_get_info();
+
+ foreach ($entities as $entity_type => $entity) {
+ foreach ($entity['bundles'] as $type => $bundle) {
+ foreach (field_info_extra_fields($entity_type, $type, 'display') as $field_name => $info) {
+ if (!isset($types[$entity_type . ':' . $field_name])) {
+ $types[$entity_type . ':' . $field_name] = array(
+ 'category' => t(ucfirst($entity_type)),
+ 'icon' => 'icon_field.png',
+ 'title' => $info['label'],
+ 'description' => isset($info['description']) ? $info['description'] : '',
+ );
+ }
+ $context_types[$entity_type . ':' . $field_name]['types'][$type] = $bundle['label'];
+ }
+ }
+ }
+
+ // Create the required context for each field related to the bundle types.
+ foreach ($types as $key => $field_content_type) {
+ list($entity_type, $field_name) = explode(':', $key, 2);
+ $types[$key]['required context'] = new ctools_context_required(t(ucfirst($entity_type)), $entity_type, array(
+ 'type' => array_keys($context_types[$key]['types']),
+ ));
+ unset($context_types[$key]['types']);
+ }
+
+ return $types;
+}
+
+/**
+ * Returns an edit form for an extra field.
+ */
+function ctools_entity_field_extra_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $subtype = $form_state['subtype_name'];
+ list($entity_type, $field_name) = explode(':', $subtype, 2);
+
+ $info = entity_get_info($entity_type);
+ $view_mode_options = array();
+ foreach ($info['view modes'] as $mode => $option) {
+ $view_mode_options[$mode] = $option['label'];
+ }
+
+ $form['view_mode'] = array(
+ '#title' => t('View mode'),
+ '#type' => 'select',
+ '#description' => t('Select a view mode for this extra field.'),
+ '#options' => $view_mode_options,
+ '#default_value' => $conf['view_mode'],
+ );
+
+ return $form;
+}
+
+function ctools_entity_field_extra_content_type_edit_form_submit($form, &$form_state) {
+ $form_state['conf']['view_mode'] = $form_state['values']['view_mode'];
+}
+
+/**
+ * Render the extra field.
+ */
+function ctools_entity_field_extra_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+ // Get a shortcut to the entity.
+ $entity = clone $context->data;
+ list($entity_type, $field_name) = explode(':', $subtype, 2);
+ list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+ $langcode = $GLOBALS['language_content']->language;
+
+ $function = $entity_type . '_view';
+ if (in_array($entity_type, array('node', 'taxonomy_term', 'user')) && function_exists($function)) {
+ // Call known ENTITY_view() to get the extra field.
+ $entity->content = $function($entity, $conf['view_mode'], $langcode);
+ }
+ else {
+ // Invoke the view-hook to get the extra field.
+ $entity->content = array();
+
+ module_invoke_all($entity_type . '_view', $entity, $conf['view_mode'], $langcode);
+ module_invoke_all('entity_view', $entity, $entity_type, $conf['view_mode'], $langcode);
+ }
+
+ if (isset($entity->content[$field_name])) {
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'entity_field_extra';
+ $block->content = $entity->content[$field_name];
+ $block->delta = $id;
+ return $block;
+ }
+}
+
+function ctools_entity_field_extra_content_type_admin_title($subtype, $conf, $context) {
+ $info = ctools_entity_field_extra_content_type_content_type($subtype);
+ return t('"@s" @field', array('@s' => $context->identifier, '@field' => $info['title']));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc b/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc
new file mode 100644
index 000000000..582bd785c
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @file
+ * Handle rendering entity fields as panes.
+ */
+
+$plugin = array(
+ 'title' => t('Entity field'),
+ 'defaults' => array('label' => '', 'formatter' => ''),
+ 'content type' => 'ctools_entity_form_field_content_type_content_type',
+);
+
+/**
+ * Just one subtype.
+ *
+ * Ordinarily this function is meant to get just one subtype. However, we are
+ * using it to deal with the fact that we have changed the subtype names. This
+ * lets us translate the name properly.
+ */
+function ctools_entity_form_field_content_type_content_type($subtype) {
+ $types = ctools_entity_form_field_content_type_content_types();
+ if (isset($types[$subtype])) {
+ return $types[$subtype];
+ }
+}
+
+/**
+ * Return all field content types available.
+ */
+function ctools_entity_form_field_content_type_content_types() {
+ // This will hold all the individual field content types.
+ $types = &drupal_static(__FUNCTION__);
+ if (isset($types)) {
+ return $types;
+ }
+
+ $types = array();
+ $content_types = array();
+ $entities = entity_get_info();
+
+ foreach ($entities as $entity_type => $entity) {
+ foreach ($entity['bundles'] as $type => $bundle) {
+ foreach (field_info_instances($entity_type, $type) as $field_name => $field) {
+ if (!isset($types[$entity_type . ':' . $field_name])) {
+ $types[$entity_type . ':' . $field_name] = array(
+ 'category' => t('Form'),
+ 'icon' => 'icon_field.png',
+ 'title' => t('Field form: @widget_label', array(
+ '@widget_label' => t($field['label']),
+ )),
+ 'description' => t('Field on the referenced entity.'),
+ );
+ }
+ $content_types[$entity_type . ':' . $field_name]['types'][$type] = $bundle['label'];
+ }
+ }
+ }
+
+ if (module_exists('field_group')) {
+ foreach ($entities as $entity_type => $entity) {
+ foreach ($entity['bundles'] as $type => $bundle) {
+ if ($group_info = field_group_info_groups($entity_type, $type, "form")) {
+ foreach ($group_info as $group_name => $group) {
+ if (!isset($types[$entity_type . ':' . $group_name])) {
+ $types[$entity_type . ':' . $group_name] = array(
+ 'category' => t('Form'),
+ 'icon' => 'icon_field.png',
+ 'title' => t('Group form: @widget_label', array('@widget_label' => $group->label)),
+ 'description' => t('Field group on the referenced entity.'),
+ );
+ }
+ $content_types[$entity_type . ':' . $group_name]['types'][$type] = $bundle['label'];
+ }
+ }
+ }
+ }
+ }
+
+ // Create the required context for each field related to the bundle types.
+ foreach ($types as $key => $field_content_type) {
+ list($entity_type, $field_name) = explode(':', $key, 2);
+ $types[$key]['required context'] = new ctools_context_required(t(ucfirst($entity_type)), $entity_type, array(
+ 'form' => array('form'),
+ 'type' => array_keys($content_types[$key]['types']),
+ ));
+ unset($content_types[$key]['types']);
+ }
+ return $types;
+}
+
+/**
+* Render the custom content type.
+*/
+function ctools_entity_form_field_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Get a shortcut to the entity.
+ $entity = $context->data;
+ list($entity_type, $field_name) = explode(':', $subtype, 2);
+
+ // Load the entity type's information for this field.
+ $ids = entity_extract_ids($entity_type, $entity);
+ $field = field_info_instance($entity_type, $field_name, $ids[2]);
+
+ // Check for field groups.
+ if (empty($field) && module_exists('field_group')) {
+ $groups = field_group_info_groups($entity_type, $entity->type, "form");
+ $group = !empty($groups[$field_name]) ? $groups[$field_name] : NULL;
+ }
+
+ // Do not render if the entity type does not have this field or group.
+ if (empty($field) && empty($group)) {
+ return;
+ }
+
+ $block = new stdClass();
+ if (isset($context->form)) {
+ $block->content = array();
+ if (!empty($field)) {
+ $block->content[$field_name] = $context->form[$field_name];
+ unset($context->form[$field_name]);
+ }
+ else {
+ // Pre-render the form to populate field groups.
+ if (isset($context->form['#pre_render'])) {
+ foreach ($context->form['#pre_render'] as $function) {
+ if (function_exists($function)) {
+ $context->form = $function($context->form);
+ }
+ }
+ unset($context->form['#pre_render']);
+ }
+
+ $block->content[$field_name] = $context->form[$field_name];
+ unset($context->form[$field_name]);
+ }
+ }
+ else {
+ $block->content = t('Entity info.');
+ }
+
+ return $block;
+}
+
+/**
+* Returns the administrative title for a type.
+*/
+function ctools_entity_form_field_content_type_admin_title($subtype, $conf, $context) {
+ list($entity_type, $field_name) = explode(':', $subtype, 2);
+
+ if (!empty($context->restrictions)) {
+ $field = field_info_instance($entity_type, $field_name, $context->restrictions['type'][0]);
+ }
+ else {
+ $field = array('label' => $subtype);
+ }
+
+ return t('"@s" @field form', array('@s' => $context->identifier, '@field' => $field['label']));
+}
+
+function ctools_entity_form_field_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/form/form.inc b/sites/all/modules/ctools/plugins/content_types/form/form.inc
new file mode 100644
index 000000000..73f7c7801
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/form/form.inc
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ // only provides a single content type
+ 'single' => TRUE,
+ 'render last' => TRUE,
+ 'title' => t('General form'),
+ 'icon' => 'icon_form.png',
+ 'description' => t('Everything in the form that is not displayed by other content.'),
+ 'required context' => new ctools_context_required(t('Form'), 'form'),
+ 'category' => t('Form'),
+);
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function ctools_form_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = 'form';
+
+ if (isset($context->form)) {
+ if (isset($context->form['#pre_render'])) {
+ foreach ($context->form['#pre_render'] as $function) {
+ if (function_exists($function)) {
+ $context->form = $function($context->form);
+ }
+ }
+ unset($context->form['#pre_render']);
+ }
+
+ $block->title = $context->form_title;
+ $block->content = array();
+ foreach (element_children($context->form) as $element) {
+ $block->content[$element] = $context->form[$element];
+ unset($context->form[$element]);
+ }
+
+ $block->delta = $context->form_id;
+ }
+ else {
+ $block->title = t('Form');
+ $block->content = t('Form goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+function ctools_form_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" base form', array('@s' => $context->identifier));
+}
+
+function ctools_form_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to override title
+ // and stuff.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/form/icon_form.png b/sites/all/modules/ctools/plugins/content_types/form/icon_form.png
new file mode 100644
index 000000000..f0417cb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/form/icon_form.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/node/icon_node.png b/sites/all/modules/ctools/plugins/content_types/node/icon_node.png
new file mode 100644
index 000000000..f0417cb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node/icon_node.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/node/node.inc b/sites/all/modules/ctools/plugins/content_types/node/node.inc
new file mode 100644
index 000000000..7e77194c9
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node/node.inc
@@ -0,0 +1,251 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'node' content type which allows individual nodes
+ * to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Existing node'),
+ 'single' => TRUE,
+ 'defaults' => array(
+ 'nid' => '',
+ 'links' => TRUE,
+ 'leave_node_title' => FALSE,
+ 'identifier' => '',
+ 'build_mode' => 'teaser',
+ ),
+ 'icon' => 'icon_node.png',
+ 'description' => t('Add a node from your site as content.'),
+ 'category' => t('Custom'),
+ 'top level' => TRUE,
+ 'js' => array('misc/autocomplete.js'),
+);
+
+/**
+ * Output function for the 'node' content type.
+ *
+ * Outputs a node based on the module and delta supplied in the configuration.
+ */
+function ctools_node_content_type_render($subtype, $conf, $panel_args) {
+ $nid = $conf['nid'];
+ $block = new stdClass();
+
+ foreach (explode('/', $_GET['q']) as $id => $arg) {
+ $nid = str_replace("%$id", $arg, $nid);
+ }
+
+ foreach ($panel_args as $id => $arg) {
+ if (is_string($arg)) {
+ $nid = str_replace("@$id", $arg, $nid);
+ }
+ }
+
+ // Support node translation
+ if (module_exists('translation')) {
+ if ($translations = module_invoke('translation', 'node_get_translations', $nid)) {
+ if (isset($translations[$GLOBALS['language']->language])) {
+ $nid = $translations[$GLOBALS['language']->language]->nid;
+ }
+ }
+ }
+
+ if (!is_numeric($nid)) {
+ return;
+ }
+
+ $node = node_load($nid);
+ if (!node_access('view', $node)) {
+ return;
+ }
+
+ // Don't store viewed node data on the node, this can mess up other
+ // views of the node.
+ $node = clone($node);
+
+ $block->module = 'node';
+ $block->delta = $node->nid;
+
+ // Set block->title to the plain node title, then additionally set block->title_link to
+ // the node url if required. The actual link is rendered in ctools_content_render().
+ $block->title = check_plain($node->title);
+ if (!empty($conf['link_node_title'])) {
+ $block->title_link = 'node/' . $node->nid;
+ }
+
+ if (empty($conf['leave_node_title'])) {
+ $node->title = NULL;
+ }
+
+ if (!empty($conf['identifier'])) {
+ $node->ctools_template_identifier = $conf['identifier'];
+ }
+
+ // Handle existing configurations with the deprecated 'teaser' option.
+ if (isset($conf['teaser'])) {
+ $conf['build_mode'] = $conf['teaser'] ? 'teaser' : 'full';
+ }
+
+ $block->content = node_view($node, $conf['build_mode']);
+
+ // Hide links if they've been suppressed.
+ if (empty($conf['links'])) {
+ $block->content['links']['#access'] = FALSE;
+ }
+
+ return $block;
+}
+
+/**
+ * The form to add or edit a node as content.
+ */
+function ctools_node_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['leave_node_title'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['leave_node_title']),
+ '#title' => t('Leave node title'),
+ '#description' => t('Advanced: if checked, do not touch the node title; this can cause the node title to appear twice unless your theme is aware of this.'),
+ );
+
+ $form['link_node_title'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['link_node_title']),
+ '#title' => t('Link the node title to the node'),
+ '#description' => t('Check this box if you would like your pane title to link to the node.'),
+ );
+
+
+ if ($form_state['op'] == 'add') {
+ $form['nid'] = array(
+ '#prefix' => '<div class="no-float">',
+ '#title' => t('Enter the title or NID of a node'),
+ '#description' => t('To use a NID from the URL, you may use %0, %1, ..., %N to get URL arguments. Or use @0, @1, @2, ..., @N to use arguments passed into the panel.'),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#autocomplete_path' => 'ctools/autocomplete/node',
+ '#weight' => -10,
+ '#suffix' => '</div>',
+ );
+ }
+ else {
+ $form['nid'] = array(
+ '#type' => 'value',
+ '#value' => $conf['nid'],
+ );
+ }
+
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['links'],
+ '#title' => t('Include node links for "add comment", "read more" etc.'),
+ );
+
+ $form['identifier'] = array(
+ '#type' => 'textfield',
+ '#default_value' => !empty($conf['identifier']) ? $conf['identifier'] : '',
+ '#title' => t('Template identifier'),
+ '#description' => t('This identifier will be added as a template suggestion to display this node: node--panel--IDENTIFIER.tpl.php. Please see the Drupal theming guide for information about template suggestions.'),
+ );
+
+ $entity = entity_get_info('node');
+ $build_mode_options = array();
+ foreach ($entity['view modes'] as $mode => $option) {
+ $build_mode_options[$mode] = $option['label'];
+ }
+
+ // Handle existing configurations with the deprecated 'teaser' option.
+ // Also remove the teaser key from the form_state.
+ if (isset($conf['teaser']) || !isset($conf['build_mode'])) {
+ unset($form_state['conf']['teaser']);
+ $conf['build_mode'] = $conf['teaser'] ? 'teaser' : 'full';
+ }
+ $form['build_mode'] = array(
+ '#title' => t('Build mode'),
+ '#type' => 'select',
+ '#description' => t('Select a build mode for this node.'),
+ '#options' => $build_mode_options,
+ '#default_value' => $conf['build_mode'],
+ );
+ return $form;
+}
+
+/**
+ * Validate the node selection.
+ */
+function ctools_node_content_type_edit_form_validate(&$form, &$form_state) {
+ if ($form_state['op'] != 'add') {
+ return;
+ }
+
+ $nid = $form_state['values']['nid'];
+ $preg_matches = array();
+ $match = preg_match('/\[id: (\d+)\]/', $nid, $preg_matches);
+ if (!$match) {
+ $match = preg_match('/^id: (\d+)/', $nid, $preg_matches);
+ }
+
+ if ($match) {
+ $nid = $preg_matches[1];
+ }
+ if (is_numeric($nid)) {
+ $node = db_query('SELECT nid, status FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
+ }
+ else {
+ $node = db_query('SELECT nid, status FROM {node} WHERE LOWER(title) = LOWER(:title)', array(':title' => $nid))->fetchObject();
+ }
+ if ($node) {
+ $form_state['values']['nid'] = $node->nid;
+ }
+
+ if (!($node || preg_match('/^[@%]\d+$/', $nid)) ||
+ // Do not allow unpublished nodes to be selected by unprivileged users
+ (empty($node->status) && !user_access('administer nodes'))) {
+ form_error($form['nid'], t('Invalid node'));
+ }
+}
+
+/**
+ * Validate the node selection.
+ */
+function ctools_node_content_type_edit_form_submit($form, &$form_state) {
+ foreach (array('nid', 'links', 'leave_node_title', 'link_node_title', 'identifier', 'build_mode') as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a node.
+ */
+function ctools_node_content_type_admin_title($subtype, $conf) {
+ if (!is_numeric($conf['nid'])) {
+ return t('Node loaded from @var', array('@var' => $conf['nid']));
+ }
+
+ $node = node_load($conf['nid']);
+ if ($node) {
+ if (!empty($node->status) || user_access('administer nodes')) {
+ return check_plain($node->title);
+ }
+ else {
+ return t('Unpublished node @nid', array('@nid' => $conf['nid']));
+ }
+ }
+ else {
+ return t('Deleted/missing node @nid', array('@nid' => $conf['nid']));
+ }
+}
+
+/**
+ * Display the administrative information for a node pane.
+ */
+function ctools_node_content_type_admin_info($subtype, $conf) {
+ // Just render the node.
+ return ctools_node_content_type_render($subtype, $conf, array());
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/icon_node.png b/sites/all/modules/ctools/plugins/content_types/node_context/icon_node.png
new file mode 100644
index 000000000..f0417cb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/icon_node.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_attachments.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_attachments.inc
new file mode 100644
index 000000000..380e26043
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_attachments.inc
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Attached files'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('A list of files attached to the node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+);
+
+function ctools_node_attachments_content_type_render($subtype, $conf, $panel_args, $context) {
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'attachments';
+
+ $block->title = t('Attached files');
+ if ($node) {
+ if (!empty($node->files)) {
+ $block->content = theme('upload_attachments', $node->files);
+ }
+ $block->delta = $node->nid;
+ }
+ else {
+ $block->content = t('Attached files go here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+function ctools_node_attachments_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" attachments', array('@s' => $context->identifier));
+}
+
+function ctools_node_attachments_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
+
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_author.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_author.inc
new file mode 100644
index 000000000..e98ce5acc
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_author.inc
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node author'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The author of the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'link' => TRUE,
+ ),
+);
+
+/**
+ * Render the custom content type.
+ */
+function ctools_node_author_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Get a shortcut to the node.
+ $node = $context->data;
+ $user = user_load($node->uid);
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'node_author';
+ $block->title = t('Author');
+ $block->content = !empty($conf['link']) ? theme('username', array('account' => $user, 'link_path' => 'user/' . $node->uid)) : check_plain(format_username($node));
+ $block->delta = $node->nid;
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_node_author_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['link'] = array(
+ '#title' => t('Link to author profile'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link'],
+ '#description' => t('Check here to link to the node author profile.'),
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the custom type settings form.
+ */
+function ctools_node_author_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_node_author_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" author', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_body.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_body.inc
new file mode 100644
index 000000000..3e69560a2
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_body.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node body'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The body of the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'no ui' => TRUE,
+);
+
+/**
+ * Render the custom content type.
+ */
+function ctools_node_body_content_type_render($subtype, $conf, $panel_args, $context) {
+ $plugin = ctools_get_content_type('entity_field');
+ $conf['formatter'] = 'text_default';
+ $conf['formatter_settings'] = array();
+ return $plugin['render callback']('node:body', $conf, $panel_args, $context);
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_node_body_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_node_body_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" body', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_book_children.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_book_children.inc
new file mode 100644
index 000000000..5d017c5cd
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_book_children.inc
@@ -0,0 +1,43 @@
+<?php
+
+if (module_exists('book')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Book children'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The children menu the book the node belongs to.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ );
+}
+
+function ctools_node_book_children_content_type_render($subtype, $conf, $panel_args, $context) {
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'book_children';
+
+ $block->title = t('Book children');
+ if ($node) {
+ $block->content = isset($node->book) ? book_children($node->book) : '';
+ $block->delta = $node->nid;
+ }
+ else {
+ $block->content = t('Book children menu goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+function ctools_node_book_children_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" book children', array('@s' => $context->identifier));
+}
+
+function ctools_node_book_children_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_book_nav.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_book_nav.inc
new file mode 100644
index 000000000..6c0d50df7
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_book_nav.inc
@@ -0,0 +1,43 @@
+<?php
+
+if (module_exists('book')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Book navigation'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The navigation menu the book the node belongs to.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ );
+}
+
+function ctools_node_book_nav_content_type_render($subtype, $conf, $panel_args, $context) {
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'book_nav';
+
+ $block->title = t('Book navigation');
+ if ($node) {
+ $block->content = isset($node->book) ? theme('book_navigation', array('book_link' => $node->book)) : '';
+ $block->delta = $node->nid;
+ }
+ else {
+ $block->content = t('Book navigation goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+function ctools_node_book_nav_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" book navigation', array('@s' => $context->identifier));
+}
+
+function ctools_node_book_nav_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_comment_form.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_comment_form.inc
new file mode 100644
index 000000000..c21e7bc7f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_comment_form.inc
@@ -0,0 +1,79 @@
+<?php
+
+if (module_exists('comment')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Comment form'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('A form to add a new comment.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array('anon_links' => FALSE),
+ );
+}
+
+function ctools_node_comment_form_content_type_render($subtype, $conf, $panel_args, $context) {
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'comments';
+ $block->delta = $node->nid;
+
+ $block->title = t('Add comment');
+
+ if (empty($node)) {
+ $block->content = t('Comment form here.');
+ }
+ else if ($node->comment == COMMENT_NODE_OPEN) {
+ if (user_access('post comments')) {
+ $comment = new stdClass();
+ $comment->nid = $node->nid;
+ $comment->pid = NULL;
+ $form_state = array(
+ 'ctools comment alter' => TRUE,
+ 'node' => $node,
+ 'build_info' => array(
+ 'args' => array(
+ $comment,
+ ),
+ ),
+ );
+ $block->content = drupal_build_form('comment_node_' . $node->type . '_form', $form_state);
+ }
+ else if (!empty($conf['anon_links'])) {
+ $block->content = theme('comment_post_forbidden', array('node' => $node));
+ }
+ }
+
+ return $block;
+}
+
+function ctools_node_comment_form_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" comment form', array('@s' => $context->identifier));
+}
+
+function ctools_node_comment_form_content_type_edit_form($form, &$form_state) {
+ $form['anon_links'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Shows links to register or login.'),
+ '#description' => t('If anonymous comments are not allowed, this will display the register and login links.'),
+ '#default_value' => $form_state['conf']['anon_links'],
+ );
+ return $form;
+}
+
+function ctools_node_comment_form_content_type_edit_form_submit($form, &$form_state) {
+ // For each part of the form defined in the 'defaults' array set when you
+ // defined the content type, copy the value from the form into the array
+ // of items to be saved. We don't ever want to use
+ // $form_state['conf'] = $form_state['values'] because values contains
+ // buttons, form id and other items we don't want stored. CTools will handle
+ // the actual form submission.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_comment_wrapper.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_comment_wrapper.inc
new file mode 100644
index 000000000..8e25429f2
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_comment_wrapper.inc
@@ -0,0 +1,117 @@
+<?php
+
+if (module_exists('comment')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Comments and comment form.'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The comments and comment form for the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'mode' => variable_get('comment_default_mode', COMMENT_MODE_THREADED),
+ 'comments_per_page' => variable_get('comment_default_per_page', '50'),
+ ),
+ );
+}
+
+/**
+ * Render the node comments.
+ */
+function ctools_node_comment_wrapper_content_type_render($subtype, $conf, $panel_args, $context) {
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'comments';
+ $block->delta = $node->nid;
+
+ $renderable = array(
+ '#theme' => 'comment_wrapper__node_' . $node->type,
+ '#node' => $node,
+ 'comments' => array(),
+ 'comment_form' => array(),
+ );
+
+ // Add in the comments.
+ if (($node->comment_count && user_access('access comments')) || user_access('administer comments')) {
+ $mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED);
+ $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50);
+ if ($cids = comment_get_thread($node, $mode, $comments_per_page)) {
+ $comments = comment_load_multiple($cids);
+ comment_prepare_thread($comments);
+ $build = comment_view_multiple($comments, $node);
+ $build['pager']['#theme'] = 'pager';
+ $renderable['comments'] = $build;
+ }
+ }
+
+ // Stuff in the comment form.
+ if ($node->comment == COMMENT_NODE_OPEN) {
+ if (user_access('post comments')) {
+ $comment = new stdClass();
+ $comment->nid = $node->nid;
+ $comment->pid = NULL;
+ $form_state = array(
+ 'ctools comment alter' => TRUE,
+ 'node' => $node,
+ 'build_info' => array(
+ 'args' => array(
+ $comment,
+ ),
+ ),
+ );
+ $renderable['comment_form'] = drupal_build_form('comment_node_' . $node->type . '_form', $form_state);
+ }
+ else if (!empty($conf['anon_links'])) {
+ $renderable['comment_form'] = theme('comment_post_forbidden', array('node' => $node));
+ }
+ }
+
+ $block->content = drupal_render($renderable);
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for the comment wrapper.
+ */
+function ctools_node_comment_wrapper_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['mode'] = array(
+ '#type' => 'select',
+ '#title' => t('Mode'),
+ '#default_value' => $conf['mode'],
+ '#options' => _comment_get_modes(),
+ '#weight' => 1,
+ );
+ foreach (_comment_per_page() as $i) {
+ $options[$i] = t('!a comments per page', array('!a' => $i));
+ }
+ $form['comments_per_page'] = array('#type' => 'select',
+ '#title' => t('Pager'),
+ '#default_value' => $conf['comments_per_page'],
+ '#options' => $options,
+ '#weight' => 3,
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the comment wrapper settings form.
+ */
+function ctools_node_comment_wrapper_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title.
+ */
+function ctools_node_comment_wrapper_content_type_admin_title($subtype, $conf, $context) {
+ return t('Comments and comment form');
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_comments.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_comments.inc
new file mode 100644
index 000000000..0f0033d2a
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_comments.inc
@@ -0,0 +1,98 @@
+<?php
+
+if (module_exists('comment')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node comments'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The comments of the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'mode' => variable_get('comment_default_mode', COMMENT_MODE_THREADED),
+ 'comments_per_page' => variable_get('comment_default_per_page', '50'),
+ ),
+ );
+}
+
+function ctools_node_comments_content_type_render($subtype, $conf, $panel_args, $context) {
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'comments';
+ $block->delta = $node->nid;
+
+ $block->title = t('Comments');
+ if (empty($node)) {
+ $block->content = t('Node comments go here.');
+ }
+ else if ($node->comment) {
+ $block->content = ctools_comment_render($node, $conf);
+ // Update the history table, stating that this user viewed this node.
+ node_tag_new($node);
+ }
+
+ return $block;
+}
+
+function ctools_node_comments_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['mode'] = array(
+ '#type' => 'select',
+ '#title' => t('Mode'),
+ '#default_value' => $conf['mode'],
+ '#options' => _comment_get_modes(),
+ '#weight' => 1,
+ );
+ foreach (_comment_per_page() as $i) {
+ $options[$i] = t('!a comments per page', array('!a' => $i));
+ }
+ $form['comments_per_page'] = array('#type' => 'select',
+ '#title' => t('Pager'),
+ '#default_value' => $conf['comments_per_page'],
+ '#options' => $options,
+ '#weight' => 3,
+ );
+ return $form;
+}
+
+function ctools_node_comments_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+function ctools_node_comments_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" comments', array('@s' => $context->identifier));
+}
+
+/**
+ * This function is a somewhat stripped down version of comment_render
+ * that removes a bunch of cruft that we both don't need, and makes it
+ * difficult to modify this.
+ */
+function ctools_comment_render($node, $conf) {
+ $output = '';
+ if (!user_access('access comments') || !$node->comment) {
+ return;
+ }
+
+ $mode = $conf['mode'];
+ $comments_per_page = $conf['comments_per_page'];
+
+ $cids = comment_get_thread($node, $mode, $comments_per_page);
+ $comments = comment_load_multiple($cids);
+
+ if ($comments) {
+ drupal_add_css(drupal_get_path('module', 'comment') . '/comment.css');
+ comment_prepare_thread($comments);
+ $build = comment_view_multiple($comments, $node);
+ $build['pager']['#theme'] = 'pager';
+ return drupal_render($build);
+ }
+ return;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_content.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_content.inc
new file mode 100644
index 000000000..38c5b5744
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_content.inc
@@ -0,0 +1,204 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node content'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The content of the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'links' => TRUE,
+ 'no_extras' => TRUE,
+ 'override_title' => FALSE,
+ 'override_title_text' => '',
+ 'identifier' => '',
+ 'link' => TRUE,
+ 'leave_node_title' => FALSE,
+ 'build_mode' => 'teaser',
+ ),
+);
+
+/**
+ * Render the node content.
+ */
+function ctools_node_content_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (!empty($context) && empty($context->data)) {
+ return;
+ }
+
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'node';
+ $block->delta = $node->nid;
+
+ if (empty($node)) {
+ $block->delta = 'placeholder';
+ $block->title = t('Node title.');
+ $block->content = t('Node content goes here.');
+ }
+ else {
+ if (!empty($conf['identifier'])) {
+ $node->ctools_template_identifier = $conf['identifier'];
+ }
+
+ $block->title = $node->title;
+ if (empty($conf['leave_node_title'])) {
+ $node->title = NULL;
+ }
+ $block->content = ctools_node_content_render_node($node, $conf);
+ }
+
+ if (!empty($conf['link']) && $node) {
+ $block->title_link = "node/$node->nid";
+ }
+
+ return $block;
+}
+
+function ctools_node_content_render_node($node, $conf) {
+ if (empty($node->content)) {
+ // Copied from node_build_content() so we can fiddle with it as we render.
+ $node->content = array();
+
+ // The 'view' hook can be implemented to overwrite the default function
+ // to display nodes.
+ if (node_hook($node, 'view')) {
+ $node = node_invoke($node, 'view', $conf['build_mode']);
+ }
+
+ // Build fields content.
+ // In case of a multiple view, node_view_multiple() already ran the
+ // 'prepare_view' step. An internal flag prevents the operation from running
+ // twice.
+ field_attach_prepare_view('node', array($node->nid => $node), $conf['build_mode']);
+ entity_prepare_view('node', array($node->nid => $node));
+ $node->content += field_attach_view('node', $node, $conf['build_mode']);
+
+ // Always display a read more link on teasers because we have no way
+ // to know when a teaser view is different than a full view.
+ $links = array();
+ if ($conf['build_mode'] == 'teaser') {
+ $links['node-readmore'] = array(
+ 'title' => t('Read more'),
+ 'href' => 'node/' . $node->nid,
+ 'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title))
+ );
+ }
+
+ $node->content['links'] = array(
+ '#theme' => 'links__node',
+ '#links' => $links,
+ '#attributes' => array('class' => array('links', 'inline')),
+ );
+
+ if (empty($conf['no_extras'])) {
+ // Allow modules to make their own additions to the node.
+ $langcode = $GLOBALS['language_content']->language;
+ module_invoke_all('node_view', $node, $conf['build_mode'], $langcode);
+ module_invoke_all('entity_view', $node, 'node', $conf['build_mode'], $langcode);
+ }
+ }
+
+ // Set the proper node part, then unset unused $node part so that a bad
+ // theme can not open a security hole.
+ $content = $node->content;
+
+ $content += array(
+ '#theme' => 'node',
+ '#node' => $node,
+ '#view_mode' => $conf['build_mode'],
+ '#language' => NULL,
+ );
+
+ // Add contextual links for this node, except when the node is already being
+ // displayed on its own page. Modules may alter this behavior (for example,
+ // to restrict contextual links to certain view modes) by implementing
+ // hook_node_view_alter().
+ if (!empty($node->nid) && !($conf['build_mode'] == 'full' && node_is_page($node))) {
+ $content['#contextual_links']['node'] = array('node', array($node->nid));
+ }
+
+ // Allow modules to modify the structured node.
+ $type = 'node';
+ drupal_alter(array('node_view', 'entity_view'), $content, $type);
+
+ // Kill the links if not requested.
+ if (!$conf['links']) {
+ $content['links']['#access'] = FALSE;
+ }
+
+ return $content;
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function ctools_node_content_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['leave_node_title'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['leave_node_title']),
+ '#title' => t('Leave node title'),
+ '#description' => t('Advanced: if checked, do not touch the node title; this can cause the node title to appear twice unless your theme is aware of this.'),
+ );
+
+ $form['link'] = array(
+ '#title' => t('Link title to node'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link'],
+ '#description' => t('Check here to make the title link to the node.'),
+ );
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['links'],
+ '#title' => t('Include node links for "add comment", "read more" etc.'),
+ );
+
+ $form['no_extras'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['no_extras'],
+ '#title' => t('No extras'),
+ '#description' => t('Check here to disable additions that modules might make to the node, such as file attachments and CCK fields; this should just display the basic teaser or body.'),
+ );
+
+ $form['identifier'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['identifier'],
+ '#title' => t('Template identifier'),
+ '#description' => t('This identifier will be added as a template suggestion to display this node: node--panel--IDENTIFIER.tpl.php. Please see the Drupal theming guide for information about template suggestions.'),
+ );
+
+ $entity = entity_get_info('node');
+ $build_mode_options = array();
+ foreach ($entity['view modes'] as $mode => $option) {
+ $build_mode_options[$mode] = $option['label'];
+ }
+
+ $form['build_mode'] = array(
+ '#title' => t('Build mode'),
+ '#type' => 'select',
+ '#description' => t('Select a build mode for this node.'),
+ '#options' => $build_mode_options,
+ '#default_value' => $conf['build_mode'],
+ );
+
+ return $form;
+}
+
+function ctools_node_content_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+function ctools_node_content_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" content', array('@s' => $context->identifier));
+}
+
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_created.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_created.inc
new file mode 100644
index 000000000..06d14427b
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_created.inc
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node created date'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The date the referenced node was created.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'format' => 'small',
+ ),
+);
+
+/**
+ * Render the custom content type.
+ */
+function ctools_node_created_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Get a shortcut to the node.
+ $node = $context->data;
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'node_created';
+ $block->title = t('Created date');
+ $block->content = format_date($node->created, $conf['format']);
+ $block->delta = $node->nid;
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_node_created_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $date_types = array();
+
+ foreach (system_get_date_types() as $date_type => $definition) {
+ $date_types[$date_type] = format_date(REQUEST_TIME, $date_type);
+ }
+ $form['format'] = array(
+ '#title' => t('Date format'),
+ '#type' => 'select',
+ '#options' => $date_types,
+ '#default_value' => $conf['format'],
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the custom type settings form.
+ */
+function ctools_node_created_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_node_created_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" created date', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_links.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_links.inc
new file mode 100644
index 000000000..6096a44fb
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_links.inc
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node links'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('Node links of the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'override_title' => FALSE,
+ 'override_title_text' => '',
+ 'build_mode' => '',
+ 'identifier' => '',
+ 'link' => TRUE,
+ ),
+);
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function ctools_node_links_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (!empty($context) && empty($context->data)) {
+ return;
+ }
+
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'node';
+ $block->delta = $node->nid;
+
+ if (empty($node)) {
+ $block->delta = 'placeholder';
+ $block->subject = t('Node title.');
+ $block->content = t('Node links go here.');
+ }
+ else {
+ if (!empty($conf['identifier'])) {
+ $node->panel_identifier = $conf['identifier'];
+ }
+
+ $block->subject = $node->title;
+ node_build_content($node, $conf['build_mode']);
+ $block->content = $node->content['links'];
+ }
+
+ if (!empty($conf['link']) && $node) {
+ $block->title_link = "node/$node->nid";
+ }
+ return $block;
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function ctools_node_links_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['link'] = array(
+ '#title' => t('Link title to node'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link'],
+ '#description' => t('Check here to make the title link to the node.'),
+ );
+
+ $entity = entity_get_info('node');
+ $build_mode_options = array();
+ foreach ($entity['view modes'] as $mode => $option) {
+ $build_mode_options[$mode] = $option['label'];
+ }
+
+ $form['build_mode'] = array(
+ '#title' => t('Build mode'),
+ '#type' => 'select',
+ '#description' => t('Select a build mode for this node.'),
+ '#options' => $build_mode_options,
+ '#default_value' => $conf['build_mode'],
+ );
+
+ $form['identifier'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['identifier'],
+ '#title' => t('Identifier'),
+ '#description' => t('Whatever is placed here will appear in @identifier, to help theme node links displayed on the panel', array('@identifier' => $node->panel_identifier)),
+ );
+
+ return $form;
+}
+
+function ctools_node_links_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+function ctools_node_links_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" links', array('@s' => $context->identifier));
+}
+
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_terms.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_terms.inc
new file mode 100644
index 000000000..f6e7aec33
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_terms.inc
@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node terms'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('Taxonomy terms of the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'vid' => 0,
+ 'term_format' => 'term-links',
+ 'link' => TRUE,
+ 'term_delimiter' => ', ',
+ ),
+);
+
+/**
+ * Render the node_terms content type.
+ */
+function ctools_node_terms_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Get a shortcut to the node.
+ $node = $context->data;
+
+ // Load all terms for this node from all vocabularies
+ $query = db_select('taxonomy_index', 't');
+ $result = $query
+ ->fields('t')
+ ->condition('t.nid', $node->nid)
+ ->execute();
+
+ $tids = array();
+ foreach ($result AS $term) {
+ $tids[] = $term->tid;
+ }
+
+ // Get the real term objects
+ $term_objects = taxonomy_term_load_multiple($tids);
+
+ $terms = array();
+
+ if (empty($conf['vid'])) {
+ // All terms.
+ foreach ($term_objects AS $term) {
+ $terms['taxonomy_term_' . $term->tid] = array(
+ 'title' => check_plain($term->name),
+ 'href' => 'taxonomy/term/' . $term->tid,
+ 'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
+ );
+ }
+ }
+ else {
+ // They want something special and custom, we'll have to do this ourselves.
+ foreach ($term_objects as $term) {
+ if ($term->vid == $conf['vid']) {
+ if ($conf['term_format'] == 'term-links') {
+ $terms['taxonomy_term_' . $term->tid] = array(
+ 'title' => $term->name,
+ 'href' => 'taxonomy/term/' . $term->tid,
+ 'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description)),
+ );
+ }
+ elseif (empty($conf['link'])) {
+ $terms[] = check_plain($term->name);
+ }
+ else {
+ $terms[] = l($term->name, 'taxonomy/term/' . $term->tid, array('attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))));
+ }
+ }
+ }
+ }
+
+ $formatted_terms = '';
+ switch ($conf['term_format']) {
+ case 'term-links':
+ drupal_alter('link', $terms, $node);
+ $formatted_terms = theme('links', array('links' => $terms));
+ break;
+
+ case 'ul':
+ $formatted_terms = theme('item_list', array('items' => $terms));
+ break;
+
+ case 'inline-delimited':
+ $delimiter = isset($conf['term_delimiter']) ? $conf['term_delimiter'] : ', ';
+ $processed_terms = array();
+ foreach ($terms as $key => $term) {
+ if (is_string($term)) {
+ $processed_terms[$key] = $term;
+ }
+ else {
+ $terms[$key] = l($term['title'], $term['href'], $term);
+ }
+ }
+
+ $formatted_terms = implode($delimiter, $processed_terms);
+ break;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'node_terms';
+ $block->delta = $node->nid;
+ $block->title = t('Terms');
+ $block->content = $formatted_terms;
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for node terms display settings.
+ *
+ * The first question is if they want to display all terms or restrict it to a
+ * specific taxonomy vocabulary.
+ *
+ * Then, they're presented with a set of radios to find out how they want the
+ * terms formatted, which can be either be via theme('links'), a regular item
+ * list (ul), or inline with a delimiter. Depending on which radio they
+ * choose, some other settings might appear. If they're doing either the ul or
+ * inline, we ask if they want the terms to appear as links or not. If they
+ * want it inline, we ask what delimiter they want to use.
+ */
+function ctools_node_terms_content_type_edit_form($form, &$form_state) {
+ ctools_include('dependent');
+
+ $conf = $form_state['conf'];
+
+ $options = array();
+ $options[0] = t('- All vocabularies -');
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ }
+ $form['vid'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => $conf['vid'],
+ '#description' => t('Optionally restrict the terms to a specific vocabulary, or allow terms from all vocabularies.'),
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ $form['term_format'] = array(
+ '#type' => 'radios',
+ '#title' => t('Term formatting'),
+ '#options' => array(
+ 'term-links' => t("Taxonomy links (uses theme('links'))"),
+ 'ul' => t('Unordered list'),
+ 'inline-delimited' => t('Inline, delimited'),
+ ),
+ '#default_value' => $conf['term_format'],
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ $form['link'] = array(
+ '#title' => t('Link to terms'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link'],
+ '#description' => t('Check here to make the terms link to the term paths.'),
+ '#dependency' => array('radio:term_format' => array('inline-delimited', 'ul')),
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ $form['term_delimiter'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Term delimiter'),
+ '#default_value' => $conf['term_delimiter'],
+ '#size' => 10,
+ '#dependency' => array('radio:term_format' => array('inline-delimited')),
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the custom type settings form.
+ */
+function ctools_node_terms_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_node_terms_content_type_admin_title($subtype, $conf, $context) {
+ $placeholders['@s'] = $context->identifier;
+ if (!empty($conf['vid'])) {
+ $vocabulary = taxonomy_vocabulary_load($conf['vid']);
+ $placeholders['@vocabulary'] = $vocabulary->name;
+ return t('"@s" terms from @vocabulary', $placeholders);
+ }
+ return t('"@s" terms', $placeholders);
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_title.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_title.inc
new file mode 100644
index 000000000..bec898256
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_title.inc
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node title'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The title of the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'link' => TRUE,
+ 'markup' => 'none',
+ 'id' => '',
+ 'class' => '',
+ ),
+);
+
+/**
+ * Render the custom content type.
+ */
+function ctools_node_title_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Get a shortcut to the node.
+ $node = $context->data;
+
+ // Load information about the node type.
+ $type = node_type_get_type($node);
+
+ // Generate the title
+ $content = !empty($conf['link']) ? l($node->title, 'node/' . $node->nid) : check_plain($node->title);
+
+ // Build any surrounding markup if so configured
+ if (isset($conf['markup']) && $conf['markup'] != 'none') {
+ $markup = '<' . $conf['markup'];
+ if (!empty($conf['id'])) {
+ $markup .= ' id="' . $conf['id'] . '"';
+ }
+ if (!empty($conf['class'])) {
+ $markup .= ' class="' . $conf['class'] . '"';
+ }
+ $markup .= '>' . $content . '</' . $conf['markup'] . '>' . "\n";
+ $content = $markup;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'node_title';
+ $block->title = $type->title_label;
+ $block->content = $content;
+ $block->delta = $node->nid;
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_node_title_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['markup'] = array(
+ '#title' => t('Title tag'),
+ '#type' => 'select',
+ '#options' => array(
+ 'none' => t('- No tag -'),
+ 'h1' => t('h1'),
+ 'h2' => t('h2'),
+ 'h3' => t('h3'),
+ 'h4' => t('h4'),
+ 'h5' => t('h5'),
+ 'h6' => t('h6'),
+ 'div' => t('div'),
+ ),
+ '#default_value' => $conf['markup'],
+ );
+
+ $form['id'] = array(
+ '#title' => t('CSS id to use'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['id'],
+ );
+
+ $form['class'] = array(
+ '#title' => t('CSS class to use'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['class'],
+ );
+
+ $form['link'] = array(
+ '#title' => t('Link to node'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link'],
+ '#description' => t('Check here to make the title link to the node.'),
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the custom type settings form.
+ */
+function ctools_node_title_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_node_title_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" title', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_type_desc.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_type_desc.inc
new file mode 100644
index 000000000..f2005d08b
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_type_desc.inc
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node type description'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('Node type description.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+);
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function ctools_node_type_desc_content_type_render($subtype, $conf, $panel_args, $context) {
+ $node = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'node_type';
+
+ if ($node) {
+ $type = node_type_get_type($node);
+ $block->title = $type->name;
+ $block->content = filter_xss_admin($type->description);
+ $block->delta = $node->type;
+ }
+ else {
+ $block->title = t('Node type description');
+ $block->content = t('Node type description goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+function ctools_node_type_desc_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" type description', array('@s' => $context->identifier));
+}
+
+function ctools_node_type_desc_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_updated.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_updated.inc
new file mode 100644
index 000000000..60ac4b545
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_updated.inc
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node last updated date'),
+ 'icon' => 'icon_node.png',
+ 'description' => t('The date the referenced node was last updated.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'category' => t('Node'),
+ 'defaults' => array(
+ 'format' => 'small',
+ ),
+);
+
+/**
+ * Render the custom content type.
+ */
+function ctools_node_updated_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data) || empty($context->data->nid)) {
+ return;
+ }
+
+ // Get a shortcut to the node.
+ $node = $context->data;
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'node_updated';
+ $block->title = t('Last updated date');
+ $block->content = format_date(!empty($node->changed) ? $node->changed : $node->created, $conf['format']);
+ $block->delta = $node->nid;
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_node_updated_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $date_types = array();
+
+ foreach (system_get_date_types() as $date_type => $definition) {
+ $date_types[$date_type] = format_date(REQUEST_TIME, $date_type);
+ }
+
+ $form['format'] = array(
+ '#title' => t('Date format'),
+ '#type' => 'select',
+ '#options' => $date_types,
+ '#default_value' => $conf['format'],
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the custom type settings form.
+ */
+function ctools_node_updated_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_node_updated_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" last updated date', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/icon_node_form.png b/sites/all/modules/ctools/plugins/content_types/node_form/icon_node_form.png
new file mode 100644
index 000000000..f0417cb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/icon_node_form.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_attachments.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_attachments.inc
new file mode 100644
index 000000000..1e248f500
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_attachments.inc
@@ -0,0 +1,51 @@
+<?php
+
+if (module_exists('upload')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form file attachments'),
+ 'description' => t('File attachments on the Node form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+ );
+}
+
+function ctools_node_form_attachments_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->title = t('Attach files');
+ $block->delta = 'url-path-options';
+
+ if (isset($context->form)) {
+ if (isset($context->form['attachments'])) {
+ $block->content = $context->form['attachments'];
+ if (isset($block->content['attachments']['#group'])) {
+ unset($block->content['attachments']['#pre_render']);
+ unset($block->content['attachments']['#theme_wrappers']);
+ $block->content['attachments']['#type'] = '';
+ }
+ // Set access to false on the original rather than removing so that
+ // vertical tabs doesn't clone it. I think this is due to references.
+ $context->form['attachments']['#access'] = FALSE;
+ }
+ }
+ else {
+ $block->content = t('Attach files.');
+ }
+ return $block;
+}
+
+function ctools_node_form_attachments_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form attach files', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_attachments_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_author.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_author.inc
new file mode 100644
index 000000000..350df40a9
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_author.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form author information'),
+ 'description' => t('Author information on the Node form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+);
+
+function ctools_node_form_author_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->title = t('Authoring information');
+ $block->delta = 'author-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form['author'])) {
+ $block->content['author'] = $context->form['author'];
+ if (isset($block->content['author']['#group'])) {
+ unset($block->content['author']['#pre_render']);
+ unset($block->content['author']['#theme_wrappers']);
+ $block->content['author']['#type'] = '';
+ $block->content['author']['name']['#size'] /= 2;
+ $block->content['author']['date']['#size'] /= 2;
+ }
+
+ // Set access to false on the original rather than removing so that
+ // vertical tabs doesn't clone it. I think this is due to references.
+ $context->form['author']['#access'] = FALSE;
+ }
+ }
+ else {
+ $block->content = t('Authoring information.');
+ }
+ return $block;
+}
+
+function ctools_node_form_author_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form author information', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_author_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_book.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_book.inc
new file mode 100644
index 000000000..ad19590f1
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_book.inc
@@ -0,0 +1,50 @@
+<?php
+
+if (module_exists('book')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form book options'),
+ 'description' => t('Book options for the node.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+ );
+}
+
+function ctools_node_form_book_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->title = t('Book outline');
+ $block->delta = 'book-outline';
+
+ if (isset($context->form)) {
+ if (isset($context->form['book'])) {
+ $block->content['book'] = $context->form['book'];
+ unset($block->content['book']['#pre_render']);
+ unset($block->content['book']['#theme_wrappers']);
+ $block->content['book']['#type'] = '';
+
+ // Set access to false on the original rather than removing so that
+ // vertical tabs doesn't clone it. I think this is due to references.
+ $context->form['book']['#access'] = FALSE;
+ }
+ }
+ else {
+ $block->content = t('Book options.');
+ }
+ return $block;
+}
+
+function ctools_node_form_book_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form book options', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_book_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_buttons.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_buttons.inc
new file mode 100644
index 000000000..b7ac9841d
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_buttons.inc
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form submit buttons'),
+ 'description' => t('Submit buttons for the node form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+);
+
+function ctools_node_form_buttons_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->title = '';
+ $block->delta = 'buttons';
+
+ if (isset($context->form)) {
+ $block->content = array();
+ foreach (array('actions', 'form_token', 'form_build_id', 'form_id') as $element) {
+ $block->content[$element] = isset($context->form[$element]) ? $context->form[$element] : NULL;
+ unset($context->form[$element]);
+ }
+ }
+ else {
+ $block->content = t('Node form buttons.');
+ }
+ return $block;
+}
+
+function ctools_node_form_buttons_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form submit buttons', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_buttons_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_comment.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_comment.inc
new file mode 100644
index 000000000..d0f137ae7
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_comment.inc
@@ -0,0 +1,50 @@
+<?php
+
+if (module_exists('comment')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form comment settings'),
+ 'description' => t('Comment settings on the Node form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+ );
+}
+
+function ctools_node_form_comment_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->title = t('Comment options');
+ $block->delta = 'comment-options';
+
+ if (isset($context->form)) {
+ if (isset($context->form['comment_settings'])) {
+ $block->content['comment_settings'] = $context->form['comment_settings'];
+ unset($block->content['comment_settings']['#pre_render']);
+ unset($block->content['comment_settings']['#theme_wrappers']);
+ $block->content['comment_settings']['#type'] = '';
+
+ // Set access to false on the original rather than removing so that
+ // vertical tabs doesn't clone it. I think this is due to references.
+ $context->form['comment_settings']['#access'] = FALSE;
+ }
+ }
+ else {
+ $block->content = t('Comment options.');
+ }
+ return $block;
+}
+
+function ctools_node_form_comment_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form comment settings', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_comment_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_language.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_language.inc
new file mode 100644
index 000000000..2043c1c52
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_language.inc
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form languages'),
+ 'description' => t('The language selection form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+);
+
+function ctools_node_form_language_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->delta = 'language-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form['language'])) {
+ $block->content['language'] = $context->form['language'];
+ unset($context->form['language']);
+ }
+ }
+ else {
+ $block->content = t('Node language form.');
+ }
+ return $block;
+}
+
+function ctools_node_form_language_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form language field', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_language_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_log.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_log.inc
new file mode 100644
index 000000000..334ff5400
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_log.inc
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form revision log message'),
+ 'description' => t('Revision log message for the node.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+);
+
+function ctools_node_form_log_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+ $block->title = t('Revision information');
+
+ if (isset($context->form)) {
+ if (isset($context->form['revision_information'])) {
+ $block->content['revision_information'] = $context->form['revision_information'];
+ unset($block->content['revision_information']['#pre_render']);
+ unset($block->content['revision_information']['#theme_wrappers']);
+ $block->content['revision_information']['#type'] = '';
+
+ // Set access to false on the original rather than removing so that
+ // vertical tabs doesn't clone it. I think this is due to references.
+ $context->form['revision_information']['#access'] = FALSE;
+ }
+ }
+ else {
+ $block->content = t('Revision information.');
+ }
+
+ return $block;
+}
+
+function ctools_node_form_log_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form revision log', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_log_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_menu.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_menu.inc
new file mode 100644
index 000000000..906ade4d7
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_menu.inc
@@ -0,0 +1,50 @@
+<?php
+
+if (module_exists('menu')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form menu settings'),
+ 'description' => t('Menu settings on the Node form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+ );
+}
+
+function ctools_node_form_menu_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->title = t('Menu options');
+ $block->delta = 'menu-options';
+
+ if (isset($context->form)) {
+ if (isset($context->form['menu'])) {
+ $block->content['menu'] = $context->form['menu'];
+ unset($block->content['menu']['#pre_render']);
+ unset($block->content['menu']['#theme_wrappers']);
+ $block->content['menu']['#type'] = '';
+
+ // Set access to false on the original rather than removing so that
+ // vertical tabs doesn't clone it. I think this is due to references.
+ $context->form['menu']['#access'] = FALSE;
+ }
+ }
+ else {
+ $block->content = t('Menu options.');
+ }
+ return $block;
+}
+
+function ctools_node_form_menu_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form menu settings', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_menu_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_path.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_path.inc
new file mode 100644
index 000000000..a1e3cba03
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_path.inc
@@ -0,0 +1,51 @@
+<?php
+
+if (module_exists('path')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form url path settings'),
+ 'description' => t('Publishing options on the Node form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+ );
+}
+
+function ctools_node_form_path_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->title = t('URL path options');
+ $block->delta = 'url-path-options';
+
+ if (isset($context->form)) {
+ if (isset($context->form['path'])) {
+ $block->content['path'] = $context->form['path'];
+ unset($block->content['path']['#pre_render']);
+ unset($block->content['path']['#theme_wrappers']);
+ $block->content['path']['#type'] = '';
+ $block->content['path']['alias']['#size'] /= 2;
+
+ // Set access to false on the original rather than removing so that
+ // vertical tabs doesn't clone it. I think this is due to references.
+ $context->form['path']['#access'] = FALSE;
+ }
+ }
+ else {
+ $block->content = t('URL Path options.');
+ }
+ return $block;
+}
+
+function ctools_node_form_path_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form path options', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_path_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_publishing.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_publishing.inc
new file mode 100644
index 000000000..e73cff21b
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_publishing.inc
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Publishing options form for the node. This contains the basic settings
+ * like published, moderated, node revision, etc.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Node form publishing options'),
+ 'icon' => 'icon_node_form.png',
+ 'description' => t('Publishing options on the Node form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+);
+
+function ctools_node_form_publishing_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+
+ $block->title = t('Publishing options');
+ $block->module = t('node_form');
+ $block->delta = 'publishing-options';
+
+ if (isset($context->form)) {
+ if (isset($context->form['options'])) {
+ $block->content['options'] = $context->form['options'];
+ unset($block->content['options']['#pre_render']);
+ unset($block->content['options']['#theme_wrappers']);
+ $block->content['options']['#type'] = '';
+
+ // Set access to false on the original rather than removing so that
+ // vertical tabs doesn't clone it. I think this is due to references.
+ $context->form['options']['#access'] = FALSE;
+ }
+ }
+ else {
+ $block->content = t('Publishing options.');
+ }
+ return $block;
+}
+
+function ctools_node_form_publishing_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form publishing options', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_publishing_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_title.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_title.inc
new file mode 100644
index 000000000..f40d274de
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_title.inc
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'icon' => 'icon_node_form.png',
+ 'title' => t('Node form title field'),
+ 'description' => t('The node title form.'),
+ 'required context' => new ctools_context_required(t('Form'), 'node_form'),
+ 'category' => t('Form'),
+);
+
+function ctools_node_form_title_content_type_render($subtype, $conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = t('node_form');
+
+ $block->delta = 'title-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form['title'])) {
+ $block->content['title'] = $context->form['title'];
+ unset($context->form['title']);
+ }
+ }
+ else {
+ $block->content = t('Node title form.');
+ }
+ return $block;
+}
+
+function ctools_node_form_title_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" node form title field', array('@s' => $context->identifier));
+}
+
+function ctools_node_form_title_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_actions.inc b/sites/all/modules/ctools/plugins/content_types/page/page_actions.inc
new file mode 100644
index 000000000..c20c40822
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_actions.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page_actions' content type which allows the local
+ * actions template variables to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Actions'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the action links (local tasks) as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_actions' content type.
+ *
+ * Outputs the actions (local tasks) of the current page.
+ */
+function ctools_page_actions_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $block->content = theme('ctools_menu_local_actions_wrapper', array('links' => menu_local_actions()));
+
+ return $block;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_breadcrumb.inc b/sites/all/modules/ctools/plugins/content_types/page/page_breadcrumb.inc
new file mode 100644
index 000000000..f5a060eff
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_breadcrumb.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page_breadcrumb' content type which allows the
+ * breadcrumb trail of the current page to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Breadcrumb'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the breadcrumb trail as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_breadcrumb' content type.
+ *
+ * Outputs the breadcrumb for the current page.
+ */
+function ctools_page_breadcrumb_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $block->content = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb()));
+
+ return $block;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_feed_icons.inc b/sites/all/modules/ctools/plugins/content_types/page/page_feed_icons.inc
new file mode 100644
index 000000000..0dba317d8
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_feed_icons.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page_feed_icons' content type which allows the
+ * feed_icons statement of the site to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Feed icons'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the site feed_icons statement as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_feed_icons' content type.
+ *
+ * Outputs the feed_icons statement for the site.
+ */
+function ctools_page_feed_icons_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $block->content = drupal_get_feeds();
+
+ return $block;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_help.inc b/sites/all/modules/ctools/plugins/content_types/page/page_help.inc
new file mode 100644
index 000000000..da1abe69f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_help.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page_help' content type which allows the
+ * help text of the current page to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Help'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the help text of the current page as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_help' content type.
+ *
+ * Outputs the breadcrumb for the current page.
+ */
+function ctools_page_help_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $block->content = theme('help');
+
+ return $block;
+}
+
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_logo.inc b/sites/all/modules/ctools/plugins/content_types/page/page_logo.inc
new file mode 100644
index 000000000..c00ca5e88
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_logo.inc
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page_logo' content type which allows the
+ * logo of the site to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Site logo'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the logo trail as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_logo' content type.
+ *
+ * Outputs the logo for the current page.
+ */
+function ctools_page_logo_content_type_render($subtype, $conf, $panel_args) {
+ $logo = theme_get_setting('logo');
+ $block = new stdClass();
+
+ if (!empty($logo)) {
+ $image = '<img src="' . $logo . '" alt="' . t('Home') . '" />';
+ $block->content = l($image, '', array('html' => TRUE, 'attributes' => array('rel' => 'home', 'id' => 'logo', 'title' => t('Home'))));
+ }
+
+ return $block;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_messages.inc b/sites/all/modules/ctools/plugins/content_types/page/page_messages.inc
new file mode 100644
index 000000000..e2fe37b35
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_messages.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page_messages' content type which allows the
+ * status messages of the current page to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Status messages'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the status messages of the current page as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_messages' content type.
+ *
+ * Outputs the breadcrumb for the current page.
+ */
+function ctools_page_messages_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $block->content = theme('status_messages');
+
+ return $block;
+}
+
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_primary_links.inc b/sites/all/modules/ctools/plugins/content_types/page/page_primary_links.inc
new file mode 100644
index 000000000..b1e3e0e3d
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_primary_links.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page' content type which allows the standard page
+ * template variables to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Primary navigation links'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the primary_links (local tasks) as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_primary_links' content type.
+ *
+ * Outputs the primary_links (local tasks) of the current page.
+ */
+function ctools_page_primary_links_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $block->content = theme('links', array('links' => menu_main_menu(), 'attributes' => array('class' => 'links primary-links')));
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_page_primary_links_content_type_edit_form($form, &$form_state) {
+ // Empty so that we can have title override.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_secondary_links.inc b/sites/all/modules/ctools/plugins/content_types/page/page_secondary_links.inc
new file mode 100644
index 000000000..8c6d4dc9f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_secondary_links.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page' content type which allows the standard page
+ * template variables to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Secondary navigation links'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the secondary_links (local tasks) as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_secondary_links' content type.
+ *
+ * Outputs the secondary_links (local tasks) of the current page.
+ */
+function ctools_page_secondary_links_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $block->content = theme('links', array('links' => menu_secondary_menu(), 'attributes' => array('class' => 'links secondary-links')));
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_page_secondary_links_content_type_edit_form($form, &$form_state) {
+ // Empty so that we can have title override.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_site_name.inc b/sites/all/modules/ctools/plugins/content_types/page/page_site_name.inc
new file mode 100644
index 000000000..3053d451a
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_site_name.inc
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page_site_name' content type which allows the
+ * site_name of the site to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used by the
+ * system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Site name'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('The name of the site, optionally links to the front page.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+ 'defaults' => array(
+ 'linked' => FALSE,
+ ),
+);
+
+/**
+ * Settings form for the Site Name pane.
+ */
+function ctools_page_site_name_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['linked'] = array(
+ '#title' => t('Linked'),
+ '#description' => t('Link the site name to the home page.'),
+ '#type' => 'checkbox',
+ '#default_value' => isset($conf['linked']) ? $conf['linked'] : FALSE,
+ );
+
+ return $form;
+}
+
+/**
+ * The submit form stores the data in $conf.
+ */
+function ctools_page_site_name_content_type_edit_form_submit($form, &$form_state) {
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ if (isset($form_state['values'][$key])) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+ }
+}
+
+/**
+ * Output function for the 'page_site_name' content type.
+ *
+ * Outputs the site_name for the current page.
+ */
+function ctools_page_site_name_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+
+ $block->content = filter_xss_admin(variable_get('site_name', 'Drupal'));
+
+ // Optionally link the site name to the homepage.
+ if (!empty($conf['linked'])) {
+ $block->content = l($block->content, '<front>');
+ }
+
+ return $block;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_slogan.inc b/sites/all/modules/ctools/plugins/content_types/page/page_slogan.inc
new file mode 100644
index 000000000..d5cba38b6
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_slogan.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page_slogan' content type which allows the
+ * slogan of the site to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Site slogan'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t("Add the site's slogan as content."),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+);
+
+/**
+ * Output function for the 'page_slogan' content type.
+ *
+ * Outputs the slogan for the current page.
+ */
+function ctools_page_slogan_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $block->content = filter_xss_admin(variable_get('site_slogan', ''));
+
+ return $block;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_tabs.inc b/sites/all/modules/ctools/plugins/content_types/page/page_tabs.inc
new file mode 100644
index 000000000..e88acd54f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_tabs.inc
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page' content type which allows the standard page
+ * template variables to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Tabs'),
+ 'single' => TRUE,
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the tabs (local tasks) as content.'),
+ 'category' => t('Page elements'),
+ 'render last' => TRUE,
+ 'defaults' => array(
+ 'type' => 'both',
+ 'id' => 'tabs',
+ ),
+);
+
+/**
+ * Output function for the 'page_tabs' content type.
+ *
+ * Outputs the tabs (local tasks) of the current page.
+ */
+function ctools_page_tabs_content_type_render($subtype, $conf, $panel_args) {
+ $block = new stdClass();
+ $menus = menu_local_tabs();
+
+ if (empty($menus['#secondary']) && empty($menus['#primary'])) {
+ return;
+ }
+
+ switch ($conf['type']) {
+ case 'primary':
+ unset($menus['#secondary']);
+ break;
+ case 'secondary':
+ unset($menus['#primary']);
+ break;
+ }
+ if ($conf['id']) {
+ $menus['#theme_wrappers'][] = 'container';
+ $menus['#attributes']['id'] = $conf['id'];
+ }
+
+ $block->content = $menus;
+
+ return $block;
+}
+
+
+function ctools_page_tabs_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['type'] = array(
+ '#title' => t('Tabs type'),
+ '#type' => 'select',
+ '#options' => array(
+ 'both' => t('Primary and secondary'),
+ 'primary' => t('Primary'),
+ 'secondary' => t('Secondary'),
+ ),
+ '#default_value' => $conf['type'],
+ );
+
+ $form['id'] = array(
+ '#title' => t('CSS id to use'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['id'],
+ );
+ return $form;
+}
+
+/**
+ * The submit form stores the data in $conf.
+ */
+function ctools_page_tabs_content_type_edit_form_submit($form, &$form_state) {
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ if (isset($form_state['values'][$key])) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_title.inc b/sites/all/modules/ctools/plugins/content_types/page/page_title.inc
new file mode 100644
index 000000000..cc091ab23
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/page/page_title.inc
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the 'page' content type which allows the standard page
+ * template variables to be embedded into a panel.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Page title'),
+ 'icon' => 'icon_page.png',
+ 'description' => t('Add the page title as content.'),
+ 'category' => t('Page elements'),
+ 'defaults' => array(
+ 'markup' => 'h1',
+ 'class' => '',
+ 'id' => '',
+ ),
+);
+
+/**
+ * Output function for the 'page_title' content type.
+ *
+ * Outputs the page title of the current page.
+ */
+function ctools_page_title_content_type_render($subtype, $conf, $panel_args) {
+ if (!drupal_get_title()) {
+ return;
+ }
+ // TODO: This should have a setting or something for the markup.
+ if (empty($conf['markup'])) {
+ $conf['markup'] = 'h1';
+ }
+
+ if (empty($conf['class'])) {
+ $conf['class'] = '';
+ }
+
+ if (empty($conf['id'])) {
+ $conf['id'] = '';
+ }
+
+ $token = ctools_set_callback_token('title', array('ctools_page_title_content_type_token', $conf['markup'], $conf['id'], $conf['class']));
+
+ $block = new stdClass();
+ if ($token) {
+ $block->content = $token;
+ }
+
+ return $block;
+}
+
+function ctools_page_title_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['markup'] = array(
+ '#title' => t('Title tag'),
+ '#type' => 'select',
+ '#options' => array(
+ 'none' => t('- No tag -'),
+ 'h1' => t('h1'),
+ 'h2' => t('h2'),
+ 'h3' => t('h3'),
+ 'h4' => t('h4'),
+ 'h5' => t('h5'),
+ 'h6' => t('h6'),
+ 'div' => t('div'),
+ ),
+ '#default_value' => empty($conf['markup']) ? 'h1' : $conf['markup'],
+ );
+
+ $form['id'] = array(
+ '#title' => t('CSS id to use'),
+ '#type' => 'textfield',
+ '#default_value' => empty($conf['id']) ? '' : $conf['id'],
+ );
+
+ $form['class'] = array(
+ '#title' => t('CSS class to use'),
+ '#type' => 'textfield',
+ '#default_value' => empty($conf['class']) ? '' : $conf['class'],
+ );
+ return $form;
+}
+
+/**
+ * The submit form stores the data in $conf.
+ */
+function ctools_page_title_content_type_edit_form_submit($form, &$form_state) {
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ if (isset($form_state['values'][$key])) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+ }
+}
+
+/**
+ * Variable token callback to properly render the page title, with markup.
+ */
+function ctools_page_title_content_type_token(&$variables, $tag, $id, $class) {
+ if ($tag == 'none') {
+ return drupal_get_title();
+ }
+
+ $output = '<' . $tag;
+ if ($id) {
+ $output .= ' id="' . $id . '"';
+ }
+
+ if ($class) {
+ $output .= ' class="' . $class . '"';
+ }
+
+ $output .= '>' . drupal_get_title() . '</' . $tag . '>' . "\n";
+ return $output;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/search/icon_search.png b/sites/all/modules/ctools/plugins/content_types/search/icon_search.png
new file mode 100644
index 000000000..3ad1deb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/search/icon_search.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/search/search_form.inc b/sites/all/modules/ctools/plugins/content_types/search/search_form.inc
new file mode 100644
index 000000000..2b6a322b1
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/search/search_form.inc
@@ -0,0 +1,156 @@
+<?php
+
+if (module_exists('search')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Advanced search form'),
+ 'icon' => 'icon_search.png',
+ 'description' => t('A search form with advanced options.'),
+ 'required context' => new ctools_context_optional(t('Keywords'), 'string'),
+ 'category' => t('Widgets'),
+ 'defaults' => array(
+ 'type' => 'node',
+ 'form' => 'advanced',
+ 'path_type' => 'default',
+ 'path' => '',
+ 'override_prompt' => FALSE,
+ 'prompt' => '',
+ ),
+ );
+}
+
+/**
+ * Render the custom content type.
+ */
+function ctools_search_form_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ $keys = '';
+ }
+ else {
+ $keys = $context->data;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'search';
+ $block->delta = 'form';
+ $block->title = '';
+
+ switch ($conf['path_type']) {
+ default:
+ case 'default':
+ $path = 'search/' . $conf['type'];
+ break;
+ case 'same':
+ $path = $_GET['q'];
+ $path = str_replace($keys, '', $path);
+ break;
+ case 'custom':
+ $path = $conf['path'];
+ break;
+ }
+
+ $prompt = $conf['override_prompt'] ? $conf['prompt'] : NULL;
+
+ $form_state = array(
+ 'build_info' => array(
+ 'args' => array($path, $keys, $conf['type'], $prompt),
+ ),
+ );
+
+ module_load_include('inc', 'search', 'search.pages');
+
+ $block->content = drupal_build_form('search_form', $form_state);
+ if ($conf['form'] == 'simple' && isset($block->content['advanced'])) {
+ $block->content['advanced']['#access'] = FALSE;
+ }
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_search_form_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $types = array();
+ foreach (search_get_info() as $module => $info) {
+ $types[$module] = $info['title'];
+ }
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Search type'),
+ '#options' => $types,
+ '#default_value' => $conf['type'],
+ );
+
+ $form['form'] = array(
+ '#type' => 'select',
+ '#title' => t('Search form'),
+ '#options' => array(
+ 'simple' => t('Simple'),
+ 'advanced' => t('Advanced'),
+ ),
+ '#default_value' => $conf['form'],
+ '#description' => t('The advanced form may have additional options based upon the search type. For example the advanced content (node) search form will allow searching by node type and taxonomy term.'),
+ );
+
+ $form['path_type'] = array(
+ '#prefix' => '<div class="container-inline">',
+ '#type' => 'select',
+ '#title' => t('Path'),
+ '#options' => array(
+ 'default' => t('Default'),
+ 'same' => t('Same page'),
+ 'custom' => t('Custom'),
+ ),
+ '#default_value' => $conf['path_type'],
+ );
+
+ $form['path'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['path'],
+ '#dependency' => array('edit-path-type' => array('custom')),
+ '#suffix' => '</div>',
+ );
+
+ $form['override_prompt'] = array(
+ '#prefix' => '<div class="container-inline">',
+ '#type' => 'checkbox',
+ '#default_value' => $conf['override_prompt'],
+ '#title' => t('Override default prompt'),
+ );
+
+ $form['prompt'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['prompt'],
+ '#dependency' => array('edit-override-prompt' => array(1)),
+ '#suffix' => '</div>',
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for search form.
+ */
+function ctools_search_form_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_search_form_content_type_admin_title($subtype, $conf, $context) {
+ $info = search_get_info();
+ $type = isset($info[$conf['type']]['title']) ? $info[$conf['type']]['title'] : t('Missing/broken type');
+ return t('@type search form', array('@type' => $type));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/search/search_result.inc b/sites/all/modules/ctools/plugins/content_types/search/search_result.inc
new file mode 100644
index 000000000..32037bdf5
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/search/search_result.inc
@@ -0,0 +1,204 @@
+<?php
+
+if (module_exists('search')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Search results'),
+ 'icon' => 'icon_search.png',
+ 'description' => t('The results of a search using keywords.'),
+ 'required context' => new ctools_context_required(t('Keywords'), 'string'),
+ 'category' => t('Widgets'),
+ 'defaults' => array(
+ 'type' => 'node',
+ 'log' => TRUE,
+ 'override_empty' => FALSE,
+ 'empty_title' => '',
+ 'empty' => '',
+ 'empty_format' => filter_fallback_format(),
+ 'override_no_key' => FALSE,
+ 'no_key_title' => '',
+ 'no_key' => '',
+ 'no_key_format' => filter_fallback_format(),
+ ),
+ );
+}
+
+/**
+ * Render the custom content type.
+ */
+function ctools_search_result_content_type_render($subtype, $conf, $panel_args, $context) {
+ $search_info = search_get_info();
+ if (empty($search_info[$conf['type']])) {
+ return;
+ }
+ $info = $search_info[$conf['type']];
+
+ $keys = NULL;
+ if (!empty($context) && isset($context->data)) {
+ $keys = $context->data;
+ }
+
+ $conditions = NULL;
+ if (isset($info['conditions_callback']) && function_exists($info['conditions_callback'])) {
+ // Build an optional array of more search conditions.
+ $conditions = $info['conditions_callback']($keys);
+ }
+
+ // Display nothing at all if no keywords were entered.
+ if (empty($keys) && empty($conditions)) {
+ if (!empty($conf['override_no_key'])) {
+ $block->title = $conf['no_key_title'];
+ $block->content = check_markup($conf['no_key'], $conf['no_key_format'], FALSE);
+ return $block;
+ }
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'search';
+ $block->delta = 'result';
+
+ $results = '';
+
+ // Only search if there are keywords or non-empty conditions.
+ if ($keys || !empty($conditions)) {
+
+ // Collect the search results.
+ $results = search_data($keys, $info['module'], $conditions);
+
+ // A workaround for ApacheSolr.
+ // @todo see http://drupal.org/node/1343142#comment-5495248
+ // This workaround is to be removed when a better one can be written.
+ if (!empty($results['search_results']['#results'])) {
+ $results['#results'] = $results['search_results']['#results'];
+ }
+ }
+
+ if (!empty($conf['log'])) {
+ // Log the search keys:
+ watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), $_GET['q']));
+ }
+
+ if (!empty($results['#results'])) {
+ $output = "<ol class=\"search-results $conf[type]-results\">\n";
+ foreach ($results['#results'] as $result) {
+ $output .= theme('search_result', array('result' => $result, 'module' => $conf['type']));
+ }
+ $output .= "</ol>\n";
+ $output .= theme('pager', array('tags' => NULL));
+
+ $block->title = t('Search results');
+ $block->content = $output;
+ }
+ else {
+ if (empty($conf['override_empty'])) {
+ $block->title = t('Your search yielded no results');
+ $block->content = search_help('search#noresults', drupal_help_arg());
+ }
+ else {
+ $block->title = $conf['empty_title'];
+ $block->content = check_markup($conf['empty'], $conf['empty_format'], FALSE);
+ }
+ }
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_search_result_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $types = array();
+ foreach (search_get_info() as $module => $info) {
+ $types[$module] = $info['title'];
+ }
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Search type'),
+ '#options' => $types,
+ '#default_value' => $conf['type'],
+ );
+
+ $form['log'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['log'],
+ '#title' => t('Record a watchdog log entry when searches are made'),
+ );
+
+ $form['override_empty'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['override_empty'],
+ '#title' => t('Override "no result" text'),
+ );
+
+ $form['empty_title'] = array(
+ '#title' => t('Title'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['empty_title'],
+ '#dependency' => array('edit-override-empty' => array(1)),
+ );
+
+ $form['empty_field'] = array(
+ '#type' => 'text_format',
+ '#title' => t('No result text'),
+ '#default_value' => $conf['empty'],
+ '#format' => $conf['empty_format'],
+ '#dependency' => array('edit-override-empty' => array(1)),
+ );
+
+ $form['override_no_key'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['override_no_key'],
+ '#title' => t('Display text if no search keywords were submitted'),
+ );
+
+
+ $form['no_key_title'] = array(
+ '#title' => t('Title'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['no_key_title'],
+ '#dependency' => array('edit-override-no-key' => array(1)),
+ );
+
+ $form['no_key_field'] = array(
+ '#type' => 'text_format',
+ '#title' => t('No result text'),
+ '#default_value' => $conf['no_key'],
+ '#format' => $conf['no_key_format'],
+ '#dependency' => array('edit-override-no-key' => array(1)),
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler for search form.
+ */
+function ctools_search_result_content_type_edit_form_submit($form, &$form_state) {
+ // Copy the text_format values over to where we normally store them.
+ $form_state['values']['empty'] = $form_state['values']['empty_field']['value'];
+ $form_state['values']['empty_format'] = $form_state['values']['empty_field']['format'];
+ $form_state['values']['no_key'] = $form_state['values']['no_key_field']['value'];
+ $form_state['values']['no_key_format'] = $form_state['values']['no_key_field']['format'];
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_search_result_content_type_admin_title($subtype, $conf, $context) {
+ $info = search_get_info();
+ $type = isset($info[$conf['type']]['title']) ? $info[$conf['type']]['title'] : t('Missing/broken type');
+ return t('@type search result', array('@type' => $type));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/term_context/icon_term.png b/sites/all/modules/ctools/plugins/content_types/term_context/icon_term.png
new file mode 100644
index 000000000..f0417cb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/term_context/icon_term.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/term_context/term_description.inc b/sites/all/modules/ctools/plugins/content_types/term_context/term_description.inc
new file mode 100644
index 000000000..f95c4f547
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/term_context/term_description.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Term description'),
+ 'icon' => 'icon_term.png',
+ 'description' => t('Term description.'),
+ 'required context' => new ctools_context_required(t('Term'), array('term', 'taxonomy_term')),
+ 'category' => t('Taxonomy term'),
+);
+
+function ctools_term_description_content_type_render($subtype, $conf, $panel_args, $context) {
+ $term = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'node_type';
+
+ if (!empty($term)) {
+ $block->title = $term->name;
+ $block->content = check_markup($term->description, $term->format, '', TRUE);
+ $block->delta = $term->tid;
+
+ if (user_access('administer taxonomy')) {
+ $block->admin_links['update'] = array(
+ 'title' => t('Edit term'),
+ 'alt' => t("Edit this term"),
+ 'href' => "taxonomy/term/$term->tid/edit",
+ 'query' => drupal_get_destination(),
+ );
+ }
+ }
+ else {
+ $block->title = '';
+ $block->content = t('Term description goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+function ctools_term_description_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" term description', array('@s' => $context->identifier));
+}
+
+function ctools_term_description_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/term_context/term_list.inc b/sites/all/modules/ctools/plugins/content_types/term_context/term_list.inc
new file mode 100644
index 000000000..7c609fb43
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/term_context/term_list.inc
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('List of related terms'),
+ 'icon' => 'icon_term.png',
+ 'description' => t('Terms related to an existing term; may be child, siblings or top level.'),
+ 'required context' => new ctools_context_required(t('Term'), array('term', 'taxonomy_term')),
+ 'category' => t('Taxonomy term'),
+ 'defaults' => array(
+ 'title' => '',
+ 'type' => 'child',
+ 'include_current_term' => FALSE,
+ 'list_type' => 'ul',
+ 'path' => 'taxonomy/term',
+ ),
+);
+
+function ctools_term_list_content_type_render($subtype, $conf, $panel_args, $context) {
+ $term = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'term-list';
+ $path = empty($conf['path']) ? 'taxonomy/term/%tid' : $conf['path'];
+ if (strpos($path, '%tid') === FALSE) {
+ if (substr($path, -1) != '/') {
+ $path .= '/';
+ }
+ $path .= '%tid';
+ }
+
+ $options = ctools_admin_term_list_options();
+ $skip = array();
+
+ if ($term) {
+ $block->title = $options[$conf['type']];
+ $block->delta = $conf['type'];
+ switch ($conf['type']) {
+ case 'related':
+ // FIXME this no longer exists, must be done with Field API
+ // $terms = taxonomy_get_related($term->tid);
+ break;
+
+ case 'child':
+ default:
+ if (!empty($conf['include_current_term'])) {
+ $terms[] = $term;
+ }
+ $terms = taxonomy_get_children($term->tid);
+ break;
+
+ case 'top':
+ $terms = taxonomy_get_tree($term->vid, 0, 1);
+ break;
+
+ case 'parent':
+ $terms = taxonomy_get_parents($term->tid);
+ if (!empty($conf['include_current_term'])) {
+ $terms[] = $term;
+ }
+ $block->title = count($terms) == 1 ? t('Parent term') : t('Parent terms');
+ break;
+
+ case 'sibling':
+ $parent = db_query('SELECT parent FROM {taxonomy_term_hierarchy} WHERE tid = :tid', array(':tid' => $term->tid))->fetchField();
+ if ($parent) {
+ $terms = taxonomy_get_children($parent, $term->vid);
+ }
+ else {
+ $terms = taxonomy_get_tree($term->vid, 0, 1);
+ }
+
+ $skip[$term->tid] = $term->tid;
+ break;
+
+ case 'synonyms':
+ // FIXME this no longer exists, must be done with Field API
+// $terms = taxonomy_get_synonyms($term->tid);
+ break;
+ }
+
+ if (!empty($terms)) {
+ foreach ($terms as $related) {
+ if (empty($skip[$related->tid])) {
+ $items[] = l($related->name, str_replace('%tid', $related->tid, $path), array('rel' => 'tag', 'title' => strip_tags($related->description)));
+ }
+ }
+
+ if (!empty($items)) {
+ $block->content = theme('item_list', array('items' => $items, 'type' => $conf['list_type']));
+ }
+ }
+ }
+ else {
+ $block->content = t('Term description goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+function ctools_admin_term_list_options() {
+ return array(
+ 'child' => t('Child terms'),
+ 'related' => t('Related terms (does not work in D7)'),
+ 'sibling' => t('Sibling terms'),
+ 'top' => t('Top level terms'),
+ 'synonyms' => t('Term synonyms (does not work in D7)'),
+ 'parent' => t('Parent term(s)'),
+ );
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function ctools_term_list_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Which terms'),
+ '#options' => ctools_admin_term_list_options(),
+ '#default_value' => $conf['type'],
+ '#prefix' => '<div class="clearfix no-float">',
+ '#suffix' => '</div>',
+ );
+
+ $form['include_current_term'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include the current term in the list'),
+ '#default_value' => !empty($conf['include_current_term']),
+ '#prefix' => '<div class="clearfix no-float">',
+ '#suffix' => '</div>',
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name="type"], unique1' => array('!value' => 'child'),
+ ':input[name="type"], unique2' => array('!value' => 'parent'),
+ ),
+ ),
+ );
+
+ $form['list_type'] = array(
+ '#type' => 'select',
+ '#title' => t('List type'),
+ '#options' => array('ul' => t('Unordered'), 'ol' => t('Ordered')),
+ '#default_value' => $conf['list_type'],
+ );
+
+ $form['path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Path'),
+ '#default_value' => empty($conf['path']) ? 'taxonomy/term/%tid' : $conf['path'],
+ '#description' => t('The path to use for the terms. You may use %tid to place the term id as part of the path; if let off, it will be appended to the end.'),
+ );
+ return $form;
+}
+
+function ctools_term_list_content_type_admin_title($subtype, $conf, $context) {
+ $options = ctools_admin_term_list_options();
+ return t('"@s" @type', array('@s' => $context->identifier, '@type' => drupal_strtolower($options[$conf['type']])));
+}
+
+function ctools_term_list_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
diff --git a/sites/all/modules/ctools/plugins/content_types/term_context/term_name.inc b/sites/all/modules/ctools/plugins/content_types/term_context/term_name.inc
new file mode 100644
index 000000000..a9a88764f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/term_context/term_name.inc
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Term name'),
+ 'icon' => 'icon_term.png',
+ 'description' => t('The name of this taxonomy term.'),
+ 'required context' => new ctools_context_required(t('Term'), array('term', 'taxonomy_term')),
+ 'category' => t('Taxonomy term'),
+ 'defaults' => array(
+ 'link' => TRUE,
+ 'markup' => 'none',
+ 'id' => '',
+ 'class' => '',
+ ),
+);
+
+
+/**
+ * Render the custom content type.
+ */
+function ctools_term_name_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Get a shortcut to the term.
+ $term = $context->data;
+
+ // Load the vocabulary.
+ $vocab = taxonomy_vocabulary_load($term->vid);
+
+ // Generate the title
+ $content = !empty($conf['link']) ? l($term->name, 'taxonomy/term/' . $term->tid) : check_plain($term->name);
+
+ // Build any surrounding markup if so configured
+ if (isset($conf['markup']) && $conf['markup'] != 'none') {
+ $markup = '<' . $conf['markup'];
+ if (!empty($conf['id'])) {
+ $markup .= ' id="' . $conf['id'] . '"';
+ }
+ if (!empty($conf['class'])) {
+ $markup .= ' class="' . $conf['class'] . '"';
+ }
+ $markup .= '>' . $content . '</' . $conf['markup'] . '>' . "\n";
+ $content = $markup;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'term_name';
+ $block->title = t('Name');
+ $block->content = $content;
+ $block->delta = $term->tid;
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for custom type settings.
+ */
+function ctools_term_name_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['markup'] = array(
+ '#title' => t('Title tag'),
+ '#type' => 'select',
+ '#options' => array(
+ 'none' => t('- No tag -'),
+ 'h1' => t('h1'),
+ 'h2' => t('h2'),
+ 'h3' => t('h3'),
+ 'h4' => t('h4'),
+ 'h5' => t('h5'),
+ 'h6' => t('h6'),
+ 'div' => t('div'),
+ ),
+ '#default_value' => $conf['markup'],
+ );
+
+ $form['id'] = array(
+ '#title' => t('CSS id to use'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['id'],
+ );
+
+ $form['class'] = array(
+ '#title' => t('CSS class to use'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['class'],
+ );
+
+ $form['link'] = array(
+ '#title' => t('Link to term'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link'],
+ '#description' => t('Check here to make the name link to the term page.'),
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the custom type settings form.
+ */
+function ctools_term_name_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function ctools_term_name_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" name', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/token/icon_token.png b/sites/all/modules/ctools/plugins/content_types/token/icon_token.png
new file mode 100644
index 000000000..f0417cb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/token/icon_token.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/token/token.inc b/sites/all/modules/ctools/plugins/content_types/token/token.inc
new file mode 100644
index 000000000..7b6f7fcd5
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/token/token.inc
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Plugin automatically declare 'tokens' as plugins.
+ */
+
+/**
+ * Plugin decleration.
+ */
+$plugin = array(
+ 'title' => t('Tokens'),
+ 'content type' => 'ctools_token_content_type_content_type',
+ 'defaults' => array('sanitize' => TRUE),
+);
+
+/**
+ * Just one subtype.
+ *
+ * Ordinarily this function is meant to get just one subtype. However, we are
+ * using it to deal with the fact that we have changed the subtype names. This
+ * lets us translate the name properly.
+ */
+function ctools_token_content_type_content_type($subtype) {
+ $types = ctools_token_content_type_content_types();
+ if (isset($types[$subtype])) {
+ return $types[$subtype];
+ }
+}
+
+/**
+ * Return all field content types available.
+ */
+function ctools_token_content_type_content_types() {
+ // This will hold all the properties.
+ $types = &drupal_static(__FUNCTION__);
+ if (isset($types)) {
+ return $types;
+ }
+
+ $types = array();
+ $info = token_info();
+
+ foreach ($info['tokens'] as $entity_type => $tokens) {
+ $category = t('@entity (tokens)', array('@entity' => ucfirst($entity_type)));
+ $context = new ctools_context_required(t(ucfirst($entity_type)), $entity_type);
+ foreach ($tokens as $name => $token) {
+ if (!empty($token['name'])) {
+ $token += array('description' => '');
+ $types[$entity_type . ':' . $name] = array(
+ 'category' => $category,
+ 'icon' => 'icon_token.png',
+ 'title' => $token['name'],
+ 'description' => $token['description'],
+ 'required context' => $context,
+ );
+ }
+ }
+ }
+
+ return $types;
+}
+
+/**
+* Render the custom content type.
+*/
+function ctools_token_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return FALSE;
+ }
+
+ $sanitize = $conf['sanitize'];
+
+ $entity = $context->data;
+ list($entity_type, $name) = explode(':', $subtype, 2);
+
+ $info = token_info();
+ $values = token_generate($entity_type, array($name => $name), array($entity_type => $entity), array('sanitize' => $sanitize));
+ if (!isset($values[$name])) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'ctools';
+ $block->title = $info['tokens'][$entity_type][$name]['name'];
+ $block->content = $values[$name];
+ $block->delta = str_replace('_', '-', $entity_type . '-' . $name);
+
+ return $block;
+}
+
+/**
+* Returns an edit form for custom type settings.
+*/
+function ctools_token_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['sanitize'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['sanitize']),
+ '#title' => t('Sanitize'),
+ '#description' => t('When enabled that output of the token will be stripped from dangerous HTML.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate the node selection.
+ */
+function ctools_token_content_type_edit_form_submit($form, &$form_state) {
+ $form_state['conf']['sanitize'] = $form_state['values']['sanitize'];
+}
+
+
+/**
+* Returns the administrative title for a type.
+*/
+function ctools_token_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" @name', array('@s' => $context->identifier, '@name' => $subtype));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/user_context/icon_user.png b/sites/all/modules/ctools/plugins/content_types/user_context/icon_user.png
new file mode 100644
index 000000000..ab248f3f1
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/user_context/icon_user.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/user_context/profile_fields.inc b/sites/all/modules/ctools/plugins/content_types/user_context/profile_fields.inc
new file mode 100644
index 000000000..55d5593db
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/user_context/profile_fields.inc
@@ -0,0 +1,129 @@
+<?php
+
+if (module_exists('profile') && !(defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') && !is_null(profile_user_categories())) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Profile category'),
+ 'icon' => 'icon_user.png',
+ 'description' => t('Contents of a single profile category.'),
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+ 'category' => t('User'),
+ 'defaults' => array('category' => '', 'empty' => ''),
+ 'hook theme' => 'ctools_profile_fields_content_type_theme',
+ );
+}
+
+/**
+ * 'Render' callback for the 'profile fields' content type.
+ */
+function ctools_profile_fields_content_type_render($subtype, $conf, $panel_args, $context) {
+ $account = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'profile fields';
+
+ if ($account) {
+ // Get the category from the options
+ $category = str_replace("_", " ", $conf['category']);
+
+ // Set the subject to the name of the category
+ $block->subject = $category;
+
+ // Put all the fields in the category into an array
+ profile_view_profile($account);
+
+ if (is_array($account->content[$category])) {
+ foreach ($account->content[$category] as $field) {
+ if (is_array($field['#attributes'])) {
+ // @todo 'class' is *always* an array now. 04/10/2009 sun
+ $vars[$field['#attributes']['class']]['title'] = $field['#title'];
+ $vars[$field['#attributes']['class']]['value'] = $field['#value'];
+ }
+ }
+ }
+
+ if (count($vars) == 0) {
+ // Output the given empty text
+ $output = $conf['empty'];
+ }
+ else {
+ // Call the theme function with the field vars
+ $output = theme('profile_fields_pane', $category, $vars);
+ }
+
+ $block->content = $output;
+ $block->delta = $account->uid;
+ }
+ else {
+ $block->subject = $conf['category'];
+ $block->content = t('Profile content goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+/**
+ * Helper function : build the list of categories for the 'edit' form.
+ */
+function _ctools_profile_fields_options() {
+ $cat_list = array();
+
+ $categories = profile_categories();
+ foreach ($categories as $key => $value) {
+ $cat_list[str_replace(" ", "_", $value['name'])] = $value['title'];
+ }
+
+ return $cat_list;
+}
+
+/**
+ * 'Edit' callback for the 'profile fields' content type.
+ */
+function ctools_profile_fields_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['category'] = array(
+ '#type' => 'radios',
+ '#title' => t('Which category'),
+ '#options' => _ctools_profile_fields_options(),
+ '#default_value' => $conf['category'],
+ '#prefix' => '<div class="clearfix no-float">',
+ '#suffix' => '</div>',
+ );
+
+ $form['empty'] = array(
+ '#type' => 'textarea',
+ '#title' => 'Empty text',
+ '#description' => t('Text to display if category has no data. Note that title will not display unless overridden.'),
+ '#rows' => 5,
+ '#default_value' => $conf['empty'],
+ '#prefix' => '<div class="clearfix no-float">',
+ '#suffix' => '</div>',
+ );
+
+ return $form;
+}
+
+function ctools_profile_fields_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * 'Title' callback for the 'profile fields' content type.
+ */
+function ctools_profile_fields_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" profile fields', array('@s' => $conf['category']));
+}
+
+function ctools_profile_fields_content_type_theme(&$theme, $plugin) {
+ $theme['profile_fields_pane'] = array(
+ 'variables' => array('category' => NULL, 'vars' => NULL),
+ 'path' => $plugin['path'],
+ 'template' => 'profile_fields_pane',
+ );
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/user_context/profile_fields_pane.tpl.php b/sites/all/modules/ctools/plugins/content_types/user_context/profile_fields_pane.tpl.php
new file mode 100644
index 000000000..373681213
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/user_context/profile_fields_pane.tpl.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * @file
+ * Display profile fields.
+ *
+ * @todo Need definition of what variables are available here.
+ */
+?>
+<?php if (is_array($vars)): ?>
+ <?php foreach ($vars as $class => $field): ?>
+ <dl class="profile-category">
+ <dt class="profile-<?php print $class; ?>"><?php print $field['title']; ?></dt>
+ <dd class="profile-<?php print $class; ?>"><?php print $field['value']; ?></dd>
+ </dl>
+ <?php endforeach; ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ctools/plugins/content_types/user_context/user_links.inc b/sites/all/modules/ctools/plugins/content_types/user_context/user_links.inc
new file mode 100644
index 000000000..4a93621d7
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/user_context/user_links.inc
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('User links'),
+ 'icon' => 'icon_user.png',
+ 'description' => t('User links of the referenced user.'),
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+ 'category' => t('User'),
+ 'defaults' => array(
+ 'override_title' => FALSE,
+ 'override_title_text' => '',
+ 'build_mode' => '',
+ ),
+);
+
+/**
+ * Output function for the user links.
+ */
+function ctools_user_links_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (!empty($context) && empty($context->data)) {
+ return;
+ }
+
+ $account = clone $context->data;
+ $block = new stdClass();
+ $block->module = 'user';
+ $block->delta = $account->uid;
+
+ if (empty($account)) {
+ $block->delta = 'placeholder';
+ $block->subject = t('User name.');
+ $block->content = t('User links go here.');
+ }
+ else {
+ $block->subject = $account->name;
+ user_build_content($account, $conf['build_mode']);
+ if (!empty($account->content['links'])) {
+ $block->content = $account->content['links'];
+ }
+ else {
+ $block->content = '';
+ }
+ }
+ return $block;
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function ctools_user_links_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $entity = entity_get_info('user');
+ $build_mode_options = array();
+ foreach ($entity['view modes'] as $mode => $option) {
+ $build_mode_options[$mode] = $option['label'];
+ }
+
+ $form['build_mode'] = array(
+ '#title' => t('Build mode'),
+ '#type' => 'select',
+ '#description' => t('Select a build mode for this user.'),
+ '#options' => $build_mode_options,
+ '#default_value' => $conf['build_mode'],
+ );
+
+ return $form;
+}
+
+function ctools_user_links_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+function ctools_user_links_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" links', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/user_context/user_picture.inc b/sites/all/modules/ctools/plugins/content_types/user_context/user_picture.inc
new file mode 100644
index 000000000..0934c20a5
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/user_context/user_picture.inc
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('User picture'),
+ 'icon' => 'icon_user.png',
+ 'description' => t('The picture of a user.'),
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+ 'category' => t('User'),
+);
+
+function ctools_user_picture_content_type_render($subtype, $conf, $panel_args, $context) {
+ global $user;
+
+ if (empty($context->data)) {
+ return;
+ }
+
+ $account = clone $context->data;
+
+ // Check if user has permissions to access the user
+ if ($user->uid != $account->uid && (!user_access('access user profiles') && !user_access('administer users'))) {
+ return;
+ }
+
+ $block = new stdClass();
+ $block->module = 'user-profile';
+ $block->title = check_plain($account->name);
+
+ $element['user_picture'] = array(
+ '#theme' => 'user_picture',
+ '#account' => $account,
+ );
+
+
+ $block->content = $element;
+ return $block;
+}
+
+/**
+ * Display the administrative title for a panel pane in the drag & drop UI
+ */
+function ctools_user_picture_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" user picture', array('@s' => $context->identifier));
+}
+
+function ctools_user_picture_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/user_context/user_profile.inc b/sites/all/modules/ctools/plugins/content_types/user_context/user_profile.inc
new file mode 100644
index 000000000..6c41882ff
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/user_context/user_profile.inc
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('User profile'),
+ 'icon' => 'icon_user.png',
+ 'description' => t('The profile of a user.'),
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+ 'category' => t('User'),
+ 'defaults' => array(
+ 'view_mode' => 'full',
+ ),
+);
+
+/**
+ * Render the user profile content type.
+ */
+function ctools_user_profile_content_type_render($subtype, $conf, $panel_args, $context) {
+ $account = isset($context->data) ? clone($context->data) : NULL;
+ if (!$account) {
+ return NULL;
+ }
+
+ // Retrieve all profile fields and attach to $account->content.
+ if (!isset($account->content)) {
+ user_build_content($account, isset($conf['view_mode']) ? $conf['view_mode'] : 'full');
+ }
+
+ $build = $account->content;
+ // We don't need duplicate rendering info in account->content.
+ unset($account->content);
+
+ $build += array(
+ '#theme' => 'user_profile',
+ '#account' => $account,
+ // @todo support view mode
+ '#view_mode' => isset($conf['view_mode']) ? $conf['view_mode'] : 'full',
+ // @todo do we need to support this?
+ '#language' => NULL,
+ );
+
+ // Allow modules to modify the structured user.
+ $type = 'user';
+ drupal_alter(array('user_view', 'entity_view'), $build, $type);
+
+ $block = new stdClass();
+ $block->module = 'user-profile';
+ $block->title = check_plain(format_username($account));
+ $block->content = $build;
+
+ return $block;
+}
+
+/**
+ * Display the administrative title for a panel pane in the drag & drop UI.
+ */
+function ctools_user_profile_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" user profile', array('@s' => $context->identifier));
+}
+
+function ctools_user_profile_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $entity = entity_get_info('user');
+ $view_mode_options = array();
+ foreach ($entity['view modes'] as $mode => $option) {
+ $view_mode_options[$mode] = $option['label'];
+ }
+
+ $form['view_mode'] = array(
+ '#title' => t('View mode'),
+ '#type' => 'select',
+ '#description' => t('Select a build mode for this user.'),
+ '#options' => $view_mode_options,
+ '#default_value' => isset($conf['view_mode']) ? $conf['view_mode'] : 'full',
+ );
+
+ return $form;
+}
+
+function ctools_user_profile_content_type_edit_form_submit($form, &$form_state) {
+ $form_state['conf']['view_mode'] = $form_state['values']['view_mode'];
+}
+
diff --git a/sites/all/modules/ctools/plugins/content_types/user_context/user_signature.inc b/sites/all/modules/ctools/plugins/content_types/user_context/user_signature.inc
new file mode 100644
index 000000000..ca7550b25
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/user_context/user_signature.inc
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('User signature'),
+ 'icon' => 'icon_user.png',
+ 'description' => t('The signature of a user.'),
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+ 'category' => t('User'),
+);
+
+function ctools_user_signature_content_type_render($subtype, $conf, $panel_args, $context) {
+ $account = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'user-signature';
+
+ if ($account === FALSE || ($account->access == 0 && !user_access('administer users'))) {
+ return;
+ }
+
+ $element['user_signature'] = array(
+ '#theme' => 'user_signature',
+ '#signature' => check_markup($account->signature, $account->signature_format),
+ );
+
+ $block->content = $element;
+ return $block;
+}
+
+/**
+ * Display the administrative title for a panel pane in the drag & drop UI
+ */
+function ctools_user_signature_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" user signature', array('@s' => $context->identifier));
+}
+
+function ctools_user_signature_content_type_edit_form($form, &$form_state) {
+ // provide a blank form so we have a place to have context setting.
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/content_types/vocabulary_context/icon_vocabulary.png b/sites/all/modules/ctools/plugins/content_types/vocabulary_context/icon_vocabulary.png
new file mode 100644
index 000000000..f0417cb65
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/vocabulary_context/icon_vocabulary.png
Binary files differ
diff --git a/sites/all/modules/ctools/plugins/content_types/vocabulary_context/vocabulary_terms.inc b/sites/all/modules/ctools/plugins/content_types/vocabulary_context/vocabulary_terms.inc
new file mode 100644
index 000000000..5f33a0381
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/content_types/vocabulary_context/vocabulary_terms.inc
@@ -0,0 +1,100 @@
+<?php
+
+if (module_exists('taxonomy')) {
+ /**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+ $plugin = array(
+ 'single' => TRUE,
+ 'title' => t('Vocabulary terms'),
+ 'icon' => 'icon_vocabulary.png',
+ 'description' => t('All the terms in a vocabulary.'),
+ 'required context' => new ctools_context_required(t('Vocabulary'), 'entity:taxonomy_vocabulary'),
+ 'category' => t('Vocabulary'),
+ 'defaults' => array('max_depth' => 0, 'tree' => 1),
+ );
+}
+
+/**
+ * Output function for the 'vocabulary terms' content type. Outputs a
+ * list of terms for the input vocabulary.
+ */
+function ctools_vocabulary_terms_content_type_render($subtype, $conf, $panel_args, $context) {
+ $vocab = isset($context->data) ? clone($context->data) : NULL;
+ $max_depth = (!empty($conf['max_depth']) ? (int)$conf['max_depth'] : NULL);
+ if ($conf['tree'] == FALSE) {
+ $terms = taxonomy_get_tree($vocab->vid, 0, $max_depth);
+ $items = array();
+ foreach ($terms as $term) {
+ $items[] = l($term->name, 'taxonomy/term/' . $term->tid);
+ }
+ $output = theme('item_list', array('items' => $items));
+ }
+ else {
+ $output = theme('item_list', array('items' => _ctools_content_vocabulary_terms($vocab->vid, $max_depth)));
+ }
+
+ $block = new stdClass();
+ $block->module = 'node_type';
+ $block->title = check_plain($vocab->name);
+ $block->content = $output;
+ $block->delta = $vocab->vid;
+
+ return $block;
+}
+
+function _ctools_content_vocabulary_terms($vid, $max_depth, $depth = -1, $tid = 0) {
+ $depth++;
+ if ($max_depth != NULL && $depth == $max_depth) {
+ return array();
+ }
+ $return = array();
+ $query = db_select('taxonomy_term_data', 't')->fields('t', array('tid'));
+ $query->join('taxonomy_term_hierarchy', 'h', ' t.tid = h.tid');
+ $query->condition('t.vid', $vid)
+ ->condition('h.parent', $tid)
+ ->orderBy('t.weight')
+ ->orderBy('t.name');
+ $tids = $query->execute()->fetchCol();
+ $terms = taxonomy_term_load_multiple($tids);
+ foreach ($terms as $term) {
+ $return[] = array(
+ 'data' => l($term->name, 'taxonomy/term/' . $term->tid),
+ 'children' => _ctools_content_vocabulary_terms($vid, $max_depth, $depth, $term->tid),
+ );
+ }
+ return $return;
+}
+
+function ctools_vocabulary_terms_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" terms', array('@s' => $context->identifier));
+}
+
+function ctools_vocabulary_terms_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['max_depth'] = array(
+ '#type' => 'select',
+ '#title' => t('Maximum depth'),
+ '#options' => array_merge(array(t('unlimited')), range(1, 9)),
+ '#default_value' => $conf['max_depth'],
+ '#description' => t('Define the maximum depth of terms being displayed.'),
+ );
+
+ $form['tree'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display as tree'),
+ '#default_value' => $conf['tree'],
+ '#description' => t('If checked, the terms are displayed in a tree, otherwise in a flat list.'),
+ );
+
+ return $form;
+}
+
+function ctools_vocabulary_terms_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
diff --git a/sites/all/modules/ctools/plugins/contexts/entity.inc b/sites/all/modules/ctools/plugins/contexts/entity.inc
new file mode 100644
index 000000000..b214aa06b
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/entity.inc
@@ -0,0 +1,273 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a node context. A node context is a node wrapped in a
+ * context object that can be utilized by anything that accepts contexts.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Entity"),
+ 'description' => t('Entity object.'),
+ 'context' => 'ctools_context_create_entity',
+ 'edit form' => 'ctools_context_entity_settings_form',
+ 'defaults' => array('entity_id' => ''),
+ 'convert list' => 'ctools_context_entity_convert_list',
+ 'convert' => 'ctools_context_entity_convert',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the ID of an entity for this context.'),
+ ),
+ 'get child' => 'ctools_context_entity_get_child',
+ 'get children' => 'ctools_context_entity_get_children',
+);
+
+function ctools_context_entity_get_child($plugin, $parent, $child) {
+ $plugins = ctools_context_entity_get_children($plugin, $parent);
+ return $plugins[$parent . ':' . $child];
+}
+
+function ctools_context_entity_get_children($plugin, $parent) {
+ $entities = entity_get_info();
+ $plugins = array();
+ foreach ($entities as $entity_type => $entity) {
+ $child_plugin = $plugin;
+ $child_plugin['title'] = $entity['label'];
+ $child_plugin['keyword'] = $entity_type;
+ $child_plugin['context name'] = $entity_type;
+ $child_plugin['name'] = $parent . ':' . $entity_type;
+ $child_plugin['description'] = t('Creates @entity context from an entity ID.', array('@entity' => $entity_type));
+ $child_plugin_id = $parent . ':' . $entity_type;
+ drupal_alter('ctools_entity_context', $child_plugin, $entity, $child_plugin_id);
+ $plugins[$child_plugin_id] = $child_plugin;
+ }
+ drupal_alter('ctools_entity_contexts', $plugins);
+ return $plugins;
+}
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_entity($empty, $data = NULL, $conf = FALSE, $plugin) {
+ $entity_type = $plugin['keyword'];
+ $entity = entity_get_info($entity_type);
+ $context = new ctools_context(array('entity:' . $entity_type, 'entity', $entity_type));
+ $context->plugin = $plugin['name'];
+ $context->keyword = $entity_type;
+
+ if ($empty) {
+ return $context;
+ }
+
+ // Attempt to retain compatibility with broken id:
+ if (is_array($data) && !isset($data['entity_id']) && isset($data['id'])) {
+ $id = $data['id'];
+ }
+ elseif (is_array($data) && isset($data['entity_id'])) {
+ $id = $data['entity_id'];
+ }
+ elseif (is_object($data)) {
+ $ids = entity_extract_ids($entity_type, $data);
+ $id = $ids[0];
+ }
+ elseif (is_numeric($data)) {
+ $id = $data;
+ $data = entity_load($entity_type, array($id));
+ $data = !empty($data[$id]) ? $data[$id] : FALSE;
+ }
+
+ if (is_array($data)) {
+ $data = entity_load($entity_type, array($id));
+ $data = !empty($data[$id]) ? $data[$id] : FALSE;
+ }
+
+ if (!empty($data)) {
+ $context->data = $data;
+ if (!empty($entity['entity keys']['label'])) {
+ $context->title = $data->{$entity['entity keys']['label']};
+ }
+ $context->argument = $id;
+
+ if ($entity['entity keys']['bundle']) {
+ $context->restrictions['type'] = array($data->{$entity['entity keys']['bundle']});
+ }
+ return $context;
+ }
+}
+
+function ctools_context_entity_settings_form($form, &$form_state) {
+ $conf = &$form_state['conf'];
+ $plugin = &$form_state['plugin'];
+
+ $form['entity'] = array(
+ '#title' => t('Enter the title or ID of a @entity entity', array('@entity' => $plugin['keyword'])),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#autocomplete_path' => 'ctools/autocomplete/' . $plugin['keyword'],
+ '#weight' => -10,
+ );
+
+ if (!empty($conf['entity_id'])) {
+ $info = entity_load($plugin['keyword'], array($conf['entity_id']));
+ $info = $info[$conf['entity_id']];
+ if ($info) {
+ $entity = entity_get_info($plugin['keyword']);
+ $uri = entity_uri($plugin['keyword'], $info);
+ if (is_array($uri) && $entity['entity keys']['label']) {
+ $link = l(t("'%title' [%type id %id]", array('%title' => $info->{$entity['entity keys']['label']}, '%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), $uri['path'], array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ }
+ elseif (is_array($uri)) {
+ $link = l(t("[%type id %id]", array('%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), $uri['path'], array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ }
+ elseif ($entity['entity keys']['label']) {
+ $link = l(t("'%title' [%type id %id]", array('%title' => $info->{$entity['entity keys']['label']}, '%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), file_create_url($uri), array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ }
+ else {
+ $link = t("[%type id %id]", array('%type' => $plugin['keyword'], '%id' => $conf['entity_id']));
+ }
+ $form['entity']['#description'] = t('Currently set to !link', array('!link' => $link));
+ }
+ }
+
+ $form['entity_id'] = array(
+ '#type' => 'value',
+ '#value' => $conf['entity_id'],
+ );
+
+ $form['entity_type'] = array(
+ '#type' => 'value',
+ '#value' => $plugin['keyword'],
+ );
+
+ $form['set_identifier'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ '#title' => t('Reset identifier to entity label'),
+ '#description' => t('If checked, the identifier will be reset to the entity label of the selected entity.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate a node.
+ */
+function ctools_context_entity_settings_form_validate($form, &$form_state) {
+ // Validate the autocomplete
+ if (empty($form_state['values']['entity_id']) && empty($form_state['values']['entity'])) {
+ form_error($form['entity'], t('You must select an entity.'));
+ return;
+ }
+
+ if (empty($form_state['values']['entity'])) {
+ return;
+ }
+
+ $id = $form_state['values']['entity'];
+ $preg_matches = array();
+ $match = preg_match('/\[id: (\d+)\]/', $id, $preg_matches);
+ if (!$match) {
+ $match = preg_match('/^id: (\d+)/', $id, $preg_matches);
+ }
+
+ if ($match) {
+ $id = $preg_matches[1];
+ }
+ if (is_numeric($id)) {
+ $entity = entity_load($form_state['values']['entity_type'], array($id));
+ $entity = $entity[$id];
+ }
+ else {
+ $entity_info = entity_get_info($form_state['values']['entity_type']);
+ $field = $entity_info['entity keys']['label'];
+ $entity = entity_load($form_state['values']['entity_type'], FALSE, array($field => $id));
+ }
+
+ // Do not allow unpublished nodes to be selected by unprivileged users
+ // || (empty($node->status) && !(user_access('administer nodes'))) need a new sanity check at some point.
+ if (!$entity) {
+ form_error($form['entity'], t('Invalid entity selected.'));
+ }
+ else {
+ $entity_id = entity_extract_ids($form_state['values']['entity_type'], $entity);
+ form_set_value($form['entity_id'], $entity_id[0], $form_state);
+ }
+}
+
+function ctools_context_entity_settings_form_submit($form, &$form_state) {
+ if ($form_state['values']['set_identifier']) {
+ $entity_info = entity_get_info($form_state['values']['entity_type']);
+ $entity = entity_load($form_state['values']['entity_type'], array($form_state['values']['entity_id']));
+ $entity = $entity[$form_state['values']['entity_id']];
+ $form_state['values']['identifier'] = $entity->{$entity_info['entity keys']['label']};
+ }
+
+ // This will either be the value set previously or a value set by the
+ // validator.
+ $form_state['conf']['entity_id'] = $form_state['values']['entity_id'];
+}
+
+/**
+ * Provide a list of ways that this context can be converted to a string.
+ */
+function ctools_context_entity_convert_list($plugin) {
+ $list = array();
+
+ $entity = entity_get_info($plugin['context name']);
+ if (isset($entity['token type'])) {
+ $token = $entity['token type'];
+ }
+ else {
+ $token = $plugin['context name'];
+ }
+
+ // Hack: we need either token.module or a core fix for this to work right,
+ // until then, we just muscle it.
+ if ($token == 'taxonomy_term') {
+ $token = 'term';
+ }
+
+ $tokens = token_info();
+ if (isset($tokens['tokens'][$token])) {
+ foreach ($tokens['tokens'][$token] as $id => $info) {
+ if (!isset($list[$id])) {
+ $list[$id] = $info['name'];
+ }
+ }
+ }
+ return $list;
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_entity_convert($context, $type, $options = array()) {
+ $entity_type = $context->type[2];
+ $entity = entity_get_info($entity_type);
+
+ if (isset($entity['token type'])) {
+ $token = $entity['token type'];
+ }
+ else {
+ $token = $entity_type;
+ }
+
+ // Hack: we need either token.module or a core fix for this to work right,
+ // until then, we just muscle it.
+ if ($token == 'taxonomy_term') {
+ $token = 'term';
+ }
+
+ $tokens = token_info();
+
+ $values = token_generate($token, array($type => $type), array($token => $context->data), $options);
+ if (isset($values[$type])) {
+ return $values[$type];
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/node.inc b/sites/all/modules/ctools/plugins/contexts/node.inc
new file mode 100644
index 000000000..997cd1365
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/node.inc
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a node context. A node context is a node wrapped in a
+ * context object that can be utilized by anything that accepts contexts.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node"),
+ 'description' => t('A node object.'),
+ 'context' => 'ctools_context_create_node',
+ 'edit form' => 'ctools_context_node_settings_form',
+ 'defaults' => array('nid' => ''),
+ 'keyword' => 'node',
+ 'context name' => 'node',
+ 'convert list' => 'ctools_context_node_convert_list',
+ 'convert' => 'ctools_context_node_convert',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the node ID of a node for this context.'),
+ ),
+ // This context is deprecated and should not be usable in the UI.
+ 'no ui' => TRUE,
+ 'no required context ui' => TRUE,
+ 'superceded by' => 'entity:node',
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_node($empty, $data = NULL, $conf = FALSE) {
+ $context = new ctools_context('node');
+ $context->plugin = 'node';
+
+ if ($empty) {
+ return $context;
+ }
+
+ if ($conf) {
+ $nid = is_array($data) && isset($data['nid']) ? $data['nid'] : (is_object($data) ? $data->nid : 0);
+
+ if (module_exists('translation')) {
+ if ($translation = module_invoke('translation', 'node_nid', $nid, $GLOBALS['language']->language)) {
+ $nid = $translation;
+ $reload = TRUE;
+ }
+ }
+
+ if (is_array($data) || !empty($reload)) {
+ $data = node_load($nid);
+ }
+ }
+
+ if (!empty($data)) {
+ $context->data = $data;
+ $context->title = $data->title;
+ $context->argument = $data->nid;
+
+ $context->restrictions['type'] = array($data->type);
+ return $context;
+ }
+}
+
+function ctools_context_node_settings_form($form, &$form_state) {
+ $conf = &$form_state['conf'];
+
+ $form['node'] = array(
+ '#title' => t('Enter the title or NID of a node'),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#autocomplete_path' => 'ctools/autocomplete/node',
+ '#weight' => -10,
+ );
+
+ if (!empty($conf['nid'])) {
+ $info = db_query('SELECT * FROM {node} WHERE nid = :nid', array(':nid' => $conf['nid']))->fetchObject();
+ if ($info) {
+ $link = l(t("'%title' [node id %nid]", array('%title' => $info->title, '%nid' => $info->nid)), "node/$info->nid", array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ $form['node']['#description'] = t('Currently set to !link', array('!link' => $link));
+ }
+ }
+
+ $form['nid'] = array(
+ '#type' => 'value',
+ '#value' => $conf['nid'],
+ );
+
+ $form['set_identifier'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ '#title' => t('Reset identifier to node title'),
+ '#description' => t('If checked, the identifier will be reset to the node title of the selected node.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate a node.
+ */
+function ctools_context_node_settings_form_validate($form, &$form_state) {
+ // Validate the autocomplete
+ if (empty($form_state['values']['nid']) && empty($form_state['values']['node'])) {
+ form_error($form['node'], t('You must select a node.'));
+ return;
+ }
+
+ if (empty($form_state['values']['node'])) {
+ return;
+ }
+
+ $nid = $form_state['values']['node'];
+ $preg_matches = array();
+ $match = preg_match('/\[id: (\d+)\]/', $nid, $preg_matches);
+ if (!$match) {
+ $match = preg_match('/^id: (\d+)/', $nid, $preg_matches);
+ }
+
+ if ($match) {
+ $nid = $preg_matches[1];
+ }
+ if (is_numeric($nid)) {
+ $node = db_query('SELECT nid, status FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
+ }
+ else {
+ $node = db_query('SELECT nid, status FROM {node} WHERE LOWER(title) = LOWER(:title)', array(':title' => $nid))->fetchObject();
+ }
+
+ // Do not allow unpublished nodes to be selected by unprivileged users
+ if (!$node || (empty($node->status) && !(user_access('administer nodes')))) {
+ form_error($form['node'], t('Invalid node selected.'));
+ }
+ else {
+ form_set_value($form['nid'], $node->nid, $form_state);
+ }
+}
+
+function ctools_context_node_settings_form_submit($form, &$form_state) {
+ if ($form_state['values']['set_identifier']) {
+ $node = node_load($form_state['values']['nid']);
+ $form_state['values']['identifier'] = $node->title;
+ }
+
+ // This will either be the value set previously or a value set by the
+ // validator.
+ $form_state['conf']['nid'] = $form_state['values']['nid'];
+}
+
+/**
+ * Provide a list of ways that this context can be converted to a string.
+ */
+function ctools_context_node_convert_list() {
+ $tokens = token_info();
+ foreach ($tokens['tokens']['node'] as $id => $info) {
+ if (!isset($list[$id])) {
+ $list[$id] = $info['name'];
+ }
+ }
+
+ return $list;
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_node_convert($context, $type) {
+ $tokens = token_info();
+ if (isset($tokens['tokens']['node'][$type])) {
+ $values = token_generate('node', array($type => $type), array('node' => $context->data));
+ if (isset($values[$type])) {
+ return $values[$type];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/node_add_form.inc b/sites/all/modules/ctools/plugins/contexts/node_add_form.inc
new file mode 100644
index 000000000..f10944c24
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/node_add_form.inc
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a node_add_form context
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Node add form'),
+ 'description' => t('A node add form.'),
+ 'context' => 'ctools_context_create_node_add_form',
+ 'edit form' => 'ctools_context_node_add_form_settings_form',
+ 'defaults' => array('type' => ''),
+ 'keyword' => 'node_add',
+ 'context name' => 'node_add_form',
+ 'convert list' => array('type' => t('Node type')),
+ 'convert' => 'ctools_context_node_add_form_convert',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the node type this context.'),
+ ),
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_node_add_form($empty, $data = NULL, $conf = FALSE) {
+ static $creating = FALSE;
+ $context = new ctools_context(array('form', 'node_add', 'node_form', 'node', 'entity:node'));
+ $context->plugin = 'node_add_form';
+
+ if ($empty || ($creating)) {
+ return $context;
+ }
+ $creating = TRUE;
+
+ if ($conf && (isset($data['types']) || isset($data['type']))) {
+ // Holdover from typo'd config.
+ $data = isset($data['types']) ? $data['types'] : $data['type'];
+ }
+
+ if (!empty($data)) {
+ $types = node_type_get_types();
+ $type = str_replace('-', '_', $data);
+
+ // Validate the node type exists.
+ if (isset($types[$type]) && node_access('create', $type)) {
+ // Initialize settings:
+ global $user;
+ $node = (object) array(
+ 'uid' => $user->uid,
+ 'name' => (isset($user->name) ? $user->name : ''),
+ 'type' => $type,
+ 'language' => LANGUAGE_NONE,
+ );
+
+ $form_id = $type . '_node_form';
+
+ $form_state = array(
+ 'want form' => TRUE,
+ 'build_info' => array(
+ 'args' => array($node)
+ )
+ );
+
+ // Use module_load_include so that caches and stuff can know to load this.
+ form_load_include($form_state, 'inc', 'node', 'node.pages');
+
+ $form = drupal_build_form($form_id, $form_state);
+
+ // In a form, $data is the object being edited.
+ $context->data = $node;
+ $context->title = $types[$type]->name;
+ $context->argument = $type;
+
+ // These are specific pieces of data to this form.
+ // All forms should place the form here.
+ $context->form = $form;
+ $context->form_id = $form_id;
+ $context->form_title = t('Submit @name', array('@name' => $types[$type]->name));
+ $context->node_type = $type;
+ $context->restrictions['type'] = array($type);
+ $context->restrictions['form'] = array('form');
+
+ $creating = FALSE;
+ return $context;
+ }
+ }
+ $creating = FALSE;
+}
+
+function ctools_context_node_add_form_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['type'] = array(
+ '#title' => t('Node type'),
+ '#type' => 'select',
+ '#options' => node_type_get_names(),
+ '#default_value' => $conf['type'],
+ '#description' => t('Select the node type for this form.'),
+ );
+
+ return $form;
+}
+
+function ctools_context_node_add_form_settings_form_submit($form, &$form_state) {
+ $form_state['conf']['type'] = $form_state['values']['type'];
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_node_add_form_convert($context, $type) {
+ switch ($type) {
+ case 'type':
+ return $context->data->type;
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/node_edit_form.inc b/sites/all/modules/ctools/plugins/contexts/node_edit_form.inc
new file mode 100644
index 000000000..7565f5c8f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/node_edit_form.inc
@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a node_edit_form context
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Node edit form"),
+ 'description' => t('A node edit form.'),
+ 'context' => 'ctools_context_create_node_edit_form',
+ 'edit form' => 'ctools_context_node_edit_form_settings_form',
+ 'defaults' => array('nid' => ''),
+ 'keyword' => 'node_edit',
+ 'context name' => 'node_edit_form',
+ 'convert list' => 'ctools_context_node_edit_convert_list',
+ 'convert' => 'ctools_context_node_edit_convert',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the node ID of a node for this argument:'),
+ ),
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_node_edit_form($empty, $node = NULL, $conf = FALSE) {
+ static $creating = FALSE;
+ $context = new ctools_context(array('form', 'node_edit', 'node_form', 'node_edit_form', 'node', 'entity:node'));
+ $context->plugin = 'node_edit_form';
+
+ if ($empty || ($creating)) {
+ return $context;
+ }
+ $creating = TRUE;
+
+ if ($conf) {
+ // In this case, $node is actually our $conf array.
+ $nid = is_array($node) && isset($node['nid']) ? $node['nid'] : (is_object($node) ? $node->nid : 0);
+
+ if (module_exists('translation')) {
+ if ($translation = module_invoke('translation', 'node_nid', $nid, $GLOBALS['language']->language)) {
+ $nid = $translation;
+ $reload = TRUE;
+ }
+ }
+
+ if (is_array($node) || !empty($reload)) {
+ $node = node_load($nid);
+ }
+ }
+
+ if (!empty($node)) {
+ $form_id = $node->type . '_node_form';
+
+ $form_state = array('want form' => TRUE, 'build_info' => array('args' => array($node)));
+
+ $file = drupal_get_path('module', 'node') . '/node.pages.inc';
+ require_once DRUPAL_ROOT . '/' . $file;
+ // This piece of information can let other modules know that more files
+ // need to be included if this form is loaded from cache:
+ $form_state['build_info']['files'] = array($file);
+
+ $form = drupal_build_form($form_id, $form_state);
+
+ // Fill in the 'node' portion of the context
+ $context->data = $node;
+ $context->title = isset($node->title) ? $node->title : '';
+ $context->argument = isset($node->nid) ? $node->nid : $node->type;
+
+ $context->form = $form;
+ $context->form_state = &$form_state;
+ $context->form_id = $form_id;
+ $context->form_title = isset($node->title) ? $node->title : '';
+ $context->node_type = $node->type;
+ $context->restrictions['type'] = array($node->type);
+ $context->restrictions['form'] = array('form');
+
+ $creating = FALSE;
+ return $context;
+ }
+ $creating = FALSE;
+}
+
+function ctools_context_node_edit_form_settings_form($form, &$form_state) {
+ $conf = &$form_state['conf'];
+
+ $form['node'] = array(
+ '#title' => t('Enter the title or NID of a node'),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#autocomplete_path' => 'ctools/autocomplete/node',
+ '#weight' => -10,
+ );
+
+ if (!empty($conf['nid'])) {
+ $info = db_query('SELECT * FROM {node} WHERE nid = :nid', array(':nid' => $conf['nid']))->fetchObject();
+ if ($info) {
+ $link = l(t("'%title' [node id %nid]", array('%title' => $info->title, '%nid' => $info->nid)), "node/$info->nid", array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ $form['node']['#description'] = t('Currently set to !link', array('!link' => $link));
+ }
+ }
+
+ $form['nid'] = array(
+ '#type' => 'value',
+ '#value' => $conf['nid'],
+ );
+
+ $form['set_identifier'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ '#title' => t('Reset identifier to node title'),
+ '#description' => t('If checked, the identifier will be reset to the node title of the selected node.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate a node.
+ */
+function ctools_context_node_edit_form_settings_form_validate($form, &$form_state) {
+ // Validate the autocomplete
+ if (empty($form_state['values']['nid']) && empty($form_state['values']['node'])) {
+ form_error($form['node'], t('You must select a node.'));
+ return;
+ }
+
+ if (empty($form_state['values']['node'])) {
+ return;
+ }
+
+ $nid = $form_state['values']['node'];
+ $preg_matches = array();
+ $match = preg_match('/\[id: (\d+)\]/', $nid, $preg_matches);
+ if (!$match) {
+ $match = preg_match('/^id: (\d+)/', $nid, $preg_matches);
+ }
+
+ if ($match) {
+ $nid = $preg_matches[1];
+ }
+ if (is_numeric($nid)) {
+ $node = db_query('SELECT nid, status FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
+ }
+ else {
+ $node = db_query('SELECT nid, status FROM {node} WHERE LOWER(title) = LOWER(:title)', array(':title' => $nid))->fetchObject();
+ }
+
+ // Do not allow unpublished nodes to be selected by unprivileged users
+ if (!$node || (empty($node->status) && !(user_access('administer nodes')))) {
+ form_error($form['node'], t('Invalid node selected.'));
+ }
+ else {
+ form_set_value($form['nid'], $node->nid, $form_state);
+ }
+}
+
+function ctools_context_node_edit_form_settings_form_submit($form, &$form_state) {
+ if ($form_state['values']['set_identifier']) {
+ $node = node_load($form_state['values']['nid']);
+ $form_state['values']['identifier'] = $node->title;
+ }
+
+ // This will either be the value set previously or a value set by the
+ // validator.
+ $form_state['conf']['nid'] = $form_state['values']['nid'];
+}
+
+/**
+ * Provide a list of ways that this context can be converted to a string.
+ */
+function ctools_context_node_edit_convert_list() {
+ // Pass through to the "node" context convert list.
+ $plugin = ctools_get_context('node');
+ return ctools_context_node_convert_list();
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_node_edit_convert($context, $type) {
+ // Pass through to the "node" context convert list.
+ $plugin = ctools_get_context('node');
+ return ctools_context_node_convert($context, $type);
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/string.inc b/sites/all/modules/ctools/plugins/contexts/string.inc
new file mode 100644
index 000000000..e731ab74b
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/string.inc
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a string context
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('String'),
+ 'description' => t('A context that is just a string.'),
+ 'context' => 'ctools_context_create_string',
+ 'edit form' => 'ctools_context_string_settings_form',
+ 'defaults' => '',
+ 'keyword' => 'string',
+ 'no ui' => FALSE,
+ 'context name' => 'string',
+ 'convert list' => array(
+ 'raw' => t('Raw string'),
+ 'html_safe' => t('HTML-safe string'),
+ 'uppercase_words_html_safe' => t('Uppercase words HTML-safe string'),
+ ),
+ 'convert' => 'ctools_context_string_convert',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the string for this context.'),
+ ),
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_string($empty, $data = NULL, $conf = FALSE) {
+ // The input is expected to be an object as created by ctools_break_phrase
+ // which contains a group of string.
+
+ $context = new ctools_context('string');
+ $context->plugin = 'string';
+
+ if ($empty) {
+ return $context;
+ }
+
+ if ($data !== FALSE ) {
+ // Support the array storage from the settings form but also handle direct input from arguments.
+ $context->data = is_array($data) ? $data['string'] : $data;
+ $context->title = ($conf) ? check_plain($data['identifier']) : check_plain($data);
+ return $context;
+ }
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_string_convert($context, $type) {
+ switch ($type) {
+ case 'raw':
+ return $context->data;
+ case 'html_safe':
+ return check_plain($context->data);
+ case 'uppercase_words_html_safe':
+ return ucwords(str_replace('-', ' ', check_plain($context->data)));
+ }
+}
+
+/**
+ * String settings form.
+ */
+function ctools_context_string_settings_form($form, &$form_state) {
+ $conf = &$form_state['conf'];
+
+ $form['string'] = array(
+ '#title' => t('Enter the string'),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#weight' => -10,
+ '#default_value' => $conf['string'],
+ );
+
+ return $form;
+}
+
+function ctools_context_string_settings_form_submit($form, &$form_state) {
+ $form_state['conf']['string'] = $form_state['values']['string'];
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/term.inc b/sites/all/modules/ctools/plugins/contexts/term.inc
new file mode 100644
index 000000000..1d6d48e6f
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/term.inc
@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a term context
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy term"),
+ 'description' => t('A single taxonomy term object.'),
+ 'context' => 'ctools_context_create_term',
+ 'edit form' => 'ctools_context_term_settings_form',
+ 'defaults' => array(
+ 'vid' => '',
+ 'tid' => '',
+ ),
+ 'keyword' => 'term',
+ 'context name' => 'term',
+ 'convert list' => array(
+ 'tid' => t('Term ID'),
+ 'name' => t('Term name'),
+ 'name_dashed' => t('Term name, lowercased and spaces converted to dashes'),
+ 'description' => t('Term Description'),
+ 'vid' => t('Vocabulary ID'),
+ ),
+ 'convert' => 'ctools_context_term_convert',
+ // This context is deprecated and should not be usable in the UI.
+ 'no ui' => TRUE,
+ 'no required context ui' => TRUE,
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_term($empty, $data = NULL, $conf = FALSE) {
+ $context = new ctools_context('term');
+ $context->plugin = 'term';
+
+ if ($empty) {
+ return $context;
+ }
+
+ if ($conf && isset($data['tid'])) {
+ $data = taxonomy_term_load($data['tid']);
+ }
+
+ if (!empty($data)) {
+ $context->data = $data;
+ $context->title = $data->name;
+ $context->argument = $data->tid;
+ $context->description = $data->description;
+ return $context;
+ }
+}
+
+function ctools_context_term_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['vid'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'select',
+ '#options' => array(),
+ '#description' => t('Select the vocabulary for this form.'),
+ '#id' => 'ctools-select-vid',
+ '#default_value' => $conf['vid'],
+ );
+
+ $description = '';
+ if (!empty($conf['tid'])) {
+ $info = db_query('SELECT * FROM {taxonomy_term_data} WHERE tid = :tid', array(':tid' => $conf['tid']))->fetchObject();
+ if ($info) {
+ $description = ' ' . t('Currently set to @term. Enter another term if you wish to change the term.', array('@term' => $info->name));
+ }
+ }
+
+ ctools_include('dependent');
+ $options = array();
+
+ $form['taxonomy']['#tree'] = TRUE;
+
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ $form['taxonomy'][$vocabulary->vid] = array(
+ '#type' => 'textfield',
+ '#description' => t('Select a term from @vocabulary.', array('@vocabulary' => $vocabulary->name)) . $description,
+ '#autocomplete_path' => 'taxonomy/autocomplete/' . $vocabulary->vid,
+ '#dependency' => array('ctools-select-vid' => array($vocabulary->vid)),
+ );
+
+ }
+
+ $form['vid']['#options'] = $options;
+
+ $form['tid'] = array(
+ '#type' => 'value',
+ '#value' => $conf['tid'],
+ );
+
+ $form['set_identifier'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ '#title' => t('Reset identifier to term title'),
+ '#description' => t('If checked, the identifier will be reset to the term name of the selected term.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate a term.
+ */
+function ctools_context_term_settings_form_validate($form, &$form_state) {
+ // Validate the autocomplete
+ $vid = $form_state['values']['vid'];
+ if (empty($form_state['values']['tid']) && empty($form_state['values']['taxonomy'][$vid])) {
+ form_error($form['taxonomy'][$vid], t('You must select a term.'));
+ return;
+ }
+
+ if (empty($form_state['values']['taxonomy'][$vid])) {
+ return;
+ }
+
+ $term = db_query('SELECT tid FROM {taxonomy_term_data} WHERE LOWER(name) = LOWER(:name) AND vid = :vid', array(':name' => $form_state['values']['taxonomy'][$vid], ':vid' => $vid))->fetchObject();
+
+ if (!$term) {
+ form_error($form['taxonomy'][$vid], t('Invalid term selected.'));
+ }
+ else {
+ form_set_value($form['tid'], $term->tid, $form_state);
+ }
+}
+
+function ctools_context_term_settings_form_submit($form, &$form_state) {
+ if ($form_state['values']['set_identifier']) {
+ $term = db_query('SELECT tid, name FROM {taxonomy_term_data} WHERE LOWER(tid) = :tid', array(':tid' => $form_state['values']['tid']))->fetchObject();
+ $form_state['values']['identifier'] = $term->name;
+ }
+
+ $form_state['conf']['tid'] = $form_state['values']['tid'];
+ $form_state['conf']['vid'] = $form_state['values']['vid'];
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_term_convert($context, $type) {
+ switch ($type) {
+ case 'tid':
+ return $context->data->tid;
+ case 'name':
+ return $context->data->name;
+ case 'name_dashed':
+ return drupal_strtolower(str_replace(' ', '-', $context->data->name));
+ case 'vid':
+ return $context->data->vid;
+ case 'description':
+ return $context->data->description;
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/terms.inc b/sites/all/modules/ctools/plugins/contexts/terms.inc
new file mode 100644
index 000000000..6c3ab3611
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/terms.inc
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a terms context
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy terms"),
+ 'description' => t('Multiple taxonomy terms, as a group.'),
+ 'context' => 'ctools_context_create_terms',
+ 'keyword' => 'terms',
+ // This context is deprecated and should not be usable in the UI.
+ 'no ui' => TRUE,
+ 'context name' => 'terms',
+ 'convert list' => array(
+ 'tid' => t('Term ID of first term'),
+ 'tids' => t('Term ID of all term, separated by + or ,'),
+ 'name' => t('Term name of first term'),
+ 'name_dashed' => t('Term name of first term, lowercased and spaces converted to dashes'),
+ 'names' => t('Term name of all terms, separated by + or ,'),
+ 'names_dashed' => t('Term name of all terms, separated by + or , and lowercased and spaces converted to dashes'),
+ 'vid' => t('Vocabulary ID of first term'),
+ ),
+ 'convert' => 'ctools_context_terms_convert',
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_terms($empty, $data = NULL, $conf = FALSE) {
+ // The input is expected to be an object as created by ctools_break_phrase
+ // which contains a group of terms.
+
+ $context = new ctools_context(array('terms', 'entity:taxonomy_term'));
+ $context->plugin = 'terms';
+
+ if ($empty) {
+ return $context;
+ }
+
+ if (!empty($data) && is_object($data)) {
+ $context->operator = $data->operator;
+ $context->tids = $data->value;
+ if (!isset($data->term)) {
+ // load the first term:
+ reset($context->tids);
+ $data->term = taxonomy_term_load(current($context->tids));
+ }
+ $context->data = $data->term;
+ $context->title = $data->term->name;
+ $context->argument = implode($context->operator == 'or' ? '+' : ',', array_unique($context->tids));
+ return $context;
+ }
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_terms_convert($context, $type) {
+ switch ($type) {
+ case 'tid':
+ return $context->data->tid;
+ case 'tids':
+ return $context->argument;
+ case 'name':
+ return $context->data->name;
+ case 'name_dashed':
+ return drupal_strtolower(str_replace(' ', '-', $context->data->name));
+ case 'names':
+ case 'names_dashed':
+ // We only run this query if this item was requested:
+ if (!isset($context->names)) {
+ if (empty($context->tids)) {
+ $context->names = '';
+ }
+ else {
+ $result = db_query('SELECT tid, name FROM {taxonomy_term_data} WHERE tid IN (:tids)', array(':tids' => $context->tids));
+ foreach ($result as $term) {
+ $names[$term->tid] = $term->name;
+ if ($type == 'names_dashed') {
+ $names[$term->tid] = drupal_strtolower(str_replace(' ', '-', $names[$term->tid]));
+ }
+ }
+ $context->names = implode($context->operator == 'or' ? ' + ' : ', ', $names);
+ }
+ }
+ return $context->names;
+ case 'vid':
+ return $context->data->vid;
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/token.inc b/sites/all/modules/ctools/plugins/contexts/token.inc
new file mode 100644
index 000000000..044557691
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/token.inc
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Provide a global context to allow for token support.
+ */
+
+$plugin = array(
+ 'title' => t('Token'),
+ 'description' => t('A context that contains token replacements from token.module.'),
+ 'context' => 'ctools_context_create_token', // func to create context
+ 'context name' => 'token',
+ 'keyword' => 'token',
+ 'convert list' => 'ctools_context_token_convert_list',
+ 'convert' => 'ctools_context_token_convert',
+);
+
+/**
+ * Create a context from manual configuration.
+ */
+function ctools_context_create_token($empty, $data = NULL, $conf = FALSE) {
+ $context = new ctools_context('token');
+ $context->plugin = 'token';
+
+ return $context;
+}
+
+/**
+ * Implementation of hook_ctools_context_convert_list().
+ */
+function ctools_context_token_convert_list() {
+ $tokens = token_info();
+ foreach ($tokens['types'] as $type => $type_info) {
+ if (empty($type_info['needs-data'])) {
+ $real_type = isset($type_info['type']) ? $type_info['type'] : $type;
+ foreach ($tokens['tokens'][$real_type] as $id => $info) {
+ $key = "$type:$id";
+ if (!isset($list[$key])) {
+ $list[$key] = $type_info['name'] . ': ' . $info['name'];
+ }
+ }
+ }
+ }
+
+ return $list;
+}
+
+/**
+ * Implementation of hook_ctools_context_converter_alter().
+ */
+function ctools_context_token_convert($context, $token) {
+ $tokens = token_info();
+ list($type, $token) = explode(':', $token, 2);
+ $parts = explode(':', $token, 2);
+ $real_type = isset($tokens['types'][$type]['type']) ? $tokens['types'][$type]['type'] : $type;
+ if (isset($tokens['tokens'][$real_type][$parts[0]])) {
+ $values = token_generate($type, array($token => $token));
+ if (isset($values[$token])) {
+ return $values[$token];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/user.inc b/sites/all/modules/ctools/plugins/contexts/user.inc
new file mode 100644
index 000000000..18781c760
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/user.inc
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a user context
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("User"),
+ 'description' => t('A single user object.'),
+ 'context' => 'ctools_context_create_user',
+ 'edit form' => 'ctools_context_user_settings_form',
+ 'defaults' => array('type' => 'select', 'uid' => ''),
+ 'keyword' => 'user',
+ 'context name' => 'user',
+ 'convert list' => 'ctools_context_user_convert_list',
+ 'convert' => 'ctools_context_user_convert',
+ 'convert default' => 'name',
+
+ // This context is deprecated and should not be usable in the UI.
+ 'no ui' => TRUE,
+ 'no required context ui' => TRUE,
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_user($empty, $data = NULL, $conf = FALSE) {
+ $context = new ctools_context(array('entity:user', 'entity', 'user'));
+ $context->plugin = 'user';
+
+ if ($empty) {
+ return $context;
+ }
+
+ if ($conf) {
+ if ($data['type'] == 'current') {
+ global $user;
+ $data = user_load($user->uid);
+ if (user_is_logged_in()) {
+ $data->logged_in_user = TRUE;
+ }
+ }
+ else {
+ $data = user_load($data['uid']);
+ }
+ }
+ // Load entity if the data provided is a numeric value. This kind of data is
+ // passed by some relationships.
+ if (is_numeric($data)) {
+ $data = user_load($data);
+ }
+
+ if (!empty($data)) {
+ $context->data = $data;
+ $context->title = isset($data->name) ? $data->name : t('Anonymous');
+ $context->argument = $data->uid;
+ return $context;
+ }
+}
+
+function ctools_context_user_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ ctools_include('dependent');
+ $form['type'] = array(
+ '#title' => t('Enter the context type'),
+ '#type' => 'radios',
+ '#options' => array(
+ 'select' => t('Select a user'),
+ 'current' => t('Logged in user'),
+ ),
+ '#default_value' => $conf['type'],
+ );
+
+ $form['user'] = array(
+ '#title' => t('Enter a user name'),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#autocomplete_path' => 'user/autocomplete',
+ '#dependency' => array('radio:type' => array('select')),
+ );
+
+ if (!empty($conf['uid'])) {
+ $info = user_load($conf['uid']);
+ if ($info) {
+ $form['user']['#description'] = t('Currently set to !link', array('!link' => theme('username', array('account' => $info))));
+ }
+ }
+
+ $form['uid'] = array(
+ '#type' => 'value',
+ '#value' => $conf['uid'],
+ );
+
+ $form['set_identifier'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ '#title' => t('Reset identifier to username'),
+ '#description' => t('If checked, the identifier will be reset to the user name of the selected user.'),
+ '#dependency' => array('radio:context[context_settings][type]' => array('select')),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate a user.
+ */
+function ctools_context_user_settings_form_validate($form, &$form_state) {
+ if ($form_state['values']['type'] != 'select') {
+ return;
+ }
+
+ // Validate the autocomplete
+ if (empty($form_state['values']['uid']) && empty($form_state['values']['user'])) {
+ form_error($form['user'], t('You must select a user.'));
+ return;
+ }
+
+ if (empty($form_state['values']['user'])) {
+ return;
+ }
+
+ $account = user_load_by_name($form_state['values']['user']);
+
+ if (!$account) {
+ form_error($form['user'], t('Invalid user selected.'));
+ }
+ else {
+ form_set_value($form['uid'], $account->uid, $form_state);
+ }
+}
+
+function ctools_context_user_settings_form_submit($form, &$form_state) {
+ if ($form_state['values']['set_identifier']) {
+ $account = user_load($form_state['values']['uid']);
+ $form_state['values']['identifier'] = $account->name;
+ }
+
+ $form_state['conf']['type'] = $form_state['values']['type'];
+ $form_state['conf']['uid'] = $form_state['values']['uid'];
+}
+
+/**
+ * Provide a list of replacements.
+ */
+function ctools_context_user_convert_list() {
+ $tokens = token_info();
+ foreach ($tokens['tokens']['user'] as $id => $info) {
+ if (!isset($list[$id])) {
+ $list[$id] = $info['name'];
+ }
+ }
+
+ return $list;
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_user_convert($context, $type) {
+ $tokens = token_info();
+ if (isset($tokens['tokens']['user'][$type])) {
+ $values = token_generate('user', array($type => $type), array('user' => $context->data));
+ if (isset($values[$type])) {
+ return $values[$type];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/user_edit_form.inc b/sites/all/modules/ctools/plugins/contexts/user_edit_form.inc
new file mode 100644
index 000000000..a28c5990c
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/user_edit_form.inc
@@ -0,0 +1,192 @@
+<?php
+/**
+ * @file
+ *
+ * Plugin to provide a user_edit_form context
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('User edit form'),
+ 'description' => t('A user edit form.'),
+ 'context' => 'ctools_context_create_user_edit_form',
+ 'edit form' => 'ctools_context_user_edit_form_settings_form',
+ 'defaults' => array('uid' => ''),
+ 'keyword' => 'user_edit',
+ 'context name' => 'user_edit_form',
+ 'convert list' => 'ctools_context_user_edit_convert_list',
+ 'convert' => 'ctools_context_user_edit_convert',
+ 'placeholder form' => array(
+ '#type' => 'textfield',
+ '#description' => t('Enter the user ID of a user for this argument:'),
+ ),
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_user_edit_form($empty, $user = NULL, $conf = FALSE) {
+ // Determine the user category.
+ $category = !empty($conf['category']) ? $conf['category'] : FALSE;
+ unset($conf['category']);
+
+ // If no category was specified, use the default 'account'.
+ if (!$category) {
+ $category = 'account';
+ }
+ // Return previously created contexts, per category.
+ static $created = array();
+ if (!empty($created[$category])) {
+ return $created[$category];
+ }
+
+ $context = new ctools_context(array('form', 'user_edit', 'user_form', 'user_edit_form', 'user', 'entity:user'));
+ // Store this context for later.
+ $created[$category] = $context;
+ $context->plugin = 'user_edit_form';
+ if ($empty) {
+ return $context;
+ }
+
+ if (!empty($conf)) {
+ // In this case, $user is actually our $conf array.
+ $uid = is_array($user) && isset($user['uid']) ? $user['uid'] : (is_object($user) ? $user->uid : 0);
+
+ if (module_exists('translation')) {
+ if ($translation = module_invoke('translation', 'user_uid', $uid, $GLOBALS['language']->language)) {
+ $uid = $translation;
+ $reload = TRUE;
+ }
+ }
+
+ if (is_array($user) || !empty($reload)) {
+ $user = user_load($uid);
+ }
+ }
+
+ if (!empty($user)) {
+ $form_id = 'user_profile_form';
+
+ $form_state = array('want form' => TRUE, 'build_info' => array('args' => array($user, $category)));
+
+ $file = drupal_get_path('module', 'user') . '/user.pages.inc';
+ require_once DRUPAL_ROOT . '/' . $file;
+ // This piece of information can let other modules know that more files
+ // need to be included if this form is loaded from cache:
+ $form_state['build_info']['files'] = array($file);
+
+ $form = drupal_build_form($form_id, $form_state);
+
+ // Fill in the 'node' portion of the context
+ $context->data = $user;
+ $context->title = isset($user->name) ? $user->name : '';
+ $context->argument = $user->uid;
+
+ $context->form = $form;
+ $context->form_state = &$form_state;
+ $context->form_id = $form_id;
+ $context->form_title = isset($user->name) ? $user->name : '';
+ $context->restrictions['form'] = array('form');
+ return $context;
+ }
+}
+
+function ctools_context_user_edit_form_settings_form($form, &$form_state) {
+ $conf = &$form_state['conf'];
+
+ $form['user'] = array(
+ '#title' => t('Enter the name or UID of a node'),
+ '#type' => 'textfield',
+ '#maxlength' => 512,
+ '#autocomplete_path' => 'ctools/autocomplete/user',
+ '#weight' => -10,
+ );
+
+ if (!empty($conf['uid'])) {
+ $info = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => $conf['uid']))->fetchObject();
+ if ($info) {
+ $link = l(t("'%name' [user id %uid]", array('%name' => $info->name, '%uid' => $info->uid)), "user/$info->uid", array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE));
+ $form['user']['#description'] = t('Currently set to !link', array('!link' => $link));
+ }
+ }
+
+ $form['uid'] = array(
+ '#type' => 'value',
+ '#value' => $conf['uid'],
+ );
+
+ $form['set_identifier'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ '#title' => t('Reset identifier to user name'),
+ '#description' => t('If checked, the identifier will be reset to the user name of the selected user.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate a node.
+ */
+function ctools_context_user_edit_form_settings_form_validate($form, &$form_state) {
+ // Validate the autocomplete
+ if (empty($form_state['values']['uid']) && empty($form_state['values']['user'])) {
+ form_error($form['user'], t('You must select a user.'));
+ return;
+ }
+
+ if (empty($form_state['values']['user'])) {
+ return;
+ }
+
+ $uid = $form_state['values']['user'];
+ $preg_matches = array();
+ $match = preg_match('/\[id: (\d+)\]/', $uid, $preg_matches);
+ if (!$match) {
+ $match = preg_match('/^id: (\d+)/', $uid, $preg_matches);
+ }
+
+ if ($match) {
+ $uid = $preg_matches[1];
+ }
+ if (is_numeric($uid)) {
+ $user = db_query('SELECT uid FROM {users} WHERE uid = :uid', array(':uid' => $uid))->fetchObject();
+ }
+ else {
+ $user = db_query('SELECT uid FROM {users} WHERE LOWER(name) = LOWER(:name)', array(':name' => $uid))->fetchObject();
+ }
+
+ form_set_value($form['uid'], $user->uid, $form_state);
+}
+function ctools_context_user_edit_form_settings_form_submit($form, &$form_state) {
+ if ($form_state['values']['set_identifier']) {
+ $user = user_load($form_state['values']['uid']);
+ $form_state['values']['identifier'] = $user->name;
+ }
+
+ // This will either be the value set previously or a value set by the
+ // validator.
+ $form_state['conf']['uid'] = $form_state['values']['uid'];
+}
+
+/**
+ * Provide a list of ways that this context can be converted to a string.
+ */
+function ctools_context_user_edit_convert_list() {
+ // Pass through to the "node" context convert list.
+ $plugin = ctools_get_context('user');
+ return ctools_context_user_convert_list();
+}
+
+/**
+ * Convert a context into a string.
+ */
+function ctools_context_user_edit_convert($context, $type) {
+ // Pass through to the "node" context convert list.
+ $plugin = ctools_get_context('user');
+ return ctools_context_user_convert($context, $type);
+}
diff --git a/sites/all/modules/ctools/plugins/contexts/vocabulary.inc b/sites/all/modules/ctools/plugins/contexts/vocabulary.inc
new file mode 100644
index 000000000..9c5d4cf79
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/contexts/vocabulary.inc
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a vocabulary context
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy vocabulary"),
+ 'description' => t('A single taxonomy vocabulary object.'),
+ 'context' => 'ctools_context_create_vocabulary',
+ 'edit form' => 'ctools_context_vocabulary_settings_form',
+ 'defaults' => array('vid' => ''),
+ 'keyword' => 'vocabulary',
+ 'context name' => 'vocabulary',
+ // This context is deprecated and should not be usable in the UI.
+ 'no ui' => TRUE,
+ 'no required context ui' => TRUE,
+ 'superceded by' => 'entity:taxonomy_vocabulary',
+);
+
+/**
+ * It's important to remember that $conf is optional here, because contexts
+ * are not always created from the UI.
+ */
+function ctools_context_create_vocabulary($empty, $data = NULL, $conf = FALSE) {
+ $context = new ctools_context('vocabulary');
+ $context->plugin = 'vocabulary';
+
+ if ($empty) {
+ return $context;
+ }
+
+ if ($conf && isset($data['vid'])) {
+ $data = taxonomy_vocabulary_load($data['vid']);
+ }
+
+ if (!empty($data)) {
+ $context->data = $data;
+ $context->title = $data->name;
+ $context->argument = $data->vid;
+ return $context;
+ }
+}
+
+function ctools_context_vocabulary_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $options = array();
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ }
+
+ $form['vid'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => $conf['vid'],
+ '#description' => t('Select the vocabulary for this form.'),
+ );
+
+ return $form;
+}
+
+function ctools_context_vocabulary_settings_form_submit($form, &$form_state) {
+ $form_state['conf']['vid'] = $form_state['values']['vid'];
+}
diff --git a/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.class.php b/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.class.php
new file mode 100644
index 000000000..60c1dc288
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.class.php
@@ -0,0 +1,1537 @@
+<?php
+
+/**
+ * Base class for export UI.
+ */
+class ctools_export_ui {
+ var $plugin;
+ var $name;
+ var $options = array();
+
+ /**
+ * Fake constructor -- this is easier to deal with than the real
+ * constructor because we are retaining PHP4 compatibility, which
+ * would require all child classes to implement their own constructor.
+ */
+ function init($plugin) {
+ ctools_include('export');
+
+ $this->plugin = $plugin;
+ }
+
+ /**
+ * Get a page title for the current page from our plugin strings.
+ */
+ function get_page_title($op, $item = NULL) {
+ if (empty($this->plugin['strings']['title'][$op])) {
+ return;
+ }
+
+ // Replace %title that might be there with the exportable title.
+ $title = $this->plugin['strings']['title'][$op];
+ if (!empty($item)) {
+ $export_key = $this->plugin['export']['key'];
+ $title = (str_replace('%title', check_plain($item->{$export_key}), $title));
+ }
+
+ return $title;
+ }
+
+ /**
+ * Called by ctools_export_ui_load to load the item.
+ *
+ * This can be overridden for modules that want to be able to export
+ * items currently being edited, for example.
+ */
+ function load_item($item_name) {
+ $item = ctools_export_crud_load($this->plugin['schema'], $item_name);
+ return empty($item) ? FALSE : $item;
+ }
+
+ // ------------------------------------------------------------------------
+ // Menu item manipulation
+
+ /**
+ * hook_menu() entry point.
+ *
+ * Child implementations that need to add or modify menu items should
+ * probably call parent::hook_menu($items) and then modify as needed.
+ */
+ function hook_menu(&$items) {
+ // During upgrades, the schema can be empty as this is called prior to
+ // actual update functions being run. Ensure that we can cope with this
+ // situation.
+ if (empty($this->plugin['schema'])) {
+ return;
+ }
+
+ $prefix = ctools_export_ui_plugin_base_path($this->plugin);
+
+ if (isset($this->plugin['menu']['items']) && is_array($this->plugin['menu']['items'])) {
+ $my_items = array();
+ foreach ($this->plugin['menu']['items'] as $item) {
+ // Add menu item defaults.
+ $item += array(
+ 'file' => 'export-ui.inc',
+ 'file path' => drupal_get_path('module', 'ctools') . '/includes',
+ );
+
+ $path = !empty($item['path']) ? $prefix . '/' . $item['path'] : $prefix;
+ unset($item['path']);
+ $my_items[$path] = $item;
+ }
+ $items += $my_items;
+ }
+ }
+
+ /**
+ * Menu callback to determine if an operation is accessible.
+ *
+ * This function enforces a basic access check on the configured perm
+ * string, and then additional checks as needed.
+ *
+ * @param $op
+ * The 'op' of the menu item, which is defined by 'allowed operations'
+ * and embedded into the arguments in the menu item.
+ * @param $item
+ * If an op that works on an item, then the item object, otherwise NULL.
+ *
+ * @return
+ * TRUE if the current user has access, FALSE if not.
+ */
+ function access($op, $item) {
+ if (!user_access($this->plugin['access'])) {
+ return FALSE;
+ }
+
+ // More fine-grained access control:
+ if ($op == 'add' && !user_access($this->plugin['create access'])) {
+ return FALSE;
+ }
+
+ // More fine-grained access control:
+ if (($op == 'revert' || $op == 'delete') && !user_access($this->plugin['delete access'])) {
+ return FALSE;
+ }
+
+ // If we need to do a token test, do it here.
+ if (!empty($this->plugin['allowed operations'][$op]['token']) && (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $op))) {
+ return FALSE;
+ }
+
+ switch ($op) {
+ case 'import':
+ return user_access('use ctools import');
+ case 'revert':
+ return ($item->export_type & EXPORT_IN_DATABASE) && ($item->export_type & EXPORT_IN_CODE);
+ case 'delete':
+ return ($item->export_type & EXPORT_IN_DATABASE) && !($item->export_type & EXPORT_IN_CODE);
+ case 'disable':
+ return empty($item->disabled);
+ case 'enable':
+ return !empty($item->disabled);
+ default:
+ return TRUE;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // These methods are the API for generating the list of exportable items.
+
+ /**
+ * Master entry point for handling a list.
+ *
+ * It is unlikely that a child object will need to override this method,
+ * unless the listing mechanism is going to be highly specialized.
+ */
+ function list_page($js, $input) {
+ $this->items = ctools_export_crud_load_all($this->plugin['schema'], $js);
+
+ // Respond to a reset command by clearing session and doing a drupal goto
+ // back to the base URL.
+ if (isset($input['op']) && $input['op'] == t('Reset')) {
+ unset($_SESSION['ctools_export_ui'][$this->plugin['name']]);
+ if (!$js) {
+ drupal_goto($_GET['q']);
+ }
+ // clear everything but form id, form build id and form token:
+ $keys = array_keys($input);
+ foreach ($keys as $id) {
+ if (!in_array($id, array('form_id', 'form_build_id', 'form_token'))) {
+ unset($input[$id]);
+ }
+ }
+ $replace_form = TRUE;
+ }
+
+ // If there is no input, check to see if we have stored input in the
+ // session.
+ if (!isset($input['form_id'])) {
+ if (isset($_SESSION['ctools_export_ui'][$this->plugin['name']]) && is_array($_SESSION['ctools_export_ui'][$this->plugin['name']])) {
+ $input = $_SESSION['ctools_export_ui'][$this->plugin['name']];
+ }
+ }
+ else {
+ $_SESSION['ctools_export_ui'][$this->plugin['name']] = $input;
+ unset($_SESSION['ctools_export_ui'][$this->plugin['name']]['q']);
+ }
+
+ // This is where the form will put the output.
+ $this->rows = array();
+ $this->sorts = array();
+
+ $form_state = array(
+ 'plugin' => $this->plugin,
+ 'input' => $input,
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ 'object' => &$this,
+ );
+ if (!isset($form_state['input']['form_id'])) {
+ $form_state['input']['form_id'] = 'ctools_export_ui_list_form';
+ }
+
+ // If we do any form rendering, it's to completely replace a form on the
+ // page, so don't let it force our ids to change.
+ if ($js && isset($_POST['ajax_html_ids'])) {
+ unset($_POST['ajax_html_ids']);
+ }
+
+ $form = drupal_build_form('ctools_export_ui_list_form', $form_state);
+ $form = drupal_render($form);
+
+ $output = $this->list_header($form_state) . $this->list_render($form_state) . $this->list_footer($form_state);
+
+ if (!$js) {
+ $this->list_css();
+ return $form . $output;
+ }
+
+ $commands = array();
+ $commands[] = ajax_command_replace('#ctools-export-ui-list-items', $output);
+ if (!empty($replace_form)) {
+ $commands[] = ajax_command_replace('#ctools-export-ui-list-form', $form);
+ }
+ print ajax_render($commands);
+ ajax_footer();
+ }
+
+ /**
+ * Create the filter/sort form at the top of a list of exports.
+ *
+ * This handles the very default conditions, and most lists are expected
+ * to override this and call through to parent::list_form() in order to
+ * get the base form and then modify it as necessary to add search
+ * gadgets for custom fields.
+ */
+ function list_form(&$form, &$form_state) {
+ // This forces the form to *always* treat as submitted which is
+ // necessary to make it work.
+ $form['#token'] = FALSE;
+ if (empty($form_state['input'])) {
+ $form["#post"] = TRUE;
+ }
+
+ // Add the 'q' in if we are not using clean URLs or it can get lost when
+ // using this kind of form.
+ if (!variable_get('clean_url', FALSE)) {
+ $form['q'] = array(
+ '#type' => 'hidden',
+ '#value' => $_GET['q'],
+ );
+ }
+
+ $all = array('all' => t('- All -'));
+
+ $form['top row'] = array(
+ '#prefix' => '<div class="ctools-export-ui-row ctools-export-ui-top-row clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ $form['bottom row'] = array(
+ '#prefix' => '<div class="ctools-export-ui-row ctools-export-ui-bottom-row clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ $form['top row']['storage'] = array(
+ '#type' => 'select',
+ '#title' => t('Storage'),
+ '#options' => $all + array(
+ t('Normal') => t('Normal'),
+ t('Default') => t('Default'),
+ t('Overridden') => t('Overridden'),
+ ),
+ '#default_value' => 'all',
+ );
+
+ $form['top row']['disabled'] = array(
+ '#type' => 'select',
+ '#title' => t('Enabled'),
+ '#options' => $all + array(
+ '0' => t('Enabled'),
+ '1' => t('Disabled')
+ ),
+ '#default_value' => 'all',
+ );
+
+ $form['top row']['search'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Search'),
+ );
+
+ $form['bottom row']['order'] = array(
+ '#type' => 'select',
+ '#title' => t('Sort by'),
+ '#options' => $this->list_sort_options(),
+ '#default_value' => 'disabled',
+ );
+
+ $form['bottom row']['sort'] = array(
+ '#type' => 'select',
+ '#title' => t('Order'),
+ '#options' => array(
+ 'asc' => t('Up'),
+ 'desc' => t('Down'),
+ ),
+ '#default_value' => 'asc',
+ );
+
+ $form['bottom row']['submit'] = array(
+ '#type' => 'submit',
+ '#id' => 'ctools-export-ui-list-items-apply',
+ '#value' => t('Apply'),
+ '#attributes' => array('class' => array('use-ajax-submit ctools-auto-submit-click')),
+ );
+
+ $form['bottom row']['reset'] = array(
+ '#type' => 'submit',
+ '#id' => 'ctools-export-ui-list-items-apply',
+ '#value' => t('Reset'),
+ '#attributes' => array('class' => array('use-ajax-submit')),
+ );
+
+ $form['#prefix'] = '<div class="clearfix">';
+ $form['#suffix'] = '</div>';
+ $form['#attached']['js'] = array(ctools_attach_js('auto-submit'));
+ $form['#attached']['library'][] = array('system', 'drupal.ajax');
+ $form['#attached']['library'][] = array('system', 'jquery.form');
+ $form['#attributes'] = array('class' => array('ctools-auto-submit-full-form'));
+ }
+
+ /**
+ * Validate the filter/sort form.
+ *
+ * It is very rare that a filter form needs validation, but if it is
+ * needed, override this.
+ */
+ function list_form_validate(&$form, &$form_state) { }
+
+ /**
+ * Submit the filter/sort form.
+ *
+ * This submit handler is actually responsible for building up all of the
+ * rows that will later be rendered, since it is doing the filtering and
+ * sorting.
+ *
+ * For the most part, you should not need to override this method, as the
+ * fiddly bits call through to other functions.
+ */
+ function list_form_submit(&$form, &$form_state) {
+ // Filter and re-sort the pages.
+ $plugin = $this->plugin;
+
+ $prefix = ctools_export_ui_plugin_base_path($plugin);
+
+ foreach ($this->items as $name => $item) {
+ // Call through to the filter and see if we're going to render this
+ // row. If it returns TRUE, then this row is filtered out.
+ if ($this->list_filter($form_state, $item)) {
+ continue;
+ }
+
+ $operations = $this->build_operations($item);
+
+ $this->list_build_row($item, $form_state, $operations);
+ }
+
+ // Now actually sort
+ if ($form_state['values']['sort'] == 'desc') {
+ arsort($this->sorts);
+ }
+ else {
+ asort($this->sorts);
+ }
+
+ // Nuke the original.
+ $rows = $this->rows;
+ $this->rows = array();
+ // And restore.
+ foreach ($this->sorts as $name => $title) {
+ $this->rows[$name] = $rows[$name];
+ }
+ }
+
+ /**
+ * Determine if a row should be filtered out.
+ *
+ * This handles the default filters for the export UI list form. If you
+ * added additional filters in list_form() then this is where you should
+ * handle them.
+ *
+ * @return
+ * TRUE if the item should be excluded.
+ */
+ function list_filter($form_state, $item) {
+ $schema = ctools_export_get_schema($this->plugin['schema']);
+ if ($form_state['values']['storage'] != 'all' && $form_state['values']['storage'] != $item->{$schema['export']['export type string']}) {
+ return TRUE;
+ }
+
+ if ($form_state['values']['disabled'] != 'all' && $form_state['values']['disabled'] != !empty($item->disabled)) {
+ return TRUE;
+ }
+
+ if ($form_state['values']['search']) {
+ $search = strtolower($form_state['values']['search']);
+ foreach ($this->list_search_fields() as $field) {
+ if (strpos(strtolower($item->$field), $search) !== FALSE) {
+ $hit = TRUE;
+ break;
+ }
+ }
+ if (empty($hit)) {
+ return TRUE;
+ }
+ }
+ }
+
+ /**
+ * Provide a list of fields to test against for the default "search" widget.
+ *
+ * This widget will search against whatever fields are configured here. By
+ * default it will attempt to search against the name, title and description fields.
+ */
+ function list_search_fields() {
+ $fields = array(
+ $this->plugin['export']['key'],
+ );
+
+ if (!empty($this->plugin['export']['admin_title'])) {
+ $fields[] = $this->plugin['export']['admin_title'];
+ }
+ if (!empty($this->plugin['export']['admin_description'])) {
+ $fields[] = $this->plugin['export']['admin_description'];
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Provide a list of sort options.
+ *
+ * Override this if you wish to provide more or change how these work.
+ * The actual handling of the sorting will happen in build_row().
+ */
+ function list_sort_options() {
+ if (!empty($this->plugin['export']['admin_title'])) {
+ $options = array(
+ 'disabled' => t('Enabled, title'),
+ $this->plugin['export']['admin_title'] => t('Title'),
+ );
+ }
+ else {
+ $options = array(
+ 'disabled' => t('Enabled, name'),
+ );
+ }
+
+ $options += array(
+ 'name' => t('Name'),
+ 'storage' => t('Storage'),
+ );
+
+ return $options;
+ }
+
+ /**
+ * Add listing CSS to the page.
+ *
+ * Override this if you need custom CSS for your list.
+ */
+ function list_css() {
+ ctools_add_css('export-ui-list');
+ }
+
+ /**
+ * Builds the operation links for a specific exportable item.
+ */
+ function build_operations($item) {
+ $plugin = $this->plugin;
+ $schema = ctools_export_get_schema($plugin['schema']);
+ $operations = $plugin['allowed operations'];
+ $operations['import'] = FALSE;
+
+ if ($item->{$schema['export']['export type string']} == t('Normal')) {
+ $operations['revert'] = FALSE;
+ }
+ elseif ($item->{$schema['export']['export type string']} == t('Overridden')) {
+ $operations['delete'] = FALSE;
+ }
+ else {
+ $operations['revert'] = FALSE;
+ $operations['delete'] = FALSE;
+ }
+ if (empty($item->disabled)) {
+ $operations['enable'] = FALSE;
+ }
+ else {
+ $operations['disable'] = FALSE;
+ }
+
+ $allowed_operations = array();
+
+ foreach ($operations as $op => $info) {
+ if (!empty($info)) {
+ $allowed_operations[$op] = array(
+ 'title' => $info['title'],
+ 'href' => ctools_export_ui_plugin_menu_path($plugin, $op, $item->{$this->plugin['export']['key']}),
+ );
+ if (!empty($info['ajax'])) {
+ $allowed_operations[$op]['attributes'] = array('class' => array('use-ajax'));
+ }
+ if (!empty($info['token'])) {
+ $allowed_operations[$op]['query'] = array('token' => drupal_get_token($op));
+ }
+ }
+ }
+
+ return $allowed_operations;
+ }
+
+ /**
+ * Build a row based on the item.
+ *
+ * By default all of the rows are placed into a table by the render
+ * method, so this is building up a row suitable for theme('table').
+ * This doesn't have to be true if you override both.
+ */
+ function list_build_row($item, &$form_state, $operations) {
+ // Set up sorting
+ $name = $item->{$this->plugin['export']['key']};
+ $schema = ctools_export_get_schema($this->plugin['schema']);
+
+ // Note: $item->{$schema['export']['export type string']} should have already been set up by export.inc so
+ // we can use it safely.
+ switch ($form_state['values']['order']) {
+ case 'disabled':
+ $this->sorts[$name] = empty($item->disabled) . $name;
+ break;
+ case 'title':
+ $this->sorts[$name] = $item->{$this->plugin['export']['admin_title']};
+ break;
+ case 'name':
+ $this->sorts[$name] = $name;
+ break;
+ case 'storage':
+ $this->sorts[$name] = $item->{$schema['export']['export type string']} . $name;
+ break;
+ }
+
+ $this->rows[$name]['data'] = array();
+ $this->rows[$name]['class'] = !empty($item->disabled) ? array('ctools-export-ui-disabled') : array('ctools-export-ui-enabled');
+
+ // If we have an admin title, make it the first row.
+ if (!empty($this->plugin['export']['admin_title'])) {
+ $this->rows[$name]['data'][] = array('data' => check_plain($item->{$this->plugin['export']['admin_title']}), 'class' => array('ctools-export-ui-title'));
+ }
+ $this->rows[$name]['data'][] = array('data' => check_plain($name), 'class' => array('ctools-export-ui-name'));
+ $this->rows[$name]['data'][] = array('data' => check_plain($item->{$schema['export']['export type string']}), 'class' => array('ctools-export-ui-storage'));
+
+ $ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
+
+ $this->rows[$name]['data'][] = array('data' => $ops, 'class' => array('ctools-export-ui-operations'));
+
+ // Add an automatic mouseover of the description if one exists.
+ if (!empty($this->plugin['export']['admin_description'])) {
+ $this->rows[$name]['title'] = $item->{$this->plugin['export']['admin_description']};
+ }
+ }
+
+ /**
+ * Provide the table header.
+ *
+ * If you've added columns via list_build_row() but are still using a
+ * table, override this method to set up the table header.
+ */
+ function list_table_header() {
+ $header = array();
+ if (!empty($this->plugin['export']['admin_title'])) {
+ $header[] = array('data' => t('Title'), 'class' => array('ctools-export-ui-title'));
+ }
+
+ $header[] = array('data' => t('Name'), 'class' => array('ctools-export-ui-name'));
+ $header[] = array('data' => t('Storage'), 'class' => array('ctools-export-ui-storage'));
+ $header[] = array('data' => t('Operations'), 'class' => array('ctools-export-ui-operations'));
+
+ return $header;
+ }
+
+ /**
+ * Render all of the rows together.
+ *
+ * By default we place all of the rows in a table, and this should be the
+ * way most lists will go.
+ *
+ * Whatever you do if this method is overridden, the ID is important for AJAX
+ * so be sure it exists.
+ */
+ function list_render(&$form_state) {
+ $table = array(
+ 'header' => $this->list_table_header(),
+ 'rows' => $this->rows,
+ 'attributes' => array('id' => 'ctools-export-ui-list-items'),
+ 'empty' => $this->plugin['strings']['message']['no items'],
+ );
+ return theme('table', $table);
+ }
+
+ /**
+ * Render a header to go before the list.
+ *
+ * This will appear after the filter/sort widgets.
+ */
+ function list_header($form_state) { }
+
+ /**
+ * Render a footer to go after thie list.
+ *
+ * This is a good place to add additional links.
+ */
+ function list_footer($form_state) { }
+
+ // ------------------------------------------------------------------------
+ // These methods are the API for adding/editing exportable items
+
+ /**
+ * Perform a drupal_goto() to the location provided by the plugin for the
+ * operation.
+ *
+ * @param $op
+ * The operation to use. A string must exist in $this->plugin['redirect']
+ * for this operation.
+ * @param $item
+ * The item in use; this may be necessary as item IDs are often embedded in
+ * redirects.
+ */
+ function redirect($op, $item = NULL) {
+ if (isset($this->plugin['redirect'][$op])) {
+ $destination = (array) $this->plugin['redirect'][$op];
+ if ($item) {
+ $export_key = $this->plugin['export']['key'];
+ $destination[0] = str_replace('%ctools_export_ui', $item->{$export_key}, $destination[0]);
+ }
+ call_user_func_array('drupal_goto', $destination);
+ }
+ else {
+ // If the operation isn't set, fall back to the plugin's base path.
+ drupal_goto(ctools_export_ui_plugin_base_path($this->plugin));
+ }
+ }
+
+ function add_page($js, $input, $step = NULL) {
+ drupal_set_title($this->get_page_title('add'), PASS_THROUGH);
+
+ // If a step not set, they are trying to create a new item. If a step
+ // is set, they're in the process of creating an item.
+ if (!empty($this->plugin['use wizard']) && !empty($step)) {
+ $item = $this->edit_cache_get(NULL, 'add');
+ }
+ if (empty($item)) {
+ $item = ctools_export_crud_new($this->plugin['schema']);
+ }
+
+ $form_state = array(
+ 'plugin' => $this->plugin,
+ 'object' => &$this,
+ 'ajax' => $js,
+ 'item' => $item,
+ 'op' => 'add',
+ 'form type' => 'add',
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ 'step' => $step,
+ // Store these in case additional args are needed.
+ 'function args' => func_get_args(),
+ );
+
+ $output = $this->edit_execute_form($form_state);
+ if (!empty($form_state['executed']) && empty($form_state['rebuild'])) {
+ $this->redirect($form_state['op'], $form_state['item']);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Main entry point to edit an item.
+ */
+ function edit_page($js, $input, $item, $step = NULL) {
+ drupal_set_title($this->get_page_title('edit', $item), PASS_THROUGH);
+
+ // Check to see if there is a cached item to get if we're using the wizard.
+ if (!empty($this->plugin['use wizard'])) {
+ $cached = $this->edit_cache_get($item, 'edit');
+ if (!empty($cached)) {
+ $item = $cached;
+ }
+ }
+
+ $form_state = array(
+ 'plugin' => $this->plugin,
+ 'object' => &$this,
+ 'ajax' => $js,
+ 'item' => $item,
+ 'op' => 'edit',
+ 'form type' => 'edit',
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ 'step' => $step,
+ // Store these in case additional args are needed.
+ 'function args' => func_get_args(),
+ );
+
+ $output = $this->edit_execute_form($form_state);
+ if (!empty($form_state['executed']) && empty($form_state['rebuild'])) {
+ $this->redirect($form_state['op'], $form_state['item']);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Main entry point to clone an item.
+ */
+ function clone_page($js, $input, $original, $step = NULL) {
+ drupal_set_title($this->get_page_title('clone', $original), PASS_THROUGH);
+
+ // If a step not set, they are trying to create a new clone. If a step
+ // is set, they're in the process of cloning an item.
+ if (!empty($this->plugin['use wizard']) && !empty($step)) {
+ $item = $this->edit_cache_get(NULL, 'clone');
+ }
+ if (empty($item)) {
+ // To make a clone of an item, we first export it and then re-import it.
+ // Export the handler, which is a fantastic way to clean database IDs out of it.
+ $export = ctools_export_crud_export($this->plugin['schema'], $original);
+ $item = ctools_export_crud_import($this->plugin['schema'], $export);
+
+ if (!empty($input[$this->plugin['export']['key']])) {
+ $item->{$this->plugin['export']['key']} = $input[$this->plugin['export']['key']];
+ }
+ else {
+ $item->{$this->plugin['export']['key']} = 'clone_of_' . $item->{$this->plugin['export']['key']};
+ }
+ }
+
+ // Tabs and breadcrumb disappearing, this helps alleviate through cheating.
+ // ...not sure this this is the best way.
+ $trail = menu_set_active_item(ctools_export_ui_plugin_base_path($this->plugin));
+
+ $name = $original->{$this->plugin['export']['key']};
+
+ $form_state = array(
+ 'plugin' => $this->plugin,
+ 'object' => &$this,
+ 'ajax' => $js,
+ 'item' => $item,
+ 'op' => 'add',
+ 'form type' => 'clone',
+ 'original name' => $name,
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ 'step' => $step,
+ // Store these in case additional args are needed.
+ 'function args' => func_get_args(),
+ );
+
+ $output = $this->edit_execute_form($form_state);
+ if (!empty($form_state['executed']) && empty($form_state['rebuild'])) {
+ $this->redirect($form_state['op'], $form_state['item']);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Execute the form.
+ *
+ * Add and Edit both funnel into this, but they have a few different
+ * settings.
+ */
+ function edit_execute_form(&$form_state) {
+ if (!empty($this->plugin['use wizard'])) {
+ return $this->edit_execute_form_wizard($form_state);
+ }
+ else {
+ return $this->edit_execute_form_standard($form_state);
+ }
+ }
+
+ /**
+ * Execute the standard form for editing.
+ *
+ * By default, export UI will provide a single form for editing an object.
+ */
+ function edit_execute_form_standard(&$form_state) {
+ $output = drupal_build_form('ctools_export_ui_edit_item_form', $form_state);
+ if (!empty($form_state['executed']) && empty($form_state['rebuild'])) {
+ $this->edit_save_form($form_state);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Get the form info for the wizard.
+ *
+ * This gets the form info out of the plugin, then adds defaults based on
+ * how we want edit forms to work.
+ *
+ * Overriding this can allow child UIs to tweak this info for specialized
+ * wizards.
+ *
+ * @param array $form_state
+ * The already created form state.
+ */
+ function get_wizard_info(&$form_state) {
+ if (!isset($form_state['step'])) {
+ $form_state['step'] = NULL;
+ }
+
+ $export_key = $this->plugin['export']['key'];
+
+ // When cloning, the name of the item being cloned is referenced in the
+ // path, not the name of this item.
+ if ($form_state['form type'] == 'clone') {
+ $name = $form_state['original name'];
+ }
+ else {
+ $name = $form_state['item']->{$export_key};
+ }
+
+ $form_info = !empty($this->plugin['form info']) ? $this->plugin['form info'] : array();
+ $form_info += array(
+ 'id' => 'ctools_export_ui_edit',
+ 'path' => ctools_export_ui_plugin_menu_path($this->plugin, $form_state['form type'], $name) . '/%step',
+ 'show trail' => TRUE,
+ 'free trail' => TRUE,
+ 'show back' => $form_state['form type'] == 'add',
+ 'show return' => FALSE,
+ 'show cancel' => TRUE,
+ 'finish callback' => 'ctools_export_ui_wizard_finish',
+ 'next callback' => 'ctools_export_ui_wizard_next',
+ 'back callback' => 'ctools_export_ui_wizard_back',
+ 'cancel callback' => 'ctools_export_ui_wizard_cancel',
+ 'order' => array(),
+ 'import order' => array(
+ 'import' => t('Import code'),
+ 'settings' => t('Settings'),
+ ),
+ );
+
+ // Set the order of forms based on the op if we have a specific one.
+ if (isset($form_info[$form_state['form type'] . ' order'])) {
+ $form_info['order'] = $form_info[$form_state['form type'] . ' order'];
+ }
+
+ // We have generic fallback forms we can use if they are not specified,
+ // and they automatically delegate back to the UI object. Use these if
+ // not specified.
+ foreach ($form_info['order'] as $key => $title) {
+ if (empty($form_info['forms'][$key])) {
+ $form_info['forms'][$key] = array(
+ 'form id' => 'ctools_export_ui_edit_item_wizard_form',
+ );
+ }
+ }
+
+ // 'free trail' means the wizard can freely go back and form from item
+ // via the trail and not with next/back buttons.
+ if ($form_state['form type'] == 'add' || ($form_state['form type'] == 'import' && empty($form_state['item']->{$export_key}))) {
+ $form_info['free trail'] = FALSE;
+ }
+
+ return $form_info;
+ }
+
+ /**
+ * Execute the wizard for editing.
+ *
+ * For complex objects, sometimes a wizard is needed. This is normally
+ * activated by setting 'use wizard' => TRUE in the plugin definition
+ * and then creating a 'form info' array to feed the wizard the data
+ * it needs.
+ *
+ * When creating this wizard, the plugin is responsible for defining all forms
+ * that will be utilized with the wizard.
+ *
+ * Using 'add order' or 'edit order' can be used to ensure that add/edit order
+ * is different.
+ */
+ function edit_execute_form_wizard(&$form_state) {
+ $form_info = $this->get_wizard_info($form_state);
+
+ // If there aren't any forms set, fail.
+ if (empty($form_info['order'])) {
+ return MENU_NOT_FOUND;
+ }
+
+ // Figure out if this is a new instance of the wizard
+ if (empty($form_state['step'])) {
+ $order = array_keys($form_info['order']);
+ $form_state['step'] = reset($order);
+ }
+
+ if (empty($form_info['order'][$form_state['step']]) && empty($form_info['forms'][$form_state['step']])) {
+ return MENU_NOT_FOUND;
+ }
+
+ ctools_include('wizard');
+ $output = ctools_wizard_multistep_form($form_info, $form_state['step'], $form_state);
+ if (!empty($form_state['complete'])) {
+ $this->edit_save_form($form_state);
+ }
+ else if ($output && !empty($form_state['item']->export_ui_item_is_cached)) {
+ // @todo this should be in the plugin strings
+ drupal_set_message(t('You have unsaved changes. These changes will not be made permanent until you click <em>Save</em>.'), 'warning');
+ }
+
+ // Unset the executed flag if any non-wizard button was pressed. Those
+ // buttons require special handling by whatever client is operating them.
+ if (!empty($form_state['executed']) && empty($form_state['clicked_button']['#wizard type'])) {
+ unset($form_state['executed']);
+ }
+ return $output;
+ }
+
+ /**
+ * Wizard 'back' callback when using a wizard to edit an item.
+ *
+ * The wizard callback delegates this back to the object.
+ */
+ function edit_wizard_back(&$form_state) {
+ // This only exists so child implementations can use it.
+ }
+
+ /**
+ * Wizard 'next' callback when using a wizard to edit an item.
+ *
+ * The wizard callback delegates this back to the object.
+ */
+ function edit_wizard_next(&$form_state) {
+ $this->edit_cache_set($form_state['item'], $form_state['form type']);
+ }
+
+ /**
+ * Wizard 'cancel' callback when using a wizard to edit an item.
+ *
+ * The wizard callback delegates this back to the object.
+ */
+ function edit_wizard_cancel(&$form_state) {
+ $this->edit_cache_clear($form_state['item'], $form_state['form type']);
+ }
+
+ /**
+ * Wizard 'cancel' callback when using a wizard to edit an item.
+ *
+ * The wizard callback delegates this back to the object.
+ */
+ function edit_wizard_finish(&$form_state) {
+ $form_state['complete'] = TRUE;
+
+ // If we are importing, and overwrite was selected, delete the original so
+ // that this one writes properly.
+ if ($form_state['form type'] == 'import' && !empty($form_state['item']->export_ui_allow_overwrite)) {
+ ctools_export_crud_delete($this->plugin['schema'], $form_state['item']);
+ }
+
+ $this->edit_cache_clear($form_state['item'], $form_state['form type']);
+ }
+
+ /**
+ * Retrieve the item currently being edited from the object cache.
+ */
+ function edit_cache_get($item, $op = 'edit') {
+ ctools_include('object-cache');
+ if (is_string($item)) {
+ $name = $item;
+ }
+ else {
+ $name = $this->edit_cache_get_key($item, $op);
+ }
+
+ $cache = ctools_object_cache_get('ctui_' . $this->plugin['name'], $name);
+ if ($cache) {
+ $cache->export_ui_item_is_cached = TRUE;
+ return $cache;
+ }
+ }
+
+ /**
+ * Cache the item currently currently being edited.
+ */
+ function edit_cache_set($item, $op = 'edit') {
+ ctools_include('object-cache');
+ $name = $this->edit_cache_get_key($item, $op);
+ return $this->edit_cache_set_key($item, $name);
+ }
+
+ function edit_cache_set_key($item, $name) {
+ return ctools_object_cache_set('ctui_' . $this->plugin['name'], $name, $item);
+ }
+
+ /**
+ * Clear the object cache for the currently edited item.
+ */
+ function edit_cache_clear($item, $op = 'edit') {
+ ctools_include('object-cache');
+ $name = $this->edit_cache_get_key($item, $op);
+ return ctools_object_cache_clear('ctui_' . $this->plugin['name'], $name);
+ }
+
+ /**
+ * Figure out what the cache key is for this object.
+ */
+ function edit_cache_get_key($item, $op) {
+ $export_key = $this->plugin['export']['key'];
+ return $op == 'edit' ? $item->{$this->plugin['export']['key']} : "::$op";
+ }
+
+ /**
+ * Called to save the final product from the edit form.
+ */
+ function edit_save_form($form_state) {
+ $item = &$form_state['item'];
+ $export_key = $this->plugin['export']['key'];
+
+ $result = ctools_export_crud_save($this->plugin['schema'], $item);
+
+ if ($result) {
+ $message = str_replace('%title', check_plain($item->{$export_key}), $this->plugin['strings']['confirmation'][$form_state['op']]['success']);
+ drupal_set_message($message);
+ }
+ else {
+ $message = str_replace('%title', check_plain($item->{$export_key}), $this->plugin['strings']['confirmation'][$form_state['op']]['fail']);
+ drupal_set_message($message, 'error');
+ }
+ }
+
+ /**
+ * Provide the actual editing form.
+ */
+ function edit_form(&$form, &$form_state) {
+ $export_key = $this->plugin['export']['key'];
+ $item = $form_state['item'];
+ $schema = ctools_export_get_schema($this->plugin['schema']);
+
+ if (!empty($this->plugin['export']['admin_title'])) {
+ $form['info'][$this->plugin['export']['admin_title']] = array(
+ '#type' => 'textfield',
+ '#title' => t('Administrative title'),
+ '#description' => t('This will appear in the administrative interface to easily identify it.'),
+ '#default_value' => $item->{$this->plugin['export']['admin_title']},
+ );
+ }
+
+ $form['info'][$export_key] = array(
+ '#title' => t($schema['export']['key name']),
+ '#type' => 'textfield',
+ '#default_value' => $item->{$export_key},
+ '#description' => t('The unique ID for this @export.', array('@export' => $this->plugin['title singular'])),
+ '#required' => TRUE,
+ '#maxlength' => 255,
+ );
+
+ if (!empty($this->plugin['export']['admin_title'])) {
+ $form['info'][$export_key]['#type'] = 'machine_name';
+ $form['info'][$export_key]['#machine_name'] = array(
+ 'exists' => 'ctools_export_ui_edit_name_exists',
+ 'source' => array('info', $this->plugin['export']['admin_title']),
+ );
+ }
+
+ if ($form_state['op'] === 'edit') {
+ $form['info'][$export_key]['#disabled'] = TRUE;
+ $form['info'][$export_key]['#value'] = $item->{$export_key};
+ }
+
+ if (!empty($this->plugin['export']['admin_description'])) {
+ $form['info'][$this->plugin['export']['admin_description']] = array(
+ '#type' => 'textarea',
+ '#title' => t('Administrative description'),
+ '#default_value' => $item->{$this->plugin['export']['admin_description']},
+ );
+ }
+
+ // Add plugin's form definitions.
+ if (!empty($this->plugin['form']['settings'])) {
+ // Pass $form by reference.
+ $this->plugin['form']['settings']($form, $form_state);
+ }
+
+ // Add the buttons if the wizard is not in use.
+ if (empty($form_state['form_info'])) {
+ // Make sure that whatever happens, the buttons go to the bottom.
+ $form['buttons']['#weight'] = 100;
+
+ // Add buttons.
+ $form['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+
+ $form['buttons']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => $item->export_type & EXPORT_IN_CODE ? t('Revert') : t('Delete'),
+ '#access' => $form_state['op'] === 'edit' && $item->export_type & EXPORT_IN_DATABASE,
+ '#submit' => array('ctools_export_ui_edit_item_form_delete'),
+ );
+ }
+ }
+
+ /**
+ * Validate callback for the edit form.
+ */
+ function edit_form_validate(&$form, &$form_state) {
+ if (!empty($this->plugin['form']['validate'])) {
+ // Pass $form by reference.
+ $this->plugin['form']['validate']($form, $form_state);
+ }
+ }
+
+ /**
+ * Perform a final validation check before allowing the form to be
+ * finished.
+ */
+ function edit_finish_validate(&$form, &$form_state) {
+ if ($form_state['op'] != 'edit') {
+ // Validate the export key. Fake an element for form_error().
+ $export_key = $this->plugin['export']['key'];
+ $element = array(
+ '#value' => $form_state['item']->{$export_key},
+ '#parents' => array($export_key),
+ );
+ $form_state['plugin'] = $this->plugin;
+ ctools_export_ui_edit_name_validate($element, $form_state);
+ }
+ }
+
+ /**
+ * Handle the submission of the edit form.
+ *
+ * At this point, submission is successful. Our only responsibility is
+ * to copy anything out of values onto the item that we are able to edit.
+ *
+ * If the keys all match up to the schema, this method will not need to be
+ * overridden.
+ */
+ function edit_form_submit(&$form, &$form_state) {
+ if (!empty($this->plugin['form']['submit'])) {
+ // Pass $form by reference.
+ $this->plugin['form']['submit']($form, $form_state);
+ }
+
+ // Transfer data from the form to the $item based upon schema values.
+ $schema = ctools_export_get_schema($this->plugin['schema']);
+ foreach (array_keys($schema['fields']) as $key) {
+ if(isset($form_state['values'][$key])) {
+ $form_state['item']->{$key} = $form_state['values'][$key];
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // These methods are the API for 'other' stuff with exportables such as
+ // enable, disable, import, export, delete
+
+ /**
+ * Callback to enable a page.
+ */
+ function enable_page($js, $input, $item) {
+ return $this->set_item_state(FALSE, $js, $input, $item);
+ }
+
+ /**
+ * Callback to disable a page.
+ */
+ function disable_page($js, $input, $item) {
+ return $this->set_item_state(TRUE, $js, $input, $item);
+ }
+
+ /**
+ * Set an item's state to enabled or disabled and output to user.
+ *
+ * If javascript is in use, this will rebuild the list and send that back
+ * as though the filter form had been executed.
+ */
+ function set_item_state($state, $js, $input, $item) {
+ ctools_export_crud_set_status($this->plugin['schema'], $item, $state);
+
+ if (!$js) {
+ drupal_goto(ctools_export_ui_plugin_base_path($this->plugin));
+ }
+ else {
+ return $this->list_page($js, $input);
+ }
+ }
+
+ /**
+ * Page callback to delete an exportable item.
+ */
+ function delete_page($js, $input, $item) {
+ $form_state = array(
+ 'plugin' => $this->plugin,
+ 'object' => &$this,
+ 'ajax' => $js,
+ 'item' => $item,
+ 'op' => $item->export_type & EXPORT_IN_CODE ? 'revert' : 'delete',
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ );
+
+ $output = drupal_build_form('ctools_export_ui_delete_confirm_form', $form_state);
+ if (!empty($form_state['executed'])) {
+ $this->delete_form_submit($form_state);
+ $this->redirect($form_state['op'], $item);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Deletes exportable items from the database.
+ */
+ function delete_form_submit(&$form_state) {
+ $item = $form_state['item'];
+
+ ctools_export_crud_delete($this->plugin['schema'], $item);
+ $export_key = $this->plugin['export']['key'];
+ $message = str_replace('%title', check_plain($item->{$export_key}), $this->plugin['strings']['confirmation'][$form_state['op']]['success']);
+ drupal_set_message($message);
+ }
+
+ /**
+ * Page callback to display export information for an exportable item.
+ */
+ function export_page($js, $input, $item) {
+ drupal_set_title($this->get_page_title('export', $item), PASS_THROUGH);
+ return drupal_get_form('ctools_export_form', ctools_export_crud_export($this->plugin['schema'], $item), t('Export'));
+ }
+
+ /**
+ * Page callback to import information for an exportable item.
+ */
+ function import_page($js, $input, $step = NULL) {
+ drupal_set_title($this->get_page_title('import'), PASS_THROUGH);
+ // Import is basically a multi step wizard form, so let's go ahead and
+ // use CTools' wizard.inc for it.
+
+ // If a step not set, they are trying to create a new item. If a step
+ // is set, they're in the process of creating an item.
+ if (!empty($step)) {
+ $item = $this->edit_cache_get(NULL, 'import');
+ }
+ if (empty($item)) {
+ $item = ctools_export_crud_new($this->plugin['schema']);
+ }
+
+ $form_state = array(
+ 'plugin' => $this->plugin,
+ 'object' => &$this,
+ 'ajax' => $js,
+ 'item' => $item,
+ 'op' => 'add',
+ 'form type' => 'import',
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ 'step' => $step,
+ // Store these in case additional args are needed.
+ 'function args' => func_get_args(),
+ );
+
+ // import always uses the wizard.
+ $output = $this->edit_execute_form_wizard($form_state);
+ if (!empty($form_state['executed'])) {
+ $this->redirect($form_state['op'], $form_state['item']);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Import form. Provides simple helptext instructions and textarea for
+ * pasting a export definition.
+ */
+ function edit_form_import(&$form, &$form_state) {
+ $form['help'] = array(
+ '#type' => 'item',
+ '#value' => $this->plugin['strings']['help']['import'],
+ );
+
+ $form['import'] = array(
+ '#title' => t('@plugin code', array('@plugin' => $this->plugin['title singular proper'])),
+ '#type' => 'textarea',
+ '#rows' => 10,
+ '#required' => TRUE,
+ '#default_value' => !empty($form_state['item']->export_ui_code) ? $form_state['item']->export_ui_code : '',
+ );
+
+ $form['overwrite'] = array(
+ '#title' => t('Allow import to overwrite an existing record.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($form_state['item']->export_ui_allow_overwrite) ? $form_state['item']->export_ui_allow_overwrite : FALSE,
+ );
+ }
+
+ /**
+ * Import form validate handler.
+ *
+ * Evaluates code and make sure it creates an object before we continue.
+ */
+ function edit_form_import_validate($form, &$form_state) {
+ $item = ctools_export_crud_import($this->plugin['schema'], $form_state['values']['import']);
+ if (is_string($item)) {
+ form_error($form['import'], t('Unable to get an import from the code. Errors reported: @errors', array('@errors' => $item)));
+ return;
+ }
+
+ $form_state['item'] = $item;
+ $form_state['item']->export_ui_allow_overwrite = $form_state['values']['overwrite'];
+ $form_state['item']->export_ui_code = $form_state['values']['import'];
+ }
+
+ /**
+ * Submit callback for import form.
+ *
+ * Stores the item in the session.
+ */
+ function edit_form_import_submit($form, &$form_state) {
+ // The validate function already imported and stored the item. This
+ // function exists simply to prevent it from going to the default
+ // edit_form_submit() method.
+ }
+}
+
+// -----------------------------------------------------------------------
+// Forms to be used with this class.
+//
+// Since Drupal's forms are completely procedural, these forms will
+// mostly just be pass-throughs back to the object.
+
+/**
+ * Add all appropriate includes to forms so that caching the form
+ * still loads the files that we need.
+ */
+function _ctools_export_ui_add_form_files($form, &$form_state) {
+ ctools_form_include($form_state, 'export');
+ ctools_form_include($form_state, 'export-ui');
+
+ // Also make sure the plugin .inc file is loaded.
+ ctools_form_include_file($form_state, $form_state['object']->plugin['path'] . '/' . $form_state['object']->plugin['file']);
+}
+
+/**
+ * Form callback to handle the filter/sort form when listing items.
+ *
+ * This simply loads the object defined in the plugin and hands it off.
+ */
+function ctools_export_ui_list_form($form, &$form_state) {
+ $form_state['object']->list_form($form, $form_state);
+ return $form;
+}
+
+/**
+ * Validate handler for ctools_export_ui_list_form.
+ */
+function ctools_export_ui_list_form_validate(&$form, &$form_state) {
+ $form_state['object']->list_form_validate($form, $form_state);
+}
+
+/**
+ * Submit handler for ctools_export_ui_list_form.
+ */
+function ctools_export_ui_list_form_submit(&$form, &$form_state) {
+ $form_state['object']->list_form_submit($form, $form_state);
+}
+
+/**
+ * Form callback to edit an exportable item.
+ *
+ * This simply loads the object defined in the plugin and hands it off.
+ */
+function ctools_export_ui_edit_item_form($form, &$form_state) {
+ // When called using #ajax via ajax_form_callback(), 'export' may
+ // not be included so include it here.
+ _ctools_export_ui_add_form_files($form, $form_state);
+
+ $form_state['object']->edit_form($form, $form_state);
+ return $form;
+}
+
+/**
+ * Validate handler for ctools_export_ui_edit_item_form.
+ */
+function ctools_export_ui_edit_item_form_validate(&$form, &$form_state) {
+ $form_state['object']->edit_form_validate($form, $form_state);
+}
+
+/**
+ * Submit handler for ctools_export_ui_edit_item_form.
+ */
+function ctools_export_ui_edit_item_form_submit(&$form, &$form_state) {
+ $form_state['object']->edit_form_submit($form, $form_state);
+}
+
+/**
+ * Submit handler to delete for ctools_export_ui_edit_item_form
+ *
+ * @todo Put this on a callback in the object.
+ */
+function ctools_export_ui_edit_item_form_delete(&$form, &$form_state) {
+ _ctools_export_ui_add_form_files($form, $form_state);
+
+ $export_key = $form_state['plugin']['export']['key'];
+ $path = $form_state['item']->export_type & EXPORT_IN_CODE ? 'revert' : 'delete';
+
+ drupal_goto(ctools_export_ui_plugin_menu_path($form_state['plugin'], $path, $form_state['item']->{$export_key}), array('cancel_path' => $_GET['q']));
+}
+
+/**
+ * Validate that an export item name is acceptable and unique during add.
+ */
+function ctools_export_ui_edit_name_validate($element, &$form_state) {
+ $plugin = $form_state['plugin'];
+ // Check for string identifier sanity
+ if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) {
+ form_error($element, t('The export id can only consist of lowercase letters, underscores, and numbers.'));
+ return;
+ }
+
+ // Check for name collision
+ if (empty($form_state['item']->export_ui_allow_overwrite) && $exists = ctools_export_crud_load($plugin['schema'], $element['#value'])) {
+ form_error($element, t('A @plugin with this name already exists. Please choose another name or delete the existing item before creating a new one.', array('@plugin' => $plugin['title singular'])));
+ }
+}
+
+/**
+ * Test for #machine_name type to see if an export exists.
+ */
+function ctools_export_ui_edit_name_exists($name, $element, &$form_state) {
+ $plugin = $form_state['plugin'];
+
+ return (empty($form_state['item']->export_ui_allow_overwrite) && ctools_export_crud_load($plugin['schema'], $name));
+}
+
+/**
+ * Delete/Revert confirm form.
+ *
+ * @todo -- call back into the object instead.
+ */
+function ctools_export_ui_delete_confirm_form($form, &$form_state) {
+ _ctools_export_ui_add_form_files($form, $form_state);
+
+ $plugin = $form_state['plugin'];
+ $item = $form_state['item'];
+
+ $form = array();
+
+ $export_key = $plugin['export']['key'];
+ $question = str_replace('%title', check_plain($item->{$export_key}), $plugin['strings']['confirmation'][$form_state['op']]['question']);
+ $path = (!empty($_REQUEST['cancel_path']) && !url_is_external($_REQUEST['cancel_path'])) ? $_REQUEST['cancel_path'] : ctools_export_ui_plugin_base_path($plugin);
+
+ $form = confirm_form($form,
+ $question,
+ $path,
+ $plugin['strings']['confirmation'][$form_state['op']]['information'],
+ $plugin['allowed operations'][$form_state['op']]['title'], t('Cancel')
+ );
+ return $form;
+}
+
+// --------------------------------------------------------------------------
+// Forms and callbacks for using the edit system with the wizard.
+
+/**
+ * Form callback to edit an exportable item using the wizard
+ *
+ * This simply loads the object defined in the plugin and hands it off.
+ */
+function ctools_export_ui_edit_item_wizard_form($form, &$form_state) {
+ _ctools_export_ui_add_form_files($form, $form_state);
+
+ $method = 'edit_form_' . $form_state['step'];
+ if (!method_exists($form_state['object'], $method)) {
+ $method = 'edit_form';
+ }
+
+ $form_state['object']->$method($form, $form_state);
+ return $form;
+}
+
+/**
+ * Validate handler for ctools_export_ui_edit_item_wizard_form.
+ */
+function ctools_export_ui_edit_item_wizard_form_validate(&$form, &$form_state) {
+ $method = 'edit_form_' . $form_state['step'] . '_validate';
+ if (!method_exists($form_state['object'], $method)) {
+ $method = 'edit_form_validate';
+ }
+
+ $form_state['object']->$method($form, $form_state);
+
+ // Additionally, if there were no errors from that, and we're finishing,
+ // perform a final validate to make sure everything is ok.
+ if (isset($form_state['clicked_button']['#wizard type']) && $form_state['clicked_button']['#wizard type'] == 'finish' && !form_get_errors()) {
+ $form_state['object']->edit_finish_validate($form, $form_state);
+ }
+}
+
+/**
+ * Submit handler for ctools_export_ui_edit_item_wizard_form.
+ */
+function ctools_export_ui_edit_item_wizard_form_submit(&$form, &$form_state) {
+ $method = 'edit_form_' . $form_state['step'] . '_submit';
+ if (!method_exists($form_state['object'], $method)) {
+ $method = 'edit_form_submit';
+ }
+
+ $form_state['object']->$method($form, $form_state);
+}
+
+/**
+ * Wizard 'back' callback when using a wizard to edit an item.
+ */
+function ctools_export_ui_wizard_back(&$form_state) {
+ $form_state['object']->edit_wizard_back($form_state);
+}
+
+/**
+ * Wizard 'next' callback when using a wizard to edit an item.
+ */
+function ctools_export_ui_wizard_next(&$form_state) {
+ $form_state['object']->edit_wizard_next($form_state);
+}
+
+/**
+ * Wizard 'cancel' callback when using a wizard to edit an item.
+ */
+function ctools_export_ui_wizard_cancel(&$form_state) {
+ $form_state['object']->edit_wizard_cancel($form_state);
+}
+
+/**
+ * Wizard 'finish' callback when using a wizard to edit an item.
+ */
+function ctools_export_ui_wizard_finish(&$form_state) {
+ $form_state['object']->edit_wizard_finish($form_state);
+}
diff --git a/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.inc b/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.inc
new file mode 100644
index 000000000..4e0a84978
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.inc
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * The default plugin exists only to provide the base class. Other plugins
+ * which do not provide a class will get this class instead. Any classes
+ * provided should use this class as their parent:
+ *
+ * @code
+ * 'handler' => array(
+ * 'class' => 'ctools_export_ui_mine',
+ * 'parent' => 'ctools_export_ui',
+ * ),
+ * @endcode
+ *
+ * Using the above notation will ensure that this plugin's is loaded before
+ * the child plugin's class and avoid whitescreens.
+ */
+$plugin = array(
+ // As this is the base class plugin, it shouldn't declare any menu items.
+ 'has menu' => FALSE,
+ 'handler' => array(
+ 'class' => 'ctools_export_ui',
+ ),
+);
diff --git a/sites/all/modules/ctools/plugins/relationships/book_parent.inc b/sites/all/modules/ctools/plugins/relationships/book_parent.inc
new file mode 100644
index 000000000..30ada4aa0
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/book_parent.inc
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for book parent.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Book parent'),
+ 'keyword' => 'book_parent',
+ 'description' => t('Adds a book parent from a node context.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'context' => 'ctools_book_parent_context',
+ 'edit form' => 'ctools_book_parent_settings_form',
+ 'defaults' => array('type' => 'top'),
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_book_parent_context($context, $conf) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data)) {
+ return ctools_context_create_empty('node');
+ }
+
+ if (isset($context->data->book)) {
+ if ($conf['type'] == 'top') {
+ $nid = $context->data->book['bid'];
+ }
+ else {
+ // Just load the parent book.
+ $item = book_link_load($context->data->book['plid']);
+ $nid = $item['nid'];
+ }
+
+ if (!empty($nid)) {
+ // Load the node.
+ $node = node_load($nid);
+ // Generate the context.
+ if (node_access('view', $node)) {
+ return ctools_context_create('node', $node);
+ }
+ }
+ }
+ else {
+ return ctools_context_create_empty('node');
+ }
+}
+
+/**
+ * Settings form for the relationship.
+ */
+function ctools_book_parent_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Relationship type'),
+ '#options' => array('parent' => t('Immediate parent'), 'top' => t('Top level book')),
+ '#default_value' => $conf['type'],
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/relationships/entity_from_field.inc b/sites/all/modules/ctools/plugins/relationships/entity_from_field.inc
new file mode 100644
index 000000000..fdffc41cf
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/entity_from_field.inc
@@ -0,0 +1,229 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for an entity from a field.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Entity'),
+ 'description' => t('Creates an entity context from a foreign key on a field.'),
+ 'context' => 'ctools_entity_from_field_context',
+ 'edit form' => 'ctools_entity_from_field_edit_form',
+ 'get child' => 'ctools_entity_from_field_get_child',
+ 'get children' => 'ctools_entity_from_field_get_children',
+ 'defaults' => array('delta' => 0),
+);
+
+function ctools_entity_from_field_get_child($plugin, $parent, $child) {
+ $plugins = ctools_entity_from_field_get_children($plugin, $parent);
+ return $plugins[$parent . ':' . $child];
+}
+
+function ctools_entity_from_field_get_children($parent_plugin, $parent) {
+ $cid = $parent_plugin['name'] . ':' . $parent;
+ $cache = &drupal_static(__FUNCTION__);
+ if (!empty($cache[$cid])) {
+ return $cache[$cid];
+ }
+
+ ctools_include('fields');
+ $entities = entity_get_info();
+ $plugins = array();
+ $context_types = array();
+
+ // Get the schema information for every field.
+ $fields_info = field_info_fields();
+ foreach ($fields_info as $field_name => $field) {
+ foreach ($field['bundles'] as $from_entity => $bundles) {
+ foreach ($bundles as $bundle) {
+ // There might be fields attached to bundles that are disabled (e.g. a
+ // module that declared a node's content type, is now disabled), but the
+ // field instance is still shown.
+ if (!empty($entities[$from_entity]['bundles'][$bundle])) {
+ $foreign_keys = ctools_field_foreign_keys($field_name);
+
+ foreach ($foreign_keys as $key => $info) {
+ if (isset($info['table'])) {
+ foreach ($entities as $to_entity => $to_entity_info) {
+ $from_entity_info = $entities[$from_entity];
+ // If somehow the bundle doesn't exist on the to-entity,
+ // skip.
+ if (!isset($from_entity_info['bundles'][$bundle])) {
+ continue;
+ }
+
+ if (isset($to_entity_info['base table']) && $to_entity_info['base table'] == $info['table'] && array_keys($info['columns'], $to_entity_info['entity keys']['id'])) {
+ $name = $field_name . '-' . $from_entity . '-' . $to_entity;
+ $plugin_id = $parent . ':' . $name;
+
+ // Record the bundle for later.
+ $context_types[$plugin_id]['types'][$bundle] = $from_entity_info['bundles'][$bundle]['label'];
+
+ // We check for every bundle; this plugin may already have
+ // been created, so don't recreate it.
+ if (!isset($plugins[$plugin_id])) {
+ $plugin = $parent_plugin;
+ $replacements = array(
+ '@to_entity' => $to_entity_info['label'],
+ '@from_entity' => $from_entity_info['label'],
+ '@field_name' => $field_name,
+ '@field_label' => ctools_field_label($field_name),
+ );
+ $plugin['title'] = t('@to_entity from @from_entity (on @from_entity: @field_label [@field_name])', $replacements);
+ $plugin['keyword'] = $to_entity;
+ $plugin['context name'] = $name;
+ $plugin['name'] = $plugin_id;
+ $plugin['description'] = t('Creates a @to_entity context from @from_entity using the @field_name field on @from_entity.', $replacements);
+ $plugin['from entity'] = $from_entity;
+ $plugin['to entity'] = $to_entity;
+ $plugin['field name'] = $field_name;
+ $plugin['join key'] = $key;
+ $plugin['source key'] = current(array_keys($info['columns']));
+ $plugin['parent'] = $parent;
+
+ $plugins[$plugin_id] = $plugin;
+
+/*
+-- commented out until I figure out how to actually load the reverse properly.
+ // Build the reverse
+ $plugin = $parent_plugin;
+ $name = $field_name . '-' . $from_entity . '-' . $to_entity . '-reverse';
+ $plugin_id = $parent . ':' . $name;
+
+ $plugin['title'] = t('@from_entity from @to_entity (on @from_entity: @field_name)', $replacements);
+ $plugin['keyword'] = $to_entity;
+ $plugin['context name'] = $name;
+ $plugin['name'] = $plugin_id;
+ $plugin['description'] = t('Creates a @from_entity context from @to_entity using the @field_name field on @from_entity.', $replacements);
+
+ $plugin['from entity'] = $from_entity;
+ $plugin['to entity'] = $to_entity;
+ $plugin['field name'] = $field_name;
+ $plugin['reverse'] = TRUE;
+ $plugin['parent'] = $parent;
+
+ // Since we can't apply restrictions on the reverse relationship
+ // we just add the required context here.
+ $plugin['required context'] = new ctools_context_required($to_entity_info['label'], $to_entity);
+
+ $plugin_entities = array(
+ 'to' => array($from_entity => $from_entity_info),
+ 'from' => array($to_entity => $to_entity_info)
+ );
+ drupal_alter('ctools_entity_context', $plugin, $plugin_entities, $plugin_id);
+
+ $plugins[$plugin_id] = $plugin;
+*/
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ foreach ($context_types as $key => $context) {
+ list($parent, $plugin_name) = explode(':', $key);
+ list($field_name, $from_entity, $to_entity) = explode('-', $plugin_name);
+
+ $from_entity_info = $entities[$from_entity];
+ $to_entity_info = $entities[$to_entity];
+
+ $plugins[$key]['required context'] = new ctools_context_required($from_entity_info['label'], $from_entity, array('type' => array_keys($context['types'])));
+
+ $plugin_entities = array(
+ 'to' => array($to_entity => $to_entity_info),
+ 'from' => array($from_entity => $from_entity_info)
+ );
+ drupal_alter('ctools_entity_context', $plugins[$key], $plugin_entities, $key);
+ }
+ drupal_alter('ctools_entity_contexts', $plugins);
+
+ $cache[$cid] = $plugins;
+ return $plugins;
+}
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_entity_from_field_context($context, $conf) {
+ // Perform access check on current logged in user.
+ global $user;
+ // Clone user object so account can be passed by value to access callback.
+ $account = clone $user;
+
+ $delta = !empty($conf['delta']) ? intval($conf['delta']) : 0;
+ $plugin = $conf['name'];
+ list($plugin, $plugin_name) = explode(':', $plugin);
+ list($field_name, $from_entity, $to_entity) = explode('-', $plugin_name);
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ $entity_info = entity_get_info($from_entity);
+ if (empty($context->data) || !isset($context->data->{$entity_info['entity keys']['id']})) {
+ return ctools_context_create_empty('entity:' . $to_entity, NULL);
+ }
+
+ if (isset($context->data->{$entity_info['entity keys']['id']})) {
+ // Load the entity.
+ $id = $context->data->{$entity_info['entity keys']['id']};
+ $entity = entity_load($from_entity, array($id));
+ $entity = $entity[$id];
+ if ($items = field_get_items($from_entity, $entity, $field_name)) {
+ if (isset($items[$delta])) {
+ ctools_include('fields');
+ $to_entity_info = entity_get_info($to_entity);
+
+ $plugin_info = ctools_get_relationship($conf['name']);
+ $to_entity_id = $items[$delta][$plugin_info['source key']];
+ $loaded_to_entity = entity_load($to_entity, array($to_entity_id));
+ $loaded_to_entity = array_shift($loaded_to_entity);
+
+ // Pass current user account and entity type to access callback.
+ if (isset($to_entity_info['access callback']) && function_exists($to_entity_info['access callback']) && !call_user_func($to_entity_info['access callback'], 'view', $loaded_to_entity)) {
+ return ctools_context_create_empty('entity:' . $to_entity, NULL);
+ }
+ else {
+ // Send it to ctools.
+ return ctools_context_create('entity:' . $to_entity, $to_entity_id);
+ }
+ }
+ else {
+ // In case that delta was empty.
+ return ctools_context_create_empty('entity:' . $to_entity, NULL);
+ }
+ }
+ }
+}
+
+function ctools_entity_from_field_edit_form($form, &$form_state) {
+ $field = field_info_field($form_state['plugin']['field name']);
+ $conf = $form_state['conf'];
+
+ if ($field && $field['cardinality'] != 1) {
+ if ($field['cardinality'] == -1) {
+ $form['delta'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Delta'),
+ '#description' => t('The relationship can only create one context, but multiple items can be related. Please select which one. Since this can have unlimited items, type in the number you want. The first one will be 0.'),
+ '#default_value' => !empty($conf['delta']) ? $conf['delta'] : 0,
+ );
+ }
+ else {
+ $form['delta'] = array(
+ '#type' => 'select',
+ '#title' => t('Delta'),
+ '#description' => t('The relationship can only create one context, but multiple items can be related. Please select which one.'),
+ '#options' => range(1, $field['cardinality']),
+ '#default_value' => !empty($conf['delta']) ? $conf['delta'] : 0,
+ );
+ }
+ }
+
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/relationships/entity_from_schema.inc b/sites/all/modules/ctools/plugins/relationships/entity_from_schema.inc
new file mode 100644
index 000000000..809473eb6
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/entity_from_schema.inc
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for an entity from a field.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Entity'),
+ 'description' => t('Creates an entity context from a foreign key on a field.'),
+ 'context' => 'ctools_entity_from_schema_context',
+ 'get child' => 'ctools_entity_from_schema_get_child',
+ 'get children' => 'ctools_entity_from_schema_get_children',
+);
+
+function ctools_entity_from_schema_get_child($plugin, $parent, $child) {
+ $plugins = ctools_entity_from_schema_get_children($plugin, $parent);
+ return $plugins[$parent . ':' . $child];
+}
+
+function ctools_entity_from_schema_get_children($parent_plugin, $parent) {
+ $entities = entity_get_info();
+ $plugins = array();
+
+ foreach (module_implements('entity_info') as $module) {
+ module_load_install($module);
+ $schemas = drupal_get_schema();
+
+ foreach ($entities as $from_entity => $from_entity_info) {
+ if (empty($from_entity_info['base table'])) {
+ continue;
+ }
+
+ $table = $from_entity_info['base table'];
+ if (isset($schemas[$table]['foreign keys'])) {
+ foreach ($schemas[$table]['foreign keys'] as $relationship => $info) {
+ foreach ($entities as $to_entity => $to_entity_info) {
+ if (empty($info['table'])) {
+ continue;
+ }
+
+ if (isset($to_entity_info['base table']) && $info['table'] == $to_entity_info['base table'] && in_array($to_entity_info['entity keys']['id'], $info['columns'])) {
+ $this_col = ctools_entity_from_schema_columns_filter($info['columns'], $to_entity_info['entity keys']['id']);
+
+ // Set up our t() replacements as we reuse these.
+ $replacements = array(
+ '@relationship' => $relationship,
+ '@base_table' => $table,
+ '@to_entity' => $to_entity_info['label'],
+ '@from_entity' => $from_entity_info['label'],
+ );
+
+ $name = $this_col . '-' . $from_entity . '-' . $to_entity;
+ $plugin_id = $parent . ':' . $name;
+ $plugin = $parent_plugin;
+
+ $plugin['title'] = t('@to_entity from @from_entity (on @base_table.@relationship)', $replacements);
+ $plugin['keyword'] = $to_entity;
+ $plugin['context name'] = $name;
+ $plugin['name'] = $plugin_id;
+ $plugin['description'] = t('Builds a relationship from a @from_entity to a @to_entity using the @base_table.@relationship field.', $replacements);
+
+ $plugin['required context'] = new ctools_context_required($from_entity_info['label'], $from_entity);
+
+ $plugin_entities = array('to' => $to_entity_info, 'from' => $from_entity_info);
+ $plugin_entities = array('to' => array($to_entity => $to_entity_info), 'from' => array($from_entity => $from_entity_info));
+
+ drupal_alter('ctools_entity_context', $plugin, $plugin_entities, $plugin_id);
+ $plugins[$plugin_id] = $plugin;
+
+ // Add the relation in the reverse direction.
+ $name = $this_col . '-' . $to_entity . '-' . $from_entity;
+ $plugin_id = $parent . ':' . $name;
+ $plugin = $parent_plugin;
+
+ $plugin['title'] = t('@from_entity from @to_entity (on @base_table.@relationship)', $replacements);
+ $plugin['keyword'] = $to_entity;
+ $plugin['context name'] = $name;
+ $plugin['name'] = $plugin_id;
+ $plugin['description'] = t('Builds a relationship from a @to_entity to a @from_entity using the @base_table.@relationship field.', $replacements);
+
+ $plugin['required context'] = new ctools_context_required($to_entity_info['label'], $to_entity);
+
+ $plugin_entities = array('to' => $from_entity_info, 'from' => $to_entity_info);
+ $plugin_entities = array('to' => array($from_entity => $from_entity_info), 'from' => array($to_entity => $to_entity_info));
+ drupal_alter('ctools_entity_context', $plugin, $plugin_entities, $plugin_id);
+ $plugins[$plugin_id] = $plugin;
+
+ }
+ }
+ }
+ }
+ }
+ }
+ drupal_alter('ctools_entity_contexts', $plugins);
+ return $plugins;
+}
+
+function ctools_entity_from_schema_columns_filter($columns, $value) {
+ foreach ($columns as $this_col => $that_col) {
+ if ($value == $that_col) {
+ return $this_col;
+ }
+ }
+}
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_entity_from_schema_context($context, $conf) {
+ $plugin = $conf['name'];
+ list($plugin, $plugin_name) = explode(':', $plugin);
+ list($this_col, $from_entity, $to_entity) = explode('-', $plugin_name);
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ $entity_info = entity_get_info($from_entity);
+ if (empty($context->data) || !isset($context->data->{$entity_info['entity keys']['id']})) {
+ return ctools_context_create_empty('entity:' . $to_entity, NULL);
+ }
+
+ if (isset($context->data->{$entity_info['entity keys']['id']})) {
+ // Load the entity.
+ $id = $context->data->{$entity_info['entity keys']['id']};
+ $entity = entity_load($from_entity, array($id));
+ $entity = $entity[$id];
+ if (isset($entity->$this_col)) {
+ $to_entity_id = $entity->$this_col;
+
+ // Send it to ctools.
+ return ctools_context_create('entity:' . $to_entity, $to_entity_id);
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/relationships/node_edit_form_from_node.inc b/sites/all/modules/ctools/plugins/relationships/node_edit_form_from_node.inc
new file mode 100644
index 000000000..15c758a22
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/node_edit_form_from_node.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for term from node.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Node edit form from node'),
+ 'keyword' => 'node_form',
+ 'description' => t('Adds node edit form from a node context.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'context' => 'ctools_node_edit_form_from_node_context',
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_node_edit_form_from_node_context($context, $conf) {
+ if (empty($context->data)) {
+ return ctools_context_create_empty('node_edit_form', NULL);
+ }
+
+ if (isset($context->data->nid)) {
+ return ctools_context_create('node_edit_form', $context->data);
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/relationships/term_from_node.inc b/sites/all/modules/ctools/plugins/relationships/term_from_node.inc
new file mode 100644
index 000000000..38f6aeaa0
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/term_from_node.inc
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for term from node.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Term from node'),
+ 'keyword' => 'term',
+ 'description' => t('Adds a taxonomy term from a node context; if multiple terms are selected, this will get the "first" term only.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'context' => 'ctools_term_from_node_context',
+ 'edit form' => 'ctools_term_from_node_settings_form',
+ 'defaults' => array('vid' => ''),
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_term_from_node_context($context, $conf) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data)) {
+ return ctools_context_create_empty('entity:taxonomy_term', NULL);
+ }
+
+ if (isset($context->data->taxonomy)) {
+ foreach ($context->data->taxonomy as $term) {
+ if ($term->vid == $conf['vid']) {
+ return ctools_context_create('entity:taxonomy_term', $term);
+ }
+ }
+ }
+}
+
+/**
+ * Settings form for the relationship.
+ */
+function ctools_term_from_node_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $options = array();
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ }
+ $form['vid'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => $conf['vid'],
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/relationships/term_parent.inc b/sites/all/modules/ctools/plugins/relationships/term_parent.inc
new file mode 100644
index 000000000..f084cca83
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/term_parent.inc
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file relationships/term_parent.inc
+ * Plugin to provide an relationship handler for term parent.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Term parent'),
+ 'keyword' => 'parent_term',
+ 'description' => t('Adds a taxonomy term parent from a term context.'),
+ 'required context' => new ctools_context_required(t('Term'), 'entity:taxonomy_term'),
+ 'context' => 'ctools_term_parent_context',
+ 'edit form' => 'ctools_term_parent_settings_form',
+ 'defaults' => array('type' => 'top'),
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_term_parent_context($context, $conf) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data)) {
+ return ctools_context_create_empty('entity:taxonomy_term');
+ }
+
+ if (isset($context->data)) {
+ $result = db_query('SELECT t1.* FROM {taxonomy_term_hierarchy} t1 INNER JOIN {taxonomy_term_hierarchy} t2 ON t1.tid = t2.parent WHERE t2.tid = :tid', array(':tid' => $context->data->tid))->fetchAssoc();
+
+ // If top level term, keep looking up until we see a top level.
+ if ($conf['type'] == 'top') {
+ // If looking for top level, and there are no parents at all, make sure
+ // the current term is the 'top level'.
+ if (empty($result)) {
+ $result['tid'] = $context->data->tid;
+ }
+ while (!empty($result['parent'])) {
+ $result = db_query('SELECT * FROM {taxonomy_term_hierarchy} WHERE tid = :tid', array(':tid' => $result['parent']))->fetchAssoc();
+ }
+ }
+
+ // Load the term.
+ if ($result) {
+ $term = taxonomy_term_load($result['tid']);
+ return ctools_context_create('entity:taxonomy_term', $term);
+ }
+ }
+}
+
+/**
+ * Settings form for the relationship.
+ */
+function ctools_term_parent_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Relationship type'),
+ '#options' => array('parent' => t('Immediate parent'), 'top' => t('Top level term')),
+ '#default_value' => $conf['type'],
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/relationships/terms_from_node.inc b/sites/all/modules/ctools/plugins/relationships/terms_from_node.inc
new file mode 100644
index 000000000..07081f2ff
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/terms_from_node.inc
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for all terms from node.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Multiple terms from node'),
+ 'keyword' => 'terms',
+ 'description' => t('Adds a taxonomy terms from a node context; if multiple terms are selected, they wil be concatenated.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'context' => 'ctools_terms_from_node_context',
+ 'edit form' => 'ctools_terms_from_node_settings_form',
+ 'defaults' => array('vocabulary' => array(), 'concatenator' => ','),
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_terms_from_node_context($context, $conf) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data)) {
+ return ctools_context_create_empty('terms', NULL);
+ }
+
+ // Collect all terms for the chosen vocabulary and concatenate them.
+ $node = $context->data;
+ $terms = array();
+
+ $fields = field_info_instances('node', $node->type);
+ foreach ($fields as $name => $info) {
+ $field_info = field_info_field($name);
+ if ($field_info['type'] == 'taxonomy_term_reference' && (empty($conf['vocabulary']) || !empty($conf['vocabulary'][$field_info['settings']['allowed_values'][0]['vocabulary']]))) {
+ $items = field_get_items('node', $node, $name);
+ if (is_array($items)) {
+ foreach ($items as $item) {
+ $terms[] = $item['tid'];
+ }
+ }
+ }
+ }
+
+ if (!empty($terms)) {
+ $all_terms = ctools_break_phrase(implode($conf['concatenator'], $terms));
+ return ctools_context_create('terms', $all_terms);
+ }
+}
+
+/**
+ * Settings form for the relationship.
+ */
+function ctools_terms_from_node_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $options = array();
+ foreach (taxonomy_vocabulary_get_names() as $name => $vocabulary) {
+ $options[$name] = $vocabulary->name;
+ }
+ $form['vocabulary'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#default_value' => $conf['vocabulary'],
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ );
+ $form['concatenator'] = array(
+ '#title' => t('Concatenator'),
+ '#type' => 'select',
+ '#options' => array(',' => ', (AND)', '+' => '+ (OR)'),
+ '#default_value' => $conf['concatenator'],
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ '#description' => t("When the value from this context is passed on to a view as argument, the terms can be concatenated in the form of 1+2+3 (for OR) or 1,2,3 (for AND)."),
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/ctools/plugins/relationships/user_category_edit_form_from_user.inc b/sites/all/modules/ctools/plugins/relationships/user_category_edit_form_from_user.inc
new file mode 100644
index 000000000..28dac72c5
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/user_category_edit_form_from_user.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for term from node.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('User category edit form from user'),
+ 'keyword' => 'user_category_form',
+ 'description' => t('Adds user category edit form from a user context.'),
+ 'required context' => new ctools_context_required(t('User'), 'user'),
+ 'context' => 'ctools_user_category_edit_form_from_user_context',
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_user_category_edit_form_from_user_context($context, $conf) {
+ if (empty($context->data)) {
+ return ctools_context_create_empty('user_edit_form', NULL);
+ }
+
+ if (isset($context->data->user_category)) {
+ return ctools_context_create('user_edit_form', $context->data, array('category' => $context->data->user_category));
+ }
+}
diff --git a/sites/all/modules/ctools/plugins/relationships/user_from_node.inc b/sites/all/modules/ctools/plugins/relationships/user_from_node.inc
new file mode 100644
index 000000000..388b1cec1
--- /dev/null
+++ b/sites/all/modules/ctools/plugins/relationships/user_from_node.inc
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for node from user.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Node author'),
+ 'keyword' => 'user',
+ 'description' => t('Creates the author of a node as a user context.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node'),
+ 'context' => 'ctools_user_from_node_context',
+ 'no ui' => TRUE,
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function ctools_user_from_node_context($context, $conf) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data) || !isset($context->data->uid)) {
+ return ctools_context_create_empty('user', NULL);
+ }
+
+ if (isset($context->data->uid)) {
+ // Load the user that is the author of the node.
+ $uid = $context->data->uid;
+ $account = user_load($uid);
+
+ // Send it to ctools.
+ return ctools_context_create('user', $account);
+ }
+}
diff --git a/sites/all/modules/ctools/stylizer/plugins/export_ui/stylizer.inc b/sites/all/modules/ctools/stylizer/plugins/export_ui/stylizer.inc
new file mode 100644
index 000000000..a1fc1d8ca
--- /dev/null
+++ b/sites/all/modules/ctools/stylizer/plugins/export_ui/stylizer.inc
@@ -0,0 +1,45 @@
+<?php
+
+$plugin = array(
+ 'schema' => 'stylizer',
+ 'access' => 'administer stylizer',
+
+ 'menu' => array(
+ 'menu item' => 'stylizer',
+ 'menu title' => 'Stylizer',
+ 'menu description' => 'Add, edit or delete stylizer styles.',
+ ),
+
+ 'title singular' => t('style'),
+ 'title singular proper' => t('Style'),
+ 'title plural' => t('styles'),
+ 'title plural proper' => t('Styles'),
+
+ 'handler' => array(
+ 'class' => 'stylizer_ui',
+ ),
+
+ 'strings' => array(
+ 'message' => array(
+ 'missing base type' => t('There are currently no style types available to add. You should enable a module that utilizes them, such as Panels.'),
+ ),
+ ),
+
+ 'use wizard' => TRUE,
+ 'form info' => array(
+ 'add order' => array(
+ 'admin' => t('Administrative settings'),
+ 'type' => t('Select style type'),
+ 'choose' => t('Select base style'),
+ ),
+ 'order' => array(
+ 'admin' => t('Administrative settings'),
+ ),
+ 'forms' => array(
+ 'choose' => array(
+ 'form id' => 'ctools_stylizer_edit_style_form_choose',
+ ),
+ ),
+ ),
+);
+
diff --git a/sites/all/modules/ctools/stylizer/plugins/export_ui/stylizer_ui.class.php b/sites/all/modules/ctools/stylizer/plugins/export_ui/stylizer_ui.class.php
new file mode 100644
index 000000000..14d629396
--- /dev/null
+++ b/sites/all/modules/ctools/stylizer/plugins/export_ui/stylizer_ui.class.php
@@ -0,0 +1,272 @@
+<?php
+
+/**
+ * UI class for Stylizer.
+ */
+class stylizer_ui extends ctools_export_ui {
+
+ function access($op, $item) {
+ $access = parent::access($op, $item);
+ if ($op == 'add' && $access && empty($this->base_types)) {
+ // Make sure there are base styles defined.
+ $access = FALSE;
+ }
+ return $access;
+ }
+
+ function list_form(&$form, &$form_state) {
+ ctools_include('stylizer');
+ parent::list_form($form, $form_state);
+
+ $all = array('all' => t('- All -'));
+
+ if (empty($this->base_types)) {
+ // Give a warning about the missing base styles.
+ drupal_set_message($this->plugin['strings']['message']['missing base type'], 'warning');
+ }
+
+ $types = $all;
+ foreach ($this->base_types as $module => $info) {
+ foreach ($info as $key => $base_type) {
+ $types[$module . '-' . $key] = $base_type['title'];
+ }
+ }
+
+ $form['top row']['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Type'),
+ '#options' => $types,
+ '#default_value' => 'all',
+ '#weight' => -10,
+ '#attributes' => array('class' => array('ctools-auto-submit')),
+ );
+
+ $plugins = ctools_get_style_bases();
+ $form_state['style_plugins'] = $plugins;
+
+ $options = $all;
+ // @todo base should use $module . '-' . $name
+ foreach ($plugins as $name => $plugin) {
+ $options[$name] = $plugin['title'];
+ }
+
+ $form['top row']['base'] = array(
+ '#type' => 'select',
+ '#title' => t('Base'),
+ '#options' => $all + $options,
+ '#default_value' => 'all',
+ '#weight' => -9,
+ '#attributes' => array('class' => array('ctools-auto-submit')),
+ );
+ }
+
+ function list_sort_options() {
+ return array(
+ 'disabled' => t('Enabled, title'),
+ 'title' => t('Title'),
+ 'name' => t('Name'),
+ 'base' => t('Base'),
+ 'type' => t('Type'),
+ 'storage' => t('Storage'),
+ );
+ }
+
+ function list_filter($form_state, $item) {
+ if (empty($form_state['style_plugins'][$item->settings['style_base']])) {
+ $this->style_plugin = array(
+ 'name' => 'broken',
+ 'title' => t('Missing plugin'),
+ 'type' => t('Unknown'),
+ 'module' => '',
+ );
+ }
+ else {
+ $this->style_plugin = $form_state['style_plugins'][$item->settings['style_base']];
+ }
+
+ // This isn't really a field, but by setting this we can list it in the
+ // filter fields and have the search box pick it up.
+ $item->plugin_title = $this->style_plugin['title'];
+
+ if ($form_state['values']['type'] != 'all') {
+ list($module, $type) = explode('-', $form_state['values']['type']);
+ if ($module != $this->style_plugin['module'] || $type != $this->style_plugin['type']) {
+ return TRUE;
+ }
+ }
+
+ if ($form_state['values']['base'] != 'all' && $form_state['values']['base'] != $this->style_plugin['name']) {
+ return TRUE;
+ }
+
+ return parent::list_filter($form_state, $item);
+ }
+
+ function list_search_fields() {
+ $fields = parent::list_search_fields();
+ $fields[] = 'plugin_title';
+ return $fields;
+ }
+
+ function list_build_row($item, &$form_state, $operations) {
+ // Set up sorting
+ switch ($form_state['values']['order']) {
+ case 'disabled':
+ $this->sorts[$item->name] = empty($item->disabled) . $item->admin_title;
+ break;
+ case 'title':
+ $this->sorts[$item->name] = $item->admin_title;
+ break;
+ case 'name':
+ $this->sorts[$item->name] = $item->name;
+ break;
+ case 'type':
+ $this->sorts[$item->name] = $this->style_plugin['type'] . $item->admin_title;
+ break;
+ case 'base':
+ $this->sorts[$item->name] = $this->style_plugin['title'] . $item->admin_title;
+ break;
+ case 'storage':
+ $this->sorts[$item->name] = $item->type . $item->admin_title;
+ break;
+ }
+
+ if (!empty($this->base_types[$this->style_plugin['module']][$this->style_plugin['type']])) {
+ $type = $this->base_types[$this->style_plugin['module']][$this->style_plugin['type']]['title'];
+ }
+ else {
+ $type = t('Unknown');
+ }
+
+ $ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
+
+ $this->rows[$item->name] = array(
+ 'data' => array(
+ array('data' => $type, 'class' => array('ctools-export-ui-type')),
+ array('data' => check_plain($item->name), 'class' => array('ctools-export-ui-name')),
+ array('data' => check_plain($item->admin_title), 'class' => array('ctools-export-ui-title')),
+ array('data' => check_plain($this->style_plugin['title']), 'class' => array('ctools-export-ui-base')),
+ array('data' => check_plain($item->type), 'class' => array('ctools-export-ui-storage')),
+ array('data' => $ops, 'class' => array('ctools-export-ui-operations')),
+ ),
+ 'title' => check_plain($item->admin_description),
+ 'class' => array(!empty($item->disabled) ? 'ctools-export-ui-disabled' : 'ctools-export-ui-enabled'),
+ );
+ }
+
+ function list_table_header() {
+ return array(
+ array('data' => t('Type'), 'class' => array('ctools-export-ui-type')),
+ array('data' => t('Name'), 'class' => array('ctools-export-ui-name')),
+ array('data' => t('Title'), 'class' => array('ctools-export-ui-title')),
+ array('data' => t('Base'), 'class' => array('ctools-export-ui-base')),
+ array('data' => t('Storage'), 'class' => array('ctools-export-ui-storage')),
+ array('data' => t('Operations'), 'class' => array('ctools-export-ui-operations')),
+ );
+ }
+
+ function init($plugin) {
+ ctools_include('stylizer');
+ $this->base_types = ctools_get_style_base_types();
+
+ parent::init($plugin);
+ }
+
+ function get_wizard_info(&$form_state) {
+ $form_info = parent::get_wizard_info($form_state);
+ ctools_include('stylizer');
+
+ // For add forms, we have temporarily set the 'form type' to include
+ // the style type so the default wizard_info can find the path. If
+ // we did that, we have to put it back.
+ if (!empty($form_state['type'])) {
+ $form_state['form type'] = 'add';
+ $form_info['show back'] = TRUE;
+ }
+
+ // Ensure these do not get out of sync.
+ $form_state['item']->settings['name'] = $form_state['item']->name;
+ $form_state['settings'] = $form_state['item']->settings;
+
+ // Figure out the base style plugin in use and make sure that is available.
+ $plugin = NULL;
+ if (!empty($form_state['item']->settings['style_base'])) {
+ $plugin = ctools_get_style_base($form_state['item']->settings['style_base']);
+ ctools_stylizer_add_plugin_forms($form_info, $plugin, $form_state['op']);
+ }
+ else {
+ // This is here so the 'finish' button does not show up, and because
+ // we don't have the selected style we don't know what the next form(s)
+ // will be.
+ $form_info['order']['next'] = t('Configure style');
+
+ }
+
+ // If available, make sure these are available for the 'choose' form.
+ if (!empty($form_state['item']->style_module)) {
+ $form_state['module'] = $form_state['item']->style_module;
+ $form_state['type'] = $form_state['item']->style_type;
+ }
+
+ $form_state['base_style_plugin'] = $plugin;
+ $form_state['settings'] = $form_state['item']->settings;
+ return $form_info;
+ }
+
+ /**
+ * Store the stylizer info in our settings.
+ *
+ * The stylizer wizard stores its stuff in slightly different places, so
+ * we have to find it and move it to the right place.
+ */
+ function store_stylizer_info(&$form_state) {
+ /*
+ foreach (array('name', 'admin_title', 'admin_description') as $key) {
+ if (!empty($form_state['values'][$key])) {
+ $form_state['item']->{$key} = $form_state['values'][$key];
+ }
+ }
+ */
+
+ if ($form_state['step'] != 'import') {
+ $form_state['item']->settings = $form_state['settings'];
+ }
+ // Do not let the 'name' accidentally get out of sync under any circumstances.
+ $form_state['item']->settings['name'] = $form_state['item']->name;
+ }
+
+ function edit_wizard_next(&$form_state) {
+ $this->store_stylizer_info($form_state);
+ parent::edit_wizard_next($form_state);
+ }
+
+ function edit_wizard_finish(&$form_state) {
+ // These might be stored by the stylizer wizard, so we should clear them.
+ if (isset($form_state['settings']['old_settings'])) {
+ unset($form_state['settings']['old_settings']);
+ }
+ $this->store_stylizer_info($form_state);
+ parent::edit_wizard_finish($form_state);
+ }
+
+ function edit_form_type(&$form, &$form_state) {
+ foreach ($this->base_types as $module => $info) {
+ foreach ($info as $key => $base_type) {
+ $types[$module . '-' . $key] = $base_type['title'];
+ }
+ }
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Type'),
+ '#options' => $types,
+ '#default_value' => 'all',
+ '#weight' => -10,
+ '#attributes' => array('class' => array('ctools-auto-submit')),
+ );
+ }
+
+ function edit_form_type_submit(&$form, &$form_state) {
+ list($form_state['item']->style_module, $form_state['item']->style_type) = explode('-', $form_state['values']['type']);
+ }
+}
diff --git a/sites/all/modules/ctools/stylizer/stylizer.info b/sites/all/modules/ctools/stylizer/stylizer.info
new file mode 100644
index 000000000..9e9890ff4
--- /dev/null
+++ b/sites/all/modules/ctools/stylizer/stylizer.info
@@ -0,0 +1,14 @@
+name = Stylizer
+description = Create custom styles for applications such as Panels.
+core = 7.x
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+dependencies[] = ctools
+dependencies[] = color
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/stylizer/stylizer.install b/sites/all/modules/ctools/stylizer/stylizer.install
new file mode 100644
index 000000000..5cefb0ddf
--- /dev/null
+++ b/sites/all/modules/ctools/stylizer/stylizer.install
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Schema for stylizer.
+ */
+function stylizer_schema() {
+ return stylizer_schema_1();
+}
+
+function stylizer_schema_1() {
+ $schema = array();
+
+ $schema['stylizer'] = array(
+ 'description' => 'Customized stylizer styles created by administrative users.',
+ 'export' => array(
+ 'bulk export' => TRUE,
+ 'export callback' => 'stylizer_style_export',
+ 'can disable' => TRUE,
+ 'identifier' => 'style',
+ 'primary key' => 'sid',
+ 'api' => array(
+ 'owner' => 'stylizer',
+ 'api' => 'stylizer',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+ 'fields' => array(
+ 'sid' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'no export' => TRUE,
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Unique ID for this style. Used to identify it programmatically.',
+ ),
+ 'admin_title' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'description' => 'Human readable title for this style.',
+ ),
+ 'admin_description' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Administrative description of this style.',
+ 'object default' => '',
+ ),
+ 'settings' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ 'object default' => array(),
+ 'initial ' => array(
+ 'name' => '_temporary',
+ 'style_base' => NULL,
+ 'palette' => array(),
+ ),
+ 'description' => 'A serialized array of settings specific to the style base that describes this plugin.',
+ ),
+ ),
+ 'primary key' => array('sid'),
+ 'unique keys' => array(
+ 'name' => array('name'),
+ ),
+ );
+
+ return $schema;
+}
diff --git a/sites/all/modules/ctools/stylizer/stylizer.module b/sites/all/modules/ctools/stylizer/stylizer.module
new file mode 100644
index 000000000..e7278975d
--- /dev/null
+++ b/sites/all/modules/ctools/stylizer/stylizer.module
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Stylizer module
+ *
+ * This module allows styles to be created and managed on behalf of modules
+ * that implement styles.
+ *
+ * The Stylizer tool allows recolorable styles to be created via a miniature
+ * scripting language. Panels utilizes this to allow administrators to add
+ * styles directly to any panel display.
+ */
+
+/**
+ * Implements hook_permission()
+ */
+function stylizer_permission() {
+ return array(
+ 'administer stylizer' => array(
+ 'title' => t("Use the Stylizer UI"),
+ 'description' => t("Allows a user to use the CTools Stylizer UI."),
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_ctools_plugin_directory() to let the system know
+ * we implement task and task_handler plugins.
+ */
+function stylizer_ctools_plugin_directory($module, $plugin) {
+ // Most of this module is implemented as an export ui plugin, and the
+ // rest is in ctools/includes/stylizer.inc
+ if ($module == 'ctools' && $plugin == 'export_ui') {
+ return 'plugins/' . $plugin;
+ }
+}
+
+/**
+ * Implements hook_ctools_plugin_type() to inform the plugin system that
+ * Stylizer style_base plugin types.
+ */
+function stylizer_ctools_plugin_type() {
+ return array(
+ 'style_bases' => array(
+ 'load themes' => TRUE,
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_panels_dashboard_blocks().
+ *
+ * Adds page information to the Panels dashboard.
+ */
+function stylizer_panels_dashboard_blocks(&$vars) {
+ $vars['links']['stylizer'] = array(
+ 'title' => l(t('Custom style'), 'admin/structure/stylizer/add'),
+ 'description' => t('Custom styles can be applied to Panel regions and Panel panes.'),
+ );
+
+ // Load all mini panels and their displays.
+ ctools_include('export');
+ ctools_include('stylizer');
+ $items = ctools_export_crud_load_all('stylizer');
+ $count = 0;
+ $rows = array();
+
+ $base_types = ctools_get_style_base_types();
+ foreach ($items as $item) {
+ $style = ctools_get_style_base($item->settings['style_base']);
+ if ($style && $style['module'] == 'panels') {
+ $type = $base_types[$style['module']][$style['type']]['title'];
+
+ $rows[] = array(
+ check_plain($item->admin_title),
+ $type,
+ array(
+ 'data' => l(t('Edit'), "admin/structure/stylizer/list/$item->name/edit"),
+ 'class' => 'links',
+ ),
+ );
+
+ // Only show 10.
+ if (++$count >= 10) {
+ break;
+ }
+ }
+ }
+
+ if ($rows) {
+ $content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
+ }
+ else {
+ $content = '<p>' . t('There are no custom styles.') . '</p>';
+ }
+
+ $vars['blocks']['stylizer'] = array(
+ 'title' => t('Manage styles'),
+ 'link' => l(t('Go to list'), 'admin/structure/stylizer'),
+ 'content' => $content,
+ 'class' => 'dashboard-styles',
+ 'section' => 'left',
+ );
+}
+
+/**
+ * Implementation of hook_theme to load all content plugins and pass thru if
+ * necessary.
+ */
+function stylizer_theme() {
+ $theme = array();
+ ctools_include('stylizer');
+ // Register all themes given for basetypes.
+ $plugins = ctools_get_style_bases();
+ $base_types = ctools_get_style_base_types();
+ foreach ($plugins as $plugin) {
+ if (!empty($base_types[$plugin['module']][$plugin['type']]) && !empty($plugin['theme'])) {
+ $base_type = $base_types[$plugin['module']][$plugin['type']];
+ $theme[$plugin['theme']] = array(
+ 'variables' => $base_type['theme variables'],
+ 'path' => $plugin['path'],
+ );
+
+ // if no theme function exists, assume template.
+ if (!function_exists("theme_$plugin[theme]")) {
+ $theme[$plugin['theme']]['template'] = str_replace('_', '-', $plugin['theme']);
+ $theme[$plugin['theme']]['file'] = $plugin['file']; // for preprocess.
+ }
+ }
+ }
+
+ return $theme;
+}
+
diff --git a/sites/all/modules/ctools/term_depth/plugins/access/term_depth.inc b/sites/all/modules/ctools/term_depth/plugins/access/term_depth.inc
new file mode 100644
index 000000000..ab05b6094
--- /dev/null
+++ b/sites/all/modules/ctools/term_depth/plugins/access/term_depth.inc
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based upon a parent term.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("Taxonomy: term depth"),
+ 'description' => t('Control access by the depth of a term.'),
+ 'callback' => 'term_depth_term_depth_ctools_access_check',
+ 'default' => array('vid' => array(), 'depth' => 0),
+ 'settings form' => 'term_depth_term_depth_ctools_access_settings',
+ 'settings form validation' => 'term_depth_term_depth_ctools_access_settings_validate',
+ 'settings form submit' => 'term_depth_term_depth_ctools_access_settings_submit',
+ 'summary' => 'term_depth_term_depth_ctools_access_summary',
+ 'required context' => new ctools_context_required(t('Term'), array('taxonomy_term', 'terms')),
+);
+
+/**
+ * Settings form for the 'term depth' access plugin.
+ */
+function term_depth_term_depth_ctools_access_settings($form, &$form_state, $conf) {
+ // If no configuration was saved before, set some defaults.
+ if (empty($conf)) {
+ $conf = array(
+ 'vid' => 0,
+ );
+ }
+ if (!isset($conf['vid'])) {
+ $conf['vid'] = 0;
+ }
+
+ // Loop over each of the configured vocabularies.
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ }
+
+ $form['settings']['vid'] = array(
+ '#title' => t('Vocabulary'),
+ '#type' => 'select',
+ '#options' => $options,
+ '#description' => t('Select the vocabulary for this form. If there exists a parent term in that vocabulary, this access check will succeed.'),
+ '#id' => 'ctools-select-vid',
+ '#default_value' => $conf['vid'],
+ '#required' => TRUE,
+ );
+
+ $form['settings']['depth'] = array(
+ '#title' => t('Depth'),
+ '#type' => 'textfield',
+ '#description' => t('Set the required depth of the term. If the term exists at the correct depth, this access check will succeed.'),
+ '#default_value' => $conf['depth'],
+ '#required' => TRUE,
+ );
+
+ return $form;
+}
+
+/**
+ * Submit function for the access plugins settings.
+ *
+ * We cast all settings to numbers to ensure they can be safely handled.
+ */
+function term_depth_term_depth_ctools_access_settings_submit($form, $form_state) {
+ foreach (array('depth', 'vid') as $key) {
+ $form_state['conf'][$key] = (integer) $form_state['values']['settings'][$key];
+ }
+}
+
+/**
+ * Check for access.
+ */
+function term_depth_term_depth_ctools_access_check($conf, $context) {
+ // As far as I know there should always be a context at this point, but this
+ // is safe.
+ if (empty($context) || empty($context->data) || empty($context->data->vid) || empty($context->data->tid)) {
+ return FALSE;
+ }
+
+ // Get the $vid.
+ if (!isset($conf['vid'])) {
+ return FALSE;
+ }
+ $depth = _term_depth($context->data->tid);
+
+ return ($depth == $conf['depth']);
+}
+
+/**
+ * Provide a summary description based upon the checked terms.
+ */
+function term_depth_term_depth_ctools_access_summary($conf, $context) {
+ $vocab = taxonomy_vocabulary_load($conf['vid']);
+
+ return t('"@term" has parent in vocabulary "@vocab" at @depth', array(
+ '@term' => $context->identifier,
+ '@vocab' => $vocab->name,
+ '@depth' => $conf['depth'],
+ ));
+}
+
+/**
+ * Find the depth of a term.
+ */
+function _term_depth($tid) {
+ static $depths = array();
+
+ if (!isset($depths[$tid])) {
+ $parent = db_select('taxonomy_term_hierarchy', 'th')
+ ->fields('th', array('parent'))
+ ->condition('tid', $tid)
+ ->execute()->fetchField();
+
+ if ($parent == 0) {
+ $depths[$tid] = 1;
+ }
+ else {
+ $depths[$tid] = 1 + _term_depth($parent);
+ }
+ }
+
+ return $depths[$tid];
+}
diff --git a/sites/all/modules/ctools/term_depth/term_depth.info b/sites/all/modules/ctools/term_depth/term_depth.info
new file mode 100644
index 000000000..d6fa37fbe
--- /dev/null
+++ b/sites/all/modules/ctools/term_depth/term_depth.info
@@ -0,0 +1,13 @@
+name = Term Depth access
+description = Controls access to context based upon term depth
+core = 7.x
+dependencies[] = ctools
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/term_depth/term_depth.module b/sites/all/modules/ctools/term_depth/term_depth.module
new file mode 100644
index 000000000..10f9e3194
--- /dev/null
+++ b/sites/all/modules/ctools/term_depth/term_depth.module
@@ -0,0 +1,7 @@
+<?php
+
+function term_depth_ctools_plugin_directory($owner, $plugin) {
+ if ($owner == 'ctools' && $plugin == 'access') {
+ return 'plugins/' . $plugin;
+ }
+}
diff --git a/sites/all/modules/ctools/tests/context.test b/sites/all/modules/ctools/tests/context.test
new file mode 100644
index 000000000..bdf14e3f4
--- /dev/null
+++ b/sites/all/modules/ctools/tests/context.test
@@ -0,0 +1,62 @@
+<?php
+
+class CtoolsContextKeywordsSubstitutionTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Keywords substitution',
+ 'description' => 'Verify that keywords are properly replaced with data.',
+ 'group' => 'Chaos Tools Suite',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('ctools');
+
+ ctools_include('context');
+ }
+
+ public function testKeywordsSubstitution() {
+ // Create node context for substitution.
+ $node = $this->drupalCreateNode();
+ $context = ctools_context_create('node', $node);
+ $contexts = array('argument_1' => $context);
+
+ // Run tests on some edge cases.
+ $checks = array(
+ '%node:changed:raw:' => array(
+ "{$node->changed}:",
+ t('Multi-level token has been replaced. Colon left untouched.'),
+ ),
+ '%node:title' => array(
+ "{$node->title}",
+ t('Keyword and converter have been replaced.'),
+ ),
+ '%%node:title' => array(
+ "%node:title",
+ t('Keyword after escaped percent sign left untouched.'),
+ ),
+ '%node:title%node:nid' => array(
+ "{$node->title}{$node->nid}",
+ t('Multiple substitutions have been replaced.'),
+ ),
+ '%node:title:' => array(
+ "{$node->title}:",
+ t('Colon after keyword and converter left untouched.'),
+ ),
+ '%node:title%%' => array(
+ "{$node->title}%",
+ t('Escaped percent sign after keyword and converter left untouched.'),
+ ),
+ '%%%node:title' => array(
+ "%{$node->title}",
+ t('Keyword after escaped and unescaped percent sign has been replaced.'),
+ ),
+ );
+ foreach ($checks as $string => $expectations) {
+ list($expected_result, $message) = $expectations;
+ $actual_result = ctools_context_keyword_substitute($string, array(), $contexts);
+ $this->assertEqual($actual_result, $expected_result, $message);
+ }
+ }
+
+}
diff --git a/sites/all/modules/ctools/tests/css.test b/sites/all/modules/ctools/tests/css.test
new file mode 100644
index 000000000..4a5200caa
--- /dev/null
+++ b/sites/all/modules/ctools/tests/css.test
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @file
+ * Tests for different parts of the ctools plugin system.
+ */
+
+/**
+ * Test menu links depending on user permissions.
+ */
+class CtoolsCssTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'CSS Tools tests',
+ 'description' => '...',
+ 'group' => 'Chaos Tools Suite',
+ );
+ }
+
+ function setUp() {
+ // Additionally enable contact module.
+ parent::setUp('ctools');
+ }
+
+ /**
+ * Test that cached plugins are loaded correctly.
+ */
+ function testCssStuff() {
+ $css = "#some-id .some-class {\n color: black;\n illegal-key: foo;\n}";
+ $filtered_css = '#some-id .some-class{color:black;}';
+
+ ctools_include('css');
+ $filename1 = ctools_css_store('unfiltered-css-test', $css, FALSE);
+ $filename2 = ctools_css_store('filtered-css-test', $css, TRUE);
+
+ $this->assertEqual($filename1, ctools_css_retrieve('unfiltered-css-test'), 'Unfiltered css file successfully fetched');
+ $file_contents = file_get_contents($filename1);
+ $this->assertEqual($css, $file_contents, 'Unfiltered css file contents are correct');
+// $match = $filename1 == ctools_css_retrieve('unfiltered-css-test') ? 'Match' : 'No match';
+// $output .= '<pre>Unfiltered: ' . $filename1 . ' ' . $match . '</pre>';
+// $output .= '<pre>' . file_get_contents($filename1) . '</pre>';
+
+ $this->assertEqual($filename2, ctools_css_retrieve('filtered-css-test'), 'Filtered css file succcesfully fetched');
+ $file_contents = file_get_contents($filename2);
+ $this->assertEqual($filtered_css, $file_contents, 'Filtered css file contents are correct');
+ // $match = $filename2 == ctools_css_retrieve('filtered-css-test') ? 'Match' : 'No match';
+// $output .= '<pre>Filtered: ' . $filename2 . ' ' . $match . '</pre>';
+// $output .= '<pre>' . file_get_contents($filename2) . '</pre>';
+//
+// drupal_add_css($filename2, array('type' => 'file'));
+// return array('#markup' => $output);
+
+
+ // Test that in case that url can be used, the value surives when a colon is in it.
+ $css = "#some-id {\n background-image: url(http://example.com/example.gif);\n}";
+ $css_data = ctools_css_disassemble($css);
+ $empty_array = array();
+ $disallowed_values_regex = '/(expression)/';
+ $filtered = ctools_css_assemble(ctools_css_filter_css_data($css_data, $empty_array, $empty_array, '', $disallowed_values_regex));
+ $url = (strpos($filtered, 'http://example.com/example.gif') !== FALSE);
+ $this->assertTrue($url, 'CSS with multiple colons can survive.');
+
+ // Test that in case the CSS has two properties defined are merged.
+ $css = "#some-id {\n font-size: 12px;\n}\n#some-id {\n color: blue;\n}";
+ $filtered = ctools_css_filter($css);
+ $font_size = (strpos($filtered, 'font-size:12px;') !== FALSE);
+ $color = (strpos($filtered, 'color:blue') !== FALSE);
+ $this->assertTrue($font_size && $color, 'Multiple properties are merged.');
+
+ $css = '@import url("other.css");p {color: red;}';
+ $filtered = ctools_css_filter($css);
+ $other_css = (strpos($filtered, 'other.css') === FALSE);
+ $color = (strpos($filtered, 'color:red') !== FALSE);
+ $this->assertTrue($other_css && $color, 'CSS is properly sanitized.');
+
+ $css = ';p {color: red; font-size: 12px;}';
+ $filtered = ctools_css_filter($css);
+ $font_size = (strpos($filtered, 'font-size:12px;') !== FALSE);
+ $color = (strpos($filtered, 'color:red') !== FALSE);
+ $this->assertTrue($font_size && $color, 'Multiple properties are retained.');
+ }
+}
diff --git a/sites/all/modules/ctools/tests/css_cache.test b/sites/all/modules/ctools/tests/css_cache.test
new file mode 100644
index 000000000..e289b42c9
--- /dev/null
+++ b/sites/all/modules/ctools/tests/css_cache.test
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @file
+ * Tests the custom CSS cache handler.
+ */
+
+/**
+ * Tests the custom CSS cache handler.
+ */
+class CtoolsObjectCache extends DrupalWebTestCase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'Ctools CSS cache',
+ 'description' => 'Tests the custom CSS cache handler.',
+ 'group' => 'Chaos Tools Suite',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ parent::setUp('ctools');
+ }
+
+ /**
+ * Tests the custom CSS cache handler.
+ *
+ * @see https://drupal.org/node/1313368
+ */
+ public function testCssCache() {
+ // Create a CSS cache entry.
+ $filename = ctools_css_cache('body { color: red; }');
+
+ // Perform a cron run. The CSS cache entry should not be removed.
+ $this->cronRun();
+ $this->assertTrue(file_exists($filename), 'The CSS cache is not cleared after performing a cron run.');
+
+ // Manually clear the caches. The CSS cache entry should be removed.
+ drupal_flush_all_caches();
+ $this->assertFalse(file_exists($filename), 'The CSS cache is cleared after clearing all caches.');
+ }
+
+}
diff --git a/sites/all/modules/ctools/tests/ctools.drush.sh b/sites/all/modules/ctools/tests/ctools.drush.sh
new file mode 100755
index 000000000..31bdee359
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools.drush.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+
+# Run this from the terminal inside a drupal root folder
+# i.e. DRUPAL_ROOT_DIR/sites/all/modules/contrib/ctools/tests/ctools.drush.sh
+
+function stamp {
+ echo ==============
+ echo timestamp : `date`
+ echo ==============
+}
+
+DRUPAL_ROOT=`drush dd`
+MODULE_DIR="$DRUPAL_ROOT/sites/all/modules"
+MODULE_NAME="ctools_drush_test"
+
+stamp
+
+echo 'Enabling ctools, views, and bulk_export modules.'
+drush en ctools views bulk_export --yes
+
+stamp
+echo 'Reading all export info'
+drush ctools-export-info
+
+stamp
+echo 'Reading all export info with format'
+drush ctools-export-info --format=json
+
+stamp
+echo 'Reading tables only from export info'
+drush ctools-export-info --tables-only
+
+stamp
+echo 'Reading tables only from export info with format'
+drush ctools-export-info --tables-only --format=json
+
+stamp
+echo 'Reading all disabled exportables'
+drush ctools-export-info --filter=disabled
+
+stamp
+echo 'Enabling all default views'
+drush ctools-export-enable views_view --yes
+
+stamp
+echo 'Reading all enabled exportables'
+drush ctools-export-info --filter=enabled
+
+stamp
+echo 'Reading all overridden exportables'
+drush ctools-export-info --filter=overridden
+
+stamp
+echo 'Reading all database only exportables'
+drush ctools-export-info --filter=database
+
+stamp
+echo 'View all default views export data'
+drush ctools-export-view views_view --yes
+
+stamp
+echo 'View default "archive" view export data'
+drush ctools-export-view views_view archive
+
+stamp
+echo 'Disable default "archive" view'
+drush ctools-export-disable views_view archive
+
+stamp
+echo 'Enable default "archive" view'
+drush ctools-export-enable views_view archive
+
+stamp
+echo 'Reading all enabled exportables (archive disabled)'
+drush ctools-export-info
+
+stamp
+echo 'Disabling all default views'
+drush ctools-export-disable views_view --yes
+
+stamp
+echo 'Revert all default views'
+drush ctools-export-revert views_view --yes
+
+stamp
+echo 'Enable all node views'
+drush ctools-export-enable views_view --module=node --yes
+
+stamp
+echo 'Disable all node views'
+drush ctools-export-disable views_view --module=node --yes
+
+stamp
+echo 'Revert all node views'
+drush ctools-export-revert views_view --module=node --yes
+
+stamp
+echo 'Revert all exportables'
+drush ctools-export-revert --all --yes
+
+stamp
+echo 'Enable all exportables'
+drush ctools-export-enable --all --yes
+
+stamp
+echo 'Disable all exportables'
+drush ctools-export-disable --all --yes
+
+stamp
+echo 'Bulk export all objects'
+drush ctools-export $MODULE_NAME --subdir='tests' --choice=1
+
+stamp
+echo 'Show all files in created folder'
+ls -lAR "$MODULE_DIR/tests/$MODULE_NAME"
+
+stamp
+echo 'Removing exported object files'
+rm -Rf $MODULE_DIR/tests
diff --git a/sites/all/modules/ctools/tests/ctools.plugins.test b/sites/all/modules/ctools/tests/ctools.plugins.test
new file mode 100644
index 000000000..fe1829cfb
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools.plugins.test
@@ -0,0 +1,100 @@
+<?php
+/**
+ * @file
+ * Tests for different parts of the ctools plugin system.
+ */
+
+/**
+ * Test menu links depending on user permissions.
+ */
+class CtoolsPluginsGetInfoTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Get plugin info',
+ 'description' => 'Verify that plugin type definitions can properly set and overide values.',
+ 'group' => 'Chaos Tools Suite',
+ );
+ }
+
+ function setUp() {
+ // Additionally enable contact module.
+ parent::setUp('ctools', 'ctools_plugin_test');
+ }
+
+ protected function assertPluginFunction($module, $type, $id, $function = 'function') {
+ $func = ctools_plugin_load_function($module, $type, $id, $function);
+ $this->assertTrue(function_exists($func), t('Plugin @plugin of plugin type @module:@type successfully retrieved @retrieved for @function.', array(
+ '@plugin' => $id,
+ '@module' => $module,
+ '@type' => $type,
+ '@function' => $function,
+ '@retrieved' => $func,
+ )));
+ }
+
+ protected function assertPluginMissingFunction($module, $type, $id, $function = 'function') {
+ $func = ctools_plugin_load_function($module, $type, $id, $function);
+ $this->assertEqual($func, NULL, t('Plugin @plugin of plugin type @module:@type for @function with missing function successfully failed.', array(
+ '@plugin' => $id,
+ '@module' => $module,
+ '@type' => $type,
+ '@function' => $func,
+ )));
+ }
+
+ protected function assertPluginClass($module, $type, $id, $class = 'handler') {
+ $class_name = ctools_plugin_load_class($module, $type, $id, $class);
+ $this->assertTrue(class_exists($class_name), t('Plugin @plugin of plugin type @module:@type successfully retrieved @retrieved for @class.', array(
+ '@plugin' => $id,
+ '@module' => $module,
+ '@type' => $type,
+ '@class' => $class,
+ '@retrieved' => $class_name,
+ )));
+ }
+
+ protected function assertPluginMissingClass($module, $type, $id, $class = 'handler') {
+ $class_name = ctools_plugin_load_class($module, $type, $id, $class);
+ $this->assertEqual($class_name, NULL, t('Plugin @plugin of plugin type @module:@type for @class with missing class successfully failed.', array(
+ '@plugin' => $id,
+ '@module' => $module,
+ '@type' => $type,
+ '@class' => $class,
+ )));
+ }
+
+ /**
+ * Test that plugins are loaded correctly.
+ */
+ function testPluginLoading() {
+ ctools_include('plugins');
+ $module = 'ctools_plugin_test';
+ $type = 'not_cached';
+
+ // Test function retrieval for plugins using different definition methods.
+ $this->assertPluginFunction($module, $type, 'plugin_array', 'function');
+ $this->assertPluginFunction($module, $type, 'plugin_array2', 'function');
+ $this->assertPluginMissingFunction($module, $type, 'plugin_array_dne', 'function');
+ $this->assertPluginFunction($module, "big_hook_$type", 'test1', 'function');
+
+ // Test class retrieval for plugins using different definition methods.
+ $this->assertPluginClass($module, $type, 'plugin_array', 'handler');
+ $this->assertPluginClass($module, $type, 'plugin_array2', 'handler');
+ $this->assertPluginMissingClass($module, $type, 'plugin_array_dne', 'handler');
+ // TODO Test big hook plugins.
+
+ $type = 'cached';
+
+ // Test function retrieval for plugins using different definition methods.
+ $this->assertPluginFunction($module, $type, 'plugin_array', 'function');
+ $this->assertPluginFunction($module, $type, 'plugin_array2', 'function');
+ $this->assertPluginMissingFunction($module, $type, 'plugin_array_dne', 'function');
+ $this->assertPluginFunction($module, "big_hook_$type", 'test1', 'function');
+
+ // Test class retrieval for plugins using different definition methods.
+ $this->assertPluginClass($module, $type, 'plugin_array', 'handler');
+ $this->assertPluginClass($module, $type, 'plugin_array2', 'handler');
+ $this->assertPluginMissingClass($module, $type, 'plugin_array_dne', 'handler');
+ // TODO Test big hook plugins.
+ }
+}
diff --git a/sites/all/modules/ctools/tests/ctools_export_test/ctools_export.test b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export.test
new file mode 100644
index 000000000..1accfd740
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export.test
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * @file
+ * Tests for the CTools export system.
+ */
+
+/**
+ * Tests export CRUD.
+ */
+class CtoolsExportCrudTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'CTools export CRUD tests',
+ 'description' => 'Test the CRUD functionality for the ctools export system.',
+ 'group' => 'Chaos Tools Suite',
+ );
+ }
+
+ protected function setUp() {
+ parent::setUp('ctools_export_test');
+ $this->resetAll();
+ }
+
+ /**
+ * Tests CRUD operation: Load.
+ */
+ function testCrudExportLoad() {
+ $loaded_export = ctools_export_crud_load('ctools_export_test', 'database_test');
+
+ $expected_export = new stdClass();
+ $expected_export->machine = 'database_test';
+ $expected_export->title = 'Database test';
+ $expected_export->number = 0;
+ $expected_export->data = array(
+ 'test_1' => 'Test 1',
+ 'test_2' => 'Test 2',
+ );
+ $expected_export->table = 'ctools_export_test';
+ $expected_export->export_type = EXPORT_IN_DATABASE;
+ $expected_export->type = 'Normal';
+
+ $this->assertEqual($expected_export, $loaded_export, 'An exportable object has been loaded correctly from the database.');
+ }
+
+ /**
+ * Tests CRUD operation: Load multiple.
+ */
+ function testCrudExportLoadMultiple() {
+ $exportable_names = array('database_test', 'overridden_test', 'default_test');
+ $loaded_exports = ctools_export_crud_load_multiple('ctools_export_test', $exportable_names);
+
+ $this->assertEqual(count($loaded_exports), 3, 'All exportables have been loaded.');
+ }
+
+ /**
+ * Tests CRUD operation: Load all.
+ */
+ function testCrudExportLoadAll() {
+ $loaded_exports = ctools_export_crud_load_all('ctools_export_test');
+
+ $this->assertEqual(count($loaded_exports), 3, 'All exportables have been loaded.');
+ }
+
+ /**
+ * Tests CRUD operation: Save.
+ */
+ function testCrudExportSave() {
+ $default_export = ctools_export_crud_load('ctools_export_test', 'default_test');
+
+ $this->assertTrue($default_export->in_code_only,'The loaded exportable is in code only.');
+
+ ctools_export_crud_save('ctools_export_test', $default_export);
+
+ // Clear the static cache.
+ ctools_export_load_object_reset('ctools_export_test');
+
+ $overridden_export = ctools_export_crud_load('ctools_export_test', 'default_test');
+
+ $this->assertTrue($overridden_export->export_type === 3, 'The loaded exportable is overridden in the database.');
+ }
+
+ /**
+ * Tests CRUD operation: New.
+ */
+ function testCrudExportNew() {
+ // Default exportable with defualt values.
+ $new_export = ctools_export_crud_new('ctools_export_test');
+
+ $expected_export = new stdClass();
+ $expected_export->machine = '';
+ $expected_export->title = '';
+ $expected_export->number = 0;
+ $expected_export->data = NULL;
+ $expected_export->export_type = NULL;
+ $expected_export->type = 'Local';
+
+ $this->assertEqual($expected_export, $new_export, 'An exportable with default values is created.');
+
+ // Default exportable without default values.
+ $new_export = ctools_export_crud_new('ctools_export_test', FALSE);
+
+ $expected_export = new stdClass();
+ $expected_export->machine = '';
+ $expected_export->title = '';
+ $expected_export->number = NULL;
+ $expected_export->data = NULL;
+
+ $this->assertEqual($expected_export, $new_export, 'An exportable without default values has been created.');
+ }
+
+ /**
+ * Tests CRUD operation: Revert.
+ */
+ function testCrudExportRevert() {
+ // Load exportable, will come from database.
+ $original_export = ctools_export_crud_load('ctools_export_test', 'overridden_test');
+
+ $this->assertTrue($original_export->export_type === 3, 'Loaded export is overridden.');
+
+ $machine = $original_export->machine;
+ ctools_export_crud_delete('ctools_export_test', $original_export);
+
+ $result = db_query("SELECT machine FROM {ctools_export_test} WHERE machine = :machine", array(':machine' => $machine))->fetchField();
+
+ $this->assertFalse($result, 'The exportable object has been removed from the database.');
+
+ // Clear the static cache.
+ ctools_export_load_object_reset('ctools_export_test');
+
+ // Reload the same object.
+ $default_export = ctools_export_crud_load('ctools_export_test', 'overridden_test');
+
+ // Check the exportable is now in_code_only.
+ $this->assertTrue($default_export->in_code_only, 'The loaded exportable is in the database only.');
+
+ // Make sure the default object loaded matches the same overridden one in the database.
+ $this->assertEqual($original_export->machine, $default_export->machine, 'The default exportable has been loaded and matches the overridden exportable.');
+ }
+
+ /**
+ * Tests CRUD operation: Delete.
+ */
+ function testCrudExportDelete() {
+ // Create a stub entry save it and delete it from the database.
+ $new_export = ctools_export_crud_new('ctools_export_test');
+ ctools_export_crud_save('ctools_export_test', $new_export);
+
+ $machine = $new_export->machine;
+ ctools_export_crud_delete('ctools_export_test', $new_export);
+ $result = ctools_export_crud_load('ctools_export_test', $machine);
+
+ $this->assertFalse($result, 'The new exportable has been removed from the database.');
+
+ // Load the database only exportable.
+ $database_export = ctools_export_crud_load('ctools_export_test', 'database_test');
+
+ $machine = $database_export->machine;
+ ctools_export_crud_delete('ctools_export_test', $database_export);
+ // Clear the exportable caches as it's been loaded above.
+ ctools_export_load_object_reset('ctools_export_test');
+ $result = ctools_export_crud_load('ctools_export_test', $machine);
+
+ $this->assertFalse($result, 'The database exportable has been removed from the database.');
+ }
+
+ /**
+ * Tests CRUD operation: Set status.
+ */
+ function testCrudExportSetStatus() {
+ // Database only object.
+ $database_export = ctools_export_crud_load('ctools_export_test', 'database_test');
+ ctools_export_crud_disable('ctools_export_test', $database_export);
+ ctools_export_load_object_reset('ctools_export_test');
+ $disabled_export = ctools_export_crud_load('ctools_export_test', 'database_test');
+
+ $this->assertTrue($disabled_export->disabled, 'The database only exportable has been disabled.');
+
+ ctools_export_crud_enable('ctools_export_test', $disabled_export);
+ ctools_export_load_object_reset('ctools_export_test');
+ $enabled_export = ctools_export_crud_load('ctools_export_test', 'database_test');
+
+ $this->assertTrue(empty($enabled_export->disabled), 'The database only exportable has been enabled.');
+
+ // Overridden object.
+ $overridden_export = ctools_export_crud_load('ctools_export_test', 'overridden_test');
+ ctools_export_crud_disable('ctools_export_test', $overridden_export);
+ ctools_export_load_object_reset('ctools_export_test');
+ $disabled_export = ctools_export_crud_load('ctools_export_test', 'overridden_test');
+
+ $this->assertTrue($disabled_export->disabled, 'The overridden exportable has been disabled.');
+
+ ctools_export_crud_enable('ctools_export_test', $disabled_export);
+ ctools_export_load_object_reset('ctools_export_test');
+ $enabled_export = ctools_export_crud_load('ctools_export_test', 'overridden_test');
+
+ $this->assertTrue(empty($enabled_export->disabled), 'The overridden exportable has been enabled.');
+
+ // Default object.
+ $default_export = ctools_export_crud_load('ctools_export_test', 'default_test');
+ ctools_export_crud_disable('ctools_export_test', $default_export);
+ ctools_export_load_object_reset('ctools_export_test');
+ $disabled_export = ctools_export_crud_load('ctools_export_test', 'default_test');
+
+ $this->assertTrue($disabled_export->disabled, 'The default exportable has been disabled.');
+
+ ctools_export_crud_enable('ctools_export_test', $disabled_export);
+ ctools_export_load_object_reset('ctools_export_test');
+ $enabled_export = ctools_export_crud_load('ctools_export_test', 'default_test');
+
+ $this->assertTrue(empty($enabled_export->disabled), 'The default exportable has been enabled.');
+ }
+
+}
diff --git a/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.default_ctools_export_tests.inc b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.default_ctools_export_tests.inc
new file mode 100644
index 000000000..9f2dd6c6b
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.default_ctools_export_tests.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Implements hook_default_export_tests().
+ */
+function ctools_export_test_default_ctools_export_tests() {
+ $ctools_export_tests = array();
+
+ $ctools_export_test = new stdClass();
+ $ctools_export_test->disabled = FALSE; /* Edit this to true to make a default export_test disabled initially */
+ $ctools_export_test->api_version = 1;
+ $ctools_export_test->machine = 'overridden_test';
+ $ctools_export_test->title = 'Overridden test';
+ $ctools_export_test->number = 1;
+ $ctools_export_test->data = array(
+ 'test_1' => 'Test 1',
+ 'test_2' => 'Test 2',
+ );
+ $ctools_export_tests['overridden_test'] = $ctools_export_test;
+
+ $ctools_export_test = new stdClass();
+ $ctools_export_test->disabled = FALSE; /* Edit this to true to make a default export_test disabled initially */
+ $ctools_export_test->api_version = 1;
+ $ctools_export_test->machine = 'default_test';
+ $ctools_export_test->title = 'Default test';
+ $ctools_export_test->number = 2;
+ $ctools_export_test->data = '';
+ $ctools_export_tests['default_test'] = $ctools_export_test;
+
+ return $ctools_export_tests;
+}
diff --git a/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info
new file mode 100644
index 000000000..e016aeb5d
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info
@@ -0,0 +1,16 @@
+name = CTools export test
+description = CTools export test module
+core = 7.x
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+dependencies[] = ctools
+hidden = TRUE
+
+files[] = ctools_export.test
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.install b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.install
new file mode 100644
index 000000000..2eb54ca39
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.install
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * Implements hook_schema();
+ */
+function ctools_export_test_schema() {
+ $schema['ctools_export_test'] = array(
+ 'description' => 'CTools export test data table',
+ 'export' => array(
+ 'key' => 'machine',
+ 'identifier' => 'ctools_export_test',
+ 'default hook' => 'default_ctools_export_tests',
+ 'bulk export' => TRUE,
+ 'api' => array(
+ 'owner' => 'ctools_export_test',
+ 'api' => 'default_ctools_export_tests',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+ 'fields' => array(
+ 'machine' => array(
+ 'description' => "The unique machine name (required by ctools).",
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'title' => array(
+ 'description' => "The human readable title.",
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'number' => array(
+ 'description' => "A number.",
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'data' => array(
+ 'type' => 'blob',
+ 'description' => "A serialized array of data.",
+ 'serialize' => TRUE,
+ 'serialized default' => 'a:0:{}',
+ ),
+ ),
+ 'primary key' => array('machine'),
+ );
+
+ return $schema;
+}
+
+/**
+ * Implments hook_install();
+ */
+function ctools_export_test_install() {
+ $ctools_export_tests = array();
+ // Put this default in the database only (no default).
+ $ctools_export_test = new stdClass();
+ $ctools_export_test->machine = 'database_test';
+ $ctools_export_test->title = 'Database test';
+ $ctools_export_test->number = 0;
+ $ctools_export_test->data = array(
+ 'test_1' => 'Test 1',
+ 'test_2' => 'Test 2',
+ );
+ $ctools_export_tests['database_test'] = $ctools_export_test;
+
+ // Put this default in the database, so we have this in code and in the database.
+ $ctools_export_test = new stdClass();
+ $ctools_export_test->machine = 'overridden_test';
+ $ctools_export_test->title = 'Overridden test';
+ $ctools_export_test->number = 1;
+ $ctools_export_test->data = array(
+ 'test_1' => 'Test 1',
+ 'test_2' => 'Test 2',
+ );
+ $ctools_export_tests['overridden_test'] = $ctools_export_test;
+
+ foreach ($ctools_export_tests as $ctools_export_test) {
+ // Save the record to the database.
+ drupal_write_record('ctools_export_test', $ctools_export_test);
+ }
+}
diff --git a/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.module b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.module
new file mode 100644
index 000000000..80f1adbb3
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.module
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function ctools_export_test_ctools_plugin_api($module, $api) {
+ if ($module == 'ctools_export_test' && $api == 'default_ctools_export_tests') {
+ return array('version' => 1);
+ }
+}
diff --git a/sites/all/modules/ctools/tests/ctools_plugin_test.info b/sites/all/modules/ctools/tests/ctools_plugin_test.info
new file mode 100644
index 000000000..15a510b62
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools_plugin_test.info
@@ -0,0 +1,20 @@
+name = Chaos tools plugins test
+description = Provides hooks for testing ctools plugins.
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+core = 7.x
+dependencies[] = ctools
+files[] = ctools.plugins.test
+files[] = object_cache.test
+files[] = css.test
+files[] = context.test
+files[] = math_expression.test
+files[] = math_expression_stack.test
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/tests/ctools_plugin_test.module b/sites/all/modules/ctools/tests/ctools_plugin_test.module
new file mode 100644
index 000000000..567f6e73d
--- /dev/null
+++ b/sites/all/modules/ctools/tests/ctools_plugin_test.module
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Define some plugin systems to test ctools plugin includes.
+ */
+
+/**
+ * Implementation of hook_ctools_plugin_dierctory()
+ */
+function ctools_plugin_test_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'ctools_plugin_test') {
+ return 'plugins/' . $plugin;
+ }
+}
+
+function ctools_plugin_test_ctools_plugin_type() {
+ return array(
+ 'extra_defaults' => array(
+ 'defaults' => array(
+ 'bool' => true,
+ 'string' => 'string',
+ 'array' => array('some value'),
+ ),
+ ),
+ 'cached' => array(
+ 'cache' => TRUE,
+ 'classes' => array(
+ 'handler',
+ ),
+ ),
+ 'not_cached' => array(
+ 'cache' => FALSE,
+ 'classes' => array(
+ 'handler',
+ ),
+ ),
+ 'big_hook_cached' => array(
+ 'cache' => TRUE,
+ 'use hooks' => TRUE,
+ 'classes' => array(
+ 'handler',
+ ),
+ ),
+ 'big_hook_not_cached' => array(
+ 'cache' => FALSE,
+ 'use hooks' => TRUE,
+ 'classes' => array(
+ 'handler',
+ ),
+ ),
+ );
+}
+
+function ctools_plugin_test_ctools_plugin_test_big_hook_cached() {
+ return array(
+ 'test1' => array(
+ 'function' => 'ctools_plugin_test_hook_cached_test',
+ 'handler' => 'class1',
+ ),
+ );
+}
+
+function ctools_plugin_test_ctools_plugin_test_big_hook_not_cached() {
+ return array(
+ 'test1' => array(
+ 'function' => 'ctools_plugin_test_hook_not_cached_test',
+ 'class' => 'class1',
+ ),
+ );
+}
+
+function ctools_plugin_test_hook_cached_test() {}
+function ctools_plugin_test_hook_not_cached_test() {}
diff --git a/sites/all/modules/ctools/tests/math_expression.test b/sites/all/modules/ctools/tests/math_expression.test
new file mode 100644
index 000000000..730e079a5
--- /dev/null
+++ b/sites/all/modules/ctools/tests/math_expression.test
@@ -0,0 +1,129 @@
+<?php
+
+/**
+ * @file
+ * Contains \CtoolsMathExpressionTestCase.
+ */
+
+/**
+ * Tests the MathExpression library of ctools.
+ */
+class CtoolsMathExpressionTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'CTools math expression tests',
+ 'description' => 'Test the math expression library of ctools.',
+ 'group' => 'Chaos Tools Suite',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('ctools', 'ctools_plugin_test');
+ }
+
+ /**
+ * Returns a random double between 0 and 1.
+ */
+ protected function rand01() {
+ return rand(0, PHP_INT_MAX) / PHP_INT_MAX;
+ }
+
+ /**
+ * A custom assertion with checks the values in a certain range.
+ */
+ protected function assertFloat($first, $second, $delta = 0.0000001, $message = '', $group = 'Other') {
+ return $this->assert(abs($first - $second) <= $delta, $message ? $message : t('Value @first is equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
+ }
+
+ public function testArithmetic() {
+ $math_expression = new ctools_math_expr();
+
+ // Test constant expressions.
+ $this->assertEqual($math_expression->e('2'), 2);
+ $random_number = rand(0, 10);
+ $this->assertEqual($random_number, $math_expression->e((string) $random_number));
+
+ // Test simple arithmetic.
+ $random_number_a = rand(5, 10);
+ $random_number_b = rand(5, 10);
+ $this->assertEqual($random_number_a + $random_number_b, $math_expression->e("$random_number_a + $random_number_b"));
+ $this->assertEqual($random_number_a - $random_number_b, $math_expression->e("$random_number_a - $random_number_b"));
+ $this->assertEqual($random_number_a * $random_number_b, $math_expression->e("$random_number_a * $random_number_b"));
+ $this->assertEqual($random_number_a / $random_number_b, $math_expression->e("$random_number_a / $random_number_b"));
+
+ // Test Associative property.
+ $random_number_c = rand(5, 10);
+ $this->assertEqual($math_expression->e("$random_number_a + ($random_number_b + $random_number_c)"), $math_expression->e("($random_number_a + $random_number_b) + $random_number_c"));
+ $this->assertEqual($math_expression->e("$random_number_a * ($random_number_b * $random_number_c)"), $math_expression->e("($random_number_a * $random_number_b) * $random_number_c"));
+
+ // Test Commutative property.
+ $this->assertEqual($math_expression->e("$random_number_a + $random_number_b"), $math_expression->e("$random_number_b + $random_number_a"));
+ $this->assertEqual($math_expression->e("$random_number_a * $random_number_b"), $math_expression->e("$random_number_b * $random_number_a"));
+
+ // Test Distributive property.
+ $this->assertEqual($math_expression->e("($random_number_a + $random_number_b) * $random_number_c"), $math_expression->e("($random_number_a * $random_number_c + $random_number_b * $random_number_c)"));
+
+ $this->assertEqual(pow($random_number_a, $random_number_b), $math_expression->e("$random_number_a ^ $random_number_b"));
+ }
+
+ public function testBuildInFunctions() {
+ $math_expression = new ctools_math_expr();
+
+ // @todo: maybe run this code multiple times to test different values.
+ $random_double = $this->rand01();
+ $random_int = rand(5, 10);
+ $this->assertFloat(sin($random_double), $math_expression->e("sin($random_double)"));
+ $this->assertFloat(cos($random_double), $math_expression->e("cos($random_double)"));
+ $this->assertFloat(tan($random_double), $math_expression->e("tan($random_double)"));
+ $this->assertFloat(exp($random_double), $math_expression->e("exp($random_double)"));
+ $this->assertFloat(sqrt($random_double), $math_expression->e("sqrt($random_double)"));
+ $this->assertFloat(log($random_double), $math_expression->e("ln($random_double)"));
+ $this->assertFloat(round($random_double), $math_expression->e("round($random_double)"));
+ $this->assertFloat(abs($random_double + $random_int), $math_expression->e('abs(' . ($random_double + $random_int) . ')'));
+ $this->assertEqual(round($random_double + $random_int), $math_expression->e('round(' . ($random_double + $random_int) . ')'));
+ $this->assertEqual(ceil($random_double + $random_int), $math_expression->e('ceil(' . ($random_double + $random_int) . ')'));
+ $this->assertEqual(floor($random_double + $random_int), $math_expression->e('floor(' . ($random_double + $random_int) . ')'));
+
+ // @fixme: you can't run time without an argument.
+ $this->assertFloat(time(), $math_expression->e('time(1)'));
+
+ $random_double_a = $this->rand01();
+ $random_double_b = $this->rand01();
+ // @fixme: max/min don't work at the moment.
+// $this->assertFloat(max($random_double_a, $random_double_b), $math_expression->e("max($random_double_a, $random_double_b)"));
+// $this->assertFloat(min($random_double_a, $random_double_b), $math_expression->e("min($random_double_a, $random_double_b)"));
+ }
+
+ public function testVariables() {
+ $math_expression = new ctools_math_expr();
+
+ $random_number_a = rand(5, 10);
+ $random_number_b = rand(5, 10);
+
+ // Store the first random number and use it on calculations.
+ $math_expression->e("var = $random_number_a");
+ $this->assertEqual($random_number_a + $random_number_b, $math_expression->e("var + $random_number_b"));
+ $this->assertEqual($random_number_a * $random_number_b, $math_expression->e("var * $random_number_b"));
+ $this->assertEqual($random_number_a - $random_number_b, $math_expression->e("var - $random_number_b"));
+ $this->assertEqual($random_number_a / $random_number_b, $math_expression->e("var / $random_number_b"));
+ }
+
+ public function testCustomFunctions() {
+ $math_expression = new ctools_math_expr();
+
+ $random_number_a = rand(5, 10);
+ $random_number_b = rand(5, 10);
+
+ // Create a one-argument function.
+ $math_expression->e("f(x) = 2 * x");
+ $this->assertEqual($random_number_a * 2, $math_expression->e("f($random_number_a)"));
+ $this->assertEqual($random_number_b * 2, $math_expression->e("f($random_number_b)"));
+
+ // Create a two-argument function.
+ $math_expression->e("g(x, y) = 2 * x + y");
+ $this->assertEqual($random_number_a * 2 + $random_number_b, $math_expression->e("g($random_number_a, $random_number_b)"));
+
+ // Use a custom function in another function.
+ $this->assertEqual(($random_number_a * 2 + $random_number_b) * 2, $math_expression->e("f(g($random_number_a, $random_number_b))"));
+ }
+}
diff --git a/sites/all/modules/ctools/tests/math_expression_stack.test b/sites/all/modules/ctools/tests/math_expression_stack.test
new file mode 100644
index 000000000..8143a55b8
--- /dev/null
+++ b/sites/all/modules/ctools/tests/math_expression_stack.test
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \CtoolsMathExpressionStackTestCase
+ */
+
+/**
+ * Tests the simple MathExpressionStack class.
+ */
+class CtoolsMathExpressionStackTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'CTools math expression stack tests',
+ 'description' => 'Test the stack class of the math expression library.',
+ 'group' => 'Chaos Tools Suite',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('ctools', 'ctools_plugin_test');
+ }
+
+ public function testStack() {
+ $stack = new ctools_math_expr_stack();
+
+ // Test the empty stack.
+ $this->assertNull($stack->last());
+ $this->assertNull($stack->pop());
+
+ // Add an element and see whether it's the right element.
+ $value = $this->randomName();
+ $stack->push($value);
+ $this->assertIdentical($value, $stack->last());
+ $this->assertIdentical($value, $stack->pop());
+ $this->assertNull($stack->pop());
+
+ // Add multiple elements and see whether they are returned in the right order.
+ $values = array($this->randomName(), $this->randomName(), $this->randomName());
+ foreach ($values as $value) {
+ $stack->push($value);
+ }
+
+ // Test the different elements at different positions with the last() method.
+ $count = count($values);
+ foreach ($values as $key => $value) {
+ $this->assertEqual($value, $stack->last($count - $key));
+ }
+
+ // Pass in a non-valid number to last.
+ $non_valid_number = rand(10, 20);
+ $this->assertNull($stack->last($non_valid_number));
+
+ // Test the order of the poping.
+ $values = array_reverse($values);
+ foreach ($values as $key => $value) {
+ $this->assertEqual($stack->last(), $value);
+ $this->assertEqual($stack->pop(), $value);
+ }
+ $this->assertNull($stack->pop());
+
+ }
+}
diff --git a/sites/all/modules/ctools/tests/object_cache.test b/sites/all/modules/ctools/tests/object_cache.test
new file mode 100644
index 000000000..8791d7e70
--- /dev/null
+++ b/sites/all/modules/ctools/tests/object_cache.test
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @file
+ * Tests for different parts of the ctools object caching system.
+ */
+
+/**
+ * Test object cache storage.
+ */
+class CtoolsObjectCache extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Ctools object cache storage',
+ 'description' => 'Verify that objects are written, readable and lockable.',
+ 'group' => 'Chaos Tools Suite',
+ );
+ }
+
+ public function setUp() {
+ // Additionally enable ctools module.
+ parent::setUp('ctools');
+ }
+
+ public function testObjectStorage() {
+ $account1 = $this->drupalCreateUser(array());
+ $this->drupalLogin($account1);
+
+ $data = array(
+ 'test1' => 'foobar',
+ );
+
+ ctools_include('object-cache');
+ ctools_object_cache_set('testdata', 'one', $data);
+ $this->assertEqual($data, ctools_object_cache_get('testdata', 'one'), 'Object cache data successfully stored');
+
+ // TODO Test object locking somehow.
+ // Object locking/testing works on session_id but simpletest uses
+ // $this->session_id so can't be tested ATM.
+
+ ctools_object_cache_clear('testdata', 'one');
+ $this->assertFalse(ctools_object_cache_get('testdata', 'one'), 'Object cache data successfully cleared');
+
+ // TODO Test ctools_object_cache_clear_all somehow...
+ // ctools_object_cache_clear_all requires session_id funtionality as well.
+ }
+}
diff --git a/sites/all/modules/ctools/tests/plugins/cached/ctoolsCachedPluginArray.class.php b/sites/all/modules/ctools/tests/plugins/cached/ctoolsCachedPluginArray.class.php
new file mode 100644
index 000000000..ea087fa63
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/cached/ctoolsCachedPluginArray.class.php
@@ -0,0 +1,7 @@
+<?php
+/**
+ * @file
+ * A cached plugin object that tests inheritance including.
+ */
+
+class ctoolsCachedPluginArray {}
diff --git a/sites/all/modules/ctools/tests/plugins/cached/ctoolsCachedPluginArray2.class.php b/sites/all/modules/ctools/tests/plugins/cached/ctoolsCachedPluginArray2.class.php
new file mode 100644
index 000000000..f774541ca
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/cached/ctoolsCachedPluginArray2.class.php
@@ -0,0 +1,7 @@
+<?php
+/**
+ * @file
+ * A cached plugin object that tests inheritance including.
+ */
+
+class ctoolsCachedPluginArray2 extends ctoolsCachedPluginArray {}
diff --git a/sites/all/modules/ctools/tests/plugins/cached/plugin_array.inc b/sites/all/modules/ctools/tests/plugins/cached/plugin_array.inc
new file mode 100644
index 000000000..40a4bf736
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/cached/plugin_array.inc
@@ -0,0 +1,20 @@
+<?php
+/**
+ * @file
+ * Chaos Tools plugin include using a plugin array to declare a plugin.
+ */
+
+/**
+ * Plugin array plugin definition.
+ */
+$plugin = array(
+ 'function' => 'ctools_plugin_test_plugin_array_cached_test',
+ 'handler' => array(
+ 'class' => 'ctoolsCachedPluginArray',
+ ),
+);
+
+/**
+ * Plugin array function plugin.
+ */
+function ctools_plugin_test_plugin_array_cached_test() {}
diff --git a/sites/all/modules/ctools/tests/plugins/cached/plugin_array2.inc b/sites/all/modules/ctools/tests/plugins/cached/plugin_array2.inc
new file mode 100644
index 000000000..e6676154d
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/cached/plugin_array2.inc
@@ -0,0 +1,20 @@
+<?php
+/**
+ * @file
+ * Chaos Tools plugin include using a plugin array to declare a plugin.
+ */
+
+/**
+ * Plugin array plugin definition.
+ */
+$plugin = array(
+ 'function' => 'ctools_plugin_test_plugin_array2_cached_test',
+ 'handler' => array(
+ 'class' => 'ctoolsCachedPluginArray2',
+ ),
+);
+
+/**
+ * Plugin array function plugin.
+ */
+function ctools_plugin_test_plugin_array2_cached_test() {}
diff --git a/sites/all/modules/ctools/tests/plugins/cached/plugin_array_dne.inc b/sites/all/modules/ctools/tests/plugins/cached/plugin_array_dne.inc
new file mode 100644
index 000000000..e3d3bfb90
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/cached/plugin_array_dne.inc
@@ -0,0 +1,15 @@
+<?php
+/**
+ * @file
+ * Chaos Tools plugin include using a plugin array to declare a plugin.
+ */
+
+/**
+ * Plugin array plugin definition.
+ */
+$plugin = array(
+ 'function' => 'ctools_plugin_test_plugin_array_dne_cached_test',
+ 'handler' => array(
+ 'class' => 'ctoolsCachedPluginArrayDNE',
+ ),
+);
diff --git a/sites/all/modules/ctools/tests/plugins/not_cached/ctoolsNotCachedPluginArray.class.php b/sites/all/modules/ctools/tests/plugins/not_cached/ctoolsNotCachedPluginArray.class.php
new file mode 100644
index 000000000..422a7b34c
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/not_cached/ctoolsNotCachedPluginArray.class.php
@@ -0,0 +1,7 @@
+<?php
+/**
+ * @file
+ * A cached plugin object that tests inheritance including.
+ */
+
+class ctoolsNotCachedPluginArray extends ctoolsNotCachedPluginArray2 {}
diff --git a/sites/all/modules/ctools/tests/plugins/not_cached/ctoolsNotCachedPluginArray2.class.php b/sites/all/modules/ctools/tests/plugins/not_cached/ctoolsNotCachedPluginArray2.class.php
new file mode 100644
index 000000000..d7687dd02
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/not_cached/ctoolsNotCachedPluginArray2.class.php
@@ -0,0 +1,7 @@
+<?php
+/**
+ * @file
+ * A cached plugin object that tests including.
+ */
+
+class ctoolsNotCachedPluginArray2 {}
diff --git a/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array.inc b/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array.inc
new file mode 100644
index 000000000..3f4539359
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array.inc
@@ -0,0 +1,20 @@
+<?php
+/**
+ * @file
+ * Chaos Tools plugin include using a plugin array to declare a plugin.
+ */
+
+/**
+ * Plugin array plugin definition.
+ */
+$plugin = array(
+ 'function' => 'ctools_plugin_test_plugin_array_not_cached_test',
+ 'handler' => array(
+ 'class' => 'ctoolsNotCachedPluginArray',
+ ),
+);
+
+/**
+ * Plugin array function plugin.
+ */
+function ctools_plugin_test_plugin_array_not_cached_test() {}
diff --git a/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array2.inc b/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array2.inc
new file mode 100644
index 000000000..06b817571
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array2.inc
@@ -0,0 +1,20 @@
+<?php
+/**
+ * @file
+ * Chaos Tools plugin include using a plugin array to declare a plugin.
+ */
+
+/**
+ * Plugin array plugin definition.
+ */
+$plugin = array(
+ 'function' => 'ctools_plugin_test_plugin_array2_not_cached_test',
+ 'handler' => array(
+ 'class' => 'ctoolsNotCachedPluginArray2',
+ ),
+);
+
+/**
+ * Plugin array function plugin.
+ */
+function ctools_plugin_test_plugin_array2_not_cached_test() {}
diff --git a/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array_dne.inc b/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array_dne.inc
new file mode 100644
index 000000000..1f19d2a44
--- /dev/null
+++ b/sites/all/modules/ctools/tests/plugins/not_cached/plugin_array_dne.inc
@@ -0,0 +1,15 @@
+<?php
+/**
+ * @file
+ * Chaos Tools plugin include using a plugin array to declare a plugin.
+ */
+
+/**
+ * Plugin array plugin definition.
+ */
+$plugin = array(
+ 'function' => 'ctools_plugin_test_plugin_array_dne_not_cached_test',
+ 'handler' => array(
+ 'class' => 'ctoolsNotCachedPluginArrayDNE',
+ ),
+);
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_block_legacy.png b/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_block_legacy.png
new file mode 100644
index 000000000..f2e07474a
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_block_legacy.png
Binary files differ
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_page.png b/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_page.png
new file mode 100644
index 000000000..27efe7c13
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_page.png
Binary files differ
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_page_legacy.png b/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_page_legacy.png
new file mode 100644
index 000000000..311733534
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/icon_views_page_legacy.png
Binary files differ
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views.inc
new file mode 100644
index 000000000..bb413d5ba
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views.inc
@@ -0,0 +1,556 @@
+<?php
+
+/**
+ * @file
+ * Content type plugin to expose all views as content.
+ */
+
+if (variable_get('ctools_content_all_views', TRUE)) {
+ $plugin = array(
+ 'title' => t('All views'),
+ 'defaults' => array(
+ 'override_pager_settings' => FALSE,
+ 'use_pager' => FALSE,
+ 'nodes_per_page' => 10,
+ 'pager_id' => 0,
+ 'offset' => 0,
+ 'more_link' => FALSE,
+ 'feed_icons' => FALSE,
+ 'panel_args' => FALSE,
+ 'link_to_view' => FALSE,
+ 'args' => '',
+ 'url' => '',
+ ),
+ 'add form' => array(
+ 'views_content_views_select_display' => t('Select display'),
+ 'views_content_views_content_type_edit_form' => array(
+ 'default' => TRUE, // put wrapper here, not on the previous form.
+ 'title' => t('Configure view'),
+ ),
+ ),
+ 'all contexts' => TRUE,
+ );
+}
+
+/**
+ * Return all content types available.
+ */
+function views_content_views_content_type_content_types($plugin) {
+ $types = array();
+ // It can be fairly intensive to calculate this, so let's cache this in the
+ // cache_views table. The nice thing there is that if views ever change, that
+ // table will always be cleared. Except for the occasional default view, so
+ // we must use the Views caching functions in order to respect Views caching
+ // settings.
+ views_include('cache');
+ $data = views_cache_get('views_content_all', TRUE);
+ if (!empty($data->data)) {
+ $types = $data->data;
+ }
+
+ if (empty($types)) {
+ $views = views_get_all_views();
+
+ foreach ($views as $view) {
+ if (empty($view->disabled)) {
+ $types[$view->name] = _views_content_views_content_type($view);
+ }
+ }
+
+ views_cache_set('views_content_all', $types, TRUE);
+ }
+
+ return $types;
+}
+
+/**
+ * Create the content type info array to give back to ctools for a given display.
+ */
+function _views_content_views_content_type($view) {
+ $title = $view->get_human_name();
+
+ $icon = 'icon_views_page_legacy.png';
+
+ return array(
+ 'view' => $view->name,
+ 'title' => $title,
+ 'icon' => $icon,
+ 'description' => filter_xss_admin($view->description),
+ 'category' => t('Views'),
+ );
+
+}
+
+/**
+ * Output function for the 'views' content type.
+ *
+ * Outputs a view based on the module and delta supplied in the configuration.
+ */
+function views_content_views_content_type_render($subtype, $conf, $panel_args, $contexts) {
+ if (!is_array($contexts)) {
+ $contexts = array($contexts);
+ }
+
+ $view = _views_content_views_update_conf($conf, $subtype);
+
+ if (empty($view) || !is_object($view) || empty($view->display_handler)) {
+ return;
+ }
+
+ if (!$view->display_handler->access($GLOBALS['user'])) {
+ return;
+ }
+
+ $arguments = explode('/', $_GET['q']);
+ $args = $conf['args'];
+
+ foreach ($arguments as $id => $arg) {
+ $args = str_replace("%$id", $arg, $args);
+ }
+
+ foreach ($panel_args as $id => $arg) {
+ if (is_string($arg)) {
+ $args = str_replace("@$id", $arg, $args);
+ }
+ }
+
+ $args = preg_replace(',/?(%\d|@\d),', '', $args);
+ $args = $args ? explode('/', $args) : array();
+
+ if ($conf['panel_args'] && is_array($panel_args)) {
+ $args = array_merge($panel_args, $args);
+ }
+
+ if (isset($conf['context']) && is_array($conf['context'])) {
+ foreach ($conf['context'] as $count => $context_info) {
+ if (!strpos($context_info, '.')) {
+ // old skool: support pre-converter contexts as well.
+ $cid = $context_info;
+ $converter = '';
+ }
+ else {
+ list($cid, $converter) = explode('.', $context_info, 2);
+ }
+ if (!empty($contexts[$cid])) {
+ $arg = ctools_context_convert_context($contexts[$cid], $converter, array('sanitize' => FALSE));
+ array_splice($args, $count, 0, array($arg));
+ }
+ else {
+ // Make sure we put an argument in even if it was not there.
+ $arg = NULL;
+ }
+ }
+ }
+
+ $view->set_arguments($args);
+
+ if ($conf['url']) {
+ $view->override_path = $conf['url'];
+ }
+
+ $block = new stdClass();
+ $block->module = 'views';
+ $block->delta = $view->name . '-' . $view->current_display;
+
+ if (!empty($conf['link_to_view'])) {
+ $block->title_link = $view->get_url();
+ }
+
+ if (!empty($conf['more_link'])) {
+ $block->more = array('href' => $view->get_url());
+ $view->display_handler->set_option('use_more', FALSE);
+ }
+
+ // Only set use_pager if they differ, this way we can avoid overwriting the
+ // pager type that Views uses.
+ if ($conf['override_pager_settings']) {
+ if (method_exists($view, 'init_pager')) {
+ // Views 3 version
+ $view->set_items_per_page($conf['nodes_per_page']);
+ $view->set_offset($conf['offset']);
+
+ $pager = $view->display_handler->get_option('pager');
+ if ($conf['use_pager'] && ($pager['type'] == 'none' || $pager['type'] == 'some')) {
+ $pager['type'] = 'full';
+ }
+ elseif (!$conf['use_pager'] && $pager['type'] != 'none' && $pager['type'] != 'some') {
+ $pager['type'] = $view->get_items_per_page() ? 'some' : 'none';
+ }
+
+ if ($conf['use_pager']) {
+ if (!isset($pager['options']['id']) || $pager['options']['id'] != $conf['pager_id']) {
+ $pager['options']['id'] = $conf['pager_id'];
+ }
+ }
+
+ $view->display_handler->set_option('pager', $pager);
+ }
+ else {
+ if (!$view->display_handler->get_option('use_pager') || empty($conf['use_pager'])) {
+ $view->display_handler->set_option('use_pager', $conf['use_pager']);
+ }
+ $view->display_handler->set_option('pager_element', $conf['pager_id']);
+ $view->display_handler->set_option('items_per_page', $conf['nodes_per_page']);
+ $view->display_handler->set_option('offset', $conf['offset']);
+ }
+ }
+
+ $stored_feeds = drupal_add_feed();
+ $block->content = $view->preview();
+ $block->title = $view->get_title();
+
+ if (empty($view->result) && !$view->display_handler->get_option('empty') && empty($view->style_plugin->definition['even empty'])) {
+ return;
+ }
+
+ if (!empty($conf['feed_icons'])) {
+ $new_feeds = drupal_add_feed();
+ if ($diff = array_diff(array_keys($new_feeds), array_keys($stored_feeds))) {
+ foreach ($diff as $url) {
+ $block->feeds[$url] = $new_feeds[$url];
+ }
+ }
+ }
+
+ $view->destroy();
+ return $block;
+}
+
+/**
+ * Returns an edit form for a block.
+ */
+function views_content_views_select_display($form, &$form_state) {
+ $view = views_get_view($form_state['subtype_name']);
+ if (empty($view)) {
+ return;
+ }
+
+ $displays = array();
+ foreach ($view->display as $id => $display) {
+ // Content pane views should never be used this way.
+ if ($display->display_plugin != 'panel_pane') {
+ $displays[$id] = views_content_get_display_label($view, $id);
+ }
+ }
+
+ $form['display'] = array(
+ '#type' => 'select',
+ '#title' => t('Display'),
+ '#options' => $displays,
+ '#description' => t('Choose which display of this view you wish to use.')
+ );
+
+ return $form;
+}
+
+/**
+ * Submit the basic view edit form.
+ *
+ * This just dumps everything into the $conf array.
+ */
+function views_content_views_select_display_submit(&$form, &$form_state) {
+ $form_state['conf']['display'] = $form_state['values']['display'];
+}
+
+/**
+ * Returns an edit form for a block.
+ */
+function views_content_views_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $view = _views_content_views_update_conf($conf, $form_state['subtype_name']);
+
+ if (empty($view) || !is_object($view)) {
+ $form['markup'] = array('#value' => t('Broken/missing/deleted view.'));
+ return;
+ }
+
+ $form_state['title'] = t('Configure view @view (@display)', array('@view' => $view->get_human_name(), '@display' => views_content_get_display_label($view, $view->current_display)));
+
+ // @todo
+ // If using the older format, just a context is listed. We should go through
+ // and check for that and forcibly set them to the right converter so that
+ // it doesn't get changed to some whacky default. Oooor just let it get changed
+ // to 'no context', I suppose.
+
+ $required = array();
+ if (isset($view->display_handler) && $arguments = $view->display_handler->get_handlers('argument')) {
+ foreach ($arguments as $arg) {
+ $required[] = new ctools_context_optional($arg->ui_name(), 'any');
+ }
+ }
+
+ if ($required) {
+ $form['context'] = ctools_context_converter_selector($form_state['contexts'], $required, isset($conf['context']) ? $conf['context'] : array());
+ }
+
+ $form['link_to_view'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link_to_view'],
+ '#title' => t('Link title to view'),
+ );
+
+ $form['more_link'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['more_link'],
+ '#title' => t('Provide a "more" link that links to the view'),
+ '#description' => t('This is independent of any more link that may be provided by the view itself; if you see two more links, turn this one off. Views will only provide a more link if using the "block" type, however, so if using embed, use this one.'),
+ );
+
+ $form['feed_icons'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['feed_icons'],
+ '#title' => t('Display feed icons'),
+ );
+
+ $form['pager_settings'] = array(
+ '#type' => 'fieldset',
+ '#collapsible' => FALSE,
+ '#title' => t('Custom pager settings'),
+ );
+
+ $form['pager_settings']['override_pager_settings'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use different pager settings from view settings'),
+ '#default_value' => $conf['override_pager_settings'],
+ '#id' => 'override-pager-checkbox',
+ );
+
+ if ($view->display_handler->get_option('use_ajax')) {
+ $form['pager_settings']['warning'] = array(
+ '#value' => '<div>' . t('<strong>Warning: </strong> This view has AJAX enabled. Overriding the pager settings will work initially, but when the view is updated via AJAX, the original settings will be used. You should not override pager settings on Views with the AJAX setting enabled.') . '</div>',
+ );
+ }
+
+ $form['pager_settings']['use_pager'] = array(
+ '#prefix' => '<div class="container-inline">',
+ '#type' => 'checkbox',
+ '#title' => t('Use pager'),
+ '#default_value' => $conf['use_pager'],
+ '#id' => 'use-pager-checkbox',
+ '#dependency' => array('override-pager-checkbox' => array(1)),
+ );
+ $form['pager_settings']['pager_id'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['pager_id'],
+ '#title' => t('Pager ID'),
+ '#size' => 4,
+ '#id' => 'use-pager-textfield',
+ '#dependency' => array('override-pager-checkbox' => array(1), 'use-pager-checkbox' => array(1)),
+ '#dependency_count' => 2,
+ '#suffix' => '</div>',
+ );
+
+ $form['pager_settings']['nodes_per_page'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['nodes_per_page'],
+ '#size' => 4,
+ '#title' => t('Num posts'),
+ '#dependency' => array('override-pager-checkbox' => array(1)),
+ );
+
+ $form['pager_settings']['offset'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['offset'],
+ '#title' => t('Offset'),
+ '#size' => 4,
+ '#description' => t('The number of items to skip and not display.'),
+ '#dependency' => array('override-pager-checkbox' => array(1)),
+ );
+
+ $form['panel_args'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Send arguments'),
+ '#default_value' => $conf['panel_args'],
+ '#description' => t('Select this to send all arguments from the panel directly to the view. If checked, the panel arguments will come after any context arguments above and precede any additional arguments passed in through the Arguments field below. Note that arguments do not include the base URL; only values after the URL or set as placeholders are considered arguments.'),
+ );
+
+ $form['args'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['args'],
+ '#title' => t('Arguments'),
+ '#size' => 30,
+ '#description' => t('Additional arguments to send to the view as if they were part of the URL in the form of arg1/arg2/arg3. You may use %0, %1, ..., %N to grab arguments from the URL. Or use @0, @1, @2, ..., @N to use arguments passed into the panel. Note: use these values only as a last resort. In future versions of Panels these may go away.'),
+ );
+
+ $form['url'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['url'],
+ '#title' => t('Override URL'),
+ '#size' => 30,
+ '#description' => t('If this is set, override the View URL; this can sometimes be useful to set to the panel URL'),
+ );
+
+ $view->destroy();
+ return $form;
+}
+
+/**
+ * Store form values in $conf.
+ */
+function views_content_views_content_type_edit_form_submit(&$form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_content_type_admin_title($subtype, $conf) {
+ $view = _views_content_views_update_conf($conf, $subtype);
+
+ if (!is_object($view)) {
+ return t('Deleted/missing view @view', array('@view' => $view));
+ }
+
+ $title = views_content_get_display_label($view, $view->current_display);
+ return t('View: @name', array('@name' => $view->get_human_name() . ': ' . $title));
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_content_type_admin_info($subtype, $conf, $contexts) {
+ $view = _views_content_views_update_conf($conf, $subtype);
+
+ if (!is_object($view)) {
+ return t('Deleted/missing view @view', array('@view' => $view));
+ }
+
+ $display = empty($conf['display']) ? $view->current_display : $conf['display'];
+ $block = new stdClass();
+
+ $block->title = t('View information');
+
+ $block->content = '<ul>';
+ $block->content .= '<li>' . t('Using display @display.', array('@display' => views_content_get_display_label($view, $display))) . '</li>';
+
+ if (!empty($conf['context']) && $arguments = $view->display_handler->get_handlers('argument')) {
+ $argument = reset($arguments);
+ foreach ($conf['context'] as $count => $context_info) {
+ if (!$argument) {
+ break;
+ }
+
+ if (!strpos($context_info, '.')) {
+ // old skool: support pre-converter contexts as well.
+ $cid = $context_info;
+ $converter = '';
+ }
+ else {
+ list($cid, $converter) = explode('.', $context_info, 2);
+ }
+
+ if (!empty($contexts[$cid])) {
+ $converters = ctools_context_get_converters($cid . '.', $contexts[$cid]);
+ $converter = !empty($converters[$context_info]) ? $converters[$context_info] : t('Default');
+ $block->content .= '<li>' . t('Argument @arg using context @context converted into @converter', array(
+ '@arg' => $argument->ui_name(), '@context' => $contexts[$cid]->get_identifier(),
+ '@converter' => $converter)) . '</li>';
+ }
+ $argument = next($arguments);
+ }
+ }
+
+ $block->content .= '<li>' . t('@count items displayed.', array('@count' => $conf['nodes_per_page'])) . '</li>';
+ if ($conf['use_pager']) {
+ $block->content .= '<li>' . t('With pager.') . '</li>';
+ }
+ else {
+ $block->content .= '<li>' . t('Without pager.') . '</li>';
+ }
+
+ if ($conf['offset']) {
+ $block->content .= '<li>' . t('Skipping first @count results', array('@count' => $conf['offset'])) . '</li>';
+ }
+ if ($conf['more_link']) {
+ $block->content .= '<li>' . t('With more link.') . '</li>';
+ }
+ if ($conf['feed_icons']) {
+ $block->content .= '<li>' . t('With feed icon.') . '</li>';
+ }
+ if ($conf['panel_args']) {
+ $block->content .= '<li>' . t('Sending arguments.') . '</li>';
+ }
+ if ($conf['args']) {
+ $block->content .= '<li>' . t('Using arguments: @args', array('@args' => $conf['args'])) . '</li>';
+ }
+ if ($conf['url']) {
+ $block->content .= '<li>' . t('Using url: @url', array('@url' => $conf['url'])) . '</li>';
+ }
+
+ $view->destroy();
+ return $block;
+}
+
+/**
+ * Update the $conf to deal with updates from Drupal 5.
+ *
+ * @param &$conf
+ * The $conf array to modify.
+ * @param $subtype
+ * The subtype in use. This should just be the view name, but in older
+ * versions it was the view name with a dash and the display ID.
+ * If this is the case, we can use it to correct the 'display' setting
+ * in the $conf.
+ * @return
+ * The $view with the initialized display. If the $view could not be
+ * loaded, the name attempted will be loaded for use in errors.
+ * Correct error checking on this function checks against is_object().
+ */
+function _views_content_views_update_conf(&$conf, $subtype) {
+ $plugin = ctools_get_content_type('views');
+
+ // Special: Existing content types get a different default than new ones:
+ if (!empty($conf) && !isset($conf['override_pager_settings'])) {
+ $conf['override_pager_settings'] = TRUE;
+ }
+
+ // Make sure that our defaults are always set if there is no
+ // previous setting. This helps updates go more smoothly.
+ foreach ($plugin['defaults'] as $key => $value) {
+ if (!isset($conf[$key])) {
+ $conf[$key] = $value;
+ }
+ }
+
+ if (strpos($subtype, '-')) {
+ list($name, $display) = explode('-', $subtype);
+ $view = views_get_view($name);
+ if (!isset($conf['display'])) {
+ $conf['display'] = $display;
+ }
+ }
+ else {
+ $name = $subtype;
+ $view = views_get_view($subtype);
+ $display = isset($conf['display']) ? $conf['display'] : 'default';
+ }
+
+ if (empty($view)) {
+ return $name;
+ }
+
+ $view->set_display($display);
+ // $view->current_display will now reflect this value.
+
+ // If set NOT to override, go ahead and refresh from the view.
+ if (empty($conf['override_pager_settings'])) {
+ if (method_exists($view, 'init_pager')) {
+ $pager = $view->display_handler->get_option('pager');
+ $conf['use_pager'] = $pager['type'] != 'none' && $pager['type'] != 'some';
+ $conf['pager_id'] = isset($pager['options']['id']) ? $pager['options']['id'] : 0;
+ $conf['offset'] = isset($pager['options']['offset']) ? $pager['options']['offset'] : 0;
+ $conf['nodes_per_page'] = isset($pager['options']['items_per_page']) ? $pager['options']['items_per_page'] : 0;
+ }
+ else {
+ $conf['use_pager'] = $view->display_handler->get_option('use_pager');
+ $conf['pager_id'] = $view->display_handler->get_option('element_id');
+ $conf['nodes_per_page'] = $view->display_handler->get_option('items_per_page');
+ $conf['offset'] = $view->display_handler->get_option('offset');
+ }
+ }
+
+ return $view;
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_attachments.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_attachments.inc
new file mode 100644
index 000000000..e938f539f
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_attachments.inc
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to display its attachment(s).
+ */
+
+$plugin = array(
+ 'title' => t('View attachment'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display the attachments on a view context.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+ 'defaults' => array(
+ 'which' => array(),
+ ),
+);
+
+/**
+ * Render the node_terms content type.
+ */
+function views_content_views_attachments_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_attachments';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ $output = views_content_context_get_output($context);
+ foreach ($conf['which'] as $attachment) {
+ if (isset($output[$attachment])) {
+ $block->content .= $output[$attachment];
+ }
+ }
+
+ return $block;
+}
+
+
+function views_content_views_attachments_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['which'] = array(
+ '#type' => 'checkboxes',
+ '#options' => array(
+ 'attachment_before' => t('"Before" attachment'),
+ 'attachment_after' => t('"After" attachment'),
+ ),
+ '#default_value' => $conf['which'],
+ );
+
+ return $form;
+}
+
+function views_content_views_attachments_content_type_edit_form_validate(&$form, &$form_state) {
+ if (!array_filter($form_state['values']['which'])) {
+ form_error($form['which'], t('You must select at least one attachment to display.'));
+ }
+}
+
+function views_content_views_attachments_content_type_edit_form_submit(&$form, &$form_state) {
+ $form_state['conf']['which'] = array_filter($form_state['values']['which']);
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_attachments_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@context" attachment', array('@context' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_empty.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_empty.inc
new file mode 100644
index 000000000..4664d65d3
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_empty.inc
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to display its attachment(s).
+ */
+
+$plugin = array(
+ 'title' => t('View empty text'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display the view empty text if there are no results.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+);
+
+/**
+ * Render the node_terms content type.
+ */
+function views_content_views_empty_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_empty';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ $output = views_content_context_get_output($context);
+ if (isset($output['empty'])) {
+ $block->content = $output['empty'];
+ }
+
+ return $block;
+}
+
+function views_content_views_empty_content_type_edit_form($form, &$form_state) {
+ // This form does nothing; it exists to let the main form select the view context.
+ return $form;
+}
+
+function views_content_views_empty_content_type_edit_form_submit(&$form, &$form_state) {
+ // Kept so we guarantee we have a submit handler.
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_empty_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@context" empty text', array('@context' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_exposed.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_exposed.inc
new file mode 100644
index 000000000..93371e21f
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_exposed.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to display its attachment(s).
+ */
+
+$plugin = array(
+ 'title' => t('View exposed widgets'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display the view exposed widgets if there are no results.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+);
+
+/**
+ * Render the node_terms content type.
+ */
+function views_content_views_exposed_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_exposed';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ $output = views_content_context_get_output($context);
+ $block->content = $output['exposed'];
+
+ return $block;
+}
+
+function views_content_views_exposed_content_type_edit_form($form, &$form_state) {
+ // This form does nothing; it exists to let the main form select the view context.
+ return $form;
+}
+
+function views_content_views_exposed_content_type_edit_form_submit(&$form, &$form_state) {
+ // Kept so we guarantee we have a submit handler.
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_exposed_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@context" exposed widgets', array('@context' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_feed.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_feed.inc
new file mode 100644
index 000000000..f2fdb8af5
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_feed.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to display its attachment(s).
+ */
+
+$plugin = array(
+ 'title' => t('View feed icon'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display the view feed icon if there are no results.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+);
+
+/**
+ * Render the views feed content type.
+ */
+function views_content_views_feed_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_feed';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ $output = views_content_context_get_output($context);
+ $block->content = $output['feed_icon'];
+
+ return $block;
+}
+
+function views_content_views_feed_content_type_edit_form($form, &$form_state) {
+ // This form does nothing; it exists to let the main form select the view context.
+ return $form;
+}
+
+function views_content_views_feed_content_type_edit_form_submit(&$form, &$form_state) {
+ // Kept so we guarantee we have a submit handler.
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_feed_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@context" feed icon', array('@context' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_footer.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_footer.inc
new file mode 100644
index 000000000..81c87a3ab
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_footer.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to display its attachment(s).
+ */
+
+$plugin = array(
+ 'title' => t('View footer'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display the view footer if there are no results.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+);
+
+/**
+ * Render the node_terms content type.
+ */
+function views_content_views_footer_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_footer';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ $output = views_content_context_get_output($context);
+ $block->content = $output['footer'];
+
+ return $block;
+}
+
+function views_content_views_footer_content_type_edit_form($form, &$form_state) {
+ // This form does nothing; it exists to let the main form select the view context.
+ return $form;
+}
+
+function views_content_views_footer_content_type_edit_form_submit(&$form, &$form_state) {
+ // Kept so we guarantee we have a submit handler.
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_footer_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@context" footer', array('@context' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_header.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_header.inc
new file mode 100644
index 000000000..f144d29fa
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_header.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to display its attachment(s).
+ */
+
+$plugin = array(
+ 'title' => t('View header'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display the view header if there are no results.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+);
+
+/**
+ * Render the node_terms content type.
+ */
+function views_content_views_header_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_header';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ $output = views_content_context_get_output($context);
+ $block->content = $output['header'];
+
+ return $block;
+}
+
+function views_content_views_header_content_type_edit_form($form, &$form_state) {
+ // This form does nothing; it exists to let the main form select the view context.
+ return $form;
+}
+
+function views_content_views_header_content_type_edit_form_submit(&$form, &$form_state) {
+ // Kept so we guarantee we have a submit handler.
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_header_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@context" header', array('@context' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_pager.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_pager.inc
new file mode 100644
index 000000000..faa65692b
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_pager.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to display its attachment(s).
+ */
+
+$plugin = array(
+ 'title' => t('View pager'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display the view pager if there are no results.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+);
+
+/**
+ * Render the node_terms content type.
+ */
+function views_content_views_pager_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_pager';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ $output = views_content_context_get_output($context);
+ $block->content = $output['pager'];
+
+ return $block;
+}
+
+function views_content_views_pager_content_type_edit_form($form, &$form_state) {
+ // This form does nothing; it exists to let the main form select the view context.
+ return $form;
+
+}
+
+function views_content_views_pager_content_type_edit_form_submit(&$form, &$form_state) {
+ // Kept so we guarantee we have a submit handler.
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_pager_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@context" pager', array('@context' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc
new file mode 100644
index 000000000..e4dded23a
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc
@@ -0,0 +1,634 @@
+<?php
+
+/**
+ * @file
+ * Content type plugin to allow Views to be exposed as a display type,
+ * leaving most of the configuration on the view.
+ */
+
+/**
+ * Implements hook_ctools_content_types()
+ */
+function views_content_views_panes_ctools_content_types() {
+ return array(
+ 'title' => t('View panes'),
+ 'admin settings' => 'views_content_admin_form',
+ 'all contexts' => TRUE,
+ );
+}
+
+/**
+ * Return all content types available.
+ */
+function views_content_views_panes_content_type_content_types($plugin) {
+ $types = array();
+ // It can be fairly intensive to calculate this, so let's cache this in the
+ // cache_views table. The nice thing there is that if views ever change, that
+ // table will always be cleared. Except for the occasional default view, so
+ // we must use the Views caching functions in order to respect Views caching
+ // settings.
+ views_include('cache');
+ $data = views_cache_get('views_content_panes', TRUE);
+ if (!empty($data->data)) {
+ $types = $data->data;
+ }
+
+ if (empty($types)) {
+ $types = array();
+
+ $views = views_get_all_views();
+
+ foreach ($views as $view) {
+ if (!empty($view->disabled)) {
+ continue;
+ }
+
+ $view->init_display();
+
+ foreach ($view->display as $id => $display) {
+ if (empty($display->handler->panel_pane_display)) {
+ continue;
+ }
+ $info = _views_content_panes_content_type($view, $display);
+ if ($info) {
+ $types[$view->name . '-' . $id] = $info;
+ }
+ }
+
+ $view->destroy();
+ }
+ views_cache_set('views_content_panes', $types, TRUE);
+ }
+
+ return $types;
+}
+
+/**
+ * Return a single content type.
+ */
+function views_content_views_panes_content_type_content_type($subtype, $plugin) {
+ list($name, $display) = explode('-', $subtype);
+ $view = views_get_view($name);
+ if (empty($view)) {
+ return;
+ }
+
+ $view->set_display($display);
+ $retval = _views_content_panes_content_type($view, $view->display[$display]);
+
+ $view->destroy();
+ return $retval;
+}
+
+function _views_content_panes_content_type($view, $display) {
+ // Ensure the handler is the right type, as Views will fall back to
+ // the default display if something is broken:
+ if (!is_a($display->handler, 'views_content_plugin_display_panel_pane')) {
+ return;
+ }
+
+ $title = views_content_get_display_title($view, $display->id);
+
+ $description = $display->handler->get_option('pane_description');
+ if (!$description) {
+ $description = $view->description;
+ }
+
+ $category = $display->handler->get_option('pane_category');
+ if (!$category['name']) {
+ $category['name'] = t('View panes');
+ }
+
+ $icon = 'icon_views_page.png';
+
+ $contexts = array();
+
+ $arguments = $display->handler->get_argument_input();
+ ctools_include('views');
+ foreach ($arguments as $argument) {
+ $contexts[] = ctools_views_get_argument_context($argument);
+ }
+
+ $allow = $display->handler->get_option('allow');
+ return array(
+ 'title' => $title,
+ 'icon' => $icon,
+ 'description' => filter_xss_admin($description),
+ 'required context' => $contexts,
+ 'category' => array($category['name'], $category['weight']),
+ 'no title override' => empty($allow['title_override']),
+ );
+}
+
+/**
+ * Output function for the 'views' content type.
+ *
+ * Outputs a view based on the module and delta supplied in the configuration.
+ */
+function views_content_views_panes_content_type_render($subtype, $conf, $panel_args, $contexts) {
+ if (!is_array($contexts)) {
+ $contexts = array($contexts);
+ }
+
+ list($name, $display) = explode('-', $subtype);
+ $view = views_get_view($name);
+ if (empty($view)) {
+ return;
+ }
+
+ $view->set_display($display);
+ views_content_views_panes_add_defaults($conf, $view);
+
+ if (!$view->display_handler->access($GLOBALS['user']) || empty($view->display_handler->panel_pane_display)) {
+ return;
+ }
+
+ $view->display_handler->set_pane_conf($conf);
+
+ $args = array();
+ $arguments = $view->display_handler->get_option('arguments');
+
+ $context_keys = isset($conf['context']) ? $conf['context'] : array();
+ foreach ($view->display_handler->get_argument_input() as $id => $argument) {
+ switch ($argument['type']) {
+ case 'context':
+ $key = array_shift($context_keys);
+ if (isset($contexts[$key])) {
+ if (strpos($argument['context'], '.')) {
+ list($context, $converter) = explode('.', $argument['context'], 2);
+ $args[] = ctools_context_convert_context($contexts[$key], $converter, array('sanitize' => FALSE));
+ }
+ else {
+ $args[] = $contexts[$key]->argument;
+ }
+ }
+ else {
+ $args[] = isset($arguments[$id]['exception']['value']) ? $arguments[$id]['exception']['value'] : 'all';
+ }
+ break;
+
+ case 'fixed':
+ $args[] = $argument['fixed'];
+ break;
+
+ case 'panel':
+ $args[] = isset($panel_args[$argument['panel']]) ? $panel_args[$argument['panel']] : NULL;
+ break;
+
+ case 'user':
+ $args[] = (isset($conf['arguments'][$id]) && $conf['arguments'][$id] !== '') ? ctools_context_keyword_substitute($conf['arguments'][$id], array(), $contexts) : NULL;
+ break;
+
+ case 'wildcard':
+ // Put in the wildcard.
+ $args[] = isset($arguments[$id]['wildcard']) ? $arguments[$id]['wildcard'] : '*';
+ break;
+
+ case 'none':
+ default:
+ // Put in NULL.
+ // views.module knows what to do with NULL (or missing) arguments
+ $args[] = NULL;
+ break;
+ }
+ }
+
+ // remove any trailing NULL arguments as these are non-args:
+ while (count($args) && end($args) === NULL) {
+ array_pop($args);
+ }
+
+ $view->set_arguments($args);
+
+ $allow = $view->display_handler->get_option('allow');
+
+ if (!empty($conf['path'])) {
+ $conf['path'] = ctools_context_keyword_substitute($conf['path'], array(), $contexts);
+ }
+ if ($allow['path_override'] && !empty($conf['path'])) {
+ $view->override_path = $conf['path'];
+ }
+ else if ($path = $view->display_handler->get_option('inherit_panels_path')) {
+ $view->override_path = $_GET['q'];
+ }
+
+ $block = new stdClass();
+ $block->module = 'views';
+ $block->delta = $view->name . '-' . $display;
+
+ if (($allow['link_to_view'] && !empty($conf['link_to_view'])) ||
+ (!$allow['link_to_view'] && $view->display_handler->get_option('link_to_view'))) {
+ $block->title_link = $view->get_url();
+ }
+
+ // more link
+ if ($allow['more_link']) {
+ if (empty($conf['more_link'])) {
+ $view->display_handler->set_option('use_more', FALSE);
+ }
+ else {
+ $view->display_handler->set_option('use_more', TRUE);
+ // make sure the view runs the count query so we know whether or not the
+ // more link applies.
+ $view->get_total_rows = TRUE;
+ }
+ }
+
+ if ($allow['items_per_page'] && isset($conf['items_per_page'])) {
+ $view->set_items_per_page($conf['items_per_page']);
+ }
+
+ if ($allow['offset']) {
+ $view->set_offset($conf['offset']);
+ }
+
+ if ($allow['use_pager']) {
+ // Only set use_pager if they differ, this way we can avoid overwriting the
+ // pager type that Views uses.
+ $pager = $view->display_handler->get_option('pager');
+ if ($conf['use_pager'] && ($pager['type'] == 'none' || $pager['type'] == 'some')) {
+ $pager['type'] = 'full';
+ }
+ elseif (!$conf['use_pager'] && $pager['type'] != 'none' && $pager['type'] != 'some') {
+ $pager['type'] = $view->get_items_per_page() || !empty($pager['options']['items_per_page']) ? 'some' : 'none';
+ }
+
+ if ($conf['use_pager']) {
+ if (!isset($pager['options']['id']) || (isset($conf['pager_id']) && $pager['options']['id'] != $conf['pager_id'])) {
+ $pager['options']['id'] = (int) $conf['pager_id'];
+ }
+ }
+
+ $view->display_handler->set_option('pager', $pager);
+ }
+
+ if ($allow['fields_override']) {
+ if ($conf['fields_override']) {
+ $fields = $view->get_items('field');
+ foreach ($fields as $field => $field_display) {
+ $fields[$field]['exclude'] = empty($conf['fields_override'][$field]);
+ }
+ $view->display_handler->set_option('fields', $fields);
+
+ }
+ }
+
+ if ($allow['exposed_form'] && !empty($conf['exposed'])) {
+ foreach ($conf['exposed'] as $filter_name => $filter_value) {
+ if (!is_array($filter_value)) {
+ $conf['exposed'][$filter_name] = ctools_context_keyword_substitute($filter_value, array(), $contexts);
+ }
+ }
+ $view->set_exposed_input($conf['exposed']);
+ }
+
+ $stored_feeds = drupal_add_feed();
+
+ $block->content = $view->preview();
+ if (empty($view->result) && !$view->display_handler->get_option('empty') && empty($view->style_plugin->definition['even empty'])) {
+ return;
+ }
+
+ // Add contextual links to the output.
+ $block = (array) $block;
+ views_add_block_contextual_links($block, $view, $display, 'panel_pane');
+ $block = (object) $block;
+
+ $block->title = $view->get_title();
+
+ if (empty($view->total_rows) || $view->total_rows <= $view->get_items_per_page()) {
+ unset($block->more);
+ }
+
+ if ((!empty($allow['feed_icons']) && !empty($conf['feed_icons'])) ||
+ (empty($allow['feed_icons']) && $view->display_handler->get_option('feed_icons'))) {
+ $new_feeds = drupal_add_feed();
+ if ($diff = array_diff(array_keys($new_feeds), array_keys($stored_feeds))) {
+ foreach ($diff as $url) {
+ $block->feeds[$url] = $new_feeds[$url];
+ }
+ }
+ }
+
+ return $block;
+}
+
+/**
+ * Add defaults to view pane settings.
+ * This helps cover us if $allow settings changed and there are no actual
+ * settings for a particular item.
+ */
+function views_content_views_panes_add_defaults(&$conf, $view) {
+ $pager = $view->display_handler->get_option('pager');
+
+ $conf += array(
+ 'link_to_view' => $view->display_handler->get_option('link_to_view'),
+ 'more_link' => $view->display_handler->get_option('use_more'),
+ 'feed_icons' => FALSE,
+ 'use_pager' => $pager['type'] != 'none' && $pager['type'] != 'some',
+ 'pager_id' => isset($pager['options']['id']) ? $pager['options']['id'] : 0,
+ 'items_per_page' => !empty($pager['options']['items_per_page']) ? $pager['options']['items_per_page'] : 10,
+ 'offset' => !empty($pager['options']['offset']) ? $pager['options']['offset'] : 0,
+ 'path_override' => FALSE,
+ 'path' => $view->get_path(),
+ 'fields_override' => $view->display_handler->get_option('fields_override'),
+ );
+}
+
+/**
+ * Returns an edit form for a block.
+ */
+function views_content_views_panes_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $contexts = $form_state['contexts'];
+ // This allows older content to continue to work, where we used to embed
+ // the display directly.
+ list($name, $display_id) = explode('-', $form_state['subtype_name']);
+ $view = views_get_view($name);
+
+ if (empty($view)) {
+ $form['markup'] = array('#markup' => t('Broken/missing/deleted view.'));
+ return $form;
+ }
+
+ $view->set_display($display_id);
+
+ // If it couldn't set the display and we got the default display instead,
+ // fail.
+ if ($view->current_display == 'default') {
+ $form['markup'] = array('#markup' => t('Broken/missing/deleted view display.'));
+ return $form;
+ }
+
+ $allow = $view->display_handler->get_option('allow');
+
+ // Provide defaults for everything in order to prevent warnings.
+ views_content_views_panes_add_defaults($conf, $view);
+
+ $form['arguments']['#tree'] = TRUE;
+
+ foreach ($view->display_handler->get_argument_input() as $id => $argument) {
+ if ($argument['type'] == 'user') {
+ $form['arguments'][$id] = array(
+ '#type' => 'textfield',
+ '#default_value' => isset($conf['arguments'][$id]) ? $conf['arguments'][$id] : '',
+ '#description' => t('You may use keywords for substitutions.'),
+ '#title' => check_plain($argument['label']),
+ );
+ }
+ }
+ if ($allow['link_to_view'] ) {
+ $form['link_to_view'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => isset($conf['link_to_view']) ? $conf['link_to_view'] : $view->display_handler->get_option('link_to_view'),
+ '#title' => t('Link title to page'),
+ );
+ }
+ if ($allow['more_link']) {
+ $form['more_link'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => isset($conf['more_link']) ? $conf['more_link'] : $view->display_handler->get_option('use_more'),
+ '#description' => t('The text of this link will be "@more". This setting can only be modified on the View configuration.', array('@more' => $view->display_handler->use_more_text())),
+ '#title' => t('Provide a "more" link.'),
+ );
+ }
+
+ if (!empty($allow['feed_icons'])) {
+ $form['feed_icons'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['feed_icons']),
+ '#title' => t('Display feed icons'),
+ );
+ }
+
+ $view->init_style();
+ if ($allow['fields_override'] && $view->style_plugin->uses_fields()) {
+ $form['fields_override'] = array(
+ '#type' => 'fieldset',
+ '#title' => 'Fields to display',
+ '#collapsible' => TRUE,
+ '#tree' => TRUE,
+ );
+ foreach ($view->display_handler->get_handlers('field') as $field => $handler) {
+ $title = $handler->ui_name();
+ if ($handler->options['label']) {
+ $title .= ' (' . check_plain($handler->options['label']) . ')';
+ }
+
+ $form['fields_override'][$field] = array(
+ '#type' => 'checkbox',
+ '#title' => $title,
+ '#default_value' => isset($conf['fields_override'][$field]) ? $conf['fields_override'][$field] : !$handler->options['exclude'],
+ );
+ }
+ }
+
+ ctools_include('dependent');
+ if ($allow['use_pager']) {
+ $form['use_pager'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use pager'),
+ '#default_value' => $conf['use_pager'],
+ '#id' => 'use-pager-checkbox',
+ '#prefix' => '<div class="container-inline">',
+ );
+ $form['pager_id'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['pager_id'],
+ '#title' => t('Pager ID'),
+ '#size' => 4,
+ '#id' => 'use-pager-textfield',
+ '#dependency' => array('use-pager-checkbox' => array(1)),
+ '#suffix' => '</div>',
+ );
+ }
+ if ($allow['items_per_page']) {
+ $form['items_per_page'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['items_per_page'],
+ '#title' => t('Num items'),
+ '#size' => 4,
+ '#description' => t('Select the number of items to display, or 0 to display all results.'),
+ );
+ }
+ if ($allow['offset']) {
+ $form['offset'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['offset'],
+ '#title' => t('Offset'),
+ '#size' => 4,
+ '#description' => t('Enter the number of items to skip; enter 0 to skip no items.'),
+ );
+ }
+ if ($allow['path_override']) {
+ $form['path'] = array(
+ '#type' => 'textfield',
+ '#default_value' => isset($conf['path']) ? $conf['path'] : $view->get_path(),
+ '#title' => t('Override path'),
+ '#size' => 30,
+ '#description' => t('If this is set, override the View URL path; this can sometimes be useful to set to the panel URL.'),
+ );
+ if (!empty($contexts)) {
+ $form['path']['#description'] .= ' ' . t('You may use substitutions in this path.');
+
+ $form['contexts'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Substitutions'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+
+ $rows = array();
+ foreach ($contexts as $context) {
+ foreach (ctools_context_get_converters('%' . check_plain($context->keyword) . ':', $context) as $keyword => $title) {
+ $rows[] = array(
+ check_plain($keyword),
+ t('@identifier: @title', array('@title' => $title, '@identifier' => $context->identifier)),
+ );
+ }
+ }
+
+ $header = array(t('Keyword'), t('Value'));
+ $form['contexts']['context'] = array('#markup' => theme('table', array('header' => $header, 'rows' => $rows)));
+ }
+ }
+
+ if (empty($conf['exposed'])) {
+ $conf['exposed'] = array();
+ }
+
+ if ($allow['exposed_form']) {
+ // If the exposed form is part of pane configuration, get the exposed
+ // form re-tool it for our use.
+ $exposed_form_state = array(
+ 'view' => &$view,
+ 'display' => &$view->display[$display_id],
+ );
+
+ $view->set_exposed_input($conf['exposed']);
+
+ if (version_compare(views_api_version(), '3', '>=')) {
+ $exposed_form_state['exposed_form_plugin'] = $view->display_handler->get_plugin('exposed_form');
+ }
+ $view->init_handlers();
+ $exposed_form = array();
+ $exposed_form = views_exposed_form($exposed_form, $exposed_form_state);
+
+ $form['exposed'] = array(
+ '#tree' => TRUE,
+ );
+
+ foreach ($exposed_form['#info'] as $id => $info) {
+ $form['exposed'][$id] = array(
+ '#type' => 'item',
+ '#id' => 'views-exposed-pane',
+ );
+
+ if (!empty($info['label'])) {
+ $form['exposed'][$id]['#title'] = $info['label'];
+ }
+
+ if (!empty($info['operator']) && !empty($exposed_form[$info['operator']])) {
+ $form['exposed'][$id][$info['operator']] = $exposed_form[$info['operator']];
+ $form['exposed'][$id][$info['operator']]['#parents'] = array('exposed', $info['operator']);
+ $form['exposed'][$id][$info['operator']]['#default_value'] = isset($conf['exposed'][$info['operator']]) ? $conf['exposed'][$info['operator']] : '';
+ }
+ $form['exposed'][$id][$info['value']] = $exposed_form[$info['value']];
+ $form['exposed'][$id][$info['value']]['#parents'] = array('exposed', $info['value']);
+ $form['exposed'][$id][$info['value']]['#default_value'] = isset($conf['exposed'][$info['value']]) ? $conf['exposed'][$info['value']] : '';
+ }
+ }
+
+ // The exposed sort stuff doesn't fall into $exposed_form['#info'] so we
+ // have to handle it separately.
+ if (isset($exposed_form['sort_by'])) {
+ $form['exposed']['sort_by'] = $exposed_form['sort_by'];
+ }
+
+ if (isset($exposed_form['sort_order'])) {
+ $form['exposed']['sort_order'] = $exposed_form['sort_order'];
+ }
+
+ // Add the view object to the form to allow additional customization
+ $form_state['view'] = $view;
+
+ return $form;
+}
+
+/**
+ * Store form values in $conf.
+ */
+function views_content_views_panes_content_type_edit_form_submit(&$form, &$form_state) {
+ // Copy everything from our defaults.
+ $keys = array('link_to_view', 'more_link', 'feed_icons', 'use_pager',
+ 'pager_id', 'items_per_page', 'offset', 'path_override', 'path', 'arguments', 'fields_override', 'exposed');
+
+ foreach ($keys as $key) {
+ if (isset($form_state['values'][$key])) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+ }
+}
+
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_panes_content_type_admin_title($subtype, $conf, $contexts) {
+ list($name, $display) = explode('-', $subtype);
+ $view = views_get_view($name);
+ if (empty($view) || empty($view->display[$display])) {
+ return t('Deleted/missing view @view', array('@view' => $name));
+ }
+
+ $view->set_display($display);
+ views_content_views_panes_add_defaults($conf, $view);
+
+ $title = views_content_get_display_title($view, $display);
+
+ return check_plain($title);
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_panes_content_type_admin_info($subtype, $conf, $contexts) {
+ $info = array();
+
+ list($view_name, $display_name) = explode('-', $subtype);
+ $view = views_get_view($view_name);
+
+ if (empty($view) || empty($view->display[$display_name])) {
+ return;
+ }
+
+ $view->set_display($display_name);
+ views_content_views_panes_add_defaults($conf, $view);
+
+ // Add arguments first
+ if (!empty($conf['arguments'])) {
+ $keys = array_keys($conf['arguments']);
+ $values = array_values($conf['arguments']);
+ $argument_input = $view->display_handler->get_option('argument_input');
+
+ foreach ($conf['arguments'] as $key => $value) {
+ if (!empty($value)){
+ $label = $argument_input[$key]['label'];
+ $info[] = $label . ': ' . $value;
+ }
+ }
+ }
+
+ $block = new stdClass;
+ if ($info) {
+ $block->title = array_shift($info);
+
+ $info[] = $view->display_handler->get_option('pane_description');
+ $block->content = theme('item_list', array('items' => $info));
+ }
+ else {
+ $block->title = $view->display_handler->get_option('pane_description');
+ $block->content = '';
+ }
+ return $block;
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_row.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_row.inc
new file mode 100644
index 000000000..9672d020d
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_row.inc
@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to display individual rows.
+ */
+
+$plugin = array(
+ 'title' => t('View row'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display all or a specific amount of rows from a loaded view context.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+ 'defaults' => array(
+ 'rows' => array(),
+ 'use_fields' => array(),
+ 'fields' => array(),
+ ),
+ 'add form' => array(
+ 'views_content_views_row_content_type_edit_form' => t('Select context'),
+ 'views_content_views_row_edit' => t('Configure rows'),
+ ),
+ 'edit form' => array(
+ 'views_content_views_row_content_type_edit_form' => t('Select context'),
+ 'views_content_views_row_edit' => t('Configure rows'),
+ ),
+);
+
+/**
+ * Render the node_terms content type.
+ */
+function views_content_views_row_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_row';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ // This guarantees the view is rendered normally which must happen.
+ $view = views_content_context_get_view($context);
+ $output = views_content_context_get_output($context);
+
+ $sets = array();
+ $plugin = $view->style_plugin;
+
+ // If all rows have to be displayed then simply get the key of all rows.
+ $row_indexes = array();
+ if (empty($conf['rows'])) {
+ if (is_array($output['rows'])) {
+ $row_indexes = array_keys($output['rows']);
+ }
+ }
+ else {
+ // If a subset of rows is requested collect the list of row keys.
+ foreach ($conf['rows'] as $index) {
+ $row_indexes[] = $index - 1;
+ }
+ }
+
+ if (empty($conf['use_fields']) || empty($plugin->row_plugin)) {
+ foreach ($row_indexes as $row_index) {
+ if (isset($output['rows'][$row_index])) {
+ $sets[$plugin->groups[$row_index]][$row_index] = $output['rows'][$row_index];
+ }
+ }
+ }
+ else {
+ // If we're using specific fields, go through and poke the 'exclude' flag.
+ foreach ($view->field as $id => $field) {
+ $view->field[$id]->options['exclude'] = empty($conf['fields'][$id]);
+ }
+
+ // Rerender just the rows we need.
+ foreach ($row_indexes as $row_index) {
+ $view->row_index = $row_index;
+ if (!empty($view->result[$view->row_index])) {
+ $sets[$plugin->groups[$view->row_index]][$view->row_index] = $plugin->row_plugin->render($view->result[$view->row_index]);
+ }
+ unset($view->row_index);
+ }
+ }
+
+ foreach ($sets as $title => $rows) {
+ $block->content .= theme($plugin->theme_functions(),
+ array(
+ 'view' => $view,
+ 'options' => $plugin->options,
+ 'rows' => $rows,
+ 'title' => $title
+ )
+ );
+ }
+
+ return $block;
+}
+
+function views_content_views_row_content_type_edit_form($form, &$form_state) {
+ // This form does nothing; it exists to let the main form select the view context.
+ return $form;
+}
+
+function views_content_views_row_content_type_edit_form_submit($form, &$form_state) {
+}
+
+function views_content_views_row_edit($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $contexts = $form_state['contexts'];
+
+ if (empty($contexts[$conf['context']])) {
+ $form['markup'] = array('#markup' => '<p>' . t('Invalid context selected.') . '</p>');
+ return $form;
+ }
+
+ if (!isset($contexts[$conf['context']]->argument)) {
+ $name = $contexts[$conf['context']]->placeholder['conf']['name'];
+ list($plugin, $view_data) = explode(':', $name);
+ list($view_name, $display_id) = explode('-', $view_data);
+ }
+ else {
+ $view_data = $contexts[$conf['context']]->argument;
+ list($view_name, $display_id) = explode(':', $view_data);
+ }
+ $contexts[$conf['context']]->data['name'] = $view_name;
+ $contexts[$conf['context']]->data['display'] = $display_id;
+ $view = views_content_context_get_view($contexts[$conf['context']]);
+ if (empty($view)) {
+ $form['markup'] = array('#markup' => '<p>' . t('Context contains an invalid view.') . '</p>');
+ return $form;
+ }
+
+ ctools_include('dependent');
+ $form['limit_rows'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Limit rows'),
+ '#default_value' => (int) !empty($conf['rows']),
+ );
+
+ $view->init_pager();
+ $rows = $view->get_items_per_page();
+
+ if (!empty($rows)) {
+ foreach (range(1, $rows) as $row) {
+ $options[$row] = t('Row @number', array('@number' => $row));
+ }
+ $form['rows'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Select rows'),
+ '#options' => $options,
+ '#default_value' => $conf['rows'],
+ '#dependency' => array('edit-limit-rows' => array(TRUE)),
+ );
+ }
+ else {
+ $form['rows'] = array('#markup' => '<p>' . t('The view must have a maximum number of items set to use this setting.') . '</p>');
+ return $form;
+ }
+
+ if ($view->display_handler->uses_fields()) {
+ $form['use_fields'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display specific fields'),
+ '#default_value' => $conf['use_fields'],
+ );
+
+ $form['fields'] = array(
+ '#type' => 'checkboxes',
+ '#options' => $view->display_handler->get_field_labels(),
+ '#default_value' => $conf['fields'],
+ '#prefix' => '<div id="edit-fields-wrapper"><div id="edit-fields">',
+ '#suffix' => '</div></div>',
+ '#dependency' => array('edit-use-fields' => array(TRUE)),
+ );
+ }
+ return $form;
+}
+
+function views_content_views_row_edit_validate(&$form, &$form_state) {
+}
+
+function views_content_views_row_edit_submit(&$form, &$form_state) {
+ $form_state['conf']['rows'] = array_filter($form_state['values']['rows']);
+ $form_state['conf']['use_fields'] = $form_state['values']['use_fields'];
+ $form_state['conf']['fields'] = array_filter($form_state['values']['fields']);
+}
+
+function views_content_views_row_content_type_admin_info($subtype, $conf, $contexts) {
+ // Go through this route to make sure we catch changes in configuration
+ // that can happen.
+ $plugin = ctools_get_content_type('views_row');
+ $context = ctools_content_select_context($plugin, $subtype, $conf, $contexts);
+
+ $block = new stdClass();
+ $block->title = t('Row information');
+
+ if (!empty($conf['use_fields'])) {
+ $display_fields = array();
+ $view = views_content_context_get_view($context);
+ if (empty($view)) {
+ $block->title = t('Broken view');
+ return $block;
+ }
+ $fields = $view->display_handler->get_field_labels();
+
+ foreach ($conf['fields'] as $field) {
+ if (!empty($fields[$field])) {
+ $display_fields[$field] = '"<em>' . check_plain($fields[$field]) . '</em>"';
+ }
+ }
+
+ if ($display_fields) {
+ $block->content = t('Displaying: !fields', array('!fields' => implode(', ', $display_fields)));
+ }
+ else {
+ $block->content = t('Displaying no fields due to misconfiguration.');
+ }
+ }
+ else {
+ $block->content = t('Displaying the configured row.');
+ }
+
+ return $block;
+}
+
+function views_content_views_row_content_type_admin_title($subtype, $conf, $context) {
+ $rows = array_filter($conf['rows']);
+ $rows = empty($rows) ? t('Show all') : implode(', ', $rows);
+ return format_plural(count($rows),
+ '"@context" row @rows',
+ '"@context" rows @rows',
+ array('@context' => $context->identifier, '@rows' => $rows)
+ );
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_view.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_view.inc
new file mode 100644
index 000000000..dfb1175c3
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_view.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Allow a view context to be displayed as whole.
+ */
+
+$plugin = array(
+ 'title' => t('Entire view'),
+ 'category' => t('View context'),
+ 'icon' => 'icon_views_page.png',
+ 'description' => t('Display the entire view.'),
+ 'required context' => new ctools_context_required(t('View'), 'view'),
+);
+
+/**
+ * Render the views view content type.
+ */
+function views_content_views_view_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (empty($context) || empty($context->data)) {
+ return;
+ }
+
+ // Build the content type block.
+ $block = new stdClass();
+ $block->module = 'views_view';
+ $block->delta = $context->argument;
+ $block->title = '';
+ $block->content = '';
+
+ $output = views_content_context_get_output($context);
+ $output = $output['view']->preview();
+ $block->content = $output;
+
+ return $block;
+}
+
+function views_content_views_view_content_type_edit_form($form, &$form_state) {
+ // This form does nothing; it exists to let the main form select the view context.
+ return $form;
+}
+
+function views_content_views_view_content_type_edit_form_submit(&$form, &$form_state) {
+ // Kept so we guarantee we have a submit handler.
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function views_content_views_view_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@context" entire view', array('@context' => $context->identifier));
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/contexts/view.inc b/sites/all/modules/ctools/views_content/plugins/contexts/view.inc
new file mode 100644
index 000000000..1e926e457
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/contexts/view.inc
@@ -0,0 +1,174 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugin to provide a node context. A node context is a node wrapped in a
+ * context object that can be utilized by anything that accepts contexts.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t("View"),
+ 'description' => t('Loads a view result into a context that can then be displayed across a panel or turned into other contexts.'),
+ 'context' => 'views_content_context_view_create',
+
+ 'edit form' => 'views_content_context_view_settings_form',
+ 'edit form validate' => 'views_content_context_view_settings_form_validate',
+ 'edit form submit' => 'views_content_context_view_settings_form_submit',
+
+ 'defaults' => array('view' => ''),
+
+ 'keyword' => 'view',
+ 'context name' => 'view',
+
+ 'get child' => 'views_content_context_view_get_child',
+ 'get children' => 'views_content_context_view_get_children',
+);
+
+function views_content_context_view_get_child($plugin, $parent, $child) {
+ list($name, $id) = explode('-', $child, 2);
+ $view = views_get_view($name);
+ if (!$view) {
+ return;
+ }
+
+ $view->set_display($id);
+ if ($view->current_display != $id) {
+ return;
+ }
+
+ $info = _views_content_get_context_from_display($view, $id, $parent, FALSE);
+ if ($info) {
+ return $info;
+ }
+ return;
+}
+
+function views_content_context_view_get_children($plugin, $parent) {
+ $types = array(
+ 'view' => $plugin,
+ );
+
+ // We're keeping the 'view' context around for legacy reasons but
+ // we want to disable the UI so you can't add it that way anymore.
+ $types['view']['no ui'] = TRUE;
+
+ $views = views_get_applicable_views('returns context');
+ foreach ($views as $data) {
+ list($view, $id) = $data;
+ $info = _views_content_get_context_from_display($view, $id, $parent, FALSE);
+ if ($info) {
+ $info['no required context ui'] = TRUE;
+ $types[$info['name']] = $info;
+ }
+ }
+
+ return $types;
+}
+
+function views_content_context_view_create($empty, $data = NULL, $conf = FALSE, $plugin = array()) {
+ $context = new ctools_context('view');
+ $context->plugin = 'view';
+
+ if ($empty) {
+ return $context;
+ }
+
+ if ($conf) {
+ if (is_array($data) && !empty($data['view'])) {
+ // This code is left in for backward compatibility. Will not be used
+ // with child plugins.
+ list($name, $display_id) = explode(':', $data['view'], 2);
+ $data = views_get_view($name);
+ if ($data) {
+ $data->set_display($display_id);
+ }
+ }
+ else if (!empty($plugin['view name'])) {
+ $data = views_get_view($plugin['view name']);
+ $data->set_display($plugin['view display id']);
+ }
+ }
+
+ if (is_object($data) && $data->current_display != 'default') {
+ // We don't store the loaded view as we don't want the view object
+ // cached. However, in order to extract it you can use:
+ // @code
+ // $output = views_content_context_get_output($context);
+ // $view = $output['view'];
+ // @endcode
+ $context->data = array(
+ 'name' => $data->name,
+ 'display' => $data->current_display,
+ 'args' => $data->args,
+ );
+
+ // At runtime, this can get populated. Once it is populated this
+ // object should not be cached.
+ $context->view = NULL;
+ $context->title = $data->get_title();
+ $context->argument = $data->name . ':' . $data->current_display;
+
+ $context->restrictions['base'] = array($data->base_table);
+
+ return $context;
+ }
+}
+
+function views_content_context_view_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $views = views_get_applicable_views('returns context');
+ foreach ($views as $data) {
+ list($view, $id) = $data;
+ $title = views_content_get_display_title($view, $id, 'admin_title');
+ $options[$view->name . ':' . $id] = $title;
+ }
+
+ if (!empty($options)) {
+ natcasesort($options);
+ $form['view'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View'),
+ );
+ }
+ else {
+ $form['view'] = array(
+ '#value' => '<p>' . t('There are currently no views with Context displays enabled. You should go to the view administration and add a Context display to use a view as a context.') . '</p>',
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Validate a node.
+ */
+function views_content_context_view_settings_form_validate($form, &$form_state) {
+ if (empty($form_state['values']['view'])) {
+ form_error($form['view'], t('You must select a view.'));
+ }
+}
+
+/**
+ * Provide a list of ways that this context can be converted to a string.
+ */
+function views_content_context_view_convert_list() {
+ $list = array(
+ );
+
+ return $list;
+}
+
+/**
+ * Convert a context into a string.
+ */
+function views_content_context_view_convert($context, $type) {
+ switch ($type) {
+ }
+}
+
diff --git a/sites/all/modules/ctools/views_content/plugins/relationships/node_from_view.inc b/sites/all/modules/ctools/views_content/plugins/relationships/node_from_view.inc
new file mode 100644
index 000000000..0f3fa8e0a
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/relationships/node_from_view.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for node from view.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Node from view'),
+ 'keyword' => 'node',
+ 'description' => t('Extract a node context from a view context of the base type node.'),
+ 'required context' => new ctools_context_required(t('View'), 'view', array('base' => 'node')),
+ 'context' => 'views_content_node_from_view_context',
+ 'edit form' => 'views_content_node_from_view_settings_form',
+ 'edit form validate' => 'views_content_node_from_view_settings_form_validate',
+ 'defaults' => array('row' => 1),
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function views_content_node_from_view_context($context, $conf, $placeholder = FALSE) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data) || $placeholder) {
+ return ctools_context_create_empty('node', NULL);
+ }
+ $view = views_content_context_get_view($context);
+ // Ensure the view executes, but we don't need its output.
+ views_content_context_get_output($context);
+
+ $row = intval($conf['row']) - 1;
+ if (isset($view->result[$row])) {
+ $nid = $view->result[$row]->{$view->base_field};
+ if ($nid) {
+ $node = node_load($nid);
+ return ctools_context_create('node', $node);
+ }
+ }
+ return ctools_context_create_empty('node', NULL);
+}
+
+/**
+ * Settings form for the relationship.
+ */
+function views_content_node_from_view_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['row'] = array(
+ '#title' => t('Row number'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['row'],
+ );
+
+ return $form;
+}
+
+function views_content_node_from_view_settings_form_validate($form, &$form_state) {
+ if (intval($form_state['values']['row']) <= 0) {
+ form_error($form['row'], t('Row number must be a positive integer value.'));
+ }
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/relationships/term_from_view.inc b/sites/all/modules/ctools/views_content/plugins/relationships/term_from_view.inc
new file mode 100644
index 000000000..fc9e20d41
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/relationships/term_from_view.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for term from view.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Term from view'),
+ 'keyword' => 'term',
+ 'description' => t('Extract a term context from a view context of the base type term.'),
+ 'required context' => new ctools_context_required(t('View'), 'view', array('base' => 'taxonomy_term_data')),
+ 'context' => 'views_content_term_from_view_context',
+ 'edit form' => 'views_content_term_from_view_settings_form',
+ 'edit form validate' => 'views_content_term_from_view_settings_form_validate',
+ 'defaults' => array('row' => 1),
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function views_content_term_from_view_context($context, $conf, $placeholder = FALSE) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data) || $placeholder) {
+ return ctools_context_create_empty('entity:taxonomy_term', NULL);
+ }
+ $view = views_content_context_get_view($context);
+ // Ensure the view executes, but we don't need its output.
+ views_content_context_get_output($context);
+
+ $row = intval($conf['row']) - 1;
+ if (isset($view->result[$row])) {
+ $tid = $view->result[$row]->{$view->base_field};
+ if ($tid) {
+ $term = taxonomy_term_load($tid);
+ return ctools_context_create('entity:taxonomy_term', $term);
+ }
+ }
+ return ctools_context_create_empty('entity:taxonomy_term', NULL);
+}
+
+/**
+ * Settings form for the relationship.
+ */
+function views_content_term_from_view_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['row'] = array(
+ '#title' => t('Row number'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['row'],
+ );
+
+ return $form;
+}
+
+function views_content_term_from_view_settings_form_validate($form, &$form_state) {
+ if (intval($form_state['values']['row']) <= 0) {
+ form_error($form['row'], t('Row number must be a positive integer value.'));
+ }
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/relationships/user_from_view.inc b/sites/all/modules/ctools/views_content/plugins/relationships/user_from_view.inc
new file mode 100644
index 000000000..47c4693cc
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/relationships/user_from_view.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for user from term.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('User from view'),
+ 'keyword' => 'user',
+ 'description' => t('Extract a user context from a view context of the base type user.'),
+ 'required context' => new ctools_context_required(t('View'), 'view', array('base' => 'users')),
+ 'context' => 'views_content_user_from_view_context',
+ 'edit form' => 'views_content_user_from_view_settings_form',
+ 'edit form validate' => 'views_content_user_from_view_settings_form_validate',
+ 'defaults' => array('row' => 1),
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function views_content_user_from_view_context($context, $conf, $placeholder = FALSE) {
+ // If unset it wants a generic, unfilled context, which is just NULL.
+ if (empty($context->data) || $placeholder) {
+ return ctools_context_create_empty('user', NULL);
+ }
+ $view = views_content_context_get_view($context);
+ // Ensure the view executes, but we don't need its output.
+ views_content_context_get_output($context);
+
+ $row = intval($conf['row']) - 1;
+ if (isset($view->result[$row])) {
+ $uid = $view->result[$row]->{$view->base_field};
+ if ($uid) {
+ $user = user_load($uid);
+ return ctools_context_create('user', $user);
+ }
+ }
+ return ctools_context_create_empty('user', NULL);
+}
+
+/**
+ * Settings form for the relationship.
+ */
+function views_content_user_from_view_settings_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['row'] = array(
+ '#title' => t('Row number'),
+ '#type' => 'textfield',
+ '#default_value' => $conf['row'],
+ );
+
+ return $form;
+}
+
+function views_content_user_from_view_settings_form_validate($form, &$form_state) {
+ if (intval($form_state['values']['row']) <= 0) {
+ form_error($form['row'], t('Row number must be a positive integer value.'));
+ }
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/relationships/view_from_argument.inc b/sites/all/modules/ctools/views_content/plugins/relationships/view_from_argument.inc
new file mode 100644
index 000000000..cefc6dba0
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/relationships/view_from_argument.inc
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for a view by argument input settings.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('View From Argument'),
+ 'description' => t('Creates a view context from argument input settings.'),
+ 'context' => 'views_content_view_from_argument_context',
+ 'get child' => 'views_content_view_from_argument_get_child',
+ 'get children' => 'views_content_view_from_argument_get_children',
+);
+
+function views_content_view_from_argument_get_child($plugin, $parent, $child) {
+ list($name, $id) = explode('-', $child, 2);
+ $view = views_get_view($name);
+ if (!$view) {
+ return;
+ }
+
+ $view->set_display($id);
+ if ($view->current_display != $id) {
+ return;
+ }
+
+ $info = _views_content_get_context_from_display($view, $id, $parent, TRUE);
+ if ($info) {
+ return $info;
+ }
+ return;
+}
+
+function views_content_view_from_argument_get_children($plugin, $parent) {
+ $types = array();
+
+ $views = views_get_applicable_views('returns context');
+ foreach ($views as $data) {
+ list($view, $id) = $data;
+ $info = _views_content_get_context_from_display($view, $id, $parent, TRUE);
+ if ($info) {
+ $types[$info['name']] = $info;
+ }
+ }
+
+ return $types;
+}
+
+/**
+ * Return a new context based on an existing context.
+ */
+function views_content_view_from_argument_context($contexts, $conf) {
+ $name = $conf['name'];
+ list($plugin, $view_data) = explode(':', $name);
+ list($view_name, $display_id) = explode('-', $view_data);
+ $keys = array_keys($conf['context']);
+
+ if (empty($contexts[$keys[0]]->data)) {
+ return ctools_context_create_empty('view', NULL);
+ }
+
+ // Load our view up.
+ $data = views_get_view($view_name);
+ if ($data) {
+ // Set the display.
+ $data->set_display($display_id);
+ $context_keys = array_keys($contexts);
+ foreach ($data->display_handler->get_argument_input() as $id => $argument) {
+ if ($argument['type'] == 'context') {
+ $key = array_shift($context_keys);
+ if (isset($contexts [$key])) {
+ if (strpos($argument['context'], '.')) {
+ list($context, $converter) = explode('.', $argument['context'], 2);
+ $args[] = ctools_context_convert_context($contexts[$key], $converter, array('sanitize' => FALSE));
+ }
+ else {
+ $args[] = $contexts[$key]->argument;
+ }
+ }
+ }
+ }
+ // remove any trailing NULL arguments as these are non-args:
+ while (count($args) && end($args) === NULL) {
+ array_pop($args);
+ }
+ $data->set_arguments($args);
+ if ($path = $data->display_handler->get_option('inherit_panels_path')) {
+ $data->override_path = $_GET['q'];
+ }
+ }
+
+ if (isset($contexts[$keys[0]]->data)) {
+ return ctools_context_create('view', $data);
+ }
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/views/views_content.views.inc b/sites/all/modules/ctools/views_content/plugins/views/views_content.views.inc
new file mode 100644
index 000000000..644ac547b
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/views/views_content.views.inc
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains Views plugin definitions for the panel pane display.
+ */
+
+/**
+ * Implements hook_views_plugins
+ */
+function views_content_views_plugins() {
+ return array(
+ 'display' => array(
+ 'panel_pane' => array(
+ 'title' => t('Content pane'),
+ 'admin' => t('Content pane'),
+ 'help' => t('Is available as content for a panel or dashboard display.'),
+ 'handler' => 'views_content_plugin_display_panel_pane',
+ 'path' => drupal_get_path('module', 'views_content') . '/plugins/views',
+ 'theme path' => drupal_get_path('module', 'views') . '/theme',
+ 'theme' => 'views_view',
+ 'register theme' => FALSE,
+ 'use ajax' => TRUE,
+ 'use pager' => TRUE,
+ 'use more' => TRUE,
+ 'accept attachments' => TRUE,
+ 'help topic' => 'display-pane',
+ 'contextual links locations' => array('panel_pane'),
+ ),
+ 'ctools_context' => array(
+ 'title' => t('Context'),
+ 'admin' => t('Context'),
+ 'help' => t('Makes the view results available as a context for use in Panels and other applications.'),
+ 'handler' => 'views_content_plugin_display_ctools_context',
+ 'path' => drupal_get_path('module', 'views_content') . '/plugins/views',
+ 'theme path' => drupal_get_path('module', 'views') . '/theme',
+ 'theme' => 'views_view',
+ 'register theme' => FALSE,
+ 'use ajax' => FALSE,
+ 'use pager' => TRUE,
+ 'use more' => FALSE,
+ 'accept attachments' => TRUE,
+ 'returns context' => TRUE,
+ 'help topic' => 'display-context',
+ ),
+ ),
+ 'style' => array(
+ 'ctools_context' => array(
+ 'title' => t('Context'),
+ 'help' => t('Contains rows in contexts.'),
+ 'handler' => 'views_content_plugin_style_ctools_context',
+ 'path' => drupal_get_path('module', 'views_content') . '/plugins/views',
+ 'theme path' => drupal_get_path('module', 'views') . '/theme',
+ 'theme' => 'views_view_unformatted',
+ 'register theme' => FALSE,
+ 'uses row plugin' => TRUE,
+ 'uses row class' => TRUE,
+ 'uses fields' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'context',
+ 'help topic' => 'style-context',
+ ),
+ ),
+ );
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_ctools_context.inc b/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_ctools_context.inc
new file mode 100644
index 000000000..967f2fa82
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_ctools_context.inc
@@ -0,0 +1,272 @@
+<?php
+/**
+ * @file
+ * Contains the block display plugin.
+ */
+
+/**
+ * The plugin that handles a block.
+ *
+ * @ingroup views_display_plugins
+ */
+class views_content_plugin_display_ctools_context extends views_plugin_display {
+ /**
+ * If this variable is true, this display counts as a context. We use this
+ * variable so that we can easily build plugins against this display type.
+ */
+ var $context_display = TRUE;
+
+ function get_style_type() { return 'context'; }
+
+ function defaultable_sections($section = NULL) {
+ if (in_array($section, array('style_options', 'style_plugin', 'row_options', 'row_plugin',))) {
+ return FALSE;
+ }
+
+ return parent::defaultable_sections($section);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['admin_title'] = array('default' => '', 'translatable' => TRUE);
+
+ // Overrides for standard stuff:
+ $options['style_plugin']['default'] = 'ctools_context';
+ $options['row_plugin']['default'] = 'fields';
+ $options['defaults']['default']['style_plugin'] = FALSE;
+ $options['defaults']['default']['style_options'] = FALSE;
+ $options['defaults']['default']['row_plugin'] = FALSE;
+ $options['defaults']['default']['row_options'] = FALSE;
+ $options['inherit_panels_path'] = array('default' => 0);
+ $options['argument_input'] = array('default' => array());
+
+ return $options;
+ }
+
+ /**
+ * The display block handler returns the structure necessary for a block.
+ */
+ function execute() {
+ $this->executing = TRUE;
+ return $this->view->render();
+ }
+
+ function preview() {
+ $this->previewing = TRUE;
+ return $this->view->render();
+ }
+
+ /**
+ * Render this display.
+ */
+ function render() {
+ if (!empty($this->previewing)) {
+ return theme($this->theme_functions(), array('view' => $this->view));
+ }
+ else {
+ // We want to process the view like we're theming it, but not actually
+ // use the template part. Therefore we run through all the preprocess
+ // functions which will populate the variables array.
+ $hooks = theme_get_registry();
+ $info = $hooks[$this->definition['theme']];
+ if (!empty($info['file'])) {
+ @include_once('./' . $info['path'] . '/' . $info['file']);
+ }
+ $this->variables = array('view' => &$this->view);
+
+ if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) {
+ foreach ($info['preprocess functions'] as $preprocess_function) {
+ if (function_exists($preprocess_function)) {
+ $preprocess_function($this->variables, $this->definition['theme']);
+ }
+ }
+ }
+ }
+
+ return $this->variables;
+ }
+
+ /**
+ * Provide the summary for page options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) {
+ // It is very important to call the parent function here:
+ parent::options_summary($categories, $options);
+
+ $categories['context'] = array(
+ 'title' => t('Context settings'),
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -10,
+ ),
+ );
+
+ $admin_title = $this->get_option('admin_title');
+ if (empty($admin_title)) {
+ $admin_title = t('Use view name');
+ }
+
+ if (drupal_strlen($admin_title) > 16) {
+ $admin_title = drupal_substr($admin_title, 0, 16) . '...';
+ }
+
+ $options['admin_title'] = array(
+ 'category' => 'context',
+ 'title' => t('Admin title'),
+ 'value' => $admin_title,
+ );
+
+ $options['inherit_panels_path'] = array(
+ 'category' => 'context',
+ 'title' => t('Use Panel path'),
+ 'value' => $this->get_option('inherit_panels_path') ? t('Yes') : t('No'),
+ );
+
+ $options['argument_input'] = array(
+ 'category' => 'context',
+ 'title' => t('Argument input'),
+ 'value' => t('Edit'),
+ );
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_form($form, $form_state);
+ switch ($form_state['section']) {
+ case 'row_plugin':
+ // This just overwrites the existing row_plugin which is using the wrong options.
+ $form['row_plugin']['#options'] = views_fetch_plugin_names('row', 'normal', array($this->view->base_table));
+ break;
+ case 'admin_title':
+ $form['#title'] .= t('Administrative title');
+
+ $form['admin_title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->get_option('admin_title'),
+ '#description' => t('This is the title that will appear for this view context in the configure context dialog. If left blank, the view name will be used.'),
+ );
+ break;
+ case 'inherit_panels_path':
+ $form['#title'] .= t('Inherit path from panel display');
+
+ $form['inherit_panels_path'] = array(
+ '#type' => 'select',
+ '#options' => array(1 => t('Yes'), 0 => t('No')),
+ '#default_value' => $this->get_option('inherit_panels_path'),
+ '#description' => t('If yes, all links generated by Views, such as more links, summary links, and exposed input links will go to the panels display path, not the view, if the display has a path.'),
+ );
+ break;
+ case 'argument_input':
+ $form['#title'] .= t('Choose the data source for view arguments');
+ $argument_input = $this->get_argument_input();
+ ctools_include('context');
+ ctools_include('dependent');
+ $form['argument_input']['#tree'] = TRUE;
+
+ $converters = ctools_context_get_all_converters();
+ ksort($converters);
+
+ foreach ($argument_input as $id => $argument) {
+ $form['argument_input'][$id] = array(
+ '#tree' => TRUE,
+ );
+
+ $safe = str_replace(array('][', '_', ' ', ':'), '-', $id);
+ $type_id = 'edit-argument-input-' . $safe;
+
+ $form['argument_input'][$id]['type'] = array(
+ '#type' => 'select',
+ '#options' => array(
+ 'none' => t('No argument'),
+ 'context' => t('From context'),
+ ),
+ '#id' => $type_id,
+ '#title' => t('@arg source', array('@arg' => $argument['name'])),
+ '#default_value' => $argument['type'],
+ );
+
+ $form['argument_input'][$id]['context'] = array(
+ '#type' => 'select',
+ '#title' => t('Required context'),
+ '#description' => t('If "From context" is selected, which type of context to use.'),
+ '#default_value' => $argument['context'],
+ '#options' => $converters,
+ '#dependency' => array($type_id => array('context')),
+ );
+
+ $form['argument_input'][$id]['context_optional'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Context is optional'),
+ '#description' => t('This context need not be present for the pane to function. If you plan to use this, ensure that the argument handler can handle empty values gracefully.'),
+ '#default_value' => $argument['context_optional'],
+ '#dependency' => array($type_id => array('context')),
+ );
+ }
+ break;
+ }
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_submit($form, $form_state);
+ switch ($form_state['section']) {
+ case 'admin_title':
+ case 'argument_input':
+ case 'inherit_panels_path':
+ $this->set_option($form_state['section'], $form_state['values'][$form_state['section']]);
+ break;
+ }
+ }
+
+ /**
+ * Adjust the array of argument input to match the current list of
+ * arguments available for this display. This ensures that changing
+ * the arguments doesn't cause the argument input field to just
+ * break.
+ */
+ function get_argument_input() {
+ $arguments = $this->get_option('argument_input');
+ $handlers = $this->get_handlers('argument');
+
+ // We use a separate output so as to seamlessly discard info for
+ // arguments that no longer exist.
+ $output = array();
+
+ foreach ($handlers as $id => $handler) {
+ if (empty($arguments[$id])) {
+ $output[$id] = array(
+ 'type' => 'none',
+ 'context' => 'any',
+ 'context_optional' => FALSE,
+ 'name' => $handler->ui_name(),
+ );
+ }
+ else {
+ $output[$id] = $arguments[$id];
+ $output[$id]['name'] = $handler->ui_name();
+ }
+ }
+
+ return $output;
+ }
+
+ function get_path() {
+ if ($this->get_option('link_display') == 'custom_url' && $override_path = $this->get_option('link_url')) {
+ return $override_path;
+ }
+ if ($this->get_option('inherit_panels_path')) {
+ return $_GET['q'];
+ }
+ return parent::get_path();
+ }
+}
diff --git a/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc b/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc
new file mode 100644
index 000000000..e02f896a4
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc
@@ -0,0 +1,416 @@
+<?php
+
+/**
+ * The plugin that handles a panel_pane.
+ */
+class views_content_plugin_display_panel_pane extends views_plugin_display {
+ /**
+ * If this variable is true, this display counts as a panel pane. We use
+ * this variable so that other modules can create alternate pane displays.
+ */
+ var $panel_pane_display = TRUE;
+ var $has_pane_conf = NULL;
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['pane_title'] = array('default' => '', 'translatable' => TRUE);
+ $options['pane_description'] = array('default' => '', 'translatable' => TRUE);
+ $options['pane_category'] = array(
+ 'contains' => array(
+ 'name' => array('default' => 'View panes', 'translatable' => TRUE),
+ 'weight' => array('default' => 0),
+ ),
+ );
+
+ $options['allow'] = array(
+ 'contains' => array(
+ 'use_pager' => array('default' => FALSE),
+ 'items_per_page' => array('default' => FALSE),
+ 'offset' => array('default' => FALSE),
+ 'link_to_view' => array('default' => FALSE),
+ 'more_link' => array('default' => FALSE),
+ 'path_override' => array('default' => FALSE),
+ 'title_override' => array('default' => FALSE),
+ 'exposed_form' => array('default' => FALSE),
+ 'fields_override' => array('default' => FALSE),
+ ),
+ );
+
+ $options['argument_input'] = array('default' => array());
+ $options['link_to_view'] = array('default' => 0);
+ $options['inherit_panels_path'] = array('default' => 0);
+
+ return $options;
+ }
+
+ function has_pane_conf() {
+ return isset($this->has_pane_conf);
+ }
+
+ function set_pane_conf($conf = array()) {
+ $this->set_option('pane_conf', $conf);
+ $this->has_pane_conf = TRUE;
+ }
+
+ /**
+ * Provide the summary for page options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) {
+ // It is very important to call the parent function here:
+ parent::options_summary($categories, $options);
+
+ $categories['panel_pane'] = array(
+ 'title' => t('Pane settings'),
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -10,
+ ),
+ );
+
+ $pane_title = $this->get_option('pane_title');
+ if (empty($pane_title)) {
+ $pane_title = t('Use view name');
+ }
+
+ if (drupal_strlen($pane_title) > 16) {
+ $pane_title = drupal_substr($pane_title, 0, 16) . '...';
+ }
+
+ $options['pane_title'] = array(
+ 'category' => 'panel_pane',
+ 'title' => t('Admin title'),
+ 'value' => $pane_title,
+ );
+
+ $pane_description = $this->get_option('pane_description');
+ if (empty($pane_description)) {
+ $pane_description = t('Use view description');
+ }
+
+ if (drupal_strlen($pane_description) > 16) {
+ $pane_description = drupal_substr($pane_description, 0, 16) . '...';
+ }
+
+ $options['pane_description'] = array(
+ 'category' => 'panel_pane',
+ 'title' => t('Admin desc'),
+ 'value' => $pane_description,
+ );
+
+ $category = $this->get_option('pane_category');
+ $pane_category = $category['name'];
+ if (empty($pane_category)) {
+ $pane_category = t('View panes');
+ }
+
+ if (drupal_strlen($pane_category) > 16) {
+ $pane_category = drupal_substr($pane_category, 0, 16) . '...';
+ }
+
+ $options['pane_category'] = array(
+ 'category' => 'panel_pane',
+ 'title' => t('Category'),
+ 'value' => $pane_category,
+ );
+
+ $options['link_to_view'] = array(
+ 'category' => 'panel_pane',
+ 'title' => t('Link to view'),
+ 'value' => $this->get_option('link_to_view') ? t('Yes') : t('No'),
+ );
+
+ $options['inherit_panels_path'] = array(
+ 'category' => 'panel_pane',
+ 'title' => t('Use Panel path'),
+ 'value' => $this->get_option('inherit_panels_path') ? t('Yes') : t('No'),
+ );
+
+ $options['argument_input'] = array(
+ 'category' => 'panel_pane',
+ 'title' => t('Argument input'),
+ 'value' => t('Edit'),
+ );
+
+ $allow = $this->get_option('allow');
+ $filtered_allow = array_filter($allow);
+
+ $options['allow'] = array(
+ 'category' => 'panel_pane',
+ 'title' => t('Allow settings'),
+ 'value' => empty($filtered_allow) ? t('None') : ($allow === $filtered_allow ? t('All') : t('Some')),
+ );
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_form($form, $form_state);
+
+ switch ($form_state['section']) {
+ case 'allow':
+ $form['#title'] .= t('Allow settings');
+ $form['description'] = array(
+ '#value' => '<div class="form-item description">' . t('Checked settings will be available in the panel pane config dialog for modification by the panels user. Unchecked settings will not be available and will only use the settings in this display.') . '</div>',
+ );
+
+ $options = array(
+ 'use_pager' => t('Use pager'),
+ 'items_per_page' => t('Items per page'),
+ 'offset' => t('Pager offset'),
+ 'link_to_view' => t('Link to view'),
+ 'more_link' => t('More link'),
+ 'path_override' => t('Path override'),
+ 'title_override' => t('Title override'),
+ 'exposed_form' => t('Use exposed widgets form as pane configuration'),
+ 'fields_override' => t('Fields override'),
+ );
+
+ $allow = array_filter($this->get_option('allow'));
+ $form['allow'] = array(
+ '#type' => 'checkboxes',
+ '#default_value' => $allow,
+ '#options' => $options,
+ );
+ break;
+ case 'pane_title':
+ $form['#title'] .= t('Administrative title');
+
+ $form['pane_title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->get_option('pane_title'),
+ '#description' => t('This is the title that will appear for this view pane in the add content dialog. If left blank, the view name will be used.'),
+ );
+ break;
+
+ case 'pane_description':
+ $form['#title'] .= t('Administrative description');
+
+ $form['pane_description'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->get_option('pane_description'),
+ '#description' => t('This is text that will be displayed when the user mouses over the pane in the add content dialog. If blank the view description will be used.'),
+ );
+ break;
+
+ case 'pane_category':
+ $form['#title'] .= t('Administrative description');
+
+ $cat = $this->get_option('pane_category');
+ $form['pane_category']['#tree'] = TRUE;
+ $form['pane_category']['name'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $cat['name'],
+ '#description' => t('This is category the pane will appear in on the add content dialog.'),
+ );
+ $form['pane_category']['weight'] = array(
+ '#title' => t('Weight'),
+ '#type' => 'textfield',
+ '#default_value' => $cat['weight'],
+ '#description' => t('This is the default weight of the category. Note that if the weight of a category is defined in multiple places, only the first one Panels sees will get that definition, so if the weight does not appear to be working, check other places that the weight might be set.'),
+ );
+ break;
+
+ case 'link_to_view':
+ $form['#title'] .= t('Link pane title to view');
+
+ $form['link_to_view'] = array(
+ '#type' => 'select',
+ '#options' => array(1 => t('Yes'), 0 => t('No')),
+ '#default_value' => $this->get_option('link_to_view'),
+ );
+ break;
+
+ case 'inherit_panels_path':
+ $form['#title'] .= t('Inherit path from panel display');
+
+ $form['inherit_panels_path'] = array(
+ '#type' => 'select',
+ '#options' => array(1 => t('Yes'), 0 => t('No')),
+ '#default_value' => $this->get_option('inherit_panels_path'),
+ '#description' => t('If yes, all links generated by Views, such as more links, summary links, and exposed input links will go to the panels display path, not the view, if the display has a path.'),
+ );
+ break;
+
+ case 'argument_input':
+ $form['#title'] .= t('Choose the data source for view arguments');
+ $argument_input = $this->get_argument_input();
+ ctools_include('context');
+ ctools_include('dependent');
+ $form['argument_input']['#tree'] = TRUE;
+
+ $converters = ctools_context_get_all_converters();
+ ksort($converters);
+
+ foreach ($argument_input as $id => $argument) {
+ $form['argument_input'][$id] = array(
+ '#tree' => TRUE,
+ );
+
+ $safe = str_replace(array('][', '_', ' '), '-', $id);
+ $type_id = 'edit-argument-input-' . $safe;
+
+ $form['argument_input'][$id]['type'] = array(
+ '#type' => 'select',
+ '#options' => array(
+ 'none' => t('No argument'),
+ 'wildcard' => t('Argument wildcard'),
+ 'context' => t('From context'),
+ 'panel' => t('From panel argument'),
+ 'fixed' => t('Fixed'),
+ 'user' => t('Input on pane config'),
+ ),
+ '#id' => $type_id,
+ '#title' => t('@arg source', array('@arg' => $argument['name'])),
+ '#default_value' => $argument['type'],
+ );
+ $form['argument_input'][$id]['context'] = array(
+ '#type' => 'select',
+ '#title' => t('Required context'),
+ '#description' => t('If "From context" is selected, which type of context to use.'),
+ '#default_value' => $argument['context'],
+ '#options' => $converters,
+ '#dependency' => array($type_id => array('context')),
+ );
+
+ $form['argument_input'][$id]['context_optional'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Context is optional'),
+ '#description' => t('This context need not be present for the pane to function. If you plan to use this, ensure that the argument handler can handle empty values gracefully.'),
+ '#default_value' => $argument['context_optional'],
+ '#dependency' => array($type_id => array('context')),
+ );
+
+ $form['argument_input'][$id]['panel'] = array(
+ '#type' => 'select',
+ '#title' => t('Panel argument'),
+ '#description' => t('If "From panel argument" is selected, which panel argument to use.'),
+ '#default_value' => $argument['panel'],
+ '#options' => array(0 => t('First'), 1 => t('Second'), 2 => t('Third'), 3 => t('Fourth'), 4 => t('Fifth'), 5 => t('Sixth')),
+ '#dependency' => array($type_id => array('panel')),
+ );
+
+ $form['argument_input'][$id]['fixed'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Fixed argument'),
+ '#description' => t('If "Fixed" is selected, what to use as an argument.'),
+ '#default_value' => $argument['fixed'],
+ '#dependency' => array($type_id => array('fixed')),
+ );
+
+ $form['argument_input'][$id]['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#description' => t('If this argument is presented to the panels user, what label to apply to it.'),
+ '#default_value' => empty($argument['label']) ? $argument['name'] : $argument['label'],
+ '#dependency' => array($type_id => array('user')),
+ );
+ }
+ break;
+ }
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_submit($form, $form_state);
+ switch ($form_state['section']) {
+ case 'allow':
+ case 'argument_input':
+ case 'link_to_view':
+ case 'inherit_panels_path':
+ case 'pane_title':
+ case 'pane_description':
+ case 'pane_category':
+ $this->set_option($form_state['section'], $form_state['values'][$form_state['section']]);
+ break;
+ }
+ }
+
+ /**
+ * Adjust the array of argument input to match the current list of
+ * arguments available for this display. This ensures that changing
+ * the arguments doesn't cause the argument input field to just
+ * break.
+ */
+ function get_argument_input() {
+ $arguments = $this->get_option('argument_input');
+ $handlers = $this->get_handlers('argument');
+
+ // We use a separate output so as to seamlessly discard info for
+ // arguments that no longer exist.
+ $output = array();
+
+ foreach ($handlers as $id => $handler) {
+ if (empty($arguments[$id])) {
+ $output[$id] = array(
+ 'type' => 'none',
+ 'context' => 'any',
+ 'context_optional' => FALSE,
+ 'panel' => 0,
+ 'fixed' => '',
+ 'name' => $handler->ui_name(),
+ );
+ }
+ else {
+ $output[$id] = $arguments[$id];
+ $output[$id]['name'] = $handler->ui_name();
+ }
+ }
+
+ return $output;
+ }
+
+ function use_more() {
+ $allow = $this->get_option('allow');
+ if (!$allow['more_link'] || !$this->has_pane_conf()) {
+ return parent::use_more();
+ }
+ $conf = $this->get_option('pane_conf');
+ return (bool) $conf['more_link'];
+ }
+
+ function get_path() {
+ if (empty($this->view->override_path)) {
+ return parent::get_path();
+ }
+ return $this->view->override_path;
+ }
+
+ function get_url() {
+ if ($this->get_option('inherit_panels_path')) {
+ return $this->get_path();
+ }
+ return parent::get_url();
+ }
+
+ function uses_exposed_form_in_block() {
+ // We'll always allow the exposed form in a block, regardless of path.
+ return TRUE;
+ }
+
+ /**
+ * Determine if this display should display the exposed
+ * filters widgets, so the view will know whether or not
+ * to render them.
+ *
+ * Regardless of what this function
+ * returns, exposed filters will not be used nor
+ * displayed unless uses_exposed() returns TRUE.
+ */
+ function displays_exposed() {
+ $conf = $this->get_option('allow');
+ // If this is set, the exposed form is part of pane configuration, not
+ // rendered normally.
+ return empty($conf['exposed_form']);
+ }
+
+}
+
diff --git a/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_style_ctools_context.inc b/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_style_ctools_context.inc
new file mode 100644
index 000000000..aafbebc1f
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_style_ctools_context.inc
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @file
+ * Contains the default style plugin.
+ */
+
+/**
+ * Default style plugin to render rows one after another with no
+ * decorations.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_content_plugin_style_ctools_context extends views_plugin_style {
+ var $rows = array();
+
+ /**
+ * Render the display in this style.
+ */
+ function render() {
+ if (!empty($this->view->display_handler->previewing)) {
+ return parent::render();
+ }
+
+ $this->rows = array();
+ $this->groups = array();
+ if ($this->uses_row_plugin() && empty($this->row_plugin)) {
+ vpr('views_plugin_style_default: Missing row plugin');
+ return;
+ }
+
+ // Some engines like solr key results on ids, but rendering really expects
+ // things to be keyed exclusively by row index. Using array_values()
+ // guarantees that.
+ $this->view->result = array_values($this->view->result);
+
+ // Group the rows according to the grouping field, if specified.
+ $sets = $this->render_grouping($this->view->result, $this->options['grouping']);
+
+ // Render each group separately and concatenate. Plugins may override this
+ // method if they wish some other way of handling grouping.
+ $output = '';
+ foreach ($sets as $title => $records) {
+ foreach ($records as $row_index => $row) {
+ $this->view->row_index = $row_index;
+ $this->rows[$row_index] = $this->row_plugin->render($row);
+ $this->groups[$row_index] = $title;
+ }
+ }
+ unset($this->view->row_index);
+ return $this->rows;
+ }
+
+}
diff --git a/sites/all/modules/ctools/views_content/views_content.admin.inc b/sites/all/modules/ctools/views_content/views_content.admin.inc
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/views_content.admin.inc
diff --git a/sites/all/modules/ctools/views_content/views_content.info b/sites/all/modules/ctools/views_content/views_content.info
new file mode 100644
index 000000000..6e2840180
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/views_content.info
@@ -0,0 +1,18 @@
+name = Views content panes
+description = Allows Views content to be used in Panels, Dashboard and other modules which use the CTools Content API.
+package = Views
+dependencies[] = ctools
+dependencies[] = views
+core = 7.x
+package = Chaos tool suite
+version = CTOOLS_MODULE_VERSION
+files[] = plugins/views/views_content_plugin_display_ctools_context.inc
+files[] = plugins/views/views_content_plugin_display_panel_pane.inc
+files[] = plugins/views/views_content_plugin_style_ctools_context.inc
+
+; Information added by Drupal.org packaging script on 2015-08-19
+version = "7.x-1.9"
+core = "7.x"
+project = "ctools"
+datestamp = "1440020680"
+
diff --git a/sites/all/modules/ctools/views_content/views_content.module b/sites/all/modules/ctools/views_content/views_content.module
new file mode 100644
index 000000000..27a666ae4
--- /dev/null
+++ b/sites/all/modules/ctools/views_content/views_content.module
@@ -0,0 +1,297 @@
+<?php
+
+/**
+ * @file views_content.module
+ *
+ * Provides views as panels content, configurable by the administrator.
+ * Each view provided as panel content must be configured in advance,
+ * but once configured, building panels with views is a little bit simpler.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function views_content_menu() {
+ $items = array();
+
+ if (!module_exists('panels')) {
+ $items['admin/config/content-views'] = array(
+ 'title' => 'Views panes',
+ 'access arguments' => array('administer views content plugin'),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('views_content_admin_page'),
+ 'description' => 'Configure Views to be used as CTools content.',
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ }
+
+ return $items;
+}
+
+/**
+ * Implementation of hook_ctools_plugin_dierctory() to let the system know
+ * where our content_type plugins are.
+ */
+function views_content_ctools_plugin_directory($owner, $plugin_type) {
+ if ($owner == 'ctools') {
+ return 'plugins/' . $plugin_type;
+ }
+}
+
+/**
+ * Don't show Views' blocks; we expose them already.
+ */
+function views_ctools_block_info($module, $delta, &$info) {
+ if (strlen($delta) == 32) {
+ $hashes = variable_get('views_block_hashes', array());
+ if (!empty($hashes[$delta])) {
+ $delta = $hashes[$delta];
+ }
+ }
+
+ if (substr($delta, 0, 1) != '-') {
+ $info = NULL;
+ }
+ else {
+ $info['category'] = t('Views');
+ $info['icon'] = 'icon_views_block_legacy.png';
+ $info['path'] = drupal_get_path('module', 'views_content');
+ $info['edit form'] = 'views_content_exposed_form_pane_edit';
+ $info['add form'] = 'views_content_exposed_form_pane_edit';
+ $info['render callback'] = 'views_content_exposed_form_pane_render';
+ }
+}
+
+/**
+ * Add settings to the "exposed form in block" views.
+ */
+function views_content_exposed_form_pane_edit($form, &$form_state) {
+ // This is a cheesy way to add defaults only to new versions of the block
+ // but leave older blocks without the setting alone. We can tell because
+ // all older content will have something set for override_title which is
+ // the only pre-existing setting.
+ if (!isset($form_state['conf']['inherit_path']) && !isset($form_state['conf']['override_title'])) {
+ $form_state['conf']['inherit_path'] = TRUE;
+ }
+
+ $form['inherit_path'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Inherit path'),
+ '#default_value' => !empty($form_state['conf']['inherit_path']),
+ );
+
+ return $form;
+}
+
+/**
+ * Store data for the exposed form in block settings page.
+ */
+function views_content_exposed_form_pane_edit_submit($form, &$form_state) {
+ $form_state['conf']['inherit_path'] = $form_state['values']['inherit_path'];
+}
+
+/**
+ * Render function for 'special' view blocks.
+ *
+ * We took over the render for the special view blocks so that we could
+ * add options to it.
+ */
+function views_content_exposed_form_pane_render($subtype, $conf, $panel_args, $contexts) {
+ $delta = str_replace('views-', '', $subtype);
+
+ if (strlen($delta) == 32) {
+ $hashes = variable_get('views_block_hashes', array());
+ if (!empty($hashes[$delta])) {
+ $delta = $hashes[$delta];
+ }
+ }
+
+ list($nothing, $type, $name, $display_id) = explode('-', $delta);
+ // Put the - back on. For views special blocks, the first character is always - but
+ // the explode killed it. Note that this code is mostly copied from views_block().
+ $type = '-' . $type;
+ if ($view = views_get_view($name)) {
+ if ($view->access($display_id)) {
+ if (!empty($conf['inherit_path'])) {
+ $view->override_path = $_GET['q'];
+ }
+
+ $view->set_display($display_id);
+ if (isset($view->display_handler)) {
+ $block = (object) $view->display_handler->view_special_blocks($type);
+ return $block;
+ }
+ }
+ $view->destroy();
+ }
+}
+
+/**
+ * Implements hook_views_api().
+ *
+ * This one is used as the base to reduce errors when updating.
+ */
+function views_content_views_api() {
+ return array(
+ 'api' => 2,
+ 'path' => drupal_get_path('module', 'views_content') . '/plugins/views',
+ );
+}
+
+/**
+ * Page callback to provide the basic administration form.
+ */
+function views_content_admin_page() {
+ $form = array();
+ views_content_admin_form($form);
+
+ return system_settings_form($form);
+}
+
+function views_content_admin_form(&$form) {
+ $form['ctools_content_all_views'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Make all views available as panes'),
+ '#description' => t("If checked, all views will be made available as content panes to be added to content types. If not checked, only Views that have a 'Content pane' display will be available as content panes. Uncheck this if you want to be able to more carefully control what view content is available to users using the panels layout UI."),
+ '#default_value' => variable_get('ctools_content_all_views', TRUE),
+ );
+}
+
+/**
+ * API function to get the view.
+ */
+function views_content_context_get_view(&$context) {
+ if (empty($context->view) || get_class($context->view) == '__PHP_Incomplete_Class') {
+ $context->view = views_get_view($context->data['name']);
+ if ($context->view) {
+ $context->view->set_display($context->data['display']);
+ $context->view->set_arguments($context->data['args']);
+ }
+ }
+
+ return $context->view;
+}
+
+/**
+ * API function to get the view.
+ */
+function views_content_context_get_output(&$context) {
+ if (empty($context->output)) {
+ $view = views_content_context_get_view($context);
+ $context->output = $view->execute_display($context->data['display']);
+ }
+
+ return $context->output;
+}
+
+/**
+ * Get the title to display for a views content display for pane or context.
+ */
+function views_content_get_display_title($view, $display_id, $option = 'pane_title') {
+ $handler = $view->display[$display_id]->handler;
+ $title = $handler->get_option($option);
+ if (!$title) {
+ if ($handler->display->display_title == $handler->definition['title']) {
+ $title = t('View: @view', array('@view' => $view->get_human_name()));
+ }
+ else {
+ $title = t('View: @view: @display', array('@view' => $view->get_human_name(), '@display' => $handler->display->display_title));
+ }
+ }
+
+ return $title;
+}
+
+/**
+ * Get the proper label for a display.
+ *
+ * Views renamed the default to Master, but it can still have a conflicting
+ * display title. Fix that.
+ */
+function views_content_get_display_label($view, $display_id) {
+ $title = $display_id == 'default' ? t('Master') : $view->display[$display_id]->display_title;
+ return $title;
+}
+
+/**
+ * Get the child plugin for a view context display.
+ *
+ * This can return both the context and relationship style. The
+ * $required parameter is used to distinguish if context is required
+ * or not, so we know whether we need it suitable as a pure context
+ * (i.e, no context required) or a relationship (i.e, context required).
+ */
+function _views_content_get_context_from_display($view, $id, $parent, $required = TRUE) {
+ $title = views_content_get_display_title($view, $id, 'admin_title');
+
+ $description = $view->description;
+ $contexts = array();
+
+ $arguments = $view->display_handler->get_argument_input();
+ ctools_include('views');
+ foreach ($arguments as $argument) {
+ $argument['label'] = $argument['name'] ? $argument['name'] : '';
+ $contexts[] = ctools_views_get_argument_context($argument);
+ }
+
+ $pass = FALSE;
+ if ($required) {
+ // If context is required, make sure we have at least one optional
+ // or required context.
+ foreach ($contexts as $context) {
+ if (is_object($context)) {
+ $pass = TRUE;
+ break;
+ }
+ }
+
+ if (!$pass) {
+ return;
+ }
+ }
+ else {
+ // If context is not required, then having any required context
+ // causes a fail.
+ foreach ($contexts as $context) {
+ if (is_object($context) && get_class($context) == 'ctools_context_required') {
+ return;
+ }
+ }
+
+ // Since we know we don't want contexts, we an unset this now.
+ $contexts = array();
+ }
+
+ if ($required) {
+ $function = 'views_content_view_from_argument_context';
+ }
+ else {
+ $function = 'views_content_context_view_create';
+ }
+
+ return array(
+ 'title' => $title,
+ 'description' => filter_xss_admin($description),
+ 'required context' => $contexts,
+ 'keyword' => 'view',
+ 'context' => $function,
+ 'context name' => $view->name,
+ 'name' => $parent . ':' . $view->name . '-' . $id,
+ 'view name' => $view->name,
+ 'view display id' => $id,
+ );
+}
+
+/**
+ * Implements hook_get_pane_links_alter().
+ */
+function views_content_get_pane_links_alter(&$links, $pane, $content_type) {
+ if ($pane->type === 'views_panes') {
+ list($view_name, $display_name) = explode('-', $pane->subtype);
+ $destination = array('destination' => current_path());
+ $links['top'][] = array(
+ 'title' => t('Edit view'),
+ 'href' => url('admin/structure/views/view/' . $view_name . '/edit/' . $display_name, array('query' => $destination, 'absolute' => TRUE)),
+ );
+ }
+}
diff --git a/sites/all/modules/ds/LICENSE.txt b/sites/all/modules/ds/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/ds/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/ds/README.txt b/sites/all/modules/ds/README.txt
new file mode 100644
index 000000000..2238c0f9f
--- /dev/null
+++ b/sites/all/modules/ds/README.txt
@@ -0,0 +1,57 @@
+ ____ _ __ _____ _ __
+ / __ \(_)________ / /___ ___ __ / ___/__ __(_) /____
+ / / / / / ___/ __ \/ / __ `/ / / / \__ \/ / / / / __/ _ \
+ / /_/ / (__ ) /_/ / / /_/ / /_/ / ___/ / /_/ / / /_/ __/
+/_____/_/____/ .___/_/\__,_/\__, / /____/\__,_/_/\__/\___/
+ /_/ /____/
+
+Display Suite gives you full control over the way content is displayed without
+having to maintain dozens of PHP template files.
+Read more: http://drupal.org/node/644662
+
+-- GETTING STARTED --
+
+1. Install Display Suite in the usual way (http://drupal.org/node/895232)
+2. Go to Administration > Structure > Display Suite > Layout
+ (admin/structure/ds/layout)
+3. Click "Manage display" for the entity (e.g., "User") whose display you like
+ to change
+4. In the vertical tab "Layout for ... in default" choose the desired layout
+ template (e.g. "Two column stacked") and click "Apply"
+5. Start managing the display by dragging fields to regions
+6. Click "Save"
+Read more: http://drupal.org/node/1795282
+
+-- CHANGES IN DISPLAY SUITE 7.x-2.x ---
+
+Display Suite 7.x-2.x introduces many changes in comparison to 7.x-1.x. Among
+them are:
+* Improved UI
+* HTML5 support
+* Panel views mode removed from Display Suite
+* Manage display of forms
+
+Do not upgrade an existing site from 7.x-1.x to 7.x-2.x. Some functionality has
+been changed, especially on the template level.
+
+Read more: http://drupal.org/node/1524800
+
+-- LINKS --
+
+Project page: http://drupal.org/project/ds
+Documentation: http://drupal.org/node/644662
+Screencastst & articles: http://drupal.org/node/644706
+Submit bug reports, feature suggestions: http://drupal.org/project/issues/ds
+
+-- MAINTAINERS --
+
+swentel - http://drupal.org/user/107403
+stalski - http://drupal.org/user/322618
+zuuperman - http://drupal.org/user/361625
+jyve - http://drupal.org/user/591438
+aspilicious - http://drupal.org/user/172527
+
+-- INSPIRATORS --
+
+mzenner - http://drupal.org/user/35077
+Wimmmmm - http://drupal.org/user/34940
diff --git a/sites/all/modules/ds/css/ds.admin.css b/sites/all/modules/ds/css/ds.admin.css
new file mode 100644
index 000000000..1eda848ae
--- /dev/null
+++ b/sites/all/modules/ds/css/ds.admin.css
@@ -0,0 +1,193 @@
+
+/**
+ * @file
+ * Administration CSS for Display Suite.
+ */
+.ds-extras-field-template {
+ margin-bottom: 1em;
+}
+
+#field-display-overview .field-formatter-settings-edit-form .ft-group {
+ border-top: 1px solid #aaa;
+ padding: 5px 0;
+ overflow: auto;
+}
+
+#field-display-overview .field-formatter-settings-edit-form .ft-group.lb {
+ border-top: none;
+}
+
+#field-display-overview .field-formatter-settings-edit-form .ft-group .form-item {
+ margin: 0;
+ white-space: normal;
+}
+
+.ds-layout-preview-title {
+ font-weight: bold;
+}
+
+.ds-layout-preview-image {
+ float: left;
+ width: 150px;
+ padding-top: 10px;
+}
+
+.ds-layout-preview-image img {
+ width: 150px;
+}
+
+.ds-layout-preview-arrow {
+ float: left;
+ margin: 80px 10px 0 5px;
+}
+
+.ds-layout-preview-suggestion {
+ min-width: 150px;
+ overflow: hidden;
+ padding-left: 30px;
+}
+
+.ds-layout-regions {
+ float: left;
+ width: 210px;
+}
+
+.ft-group div.form-item {
+ float: left;
+ width: 130px;
+}
+
+.ds-select-layout {
+ clear: both;
+}
+
+.ds-layout-change-save {
+ clear: both;
+}
+
+/**
+ * Displays list
+ */
+table th.ds-display-list-options {
+ width: 300px;
+}
+
+/**
+ * Limit
+ */
+.limit-float {
+ float: left;
+ margin-right: 5px;
+}
+
+/**
+ * Modal
+ */
+#modalBackdrop {
+ position: fixed!important;
+ background-color: #000!important;
+}
+
+div.ctools-modal-content .modal-header {
+ padding: 5px;
+ background-color: #E1E2DC;
+}
+
+div.ctools-modal-content a.close {
+ color: #666;
+}
+
+div.ctools-modal-content .modal-title {
+ /*font-weight: normal;*/
+ color: #666;
+}
+
+div.ctools-modal-content .modal-header img {
+ display: none;
+}
+
+div.ctools-modal-content .form-item label {
+ width: 100%;
+ float: none;
+ clear: both;
+}
+
+div.ctools-modal-content .resizable-textarea {
+ width: 100%;
+ margin-left: 0;
+ margin-right: 0;
+}
+
+div.ctools-modal-content {
+ font-size: 12px;
+ border: solid 1px #ddd;
+ -webkit-border-radius: 0.5em;
+ -moz-border-radius: 0.5em;
+ -webkit-box-shadow: -1em 1em 1em rgba(0, 0, 0, 0.5);
+}
+
+a.section-link {
+ display: block;
+}
+
+#ctools-content-selection h2 {
+ color: #666;
+}
+
+.option-text-aligner .form-item {
+ float: left;
+ padding: .25em 1em .25em 0;
+ margin: 0;
+}
+
+.option-text-aligner {
+ clear: both;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+/**
+ * CTools content selection.
+ */
+#ctools-content-selection #ds-left {
+ float: left;
+ width: 30%;
+}
+
+#ctools-content-selection #ds-right {
+ float: left;
+ width: 70%;
+}
+
+#ctools-content-selection .selection-hide {
+ display: none;
+}
+
+#ctools-content-selection .content-item {
+ width: 50%;
+ float: left;
+}
+
+/**
+ * Fieldset in vertical tabs.
+ */
+.vertical-tabs fieldset#edit-additional-settings-ds-page-title-options-page-option-contexts {
+ margin: 1em 0;
+ padding: 2.5em 0 0;
+ border: 1px solid #ccc;
+}
+
+#edit-additional-settings-ds-page-title-options-page-option-contexts legend {
+ display: block;
+}
+
+/**
+ * Change layout screen.
+ */
+ .change_ds_layout_info {
+ margin-bottom: 10px;
+ }
+ .change_ds_layout_old_region {
+ font-weight: bold;
+ }
diff --git a/sites/all/modules/ds/drush/ds.drush.inc b/sites/all/modules/ds/drush/ds.drush.inc
new file mode 100644
index 000000000..8411d3458
--- /dev/null
+++ b/sites/all/modules/ds/drush/ds.drush.inc
@@ -0,0 +1,218 @@
+<?php
+
+/**
+ * @file
+ * Display Suite drush integration.
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function ds_drush_command() {
+ $items = array();
+
+ $items['ds-build'] = array(
+ 'description' => 'Create a basic template and configuration file for a new Display Suite layout.',
+ 'arguments' => array(
+ 'name' => 'Name for your layout.',
+ ),
+ 'options' => array(
+ 'name' => 'Name for your layout.',
+ 'regions' => 'Regions for your layout, comma-separated.',
+ 'css' => 'Set this to true if you want to include a CSS file for your layout.',
+ 'image' => 'Set this to true if you want to include a preview image for your layout.',
+ ),
+ 'examples' => array(
+ 'drush ds-build "My layout name"' => 'Create a layout with a few example regions.',
+ 'drush ds-build "My layout name" --regions="Region 1, Region 2"' => 'Create a layout with custom regions.',
+ 'drush ds-build "My layout name" --css' => 'Create a layout with an included CSS file.',
+ ),
+ );
+
+ return $items;
+}
+
+/**
+ * Create a basic template and configuration file for a new Display Suite layout.
+ */
+function drush_ds_build($name = NULL) {
+ // Determine the layout name.
+ if (!isset($name)) {
+ $name = drush_get_option('name');
+ }
+ if (!$name) {
+ drush_die(dt('You need to set a name for your layout. Type "drush help ds-build" for help.'));
+ }
+
+ // Determine the machine name.
+ $machine_name = ds_prepare_machine_name($name);
+
+ // Determine the path to our example layout templates.
+ $ds_layout_path = dirname(__FILE__) . '/example_layout';
+
+ // We create files in the current working directory.
+ $layout_path = drush_cwd() . '/' . $machine_name;
+ drush_op('mkdir', $layout_path);
+
+ // Determine regions.
+ $regions = drush_get_option('regions');
+ if ($regions) {
+ $regions = preg_split('/,(\ )?/', $regions);
+ }
+
+ // Copy the example templates.
+ $tpl_machine_name = strtr($machine_name, '_', '-');
+ drush_op('copy', $ds_layout_path . '/example-layout.tpl.php', $layout_path . "/$tpl_machine_name.tpl.php");
+ drush_op('copy', $ds_layout_path . '/example_layout.inc', $layout_path . "/$machine_name.inc");
+
+ // Prepare an array of things that need to be rewritten in our templates.
+ $find = array();
+ $replace = array();
+
+ // Replace example name.
+ $find[] = '/example layout/i';
+ $replace[] = $name;
+ $find[] = '/example_layout/';
+ $replace[] = $machine_name;
+
+ // Include a CSS file for this layout.
+ $css = drush_get_option('css');
+ if (isset($css)) {
+ drush_op('copy', $ds_layout_path . '/example_layout.css', $layout_path . "/$machine_name.css");
+
+ // Replace example styling if we have custom regions.
+ if ($regions) {
+ // Separate variables so this won't mess up our other templates.
+ $css_find = $find;
+ $css_replace = $replace;
+
+ $css_find[] = "/(\*\/\n\n).+(\n)$/s";
+ $css_replace[] = '$1' . ds_prepare_regions_css($regions) . '$2';
+
+ drush_op('ds_file_preg_replace', array($layout_path . "/$machine_name.css"), $css_find, $css_replace);
+ }
+
+ // Uncomment the CSS rule in our configuration.
+ $find[] = "/\/\/ ('css' => TRUE,)/";
+ $replace[] = '$1';
+ }
+
+ // Check on form option.
+ $image = drush_get_option('image');
+ if (isset($image)) {
+ // Uncomment the Form rule in our configuration.
+ $find[] = "/\/\/ ('image' => TRUE,)/";
+ $replace[] = '$1';
+ }
+
+ // Replace example region PHP/HTML code.
+ if ($regions) {
+ $find[] = '/ <!-- regions -->.+<!-- \/regions -->/s';
+ $replace[] = ds_prepare_regions_html($regions);
+ $find[] = "/( \* Regions:\n).+(\n \*\/)/s";
+ $replace[] = '$1' . ds_prepare_regions_variable_documentation($regions) . '$2';
+ $find[] = "/( 'regions' => array\(\n).+(\n \),)/s";
+ $replace[] = '$1' . ds_prepare_regions_configuration($regions) . '$2';
+ }
+
+ // Rewrite templates.
+ drush_op('ds_file_preg_replace', array($layout_path . "/$tpl_machine_name.tpl.php", $layout_path . "/$machine_name.inc"), $find, $replace);
+
+ // Notify user of the newly created templates.
+ drush_print(dt('Templates for "!name" created in: !path', array('!name' => $name, '!path' => $layout_path)));
+}
+
+/**
+ * Prepare a string for use as a valid machine name.
+ */
+function ds_prepare_machine_name($string) {
+ $machine_name = str_replace(' ', '_', drupal_strtolower($string));
+ // Remove characters not valid in function names.
+ $machine_name = preg_replace('/[^a-z0-9_]/', '', $machine_name);
+
+ return $machine_name;
+}
+
+/**
+ * Perform preg_replace() on the contents of an array of files.
+ */
+function ds_file_preg_replace($file_paths, $find, $replace) {
+ foreach ($file_paths as $path) {
+ $file_contents = file_get_contents($path);
+ $file_contents = preg_replace($find, $replace, $file_contents);
+ file_put_contents($path, $file_contents);
+ }
+}
+
+/**
+ * Prepare HTML structure for an array of regions.
+ */
+function ds_prepare_regions_html($region_names) {
+ $output = array();
+
+ foreach ($region_names as $name) {
+ $machine_name = ds_prepare_machine_name($name);
+ $html_class = drupal_html_class($name);
+
+ $output[] = <<<END
+ <<?php print \${$machine_name}_wrapper; ?> class="ds-$html_class<?php print \${$machine_name}_classes; ?>">
+ <?php print \$$machine_name; ?>
+ </<?php print \${$machine_name}_wrapper; ?>>
+END;
+ }
+
+ return implode("\n\n", $output);
+}
+
+/**
+ * Prepare variable documentation for an array of regions.
+ */
+function ds_prepare_regions_variable_documentation($region_names) {
+ $output = array();
+
+ foreach ($region_names as $name) {
+ $machine_name = ds_prepare_machine_name($name);
+
+ $output[] = <<<END
+ *
+ * - \$$machine_name: Rendered content for the "$name" region.
+ * - \${$machine_name}_classes: String of classes that can be used to style the "$name" region.
+END;
+ }
+
+ return implode("\n", $output);
+}
+
+/**
+ * Prepare configuration for an array of regions.
+ */
+function ds_prepare_regions_configuration($region_names) {
+ $output = array();
+
+ foreach ($region_names as $name) {
+ $machine_name = ds_prepare_machine_name($name);
+ $output[] = " '$machine_name' => t('$name'),";
+ }
+
+ return implode("\n", $output);
+}
+
+/**
+ * Prepare styling for an array of regions.
+ */
+function ds_prepare_regions_css($region_names) {
+ $output = array();
+
+ foreach ($region_names as $name) {
+ $machine_name = ds_prepare_machine_name($name);
+ $html_class = drupal_html_class($name);
+
+ $output[] = <<<END
+.ds-$html_class {
+ /* Styles for the "$html_class" region go here */
+}
+END;
+ }
+
+ return implode("\n\n", $output);
+}
diff --git a/sites/all/modules/ds/drush/example_layout/README.txt b/sites/all/modules/ds/drush/example_layout/README.txt
new file mode 100644
index 000000000..dac532991
--- /dev/null
+++ b/sites/all/modules/ds/drush/example_layout/README.txt
@@ -0,0 +1,2 @@
+Use this example layout template and configuration as a starting point for your
+own, custom Display Suite layouts.
diff --git a/sites/all/modules/ds/drush/example_layout/example-layout.tpl.php b/sites/all/modules/ds/drush/example_layout/example-layout.tpl.php
new file mode 100644
index 000000000..d93b4627d
--- /dev/null
+++ b/sites/all/modules/ds/drush/example_layout/example-layout.tpl.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @file
+ * Display Suite example layout template.
+ *
+ * Available variables:
+ *
+ * Layout:
+ * - $classes: String of classes that can be used to style this layout.
+ * - $contextual_links: Renderable array of contextual links.
+ * - $layout_wrapper: wrapper surrounding the layout.
+ *
+ * Regions:
+ *
+ * - $left: Rendered content for the "Left" region.
+ * - $left_classes: String of classes that can be used to style the "Left" region.
+ * - $left_wrapper: wrapper surrounding the left region.
+ *
+ * - $right: Rendered content for the "Right" region.
+ * - $right_classes: String of classes that can be used to style the "Right" region.
+ * - $right_wrapper: wrapper surrounding the right region.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="<?php print $classes;?> clearfix">
+
+ <!-- Needed to activate contextual links -->
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <!-- regions -->
+
+ <<?php print $left_wrapper ?> class="ds-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+
+ <<?php print $right_wrapper ?> class="ds-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+
+ <!-- These comments are required for the Drush command. You can remove them in your own copy -->
+ <!-- /regions -->
+
+</<?php print $layout_wrapper ?>>
+
+<!-- Needed to activate display suite support on forms -->
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/drush/example_layout/example_layout-rtl.css b/sites/all/modules/ds/drush/example_layout/example_layout-rtl.css
new file mode 100644
index 000000000..f177baee9
--- /dev/null
+++ b/sites/all/modules/ds/drush/example_layout/example_layout-rtl.css
@@ -0,0 +1,12 @@
+/**
+ * @file
+ * Display Suite example layout RTL stylesheet.
+ */
+
+.ds-left {
+ float: right;
+}
+
+.ds-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/drush/example_layout/example_layout.css b/sites/all/modules/ds/drush/example_layout/example_layout.css
new file mode 100644
index 000000000..f58b28329
--- /dev/null
+++ b/sites/all/modules/ds/drush/example_layout/example_layout.css
@@ -0,0 +1,14 @@
+/**
+ * @file
+ * Display Suite example layout stylesheet.
+ */
+
+.ds-left {
+ width: 20%;
+ float: left; /* LTR */
+}
+
+.ds-right {
+ width: 80%;
+ float: right; /* LTR */
+}
diff --git a/sites/all/modules/ds/drush/example_layout/example_layout.inc b/sites/all/modules/ds/drush/example_layout/example_layout.inc
new file mode 100644
index 000000000..8b8951764
--- /dev/null
+++ b/sites/all/modules/ds/drush/example_layout/example_layout.inc
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Display Suite example layout configuration.
+ */
+
+function ds_example_layout() {
+ return array(
+ 'label' => t('Example layout'),
+ 'regions' => array(
+ 'left' => t('Left'),
+ 'right' => t('Right'),
+ ),
+ // Uncomment if you want to include a CSS file for this layout (example_layout.css)
+ // 'css' => TRUE,
+ // Uncomment if you want to include a preview for this layout (example_layout.png)
+ // 'image' => TRUE,
+ );
+}
diff --git a/sites/all/modules/ds/drush/example_layout/example_layout.png b/sites/all/modules/ds/drush/example_layout/example_layout.png
new file mode 100644
index 000000000..cf953c164
--- /dev/null
+++ b/sites/all/modules/ds/drush/example_layout/example_layout.png
Binary files differ
diff --git a/sites/all/modules/ds/ds.api.php b/sites/all/modules/ds/ds.api.php
new file mode 100644
index 000000000..7f9a446e1
--- /dev/null
+++ b/sites/all/modules/ds/ds.api.php
@@ -0,0 +1,621 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by Display Suite module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function hook_test_ctools_plugin_api($module, $api) {
+ if (($module == 'ds' && $api == 'ds') || ($module == 'ds_extras' && $api == 'ds_extras')) {
+ return array('version' => 1);
+ }
+}
+
+/**
+ * Expose Display Suite field settings.
+ *
+ * This hook is called by CTools. For this hook to work, you need
+ * hook_ctools_plugin_api(). The values of this hook can be overridden
+ * and reverted through the UI.
+ */
+function hook_ds_field_settings_info() {
+ $dsfieldsettings = array();
+
+ $dsfieldsetting = new stdClass;
+ $dsfieldsetting->disabled = FALSE; /* Edit this to true to make a default dsfieldsetting disabled initially */
+ $dsfieldsetting->api_version = 1;
+ $dsfieldsetting->id = 'node|article|default';
+ $dsfieldsetting->entity_type = 'node';
+ $dsfieldsetting->bundle = 'article';
+ $dsfieldsetting->view_mode = 'default';
+ $dsfieldsetting->settings = array(
+ 'title' => array(
+ 'weight' => '0',
+ 'label' => 'hidden',
+ 'format' => 'default',
+ 'formatter_settings' => array(
+ 'link' => '1',
+ 'wrapper' => 'h3',
+ 'class' => '',
+ ),
+ ),
+ 'node_link' => array(
+ 'weight' => '1',
+ 'label' => 'hidden',
+ 'format' => 'default',
+ ),
+ );
+ $dsfieldsettings['node|article|default'] = $dsfieldsetting;
+
+ return $dsfieldsettings;
+}
+
+/**
+ * Expose default layout settings info.
+ *
+ * This hook is called by CTools. For this hook to work, you need
+ * hook_ctools_plugin_api(). The values of this hook can be overridden
+ * and reverted through the UI.
+ */
+function hook_ds_layout_settings_info() {
+ $dslayouts = array();
+
+ $dslayout = new stdClass;
+ $dslayout->disabled = FALSE; /* Edit this to true to make a default dslayout disabled initially */
+ $dslayout->api_version = 1;
+ $dslayout->id = 'node|article|default';
+ $dslayout->entity_type = 'node';
+ $dslayout->bundle = 'article';
+ $dslayout->view_mode = 'default';
+ $dslayout->layout = 'ds_2col';
+ $dslayout->settings = array(
+ 'hide_empty_regions' => 0,
+ 'regions' => array(
+ 'left' => array(
+ 0 => 'title',
+ 1 => 'node_link',
+ ),
+ 'right' => array(
+ 0 => 'body',
+ ),
+ ),
+ 'fields' => array(
+ 'title' => 'left',
+ 'node_link' => 'left',
+ 'body' => 'right',
+ ),
+ 'classes' => array(),
+ );
+ $dslayouts['node|article|default'] = $dslayout;
+
+ return $dslayouts;
+}
+
+/**
+ * Expose default view modes.
+ *
+ * This hook is called by CTools. For this hook to work, you need
+ * hook_ctools_plugin_api(). The values of this hook can be overridden
+ * and reverted through the UI.
+ */
+function hook_ds_view_modes_info() {
+ $ds_view_modes = array();
+
+ $ds_view_mode = new stdClass;
+ $ds_view_mode->disabled = FALSE; /* Edit this to true to make a default ds_view_mode disabled initially */
+ $ds_view_mode->api_version = 1;
+ $ds_view_mode->view_mode = 'test_exportables';
+ $ds_view_mode->label = 'Test exportables';
+ $ds_view_mode->entities = array(
+ 'node' => 'node',
+ );
+ $ds_view_modes['test_exportables'] = $ds_view_mode;
+
+ return $ds_view_modes;
+}
+
+/**
+ * Define fields. These fields are not overridable through the interface.
+ * If you want those, look at hook_ds_custom_fields_info().
+ *
+ * @param $entity_type
+ * The name of the entity which we are requesting fields for, e.g. 'node'.
+ *
+ * @return $fields
+ * A collection of fields which keys are the entity type name and values
+ * a collection fields.
+ *
+ * @see ds_get_fields()
+ */
+function hook_ds_fields_info($entity_type) {
+ $fields = array();
+
+ $fields['title'] = array(
+
+ // title: title of the field
+ 'title' => t('Title'),
+
+ // type: type of field
+ // - DS_FIELD_TYPE_THEME : calls a theming function.
+ // - DS_FIELD_TYPE_FUNCTION : calls a custom function.
+ // - DS_FIELD_TYPE_CODE : calls ds_render_code_field().
+ // - DS_FIELD_TYPE_BLOCK : calls ds_render_block_field().
+ // - DS_FIELD_TYPE_PREPROCESS : calls nothing, just takes a key from the
+ // variable field that is passed on.
+ // - DS_FIELD_TYPE_IGNORE : calls nothing, use this if you simple want
+ // to drag and drop. The field itself will have
+ // a theme function.
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+
+ // ui_limit : only used for the manage display screen so
+ // you can limit fields to show based on bundles or view modes
+ // the values are always in the form of $bundle|$view_mode
+ // You may use * to select all.
+ // Make sure you use the machine name.
+ 'ui_limit' => array('article|full', '*|search_index'),
+
+ // file: an optional file in which the function resides.
+ // Only for DS_FIELD_TYPE_FUNCTION.
+ 'file' => 'optional_filename',
+
+ // function: only for DS_FIELD_TYPE_FUNCTION.
+ 'function' => 'theme_ds_title_field',
+
+ // properties: can have different keys.
+ 'properties' => array(
+
+ // formatters: optional if a function is used.
+ // In case the field_type is DS_FIELD_TYPE_THEME, you also
+ // need to register these formatters as a theming function
+ // since the key will be called with theme('function').
+ // The value is the caption used in the selection config on Field UI.
+ 'formatters' => array(
+ 'node_title_nolink_h1' => t('H1 title'),
+ 'node_title_link_h1' => t('H1 title, linked to node'),
+ ),
+
+ // settings & default: optional if you have a settings form for your field.
+ 'settings' => array(
+ 'wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')),
+ 'link' => array('type' => 'select', 'options' => array('yes', 'no')),
+ ),
+ 'default' => array('wrapper' => 'h2', 'link' => 0),
+
+ // code: optional, only for code field.
+ 'code' => 'my code here',
+
+ // use_token: optional, only for code field.
+ 'use_token' => TRUE, // or FALSE,
+
+ // block: the module and delta of the block, only for block fields.
+ //
+ // @note: Display Suite uses a "|" token to split the module from
+ // the delta.
+ 'block' => 'user|menu',
+
+ // block_render: block render type, only for block fields.
+ // - DS_BLOCK_TEMPLATE : render through block template file.
+ // - DS_BLOCK_TITLE_CONTENT : render only title and content.
+ // - DS_BLOCK_CONTENT : render only content.
+ 'block_render' => DS_BLOCK_CONTENT,
+ )
+ );
+
+ return array('node' => $fields);
+
+}
+
+/**
+ * Define custom fields which can be overridden through the UI and which
+ * are exportable. The keys are almost the same as in hook_ds_fields_info()
+ * except that field_type is limited and you need an entities key.
+ *
+ * This hook is called by CTools. For this hook to work, you need
+ * hook_ctools_plugin_api(). The values of this hook can be overridden
+ * and reverted through the UI.
+ */
+function hook_ds_custom_fields_info() {
+ $ds_fields = array();
+
+ $ds_field = new stdClass;
+ $ds_field->api_version = 1;
+ $ds_field->field = 'custom_field';
+ $ds_field->label = 'Custom field';
+
+ // Field type: either block or code
+ // DS_FIELD_TYPE_CODE: 5
+ // DS_FIELD_TYPE_BLOCK: 6
+ $ds_field->field_type = 5;
+
+ // Collection of entities on which this custom field can work on.
+ $ds_field->entities = array(
+ 'node' => 'node',
+ );
+ $ds_field->properties = array(
+ 'code' => array(
+ 'value' => '<?php print "this is a custom field"; ?>',
+ 'format' => 'ds_code',
+ ),
+ 'use_token' => 0,
+ );
+ $ds_fields['custom_field'] = $ds_field;
+
+ return $ds_fields;
+}
+
+/**
+ * Expose Views layouts definitions.
+ *
+ * This hook is called by CTools. For this hook to work, you need
+ * hook_ctools_plugin_api(). The values of this hook can be overridden
+ * and reverted through the UI.
+ */
+function hook_ds_vd_info() {
+ $ds_vds = array();
+
+ $ds_vd = new stdClass;
+ $ds_vd->api_version = 1;
+ $ds_vd->vd = 'frontpage-page';
+ $ds_vd->label = 'Frontpage: Views displays';
+ $ds_vds['frontpage-page'] = $ds_vd;
+
+ return $ds_vds;
+}
+
+/**
+ * Alter fields defined by Display Suite.
+ *
+ * This function is called for each entity type.
+ *
+ * @param $fields
+ * An array with fields which can be altered just before they get cached.
+ * @param $entity_type
+ * The name of the entity type.
+ */
+function hook_ds_fields_info_alter(&$fields, $entity_type) {
+ if (isset($fields['title'])) {
+ $fields['title']['title'] = t('My title');
+ }
+}
+
+/**
+ * Alter fields defined by Display Suite just before they get
+ * rendered on the Field UI. Use this hook to inject fields
+ * which you can't alter with hook_ds_fields_info_alter().
+ *
+ * Use this in edge cases, see ds_extras_ds_fields_ui_alter()
+ * which adds fields chosen in Views UI. This also runs
+ * when a layout has been chosen.
+ *
+ * @param $fields
+ * An array with fields which can be altered just before they get cached.
+ * @param $entity_type
+ * The name of the entity type.
+ */
+function hook_ds_fields_ui_alter(&$fields, $context) {
+ $fields['title'] = t('Extra title');
+}
+
+/**
+ * Define theme functions for fields.
+ *
+ * This only is necessary when you're using the field settings
+ * plugin which comes with the DS extras module and you want to
+ * expose a special field theming function to the interface.
+ *
+ * The theme function gets $variables as the only parameter.
+ * The optional configuration through the UI is in $variables['ds-config'].
+ *
+ * Note that 'theme_ds_field_' is always needed, so the suggestions can work.
+ *
+ * @return $field_theme_functions
+ * A collection of field theming functions.
+ */
+function hook_ds_field_theme_functions_info() {
+ return array('theme_ds_field_mine' => t('Theme field'));
+}
+
+/**
+ * Return configuration summary for the field format.
+ *
+ * As soon as you have hook_ds_fields and one of the fields
+ * has a settings key, Display Suite will call this hook for the summary.
+ *
+ * @param $field
+ * The configuration of the field.
+ *
+ * @return $summary
+ * The summary to show on the Field UI.
+ */
+function hook_ds_field_format_summary($field) {
+ return 'Field summary';
+}
+
+/**
+ * Return a settings form for a Display Suite field.
+ *
+ * As soon as you have hook_ds_fields and one of the fields
+ * has a settings key, Display Suite will call this hook for field form.
+ *
+ * @param $field
+ * The configuration of the field.
+ *
+ * @return $form
+ * A form definition.
+ */
+function hook_ds_field_settings_form($field) {
+
+ // Saved formatter settings are on $field['formatter_settings'];
+ $settings = isset($field['formatter_settings']) ? $field['formatter_settings'] : $field['properties']['default'];
+
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#default_value' => $settings['label'],
+ );
+}
+
+/**
+ * Modify the layout settings just before they get saved.
+ *
+ * @param $record
+ * The record just before it gets saved into the database.
+ * @param $form_state
+ * The form_state values.
+ */
+function hook_ds_layout_settings_alter($record, $form_state) {
+ $record->settings['hide_page_title'] = TRUE;
+}
+
+/**
+ * Modify the field settings before they get saved.
+ *
+ * @param $field_settings
+ * A collection of field settings which keys are fields.
+ * @param $form
+ * The current form which is submitted.
+ * @param $form_state
+ * The form state with all its values.
+ */
+function hook_ds_field_settings_alter(&$field_settings, $form, $form_state) {
+ $field_settings['title']['region'] = 'left';
+}
+
+/**
+ * Define layouts from code.
+ *
+ * @return $layouts
+ * A collection of layouts.
+ */
+function hook_ds_layout_info() {
+ $path = drupal_get_path('module', 'foo');
+
+ $layouts = array(
+ 'foo_1col' => array(
+ 'label' => t('Foo one column'),
+ 'path' => $path . '/layouts/foo_1col',
+ 'regions' => array(
+ 'foo_content' => t('Content'),
+ ),
+ 'css' => TRUE,
+ // optional, form only applies to node form at this point.
+ 'form' => TRUE,
+ ),
+ );
+
+ return $layouts;
+}
+
+/**
+ * Alter the layout render array.
+ *
+ * @param $layout_render_array
+ * The render array
+ * @param $context
+ * An array with the context that is being rendered. Available keys are
+ * - entity
+ * - entity_type
+ * - bundle
+ * - view_mode
+ * @param array $vars
+ * All variables available for render. You can use this to add css classes.
+ */
+function hook_ds_pre_render_alter(&$layout_render_array, $context, &$vars) {
+ $layout_render_array['left'][] = array('#markup' => 'cool!', '#weight' => 20);
+ $vars['attributes_array']['class'][] = 'custom';
+}
+
+/**
+ * Alter layouts found by Display Suite.
+ *
+ * @param $layouts
+ * A array of layouts which keys are the layout and which values are
+ * properties of that layout (label, path, regions and css).
+ */
+function hook_ds_layout_info_alter(&$layouts) {
+ unset($layouts['ds_2col']);
+}
+
+/**
+ * Alter the region options in the field UI screen.
+ *
+ * This function is only called when a layout has been chosen.
+ *
+ * @param $context
+ * A collection of keys for the context. The keys are 'entity_type',
+ * 'bundle' and 'view_mode'.
+ * @param $region_info
+ * A collection of info for regions. The keys are 'region_options'
+ * and 'table_regions'.
+ */
+function hook_ds_layout_region_alter($context, &$region_info) {
+ $region_info['region_options'][$block_key] = $block['title'];
+ $region_info['table_regions'][$block_key] = array(
+ 'title' => check_plain($block['title']),
+ 'message' => t('No fields are displayed in this region'),
+ );
+}
+
+/**
+ * Alter the field label options. Note that you will either
+ * update the preprocess functions or the field.tpl.php file when
+ * adding new options.
+ *
+ * @param $field_label_options
+ * A collection of field label options.
+ */
+function hook_ds_label_options_alter(&$field_label_options) {
+ $field_label_options['label_after'] = t('Label after field');
+}
+
+/**
+ * Themes can also define extra layouts.
+ *
+ * Create a ds_layouts folder and then a folder name that will
+ * be used as key for the layout. The folder should at least have 2 files:
+ *
+ * - key.inc
+ * - key.tpl.php
+ *
+ * The css file is optional.
+ * - key.css
+ *
+ * e.g.
+ * bartik/ds_layouts/bartik_ds/bartik_ds.inc
+ * /bartik-ds.tpl.php
+ * /bartik_ds.css
+ *
+ * bartik_ds.inc must look like this:
+ *
+
+ // Fuction name is ds_LAYOUT_KEY
+ function ds_bartik_ds() {
+ return array(
+ 'label' => t('Bartik DS'),
+ 'regions' => array(
+ // The key of this region name is also the variable used in
+ // the template to print the content of that region.
+ 'bartik' => t('Bartik DS'),
+ ),
+ // Add this if there is a default css file.
+ 'css' => TRUE,
+ // Add this if there is a default preview image
+ 'image' => TRUE,
+ );
+ }
+
+ */
+
+/**
+ * Alter the view mode just before it's rendered by the DS views entity plugin.
+ *
+ * @param $view_mode
+ * The name of the view mode.
+ * @param $context
+ * A collection of items which can be used to identify in what
+ * context an entity is being rendered. The variable contains 3 keys:
+ * - entity: The entity being rendered.
+ * - view_name: the name of the view.
+ * - display: the name of the display of the view.
+ */
+function hook_ds_views_view_mode_alter(&$view_mode, $context) {
+ if ($context['view_name'] == 'my_view_name') {
+ $view_mode = 'new_view_mode';
+ }
+}
+
+/**
+ * Theme an entity coming from the views entity plugin.
+ *
+ * @param $entity
+ * The complete entity.
+ * @param $view_mode
+ * The name of the view mode.
+ */
+function ds_views_row_ENTITY_NAME($entity, $view_mode) {
+ $nid = $vars['row']->{$vars['field_alias']};
+ $node = node_load($nid);
+ $element = node_view($node, $view_mode);
+ return drupal_render($element);
+}
+
+/**
+ * Theme an entity through an advanced function coming from the views entity plugin.
+ *
+ * @param $vars
+ * An array of variables from the views preprocess functions.
+ * @param $view_mode
+ * The name of the view mode.
+ */
+function ds_views_row_adv_VIEWS_NAME(&$vars, $view_mode) {
+ // You can do whatever you want to here.
+ $vars['object'] = 'This is what I want for christmas.';
+}
+
+/**
+ * Modify the entity render array in the context of a view.
+ *
+ * @param array $content
+ * By reference. An entity view render array.
+ * @param array $context
+ * By reference. An associative array containing:
+ * - row: The current active row object being rendered.
+ * - view: By reference. The current view object.
+ * - view_mode: The view mode which is set in the Views' options.
+ * - load_comments: The same param passed to each row function.
+ *
+ * @see ds_views_row_render_entity()
+ */
+function hook_ds_views_row_render_entity_alter(&$content, &$context) {
+ if ($context['view_mode'] == 'my_mode') {
+ // Modify the view, or the content render array in the context of a view.
+ $view = &$context['view'];
+ $element = &drupal_array_get_nested_value($content, array('field_example', 0));
+ }
+}
+
+/**
+ * Alter the strings used to separate taxonomy terms.
+ */
+function hook_ds_taxonomy_term_separators(&$separators) {
+ // Remove the option to use a hyphen.
+ unset($separators[' - ']);
+ // Add the option to use a pipe.
+ $separators[' | '] = t('pipe');
+}
+
+/**
+ * Allow modules to provide additional classes for regions and layouts.
+ */
+function hook_ds_classes_alter(&$classes, $name) {
+ if ('ds_classes_regions' === $name) {
+ $classes['css-class-name'] = t('Custom Styling');
+ }
+}
+
+/**
+ * Alter the field template settings form
+ *
+ * @param array $form
+ * The form containing the field settings
+ * @param array $field_settings
+ * The settings of the field
+ */
+function hook_ds_field_theme_functions_settings_alter(&$form, $field_settings) {
+ $form['something'] = array(
+ '#type' => 'textfield',
+ '#title' => 'test',
+ );
+}
+
+/*
+ * @} End of "addtogroup hooks".
+ */
diff --git a/sites/all/modules/ds/ds.ds_fields_info.inc b/sites/all/modules/ds/ds.ds_fields_info.inc
new file mode 100644
index 000000000..ff33be5db
--- /dev/null
+++ b/sites/all/modules/ds/ds.ds_fields_info.inc
@@ -0,0 +1,418 @@
+<?php
+
+/**
+ * @file
+ * Display Suite fields.
+ */
+
+/**
+ * Implements hook_ds_fields_info().
+ */
+function ds_ds_fields_info($entity_type) {
+
+ /* --------------------------------------------------------------
+ Custom fields.
+ -------------------------------------------------------------- */
+
+ ctools_include('export');
+ $custom_fields = ctools_export_crud_load_all('ds_fields');
+ foreach ($custom_fields as $key => $field) {
+ if (isset($field->entities[$entity_type])) {
+ $fields[$entity_type][$key] = array(
+ 'title' => $field->label,
+ 'field_type' => $field->field_type,
+ 'properties' => $field->properties,
+ );
+ if (!empty($field->ui_limit)) {
+ $lines = explode("\n", $field->ui_limit);
+ $fields[$entity_type][$key]['ui_limit'] = $lines;
+ }
+ }
+ }
+
+ /* --------------------------------------------------------------
+ General node fields.
+ -------------------------------------------------------------- */
+
+ // Node title.
+ $fields['node']['title'] = array(
+ 'title' => t('Title'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_field',
+ 'properties' => array(
+ 'entity_render_key' => 'title',
+ 'settings' => array(
+ 'link' => array('type' => 'select', 'options' => array('no', 'yes')),
+ 'wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')),
+ 'class' => array('type' => 'textfield', 'description' => t('Put a class on the wrapper. Eg: block-title')),
+ ),
+ 'default' => array('wrapper' => 'h2', 'link' => 0, 'class' => ''),
+ )
+ );
+
+ // Links.
+ $fields['node']['links'] = array(
+ 'title' => t('Links'),
+ 'field_type' => DS_FIELD_TYPE_IGNORE,
+ );
+
+ // Comments.
+ if (module_exists('comment')) {
+ $fields['node']['comments'] = array(
+ 'title' => t('Comments'),
+ 'field_type' => DS_FIELD_TYPE_IGNORE,
+ 'ui_limit' => array(
+ '*|full', '*|default',
+ ),
+ );
+ }
+
+ // Node link.
+ $fields['node']['node_link'] = array(
+ 'title' => t('Read more'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_field',
+ 'properties' => array(
+ 'settings' => array(
+ 'link text' => array('type' => 'textfield'),
+ 'link class' => array('type' => 'textfield', 'description' => t('Put a class on the link. Eg: btn btn-default')),
+ 'wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')),
+ 'class' => array('type' => 'textfield', 'description' => t('Put a class on the wrapper. Eg: block-title')),
+ ),
+ 'default' => array('link text' => 'Read more', 'link class' => '', 'wrapper' => '', 'class' => '', 'link' => 1),
+ )
+ );
+
+ // Author.
+ $fields['node']['author'] = array(
+ 'title' => t('Author'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_author_field',
+ 'properties' => array(
+ 'formatters' => array(
+ 'author' => t('Author'),
+ 'author_linked' => t('Author linked to profile')
+ ),
+ ),
+ );
+
+ // Created time.
+ $format_types = system_get_date_types();
+ $date_formatters = array();
+ foreach ($format_types as $formatter) {
+ $date_formatters['ds_post_date_' . $formatter['type']] = t($formatter['title']);
+ }
+ $fields['node']['post_date'] = array(
+ 'title' => t('Post date'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_date_field',
+ 'properties' => array(
+ 'formatters' => $date_formatters,
+ 'entity_render_key' => 'created',
+ ),
+ );
+
+ // Updated time.
+ $fields['node']['changed_date'] = array(
+ 'title' => t('Last modified'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_date_field',
+ 'properties' => array(
+ 'formatters' => $date_formatters,
+ 'entity_render_key' => 'changed',
+ ),
+ );
+
+ // "Submitted by"-line. Skip this if the "Submitted By" module is used.
+ if (!module_exists('submitted_by')) {
+ $date_formatters = array('ds_time_ago' => t('Time ago')) + $date_formatters;
+ $fields['node']['submitted_by'] = array(
+ 'title' => t('Submitted by'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_submitted_by',
+ 'properties' => array(
+ 'formatters' => $date_formatters,
+ ),
+ );
+ }
+
+ // User picture
+ if (variable_get('user_pictures', 0)) {
+ $key = 'user_picture';
+ $type = DS_FIELD_TYPE_IGNORE;
+ $picture_formatters = array();
+ if (module_exists('image')) {
+ $key = 'ds_user_picture';
+ $type = DS_FIELD_TYPE_FUNCTION;
+ $styles = image_styles();
+ foreach ($styles as $formatter) {
+ $picture_formatters['ds_picture_' . $formatter['name']] = drupal_ucfirst(str_replace('_', ' ', $formatter['name']));
+ }
+ }
+ else {
+ $picture_formatters['default'] = t('Default');
+ }
+ $fields['node'][$key] = array(
+ 'title' => t('User picture'),
+ 'field_type' => $type,
+ 'properties' => array(
+ 'formatters' => $picture_formatters,
+ ),
+ );
+ if ($type == DS_FIELD_TYPE_FUNCTION) {
+ $fields['node'][$key]['function'] = 'ds_render_user_picture';
+ }
+ }
+
+ /* --------------------------------------------------------------
+ Book support.
+ -------------------------------------------------------------- */
+
+ if (module_exists('book')) {
+
+ $ui_limit = array();
+ $types = variable_get('book_allowed_types', array('book'));
+ foreach ($types as $type) {
+ $ui_limit[] = $type . '|full';
+ }
+
+ if (!empty($ui_limit)) {
+ $fields['node']['book_navigation'] = array(
+ 'title' => t('Book navigation'),
+ 'field_type' => DS_FIELD_TYPE_IGNORE,
+ 'ui_limit' => $ui_limit,
+ );
+ }
+ }
+
+ /* --------------------------------------------------------------
+ Comment support.
+ -------------------------------------------------------------- */
+
+ if (module_exists('comment')) {
+
+ // Comment Links.
+ $fields['comment']['links'] = array(
+ 'title' => t('Links'),
+ 'field_type' => DS_FIELD_TYPE_IGNORE,
+ );
+
+ // Created time.
+ $format_types = system_get_date_types();
+ $date_formatters = array();
+ foreach ($format_types as $formatter) {
+ $date_formatters['ds_post_date_' . $formatter['type']] = t($formatter['title']);
+ }
+ $fields['comment']['post_date'] = array(
+ 'title' => t('Post date'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_date_field',
+ 'properties' => array(
+ 'formatters' => $date_formatters,
+ 'entity_render_key' => 'created',
+ ),
+ );
+
+ // Permalink.
+ $fields['comment']['permalink'] = array(
+ 'title' => t('Permalink'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ );
+
+ // Submitted.
+ $fields['comment']['submitted'] = array(
+ 'title' => t('Submitted'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ );
+
+ // Title.
+ $fields['comment']['title'] = array(
+ 'title' => t('Title'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ );
+
+ // Author.
+ $fields['comment']['author'] = array(
+ 'title' => t('Author'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ );
+
+ // User signature.
+ if (variable_get('user_signatures', 0)) {
+ $fields['comment']['signature'] = array(
+ 'title' => t('User signature'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ );
+ }
+
+ // User picture
+ if (variable_get('user_pictures', 0)) {
+ $key = 'picture';
+ $type = DS_FIELD_TYPE_PREPROCESS;
+ $picture_formatters = array();
+ if (module_exists('image')) {
+ $key = 'ds_user_picture';
+ $type = DS_FIELD_TYPE_FUNCTION;
+ $styles = image_styles();
+ foreach ($styles as $formatter) {
+ $picture_formatters['ds_picture_' . $formatter['name']] = drupal_ucfirst(str_replace('_', ' ', $formatter['name']));
+ }
+ }
+ else {
+ $picture_formatters['default'] = t('Default');
+ }
+ $fields['comment'][$key] = array(
+ 'title' => t('User picture'),
+ 'field_type' => $type,
+ 'properties' => array(
+ 'formatters' => $picture_formatters,
+ ),
+ );
+ if ($type == DS_FIELD_TYPE_FUNCTION) {
+ $fields['comment'][$key]['function'] = 'ds_render_user_picture';
+ }
+ }
+ }
+
+ /* --------------------------------------------------------------
+ User support.
+ -------------------------------------------------------------- */
+
+ // Username
+ $fields['user']['name'] = array(
+ 'title' => t('Username'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_field',
+ 'properties' => array(
+ 'entity_render_key' => 'name',
+ 'settings' => array(
+ 'link' => array('type' => 'select', 'options' => array('no', 'yes')),
+ 'wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')),
+ 'class' => array('type' => 'textfield', 'description' => t('Put a class on the wrapper. Eg: block-title')),
+ ),
+ 'default' => array('wrapper' => 'h2', 'link' => 0, 'class' => ''),
+ )
+ );
+
+ // User signature
+ if (variable_get('user_signatures', 0)) {
+ $fields['user']['user_signature'] = array(
+ 'title' => t('User signature'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_markup',
+ 'properties' => array(
+ 'key' => 'signature',
+ 'format' => 'signature_format',
+ ),
+ );
+ }
+
+ // User picture
+ if (variable_get('user_pictures', 0)) {
+ $key = 'user_picture';
+ $type = DS_FIELD_TYPE_IGNORE;
+ $picture_formatters = array();
+ if (module_exists('image')) {
+ $key = 'ds_user_picture';
+ $type = DS_FIELD_TYPE_FUNCTION;
+ $styles = image_styles();
+ foreach ($styles as $formatter) {
+ $picture_formatters['ds_picture_' . $formatter['name']] = drupal_ucfirst(str_replace('_', ' ', $formatter['name']));
+ }
+ }
+ else {
+ $picture_formatters['default'] = t('Default');
+ }
+ $fields['user'][$key] = array(
+ 'title' => t('User picture'),
+ 'field_type' => $type,
+ 'properties' => array(
+ 'formatters' => $picture_formatters,
+ ),
+ );
+ if ($type == DS_FIELD_TYPE_FUNCTION) {
+ $fields['user'][$key]['function'] = 'ds_render_user_picture';
+ }
+ }
+
+ /* --------------------------------------------------------------
+ Taxonomy support.
+ -------------------------------------------------------------- */
+
+ if (module_exists('taxonomy')) {
+ // Taxonomy term title.
+ $fields['taxonomy_term']['title'] = array(
+ 'title' => t('Title'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_field',
+ 'properties' => array(
+ 'entity_render_key' => 'name',
+ 'settings' => array(
+ 'link' => array('type' => 'select', 'options' => array('no', 'yes')),
+ 'wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')),
+ 'class' => array('type' => 'textfield', 'description' => t('Put a class on the wrapper. Eg: block-title')),
+ ),
+ 'default' => array('wrapper' => 'h2', 'link' => 0, 'class' => ''),
+ )
+ );
+
+ // Taxonomy term link.
+ $fields['taxonomy_term']['more_link'] = array(
+ 'title' => t('Read more'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_field',
+ 'properties' => array(
+ 'settings' => array(
+ 'link text' => array('type' => 'textfield'),
+ 'wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')),
+ 'class' => array('type' => 'textfield', 'description' => t('Put a class on the wrapper. Eg: block-title')),
+ ),
+ 'default' => array('link text' => 'Read more', 'wrapper' => '', 'class' => '', 'link' => 1),
+ )
+ );
+ }
+
+ // Support for ECK Entity title
+ if (module_exists('eck')) {
+ $entity_info = entity_get_info($entity_type);
+ if (isset($entity_info['module']) && $entity_info['module'] == 'eck') {
+ $fields[$entity_type]['title'] = array(
+ 'title' => t('Title'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_field',
+ 'properties' => array(
+ 'entity_render_key' => 'title',
+ 'settings' => array(
+ 'link' => array('type' => 'select', 'options' => array('no', 'yes')),
+ 'wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')),
+ 'class' => array('type' => 'textfield', 'description' => t('Put a class on the wrapper. Eg: block-title')),
+ ),
+ 'default' => array('wrapper' => 'h2', 'link' => 0, 'class' => ''),
+ )
+ );
+ }
+ }
+
+ // Support for fieldable panels panes.
+ if (module_exists('fieldable_panels_panes')) {
+ $fields['fieldable_panels_pane']['title_ds'] = array(
+ 'title' => t('Display Suite Title'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_render_field',
+ 'properties' => array(
+ 'entity_render_key' => 'title',
+ 'settings' => array(
+ 'link' => array('type' => 'select', 'options' => array('no', 'yes')),
+ 'wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')),
+ 'class' => array('type' => 'textfield', 'description' => t('Put a class on the wrapper. Eg: block-title')),
+ ),
+ 'default' => array('wrapper' => 'h2', 'link' => 0, 'class' => ''),
+ )
+ );
+ }
+
+ if (isset($fields[$entity_type])) {
+ return array($entity_type => $fields[$entity_type]);
+ }
+ return;
+}
diff --git a/sites/all/modules/ds/ds.info b/sites/all/modules/ds/ds.info
new file mode 100644
index 000000000..8ad3bfdab
--- /dev/null
+++ b/sites/all/modules/ds/ds.info
@@ -0,0 +1,21 @@
+name = "Display Suite"
+description = "Extend the display options for every entity type."
+core = "7.x"
+package = "Display Suite"
+dependencies[] = ctools
+files[] = views/views_plugin_ds_entity_view.inc
+files[] = views/views_plugin_ds_fields_view.inc
+files[] = tests/ds.base.test
+files[] = tests/ds.search.test
+files[] = tests/ds.entities.test
+files[] = tests/ds.exportables.test
+files[] = tests/ds.views.test
+files[] = tests/ds.forms.test
+configure = admin/structure/ds
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/ds.install b/sites/all/modules/ds/ds.install
new file mode 100644
index 000000000..b65d8e045
--- /dev/null
+++ b/sites/all/modules/ds/ds.install
@@ -0,0 +1,327 @@
+<?php
+
+/**
+ * @file
+ * Display Suite install file.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function ds_install() {
+ db_update('system')
+ ->fields(array('weight' => 1))
+ ->condition('name', 'ds')
+ ->execute();
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function ds_uninstall() {
+ variable_del('ds_classes_regions');
+}
+
+/**
+ * Implements hook_schema().
+ */
+function ds_schema() {
+
+ $schema['ds_field_settings'] = array(
+ 'description' => 'The table that holds Display Suite field settings per display.',
+
+ // CTools export definitions.
+ 'export' => array(
+ 'key' => 'id',
+ 'identifier' => 'ds_fieldsetting',
+ 'default hook' => 'ds_field_settings_info',
+ 'can disable' => FALSE,
+ 'api' => array(
+ 'owner' => 'ds',
+ 'api' => 'ds',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+
+ 'fields' => array(
+ 'id' => array(
+ 'description' => 'The unique id this setting.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'entity_type' => array(
+ 'description' => 'The name of the entity.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'bundle' => array(
+ 'description' => 'The name of the entity.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'view_mode' => array(
+ 'description' => 'The name of the view_mode.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'settings' => array(
+ 'description' => 'The Display Suite field settings for this layout.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('id'),
+ 'indexes' => array(
+ 'ds_entity' => array('entity_type'),
+ 'ds_bundle' => array('bundle'),
+ 'ds_view_mode' => array('view_mode'),
+ ),
+ );
+
+ $schema['ds_layout_settings'] = array(
+ 'description' => 'The table that holds the layouts configuration.',
+
+ // CTools export definitions.
+ 'export' => array(
+ 'key' => 'id',
+ 'identifier' => 'ds_layout',
+ 'default hook' => 'ds_layout_settings_info',
+ 'can disable' => FALSE,
+ 'api' => array(
+ 'owner' => 'ds',
+ 'api' => 'ds',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+
+ 'fields' => array(
+ 'id' => array(
+ 'description' => 'The unique id the layout.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'entity_type' => array(
+ 'description' => 'The name of the entity.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'bundle' => array(
+ 'description' => 'The name of the entity.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'view_mode' => array(
+ 'description' => 'The name of the view_mode.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'layout' => array(
+ 'description' => 'The name of the layout.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'settings' => array(
+ 'description' => 'The settings for this layout.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('id'),
+ 'indexes' => array(
+ 'ds_entity' => array('entity_type'),
+ 'ds_bundle' => array('bundle'),
+ 'ds_view_mode' => array('view_mode'),
+ ),
+ );
+
+ $schema['ds_view_modes'] = array(
+ 'description' => 'The table that holds custom view modes managed by Display Suite.',
+
+ // CTools export definitions.
+ 'export' => array(
+ 'key' => 'view_mode',
+ 'identifier' => 'ds_view_mode',
+ 'default hook' => 'ds_view_modes_info',
+ 'can disable' => FALSE,
+ 'api' => array(
+ 'owner' => 'ds',
+ 'api' => 'ds',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+
+ 'fields' => array(
+ 'view_mode' => array(
+ 'description' => 'The machine name of the view mode.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'label' => array(
+ 'description' => 'The label of the view mode.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'entities' => array(
+ 'description' => 'The entities for this view mode.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('view_mode'),
+ );
+
+ $schema['ds_fields'] = array(
+ 'description' => 'The table that holds custom fields managed by Display Suite.',
+
+ // CTools export definitions.
+ 'export' => array(
+ 'key' => 'field',
+ 'identifier' => 'ds_field',
+ 'default hook' => 'ds_custom_fields_info',
+ 'can disable' => FALSE,
+ 'api' => array(
+ 'owner' => 'ds',
+ 'api' => 'ds',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+
+ 'fields' => array(
+ 'field' => array(
+ 'description' => 'The machine name of the field.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'label' => array(
+ 'description' => 'The label of the field.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'field_type' => array(
+ 'description' => 'The type of of the field',
+ 'type' => 'int',
+ 'size' => 'small',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'entities' => array(
+ 'description' => 'The entities for this field.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
+ 'ui_limit' => array(
+ 'description' => 'The UI limit for this field.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
+ 'properties' => array(
+ 'description' => 'The properties for this field.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('field'),
+ );
+
+ return $schema;
+}
+
+/**
+ * Add the ui_limit table to the fields table.
+ */
+function ds_update_7201() {
+ $schema = ds_schema();
+ if (!db_field_exists('ds_fields', 'ui_limit')) {
+ db_add_field('ds_fields', 'ui_limit', $schema['ds_fields']['fields']['ui_limit']);
+ }
+}
+
+/**
+ * Increase the label storage length to 128.
+ */
+function ds_update_7202() {
+ db_change_field('ds_fields', 'label', 'label',
+ array(
+ 'description' => 'The label of the field.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ ));
+}
+
+
+/**
+ * Increase the view_mode storage length to 64
+ */
+function ds_update_7203() {
+ db_change_field('ds_field_settings', 'view_mode', 'view_mode',
+ array(
+ 'description' => 'The name of the view_mode.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ )
+ );
+ db_change_field('ds_layout_settings', 'view_mode', 'view_mode',
+ array(
+ 'description' => 'The name of the view_mode.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ )
+ );
+ db_change_field('ds_view_modes', 'view_mode', 'view_mode',
+ array(
+ 'description' => 'The machine name of the view mode.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ )
+ );
+}
diff --git a/sites/all/modules/ds/ds.module b/sites/all/modules/ds/ds.module
new file mode 100644
index 000000000..383c8f51e
--- /dev/null
+++ b/sites/all/modules/ds/ds.module
@@ -0,0 +1,1366 @@
+<?php
+
+/**
+ * @file
+ * Display Suite core functions.
+ */
+
+/**
+ * Constants for field types.
+ */
+define('DS_FIELD_TYPE_THEME', 1);
+define('DS_FIELD_TYPE_FUNCTION', 2);
+define('DS_FIELD_TYPE_PREPROCESS', 3);
+define('DS_FIELD_TYPE_IGNORE', 4);
+define('DS_FIELD_TYPE_CODE', 5);
+define('DS_FIELD_TYPE_BLOCK', 6);
+define('DS_FIELD_TYPE_CTOOLS', 7);
+
+/**
+ * Constants for block fields rendering.
+ */
+define('DS_BLOCK_TEMPLATE', 1);
+define('DS_BLOCK_TITLE_CONTENT', 2);
+define('DS_BLOCK_CONTENT', 3);
+
+/**
+ * Implements hook_permission().
+ */
+function ds_permission() {
+ return array(
+ 'admin_display_suite' => array(
+ 'title' => t('Administer Display Suite'),
+ 'description' => t('General permission for Display Suite settings pages.')
+ ),
+ );
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function ds_hook_info() {
+ $hooks['ds_fields_info'] = array(
+ 'group' => 'ds_fields_info',
+ );
+ return $hooks;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function ds_menu() {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ return _ds_menu();
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function ds_menu_alter(&$items) {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ return _ds_menu_alter($items);
+}
+
+/**
+ * Implements hook_theme().
+ */
+function ds_theme() {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ return _ds_theme();
+}
+
+/**
+ * Implements hook_ds_layout_info().
+ */
+function ds_ds_layout_info() {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ return _ds_ds_layout_info();
+}
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function ds_ctools_plugin_api($owner, $api) {
+ if ($owner == 'ds' && ($api == 'ds' || $api == 'plugins')) {
+ return array('version' => 1);
+ }
+}
+
+/**
+ * Implements hook_ctools_plugin_directory().
+ */
+function ds_ctools_plugin_directory($owner, $plugin_type) {
+ if ($owner == 'ctools' && $plugin_type == 'content_types') {
+ return 'plugins/' . $plugin_type;
+ }
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function ds_views_api() {
+ return array('api' => 3);
+}
+
+/**
+ * Implements hook_node_type_update().
+ */
+function ds_node_type_update($info) {
+ if (!empty($info->old_type) && $info->old_type != $info->type) {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ _ds_entity_type_update('node', $info, 'update');
+ }
+}
+
+/**
+ * Implements hook_node_type_delete().
+ */
+function ds_node_type_delete($info) {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ _ds_entity_type_update('node', $info, 'delete');
+}
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function ds_theme_registry_alter(&$theme_registry) {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ _ds_theme_registry_alter($theme_registry);
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function ds_entity_info_alter(&$entity_info) {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ _ds_entity_info_alter($entity_info);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_form_field_ui_display_overview_form_alter(&$form, &$form_state) {
+ form_load_include($form_state, 'inc', 'ds', 'includes/ds.field_ui');
+ // Also load admin on behalf of DS extras when enabled.
+ if (module_exists('ds_extras')) {
+ form_load_include($form_state, 'inc', 'ds_extras', 'includes/ds_extras.admin');
+ }
+ ds_field_ui_fields_layouts($form, $form_state);
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function ds_module_implements_alter(&$implementations, $hook) {
+ // node_field_display_module_alter() disables all labels on all fields
+ // when the view mode is 'search_index'. If you set display modes for
+ // this view mode by hand, then the hook isn't needed. Since this
+ // may be called hundreds of times on some pages, it's worth disabling it.
+ // See http://drupal.org/node/834278
+ // This code is also in Performance hacks module, but it's not bad to
+ // disable this too in Display Suite by default.
+ if ($hook == 'field_display_node_alter') {
+ unset($implementations['node']);
+ }
+}
+
+/**
+ * Implements hook_features_api().
+ */
+function ds_features_api() {
+ module_load_include('inc', 'ds', 'includes/ds.registry');
+ return _ds_features_api();
+}
+
+/**
+ * Function to check if we go on with Display Suite.
+ */
+function ds_kill_switch() {
+ if (variable_get('ds_disable', FALSE)) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Get entity view modes.
+ *
+ * @param $entity_type
+ * The name of the entity type.
+ */
+function ds_entity_view_modes($entity_type = NULL) {
+ if (!empty($entity_type)) {
+ switch ($entity_type) {
+ // For taxonomy terms the base table and the entity type are different
+ case 'taxonomy_term_data':
+ $entity_info = entity_get_info('taxonomy_term');
+ break;
+ default:
+ $entity_info = entity_get_info($entity_type);
+ break;
+ }
+ return $entity_info['view modes'];
+ }
+}
+
+/**
+ * Get Display Suite layouts.
+ */
+function ds_get_layout_info() {
+ static $layouts = FALSE;
+
+ if (!$layouts) {
+ $errors = array();
+
+ $layouts = module_invoke_all('ds_layout_info');
+
+ // Give modules a chance to alter the layouts information.
+ drupal_alter('ds_layout_info', $layouts);
+
+ // Check that there is no 'content' region, but ignore panel layouts.
+ // Because when entities are rendered, the field items are stored into a
+ // 'content' key so fields would be overwritten before they're all moved.
+ foreach ($layouts as $key => $info) {
+ if (isset($info['panels'])) {
+ continue;
+ }
+ if (isset($info['regions']['content'])) {
+ $errors[] = $key;
+ }
+ }
+ if (!empty($errors)) {
+ drupal_set_message(t('Following layouts have a "content" region key which is invalid: %layouts.', array('%layouts' => implode(', ', $errors))), 'error');
+ }
+ }
+
+ return $layouts;
+}
+
+/**
+ * Get a layout for a given entity.
+ *
+ * @param $entity_type
+ * The name of the entity.
+ * @param $bundle
+ * The name of the bundle.
+ * @param $view_mode
+ * The name of the view mode.
+ * @param $fallback
+ * Whether to fallback to default or not.
+ *
+ * @return $layout
+ * Array of layout variables for the regions.
+ */
+function ds_get_layout($entity_type, $bundle, $view_mode, $fallback = TRUE) {
+
+ static $layouts = array();
+ $layout_key = $entity_type . '_' . $bundle . '_' . $view_mode;
+
+ if (!isset($layouts[$layout_key])) {
+
+ $entity_info = entity_get_info();
+
+ $overridden = TRUE;
+ if ($view_mode != 'form') {
+ $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
+ $overridden = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? TRUE : FALSE);
+ }
+ // Check if a layout is configured.
+ if (isset($entity_info[$entity_type]['bundles'][$bundle]['layouts'][$view_mode]) && ($overridden || $view_mode == 'default')) {
+ $layouts[$layout_key] = $entity_info[$entity_type]['bundles'][$bundle]['layouts'][$view_mode];
+ $layouts[$layout_key]['view_mode'] = $view_mode;
+ }
+
+ // In case $view_mode is not found, check if we have a default layout,
+ // but only if the view mode settings aren't overridden for this view mode.
+ if ($view_mode != 'default' && !$overridden && $fallback && isset($entity_info[$entity_type]['bundles'][$bundle]['layouts']['default'])) {
+ $layouts[$layout_key] = $entity_info[$entity_type]['bundles'][$bundle]['layouts']['default'];
+ $layouts[$layout_key]['view_mode'] = 'default';
+ }
+
+ // Register the false return value as well.
+ if (!isset($layouts[$layout_key])) {
+ $layouts[$layout_key] = FALSE;
+ }
+ }
+
+ return $layouts[$layout_key];
+}
+
+/**
+ * Get all fields.
+ *
+ * @param $entity_type
+ * The name of the entity.
+ * @param $cache
+ * Whether we need to get the fields from cache or not.
+ * @return
+ * Collection of fields.
+ */
+function ds_get_fields($entity_type, $cache = TRUE) {
+ global $language;
+
+ static $static_fields, $fields_cached = array();
+ static $loaded = FALSE;
+ // Load the ds file that handles ds call of hook_ds_fields_info, otherwise it
+ // doesn't get loaded
+ module_load_include('inc', 'ds', 'ds.ds_fields_info');
+
+ // Get fields from cache.
+ if (!$loaded) {
+ $loaded = TRUE;
+ if ($cache && $cached_fields = cache_get('ds_fields:' . $language->language)) {
+ $fields_cached = $static_fields = $cached_fields->data;
+ }
+ }
+
+ if (!isset($static_fields[$entity_type])) {
+
+ // Do we have them cached or not ?
+ if (!isset($fields_cached[$entity_type])) {
+
+ // Get all fields for this entity type.
+ $fields = array();
+ foreach (module_implements('ds_fields_info') as $module) {
+ $function = $module . '_ds_fields_info';
+ $all_fields = $function($entity_type);
+ if (!empty($all_fields)) {
+ foreach ($all_fields as $key => $field_results) {
+ if ($key === $entity_type) {
+ // Add module key to field definition.
+ foreach ($field_results as $f => $d) {
+ $field_results[$f]['module'] = $module;
+ }
+ $fields = array_merge($field_results, $fields);
+ }
+ }
+ }
+ }
+
+ // Give modules a change to alter fields.
+ drupal_alter('ds_fields_info', $fields, $entity_type);
+
+ $fields_cached[$entity_type] = $fields;
+
+ // Cache the fields.
+ if ($cache) {
+ cache_set('ds_fields:' . $language->language, $fields_cached, 'cache');
+ }
+ }
+ else {
+ $fields = $fields_cached[$entity_type];
+ }
+
+ // Store the fields statically.
+ $static_fields[$entity_type] = $fields;
+ }
+
+ return isset($static_fields[$entity_type]) ? $static_fields[$entity_type] : array();
+}
+
+/**
+ * Get the field settings.
+ *
+ * @param $entity_type
+ * The name of the entity.
+ * @param $bundle
+ * The name of bundle (ie, page or story for node types, profile for users)
+ * @param $view_mode
+ * The name of view mode.
+ */
+function ds_get_field_settings($entity_type, $bundle, $view_mode, $default = TRUE) {
+ static $field_settings = NULL;
+
+ if (!isset($field_settings)) {
+ if ($cache = cache_get('ds_field_settings')) {
+ $field_settings = $cache->data;
+ }
+ else {
+ ctools_include('export');
+ $ds_field_settings = ctools_export_crud_load_all('ds_field_settings');
+ foreach ($ds_field_settings as $layout => $layout_settings) {
+ // Do not store configuration when the field settings is disabled.
+ if (!empty($layout_settings->disabled)) {
+ continue;
+ }
+ // Do not store configuration if settings key is not set.
+ if (!isset($layout_settings->settings)) {
+ continue;
+ }
+ foreach ($layout_settings->settings as $field => $settings) {
+ $field_settings[$layout_settings->entity_type][$layout_settings->bundle][$layout_settings->view_mode][$field] = $settings;
+ }
+ }
+ cache_set('ds_field_settings', $field_settings, 'cache');
+ }
+ }
+
+ return (isset($field_settings[$entity_type][$bundle][$view_mode])) ? $field_settings[$entity_type][$bundle][$view_mode] : (isset($field_settings[$entity_type][$bundle]['default']) && $default ? $field_settings[$entity_type][$bundle]['default'] : array());
+}
+
+/**
+ * Get the value for a Display Suite field.
+ *
+ * @param $key
+ * The key of the field.
+ * @param $field
+ * The configuration of a DS field.
+ * @param $entity
+ * The current entity.
+ * @param $entity_type
+ * The name of the entity.
+ * @param $bundle
+ * The name of the bundle.
+ * @param $view_mode
+ * The name of the view mode.
+ * @param $build
+ * The current built of the entity.
+ * @return $markup
+ * The markup of the field used for output.
+ */
+function ds_get_field_value($key, $field, $entity, $entity_type, $bundle, $view_mode, $build = array()) {
+
+ $field['field_name'] = $key;
+ $field['entity'] = $entity;
+ $field['entity_type'] = $entity_type;
+ $field['bundle'] = $bundle;
+ $field['view_mode'] = $view_mode;
+ $field['build'] = $build;
+
+ // Special case for ds_views which can handle custom fields now.
+ if ($field['field_type'] != DS_FIELD_TYPE_PREPROCESS && $entity_type == 'ds_views') {
+ $entity->preprocess_fields[] = $key;
+ }
+
+ switch ($field['field_type']) {
+
+ case DS_FIELD_TYPE_PREPROCESS:
+ $entity->preprocess_fields[] = $key;
+ break;
+
+ case DS_FIELD_TYPE_FUNCTION:
+ if (isset($field['file'])) {
+ include_once $field['file'];
+ }
+ return $field['function']($field);
+
+ case DS_FIELD_TYPE_THEME:
+ $format = (isset($field['formatter'])) ? $field['formatter'] : key($field['properties']['formatters']);
+ return theme($format, $field);
+
+ case DS_FIELD_TYPE_CODE:
+ return ds_render_code_field($field);
+
+ case DS_FIELD_TYPE_CTOOLS:
+ return ds_render_ctools_field($field);
+
+ case DS_FIELD_TYPE_BLOCK:
+ return ds_render_block_field($field);
+ }
+}
+
+/**
+ * Implements hook_field_attach_view_alter().
+ */
+function ds_field_attach_view_alter(&$build, $context) {
+ static $loaded_css = array();
+
+ // Global kill switch. In some edge cases, a view might
+ // be inserted into the view of an entity, in which the
+ // same entity is available as well. This is simply not
+ // possible, so you can temporarily disable DS completely
+ // by setting this variable, either from code or via
+ // the UI through admin/structure/ds/
+ if (ds_kill_switch()) {
+ return;
+ }
+
+ // If views and core doesn't send information along on the entity,
+ // Display Suite doesn't have a context to render the fields.
+ if (!isset($build['#entity_type']) && !isset($build['#bundle'])) {
+ return;
+ }
+
+ // If no layout is configured, stop as well.
+ if (!ds_get_layout($build['#entity_type'], $build['#bundle'], $context['view_mode'])) {
+ return;
+ }
+
+ $entity_type = $build['#entity_type'];
+ $bundle = $build['#bundle'];
+ $view_mode = $context['view_mode'];
+ $entity = $context['entity'];
+ $layout = ds_get_layout($entity_type, $bundle, $view_mode);
+
+ // Check on field/delta limit.
+ if (isset($layout['settings']['limit'])) {
+ foreach ($layout['settings']['limit'] as $field => $limit) {
+ if (isset($build[$field])) {
+ if ($limit === 'delta' && isset($entity->ds_delta) && isset($entity->ds_delta[$field])) {
+ $delta = $entity->ds_delta[$field];
+ foreach ($build[$field]['#items'] as $key => $item) {
+ if ($key != $delta) {
+ unset($build[$field][$key]);
+ }
+ }
+ }
+ else {
+ $count = count($build[$field]['#items']);
+ if ($count > $limit) {
+ $build[$field]['#items'] = array_slice($build[$field]['#items'], 0, $limit);
+ }
+ }
+ }
+ }
+ }
+
+ // Add Display Suite display fields.
+ $fields = ds_get_fields($entity_type);
+ $field_values = ds_get_field_settings($entity_type, $bundle, $layout['view_mode']);
+
+ foreach ($field_values as $key => $field) {
+
+ // Ignore if this field is not a DS field.
+ if (!isset($fields[$key])) {
+ continue;
+ }
+
+ $field = $fields[$key];
+ if (isset($field_values[$key]['format'])) {
+ $field['formatter'] = $field_values[$key]['format'];
+ }
+
+ if (isset($field_values[$key]['formatter_settings'])) {
+ $field['formatter_settings'] = $field_values[$key]['formatter_settings'];
+ }
+ $field_value = ds_get_field_value($key, $field, $entity, $entity_type, $bundle, $view_mode, $build);
+
+ // Title label.
+ if ($key == 'title' && $entity_type == 'node') {
+ $node_type = node_type_get_type($entity);
+ $field['title'] = function_exists('i18n_node_translate_type') ? i18n_node_translate_type($node_type->type, 'title_label', $node_type->title_label) : $node_type->title_label;
+ }
+
+ if (!empty($field_value) || (string) $field_value === '0') {
+
+ // Special case for views.
+ if (!empty($build['render_code_fields'])) {
+ $build[$key] = $field_value;
+ }
+ else {
+ $build[$key] = array(
+ '#theme' => 'field',
+ '#field_type' => 'ds',
+ '#skip_edit' => TRUE,
+ '#title' => $field['title'],
+ '#weight' => isset($field_values[$key]['weight']) ? $field_values[$key]['weight'] : 0,
+ '#label_display' => isset($field_values[$key]['label']) ? $field_values[$key]['label'] : 'inline',
+ '#field_name' => $key,
+ '#bundle' => $bundle,
+ '#object' => $entity,
+ '#entity_type' => $entity_type,
+ '#view_mode' => $view_mode,
+ '#access' => (variable_get('ds_extras_field_permissions', FALSE) && function_exists('ds_extras_ds_field_access')) ? ds_extras_ds_field_access($key, $entity_type) : TRUE,
+ '#items' => array(
+ 0 => array(
+ 'value' => $field_value,
+ ),
+ ),
+ 0 => array(
+ '#markup' => $field_value,
+ ),
+ );
+ }
+ }
+ }
+
+ $disable_css = FALSE;
+ if (!empty($layout['settings']['layout_disable_css'])) {
+ $disable_css = TRUE;
+ }
+
+ // Add path to css file for this layout and disable block regions if necessary.
+ if (!$disable_css && isset($layout['css']) && !isset($loaded_css[$layout['path'] . '/' . $layout['layout'] . '.css'])) {
+ // Register css file.
+ $loaded_css[$layout['path'] . '/' . $layout['layout'] . '.css'] = TRUE;
+ // Add css file.
+ if (isset($layout['module']) && $layout['module'] == 'panels') {
+ $build['#attached']['css'][] = $layout['path'] . '/' . $layout['panels']['css'];
+ }
+ else {
+ $build['#attached']['css'][] = $layout['path'] . '/' . $layout['layout'] . '.css';
+ }
+ }
+}
+
+/**
+ * Add variables to an entity.
+ *
+ * This function is added in ds_theme_registry_alter().
+ */
+function ds_entity_variables(&$vars) {
+ if (isset($vars['elements']) && isset($vars['elements']['#bundle']) && $layout = ds_get_layout($vars['elements']['#entity_type'], $vars['elements']['#bundle'], $vars['elements']['#view_mode'])) {
+
+ $render_container = 'content';
+ // User uses user_profile as container.
+ if ($vars['elements']['#entity_type'] == 'user') {
+ $render_container = 'user_profile';
+ }
+
+ // Move any preprocess fields to render container.
+ // Inconsistency in taxonomy term naming.
+ $entity_type = $vars['elements']['#entity_type'];
+ if ($vars['elements']['#entity_type'] == 'taxonomy_term') {
+ $entity_type = 'term';
+ }
+
+ // Get entity id and bundle
+ $id = NULL;
+ $bundle = $vars['elements']['#bundle'];
+ $entity = isset($vars[$entity_type]) ? $vars[$entity_type] : (isset($vars['elements']['#' . $entity_type]) ? $vars['elements']['#' . $entity_type] : NULL);
+ list($id,, $bundle) = entity_extract_ids($entity_type, $entity);
+
+ if (isset($vars[$entity_type]->preprocess_fields)) {
+ foreach ($vars[$entity_type]->preprocess_fields as $field) {
+
+ // Process RDF if the module is enabled before moving preprocess fields.
+ if (module_exists('rdf')) {
+ rdf_process($vars, $field);
+ // Remove it so we can unset the field later on.
+ unset($vars['rdf_template_variable_attributes_array'][$field]);
+ }
+
+ // Move the field to content so it renders, remove it
+ // because we don't need it anymore.
+ if (isset($vars[$field])) {
+ $vars[$render_container][$field] = array('#markup' => $vars[$field]);
+ if (!isset($vars['preprocess_keep'])) {
+ unset($vars[$field]);
+ }
+ }
+ }
+ }
+
+ // Check if this is a flexible panels layout.
+ if (!empty($layout['flexible'])) {
+ ctools_include('plugins', 'panels');
+ $vars['css_id'] = $vars['settings'] = $vars['display'] = $vars['renderer'] = '';
+ $vars['layout'] = panels_get_layout($layout['panels']['name']);
+ $vars['theme_hook_suggestion'] = 'panels_flexible';
+ }
+ else {
+ // Template layout.
+ if (!isset($vars['classes_array'])) {
+ $vars['classes_array'] = array();
+ }
+
+ // Add view-mode-{name} to classes.
+ if (!in_array('view-mode-' . $vars['elements']['#view_mode'], $vars['classes_array'])) {
+ $vars['classes_array'][] = 'view-mode-' . $vars['elements']['#view_mode'];
+ }
+
+ // In case this is a panels layout, use panels info array.
+ if (isset($layout['module']) && $layout['module'] == 'panels') {
+ $layout['layout'] = $layout['panels']['theme'];
+ }
+
+ $bundle = strtr($bundle, '-', '_');
+ $vars['theme_hook_suggestions'][] = $layout['layout'];
+ $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'];
+ $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'] . '_' . $vars['elements']['#view_mode'];
+ $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'] . '_' . $bundle;
+ $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'] . '_' . $bundle . '_' . $vars['elements']['#view_mode'];
+ if (!empty($id)) {
+ $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'] . '__' . $id;
+ }
+ }
+
+ // If the layout has wrapper class lets add it.
+ if (isset($layout['settings']['classes']['layout_class'])) {
+ foreach ($layout['settings']['classes']['layout_class'] as $layout_class) {
+ $vars['classes_array'][] = $layout_class;
+ }
+ }
+
+ $layout_render_array = array();
+ // Create region variables based on the layout settings.
+ foreach ($layout['regions'] as $region_name => $region) {
+
+ // Create the region content.
+ $layout_render_array[$region_name] = array();
+ if (isset($layout['settings']['regions'][$region_name])) {
+ foreach ($layout['settings']['regions'][$region_name] as $key => $field) {
+ // Make sure the field exists.
+ if (!isset($vars[$render_container][$field])) {
+ continue;
+ }
+ if (!isset($vars[$render_container][$field]['#weight'])) {
+ $vars[$render_container][$field]['#weight'] = $key;
+ }
+ $layout_render_array[$region_name][$key] = $vars[$render_container][$field];
+ }
+ }
+
+ // Add extras classes to the region.
+ if (empty($layout['flexible'])) {
+ $vars[$region_name . '_classes'] = !empty($layout['settings']['classes'][$region_name]) ? ' ' . implode(' ', $layout['settings']['classes'][$region_name]) : '';
+
+ // Token support for region classes.
+ if (module_exists('token') && isset($vars[$entity_type])) {
+ $vars[$region_name . '_classes'] = token_replace($vars[$region_name . '_classes'], array($entity_type => $vars[$entity_type]), array('clear' => TRUE, 'sanitize' => TRUE));
+ }
+ }
+ // Add a wrapper to the region.
+ if (empty($layout['flexible'])) {
+ $vars[$region_name . '_wrapper'] = isset($layout['settings']['wrappers'][$region_name]) ? $layout['settings']['wrappers'][$region_name] : 'div';
+ }
+ }
+
+ // Let other modules know we have rendered.
+ $vars['rendered_by_ds'] = TRUE;
+
+ // Add a layout wrapper.
+ $vars['layout_wrapper'] = isset($layout['settings']['layout_wrapper']) ? $layout['settings']['layout_wrapper'] : 'div';
+
+ // Add layout attributes if any.
+ $vars['layout_attributes'] = '';
+ if (!empty($layout['settings']['layout_attributes'])) {
+ if (isset($vars[$entity_type])) {
+ $vars['layout_attributes'] .= ' ' . token_replace($layout['settings']['layout_attributes'], array($entity_type => $vars[$entity_type]), array('clear' => TRUE, 'sanitize' => TRUE));
+ }
+ else {
+ $vars['layout_attributes'] .= ' ' . $layout['settings']['layout_attributes'];
+ }
+ }
+ // Merge in other attributes which were passed to the template.
+ if (!empty($layout['settings']['layout_attributes_merge'])) {
+ // Handle classes separately.
+ if (isset($vars['attributes_array']['class'])) {
+ $vars['classes_array'] += $vars['attributes_array']['class'];
+ unset($vars['attributes_array']['class']);
+ }
+ $vars['layout_attributes'] .= ' ' . drupal_attributes($vars['attributes_array']);
+ }
+
+ // Token support for layout classes.
+ if (module_exists('token') && isset($vars[$entity_type])) {
+ foreach ($vars['classes_array'] as &$class) {
+ $class = token_replace($class, array($entity_type => $vars[$entity_type]), array('clear' => TRUE, 'sanitize' => TRUE));
+ }
+ }
+
+ // Add an onclick attribute on the wrapper.
+ if (!empty($layout['settings']['layout_link_attribute'])) {
+ $url = '';
+ switch ($layout['settings']['layout_link_attribute']) {
+ case 'content':
+ if ($entity_type == 'user') {
+ $uri = entity_uri($vars['elements']['#entity_type'], $vars['elements']['#account']);
+ }
+ else {
+ $uri = entity_uri($vars['elements']['#entity_type'], $vars[$entity_type]);
+ }
+ if (!empty($uri)) {
+ $url = $uri['path'];
+ }
+ break;
+ case 'custom':
+ $url = $layout['settings']['layout_link_custom'];
+ break;
+ case 'tokens':
+ $url = token_replace($layout['settings']['layout_link_custom'], array($vars['elements']['#entity_type'] => $vars[$entity_type]), array('clear' => TRUE));
+ break;
+ }
+
+ if (!empty($url)) {
+ $vars['layout_attributes'] .= ' onclick="location.href=\'' . url($url) . '\'"';
+ }
+ }
+
+ // Set field weights for all fields, including pre-process.
+ foreach ($layout_render_array as $region => &$fields) {
+ foreach ($fields as $field_key => &$field) {
+ $field['#weight'] = $field_key;
+ }
+ }
+
+ // Let other modules alter the ds array before creating the region variables.
+ $context = array('entity' => isset($vars[$entity_type]) ? $vars[$entity_type] : (isset($vars['elements']['#' . $entity_type]) ? $vars['elements']['#' . $entity_type] : ''), 'entity_type' => $vars['elements']['#entity_type'], 'bundle' => $vars['elements']['#bundle'], 'view_mode' => $vars['elements']['#view_mode']);
+ drupal_alter('ds_pre_render', $layout_render_array, $context, $vars);
+ foreach ($layout_render_array as $region_name => $content) {
+ // In case this is a panels layout, add the region to the $content variable.
+ if (isset($layout['module']) && $layout['module'] == 'panels') {
+ $vars['content'][$region_name] = drupal_render($content);
+ }
+ else {
+ $vars[$region_name] = drupal_render($content);
+ }
+ }
+ }
+}
+
+/**
+ * Create entity context.
+ */
+function ds_create_entity_context($entity_type, $entity, &$contexts, $context_arguments = array()) {
+ ctools_include('context');
+ if (empty($context_arguments)) {
+ $context_arguments = array(array(
+ 'keyword' => $entity_type,
+ 'identifier' => drupal_ucfirst($entity_type) . ' being viewed',
+ 'id' => 1,
+ 'name' => 'entity_id:' . $entity_type,
+ 'settings' => array(),
+ ));
+ }
+ ctools_context_get_context_from_arguments($context_arguments, $contexts, array($entity));
+}
+
+/**
+ * Render a code field.
+ */
+function ds_render_code_field($field) {
+ if (isset($field['properties']['code'])) {
+ $value = $field['properties']['code']['value'];
+ // Token support - check on token property so we don't run every single field through token.
+ if (isset($field['properties']['use_token']) && $field['properties']['use_token']) {
+ $value = token_replace($value, array($field['entity_type'] => $field['entity']), array('clear' => TRUE));
+ }
+ $format = (isset($field['properties']['code']['format'])) ? $field['properties']['code']['format'] : 'plain_text';
+ if ($format == 'ds_code' && module_exists('ds_format')) {
+ $value = ds_format_php_eval($value, $field['entity'], isset($field['build']) ? $field['build'] : array());
+ }
+ else {
+ $value = check_markup($value, $format);
+ }
+ return $value;
+ }
+}
+
+/**
+ * Render a CTools field.
+ */
+function ds_render_ctools_field($field) {
+ if (isset($field['formatter_settings']['ctools'])) {
+
+ // Extreme use case where a taxonomy_term object is not
+ // loaded on the entity and triggers notices if a view is embedded
+ // with taxonomy term fields from the same object.
+ // see http://drupal.org/node/1238132 - To reproduce:
+ // 1) add 2 taxonomy field instances on a bundle
+ // 2) configure a ds layout showing only one
+ // 3) embed a view with the 2 taxonomies as fields.
+ if (isset($field['formatter_settings']['load_terms']) && $field['formatter_settings']['load_terms']) {
+ static $terms_loaded = array();
+ if (isset($field['entity']->language)) {
+ $language = $field['entity']->language;
+ }
+ else {
+ $language = LANGUAGE_NONE;
+ }
+ $instances = field_info_instances($field['entity_type'], $field['bundle']);
+ foreach ($instances as $key => $instance) {
+ $info = field_info_field($key);
+ if ($info['module'] == 'taxonomy') {
+ if (empty($field['entity']->{$key})) {
+ continue;
+ }
+ if (!isset($field['entity']->{$key}[$language])) {
+ $language = LANGUAGE_NONE;
+ }
+ foreach ($field['entity']->{$key}[$language] as $tkey => $item) {
+ if (isset($item['tid']) && !isset($item['taxonomy_term'])) {
+ if (!isset($terms_loaded[$item['tid']])) {
+ $term = taxonomy_term_load($item['tid']);
+ if (!is_object($term)) {
+ // This term is missing in the database.
+ continue;
+ }
+ $terms_loaded[$item['tid']] = $term;
+ }
+ $field['entity']->{$key}[$language][$tkey]['taxonomy_term'] = $terms_loaded[$item['tid']];
+ }
+ }
+ }
+ }
+ }
+
+ ctools_include('content');
+ ctools_include('context');
+
+ // Get variables.
+ $show_title = $field['formatter_settings']['show_title'];
+ $title_wrapper = trim($field['formatter_settings']['title_wrapper']);
+ $ctools = unserialize($field['formatter_settings']['ctools']);
+ $type = $ctools['type'];
+ $subtype = $ctools['subtype'];
+ $conf = $ctools['conf'];
+ $entity_type = $field['entity_type'];
+ $keywords = $arguments = $contexts = array();
+
+ // Create entity context.
+ ds_create_entity_context($entity_type, $field['entity'], $contexts);
+
+ // Build the content.
+ $data = ctools_content_render($type, $subtype, $conf, $keywords, $arguments, $contexts);
+ // Return content.
+ if (!empty($data->content)) {
+ $content = '';
+ if ($show_title) {
+ if (empty($title_wrapper)) $title_wrapper = 'div';
+ $content .= '<' . check_plain($title_wrapper) . ' class="title">' . $data->title . '</' . check_plain($title_wrapper) . '>';
+ }
+ if (is_array($data->content)) {
+ $content .= drupal_render($data->content);
+ }
+ else {
+ $content .= $data->content;
+ }
+ return $content;
+ }
+ }
+}
+
+/**
+ * Render a block field.
+ */
+function ds_render_block_field($field) {
+ // Invoke the block_view hook of the module.
+ list($module, $delta) = explode('|', $field['properties']['block']);
+ $block = module_invoke($module, 'block_view', $delta);
+
+ // Get contextual links.
+ $contextual_links = array();
+ $contextual = module_exists('contextual') && user_access('access contextual links');
+ if ($contextual) {
+ if (isset($block['content']) && is_array($block['content']) && isset($block['content']['#contextual_links'])) {
+ $contextual_links = $block['content']['#contextual_links'];
+ }
+ }
+
+ // Render the block content.
+ if (isset($block['content']) && is_array($block['content'])) {
+ $block['content'] = drupal_render($block['content']);
+ }
+
+ // Go through in case we have actual content.
+ if (!empty($block['content'])) {
+
+ // Make sure subject is set.
+ if (!isset($block['subject'])) {
+ $block['subject'] = '';
+ }
+
+ global $theme_key;
+ if (module_exists('block')) {
+ $full_block = db_query("SELECT * FROM {block} WHERE module = :module AND delta = :delta AND theme = :theme", array(':module' => $module, ':delta' => $delta, ':theme' => $theme_key))->fetchObject();
+ }
+ if (!empty($full_block)) {
+ if ($full_block->title == '<none>') {
+ $block['subject'] = '';
+ }
+ elseif (!empty($full_block->title)) {
+ $block['subject'] = $full_block->title;
+ }
+ }
+
+ // i18n support.
+ if (function_exists('i18n_block_block_view_alter')) {
+
+ // Check language visibility.
+ global $language;
+ static $block_languages = FALSE;
+ if (!$block_languages) {
+ $block_languages = array();
+ $result = db_query('SELECT module, delta, language FROM {i18n_block_language}');
+ foreach ($result as $record) {
+ $block_languages[$record->module][$record->delta][$record->language] = TRUE;
+ }
+ }
+ if (isset($block_languages[$module][$delta]) && !isset($block_languages[$module][$delta][$language->language])) {
+ return;
+ }
+
+ // Translate.
+ if (!empty($full_block->i18n_mode)) {
+ i18n_block_block_view_alter($block, $full_block);
+ if (!empty($block['title'])) {
+ $block['subject'] = $block['title'];
+ }
+ }
+ }
+
+ $block = (object) $block;
+ switch ($field['properties']['block_render']) {
+ case DS_BLOCK_TEMPLATE:
+ $block->region = NULL;
+ $block->module = $module;
+ $block->delta = $delta;
+ $elements = array('elements' => array('#block' => $block, '#children' => $block->content));
+ // Add contextual links.
+ if ($contextual) {
+ $elements['elements'] += array('#contextual_links' => array_merge($contextual_links, array('block' => array('admin/structure/block/manage', array($block->module, $block->delta)))));
+ }
+ return theme('block', $elements);
+ break;
+ case DS_BLOCK_TITLE_CONTENT:
+ return '<h2 class="block-title">' . $block->subject . '</h2>' . $block->content;
+ break;
+ case DS_BLOCK_CONTENT:
+ return $block->content;
+ break;
+ }
+ }
+}
+
+/**
+ * Render a field.
+ */
+function ds_render_field($field) {
+ $title_field = FALSE;
+
+ $output = '';
+ $settings = isset($field['formatter_settings']) ? $field['formatter_settings'] : array();
+ $settings += $field['properties']['default'];
+
+ // Basic string.
+ if (isset($settings['link text'])) {
+ $output = t($settings['link text']);
+ }
+ elseif (isset($field['properties']['entity_render_key']) && isset($field['entity']->{$field['properties']['entity_render_key']})) {
+ if ($field['entity_type'] == 'user' && $field['properties']['entity_render_key'] == 'name') {
+ $output = format_username($field['entity']);
+ }
+ else {
+ $title_field = $field['properties']['entity_render_key'] == 'title' && $field['entity_type'] == 'node';
+ $output = $field['entity']->{$field['properties']['entity_render_key']};
+ }
+ }
+
+ if (empty($output)) {
+ return;
+ }
+
+ // Link.
+ if ($settings['link']) {
+ if (isset($field['entity']->uri)) {
+ $uri_info = $field['entity']->uri;
+ }
+ else {
+ $uri_info = entity_uri($field['entity_type'], $field['entity']);
+ }
+ if (isset($settings['link class'])) {
+ $uri_info['options']['attributes']['class'][] = $settings['link class'];
+ }
+ $output = l($output, $uri_info['path'], $uri_info['options']);
+ if ($title_field) {
+ $output = ds_edit_support('title', $output, $field);
+ }
+ }
+ else {
+ $output = check_plain($output);
+ if ($title_field) {
+ $output = ds_edit_support('title', $output, $field);
+ }
+ }
+
+ // Wrapper and class.
+ if (!empty($settings['wrapper'])) {
+ $wrapper = check_plain($settings['wrapper']);
+ $class = (!empty($settings['class'])) ? ' class="' . check_plain($settings['class']) . '"' : '';
+ $output = '<' . $wrapper . $class . '>' . $output . '</' . $wrapper . '>';
+ }
+
+ return $output;
+}
+
+/**
+ * Support for edit module.
+ *
+ * @param $field_name
+ * The name of the field.
+ * @param $output
+ * The output of the field.
+ * @param $field
+ * The complete field array.
+ *
+ * @return
+ * The field ready for edit module or the same value in case
+ * the edit module is not enabled.
+ */
+function ds_edit_support($field_name, $output, $field) {
+
+ if (module_exists('edit')) {
+ $edit_id = "node/" . $field['entity']->nid . "/" . $field_name . "/" . $field['entity']->language . "/" . $field['view_mode'];
+ $output = edit_wrap_pseudofield($output, $edit_id);
+ }
+
+ return $output;
+}
+
+/**
+ * Render an author field.
+ */
+function ds_render_author_field($field) {
+
+ // Users without a user name are anonymous users. These are never linked.
+ if (empty($field['entity']->name)) {
+ $output = check_plain(variable_get('anonymous', t('Anonymous')));
+ }
+
+ if ($field['formatter'] == 'author') {
+ $output = format_username($field['entity']);
+ }
+
+ if ($field['formatter'] == 'author_linked') {
+ $output = theme('username', array('account' => $field['entity']));
+ }
+
+ return ds_edit_support('author', $output, $field);
+}
+
+/**
+ * Render a markup field.
+ */
+function ds_render_markup($field) {
+ if (isset($field['entity']->{$field['properties']['key']})) {
+ // Check for format, and let's take filtered_html as a sane default.
+ $format = isset($field['entity']->{$field['properties']['format']}) ? $field['entity']->{$field['properties']['format']} : 'filtered_html';
+ return check_markup($field['entity']->{$field['properties']['key']}, $format, '', TRUE);;
+ }
+}
+
+/**
+ * Return the picture.
+ */
+function ds_return_picture($entity) {
+
+ // Gravatar support.
+ if (module_exists('gravatar')) {
+ $entity = _gravatar_load_account($entity);
+ $entity->picture = _gravatar_get_account_user_picture($entity);
+ }
+
+ if (!empty($entity->picture)) {
+ if (is_numeric($entity->picture)) {
+ return file_load($entity->picture);
+ }
+ else {
+ return $entity->picture;
+ }
+ }
+ elseif (variable_get('user_picture_default', '')) {
+ return variable_get('user_picture_default', '');
+ }
+}
+
+/**
+ * Render a user picture.
+ */
+function ds_render_user_picture($field) {
+ $picture = ds_return_picture($field['entity']);
+
+ if (!empty($picture)) {
+ $filepath = (isset($picture->uri)) ? $picture->uri : $picture;
+ $name = format_username($field['entity']);
+ $alt = t("@user's picture", array('@user' => $name));
+ $vars = array('path' => $filepath, 'alt' => $alt, 'title' => $alt);
+
+ // If the image does not have a valid Drupal scheme (for eg. HTTP),
+ // don't load image styles.
+ if (module_exists('image') && file_valid_uri($filepath)) {
+ $vars['style_name'] = str_replace('ds_picture_', '', $field['formatter']);
+ $image = theme('image_style', $vars);
+ }
+ else {
+ $image = theme('image', $vars);
+ }
+
+ if (!empty($field['entity']->uid) && user_access('access user profiles')) {
+ return l($image, 'user/' . $field['entity']->uid, array('html' => TRUE));
+ }
+ else {
+ return $image;
+ }
+ }
+}
+
+/**
+ * Render a date field.
+ */
+function ds_render_date_field($field) {
+ $date_format = str_replace('ds_post_date_', '', $field['formatter']);
+ return ds_edit_support($field['properties']['entity_render_key'], format_date($field['entity']->{$field['properties']['entity_render_key']}, $date_format), $field);
+}
+
+/**
+ * Render a "Submitted by"-line.
+ */
+function ds_render_submitted_by($field) {
+ $account = user_load($field['entity']->uid);
+ switch ($field['formatter']) {
+ case 'ds_time_ago':
+ $interval = REQUEST_TIME - $field['entity']->created;
+ return t('Submitted !interval ago by !user.', array('!interval' => format_interval($interval), '!user' => theme('username', array('account' => $account))));
+ default:
+ $date_format = str_replace('ds_post_date_', '', $field['formatter']);
+ return t('Submitted by !user on !date.', array('!user' => theme('username', array('account' => $account)), '!date' => format_date($field['entity']->created, $date_format)));
+ }
+}
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function ds_field_formatter_info() {
+
+ $formatters = array();
+ if (module_exists('taxonomy')) {
+ $formatters['ds_taxonomy_view_mode'] = array(
+ 'label' => t('Rendered taxonomy term'),
+ 'description' => t('Display the referenced term in a specific view mode'),
+ 'field types' => array('taxonomy_term_reference'),
+ 'settings' => array(
+ 'taxonomy_term_reference_view_mode' => 'full',
+ 'use_content_language' => TRUE,
+ ),
+ );
+ $formatters['ds_taxonomy_separator'] = array(
+ 'label' => t('Separated'),
+ 'description' => t('Display the referenced term with a separator.'),
+ 'field types' => array('taxonomy_term_reference'),
+ 'settings' => array(
+ 'taxonomy_term_link' => TRUE,
+ 'taxonomy_term_separator' => ', ',
+ ),
+ );
+
+ if (module_exists('i18n_taxonomy')) {
+ $formatters['ds_taxonomy_separator_localized'] = array(
+ 'label' => t('Separated (localized)'),
+ 'description' => t('Display the referenced term with a separator. Use this with the "localize" translation mode for vocabularies.'),
+ 'field types' => array('taxonomy_term_reference'),
+ 'settings' => array(
+ 'taxonomy_term_link' => TRUE,
+ 'taxonomy_term_separator' => ', ',
+ ),
+ );
+ }
+ }
+
+ return $formatters;
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ */
+function ds_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+ global $language;
+ $element = array();
+
+ if ($display['type'] === 'ds_taxonomy_view_mode') {
+ $view_mode = $display['settings']['taxonomy_term_reference_view_mode'];
+ foreach ($items as $delta => $item) {
+ if ($item['tid'] == 'autocreate') {
+ // We don't necessarily have a link when this is
+ // an autocreated term, usually in preview.
+ // So just send the term as check plained markup.
+ $build['#markup'] = check_plain($item['name']);
+ }
+ else {
+ $term = taxonomy_term_load($item['tid']);
+ if (module_exists('i18n_taxonomy')) {
+ $term = i18n_taxonomy_localize_terms($term);
+ }
+ if (!is_object($term)) {
+ // This term is missing in the database.
+ continue;
+ }
+ $settings = $display['settings'];
+ if ($settings['use_content_language'] && !empty($GLOBALS['language_content']->language)) {
+ $langcode = $GLOBALS['language_content']->language;
+ }
+ if (!empty($term)) {
+ $build = taxonomy_term_view($term, $view_mode, $langcode);
+ }
+ else {
+ $build['#markup'] = '';
+ }
+ }
+ $element[$delta] = $build;
+ }
+ }
+
+ if ($display['type'] === 'ds_taxonomy_separator' || $display['type'] == 'ds_taxonomy_separator_localized') {
+ $linked = $display['settings']['taxonomy_term_link'];
+
+ $terms = array();
+ foreach ($items as $delta => $item) {
+ if ($item['tid'] == 'autocreate') {
+ // We don't necessarily have a link when this is
+ // an autocreated term, usually in preview.
+ // So just send the term as check plained markup.
+ $item_display = check_plain($item['name']);
+ }
+ else {
+ $term = taxonomy_term_load($item['tid']);
+ if (module_exists('i18n_taxonomy')) {
+ $term = i18n_taxonomy_localize_terms($term);
+ }
+ if (!is_object($term)) {
+ // This term is missing in the database.
+ continue;
+ }
+ if ($display['type'] == 'ds_taxonomy_separator_localized' && function_exists('i18n_taxonomy_term_name')) {
+ $term->name = i18n_taxonomy_term_name($term, $language->language);
+ }
+ $item_display = check_plain($term->name);
+ if ($linked) {
+ $uri = entity_uri('taxonomy_term', $term);
+ $item_display = l($term->name, $uri['path'], $uri['options']);
+ }
+ }
+ $terms[] = $item_display;
+ }
+
+ if (!empty($terms)) {
+ $output = '';
+ $separator = $display['settings']['taxonomy_term_separator'];
+ $output = implode($separator, $terms);
+ $element[0] = array('#markup' => $output);
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_contextual_links_view_alter().
+ */
+function ds_contextual_links_view_alter(&$element, $items) {
+
+ if (!empty($element['#element']['#entity_type']) && !empty($element['#element']['#bundle']) && module_exists('field_ui') && user_access('administer content types')) {
+ $entity_type = $element['#element']['#entity_type'];
+ $bundle = $element['#element']['#bundle'];
+ $view_mode = isset($element['#element']['#view_mode']) ? $element['#element']['#view_mode'] : 'default';
+
+ // Get the manage display URI.
+ $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle);
+
+ // Check view mode settings.
+ $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
+ $overridden = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? TRUE : FALSE);
+
+ if (!$overridden) {
+ $admin_path .= '/display';
+ }
+ else {
+ $admin_path .= '/display/' . $view_mode;
+ }
+
+ $element['#links']['manage-display'] = array(
+ 'title' => t('Manage display'),
+ 'href' => $admin_path,
+ 'query' => drupal_get_destination(),
+ );
+ }
+}
diff --git a/sites/all/modules/ds/ds.views.inc b/sites/all/modules/ds/ds.views.inc
new file mode 100644
index 000000000..5382fbdb2
--- /dev/null
+++ b/sites/all/modules/ds/ds.views.inc
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Views hooks file.
+ */
+
+/**
+ * Implements hook_views_plugins().
+ */
+function ds_views_plugins() {
+ $path = drupal_get_path('module', 'ds');
+ $views_plugins = array(
+ 'module' => 'ds',
+ 'row' => array(
+ 'ds' => array(
+ 'title' => t('Display Suite'),
+ 'help' => t('Display the entity with the Display Suite module.'),
+ 'handler' => 'views_plugin_ds_entity_view',
+ 'path' => $path . '/views',
+ 'base' => array('node', 'comment', 'users', 'apachesolr', 'taxonomy_term_data', 'file_managed', 'micro'),
+ 'theme' => 'ds_row_entity',
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ ),
+ ),
+ );
+
+ if (module_exists('ds_extras') && variable_get('ds_extras_vd')) {
+ $views_plugins['row']['ds_fields'] = array(
+ 'uses fields' => TRUE,
+ 'title' => t('Display Suite fields'),
+ 'help' => t('Display fields through the Display Suite module.'),
+ 'handler' => 'views_plugin_ds_fields_view',
+ 'path' => $path . '/views',
+ 'theme' => 'ds_row_fields',
+ 'theme path' => $path . '/views',
+ 'theme file' => 'views_plugin_ds_fields_view.inc',
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ );
+ }
+
+ return $views_plugins;
+}
diff --git a/sites/all/modules/ds/images/arrow.png b/sites/all/modules/ds/images/arrow.png
new file mode 100644
index 000000000..715f5cdd4
--- /dev/null
+++ b/sites/all/modules/ds/images/arrow.png
Binary files differ
diff --git a/sites/all/modules/ds/images/preview.png b/sites/all/modules/ds/images/preview.png
new file mode 100644
index 000000000..e78116b18
--- /dev/null
+++ b/sites/all/modules/ds/images/preview.png
Binary files differ
diff --git a/sites/all/modules/ds/includes/ds.contextual.inc b/sites/all/modules/ds/includes/ds.contextual.inc
new file mode 100644
index 000000000..562336798
--- /dev/null
+++ b/sites/all/modules/ds/includes/ds.contextual.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Menu callbacks for contextual links and tabs added by DS.
+ */
+
+/**
+ * Menu callback: redirect to manage display.
+ */
+function ds_contextual_page_tab($object, $entity_type) {
+
+ switch ($entity_type) {
+ case 'node':
+ $bundle = $object->type;
+ $view_mode = (!empty($object->ds_switch)) ? $object->ds_switch : 'full';
+
+ // Let's always go back to the node page.
+ $destination = 'node/' . $object->nid;
+ break;
+ case 'user':
+ $bundle = 'user';
+ $view_mode = 'full';
+ $destination = 'user/' . $object->uid;
+ break;
+ case 'taxonomy_term':
+ $bundle = $object->vocabulary_machine_name;
+ $view_mode = 'full';
+ $destination = 'taxonomy/term/' . $object->tid;
+ break;
+ }
+
+ // Check if we have a configured layout. Do not fallback to default.
+ $layout = ds_get_layout($entity_type, $bundle, $view_mode, FALSE);
+
+ // Get the manage display URI.
+ $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle);
+
+ // Check view mode settings.
+ $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
+ $overriden = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? TRUE : FALSE);
+
+ if (empty($layout) && !$overriden) {
+ $admin_path .= '/display';
+ }
+ else {
+ $admin_path .= '/display/' . $view_mode;
+ }
+
+ drupal_goto($admin_path, array('query' => array('destination' => $destination)));
+}
diff --git a/sites/all/modules/ds/includes/ds.displays.inc b/sites/all/modules/ds/includes/ds.displays.inc
new file mode 100644
index 000000000..4ddb198d2
--- /dev/null
+++ b/sites/all/modules/ds/includes/ds.displays.inc
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @file
+ * Shows the overview screen with all links to entities.
+ */
+
+/**
+ * Menu callback: Show the layout list.
+ */
+function ds_layout_list() {
+ $build = array();
+
+ // All entities.
+ $rows = array();
+ $entities = entity_get_info();
+
+ // Change label of node content type to 'Content' and move to the top
+ if (isset($entities['node'])) {
+ $node_entity = $entities['node'];
+ $node_entity['label'] = 'Content';
+ unset($entities['node']);
+ $entities = array_merge(array('node' => $node_entity), $entities);
+ }
+
+ if (isset($entities['comment'])) {
+ $comment_entity = $entities['comment'];
+ unset($entities['comment']);
+ $entities['comment'] = $comment_entity;
+ }
+
+ foreach ($entities as $entity_type => $entity) {
+ if ((isset($entity['fieldable']) && $entity['fieldable']) || isset($entity['ds_display'])) {
+
+ $rows = array();
+ foreach ($entity['bundles'] as $bundle_type => $bundle) {
+ $row = array();
+
+ $path = isset($bundle['admin']['real path']) ? $bundle['admin']['real path'] : (isset($bundle['admin']['path']) ? $bundle['admin']['path'] : '');
+ if (empty($path)) {
+ continue;
+ }
+
+ $row[] = check_plain($bundle['label']);
+ $links = array(l(t('Manage display'), $path . '/display'));
+ // Add Mangage Form link if this entity type is supported by ds_forms.
+ if (module_exists('ds_forms') && _ds_forms_is_entity_type_supported($entity_type)) {
+ $links[] = l(t('Manage form'), $path . '/fields');
+ }
+ $row[] = implode(' - ', $links);
+ $rows[] = $row;
+ }
+
+ if (!empty($rows)) {
+ $variables = array(
+ 'header' => array(array('data' => $entity['label']), array('data' => t('operations'), 'class' => 'ds-display-list-options')),
+ 'rows' => $rows,
+ );
+ $build['list_' . $entity_type] = array(
+ '#markup' => theme('table', $variables)
+ );
+
+ $entity_rows = array();
+ $rows = array();
+ }
+ }
+ }
+ drupal_add_css(drupal_get_path('module', 'ds') . '/css/ds.admin.css');
+
+ return $build;
+}
+
+/**
+ * Emergency page.
+ */
+function ds_emergency() {
+
+ $form['ds_fields_error'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Fields error'),
+ );
+
+ $form['ds_fields_error']['ds_disable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Disable attaching fields via Display Suite'),
+ '#description' => t('In case you get an error after configuring a layout printing a message like "Fatal error: Unsupported operand types", you can temporarily disable adding fields from DS by toggling this checkbox. You probably are trying to render an node inside a node, for instance through a view, which is simply not possible. See <a href="http://drupal.org/node/1264386">http://drupal.org/node/1264386</a>.'),
+ '#default_value' => variable_get('ds_disable', FALSE),
+ '#weight' => 0,
+ );
+
+ $form['ds_fields_error']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Disable/enable field attach'),
+ '#submit' => array('ds_emergency_fields_attach'),
+ '#weight' => 1,
+ );
+
+ if (module_exists('ds_extras')) {
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+ if (!empty($region_blocks)) {
+
+ $region_blocks_options = array();
+ foreach ($region_blocks as $key => $info) {
+ $region_blocks_options[$key] = $info['title'];
+ }
+
+ $form['region_to_block'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Block regions'),
+ );
+
+ $form['region_to_block']['remove_block_region'] = array(
+ '#type' => 'checkboxes',
+ '#options' => $region_blocks_options,
+ '#description' => t('In case you renamed a content type, you will not see the configured block regions anymore, however the block on the block settings page is still available. On this screen you can remove orphaned block regions.'),
+ );
+
+ $form['region_to_block']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Remove block regions'),
+ '#submit' => array('ds_emergency_region_to_block_submit'),
+ '#weight' => 1,
+ );
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Submit callback of fields error form.
+ */
+function ds_emergency_fields_attach($form, &$form_state) {
+ variable_set('ds_disable', $form_state['values']['ds_disable']);
+ drupal_set_message(t('The configuration options have been saved.'));
+}
+
+/**
+ * Submit callback of region to block form.
+ */
+function ds_emergency_region_to_block_submit($form, &$form_state) {
+ if (isset($form_state['values']['remove_block_region'])) {
+ $variable_set = FALSE;
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+ $remove = $form_state['values']['remove_block_region'];
+ foreach ($remove as $key => $value) {
+ if ($value !== 0 && $key == $value) {
+ $variable_set = TRUE;
+ if (module_exists('block')) {
+ db_delete('block')
+ ->condition('delta', $key)
+ ->condition('module', 'ds_extras')
+ ->execute();
+ }
+ unset($region_blocks[$key]);
+ }
+ }
+
+ if ($variable_set) {
+ drupal_set_message(t('Block regions were removed.'));
+ variable_set('ds_extras_region_blocks', $region_blocks);
+ }
+ }
+ else {
+ drupal_set_message(t('No block regions were removed.'));
+ }
+}
diff --git a/sites/all/modules/ds/includes/ds.field_ui.inc b/sites/all/modules/ds/includes/ds.field_ui.inc
new file mode 100644
index 000000000..153fb146d
--- /dev/null
+++ b/sites/all/modules/ds/includes/ds.field_ui.inc
@@ -0,0 +1,2642 @@
+<?php
+
+/**
+ * @file
+ * Field UI functions for Display Suite.
+ */
+
+/**
+ * Adds the Display Suite fields and layouts to the form.
+ */
+function ds_field_ui_fields_layouts(&$form, &$form_state) {
+ global $base_root, $base_path;
+
+ // Get the entity_type, bundle and view mode.
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ $form['#export_id'] = $entity_type . '|' . $bundle . '|' . $view_mode;
+
+ // Create vertical tabs.
+ ds_field_ui_create_vertical_tabs($form);
+
+ // Add layout fieldset.
+ _ds_field_ui_table_layouts($entity_type, $bundle, $view_mode, $form, $form_state);
+
+ // Add/alter fields on the table, but only if a layout is selected.
+ if ($view_mode != 'form' && !empty($form['#ds_layout'])) {
+ _ds_field_ui_fields($entity_type, $bundle, $view_mode, $form, $form_state);
+
+ // Also alter core fields
+ _ds_field_ui_core_fields($entity_type, $bundle, $view_mode, $form, $form_state);
+ }
+
+ // Add buttons to add fields in overlay.
+ if (isset($form['#ds_layout']) && user_access('admin_fields') && $view_mode != 'form' && module_exists('ds_ui')) {
+ _ds_field_ui_custom_fields($entity_type, $bundle, $view_mode, $form, $form_state);
+ }
+
+ // Special validate function for field group.
+ if (isset($form_state['no_field_group'])) {
+ array_unshift($form['#validate'], '_ds_field_group_field_ui_fix_notices');
+ }
+
+ // Attach js.
+ $form['#attached']['js'][] = drupal_get_path('module', 'ds') . '/js/ds.admin.js';
+
+ // Attach css.
+ $form['#attached']['css'][] = drupal_get_path('module', 'ds') . '/css/ds.admin.css';
+
+ // Add process function to add the regions.
+ $form['#process'][] = 'ds_field_ui_regions';
+
+ // Add a destination so we can get back if layout has been changed.
+ $form['ds_source'] = array(
+ '#type' => 'hidden',
+ '#value' => $base_root . $base_path,
+ );
+ $form['ds_destination'] = array(
+ '#type' => 'hidden',
+ '#value' => drupal_get_destination(),
+ );
+ $form['ds_entity_type'] = array(
+ '#type' => 'hidden',
+ '#value' => $entity_type,
+ );
+ $form['ds_bundle'] = array(
+ '#type' => 'hidden',
+ '#value' => $bundle,
+ );
+ $form['ds_view_mode'] = array(
+ '#type' => 'hidden',
+ '#value' => $view_mode,
+ );
+}
+
+/**
+ * Create vertical tabs.
+ */
+function ds_field_ui_create_vertical_tabs(&$form) {
+
+ // Add additional settings vertical tab.
+ if (!isset($form['additional_settings'])) {
+ $form['additional_settings'] = array(
+ '#type' => 'vertical_tabs',
+ '#theme_wrappers' => array('vertical_tabs'),
+ '#prefix' => '<div>',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+ $form['#attached']['js'][] = 'misc/form.js';
+ $form['#attached']['js'][] = 'misc/collapse.js';
+ }
+
+ $view_mode_admin_access = user_access('admin_view_modes') && module_exists('ds_ui');
+ if (isset($form['modes'])) {
+ if ($view_mode_admin_access) {
+ $form['modes']['view_modes_custom']['#description'] = l(t('Manage view modes'), 'admin/structure/ds/view_modes');
+ }
+ $form['additional_settings']['modes'] = $form['modes'];
+ $form['additional_settings']['modes']['#weight'] = -10;
+ unset($form['modes']);
+ }
+ else {
+ if ($view_mode_admin_access) {
+ $form['additional_settings']['modes']['view_modes_custom']['#description'] = l(t('Manage view modes'), 'admin/structure/ds/view_modes');
+ }
+ }
+}
+
+/**
+ * Menu callback: Disable layout and field settings form.
+ */
+function ds_disable_layout_field_settings_form($form, &$form_state, $id = '') {
+ $layout = new stdClass();
+ ctools_include('export');
+ $ds_layout_settings = ctools_export_crud_load_all('ds_layout_settings');
+ if (isset($ds_layout_settings[$id])) {
+ $layout = $ds_layout_settings[$id];
+ }
+
+ if (isset($layout) && $layout->export_type != 1 && empty($layout->disable)) {
+ $form['#layout'] = $layout;
+ $form['#export_id'] = $id;
+ return confirm_form($form,
+ t('Are you sure you want to disable the layout and field settings for %layout?', array('%layout' => implode(', ', explode('|', $layout->id)))),
+ drupal_get_destination(),
+ t('This action cannot be undone.'),
+ t('Disable'),
+ t('Cancel')
+ );
+ }
+ else {
+ drupal_set_message(t('This operation is not possible.'));
+ }
+}
+
+/**
+ * Submit callback: disable layout and field settings.
+ */
+function ds_disable_layout_field_settings_form_submit(&$form, &$form_state) {
+ $layout = $form['#layout'];
+
+ ctools_include('export');
+ ctools_export_crud_disable('ds_layout_settings', $form['#export_id']);
+ ctools_export_crud_disable('ds_field_settings', $form['#export_id']);
+ // @todo layout fields
+
+ // Clear the ds_fields cache.
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+ cache_clear_all('ds_field_settings', 'cache');
+
+ // Clear entity info cache.
+ cache_clear_all('entity_info', 'cache', TRUE);
+
+ drupal_set_message(t('Layout has been disabled.'));
+
+ $form_state['redirect'] = isset($_GET['destination']) ? $_GET['destination'] : drupal_get_destination();
+}
+
+/**
+ * Menu callback: Enable layout and field settings form.
+ */
+function ds_enable_layout_field_settings_form($form, &$form_state, $id = '') {
+ $layout = new stdClass();
+ ctools_include('export');
+ $ds_layout_settings = ctools_export_crud_load_all('ds_layout_settings');
+ if (isset($ds_layout_settings[$id])) {
+ $layout = $ds_layout_settings[$id];
+ }
+
+ if (isset($layout) && $layout->export_type != 1 && !empty($layout->disabled)) {
+ $form['#layout'] = $layout;
+ $form['#export_id'] = $id;
+ return confirm_form($form,
+ t('Are you sure you want to enable the layout and field settings for %layout?', array('%layout' => implode(', ', explode('|', $layout->id)))),
+ drupal_get_destination(),
+ t('This action cannot be undone.'),
+ t('Enable'),
+ t('Cancel')
+ );
+ }
+ else {
+ drupal_set_message(t('This operation is not possible.'));
+ }
+}
+
+/**
+ * Submit callback: enable layout and field settings.
+ */
+function ds_enable_layout_field_settings_form_submit(&$form, &$form_state) {
+ $layout = $form['#layout'];
+
+ ctools_include('export');
+ ctools_export_crud_enable('ds_layout_settings', $form['#export_id']);
+ ctools_export_crud_enable('ds_field_settings', $form['#export_id']);
+
+ // Clear the ds_fields cache.
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+ cache_clear_all('ds_field_settings', 'cache');
+
+ // Clear entity info cache.
+ cache_clear_all('entity_info', 'cache', TRUE);
+
+ drupal_set_message(t('Layout has been enabled'));
+
+ $form_state['redirect'] = isset($_GET['destination']) ? $_GET['destination'] : drupal_get_destination();
+}
+
+/**
+ * Menu callback: Revert layout and field settings form.
+ */
+function ds_revert_layout_field_settings_form($form, &$form_state, $id = '') {
+ $layout = new stdClass();
+ ctools_include('export');
+ $ds_layout_settings = ctools_export_crud_load_all('ds_layout_settings');
+ if (isset($ds_layout_settings[$id])) {
+ $layout = $ds_layout_settings[$id];
+ }
+
+ if (isset($layout) && $layout->export_type == 3) {
+ $form['#layout'] = $layout;
+ return confirm_form($form,
+ t('Are you sure you want to revert the layout for %layout?', array('%layout' => implode(', ', explode('|', $layout->id)))),
+ drupal_get_destination(),
+ t('This action cannot be undone.'),
+ t('Revert'),
+ t('Cancel')
+ );
+ }
+ else {
+ drupal_set_message(t('This operation is not possible.'));
+ }
+}
+
+/**
+ * Submit callback: revert layout and field settings.
+ */
+function ds_revert_layout_field_settings_form_submit(&$form, &$form_state) {
+ $layout = $form['#layout'];
+
+ db_delete('ds_field_settings')
+ ->condition('id', $layout->id)
+ ->execute();
+
+ db_delete('ds_layout_settings')
+ ->condition('id', $layout->id)
+ ->execute();
+
+ // Clear the ds_fields cache.
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+ cache_clear_all('ds_field_settings', 'cache');
+
+ // Clear entity info cache.
+ cache_clear_all('entity_info', 'cache', TRUE);
+
+ drupal_set_message(t('Layout has been reverted'));
+
+ $form_state['redirect'] = isset($_GET['destination']) ? $_GET['destination'] : drupal_get_destination();
+}
+
+/**
+ * Add Regions to 'Manage fields' or 'Manage display' screen.
+ *
+ * @param $form
+ * The form to add layout fieldset and extra Display Suite fields.
+ * @param $form_state
+ * The current form state.
+ */
+function ds_field_ui_regions($form, $form_state) {
+
+ // Get the entity_type, bundle and view mode.
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ // Ignore fieldgroup options.
+ if (isset($form_state['no_field_group'])) {
+ unset($form['fields']['_add_new_group']);
+ $form['additional_settings']['field_group']['#access'] = FALSE;
+ }
+
+ // Check layout.
+ $layout = isset($form['#ds_layout']) ? $form['#ds_layout'] : FALSE;
+
+ // Change UI to add Region column if we have a layout.
+ if ($layout) {
+ $table = &$form['fields'];
+
+ if ($view_mode != 'form') {
+ $table['#header'] = array(
+ t('Field'),
+ t('Weight'),
+ t('Parent'),
+ t('Region'),
+ t('Label'),
+ array('data' => t('Format'), 'colspan' => 3),
+ );
+ }
+ else {
+ $table['#header'] = array(
+ t('Label'),
+ t('Weight'),
+ t('Parent'),
+ t('Region'),
+ t('Name'),
+ t('Field'),
+ t('Widget'),
+ array('data' => t('Operations'), 'colspan' => 2),
+ );
+ }
+
+ // Remove label and format for views.
+ if ($entity_type == 'ds_views') {
+ $table['#header'][4] = '';
+ }
+
+ $table['#regions'] = array();
+ foreach ($layout->regions as $region_key => $region_title) {
+ $region_options[$region_key] = $region_title;
+ $table['#regions'][$region_key] = array(
+ 'title' => $region_title,
+ 'message' => t('No fields are displayed in this region'),
+ );
+ }
+
+ // Let other modules alter the regions.
+ $context = array(
+ 'entity_type' => $entity_type,
+ 'bundle' => $bundle,
+ 'view_mode' => $view_mode
+ );
+ $region_info = array(
+ 'region_options' => &$region_options,
+ 'table_regions' => &$table['#regions'],
+ );
+ drupal_alter('ds_layout_region', $context, $region_info);
+
+ $region_options['hidden'] = $view_mode != 'form' ? t('Disabled') : t('Hidden');
+ $table['#regions']['hidden'] = array(
+ 'title' => $view_mode != 'form' ? t('Disabled') : t('Hidden'),
+ 'message' => t('No fields are hidden.')
+ );
+
+ $region = array(
+ '#type' => 'select',
+ '#options' => $region_options,
+ '#default_value' => 'hidden',
+ '#attributes' => array(
+ 'class' => array('ds-field-region'),
+ )
+ );
+
+ $limit_items = array(
+ '#type' => 'textfield',
+ '#size' => 2,
+ '#default_value' => '',
+ '#weight' => 10,
+ '#default_value' => '#',
+ '#prefix' => '<div class="limit-float">',
+ '#suffix' => '</div><div class="clearfix"></div>',
+ '#attributes' => array(
+ 'alt' => t('Enter a number to limit the number of items or \'delta\' to print a specific delta (usually configured in views or found in entity->ds_delta). Leave empty to display them all. Note that depending on the formatter settings, this option might not always work.'),
+ 'title' => t('Enter a number to limit the number of items or \'delta\' to print a specific delta (usually configured in views or found in entity->ds_delta). Leave empty to display them all. Note that depending on the formatter settings, this option might not always work.'),
+ ),
+ );
+
+ // Hide this if we formatter_settings_edit is not empty so it doesn't confuse users.
+ if (!empty($form_state['formatter_settings_edit'])) {
+ $limit_items['#access'] = FALSE;
+ }
+
+ // Update existing rows by changing rowHandler and adding regions.
+ foreach (element_children($table) as $name) {
+ $row = &$table[$name];
+ $row['#js_settings'] = array('rowHandler' => 'ds');
+ $row['#region_callback'] = 'ds_field_ui_row_region';
+
+ // Remove hidden format.
+ if (isset($row['format']['type']['#options']['hidden'])) {
+ unset($row['format']['type']['#options']['hidden']);
+ }
+
+ // Add label class.
+ if (isset($row['label'])) {
+ if (isset($form_state['formatter_settings']) && isset($form_state['formatter_settings'][$name]['ft'])) {
+ if (!empty($form_state['formatter_settings'][$name]['ft']['lb'])) {
+ $row['human_name']['#markup'] = check_plain($form_state['formatter_settings'][$name]['ft']['lb']) . ' ' . t('(Original: !orig)', array('!orig' => $row['human_name']['#markup']));
+ }
+ }
+ }
+
+ // Limit items.
+ $field_info = field_info_field($name);
+ if (isset($field_info['cardinality']) && $field_info['cardinality'] != 1 && $view_mode != 'form') {
+ $row['format']['type']['#prefix'] = '<div class="limit-float">';
+ $row['format']['type']['#suffix'] = '</div>';
+ $row['format']['limit'] = $limit_items;
+ $row['format']['limit']['#default_value'] = (isset($layout->settings['limit']) && isset($layout->settings['limit'][$name])) ? $layout->settings['limit'][$name] : '#';
+ }
+
+ // Disable label and format for views.
+ if ($entity_type == 'ds_views') {
+ $row['label']['#access'] = FALSE;
+ }
+
+ // Add region.
+ $split = ($view_mode != 'form') ? 7 : 6;
+ if ($row['#row_type'] == 'group' && $view_mode == 'form') {
+ $split = ($view_mode != 'form') ? 8 : 7;
+ }
+ $second = array_splice($row, $split);
+ $row['region'] = $region;
+ $row['region']['#default_value'] = (isset($layout->settings['fields'][$name]) && isset($region_options[$layout->settings['fields'][$name]])) ? $layout->settings['fields'][$name] : 'hidden';
+ $row = array_merge($row, $second);
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Returns the region to which a row in the Field UI screen belongs.
+ *
+ * @param $row
+ * The current row that is being rendered in the Field UI screen.
+ */
+function ds_field_ui_row_region($row) {
+ return isset($row['region']['#value']) ? $row['region']['#value'] : 'hidden';
+}
+
+/**
+ * Move the view modes so Field UI can handle them.
+ */
+function ds_field_ui_layouts_validate($form, &$form_state) {
+ if (isset($form_state['values']['additional_settings']['modes']['view_modes_custom'])) {
+ $form_state['values']['view_modes_custom'] = $form_state['values']['additional_settings']['modes']['view_modes_custom'];
+ }
+}
+
+/**
+ * Change a layout for a given entity.
+ *
+ * @param $entity_type
+ * The name of the entity.
+ * @param $bundle
+ * The name of the bundle.
+ * @param $view_mode
+ * The name of the view mode.
+ */
+function ds_field_ui_layout_change($form, $form_state, $entity_type = '', $bundle = '', $view_mode = '', $new_layout = '') {
+
+ $old_layout = NULL;
+ $all_layouts = ds_get_layout_info();
+
+ if (!empty($entity_type) && !empty($bundle) && !empty($view_mode)) {
+ $old_layout = ds_get_layout($entity_type, $bundle, $view_mode, FALSE);
+ }
+
+ if ($old_layout && isset($all_layouts[$new_layout])) {
+
+ $new_layout_key = $new_layout;
+ $new_layout = $all_layouts[$new_layout];
+
+ $form['#entity_type'] = $entity_type;
+ $form['#bundle'] = $bundle;
+ $form['#view_mode'] = $view_mode;
+ $form['#old_layout'] = $old_layout;
+ $form['#new_layout'] = $new_layout;
+ $form['#new_layout_key'] = $new_layout_key;
+ $form['#export_id'] = $entity_type . '|' . $bundle . '|' . $view_mode;
+
+ $form['info'] = array(
+ '#markup' => t('You are changing from %old to %new layout for !bundle in !view_mode view mode.', array('%old' => $old_layout['label'], '%new' => $new_layout['label'], '!bundle' => $bundle, '!view_mode' => $view_mode)),
+ '#prefix' => "<div class='change_ds_layout_info'>",
+ '#suffix' => "</div>",
+ );
+
+ // Old region options.
+ $regions = array();
+ foreach ($old_layout['regions'] as $key => $title) {
+ $regions[$key] = $title;
+ }
+
+ // Let other modules alter the regions.
+ // For old regions.
+ $context = array(
+ 'entity_type' => $entity_type,
+ 'bundle' => $bundle,
+ 'view_mode' => $view_mode,
+ );
+ $region_info = array(
+ 'region_options' => $regions,
+ );
+ drupal_alter('ds_layout_region', $context, $region_info);
+ $regions = $region_info['region_options'];
+ $form['#old_layout']['regions'] = $regions;
+
+ // For new regions.
+ $region_info = array(
+ 'region_options' => $new_layout['regions'],
+ );
+ drupal_alter('ds_layout_region', $context, $region_info);
+ $new_layout['regions'] = $region_info['region_options'];
+ $form['#new_layout']['regions'] = $new_layout['regions'];
+
+ // Display the region options
+ $selectable_regions = array('' => t('- None -')) + $new_layout['regions'];
+ $form['regions_pre']['#markup'] = '<div class="ds-layout-regions">';
+ foreach ($regions as $region => $region_title) {
+ $form['region_' . $region] = array(
+ '#type' => 'container',
+ );
+ $form['region_' . $region]['ds_label_' . $region] = array(
+ '#markup' => 'Fields in <span class="change_ds_layout_old_region"> ' . $region_title . '</span> go into',
+ );
+ $form['region_' . $region]['ds_' . $region] = array(
+ '#type' => 'select',
+ '#options' => $layout_options = $selectable_regions,
+ '#default_value' => $region,
+ );
+ }
+ $form['regions_post']['#markup'] = '</div>';
+
+ // Show previews from old and new layouts
+ $form['preview'] = array(
+ '#type' => 'container',
+ '#prefix' => '<div class="ds-layout-preview"/>',
+ '#suffix' => '</div>',
+ );
+
+ $fallback_image = drupal_get_path('module', 'ds') . '/images/preview.png';
+ $old_image = (isset($old_layout['image']) && !empty($old_layout['image'])) ? $old_layout['path'] . '/' . $old_layout['layout'] . '.png' : $fallback_image;
+ if (isset($old_layout['panels']) && !empty($old_layout['panels']['icon'])) {
+ $old_image = $old_layout['panels']['path'] . '/' . $old_layout['panels']['icon'];
+ }
+ $new_image = (isset($new_layout['image']) && !empty($new_layout['image'])) ? $new_layout['path'] . '/' . $new_layout_key . '.png' : $fallback_image;
+ if (isset($new_layout['panels']) && !empty($new_layout['panels']['icon'])) {
+ $new_image = $new_layout['panels']['path'] . '/' . $new_layout['panels']['icon'];
+ }
+ $arrow = drupal_get_path('module', 'ds') . '/images/arrow.png';
+
+ $form['preview']['old_layout'] = array(
+ '#markup' => '<div class="ds-layout-preview-image"><img src="' . base_path() . $old_image . '"/></div>',
+ );
+ $form['preview']['arrow'] = array(
+ '#markup' => '<div class="ds-layout-preview-arrow"><img src="' . base_path() . $arrow . '"/></div>',
+ );
+ $form['preview']['new_layout'] = array(
+ '#markup' => '<div class="ds-layout-preview-image"><img src="' . base_path() . $new_image . '"/></div>',
+ );
+ $form['#attached']['css'][] = drupal_get_path('module', 'ds') . '/css/ds.admin.css';
+
+ // Submit button
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#prefix' => '<div class="ds-layout-change-save">',
+ '#suffix' => '</div>',
+ );
+ }
+ else {
+ $form['nothing'] = array('#markup' => t('No valid configuration found.'));
+ }
+
+ return $form;
+}
+
+/**
+ * Submit callback: save the layout change.
+ */
+function ds_field_ui_layout_change_submit($form, &$form_state) {
+
+ // Prepare some variables.
+ $old_layout = $form['#old_layout'];
+ $new_layout = $form['#new_layout'];
+ $new_layout_key = $form['#new_layout_key'];
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ // Create new record.
+ $record = new stdClass();
+ $record->id = $form['#export_id'];
+ $record->entity_type = $entity_type;
+ $record->bundle = $bundle;
+ $record->view_mode = $view_mode;
+ $record->layout = $new_layout_key;
+ $record->settings = $old_layout['settings'];
+ unset($record->settings['regions']);
+ unset($record->settings['fields']);
+
+ // map old regions to new ones
+ foreach ($old_layout['regions'] as $region => $region_title) {
+ $new_region = $form_state['values']['ds_' . $region];
+ if ($new_region != '' && isset($old_layout['settings']['regions'][$region])) {
+ foreach ($old_layout['settings']['regions'][$region] as $field_key => $field) {
+ if (!isset($record->settings['regions'][$new_region])) {
+ $record->settings['regions'][$new_region] = array();
+ }
+ $record->settings['regions'][$new_region][] = $field;
+ $record->settings['fields'][$field] = $new_region;
+ }
+ }
+ }
+
+ // Remove old record.
+ db_delete('ds_layout_settings')
+ ->condition('entity_type', $entity_type)
+ ->condition('bundle', $bundle)
+ ->condition('view_mode', $view_mode)
+ ->execute();
+
+ // Save new record.
+ drupal_write_record('ds_layout_settings', $record);
+
+ // Clear entity info cache.
+ cache_clear_all('entity_info', 'cache', TRUE);
+
+ // Show message.
+ drupal_set_message(t('The layout change has been saved.'));
+}
+
+/**
+ * Save the layout settings from the 'Manage display' screen.
+ */
+function ds_field_ui_layouts_save($form, &$form_state) {
+ $weight = 0;
+
+ // Get default values.
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ // Determine layout variables.
+ $layout = $form_state['values']['additional_settings']['layout'];
+ $old_layout = $form_state['values']['additional_settings']['old_layout'];
+ $new_layout = ($layout != $old_layout) || empty($old_layout);
+
+ // Save layout and add regions if necessary.
+ $record = new stdClass;
+ $record->id = $form['#export_id'];
+ $record->entity_type = $entity_type;
+ $record->bundle = $bundle;
+ $record->view_mode = $view_mode;
+ $record->layout = $layout;
+ $record->settings = array();
+
+ $form_state['layout_saved'] = FALSE;
+
+ // Remove old layout if necessary.
+ if ($new_layout && !empty($old_layout)) {
+ db_delete('ds_layout_settings')
+ ->condition('entity_type', $entity_type)
+ ->condition('bundle', $bundle)
+ ->condition('view_mode', $view_mode)
+ ->execute();
+ }
+
+ if ($new_layout && !empty($layout)) {
+
+ $form_state['layout_saved'] = TRUE;
+
+ // Save new layout.
+ $record->settings = $record->settings;
+
+ // Let other modules alter the layout settings.
+ drupal_alter('ds_layout_settings', $record, $form_state);
+
+ // Move current visible fields into a default region, so
+ // we keep their current settings.
+ $layouts = ds_get_layout_info();
+ $sl = $layouts[$layout];
+ $first_region = key($sl['regions']);
+ $record->settings['regions'] = array();
+ $record->settings['fields'] = array();
+ $record->settings['classes'] = array();
+ $record->settings['wrappers'] = array();
+ $record->settings['layout_wrapper'] = 'div';
+ $record->settings['layout_attributes'] = '';
+ $record->settings['layout_attributes_merge'] = variable_get('ds_layout_attributes_merge', TRUE);
+ $record->settings['layout_link_attribute'] = FALSE;
+ $record->settings['layout_link_custom'] = '';
+ $fields = _ds_sort_fields($form_state['values']['fields'], 'weight');
+ foreach ($fields as $field_key => $field) {
+
+ // Ignore new fieldgroup, new field or existing field.
+ if (in_array($field_key, array('_add_new_field', '_add_existing_field', '_add_new_group'))) {
+ continue;
+ }
+
+ // Can either be form or display.
+ if ((isset($field['type']) && $field['type'] != 'hidden') || $record->view_mode == 'form') {
+ $record->settings['regions'][$first_region][$weight++] = $field_key;
+ $record->settings['fields'][$field_key] = $first_region;
+ }
+ }
+ // In case this is the full node view mode and if the comment module
+ // is enabled for this content type, add it as well.
+ if ($record->entity_type == 'node' && $record->view_mode == 'full' && module_exists('comment')) {
+ $record->settings['regions'][$first_region][] = 'comments';
+ $record->settings['fields']['comments'] = $first_region;
+ }
+
+ // Save the record.
+ drupal_write_record('ds_layout_settings', $record);
+ }
+ // Update existing layout.
+ elseif (!empty($layout)) {
+
+ $form_state['layout_saved'] = TRUE;
+
+ $fields = _ds_sort_fields($form_state['values']['fields'], 'weight');
+
+ foreach ($fields as $key => $field) {
+
+ // Make sure we need to save anything for this field.
+ if (_ds_field_valid($key, $field, $form_state, $view_mode)) {
+ continue;
+ }
+
+ if (!isset($record->settings['regions'][$field['region']])) {
+ $record->settings['regions'][$field['region']] = array();
+ }
+ $record->settings['regions'][$field['region']][$weight++] = $key;
+ $record->settings['fields'][$key] = $field['region'];
+
+ // Save limit.
+ $limit = isset($field['format']['limit']) ? trim($field['format']['limit']) : '';
+ if (is_numeric($limit) || $limit === 'delta') {
+ $record->settings['limit'][$key] = $limit;
+ }
+ }
+
+ // Save the region classes.
+ $record->settings['classes'] = array();
+ foreach (array_keys($form['fields']['#regions']) as $region) {
+
+ // Ignore hidden region.
+ if ($region == 'hidden') {
+ continue;
+ }
+
+ if (isset($form_state['values']['additional_settings']['layout_class'])) {
+ $record->settings['classes']['layout_class'] = $form_state['values']['additional_settings']['layout_class'];
+ }
+
+ // Additional classes on regions.
+ if (isset($form_state['values']['additional_settings'][$region])) {
+ // Do not save empty string.
+ $classes = is_array($form_state['values']['additional_settings'][$region]) ? implode(' ', $form_state['values']['additional_settings'][$region]) : array();
+ if (!empty($classes)) {
+ $record->settings['classes'][$region] = $form_state['values']['additional_settings'][$region];
+ }
+ }
+
+ // Additional wrappers on regions.
+ if (isset($form_state['values']['additional_settings']['region_wrapper'][$region])) {
+ $record->settings['wrappers'][$region] = $form_state['values']['additional_settings']['region_wrapper'][$region];
+ }
+ }
+
+ // Layout wrapper
+ $record->settings['layout_wrapper'] = $form_state['values']['additional_settings']['region_wrapper']['layout_wrapper'];
+ $record->settings['layout_attributes'] = filter_xss_admin($form_state['values']['additional_settings']['region_wrapper']['layout_attributes']);
+ $record->settings['layout_attributes_merge'] = $form_state['values']['additional_settings']['region_wrapper']['layout_attributes_merge'];
+
+ // Link attribute.
+ $record->settings['layout_link_attribute'] = $form_state['values']['additional_settings']['region_wrapper']['layout_link_attribute'];
+ $record->settings['layout_link_custom'] = $form_state['values']['additional_settings']['region_wrapper']['layout_link_custom'];
+
+ // Additional settings
+ if (isset($form_state['values']['additional_settings']['preview']['info']['settings']['disable_css'])) {
+ $record->settings['layout_disable_css'] = $form_state['values']['additional_settings']['preview']['info']['settings']['disable_css'];
+ }
+ else {
+ $record->settings['layout_disable_css'] = FALSE;
+ }
+
+ $record->settings = $record->settings;
+
+ // Let other modules alter the layout settings.
+ drupal_alter('ds_layout_settings', $record, $form_state);
+
+ $l = $form['#ds_layout'];
+ if ($l->export_type == 2) {
+ drupal_write_record('ds_layout_settings', $record);
+ }
+ else {
+ drupal_write_record('ds_layout_settings', $record, array('id'));
+ }
+
+ // Clear entity info cache.
+ cache_clear_all('entity_info', 'cache', TRUE);
+ }
+}
+
+/**
+ * Form validation handler for _ds_field_ui_fields().
+ */
+function ds_field_ui_fields_validate($form, &$form_state) {
+ foreach (element_children($form['fields']) as $key) {
+ if (isset($form_state['values']['fields'][$key]['settings_edit_form'])) {
+ $settings = isset($form_state['values']['fields'][$key]['settings_edit_form']['settings']['ft']) ? $form_state['values']['fields'][$key]['settings_edit_form']['settings']['ft'] : array();
+ if (!empty($settings)) {
+ $merge = isset($form_state['formatter_settings'][$key]['ft']) ? $form_state['formatter_settings'][$key]['ft'] : array();
+ $form_state['formatter_settings'][$key]['ft'] = array_merge($merge, $settings);
+ }
+ }
+ }
+}
+
+/**
+ * Save the field settings from the 'Manage display' screen.
+ */
+function ds_field_ui_fields_save($form, &$form_state) {
+ // Setup some variables.
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ // Delete previous field configuration configuration.
+ db_delete('ds_field_settings')
+ ->condition('entity_type', $entity_type)
+ ->condition('bundle', $bundle)
+ ->condition('view_mode', $view_mode)
+ ->execute();
+
+ if (empty($form_state['layout_saved'])) {
+ return;
+ }
+
+ $field_settings = array();
+
+ // Save settings for each field.
+ $fields = $form['#ds_fields'];
+ foreach ($fields as $key => $field) {
+
+ // Field settings.
+ $field_values = $form_state['values']['fields'][$field];
+
+ // In case the region is hidden, do not save.
+ if (isset($field_values['region']) && $field_values['region'] == 'hidden') {
+ continue;
+ }
+
+ // Build settings.
+ $settings = array();
+ $settings['weight'] = $field_values['weight'];
+ $settings['label'] = $field_values['label'];
+ $settings['format'] = $field_values['format']['type'];
+
+ // Any formatter settings.
+ if (isset($form_state['formatter_settings'][$field])) {
+ $settings['formatter_settings'] = $form_state['formatter_settings'][$field];
+ }
+
+ $field_settings[$field] = $settings;
+ }
+
+ // Allow other modules to modify the field settings before they get saved.
+ drupal_alter('ds_field_settings', $field_settings, $form, $form_state);
+
+ // Save the record.
+ if (!empty($field_settings)) {
+ $record = new stdClass;
+ $record->id = $form['#export_id'];
+ $record->entity_type = $entity_type;
+ $record->bundle = $bundle;
+ $record->view_mode = $view_mode;
+ $record->settings = $field_settings;
+ drupal_write_record('ds_field_settings', $record);
+ }
+
+ // Clear the ds_fields cache.
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+ cache_clear_all('ds_field_settings', 'cache');
+}
+
+/**
+ * Clone a fields layout.
+ */
+function ds_field_ui_layout_clone($form, &$form_state) {
+
+ $clone = $form_state['values']['additional_settings']['clone'];
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ ctools_include('export');
+ $layout = ctools_export_crud_load('ds_layout_settings', $clone);
+
+ // Delete previous layout settings configuration.
+ db_delete('ds_layout_settings')
+ ->condition('entity_type', $entity_type)
+ ->condition('bundle', $bundle)
+ ->condition('view_mode', $view_mode)
+ ->execute();
+
+ // Delete previous field configuration configuration.
+ db_delete('ds_field_settings')
+ ->condition('entity_type', $entity_type)
+ ->condition('bundle', $bundle)
+ ->condition('view_mode', $view_mode)
+ ->execute();
+
+ // Save new layout record for ds.
+ if ($layout) {
+ $record = new stdClass();
+ $record->id = $form['#export_id'];
+ $record->entity_type = $entity_type;
+ $record->bundle = $bundle;
+ $record->view_mode = $view_mode;
+ $record->layout = $layout->layout;
+ $record->settings = $layout->settings;
+
+ // Let other modules alter the layout settings.
+ drupal_alter('ds_layout_settings', $record, $form_state);
+
+ // Save layout record.
+ drupal_write_record('ds_layout_settings', $record);
+
+ // Copy the view mode settings.
+ list($ce, $cb, $cv) = explode('|', $clone);
+ _ds_field_ui_clone_view_mode_settings($entity_type, $bundle, $view_mode, $cv);
+
+ // Clear entity info cache.
+ cache_clear_all('entity_info', 'cache', TRUE);
+
+ // Show message.
+ drupal_set_message(t('The layout has been cloned.'));
+
+ }
+ else {
+ drupal_set_message(t('No layout was cloned.'));
+ }
+}
+
+/**
+ * Populates display settings for a new view mode from the another view mode.
+ *
+ * This is almost a straight copy from Field UI, but with the addition
+ * that we can pass the view mode from which we want to clone from.
+ */
+function _ds_field_ui_clone_view_mode_settings($entity_type, $bundle, $view_mode, $copy_view_mode) {
+
+ $settings = field_bundle_settings($entity_type, $bundle);
+
+ // Update display settings for field instances.
+ $instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle));
+ foreach ($instances as $instance) {
+ // If this field instance has display settings defined for this view mode,
+ // respect those settings.
+ if (isset($instance['display'][$copy_view_mode])) {
+ $instance['display'][$view_mode] = $instance['display'][$copy_view_mode];
+ field_update_instance($instance);
+ }
+ }
+
+ // Update display settings for 'extra fields'.
+ foreach (array_keys($settings['extra_fields']['display']) as $name) {
+ if (isset($settings['extra_fields']['display'][$name][$copy_view_mode])) {
+ $settings['extra_fields']['display'][$name][$view_mode] = $settings['extra_fields']['display'][$name][$copy_view_mode];
+ }
+ }
+
+ // Save the settings.
+ field_bundle_settings($entity_type, $bundle, $settings);
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ */
+function ds_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ // Taxonomy view modes.
+ if ($display['type'] === 'ds_taxonomy_view_mode') {
+ $options = array();
+ $view_modes = ds_entity_view_modes('taxonomy_term');
+ foreach ($view_modes as $key => $info) {
+ $options[$key] = $info['label'];
+ }
+ $element['taxonomy_term_reference_view_mode'] = array(
+ '#title' => t('View mode'),
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => $settings['taxonomy_term_reference_view_mode'],
+ );
+ $element['use_content_language'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use current content language'),
+ '#default_value' => $settings['use_content_language'],
+ );
+ return $element;
+ }
+
+ // Taxonomy separated.
+ if ($display['type'] === 'ds_taxonomy_separator' || $display['type'] == 'ds_taxonomy_separator_localized') {
+ $element['taxonomy_term_link'] = array(
+ '#title' => t('Link to term'),
+ '#type' => 'checkbox',
+ '#size' => 10,
+ '#default_value' => $settings['taxonomy_term_link'],
+ );
+ $separators = array(
+ ' ' => t('space'),
+ ', ' => t('comma'),
+ ' - ' => t('dash'),
+ ' / ' => t('slash'),
+ );
+ drupal_alter('ds_taxonomy_term_separators', $separators);
+ $element['taxonomy_term_separator'] = array(
+ '#title' => t('Separator'),
+ '#type' => 'select',
+ '#options' => $separators,
+ '#default_value' => $settings['taxonomy_term_separator'],
+ '#states' => array(
+ 'visible' => array(
+ 'select[name="fields[field_tags][settings_edit_form][settings][taxonomy_term_list]"]' => array('value' => 'separated_list'),
+ ),
+ ),
+ );
+ return $element;
+ }
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function ds_field_formatter_settings_summary($field, $instance, $view_mode) {
+ $summary = '';
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ if ($display['type'] === 'ds_taxonomy_view_mode') {
+ $entity_info = entity_get_info('taxonomy_term');
+ $modes = $entity_info['view modes'];
+ $mode = $modes[$settings['taxonomy_term_reference_view_mode']]['label'];
+ $summary .= t('View mode: %mode', array('%mode' => $mode)) . '<br />';;
+ $summary .= !empty($settings['use_content_language']) ? t('Use current content language') : t('Use field language');
+ }
+
+ if ($display['type'] === 'ds_taxonomy_separator' || $display['type'] == 'ds_taxonomy_separator_localized') {
+ $separators = array(
+ ' ' => t('space'),
+ ', ' => t('comma'),
+ ' - ' => t('dash'),
+ ' / ' => t('slash'),
+ );
+ drupal_alter('ds_taxonomy_term_separators', $separators);
+ $summary .= t('Separated by !sep', array('!sep' => $separators[$settings['taxonomy_term_separator']]));
+ $summary .= $settings['taxonomy_term_link'] ? ', ' . t('linked') : ', ' . t('not linked');
+ }
+
+ return $summary;
+}
+
+/**
+ * Creates a summary for the field format configuration summary.
+ *
+ * @param $field
+ * The configuration of the field.
+ *
+ * @return $summary
+ * An markup array.
+ */
+function ds_field_settings_summary($field, $form_state, $form, $view_mode) {
+
+ $summary = '';
+
+ // Not all fields have settings.
+ if (isset($field['properties']['settings'])) {
+ $summary = module_invoke($field['module'], 'ds_field_format_summary', $field);
+ }
+
+ if (module_exists('ds_extras') && variable_get('ds_extras_field_template', FALSE)) {
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.admin');
+
+ // Field template summary
+ if (!in_array($field['field_type'], array(DS_FIELD_TYPE_IGNORE, DS_FIELD_TYPE_PREPROCESS))) {
+ $functions = module_invoke_all('ds_field_theme_functions_info');
+ $default_field_function = variable_get('ft-default', 'theme_field');
+ $field_function = isset($form_state['formatter_settings'][$field['name']]['ft']['func']) ? $form_state['formatter_settings'][$field['name']]['ft']['func'] : $default_field_function;
+
+ $summary .= 'Field template: ' . check_plain($functions[$field_function]) . '<br />';
+ }
+ }
+
+ if (!empty($form_state['complete form'])) {
+ $formatter_name = $form_state['complete form']['fields'][$field['name']]['format']['type']['#value'];
+ }
+ else {
+ $formatter_name = $form['fields'][$field['name']]['format']['type']['#default_value'];
+ }
+
+ // Allow other modules to alter the formatter summary.
+ $context = array(
+ 'formatter' => $formatter_name,
+ 'field' => $field,
+ 'instance' => array(
+ 'display' => array(
+ $view_mode => array(
+ 'label' => '',
+ 'type' => '',
+ 'weight' => '',
+ 'settings' => isset($field['formatter_settings']) ? $field['formatter_settings'] : array(),
+ 'module' => '',
+ )
+ )
+ ),
+ 'view_mode' => $view_mode,
+ 'ds' => TRUE,
+ );
+ drupal_alter('field_formatter_settings_summary', $summary, $context);
+
+ if (empty($summary)) {
+ return NULL; // no summary return nothing
+ }
+
+ return array(
+ '#markup' => '<div class="field-formatter-summary">' . $summary . '</div>',
+ '#cell_attributes' => array('class' => array('field-formatter-summary-cell')),
+ );
+}
+
+/**
+ * Creates a form for Display Suite fields.
+ * .
+ * @param $field
+ * The field definition.
+ *
+ * @return $form
+ * A form definition.
+ */
+function ds_field_settings_form($field, &$form_state, $entity_form, $view_mode) {
+ $form = module_invoke($field['module'], 'ds_field_settings_form', $field);
+
+ // Add field template settings to every field if enabled.
+ if (module_exists('ds_extras') && variable_get('ds_extras_field_template', FALSE)) {
+ $context = array(
+ 'instance' => array(
+ 'entity_type' => $field['entity_type'],
+ 'bundle' => $field['bundle'],
+ 'field_name' => $field['name'],
+ ),
+ 'view_mode' => $field['view_mode'],
+ );
+
+ // Load the formatter settings form
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.admin');
+ // Protect against empty $form.
+ if (!is_array($form)) $form = array();
+ if (!in_array($field['field_type'], array(DS_FIELD_TYPE_IGNORE, DS_FIELD_TYPE_PREPROCESS))) {
+ ds_extras_field_template_settings_form($form, $form_state, $context);
+ }
+ else {
+ $form['#markup'] = t('This field does not support Field templates.');
+ }
+ }
+
+ $formatter_name = $form_state['complete form']['fields'][$field['name']]['format']['type']['#value'];
+
+ // Allow other modules to alter the formatter settings form.
+ $context = array(
+ 'ds' => TRUE,
+ 'formatter' => $formatter_name,
+ 'field' => $field,
+ 'instance' => array(
+ 'label' => $field['title'],
+ 'bundle' => $field['bundle'],
+ 'entity_type' => $field['entity_type'],
+ 'display' => array(
+ 'default' => array(
+ 'settings' => isset($field['formatter_settings']) ? $field['formatter_settings'] : array(),
+ ),
+ ),
+ ),
+ 'view_mode' => $view_mode,
+ 'form' => $entity_form,
+ 'form_state' => $form_state,
+ );
+ drupal_alter('field_formatter_settings_form', $form, $context);
+
+ return $form;
+}
+
+/**
+ * Implements hook_ds_field_format_summary().
+ */
+function ds_ds_field_format_summary($field) {
+ $summary = '';
+ $settings = isset($field['formatter_settings']) ? $field['formatter_settings'] : $field['properties']['default'];
+ $functions = module_invoke_all('ds_field_theme_functions_info');
+ foreach ($settings as $key => $value) {
+
+ // Ignore Field Formatter conditions.
+ if ($key == 'conditions') {
+ continue;
+ }
+
+ if ($key == 'ctools') {
+ $conf = unserialize($value);
+ $summary .= t('Type: !type', array('!type' => check_plain(drupal_ucfirst(str_replace('_', ' ', $conf['subtype'])))));
+ }
+ elseif ($key == 'ft' || is_array($value)) {
+ // Do nothing
+ }
+ elseif (!empty($value)) {
+ $value = is_numeric($value) ? ($value ? t('Yes') : t('No')) : check_plain($value);
+ $summary .= ' ' . str_replace('_', ' ', drupal_ucfirst(check_plain($key))) . ': ' . check_plain($value) . '<br />';
+ }
+ }
+
+ if (empty($summary) && ($field['field_type'] == DS_FIELD_TYPE_CTOOLS)) {
+ $summary .= t('Not configured yet.') . '<br />';
+ }
+
+ return $summary;
+}
+
+/**
+ * Implements hook_ds_field_settings_form().
+ */
+function ds_ds_field_settings_form($field) {
+ $form = array();
+
+ $settings = !empty($field['formatter_settings']) ? $field['formatter_settings'] : (!empty($field['properties']['default']) ? $field['properties']['default'] : array());
+
+ if (empty($field['properties']['settings'])) {
+ return $form;
+ }
+
+ foreach ($field['properties']['settings'] as $key => $value) {
+
+ switch ($value['type']) {
+
+ case 'textfield':
+ $form[$key] = array(
+ '#type' => 'textfield',
+ '#title' => str_replace('_', ' ', check_plain(drupal_ucfirst($key))),
+ '#default_value' => isset($settings[$key]) ? $settings[$key] : '',
+ '#size' => 40,
+ '#description' => isset($value['description']) ? check_plain($value['description']) : '',
+ );
+ break;
+
+ case 'select':
+ $form[$key] = array(
+ '#type' => 'select',
+ '#title' => check_plain(drupal_ucfirst($key)),
+ '#default_value' => isset($settings[$key]) ? $settings[$key] : '',
+ '#options' => $value['options'],
+ '#description' => isset($value['description']) ? check_plain($value['description']) : '',
+ );
+ break;
+
+ case 'checkbox':
+ $form[$key] = array(
+ '#type' => 'checkbox',
+ '#title' => str_replace('_', ' ', check_plain(drupal_ucfirst($key))),
+ '#default_value' => isset($settings[$key]) ? $settings[$key] : '',
+ '#description' => isset($value['description']) ? check_plain($value['description']) : '',
+ );
+ break;
+
+ case 'ctools':
+ ctools_include('modal');
+ ctools_include('object-cache');
+ ctools_modal_add_js();
+ $form[$key] = array(
+ '#type' => 'hidden',
+ '#default_value' => isset($settings[$key]) ? $settings[$key] : '',
+ '#weight' => 2,
+ );
+ $action = 'add';
+ $args = '';
+ $conf = array();
+ $query = array('query' => array('selection' => 1));
+ $title = t('Select content');
+ if (isset($settings[$key])) {
+ $query = array();
+ $ctools = unserialize($settings['ctools']);
+ $type = $ctools['type'];
+ $subtype = $ctools['subtype'];
+ $args = '/' . $type . '/' . $subtype;
+ $action = 'edit';
+ $conf = $ctools['conf'];
+ $title = t('Edit content');
+ }
+ $form['select'] = array(
+ '#markup' => '<div class="select-content-link">' . l($title, 'admin/structure/ds/fields/manage_ctools/content/' . $action . '/' . $field['entity_type'] . '/' . $field['name'] . $args, array('attributes' => array('class' => array('ctools-use-modal'))) + $query) . '</div>',
+ '#weight' => -10,
+ );
+ $form['load_terms'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Load terms'),
+ '#description' => t('Toggle if you are embedding a view with term fields.'),
+ '#default_value' => isset($settings['load_terms']) ? $settings['load_terms'] : '',
+ '#weight' => -1,
+ );
+ $form['show_title']['#weight'] = 0;
+ $form['title_wrapper']['#weight'] = 1;
+ ctools_object_cache_set($field['name'], $field['name'], $conf);
+ break;
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Add entity contexts.
+ */
+function ds_get_entity_context($entity_type) {
+ ctools_include('context');
+ $arguments = array(
+ array(
+ 'keyword' => $entity_type,
+ 'identifier' => drupal_ucfirst($entity_type) . ' being viewed',
+ 'id' => 1,
+ 'name' => 'entity_id:' . $entity_type,
+ 'settings' => array(),
+ ),
+ );
+
+ return ctools_context_get_placeholders_from_argument($arguments);
+}
+
+/**
+ * Return the configuration settings for the CTools field.
+ */
+function ds_ctools_content($action = 'add', $entity_type = '', $field_name = '', $type_name = '', $subtype_name = '', $step = NULL) {
+
+ ctools_include('modal');
+ ctools_include('ajax');
+ ctools_include('content');
+ ctools_include('object-cache');
+
+ $commands = array();
+ $content_type = ctools_get_content_type($type_name);
+ $subtype = ctools_content_get_subtype($content_type, $subtype_name);
+
+ if ($data = ctools_object_cache_get($field_name, $field_name)) {
+ $conf = $data;
+ }
+ else {
+ $conf = ctools_content_get_defaults($content_type, $subtype);
+ }
+
+ $url = 'admin/structure/ds/fields/manage_ctools/content/' . $action . '/' . $entity_type . '/' . $field_name;
+ $base_url = $url;
+ if (!empty($type_name) && !empty($subtype_name)) {
+ $url .= '/' . $type_name . '/' . $subtype_name . '/%step';
+ }
+ $form_info = array(
+ 'path' => $url,
+ 'show cancel' => TRUE,
+ 'next callback' => 'ds_ctools_content_next',
+ );
+
+ // Get entity context.
+ $contexts = ds_get_entity_context($entity_type);
+
+ $form_state = array(
+ 'contexts' => $contexts,
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ 'field_name' => $field_name,
+ );
+
+ // Call the content form.
+ $output = ctools_content_form($action, $form_info, $form_state, $content_type, $subtype_name, $subtype, $conf, $step);
+
+ if (!empty($form_state['complete']) || isset($_GET['dismiss'])) {
+ $configuration = array(
+ 'conf' => $form_state['conf'],
+ 'type' => $type_name,
+ 'subtype' => $subtype_name,
+ );
+ $commands[] = ctools_modal_command_dismiss();
+ $commands[] = ajax_command_invoke('input[name="fields[' . $field_name . '][settings_edit_form][settings][ctools]"]', 'dsCtoolsContentConfiguration', array(serialize($configuration)));
+ $commands[] = ajax_command_invoke('.select-content-link', 'dsCtoolsContentUpdate', array(serialize($configuration)));
+ ctools_object_cache_clear($field_name, $field_name);
+ }
+ // Content selection
+ elseif (!empty($form_state['cancel']) || isset($_GET['selection'])) {
+ ctools_object_cache_clear($field_name, $field_name);
+ $commands[] = ds_ctools_content_select($contexts, $field_name, $action, $entity_type);
+ }
+ // No configuration anymore.
+ elseif ($output === FALSE && !isset($_GET['dismiss'])) {
+ $output = t('No further configuration exists for this content type.<br/><br/><a href="!close_modal" class="use-ajax">Click here to close the modal and save the settings.</a><br/><br/><a href="!new_content" class="use-ajax">Click here to select new content</a>.', array('!new_content' => url($base_url, array('query' => array('selection' => TRUE))), '!close_modal' => url($url, array('query' => array('dismiss' => 1)))));
+ $commands[] = ctools_modal_command_display(t('Edit content'), $output);
+ }
+ // Form render.
+ else {
+ $commands = ctools_modal_form_render($form_state, $output);
+ }
+
+ print ajax_render($commands);
+ ajax_footer();
+ exit;
+}
+
+/**
+ * Handle the 'next' click on the add/edit field form wizard.
+ */
+function ds_ctools_content_next(&$form_state) {
+ ctools_object_cache_set($form_state['field_name'], $form_state['field_name'], $form_state['conf']);
+}
+
+/**
+ * Select content.
+ *
+ * @param $contexts
+ * A collection of contexts, usually the entity.
+ * @param $field_name
+ * The name of the field.
+ * @param $action
+ * The name of the action.
+ * @param $entity_type
+ * The name of the entity type.
+ */
+function ds_ctools_content_select($contexts, $field_name, $action, $entity_type) {
+
+ // Get content types.
+ $content_types = ctools_content_get_available_types($contexts);
+
+ $categories = $category_names = $ordered = array();
+
+ foreach ($content_types as $type_name => $subtypes) {
+ foreach ($subtypes as $subtype_name => $content_type) {
+ list($category_key, $category) = ds_ctools_get_category($content_type);
+
+ if (empty($categories[$category_key])) {
+ $categories[$category_key] = array(
+ 'title' => $category,
+ 'content' => array(),
+ );
+ $category_names[$category_key] = $category;
+ }
+
+ $content_title = filter_xss_admin($content_type['title']);
+
+ // Ensure content with the same title doesn't overwrite each other.
+ while (isset($categories[$category_key]['content'][$content_title])) {
+ $content_title .= '-';
+ }
+
+ $categories[$category_key]['content'][$content_title] = $content_type;
+ $categories[$category_key]['content'][$content_title]['type_name'] = $type_name;
+ $categories[$category_key]['content'][$content_title]['subtype_name'] = $subtype_name;
+ }
+ }
+
+ // Now sort
+ natcasesort($category_names);
+ foreach ($category_names as $category => $name) {
+ $ordered[$category] = $categories[$category];
+ }
+
+ $left = '';
+ $right = '<div class="content">' . t('Content options are divided by category. Please select a category from the left to proceed.') . '</div>';
+ foreach ($ordered as $section => $section_content) {
+ // Section.
+ if ($section == 'root') {
+ $section_content['title'] = t('Content');
+ }
+ $left .= '<div class="section"><a href="" id="' . $section . '" class="section-link">' . $section_content['title'] . '</a></div>';
+ // Content.
+ $right .= '<div id="' . $section . '-container" class="selection-hide content">';
+ $right .= '<h2>' . $section_content['title'] . '</h2>';
+ foreach ($section_content['content'] as $key => $value) {
+ $right .= '<div class="content-item">';
+ $variables = array(
+ 'path' => ctools_content_admin_icon($value),
+ );
+ $right .= theme('image', $variables) . '&nbsp;';
+ $right .= ctools_ajax_text_button($key, 'admin/structure/ds/fields/manage_ctools/content/' . $action . '/' . $entity_type . '/' . $field_name . '/' . $value['type_name'] . '/' . $value['subtype_name'], $key);
+ $right .= '</div>';
+ }
+ $right .= '</div>';
+ }
+
+ // Create output.
+ $output = '<div id="ctools-content-selection">';
+ $output .= '<div id="ds-left">' . $left . '</div>';
+ $output .= '<div id="ds-right">' . $right . '</div>';
+ $output .= '</div>';
+
+ return ctools_modal_command_display(t('Select content'), $output);
+}
+
+/**
+ * Helper function to get the category.
+ */
+function ds_ctools_get_category($content_type) {
+ if (isset($content_type['top level'])) {
+ $category = 'root';
+ }
+ elseif (isset($content_type['category'])) {
+ if (is_array($content_type['category'])) {
+ list($category, $weight) = $content_type['category'];
+ }
+ else {
+ $category = $content_type['category'];
+ }
+ }
+ else {
+ $category = t('Uncategorized');
+ }
+
+ return array(preg_replace('/[^a-z0-9]/', '-', drupal_strtolower($category)), $category);
+}
+
+/**
+ * Add fake field group value in.
+ */
+function _ds_field_group_field_ui_fix_notices($form, &$form_state) {
+ $field_group = array(
+ 'group_name' => '',
+ 'label' => '',
+ );
+ $form_state['values']['fields']['_add_new_group'] = $field_group;
+}
+
+/**
+ * Add the layouts fieldset on the Field UI screen.
+ *
+ * @param $entity_type
+ * The name of the entity type.
+ * @param $bundle
+ * The name of the bundle
+ * @param $view_mode
+ * The name of the view_mode
+ * @param $form
+ * A collection of form properties.
+ */
+function _ds_field_ui_table_layouts($entity_type, $bundle, $view_mode, &$form, $form_state) {
+
+ $layout_options = array();
+ $ds_layouts = ds_get_layout_info();
+ $layout_options = array('' => t('- None -'));
+ foreach ($ds_layouts as $key => $layout) {
+ $optgroup = 'Display Suite';
+
+ // Panels can not be used on Views fields and forms.
+ if (!empty($layout['module']) && $layout['module'] == 'panels' && isset($form_state['no_panels'])) {
+ continue;
+ }
+
+ // Create new layout option group.
+ if (!empty($layout['module'])) {
+ $optgroup = drupal_ucfirst($layout['module']);
+ }
+
+ if (!isset($layout_options[$optgroup])) {
+ $layout_options[$optgroup] = array();
+ }
+
+ // Stack the layout.
+ $layout_options[$optgroup][$key] = $layout['label'];
+ }
+
+ // If there is only one $optgroup, move it to the root.
+ if (count($layout_options) == 2) {
+ $options = $layout_options[$optgroup];
+ $layout_options = array_merge(array('' => t('- None -')), $options);
+ }
+
+ // Add layouts form.
+ $form['additional_settings']['ds_layouts'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Layout for !bundle in !view_mode', array('!bundle' => str_replace('_', ' ', $bundle), '!view_mode' => str_replace('_', ' ', $view_mode))),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ '#parents' => array('additional_settings'),
+ '#weight' => -100,
+ );
+
+ ctools_include('export');
+ $layout = new stdClass();
+ $ds_layout_settings = ctools_export_crud_load_all('ds_layout_settings');
+ if (isset($ds_layout_settings[$form['#export_id']])) {
+ $layout = $ds_layout_settings[$form['#export_id']];
+ }
+
+ if (!empty($layout) && isset($layout->layout) && isset($ds_layouts[$layout->layout]) && empty($layout->disabled)) {
+ $layout->settings = $layout->settings;
+ $layout->regions = $ds_layouts[$layout->layout]['regions'];
+ $form['#ds_layout'] = $layout;
+ }
+
+
+ // The layout is defined in code.
+ if (isset($layout->export_type)) {
+ // Enabled/disable the layout.
+ if (empty($layout->disabled)) {
+ $link = t('This layout is defined in code') . ': ' . l(t('disable layout.'), 'admin/structure/ds/disable/' . $form['#export_id'], array('query' => drupal_get_destination()));
+ }
+ else {
+ $link = t('A layout is defined in code but has been disabled') . ': ' . l(t('enable layout.'), 'admin/structure/ds/enable/' . $form['#export_id'], array('query' => drupal_get_destination()));
+ }
+ $form['additional_settings']['ds_layouts']['enable_disable'] = array(
+ '#markup' => $link,
+ '#weight' => 2,
+ );
+
+ // Overridden in database.
+ if ($layout->export_type == 3) {
+ $form['additional_settings']['ds_layouts']['revert'] = array(
+ '#markup' => l(t('This layout is overridden. Click to revert to default settings.'), 'admin/structure/ds/revert-layout/' . $form['#export_id'], array('query' => drupal_get_destination())),
+ '#weight' => 1,
+ );
+ }
+ }
+
+ // Load the layout preview form
+ $layout->layout_options = $layout_options;
+ _ds_field_ui_table_layouts_preview($form, $form_state, $ds_layouts, $layout, $entity_type, $bundle, $view_mode);
+
+ if (!empty($layout) && isset($layout->regions)) {
+
+ // Add wrappers
+ $wrapper_options = array(
+ 'div' => 'Div',
+ 'span' => 'Span',
+ 'section' => 'Section',
+ 'article' => 'Article',
+ 'header' => 'Header',
+ 'footer' => 'Footer',
+ 'aside' => 'Aside',
+ 'figure' => 'Figure'
+ );
+ $form['additional_settings']['region_wrapper'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Custom wrappers'),
+ '#description' => t('Choose a wrapper. All Display Suite layouts support this option.')
+ );
+
+ // Hide the fieldset in case of the reset layout.
+ if ($layout->layout === 'ds_reset') {
+ $form['additional_settings']['region_wrapper']['#access'] = FALSE;
+ }
+
+ foreach (array_keys($layout->regions) as $region) {
+ $form['additional_settings']['region_wrapper'][$region] = array(
+ '#type' => 'select',
+ '#options' => $wrapper_options,
+ '#title' => t('Wrapper for @region', array('@region' => $layout->regions[$region])),
+ '#default_value' => isset($layout->settings['wrappers'], $layout->settings['wrappers'][$region]) ? $layout->settings['wrappers'][$region] : 'div',
+ );
+ }
+
+ $form['additional_settings']['region_wrapper']['layout_wrapper'] = array(
+ '#type' => 'select',
+ '#options' => $wrapper_options,
+ '#title' => t('Layout wrapper'),
+ '#default_value' => isset($layout->settings['layout_wrapper']) ? $layout->settings['layout_wrapper'] : 'div',
+ '#weight' => 10,
+ );
+
+ $form['additional_settings']['region_wrapper']['layout_attributes'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Layout attributes'),
+ '#description' => 'E.g. role="navigation"',
+ '#default_value' => isset($layout->settings['layout_attributes']) ? $layout->settings['layout_attributes'] : '',
+ '#weight' => 11,
+ );
+ $form['additional_settings']['region_wrapper']['layout_attributes_merge'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Merge other layout attributes'),
+ '#description' => 'Certain modules might provide additional attributes for the template wrapper, e.g. RDFa. Disable this option if you prefer to specify these attributes above.',
+ '#default_value' => isset($layout->settings['layout_attributes_merge']) ? $layout->settings['layout_attributes_merge'] : variable_get('ds_layout_attributes_merge', TRUE),
+ '#weight' => 12,
+ );
+
+ $form['additional_settings']['region_wrapper']['layout_link_attribute'] = array(
+ '#type' => 'select',
+ '#options' => array(
+ '' => t('No link'),
+ 'content' => t('Link to content'),
+ 'custom' => t('Custom'),
+ 'tokens' => t('Tokens')
+ ),
+ '#title' => t('Add link'),
+ '#description' => t('This will add an onclick attribute on the layout wrapper.'),
+ '#default_value' => isset($layout->settings['layout_link_attribute']) ? $layout->settings['layout_link_attribute'] : FALSE,
+ '#weight' => 12,
+ );
+
+ $form['additional_settings']['region_wrapper']['layout_link_custom'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Custom link'),
+ '#description' => t('You may use tokens for this link if you selected tokens.'),
+ '#default_value' => isset($layout->settings['layout_link_custom']) ? $layout->settings['layout_link_custom'] : FALSE,
+ '#weight' => 13,
+ '#states' => array(
+ 'visible' => array(array(
+ ':input[name="additional_settings[region_wrapper][layout_link_attribute]"]' => array(array("value" => "tokens"), array("value" => "custom")),
+ )),
+ ),
+ );
+
+ if (module_exists('token')) {
+ $form['additional_settings']['region_wrapper']['tokens'] = array(
+ '#title' => t('Tokens'),
+ '#type' => 'container',
+ '#weight' => 14,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="additional_settings[region_wrapper][layout_link_attribute]"]' => array("value" => "tokens"),
+ ),
+ ),
+ );
+ $form['additional_settings']['region_wrapper']['tokens']['help'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => 'all',
+ '#global_types' => FALSE,
+ '#dialog' => TRUE,
+ );
+ }
+
+
+
+ // Add extra classes for the regions to have more control while theming.
+ $form['additional_settings']['ds_classes'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Custom classes'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#parents' => array('additional_settings'),
+ '#access' => empty($chosen_layout['flexible']),
+ );
+
+ $classes_access = (user_access('admin_classes') && module_exists('ds_ui'));
+ $classes = _ds_classes();
+ if (!empty($classes)) {
+
+ $form['additional_settings']['ds_classes']['layout_class'] = array(
+ '#type' => 'select',
+ '#multiple' => TRUE,
+ '#options' => $classes,
+ '#title' => t('Class for layout'),
+ '#default_value' => isset($layout->settings['classes']['layout_class']) ? $layout->settings['classes']['layout_class'] : '',
+ );
+
+ foreach (array_keys($layout->regions) as $region) {
+ $form['additional_settings']['ds_classes'][$region] = array(
+ '#type' => 'select',
+ '#multiple' => TRUE,
+ '#options' => $classes,
+ '#title' => t('Class for @region', array('@region' => $region)),
+ '#default_value' => isset($layout->settings['classes'][$region]) ? $layout->settings['classes'][$region] : '',
+ );
+ }
+ if ($classes_access) {
+ $form['additional_settings']['ds_classes']['info'] = array('#markup' => l(t('Manage region and field CSS classes'), 'admin/structure/ds/classes', array('query' => drupal_get_destination())));
+ }
+ elseif (user_access('admin_classes')) {
+ $form['additional_settings']['ds_classes']['info'] = array('#markup' => l(t('To manage region and field CSS classes, the Display Suite UI module should be enabled.'), 'admin/modules', array('query' => drupal_get_destination(), 'fragment' => 'edit-modules-display-suite')));
+ }
+ }
+ else {
+ if ($classes_access) {
+ $form['additional_settings']['ds_classes']['info'] = array('#markup' => '<p>' . t('You have not defined any CSS classes which can be used on regions.') . '</p><p>' . l(t('Manage region and field CSS classes'), 'admin/structure/ds/classes', array('query' => drupal_get_destination())) . '</p>');
+ }
+ else {
+ $form['additional_settings']['ds_classes']['#access'] = FALSE;
+ }
+ }
+ }
+ else {
+
+ if ($view_mode != 'form') {
+ // See if we can clone from another view mode.
+ $options = array();
+ $ds_layout_settings = ctools_export_crud_load_all('ds_layout_settings');
+ foreach ($ds_layout_settings as $row) {
+ // Do not clone from form layouts.
+ if ($row->view_mode == 'form') {
+ continue;
+ }
+ if ($row->entity_type == $entity_type && $row->bundle == $bundle) {
+ $name = drupal_ucfirst(str_replace('_', ' ', $row->entity_type)) . ' > ' . drupal_ucfirst(str_replace('_', ' ', $row->bundle)) . ' > ' . drupal_ucfirst(str_replace('_', ' ', $row->view_mode));
+ if (!empty($row->disabled)) {
+ $name .= ' ' . t('(disabled)');
+ }
+ $options[$row->id] = $name;
+ }
+ }
+
+ if (!empty($options)) {
+
+ // Clone from another layout.
+ $form['additional_settings']['ds_clone'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Clone layout'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#parents' => array('additional_settings'),
+ );
+ $form['additional_settings']['ds_clone']['clone'] = array(
+ '#title' => t('Select an existing layout to clone.'),
+ '#type' => 'select',
+ '#options' => $options,
+ '#weight' => 20,
+ );
+ $form['additional_settings']['ds_clone']['clone_submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Clone layout'),
+ '#submit' => array('ds_field_ui_layout_clone'),
+ '#weight' => 21,
+ );
+ }
+ }
+ }
+
+ $form['additional_settings']['ds_layouts']['id'] = array(
+ '#type' => 'value',
+ '#value' => isset($layout->id) ? $layout->id : $form['#export_id'],
+ );
+
+ $form['additional_settings']['ds_layouts']['old_layout'] = array(
+ '#type' => 'value',
+ '#value' => isset($layout->layout) ? $layout->layout : 0,
+ );
+
+ // Add validate and submit handlers. Layout needs be first so
+ // we can reset the type key for Field API fields.
+ $form['#validate'][] = 'ds_field_ui_layouts_validate';
+ $submit = $form['#submit'];
+ $form['#submit'] = array('ds_field_ui_layouts_save');
+ $form['#submit'] = array_merge($form['#submit'], $submit);
+}
+
+/**
+ * Add the layout previews to the Field UI screen.
+ *
+ * @param $form
+ * A collection of form properties.
+ * @param $form_state
+ * The state of the form
+ * @param $ds_layouts
+ * Collection of all the layouts
+ * @param $layout
+ * Current selected layout
+ * @param $entity_type
+ * The name of the entity type.
+ * @param $bundle
+ * The name of the bundle
+ * @param $view_mode
+ * The name of the view_mode
+ */
+function _ds_field_ui_table_layouts_preview(&$form, &$form_state, $ds_layouts, $layout, $entity_type, $bundle, $view_mode) {
+ $layout_string = '';
+
+ $form['additional_settings']['ds_layouts']['layout'] = array(
+ '#type' => 'select',
+ '#title' => t('Select a layout'),
+ '#options' => $layout->layout_options,
+ '#default_value' => isset($layout->layout) && empty($layout->disabled) ? $layout->layout : '',
+ '#prefix' => '<div class="ds-select-layout">',
+ '#suffix' => '</div>',
+ '#weight' => -1,
+ '#ajax' => array(
+ 'callback' => 'ds_field_ui_table_layouts_preview_callback',
+ 'wrapper' => 'ds_layout_wrapper',
+ ),
+ );
+ if (!isset($layout->layout)) {
+ $form['additional_settings']['ds_layouts']['layout']['#description'] = t("A layout must be selected to enable Display Suite functionality.");
+ }
+
+ $form['additional_settings']['ds_layouts']['preview'] = array(
+ '#type' => 'container',
+ '#prefix' => '<div id="ds_layout_wrapper">',
+ '#suffix' => '</div>',
+ '#weight' => -3,
+ );
+
+ if (isset($layout->layout) || isset($form_state['values']['additional_settings']['layout'])) {
+ $layout_string = isset($form_state['values']['additional_settings']['layout']) ? $form_state['values']['additional_settings']['layout'] : $layout->layout;
+ }
+
+ if (!empty($layout_string)) {
+ $chosen_layout = $ds_layouts[$layout_string];
+
+ if (empty($chosen_layout['flexible'])) {
+
+ $selected = '<strong>' . $chosen_layout['label'] . '</strong>';
+ $selected .= '<br/>' . t('The default template can be found in %path', array('%path' => $chosen_layout['path']));
+ $suggestions = t('Template suggestions') . ':<ul>';
+ $suggestions_array = array();
+
+ $suggestions_array[0] = $layout_string . '--' . $entity_type;
+ $suggestions_array[2] = $layout_string . '--' . $entity_type . '-' . $bundle;
+ $suggestions_array[4] = $layout_string . '--' . $entity_type . '--{id}';
+ if (!isset($form_state['no_view_mode_suggestions']) && $view_mode != 'default') {
+ $suggestions_array[1] = $layout_string . '--' . $entity_type . '-' . $view_mode;
+ $suggestions_array[3] = $layout_string . '--' . $entity_type . '-' . $bundle . '-' . $view_mode;
+ }
+
+ ksort($suggestions_array);
+ $suggestions .= '<ul><li>' . implode('.tpl.php</li><li>', $suggestions_array) . '.tpl.php</li></ul>';
+ }
+ else {
+ $suggestions = '';
+ $selected = t('You have selected the flexible %layout_label layout.', array('%layout_label' => $chosen_layout['label'], '%path' => $chosen_layout['path']));
+ }
+
+ if (isset($form_state['values']['additional_settings']['layout']) || (!empty($layout) && isset($layout->regions))) {
+ $fallback_image = drupal_get_path('module', 'ds') . '/images/preview.png';
+ $current_layout = isset($form_state['values']['additional_settings']['layout']) && (!isset($layout->layout) || $form_state['values']['additional_settings']['layout'] != $layout->layout) ? t('Current layout (after save)') : t('Current layout');
+ $image = (isset($chosen_layout['image']) && !empty($chosen_layout['image'])) ? $chosen_layout['path'] . '/' . $layout_string . '.png' : $fallback_image;
+ if (isset($chosen_layout['panels']) && !empty($chosen_layout['panels']['icon'])) {
+ $image = $chosen_layout['panels']['path'] . '/' . $chosen_layout['panels']['icon'];
+ }
+
+ $form['additional_settings']['ds_layouts']['preview'] ['title'] = array(
+ '#markup' => '<div class="ds-layout-preview-title">' . $current_layout . '</div>',
+ );
+ $form['additional_settings']['ds_layouts']['preview'] ['image'] = array(
+ '#markup' => '<div class="ds-layout-preview-image"><img src="' . base_path() . $image . '"/></div>',
+ );
+ $form['additional_settings']['ds_layouts']['preview']['info'] = array(
+ '#type' => 'container',
+ '#attributes' => array(
+ 'class' => array('ds-layout-preview-suggestion'),
+ ),
+ );
+ $form['additional_settings']['ds_layouts']['preview']['info']['suggestions'] = array(
+ '#markup' => '<p>' . $selected . '</p><p>' . t('!suggestions', array('!suggestions' => strtr($suggestions, '_', '-'))) . '</p>',
+ );
+
+ if (!empty($chosen_layout['css'])) {
+ $disable_css = FALSE;
+ if (isset($layout->settings['layout_disable_css'])) {
+ $disable_css = $layout->settings['layout_disable_css'];
+ }
+
+ $form['additional_settings']['ds_layouts']['preview']['info']['settings']['disable_css'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Disable layout CSS styles'),
+ '#default_value' => $disable_css,
+ );
+ }
+ }
+
+ if (isset($form_state['values']['additional_settings']['layout']) && (!isset($layout->layout) || $form_state['values']['additional_settings']['layout'] != $layout->layout)) {
+
+ // Get admin path.
+ $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle);
+ if ($view_mode != 'form') {
+ $admin_path .= '/display';
+ }
+ else {
+ $admin_path .= '/fields';
+ }
+
+ // If regions aren't set we don't have to move fields.
+ if (isset($layout->regions)) {
+ $url = 'admin/structure/ds/change-layout/' . $entity_type . '/' . $bundle . '/' . $view_mode . '/' . $layout_string . '?destination=' . $admin_path;
+ $form['#validate'][] = 'ds_field_ui_change_layout_validate';
+ }
+ else {
+ $url = $admin_path;
+ }
+
+ if ($view_mode != 'default' && $view_mode != 'form') {
+ $url .= '/' . $view_mode;
+ }
+
+ $form['layout_changed_url'] = array(
+ '#type' => 'value',
+ '#value' => $url,
+ );
+
+ $form['#submit'][] = 'ds_field_ui_change_layout_submit';
+ }
+ }
+}
+
+/**
+ * Ajax callback for _ds_field_ui_table_layouts_preview().
+ */
+function ds_field_ui_table_layouts_preview_callback($form, $form_state) {
+ return $form['additional_settings']['ds_layouts']['preview'];
+}
+
+/**
+ * Form validation handler for _ds_field_ui_table_layouts_preview().
+ */
+function ds_field_ui_change_layout_validate(&$form, &$form_state) {
+ $key1 = array_search('ds_field_ui_layouts_save', $form['#submit']);
+ $key2 = array_search('ds_field_ui_fields_save', $form['#submit']);
+ unset($form['#submit'][$key1]);
+ unset($form['#submit'][$key2]);
+}
+
+/**
+ * Form submission handler for _ds_field_ui_table_layouts_preview().
+ */
+function ds_field_ui_change_layout_submit($form, &$form_state) {
+ $values = $form_state['values'];
+ if (isset($values['additional_settings']['preview']['info']['settings']['disable_css'])) {
+ $disable_css = $values['additional_settings']['preview']['info']['settings']['disable_css'];
+ }
+ else {
+ $disable_css = FALSE;
+ }
+
+ $record = db_select('ds_layout_settings')
+ ->fields('ds_layout_settings')
+ ->condition('entity_type', $values['ds_entity_type'])
+ ->condition('bundle', $values['ds_bundle'])
+ ->condition('view_mode', $values['ds_view_mode'])
+ ->execute()
+ ->fetchObject();
+
+ $record->settings = unserialize($record->settings);
+ $record->settings['layout_disable_css'] = $disable_css;
+
+ drupal_write_record('ds_layout_settings', $record, array('id'));
+
+ unset($_GET['destination']);
+ global $base_url;
+ $url = $base_url . '/' . $values['layout_changed_url'];
+ $form_state['redirect'] = $url;
+}
+
+/**
+ * Add the fields to the Field UI form.
+ *
+ * @param $entity_type
+ * The name of the entity type.
+ * @param $bundle
+ * The name of the bundle
+ * @param $view_mode
+ * The name of the view_mode
+ * @param $form
+ * A collection of form properties.
+ * @param $form_state
+ * A collection of form_state properties.
+ */
+function _ds_field_ui_fields($entity_type, $bundle, $view_mode, &$form, &$form_state) {
+
+ // Do not add the fields if there is no layout.
+ if (!isset($form['#ds_layout'])) {
+ return;
+ }
+
+ // Get the fields and put them on the form.
+ $fields = ds_get_fields($entity_type, FALSE);
+
+ // Ultimate alter on Field UI fields, only used for edge cases.
+ $context = array(
+ 'entity_type' => $entity_type,
+ 'bundle' => $bundle,
+ 'view_mode' => $view_mode,
+ );
+ // Load views file if entity type is not ds_views.
+ // We need to cache the hook it's implementing.
+ if ($entity_type != 'ds_views' && module_exists('ds_extras') && variable_get('ds_extras_vd', FALSE)) {
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.vd');
+ }
+ drupal_alter('ds_fields_ui', $fields, $context);
+
+ // Get field settings.
+ $field_settings = ds_get_field_settings($entity_type, $bundle, $view_mode, FALSE);
+ $form['#field_settings'] = $field_settings;
+
+ $table = &$form['fields'];
+ $form['#ds_fields'] = array();
+
+ $field_label_options = array(
+ 'above' => t('Above'),
+ 'inline' => t('Inline'),
+ 'hidden' => t('<Hidden>'),
+ );
+ drupal_alter('ds_label_options', $field_label_options);
+
+ // Regions for fields.
+ $field_regions = array();
+ if (isset($form['#ds_layout']->settings['fields'])) {
+ $field_regions = $form['#ds_layout']->settings['fields'];
+ }
+
+ foreach ($fields as $key => $field) {
+
+ // Check on ui_limit.
+ if (!empty($field['ui_limit'])) {
+
+ $continue = TRUE;
+ foreach ($field['ui_limit'] as $limitation) {
+ list($limit_bundle, $limit_view_mode) = explode('|', $limitation);
+ if ($limit_bundle == '*' || $limit_bundle == $bundle) {
+ if ($limit_view_mode == '*' || $limit_view_mode == $view_mode) {
+ $continue = FALSE;
+ }
+ }
+ }
+
+ if ($continue) {
+ continue;
+ }
+ }
+
+ $form['#ds_fields'][] = $key;
+
+ // Check on formatter settings.
+ if (isset($form_state['formatter_settings'][$key])) {
+ $field['formatter_settings'] = $form_state['formatter_settings'][$key];
+ }
+ elseif (isset($field_settings[$key]['formatter_settings'])) {
+ $field['formatter_settings'] = $field_settings[$key]['formatter_settings'];
+ $form_state['formatter_settings'][$key] = $field['formatter_settings'];
+ }
+
+ if (!isset($field_settings[$key]['ft']) && isset($field_settings[$key]['ft'])) {
+ $form_state['formatter_settings'][$key]['ft'] = $field_settings[$key]['ft'];
+ }
+
+ $value = isset($form_state['formatter_settings']) ? $form_state['formatter_settings'] : array();
+
+ $hidden = array('hidden' => t('<Hidden>'));
+ $formatters = isset($field['properties']['formatters']) ? $hidden + $field['properties']['formatters'] : $hidden + array('default' => t('Default'));
+
+ $table[$key] = array(
+ '#row_type' => 'field',
+ '#js_settings' => array('field'),
+ '#region_callback' => 'field_ui_display_overview_row_region',
+ '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')),
+ 'human_name' => array(
+ '#markup' => check_plain($field['title']),
+ ),
+ 'weight' => array(
+ '#type' => 'textfield',
+ '#default_value' => isset($field_settings[$key]['weight']) ? $field_settings[$key]['weight'] : 0,
+ '#size' => 3,
+ '#attributes' => array('class' => array('field-weight')),
+ ),
+ 'parent_wrapper' => array(
+ 'parent' => array(
+ '#type' => 'select',
+ '#empty_value' => '',
+ '#options' => array(),
+ '#attributes' => array('class' => array('field-parent')),
+ '#parents' => array('fields', $key, 'parent'),
+ ),
+ 'hidden_name' => array(
+ '#type' => 'hidden',
+ '#default_value' => $key,
+ '#attributes' => array('class' => array('field-name')),
+ ),
+ ),
+ 'label' => array(
+ '#type' => 'select',
+ '#options' => $field_label_options,
+ '#default_value' => isset($field_settings[$key]['label']) ? $field_settings[$key]['label'] : 'hidden',
+ ),
+ 'format' => array(
+ 'type' => array(
+ '#type' => 'select',
+ '#options' => $formatters,
+ '#default_value' => isset($field_settings[$key]['format']) ? $field_settings[$key]['format'] : 'hidden',
+ '#attributes' => array('class' => array('field-formatter-type')),
+ ),
+ ),
+ 'settings_summary' => array(),
+ 'settings_edit' => array(),
+ );
+
+ // Don't show summary or cogwheel in hidden region.
+ if (_ds_field_ui_check_hidden_region($key, $form_state, $field_regions)) {
+ continue;
+ }
+
+ $field['name'] = $key;
+ $field['entity_type'] = $entity_type;
+ $field['bundle'] = $bundle;
+ $field['view_mode'] = $view_mode;
+
+ if ($form_state['formatter_settings_edit'] == $key) {
+ $table[$key]['settings_summary']['#attributes']['colspan'] = 2;
+ $settings_form = ds_field_settings_form($field, $form_state, $form, $view_mode);
+ ds_field_row_form_format_construct($table, $key, $settings_form);
+ }
+ else {
+ // After saving, the settings are updated here as well. First we create
+ // the element for the table cell.
+ $summary = ds_field_settings_summary($field, $form_state, $form, $view_mode);
+
+ if (isset($summary)) {
+ $table[$key]['settings_summary'] = $summary;
+ ds_field_row_form_format_summary_construct($table, $key);
+ }
+ }
+ }
+
+ // Add fields submit handler.
+ $form['#submit'][] = 'ds_field_ui_fields_save';
+}
+
+/**
+ * Alter the core field on the the Field UI form.
+ *
+ * @param $entity_type
+ * The name of the entity type.
+ * @param $bundle
+ * The name of the bundle
+ * @param $view_mode
+ * The name of the view_mode
+ * @param $form
+ * A collection of form properties.
+ * @param $form_state
+ * A collection of form_state properties.
+ */
+function _ds_field_ui_core_fields($entity_type, $bundle, $view_mode, &$form, &$form_state) {
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ // Gather type information.
+ $instances = field_info_instances($entity_type, $bundle);
+
+ $field_types = field_info_field_types();
+ $extra_fields = field_info_extra_fields($entity_type, $bundle, 'display');
+
+ $table = &$form['fields'];
+
+ // Regions for fields.
+ $field_regions = array();
+ if (isset($form['#ds_layout']->settings['fields'])) {
+ $field_regions = $form['#ds_layout']->settings['fields'];
+ }
+
+ // Field rows.
+ foreach ($instances as $key => $instance) {
+
+ // Don't show summary or cogwheel in hidden region.
+ if (_ds_field_ui_check_hidden_region($key, $form_state, $field_regions)) {
+ $table[$key]['settings_summary']['#markup'] = '';
+ $table[$key]['settings_edit'] = array();
+ continue;
+ }
+
+ $field = field_info_field($instance['field_name']);
+ $display = $instance['display'][$view_mode];
+
+ // Check the currently selected formatter, and merge persisted values for
+ // formatter settings.
+ if (isset($form_state['values']['fields'][$key]['type'])) {
+ $formatter_type = $form_state['values']['fields'][$key]['type'];
+ }
+ else {
+ $formatter_type = $display['type'];
+ }
+
+ $settings = $display['settings'];
+ if (isset($form_state['formatter_settings'][$key])) {
+ $settings = array_merge($settings, $form_state['formatter_settings'][$key]);
+ }
+ $settings += field_info_formatter_settings($formatter_type);
+
+ // Import field settings and merge with Field API settings.
+ if (!isset($form_state['formatter_settings'][$key]) && !empty($form['#field_settings'][$key]['formatter_settings']['ft'])) {
+ $form_state['formatter_settings'][$key] = $settings;
+ $form_state['formatter_settings'][$key]['ft'] = $form['#field_settings'][$key]['formatter_settings']['ft'];
+ }
+
+ // Change default value or Field API format, so we can change the right
+ // settings form when clicking on the cogwheel.
+ $form['fields'][$key]['format']['type']['#default_value'] = $formatter_type;
+
+ $instance['display'][$view_mode]['type'] = $formatter_type;
+ $formatter = field_info_formatter_types($formatter_type);
+ $instance['display'][$view_mode]['module'] = $formatter['module'];
+ $instance['display'][$view_mode]['settings'] = $settings;
+
+ // Base button element for the various formatter settings actions.
+ $base_button = array(
+ '#submit' => array(
+ 'field_ui_display_overview_multistep_submit',
+ ),
+ '#ajax' => array(
+ 'callback' => 'field_ui_display_overview_multistep_js',
+ 'wrapper' => 'field-display-overview-wrapper',
+ 'effect' => 'fade',
+ ),
+ '#field_name' => $key,
+ );
+
+ if ($form_state['formatter_settings_edit'] == $key) {
+ $formatter_type = $form_state['complete form']['fields'][$key]['format']['type']['#default_value'];
+ $formatter = field_info_formatter_types($formatter_type);
+ $instance['display'][$view_mode]['type'] = $formatter_type;
+ $instance['display'][$view_mode]['module'] = $formatter['module'];
+ $instance['display'][$view_mode]['settings'] = $settings + field_info_formatter_settings($formatter_type);
+ $function = $formatter['module'] . '_field_formatter_settings_form';
+
+ // Add the default formatter settings if any.
+ $settings_form = array();
+ if (function_exists($function)) {
+ $settings_form = $function($field, $instance, $view_mode, $form, $form_state);
+ }
+
+ // Add the field templates form when needed
+ if (module_exists('ds_extras') && variable_get('ds_extras_field_template', FALSE)) {
+ $context = array(
+ 'instance' => $instance,
+ 'view_mode' => $view_mode,
+ );
+
+ // Load the form
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.admin');
+ if (!is_array($settings_form)) $settings_form = array();
+ ds_extras_field_template_settings_form($settings_form, $form_state, $context);
+ }
+
+ // Allow other modules to alter the formatter settings form.
+ $context = array(
+ 'module' => $formatter['module'],
+ 'formatter' => $formatter,
+ 'field' => $field,
+ 'instance' => $instance,
+ 'view_mode' => $view_mode,
+ 'form' => $form,
+ 'form_state' => $form_state,
+ );
+ drupal_alter('field_formatter_settings_form', $settings_form, $context);
+
+ if ($settings_form) {
+ $table[$key]['format']['#cell_attributes'] = array('colspan' => 3);
+ $table[$key]['format']['settings_edit_form'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('field-formatter-settings-edit-form')),
+ '#parents' => array('fields', $key, 'settings_edit_form'),
+ 'label' => array(
+ '#markup' => t('Format settings:') . ' <span class="formatter-name">' . $formatter['label'] . '</span>',
+ ),
+ 'settings' => $settings_form,
+ 'actions' => array(
+ '#type' => 'actions',
+ 'save_settings' => $base_button + array(
+ '#type' => 'submit',
+ '#name' => $key . '_formatter_settings_update',
+ '#value' => t('Update'),
+ '#op' => 'update',
+ ),
+ 'cancel_settings' => $base_button + array(
+ '#type' => 'submit',
+ '#name' => $key . '_formatter_settings_cancel',
+ '#value' => t('Cancel'),
+ '#op' => 'cancel',
+ // Do not check errors for the 'Cancel' button, but make sure we
+ // get the value of the 'formatter type' select.
+ '#limit_validation_errors' => array(array('fields', $key, 'type')),
+ ),
+ ),
+ );
+ $table[$key]['#attributes']['class'][] = 'field-formatter-settings-editing';
+ $table[$key]['format']['type']['#attributes']['class'] = array('element-invisible');
+ }
+ }
+ else {
+ $summary = module_invoke($formatter['module'], 'field_formatter_settings_summary', $field, $instance, $view_mode);
+
+ // Allow other modules to alter the formatter summary.
+ $context = array(
+ 'module' => $formatter['module'],
+ 'formatter' => $formatter,
+ 'field' => $field,
+ 'instance' => $instance,
+ 'view_mode' => $view_mode,
+ );
+ drupal_alter('field_formatter_settings_summary', $summary, $context);
+
+ if (module_exists('ds_extras') && variable_get('ds_extras_field_template', FALSE)) {
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.admin');
+
+ // Field template summary
+ $functions = module_invoke_all('ds_field_theme_functions_info');
+ $default_field_function = variable_get('ft-default', 'theme_field');
+ $field_function = isset($form_state['formatter_settings'][$key]['ft']['func']) ? $form_state['formatter_settings'][$key]['ft']['func'] : $default_field_function;
+
+ if (!empty($summary)) {
+ $summary .= '<br />';
+ }
+ $summary .= 'Field template: ' . check_plain($functions[$field_function]) . '<br />';
+ }
+
+ if (!empty($summary)) {
+ $table[$key]['settings_summary'] = array();
+ $table[$key]['settings_edit'] = array();
+
+ $table[$key]['settings_summary'] = array(
+ '#markup' => '<div class="field-formatter-summary">' . $summary . '</div>',
+ '#cell_attributes' => array('class' => array('field-formatter-summary-cell')),
+ );
+ // Render the other part of the summary
+ ds_field_row_form_format_summary_construct($table, $key);
+ }
+ }
+ }
+}
+
+/**
+ * Helper function to check if we are in a hidden region or not.
+ */
+function _ds_field_ui_check_hidden_region($key, $form_state, $field_regions) {
+ $continue = FALSE;
+ if (isset($form_state['input']['fields'][$key]['region'])) {
+ if ($form_state['input']['fields'][$key]['region'] == 'hidden') {
+ $continue = TRUE;
+ }
+ }
+ elseif (!isset($field_regions[$key]) || $field_regions[$key] == 'hidden') {
+ $continue = TRUE;
+ }
+
+ return $continue;
+}
+
+/**
+ * Helper function for building the formatter settings.
+ */
+function ds_field_row_form_format_construct(&$table, $key, $settings_form) {
+
+ $base_button = array(
+ '#submit' => array(
+ 'field_ui_display_overview_multistep_submit',
+ ),
+ '#validate' => array(
+ 'ds_field_ui_fields_validate',
+ ),
+ '#ajax' => array(
+ 'callback' => 'field_ui_display_overview_multistep_js',
+ 'wrapper' => 'field-display-overview-wrapper',
+ 'effect' => 'fade',
+ ),
+ '#field_name' => $key,
+ );
+ $table[$key]['format']['settings_edit'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('field-formatter-settings-edit-form')),
+ '#parents' => array('fields', $key, 'settings_edit_form'),
+ '#weight' => -5,
+ // Create a settings form where hooks can pick in.
+ 'settings' => $settings_form,
+ 'actions' => array(
+ '#type' => 'actions',
+ 'save_settings' => $base_button + array(
+ '#type' => 'submit',
+ '#name' => $key . '_formatter_settings_update',
+ '#value' => t('Update'),
+ '#op' => 'update',
+ ),
+ 'cancel_settings' => $base_button + array(
+ '#type' => 'submit',
+ '#name' => $key . '_formatter_settings_cancel',
+ '#value' => t('Cancel'),
+ '#op' => 'cancel',
+ // Do not check errors for the 'Cancel' button.
+ '#limit_validation_errors' => array(),
+ ),
+ ),
+ );
+ $table[$key]['#attributes']['class'][] = 'field-formatter-settings-editing';
+ $table[$key]['format']['type']['#attributes']['class'] = array('element-invisible');
+}
+
+/**
+ * Helper function for formatter summary settings.
+ */
+function ds_field_row_form_format_summary_construct(&$table, $key) {
+
+ $base_button = array(
+ '#submit' => array('field_ui_display_overview_multistep_submit'),
+ '#ajax' => array(
+ 'callback' => 'field_ui_display_overview_multistep_js',
+ 'wrapper' => 'field-display-overview-wrapper',
+ 'effect' => 'fade',
+ ),
+ '#field_name' => $key,
+ );
+
+ // Add the configure button.
+ $table[$key]['settings_edit'] = $base_button + array(
+ '#type' => 'image_button',
+ '#name' => $key . '_formatter_settings_edit',
+ '#src' => 'misc/configure.png',
+ '#attributes' => array('class' => array('field-formatter-settings-edit'), 'alt' => t('Edit')),
+ '#op' => 'edit',
+ // Do not check errors for the 'Edit' button.
+ '#limit_validation_errors' => array(),
+ '#prefix' => '<div class="field-formatter-settings-edit-wrapper">',
+ '#suffix' => '</div>',
+ );
+}
+
+/**
+ * Add tab for adding new fields on the fly.
+ *
+ * @param $entity_type
+ * The name of the entity type.
+ * @param $bundle
+ * The name of the bundle
+ * @param $view_mode
+ * The name of the view_mode
+ * @param $form
+ * A collection of form properties.
+ * @param $form_state
+ * A collection of form_state properties.
+ */
+function _ds_field_ui_custom_fields($entity_type, $bundle, $view_mode, &$form, $form_state) {
+
+ $form['additional_settings']['add_custom_fields'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Custom fields'),
+ '#description' => t('Click on one of the buttons to create a new field.') . '<p></p>',
+ '#access' => user_access('admin_fields'),
+ );
+
+ // Include the CTools tools that we need.
+ ctools_include('ajax');
+ ctools_include('modal');
+
+ // Add CTools' javascript to the page.
+ ctools_modal_add_js();
+
+ $field_types = array(
+ 'custom_field' => t('Add a code field'),
+ 'manage_ctools' => t('Add a dynamic field'),
+ );
+ $field_types['manage_block'] = t('Add a block field');
+ $field_types['manage_preprocess'] = t('Add a preprocess field');
+
+ foreach ($field_types as $field_key => $field_title) {
+
+ $form['ctools_add_field_' . $field_key . '_url'] = array(
+ '#type' => 'hidden',
+ '#attributes' => array('class' => array('ctools_add_field_' . $field_key . '-url')),
+ '#value' => url('admin/structure/ds/nojs/add_field/' . $field_key),
+ );
+
+ $form['additional_settings']['add_custom_fields']['ctools_add_field_' . $field_key] = array(
+ '#type' => 'button',
+ '#value' => $field_title,
+ '#attributes' => array('class' => array('ctools-use-modal')),
+ '#id' => 'ctools_add_field_' . $field_key,
+ );
+ }
+ $form['additional_settings']['add_custom_fields']['manage_fields'] = array(
+ '#type' => 'link',
+ '#title' => 'Manage fields',
+ '#href' => 'admin/structure/ds/fields',
+ '#prefix' => '<div>',
+ '#suffix' => '</div>',
+ );
+}
+
+/**
+ * Utility function to check if we need to save anything for this field.
+ */
+function _ds_field_valid($key, $field, &$form_state, $view_mode = 'default') {
+ $continue = FALSE;
+
+ // Ignore the Field group module and the region to block plugin.
+ if ($key == '_add_new_group' || $key == '_add_new_field' || $key == '_add_new_block_region') {
+ $continue = TRUE;
+ }
+
+ // If the field is in hidden region, do not save. Check if the
+ // field has a type key which means it's from Field API and
+ // we need to reset that type to 'hidden' so it doesn't get
+ // fired by Field API in the frontend.
+ if (isset($field['region']) && $field['region'] == 'hidden') {
+ if (isset($field['type']) && $view_mode != 'form') {
+ $form_state['values']['fields'][$key]['type'] = 'hidden';
+ }
+
+ // In case of a form, fields will be set with #access to FALSE.
+ if ($view_mode != 'form') {
+ $continue = TRUE;
+ }
+ }
+
+ return $continue;
+}
+
+/**
+ * Utility function to return CSS classes.
+ */
+function _ds_classes($name = 'ds_classes_regions') {
+ static $classes = array();
+
+ if (!isset($classes[$name])) {
+ $classes[$name] = array();
+ $custom_classes = trim(variable_get($name, ''));
+ if (!empty($custom_classes)) {
+ $classes[$name][''] = t('None');
+ $custom_classes = explode("\n", $custom_classes);
+ foreach ($custom_classes as $key => $value) {
+ $classes_splitted = explode("|", $value);
+ $key = trim($classes_splitted[0]);
+ $friendly_name = isset($classes_splitted[1]) ? trim($classes_splitted[1]) : $key;
+ $classes[$name][check_plain($key)] = $friendly_name;
+ }
+ }
+ $name_clone = $name; // Prevent the name from being changed.
+ drupal_alter('ds_classes', $classes[$name], $name_clone);
+ }
+
+ return $classes[$name];
+}
+
+/**
+ * Utility function to sort a multidimensional array by a value in a sub-array.
+ *
+ * @param $a
+ * The array to sort.
+ * @param $subkey
+ * The subkey to sort by.
+ */
+function _ds_sort_fields($a, $subkey) {
+ foreach ($a as $k => $v) {
+ if (isset($v[$subkey])) {
+ $b[$k] = $v[$subkey];
+ }
+ }
+ asort($b);
+ foreach ($b as $key => $val) {
+ $c[$key] = $a[$key];
+ }
+ return $c;
+}
diff --git a/sites/all/modules/ds/includes/ds.registry.inc b/sites/all/modules/ds/includes/ds.registry.inc
new file mode 100644
index 000000000..4f8ecefa5
--- /dev/null
+++ b/sites/all/modules/ds/includes/ds.registry.inc
@@ -0,0 +1,590 @@
+<?php
+
+/**
+ * @file
+ * Registry file for Display Suite.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function _ds_menu() {
+ $items = array();
+
+ // Layout overview.
+ $items['admin/structure/ds'] = array(
+ 'title' => 'Display Suite',
+ 'description' => 'Manage layouts for entities and configure fields, view modes etc.',
+ 'page callback' => 'ds_layout_list',
+ 'file' => 'includes/ds.displays.inc',
+ 'access arguments' => array('admin_display_suite'),
+ );
+
+ // Layout overview, primary tab.
+ $items['admin/structure/ds/list'] = array(
+ 'title' => 'Displays',
+ 'weight' => -10,
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+
+ // Layout overview, secondary tab.
+ $items['admin/structure/ds/list/list'] = array(
+ 'title' => 'List',
+ 'weight' => -10,
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+
+ // Emergency page
+ $items['admin/structure/ds/list/emergency'] = array(
+ 'title' => 'Emergency',
+ 'description' => 'In case you have errors via Display Suite, visit this page.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_emergency'),
+ 'access arguments' => array('admin_display_suite'),
+ 'file' => 'includes/ds.displays.inc',
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 100,
+ );
+
+ // Change layout.
+ $items['admin/structure/ds/change-layout'] = array(
+ 'title' => 'Change layout',
+ 'description' => 'Act on layout change to move fields elsewhere',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_field_ui_layout_change'),
+ 'access arguments' => array('admin_display_suite'),
+ 'file' => 'includes/ds.field_ui.inc',
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+
+ // Revert layout.
+ $items['admin/structure/ds/revert-layout'] = array(
+ 'title' => 'Revert layout',
+ 'description' => 'Revert layout and field settings.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_revert_layout_field_settings_form'),
+ 'file' => 'includes/ds.field_ui.inc',
+ 'access arguments' => array('admin_display_suite'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+
+ // Disable layout.
+ $items['admin/structure/ds/disable'] = array(
+ 'title' => 'Disable layout',
+ 'description' => 'Disable layout and field settings',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_disable_layout_field_settings_form'),
+ 'file' => 'includes/ds.field_ui.inc',
+ 'access arguments' => array('admin_display_suite'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+
+ // Enable layout.
+ $items['admin/structure/ds/enable'] = array(
+ 'title' => 'Enable layout',
+ 'description' => 'Enable layout and field settings',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_enable_layout_field_settings_form'),
+ 'file' => 'includes/ds.field_ui.inc',
+ 'access arguments' => array('admin_display_suite'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+
+ // CTools content.
+ $items['admin/structure/ds/fields/manage_ctools/content'] = array(
+ 'title' => 'Ctools field content',
+ 'page callback' => 'ds_ctools_content',
+ 'file' => 'includes/ds.field_ui.inc',
+ 'access arguments' => array('admin_display_suite'),
+ 'type' => MENU_CALLBACK,
+ );
+
+ // Contextual links.
+ if (module_exists('contextual') && module_exists('field_ui')) {
+ $items['node/%node/display'] = array(
+ 'title' => 'Manage display',
+ 'description' => 'Manage display of this content.',
+ 'page callback' => 'ds_contextual_page_tab',
+ 'page arguments' => array(1, 'node'),
+ 'file' => 'includes/ds.contextual.inc',
+ 'access arguments' => array('administer content types'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+
+ $items['user/%user/display'] = array(
+ 'title' => 'Manage display',
+ 'description' => 'Manage display of this user profile.',
+ 'page callback' => 'ds_contextual_page_tab',
+ 'page arguments' => array(1, 'user'),
+ 'file' => 'includes/ds.contextual.inc',
+ 'access arguments' => array('administer users'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+
+ if (module_exists('taxonomy')) {
+ $items['taxonomy/term/%taxonomy_term/display'] = array(
+ 'title' => 'Manage display',
+ 'description' => 'Manage display of this term.',
+ 'page callback' => 'ds_contextual_page_tab',
+ 'page arguments' => array(2, 'taxonomy_term'),
+ 'access arguments' => array('administer taxonomy'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 11,
+ 'file' => 'includes/ds.contextual.inc',
+ );
+ }
+ }
+
+ return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function _ds_theme() {
+ $theme_functions = array();
+
+ // Layouts
+ $layouts = ds_get_layout_info();
+ foreach ($layouts as $key => $layout) {
+
+ // We don't need panel layouts to be registered.
+ if (isset($layout['module']) && $layout['module'] == 'panels') {
+ continue;
+ }
+
+ $theme_functions[$key] = array(
+ 'render element' => 'elements',
+ 'template' => strtr($key, '_', '-'),
+ 'path' => $layout['path'],
+ );
+ }
+
+ // Field templates
+ $field_functions = module_invoke_all('ds_field_theme_functions_info');
+ foreach ($field_functions as $key => $label) {
+ $theme_functions[$key] = array(
+ 'render element' => 'element',
+ 'function' => $key,
+ );
+ }
+
+ return $theme_functions;
+}
+
+/**
+ * Implements hook_features_api().
+ */
+function _ds_features_api() {
+ static $api = FALSE;
+
+ if (!$api) {
+ module_load_include('inc', 'features', 'includes/features.ctools');
+ $api = ctools_component_features_api('ds');
+ foreach ($api as $key => $value) {
+ switch ($key) {
+ case 'ds_field_settings':
+ $api[$key]['name'] = 'Display Suite field settings';
+ break;
+ case 'ds_layout_settings':
+ $api[$key]['name'] = 'Display Suite layout settings';
+ break;
+ case 'ds_view_modes':
+ $api[$key]['name'] = 'Display Suite view modes';
+ break;
+ case 'ds_fields':
+ $api[$key]['name'] = 'Display Suite fields';
+ break;
+ }
+ }
+ }
+
+ return $api;
+}
+
+/**
+ * Remove or rename layout & field settings on entity machine name update.
+ *
+ * @param $entity_type
+ * The name of the entity type.
+ * @param $info
+ * The entity info.
+ * @param $action
+ * The action, either update or delete.
+ */
+function _ds_entity_type_update($entity_type, $info, $action) {
+
+ // Delete settings.
+ if ($action == 'delete') {
+ db_delete('ds_layout_settings')
+ ->condition('entity_type', $entity_type)
+ ->condition('bundle', $info->type)
+ ->execute();
+ db_delete('ds_field_settings')
+ ->condition('entity_type', $entity_type)
+ ->condition('bundle', $info->type)
+ ->execute();
+ }
+
+ // Update settings.
+ if ($action == 'update') {
+ $records = db_query('SELECT * FROM {ds_layout_settings} WHERE entity_type = :entity_type AND bundle = :bundle', array(':entity_type' => $entity_type, ':bundle' => $info->old_type));
+ foreach ($records as $record) {
+ $old_id = $entity_type . '|' . $info->old_type . '|' . $record->view_mode;
+ $new_id = $entity_type . '|' . $info->type . '|' . $record->view_mode;
+ db_update('ds_layout_settings')
+ ->fields(array(
+ 'id' => $new_id,
+ 'bundle' => $info->type)
+ )
+ ->condition('id', $old_id)
+ ->execute();
+ }
+ $records = db_query('SELECT * FROM {ds_field_settings} WHERE entity_type = :entity_type AND bundle = :bundle', array(':entity_type' => $entity_type, ':bundle' => $info->old_type));
+ foreach ($records as $record) {
+ $old_id = $entity_type . '|' . $info->old_type . '|' . $record->view_mode;
+ $new_id = $entity_type . '|' . $info->type . '|' . $record->view_mode;
+ db_update('ds_field_settings')
+ ->fields(array(
+ 'id' => $new_id,
+ 'bundle' => $info->type)
+ )
+ ->condition('id', $old_id)
+ ->execute();
+ }
+ }
+
+ // Clear cache.
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+ cache_clear_all('ds_field_settings', 'cache');
+ field_info_cache_clear();
+}
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function _ds_theme_registry_alter(&$theme_registry) {
+
+ // Inject ds_entity_variables in all entity theming functions.
+ $entity_info = entity_get_info();
+ foreach ($entity_info as $entity => $info) {
+ if (isset($entity_info[$entity]['fieldable']) && $entity_info[$entity]['fieldable']) {
+
+ // User uses user_profile for theming.
+ if ($entity == 'user') $entity = 'user_profile';
+
+ // Only add preprocess functions if entity exposes theme function.
+ if (isset($theme_registry[$entity])) {
+ $theme_registry[$entity]['preprocess functions'][] = 'ds_entity_variables';
+ }
+ }
+ }
+
+ // Support for File Entity.
+ if (isset($theme_registry['file_entity'])) {
+ $theme_registry['file_entity']['preprocess functions'][] = 'ds_entity_variables';
+ }
+
+ // Support for Entity API.
+ if (isset($theme_registry['entity'])) {
+ $theme_registry['entity']['preprocess functions'][] = 'ds_entity_variables';
+ }
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function _ds_entity_info_alter(&$entity_info) {
+
+ // Make sure ds_view_modes table exists.
+ if (!db_table_exists('ds_view_modes')) {
+ return;
+ }
+
+ ctools_include('export');
+
+ // Add custom view modes to entities.
+ $custom_view_modes = ctools_export_crud_load_all('ds_view_modes');
+ foreach ($custom_view_modes as $view_mode_key => $view_mode_value) {
+ $view_mode = array(
+ 'label' => check_plain($view_mode_value->label),
+ 'custom settings' => FALSE,
+ );
+ foreach ($view_mode_value->entities as $entity_type) {
+ if (isset($entity_info[$entity_type])) {
+ $entity_info[$entity_type]['view modes'][$view_mode_key] = $view_mode;
+ }
+ }
+ }
+
+ // Add layout if applicable.
+ $ds_layouts = ds_get_layout_info();
+ $ds_layout_settings = ctools_export_crud_load_all('ds_layout_settings');
+ foreach ($ds_layout_settings as $row) {
+
+ // Don't store any configuration if the layout is disabled.
+ if (!empty($row->disabled)) {
+ continue;
+ }
+
+ // Don't store any configuration with layouts that don't exist anymore.
+ if (!isset($ds_layouts[$row->layout])) {
+ continue;
+ }
+
+ // Don't store any configuration if the entity type is not defined.
+ if (!isset($entity_info[$row->entity_type])) {
+ continue;
+ }
+
+ // Don't store any configuration if the bundle doesn't exist.
+ if (!isset($entity_info[$row->entity_type]['bundles'][$row->bundle])) {
+ continue;
+ }
+
+ $layout = $ds_layouts[$row->layout];
+ $layout['layout'] = $row->layout;
+ $layout['settings'] = $row->settings;
+ $entity_info[$row->entity_type]['bundles'][$row->bundle]['layouts'][$row->view_mode] = $layout;
+ }
+
+
+ $revision = array(
+ 'label' => 'Revision',
+ 'custom settings' => FALSE,
+ );
+ $entity_info['node']['view modes']['revision'] = $revision;
+}
+
+/**
+ * Implements hook_ds_layout_info().
+ */
+function _ds_ds_layout_info() {
+ $path = drupal_get_path('module', 'ds');
+ $layouts = array(
+ 'ds_1col' => array(
+ 'label' => t('One column'),
+ 'path' => $path . '/layouts/ds_1col',
+ 'regions' => array(
+ 'ds_content' => t('Content'),
+ ),
+ 'image' => TRUE,
+ ),
+ 'ds_1col_wrapper' => array(
+ 'label' => t('One column + wrapper'),
+ 'path' => $path . '/layouts/ds_1col_wrapper',
+ 'regions' => array(
+ 'ds_content' => t('Content'),
+ ),
+ 'image' => TRUE,
+ ),
+ 'ds_2col' => array(
+ 'label' => t('Two column'),
+ 'path' => $path . '/layouts/ds_2col',
+ 'regions' => array(
+ 'left' => t('Left'),
+ 'right' => t('Right')
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_2col_fluid' => array(
+ 'label' => t('Fluid two column'),
+ 'path' => $path . '/layouts/ds_2col_fluid',
+ 'regions' => array(
+ 'left' => t('Left'),
+ 'right' => t('Right')
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_2col_stacked' => array(
+ 'label' => t('Two column stacked'),
+ 'path' => $path . '/layouts/ds_2col_stacked',
+ 'regions' => array(
+ 'header' => t('Header'),
+ 'left' => t('Left'),
+ 'right' => t('Right'),
+ 'footer' => t('Footer'),
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_2col_stacked_fluid' => array(
+ 'label' => t('Fluid two column stacked'),
+ 'path' => $path . '/layouts/ds_2col_stacked_fluid',
+ 'regions' => array(
+ 'header' => t('Header'),
+ 'left' => t('Left'),
+ 'right' => t('Right'),
+ 'footer' => t('Footer'),
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_3col' => array(
+ 'label' => t('Three column - 25/50/25'),
+ 'path' => $path . '/layouts/ds_3col',
+ 'regions' => array(
+ 'left' => t('Left'),
+ 'middle' => t('Middle'),
+ 'right' => t('Right'),
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_3col_equal_width' => array(
+ 'label' => t('Three column - equal width'),
+ 'path' => $path . '/layouts/ds_3col_equal_width',
+ 'regions' => array(
+ 'left' => t('Left'),
+ 'middle' => t('Middle'),
+ 'right' => t('Right'),
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_3col_stacked' => array(
+ 'label' => t('Three column stacked - 25/50/25'),
+ 'path' => $path . '/layouts/ds_3col_stacked',
+ 'regions' => array(
+ 'header' => t('Header'),
+ 'left' => t('Left'),
+ 'middle' => t('Middle'),
+ 'right' => t('Right'),
+ 'footer' => t('Footer'),
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_3col_stacked_fluid' => array(
+ 'label' => t('Fluid three column stacked - 25/50/25'),
+ 'path' => $path . '/layouts/ds_3col_stacked_fluid',
+ 'regions' => array(
+ 'header' => t('Header'),
+ 'left' => t('Left'),
+ 'middle' => t('Middle'),
+ 'right' => t('Right'),
+ 'footer' => t('Footer'),
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_3col_stacked_equal_width' => array(
+ 'label' => t('Three column stacked - equal width'),
+ 'path' => $path . '/layouts/ds_3col_stacked_equal_width',
+ 'regions' => array(
+ 'header' => t('Header'),
+ 'left' => t('Left'),
+ 'middle' => t('Middle'),
+ 'right' => t('Right'),
+ 'footer' => t('Footer'),
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_4col' => array(
+ 'label' => t('Four column - equal width'),
+ 'path' => $path . '/layouts/ds_4col',
+ 'regions' => array(
+ 'first' => t('First'),
+ 'second' => t('Second'),
+ 'third' => t('Third'),
+ 'fourth' => t('Fourth'),
+ ),
+ 'css' => TRUE,
+ 'image' => TRUE,
+ ),
+ 'ds_reset' => array(
+ 'label' => t('Reset'),
+ 'path' => $path . '/layouts/ds_reset',
+ 'regions' => array(
+ 'ds_content' => t('Content'),
+ ),
+ 'image' => TRUE,
+ ),
+ );
+
+ // Support for panels.
+ if (module_exists('panels')) {
+ ctools_include('plugins', 'panels');
+ $panel_layouts = panels_get_layouts();
+ foreach ($panel_layouts as $key => $layout) {
+ // The easy ones.
+ if (isset($layout['regions'])) {
+ $layouts['panels-' . $key] = array(
+ 'label' => $layout['title'],
+ 'path' => $layout['path'],
+ 'module' => 'panels',
+ // We need the Panels plugin info array to correctly include the
+ // layout and its CSS files later on.
+ 'panels' => $layout,
+ 'flexible' => FALSE,
+ 'regions' => $layout['regions'],
+ );
+ if (!empty($layout['css'])) {
+ $layouts['panels-' . $key]['css'] = TRUE;
+ }
+ }
+ // Flexible panel layouts, ignore the default flexible.
+ else {
+ if ($layout['name'] != 'flexible') {
+ $regions = panels_flexible_panels(array(), array(), $layout);
+ $layouts['panels-' . $key] = array(
+ 'label' => $layout['title'],
+ 'path' => $layout['path'],
+ 'module' => 'panels',
+ 'panels' => $layout,
+ 'flexible' => TRUE,
+ 'regions' => $regions,
+ );
+ }
+ }
+ }
+ }
+
+ // Get layouts defined in the active theme and base theme (if applicable).
+ $themes = list_themes();
+ $theme = variable_get('theme_default', 'bartik');
+
+ $base_theme = array();
+ $ancestor = $theme;
+ while ($ancestor && isset($themes[$ancestor]->base_theme)) {
+ $ancestor = $themes[$ancestor]->base_theme;
+ $base_theme[] = $themes[$ancestor];
+ }
+
+ foreach (array_reverse($base_theme) as $base) {
+ _ds_layouts_scan_theme($base->name, $layouts);
+ }
+ _ds_layouts_scan_theme($theme, $layouts);
+
+ return $layouts;
+}
+
+function _ds_layouts_scan_theme($theme, &$layouts) {
+ $theme_layouts = file_scan_directory(drupal_get_path('theme', $theme) . '/ds_layouts', '/inc$/');
+ foreach ($theme_layouts as $file => $values) {
+ include_once(DRUPAL_ROOT . '/' . $file);
+ $function = 'ds_' . $values->name;
+ $layouts[$values->name] = $function();
+ $layouts[$values->name]['path'] = str_replace('/' . $values->filename, '', $file);
+ }
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function _ds_menu_alter(&$items) {
+ // Do not conflict with the revisioning module.
+ if (module_exists('revisioning')) {
+ $items['node/%node/revisions/%vid/view']['page callback'] = 'ds_revision_node_show';
+ $items['node/%node/revisions/%vid/view']['file'] = 'includes/ds.revision.inc';
+ $items['node/%node/revisions/%vid/view']['file path'] = drupal_get_path('module', 'ds');
+ }
+ else {
+ $items['node/%node/revisions/%/view']['page callback'] = 'ds_revision_node_show';
+ $items['node/%node/revisions/%/view']['file'] = 'includes/ds.revision.inc';
+ $items['node/%node/revisions/%/view']['file path'] = drupal_get_path('module', 'ds');
+ }
+}
diff --git a/sites/all/modules/ds/includes/ds.revision.inc b/sites/all/modules/ds/includes/ds.revision.inc
new file mode 100644
index 000000000..fabe48ecb
--- /dev/null
+++ b/sites/all/modules/ds/includes/ds.revision.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Display Suite revision callback.
+ */
+
+/**
+ * Menu callback: show an individual revision node using the revision view mode.
+ */
+function ds_revision_node_show($node, $message = NULL) {
+
+ drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
+
+ // Update the history table, stating that this user viewed this node.
+ node_tag_new($node);
+
+ // Determine view mode.
+ $layout = ds_get_layout('node', $node->type, 'revision', FALSE);
+ $view_mode = ($layout) ? 'revision' : 'full';
+ drupal_static('ds_view_mode', $view_mode);
+
+ // For markup consistency with other pages, use node_view_multiple() rather than node_view().
+ return node_view_multiple(array($node->nid => $node), $view_mode);
+}
diff --git a/sites/all/modules/ds/js/ds.admin.js b/sites/all/modules/ds/js/ds.admin.js
new file mode 100644
index 000000000..081d82579
--- /dev/null
+++ b/sites/all/modules/ds/js/ds.admin.js
@@ -0,0 +1,118 @@
+/**
+ * @file
+ * Javascript functionality for Display Suite's administration UI.
+ */
+
+(function($) {
+
+Drupal.DisplaySuite = Drupal.DisplaySuite || {};
+Drupal.DisplaySuite.fieldopened = '';
+Drupal.DisplaySuite.layout_original = '';
+
+/**
+ * Ctools selection content.
+ */
+Drupal.behaviors.CToolsSelection = {
+ attach: function (context) {
+ if ($('#ctools-content-selection').length > 0) {
+ $('#ctools-content-selection .section-link').click(function() {
+ $('#ctools-content-selection .content').hide();
+ container = $(this).attr('id') + '-container';
+ $('#' + container).show();
+ return false;
+ });
+ }
+ }
+};
+
+/**
+ * Save the Dynamic field content configuration.
+ */
+$.fn.dsCtoolsContentConfiguration = function (configuration) {
+ $(this[0]).val(configuration);
+}
+
+/**
+ * Update the select content text.
+ */
+$.fn.dsCtoolsContentUpdate = function () {
+ $(this[0]).html(Drupal.t('Click update to save the configuration'));
+}
+
+/**
+ * Save the page after saving a new field.
+ */
+$.fn.dsRefreshDisplayTable = function () {
+ $('#edit-submit').click();
+}
+
+/**
+ * Row handlers for the 'Manage display' screen.
+ */
+Drupal.fieldUIDisplayOverview = Drupal.fieldUIDisplayOverview || {};
+
+Drupal.fieldUIDisplayOverview.ds = function (row, data) {
+
+ this.row = row;
+ this.name = data.name;
+ this.region = data.region;
+ this.tableDrag = data.tableDrag;
+
+ // Attach change listener to the 'region' select.
+ this.$regionSelect = $('select.ds-field-region', row);
+ this.$regionSelect.change(Drupal.fieldUIOverview.onChange);
+
+ // Attach change listener to the 'formatter type' select.
+ this.$formatSelect = $('select.field-formatter-type', row);
+ this.$formatSelect.change(Drupal.fieldUIOverview.onChange);
+
+ return this;
+};
+
+Drupal.fieldUIDisplayOverview.ds.prototype = {
+
+ /**
+ * Returns the region corresponding to the current form values of the row.
+ */
+ getRegion: function () {
+ return this.$regionSelect.val();
+ },
+
+ /**
+ * Reacts to a row being changed regions.
+ *
+ * This function is called when the row is moved to a different region, as a
+ * result of either :
+ * - a drag-and-drop action
+ * - user input in one of the form elements watched by the
+ * Drupal.fieldUIOverview.onChange change listener.
+ *
+ * @param region
+ * The name of the new region for the row.
+ * @return
+ * A hash object indicating which rows should be AJAX-updated as a result
+ * of the change, in the format expected by
+ * Drupal.displayOverview.AJAXRefreshRows().
+ */
+ regionChange: function (region) {
+
+ // Replace dashes with underscores.
+ region = region.replace(/-/g, '_');
+
+ // Set the region of the select list.
+ this.$regionSelect.val(region);
+
+ // Prepare rows to be refreshed in the form.
+ var refreshRows = {};
+ refreshRows[this.name] = this.$regionSelect.get(0);
+
+ // If a row is handled by field_group module, loop through the children.
+ if ($(this.row).hasClass('field-group') && $.isFunction(Drupal.fieldUIDisplayOverview.group.prototype.regionChangeFields)) {
+ Drupal.fieldUIDisplayOverview.group.prototype.regionChangeFields(region, this, refreshRows);
+ }
+
+ return refreshRows;
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/ds/layouts/ds_1col/ds-1col.tpl.php b/sites/all/modules/ds/layouts/ds_1col/ds-1col.tpl.php
new file mode 100644
index 000000000..c8d9c02db
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_1col/ds-1col.tpl.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 1 column template.
+ */
+?>
+<<?php print $ds_content_wrapper; print $layout_attributes; ?> class="ds-1col <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <?php print $ds_content; ?>
+</<?php print $ds_content_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_1col/ds_1col.png b/sites/all/modules/ds/layouts/ds_1col/ds_1col.png
new file mode 100644
index 000000000..6137607eb
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_1col/ds_1col.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_1col_wrapper/ds-1col-wrapper.tpl.php b/sites/all/modules/ds/layouts/ds_1col_wrapper/ds-1col-wrapper.tpl.php
new file mode 100644
index 000000000..38518d621
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_1col_wrapper/ds-1col-wrapper.tpl.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 1 column template with layout wrapper.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-1col <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $ds_content_wrapper ?> class="<?php print trim($ds_content_classes); ?>">
+ <?php print $ds_content; ?>
+ </<?php print $ds_content_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?> \ No newline at end of file
diff --git a/sites/all/modules/ds/layouts/ds_1col_wrapper/ds_1col_wrapper.png b/sites/all/modules/ds/layouts/ds_1col_wrapper/ds_1col_wrapper.png
new file mode 100644
index 000000000..6137607eb
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_1col_wrapper/ds_1col_wrapper.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_2col/ds-2col.tpl.php b/sites/all/modules/ds/layouts/ds_2col/ds-2col.tpl.php
new file mode 100644
index 000000000..7592143d1
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col/ds-2col.tpl.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 2 column template.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-2col <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_2col/ds_2col-rtl.css b/sites/all/modules/ds/layouts/ds_2col/ds_2col-rtl.css
new file mode 100644
index 000000000..12946d62c
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col/ds_2col-rtl.css
@@ -0,0 +1,12 @@
+/**
+ * @file
+ * RTL styling for the ds_2col template.
+ */
+
+.ds-2col > .group-left {
+ float: right;
+}
+
+.ds-2col > .group-right {
+ float: right;
+}
diff --git a/sites/all/modules/ds/layouts/ds_2col/ds_2col.css b/sites/all/modules/ds/layouts/ds_2col/ds_2col.css
new file mode 100644
index 000000000..1d1f810ef
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col/ds_2col.css
@@ -0,0 +1,14 @@
+/**
+ * @file
+ * Styling for the ds_2col template.
+ */
+
+.ds-2col > .group-left {
+ float: left; /* LTR */
+ width: 50%;
+}
+
+.ds-2col > .group-right {
+ float: left; /* LTR */
+ width: 50%;
+}
diff --git a/sites/all/modules/ds/layouts/ds_2col/ds_2col.png b/sites/all/modules/ds/layouts/ds_2col/ds_2col.png
new file mode 100644
index 000000000..cf953c164
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col/ds_2col.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_2col_fluid/ds-2col-fluid.tpl.php b/sites/all/modules/ds/layouts/ds_2col_fluid/ds-2col-fluid.tpl.php
new file mode 100644
index 000000000..03d38be6e
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_fluid/ds-2col-fluid.tpl.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 2 column template.
+ */
+
+ // Add sidebar classes so that we can apply the correct width in css.
+ if (($left && !$right) || ($right && !$left)) {
+ $classes .= ' group-one-column';
+ }
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-2col-fluid <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <?php if ($left): ?>
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+ <?php endif; ?>
+
+ <?php if ($right): ?>
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+ <?php endif; ?>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid-rtl.css b/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid-rtl.css
new file mode 100644
index 000000000..e1561a892
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid-rtl.css
@@ -0,0 +1,12 @@
+/**
+ * @file
+ * RTL styling for the ds_2col_fluid template.
+ */
+
+.ds-2col-fluid > .group-left {
+ float: right;
+}
+
+.ds-2col-fluid > .group-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid.css b/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid.css
new file mode 100644
index 000000000..80a96d268
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid.css
@@ -0,0 +1,20 @@
+/**
+ * @file
+ * Styling for the ds_2col_fluid template.
+ */
+
+.ds-2col-fluid > .group-left {
+ float: left; /* LTR */
+ width: 50%;
+}
+
+.ds-2col-fluid > .group-right {
+ float: right; /* LTR */
+ width: 50%;
+}
+
+.ds-2col-fluid.group-one-column > .group-left,
+.ds-2col-fluid.group-one-column > .group-right {
+ width: 100%;
+ float: none;
+}
diff --git a/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid.png b/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid.png
new file mode 100644
index 000000000..7f46b44b1
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_fluid/ds_2col_fluid.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_2col_stacked/ds-2col-stacked.tpl.php b/sites/all/modules/ds/layouts/ds_2col_stacked/ds-2col-stacked.tpl.php
new file mode 100644
index 000000000..93d7194e5
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_stacked/ds-2col-stacked.tpl.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 2 column stacked template.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-2col-stacked <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $header_wrapper ?> class="group-header<?php print $header_classes; ?>">
+ <?php print $header; ?>
+ </<?php print $header_wrapper ?>>
+
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+
+ <<?php print $footer_wrapper ?> class="group-footer<?php print $footer_classes; ?>">
+ <?php print $footer; ?>
+ </<?php print $footer_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked-rtl.css b/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked-rtl.css
new file mode 100644
index 000000000..d059618f6
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked-rtl.css
@@ -0,0 +1,12 @@
+/**
+ * @file
+ * RTL styling for the ds-2col-stacked template.
+ */
+
+.ds-2col-stacked > .group-left {
+ float: right;
+}
+
+.ds-2col-stacked > .group-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked.css b/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked.css
new file mode 100644
index 000000000..6d6727f53
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked.css
@@ -0,0 +1,21 @@
+/**
+ * @file
+ * Styling for the ds-2col-stacked template.
+ */
+
+.ds-2col-stacked > .group-header {
+}
+
+.ds-2col-stacked > .group-left {
+ float: left; /* LTR */
+ width: 50%;
+}
+
+.ds-2col-stacked > .group-right {
+ float: right; /* LTR */
+ width: 50%;
+}
+
+.ds-2col-stacked > .group-footer {
+ clear: both;
+}
diff --git a/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked.png b/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked.png
new file mode 100644
index 000000000..3d94a8481
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_stacked/ds_2col_stacked.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds-2col-stacked-fluid.tpl.php b/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds-2col-stacked-fluid.tpl.php
new file mode 100644
index 000000000..9c8c34ed5
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds-2col-stacked-fluid.tpl.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Display Suite fluid 2 column stacked template.
+ */
+
+ // Add sidebar classes so that we can apply the correct width in css.
+ if (($left && !$right) || ($right && !$left)) {
+ $classes .= ' group-one-column';
+ }
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-2col-stacked-fluid <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $header_wrapper ?> class="group-header<?php print $header_classes; ?>">
+ <?php print $header; ?>
+ </<?php print $header_wrapper ?>>
+
+ <?php if ($left): ?>
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+ <?php endif; ?>
+
+ <?php if ($right): ?>
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+ <?php endif; ?>
+
+ <<?php print $footer_wrapper ?> class="group-footer<?php print $footer_classes; ?>">
+ <?php print $footer; ?>
+ </<?php print $footer_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid-rtl.css b/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid-rtl.css
new file mode 100644
index 000000000..b6c864ab6
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid-rtl.css
@@ -0,0 +1,12 @@
+/**
+ * @file
+ * RTL styling for the ds-2col-stacked-fluid template.
+ */
+
+.ds-2col-stacked-fluid > .group-left {
+ float: right;
+}
+
+.ds-2col-stacked-fluid > .group-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid.css b/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid.css
new file mode 100644
index 000000000..11604fd93
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid.css
@@ -0,0 +1,27 @@
+/**
+ * @file
+ * Styling for the ds-2col-stacked-fluid template.
+ */
+
+.ds-2col-stacked-fluid > .group-header {
+}
+
+.ds-2col-stacked-fluid > .group-left {
+ float: left; /* LTR */
+ width: 50%;
+}
+
+.ds-2col-stacked-fluid > .group-right {
+ float: right; /* LTR */
+ width: 50%;
+}
+
+.ds-2col-stacked-fluid.group-one-column > .group-left,
+.ds-2col-stacked-fluid.group-one-column > .group-right {
+ width: 100%;
+ float: none;
+}
+
+.ds-2col-stacked-fluid > .group-footer {
+ clear: both;
+}
diff --git a/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid.png b/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid.png
new file mode 100644
index 000000000..2bb1b0e09
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_2col_stacked_fluid/ds_2col_stacked_fluid.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_3col/ds-3col.tpl.php b/sites/all/modules/ds/layouts/ds_3col/ds-3col.tpl.php
new file mode 100644
index 000000000..7ea1d84be
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col/ds-3col.tpl.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 3 column 25/50/25 template.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-3col <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes;?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+
+ <<?php print $middle_wrapper ?> class="group-middle<?php print $middle_classes;?>">
+ <?php print $middle; ?>
+ </<?php print $middle_wrapper ?>>
+
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes;?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_3col/ds_3col-rtl.css b/sites/all/modules/ds/layouts/ds_3col/ds_3col-rtl.css
new file mode 100644
index 000000000..3b7529a98
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col/ds_3col-rtl.css
@@ -0,0 +1,16 @@
+/**
+ * @file
+ * RTL styling for the ds-3col template.
+ */
+
+.ds-3col > .group-left {
+ float: right;
+}
+
+.ds-3col > .group-middle {
+ float: right;
+}
+
+.ds-3col > .group-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col/ds_3col.css b/sites/all/modules/ds/layouts/ds_3col/ds_3col.css
new file mode 100644
index 000000000..ff1ce2311
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col/ds_3col.css
@@ -0,0 +1,19 @@
+/**
+ * @file
+ * Styling for the ds-3col template.
+ */
+
+.ds-3col > .group-left {
+ width: 25%;
+ float: left; /* LTR */
+}
+
+.ds-3col > .group-middle {
+ width: 50%;
+ float: left; /* LTR */
+}
+
+.ds-3col > .group-right {
+ width: 25%;
+ float: right; /* LTR */
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col/ds_3col.png b/sites/all/modules/ds/layouts/ds_3col/ds_3col.png
new file mode 100644
index 000000000..a309e9fc0
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col/ds_3col.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_3col_equal_width/ds-3col-equal-width.tpl.php b/sites/all/modules/ds/layouts/ds_3col_equal_width/ds-3col-equal-width.tpl.php
new file mode 100644
index 000000000..aaf96064a
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_equal_width/ds-3col-equal-width.tpl.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 3 column equal width template.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-3col-equal <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+
+ <<?php print $middle_wrapper ?> class="group-middle<?php print $middle_classes; ?>">
+ <?php print $middle; ?>
+ </<?php print $middle_wrapper ?>>
+
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width-rtl.css b/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width-rtl.css
new file mode 100644
index 000000000..77781e944
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width-rtl.css
@@ -0,0 +1,16 @@
+/**
+ * @file
+ * RTL styling for the ds-3col-equal template.
+ */
+
+.ds-3col-equal > .group-left {
+ float: right;
+}
+
+.ds-3col-equal > .group-middle {
+ float: right;
+}
+
+.ds-3col-equal > .group-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width.css b/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width.css
new file mode 100644
index 000000000..5f33f16fa
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width.css
@@ -0,0 +1,19 @@
+/**
+ * @file
+ * Styling for the ds-3col-equal template.
+ */
+
+.ds-3col-equal > .group-left {
+ width: 33%;
+ float: left; /* LTR */
+}
+
+.ds-3col-equal > .group-middle {
+ width: 34%;
+ float: left; /* LTR */
+}
+
+.ds-3col-equal > .group-right {
+ width: 33%;
+ float: right; /* LTR */
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width.png b/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width.png
new file mode 100644
index 000000000..f27045df9
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_equal_width/ds_3col_equal_width.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked/ds-3col-stacked.tpl.php b/sites/all/modules/ds/layouts/ds_3col_stacked/ds-3col-stacked.tpl.php
new file mode 100644
index 000000000..be6de7b72
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked/ds-3col-stacked.tpl.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 3 column 25/50/25 stacked template.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-3col-stacked <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $header_wrapper ?> class="group-header<?php print $header_classes; ?>">
+ <?php print $header; ?>
+ </<?php print $header_wrapper ?>>
+
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+
+ <<?php print $middle_wrapper ?> class="group-middle<?php print $middle_classes; ?>">
+ <?php print $middle; ?>
+ </<?php print $middle_wrapper ?>>
+
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+
+ <<?php print $footer_wrapper ?> class="group-footer<?php print $footer_classes; ?>">
+ <?php print $footer; ?>
+ </<?php print $footer_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked-rtl.css b/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked-rtl.css
new file mode 100644
index 000000000..5370b6fe6
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked-rtl.css
@@ -0,0 +1,16 @@
+/**
+ * @file
+ * RTL styling for the ds-3col-stacked template.
+ */
+
+.ds-3col-stacked > .group-left {
+ float: right;
+}
+
+.ds-3col-stacked > .group-middle {
+ float: right;
+}
+
+.ds-3col-stacked > .group-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked.css b/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked.css
new file mode 100644
index 000000000..79401f454
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked.css
@@ -0,0 +1,27 @@
+/**
+ * @file
+ * Styling for the ds-3col-stacked template.
+ */
+
+.ds-3col-stacked > .group-header {
+ clear: both;
+}
+
+.ds-3col-stacked > .group-left {
+ width: 25%;
+ float: left; /* LTR */
+}
+
+.ds-3col-stacked > .group-middle {
+ width: 50%;
+ float: left; /* LTR */
+}
+
+.ds-3col-stacked > .group-right {
+ width: 25%;
+ float: right; /* LTR */
+}
+
+.ds-3col-stacked > .group-footer {
+ clear: both;
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked.png b/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked.png
new file mode 100644
index 000000000..e18c863bc
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked/ds_3col_stacked.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds-3col-stacked-equal-width.tpl.php b/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds-3col-stacked-equal-width.tpl.php
new file mode 100644
index 000000000..18ea38a56
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds-3col-stacked-equal-width.tpl.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 3 column stacked template.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-3col-stacked-equal <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $header_wrapper ?> class="group-header<?php print $header_classes; ?>">
+ <?php print $header; ?>
+ </<?php print $header_wrapper ?>>
+
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+
+ <<?php print $middle_wrapper ?> class="group-middle<?php print $middle_classes; ?>">
+ <?php print $middle; ?>
+ </<?php print $middle_wrapper ?>>
+
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+
+ <<?php print $footer_wrapper ?> class="group-footer<?php print $footer_classes; ?>">
+ <?php print $footer; ?>
+ </<?php print $footer_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width-rtl.css b/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width-rtl.css
new file mode 100644
index 000000000..8dc278ce9
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width-rtl.css
@@ -0,0 +1,16 @@
+/**
+ * @file
+ * RTL styling for the ds-3col-stacked-equal template.
+ */
+
+.ds-3col-stacked-equal > .group-left {
+ float: right;
+}
+
+.ds-3col-stacked-equal > .group-middle {
+ float: right;
+}
+
+.ds-3col-stacked-equal > .group-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width.css b/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width.css
new file mode 100644
index 000000000..595fc23c6
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width.css
@@ -0,0 +1,27 @@
+/**
+ * @file
+ * Styling for the ds-3col-stacked-equal template.
+ */
+
+.ds-3col-stacked-equal > .group-header {
+ clear: both;
+}
+
+.ds-3col-stacked-equal > .group-left {
+ width: 33%;
+ float: left; /* LTR */
+}
+
+.ds-3col-stacked-equal > .group-middle {
+ width: 34%;
+ float: left; /* LTR */
+}
+
+.ds-3col-stacked-equal > .group-right {
+ width: 33%;
+ float: right; /* LTR */
+}
+
+.ds-3col-stacked-equal > .group-footer {
+ clear: both;
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width.png b/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width.png
new file mode 100644
index 000000000..0a7d8dc4a
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked_equal_width/ds_3col_stacked_equal_width.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds-3col-stacked-fluid.tpl.php b/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds-3col-stacked-fluid.tpl.php
new file mode 100644
index 000000000..8f2fa1c73
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds-3col-stacked-fluid.tpl.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Display Suite fluid 3 column 25/50/25 stacked template.
+ */
+
+ // Add sidebar classes so that we can apply the correct width to the center region in css.
+ if (($left && !$right) || ($right && !$left)) $classes .= ' group-one-sidebar';
+ if ($left && $right) $classes .= ' group-two-sidebars';
+ if ($left) $classes .= ' group-sidebar-left';
+ if ($right) $classes .= ' group-sidebar-right';
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-3col-stacked-fluid <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $header_wrapper ?> class="group-header<?php print $header_classes; ?>">
+ <?php print $header; ?>
+ </<?php print $header_wrapper ?>>
+
+ <?php if ($left): ?>
+ <<?php print $left_wrapper ?> class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </<?php print $left_wrapper ?>>
+ <?php endif; ?>
+
+ <?php if ($middle): ?>
+ <<?php print $middle_wrapper ?> class="group-middle<?php print $middle_classes; ?>">
+ <?php print $middle; ?>
+ </<?php print $middle_wrapper ?>>
+ <?php endif; ?>
+
+ <?php if ($right): ?>
+ <<?php print $right_wrapper ?> class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </<?php print $right_wrapper ?>>
+ <?php endif; ?>
+
+ <<?php print $footer_wrapper ?> class="group-footer<?php print $footer_classes; ?>">
+ <?php print $footer; ?>
+ </<?php print $footer_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid-rtl.css b/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid-rtl.css
new file mode 100644
index 000000000..5ffc0483d
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid-rtl.css
@@ -0,0 +1,16 @@
+/**
+ * @file
+ * RTL styling for the ds-3col-stacked-fluid template.
+ */
+
+.ds-3col-stacked-fluid > .group-left {
+ float: right;
+}
+
+.ds-3col-stacked-fluid > .group-middle {
+ float: right;
+}
+
+.ds-3col-stacked-fluid > .group-right {
+ float: left;
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid.css b/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid.css
new file mode 100644
index 000000000..c2322ba1b
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid.css
@@ -0,0 +1,35 @@
+/**
+ * @file
+ * Styling for the ds-3col-stacked-fluid template.
+ */
+
+.ds-3col-stacked-fluid > .group-header {
+ clear: both;
+}
+
+.ds-3col-stacked-fluid > .group-left {
+ width: 25%;
+ float: left; /* LTR */
+}
+
+.ds-3col-stacked-fluid > .group-middle {
+ width: 100%;
+ float: left; /* LTR */
+}
+
+.ds-3col-stacked-fluid.group-one-sidebar > .group-middle {
+ width: 75%;
+}
+
+.ds-3col-stacked-fluid.group-two-sidebars > .group-middle {
+ width: 50%;
+}
+
+.ds-3col-stacked-fluid > .group-right {
+ width: 25%;
+ float: right; /* LTR */
+}
+
+.ds-3col-stacked-fluid > .group-footer {
+ clear: both;
+}
diff --git a/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid.png b/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid.png
new file mode 100644
index 000000000..b63c246ba
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_3col_stacked_fluid/ds_3col_stacked_fluid.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_4col/ds-4col.tpl.php b/sites/all/modules/ds/layouts/ds_4col/ds-4col.tpl.php
new file mode 100644
index 000000000..afcad5db9
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_4col/ds-4col.tpl.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 4 column template.
+ */
+?>
+<<?php print $layout_wrapper; print $layout_attributes; ?> class="ds-4col <?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <<?php print $first_wrapper ?> class="group-first<?php print $first_classes; ?>">
+ <?php print $first; ?>
+ </<?php print $first_wrapper ?>>
+
+ <<?php print $second_wrapper ?> class="group-second<?php print $second_classes; ?>">
+ <?php print $second; ?>
+ </<?php print $second_wrapper ?>>
+
+ <<?php print $third_wrapper ?> class="group-third<?php print $third_classes; ?>">
+ <?php print $third; ?>
+ </<?php print $third_wrapper ?>>
+
+ <<?php print $fourth_wrapper ?> class="group-fourth<?php print $fourth_classes; ?>">
+ <?php print $fourth; ?>
+ </<?php print $fourth_wrapper ?>>
+
+</<?php print $layout_wrapper ?>>
+
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_4col/ds_4col-rtl.css b/sites/all/modules/ds/layouts/ds_4col/ds_4col-rtl.css
new file mode 100644
index 000000000..4fac2ba68
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_4col/ds_4col-rtl.css
@@ -0,0 +1,20 @@
+/**
+ * @file
+ * RTL styling for the ds-4col template.
+ */
+
+.ds-4col > .group-first {
+ float: right;
+}
+
+.ds-4col > .group-second {
+ float: right;
+}
+
+.ds-4col > .group-third {
+ float: right;
+}
+
+.ds-4col > .group-fourth {
+ float: right;
+}
diff --git a/sites/all/modules/ds/layouts/ds_4col/ds_4col.css b/sites/all/modules/ds/layouts/ds_4col/ds_4col.css
new file mode 100644
index 000000000..c902e05ee
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_4col/ds_4col.css
@@ -0,0 +1,24 @@
+/**
+ * @file
+ * Styling for the ds-4col template.
+ */
+
+.ds-4col > .group-first {
+ width: 25%;
+ float: left; /* LTR */
+}
+
+.ds-4col > .group-second {
+ width: 25%;
+ float: left; /* LTR */
+}
+
+.ds-4col > .group-third {
+ width: 25%;
+ float: left; /* LTR */
+}
+
+.ds-4col > .group-fourth {
+ width: 25%;
+ float: left; /* LTR */
+}
diff --git a/sites/all/modules/ds/layouts/ds_4col/ds_4col.png b/sites/all/modules/ds/layouts/ds_4col/ds_4col.png
new file mode 100644
index 000000000..567e74f1e
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_4col/ds_4col.png
Binary files differ
diff --git a/sites/all/modules/ds/layouts/ds_reset/ds-reset.tpl.php b/sites/all/modules/ds/layouts/ds_reset/ds-reset.tpl.php
new file mode 100644
index 000000000..d616a875e
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_reset/ds-reset.tpl.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @file
+ * Display Suite reset template.
+ */
+?>
+<?php print $ds_content; ?>
+<?php if (!empty($drupal_render_children)): ?>
+ <?php print $drupal_render_children ?>
+<?php endif; ?>
diff --git a/sites/all/modules/ds/layouts/ds_reset/ds_reset.png b/sites/all/modules/ds/layouts/ds_reset/ds_reset.png
new file mode 100644
index 000000000..d1d3fc10f
--- /dev/null
+++ b/sites/all/modules/ds/layouts/ds_reset/ds_reset.png
Binary files differ
diff --git a/sites/all/modules/ds/modules/ds_devel/ds_devel.info b/sites/all/modules/ds/modules/ds_devel/ds_devel.info
new file mode 100644
index 000000000..7b59e714e
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_devel/ds_devel.info
@@ -0,0 +1,13 @@
+name = "Display Suite Devel"
+description = "Development functionality for Display Suite."
+core = "7.x"
+package = "Display Suite"
+dependencies[] = ds
+dependencies[] = devel
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/modules/ds_devel/ds_devel.module b/sites/all/modules/ds/modules/ds_devel/ds_devel.module
new file mode 100644
index 000000000..fcc60826a
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_devel/ds_devel.module
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Display Suite Devel
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function ds_devel_menu() {
+ $items = array();
+
+ $items['node/%node/devel/markup'] = array(
+ 'title' => 'Markup',
+ 'page callback' => 'ds_devel_render_object',
+ 'page arguments' => array('node', 1),
+ 'access arguments' => array('access devel information'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 101,
+ );
+
+ return $items;
+}
+
+/**
+ * Renders the markup of a node in HTML entities.
+ */
+function ds_devel_render_object($type, $node, $view_mode = 'full') {
+
+ $build = node_view($node, $view_mode);
+ $markup = drupal_render($build);
+
+ $links = array();
+ $links[] = l('Default', 'node/' . $node->nid . '/devel/markup/');
+ $view_modes = ds_entity_view_modes('node');
+ foreach ($view_modes as $key => $info) {
+ if (!empty($info['custom settings'])) {
+ $links[] = l($info['label'], 'node/' . $node->nid . '/devel/markup/' . $key);
+ }
+ }
+
+ return array(
+ '#markup' => '<div>' . implode(' - ', $links) . '</div><hr /><code><pre>' . check_plain($markup) . '</pre></code>'
+ );
+}
diff --git a/sites/all/modules/ds/modules/ds_extras/README.txt b/sites/all/modules/ds/modules/ds_extras/README.txt
new file mode 100644
index 000000000..9ce2b342d
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/README.txt
@@ -0,0 +1,20 @@
+
+The extras module contains functionality that is not often used. It holds
+following functionality:
+
+ - switch view modes: switch view mode on a per node basis.
+ - block region: add regions which will be exposed as blocks.
+ - extra fields: expose extra fields defined by other modules.
+ - field permissions: add view permissions on DS fields.
+ - Flag: expose flags as fields
+ - Hidden region: region which in case it has fields will not be printed.
+ - field templates: overwrite any field with custom markup.
+ - switch view mode field: switch from one view mode to another inline.
+ - page title options: hide the page title or manually set (with substitutions).
+ - contextual links: add the 'manage display' link to contextual links
+ and on the full page view of nodes, users and terms.
+ - Views displays: render views (row fields) into a different layout.
+ Important: If you are creating new ds fields for vd,
+ check ds_vd_render_title_field() how to return the content.
+
+Any other functionality will be included in this module. \ No newline at end of file
diff --git a/sites/all/modules/ds/modules/ds_extras/ds_extras.ds_fields_info.inc b/sites/all/modules/ds/modules/ds_extras/ds_extras.ds_fields_info.inc
new file mode 100644
index 000000000..0ee5a92bc
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/ds_extras.ds_fields_info.inc
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Display Suite extras fields.
+ */
+
+/**
+ * Implements hook_ds_fields_info().
+ */
+function ds_extras_ds_fields_info($entity_type) {
+ $fields = array();
+
+ // Switch field support.
+ if (variable_get('ds_extras_switch_field', FALSE) && $entity_type == 'node') {
+ $fields[$entity_type]['ds_switch_field'] = array(
+ 'title' => t('Switch view mode'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_extras_switch_view_mode_field',
+ 'properties' => array('settings' => array()),
+ );
+ }
+
+ // Flag support.
+ if (variable_get('ds_extras_flag') && module_exists('flag')) {
+ if ($entity_type == 'node') {
+ $flags = flag_get_flags('node');
+ foreach ($flags as $name => $flag) {
+ $ui_limit = array();
+ if (!empty($flag->types)) {
+ foreach ($flag->types as $type) {
+ $ui_limit[] = $type . '|*';
+ }
+ }
+ $fields['node']['ds_flag_' . $name] = array(
+ 'title' => t('Flag: ' . $flag->get_label('title')),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_extras_flags_add_flag_link',
+ 'properties' => array(
+ 'flag' => $name,
+ ),
+ 'ui_limit' => $ui_limit,
+ );
+ }
+ }
+ }
+
+ // DS Views.
+ if ($entity_type == 'ds_views') {
+ $fields[$entity_type] = array(
+ 'title' => array(
+ 'title' => t('Views title'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_vd_render_title_field',
+ 'properties' => array(
+ 'formatters' => array(
+ 'vd_title_h1' => t('H1 title'),
+ 'vd_title_h2' => t('H2 title'),
+ 'vd_title_p' => t('Paragraph title'),
+ ),
+ )
+ ),
+ 'header' => array(
+ 'title' => t('Views header'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'exposed' => array(
+ 'title' => t('Exposed filters'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'attachment_before' => array(
+ 'title' => t('Attachment before'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'rows' => array(
+ 'title' => t('Views content'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'empty' => array(
+ 'title' => t('Empty text'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'pager' => array(
+ 'title' => t('Pager'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'attachment_after' => array(
+ 'title' => t('Attachment after'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'more' => array(
+ 'title' => t('More'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'footer' => array(
+ 'title' => t('Views footer'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ 'feed_icon' => array(
+ 'title' => t('Feed icon'),
+ 'field_type' => DS_FIELD_TYPE_PREPROCESS,
+ ),
+ );
+ }
+
+ if (!empty($fields)) {
+ return $fields;
+ }
+}
diff --git a/sites/all/modules/ds/modules/ds_extras/ds_extras.info b/sites/all/modules/ds/modules/ds_extras/ds_extras.info
new file mode 100644
index 000000000..94f877788
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/ds_extras.info
@@ -0,0 +1,13 @@
+name = "Display Suite Extras"
+description = "Contains additional features for Display Suite."
+core = "7.x"
+package = "Display Suite"
+dependencies[] = ds
+configure = admin/structure/ds/list/extras
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/modules/ds_extras/ds_extras.install b/sites/all/modules/ds/modules/ds_extras/ds_extras.install
new file mode 100644
index 000000000..da9e60072
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/ds_extras.install
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Install file.
+ */
+
+ /**
+ * Implements hook_install().
+ */
+function ds_extras_install() {
+ db_update('system')
+ ->fields(array('weight' => 2))
+ ->condition('name', 'ds_extras')
+ ->execute();
+
+ $schema['node_revision'] = array();
+ ds_extras_schema_alter($schema);
+ foreach ($schema['node_revision']['fields'] as $name => $spec) {
+ db_add_field('node_revision', $name, $spec);
+ }
+}
+
+/**
+ * Implements hook_schema().
+ */
+function ds_extras_schema() {
+
+ $schema['ds_vd'] = array(
+ 'description' => 'The base table for views displays.',
+
+ // CTools export definitions.
+ 'export' => array(
+ 'key' => 'vd',
+ 'identifier' => 'ds_vd',
+ 'default hook' => 'ds_vd_info',
+ 'can disable' => FALSE,
+ 'api' => array(
+ 'owner' => 'ds_extras',
+ 'api' => 'ds_extras',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+
+ 'fields' => array(
+ 'vd' => array(
+ 'description' => 'The primary identifier for the views display.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'label' => array(
+ 'description' => 'The label for the views display.',
+ 'type' => 'varchar',
+ 'length' => 132,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ ),
+ 'primary key' => array('vd'),
+ );
+
+ return $schema;
+}
+
+
+/**
+ * Implements hook_schema_alter().
+ */
+function ds_extras_schema_alter(&$schema) {
+
+ // Add a field ds_switch to the node_revision table in order
+ // to store the name of view mode to switch to.
+ if (isset($schema['node_revision'])) {
+ $schema['node_revision']['fields']['ds_switch'] = array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ );
+ }
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function ds_extras_uninstall() {
+ variable_del('ds_extras_region_to_block');
+ variable_del('ds_extras_region_blocks');
+ variable_del('ds_extras_switch_view_mode');
+ variable_del('ds_extras_vd');
+ variable_del('ds_extras_hide_page_title');
+ variable_del('ds_extras_field_template');
+ variable_del('ds_classes_fields');
+ variable_del('ds_extras_fields_extra');
+ variable_del('ds_extras_fields_extra_list');
+ variable_del('ds_extras_switch_field');
+ variable_del('ds_extras_editor_switch');
+ variable_del('ft-default');
+ variable_del('ft-kill-colon');
+ variable_del('ds_extras_flag');
+ variable_del('ds_extras_hidden_region');
+ variable_del('ds_extras_hide_page_sidebars');
+ db_drop_field('node_revision', 'ds_switch');
+}
+
+/**
+ * Fix storage of formatter settings in ds_field_settings table.
+ */
+function ds_extras_update_7200() {
+ ctools_include('export');
+ $ds_field_settings = ctools_export_crud_load_all('ds_field_settings');
+ db_truncate('ds_field_settings')->execute();
+ foreach ($ds_field_settings as $layout => $field_settings) {
+ $record = $field_settings;
+ foreach ($field_settings->settings as $field => $settings) {
+ // Move any field template settings to 'formatter_settings' key.
+ if (isset($settings['ft'])) {
+ $settings['formatter_settings']['ft'] = $settings['ft'];
+ unset($settings['ft']);
+ }
+
+ // Inspect the classes key, in case it's an array, something went
+ // wrong during saving, simply unset the array completely.
+ if (isset($settings['formatter_settings']['ft']['classes']) && is_array($settings['formatter_settings']['ft']['classes'])) {
+ unset($settings['formatter_settings']['ft']['classes']);
+ }
+
+ $record->settings[$field] = $settings;
+ }
+ drupal_write_record('ds_field_settings', $record);
+ }
+}
diff --git a/sites/all/modules/ds/modules/ds_extras/ds_extras.module b/sites/all/modules/ds/modules/ds_extras/ds_extras.module
new file mode 100644
index 000000000..ac0fba5ce
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/ds_extras.module
@@ -0,0 +1,1106 @@
+<?php
+
+/**
+ * @file
+ * Display Suite extras main functions.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function ds_extras_menu() {
+ $items = array();
+
+ $items['admin/structure/ds/list/extras'] = array(
+ 'title' => 'Extras',
+ 'description' => 'Configure extra functionality for Display Suite.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_extras_settings'),
+ 'access arguments' => array('admin_display_suite'),
+ 'file' => 'includes/ds_extras.admin.inc',
+ 'type' => MENU_LOCAL_TASK,
+ );
+
+ if (variable_get('ds_extras_switch_field')) {
+ $items['ds-switch-view-mode'] = array(
+ 'title' => 'Switch view',
+ 'description' => 'Switches a view mode inline.',
+ 'page callback' => 'ds_switch_view_mode_inline',
+ 'access arguments' => array('access content'),
+ 'file' => 'includes/ds_extras.pages.inc',
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if (variable_get('ds_extras_vd', FALSE) && module_exists('field_ui') && module_exists('views')) {
+ $items['admin/structure/ds/vd'] = array(
+ 'title' => 'Views displays',
+ 'description' => 'Manage your views templates.',
+ 'page callback' => 'ds_extras_vd_overview',
+ 'file' => 'includes/ds_extras.vd.inc',
+ 'access arguments' => array('admin_display_suite'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items['admin/structure/ds/vd/manage'] = array(
+ 'title' => 'Manage layout',
+ 'description' => 'Manage your views templates.',
+ 'page callback' => 'ds_extras_vd_manage',
+ 'file' => 'includes/ds_extras.vd.inc',
+ 'access arguments' => array('admin_display_suite'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ }
+
+ return $items;
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function ds_extras_entity_info() {
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.registry');
+ return _ds_extras_entity_info();
+}
+
+/**
+ * Implements hook_ds_layout_info_alter().
+ */
+function ds_extras_ds_layout_info_alter(&$layouts) {
+ if (variable_get('ds_extras_hidden_region')) {
+ foreach ($layouts as $key => $layout) {
+ $layouts[$key]['regions']['ds_hidden'] = t('Hidden');
+ }
+ }
+}
+
+/**
+ * Implements hook_permission().
+ */
+function ds_extras_permission() {
+ $permissions = array();
+
+ // Extra check to make sure this doesn't get fired on install.
+ if (variable_get('ds_extras_switch_view_mode', FALSE)) {
+ foreach (node_type_get_names() as $key => $name) {
+ $permissions['ds_switch ' . $key] = array(
+ 'title' => t('Switch view modes on :type', array(':type' => $name))
+ );
+ }
+ }
+
+ if (variable_get('ds_extras_field_permissions', FALSE)) {
+ $entities = entity_get_info();
+ foreach ($entities as $entity_type => $info) {
+ $fields = ds_get_fields($entity_type);
+ foreach ($fields as $key => $finfo) {
+ $permissions['view ' . $key . ' on ' . $entity_type] = array(
+ 'title' => t('View !field on !entity_type', array('!field' => $finfo['title'], '!entity_type' => $info['label'])),
+ );
+ }
+ }
+ }
+
+ return $permissions;
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function ds_extras_admin_paths() {
+ if (variable_get('node_admin_theme')) {
+ return array(
+ 'node/*/display' => TRUE,
+ 'user/*/display' => TRUE,
+ 'taxonomy/term/*/display' => TRUE,
+ );
+ }
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function ds_extras_menu_alter(&$items) {
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.registry');
+ _ds_extras_menu_alter($items);
+}
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function ds_extras_theme_registry_alter(&$theme_registry) {
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.registry');
+ _ds_extras_theme_registry_alter($theme_registry);
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function ds_extras_module_implements_alter(&$implementations, $hook) {
+ module_load_include('inc', 'ds_extras', 'includes/ds_extras.registry');
+ _ds_extras_module_implements_alter($implementations, $hook);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_extras_form_field_ui_display_overview_form_alter(&$form, &$form_state) {
+ form_load_include($form_state, 'inc', 'ds_extras', 'includes/ds_extras.admin');
+ ds_extras_field_ui_alter($form, $form_state);
+}
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function ds_extras_ctools_plugin_api($owner, $api) {
+ if ($owner == 'ds_extras' && $api == 'ds_extras') {
+ return array('version' => 1);
+ }
+}
+
+/**
+ * DS fields access.
+ *
+ * @param $field
+ * The machine name of the field
+ * @param $entity_type
+ * The name of the entity type.
+ */
+function ds_extras_ds_field_access($field, $entity_type) {
+
+ if (user_access('view ' . $field . ' on ' . $entity_type)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Implements hook_field_attach_view_alter().
+ */
+function ds_extras_field_attach_view_alter(&$build, $context) {
+
+ // If views and core doesn't send information along on the entity,
+ // Display Suite doesn't have a context to render the fields.
+ if (!isset($build['#entity_type']) && !isset($build['#bundle'])) {
+ return;
+ }
+
+ $block_data = &drupal_static('ds_block_region');
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+
+ if (empty($region_blocks)) {
+ return;
+ }
+
+ $entity_type = $build['#entity_type'];
+ $bundle = $build['#bundle'];
+ $view_mode = $context['view_mode'];
+
+ $properties = array();
+ foreach (element_properties($build) as $property) {
+ $properties[$property] = $build[$property];
+ }
+ $properties['#view_mode'] = $view_mode;
+
+ if ($layout = ds_get_layout($entity_type, $bundle, $view_mode)) {
+ foreach ($region_blocks as $block_key => $block) {
+ if ($block['info'] == "{$entity_type}_{$bundle}_{$view_mode}" && isset($layout['settings']['regions'][$block_key]) && !empty($layout['settings']['regions'][$block_key])) {
+ foreach ($layout['settings']['regions'][$block_key] as $key => $field) {
+ if (isset($build[$field])) {
+ $block_data[$block_key][$field] = $build[$field];
+ unset($build[$field]);
+ }
+ }
+ if (isset($block_data[$block_key]) && is_array($block_data[$block_key])) {
+ $block_data[$block_key] += $properties;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_extras_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
+ $theme_functions = module_invoke_all('ds_field_theme_functions_info');
+ unset($theme_functions['theme_ds_field_expert']);
+ $form['instance']['ds_extras_field_template'] = array(
+ '#type' => 'select',
+ '#title' => t('Field Template'),
+ '#default_value' => isset($form['#instance']['ds_extras_field_template']) ? $form['#instance']['ds_extras_field_template'] : '',
+ '#options' => $theme_functions,
+ '#description' => t('Choose the default HTML for this field.'),
+ '#empty_option' => t('Use sitewide default'),
+ );
+}
+
+/**
+ * Utility function to return the view mode for the current entity.
+ *
+ * The drupal_static is called in ds_extras_node_show to set
+ * the current view mode. Through this technique, the hide page
+ * title functionality can also work across other view modes
+ * if another one is chosen for the full page of the node.
+ */
+function ds_extras_get_view_mode() {
+ return drupal_static('ds_extras_view_mode');
+}
+
+/**
+ * Page title options for a full entity page view.
+ */
+function ds_extras_process_page_title(&$variables) {
+ $page_title = drupal_static('ds_page_title');
+ if (!empty($page_title)) {
+ $variables['title'] = $page_title['title'];
+ if (!empty($page_title['head_title'])) {
+ drupal_set_title($page_title['head_title']);
+ }
+ }
+
+ // Support for Views page title, currently only hiding the title.
+ if (variable_get('ds_extras_vd', FALSE) && $view = views_get_page_view()) {
+ if ($layout = ds_get_layout('ds_views', $view->name . '-' . $view->current_display, 'default')) {
+ if (isset($layout['settings']['hide_page_title']) && $layout['settings']['hide_page_title'] == 1) {
+ $variables['title'] = '';
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_ds_layout_settings_alter().
+ */
+function ds_extras_ds_layout_settings_alter($record, $form_state) {
+ if (isset($form_state['values']['additional_settings']['ds_page_title']['ds_page_title_options']['page_option_type']) ||
+ isset($form_state['values']['page_option_type'])) {
+
+ // Save page title view type
+ if (isset($form_state['values']['additional_settings']['ds_page_title']['ds_page_title_options']['page_option_type'])) {
+ $record->settings['hide_page_title'] = $form_state['values']['additional_settings']['ds_page_title']['ds_page_title_options']['page_option_type'];
+ }
+ else {
+ $form_state['values']['page_option_type'];
+ }
+
+ // Save page title
+ if (isset($form_state['values']['additional_settings']['ds_page_title']['ds_page_title_options']['page_option_title'])) {
+ $record->settings['page_option_title'] = $form_state['values']['additional_settings']['ds_page_title']['ds_page_title_options']['page_option_title'];
+ }
+ else {
+ $record->settings['page_option_title'] = $form_state['values']['page_option_title'];
+ }
+ }
+ if (isset($form_state['values']['additional_settings']['hide_sidebars'])) {
+ $record->settings['hide_sidebars'] = $form_state['values']['additional_settings']['hide_sidebars'];
+ }
+}
+
+/**
+ * Switch view mode field.
+ */
+function ds_extras_switch_view_mode_field($field) {
+ $output = '';
+ static $added = FALSE;
+
+ if (isset($field['formatter_settings'])) {
+
+ $entity = $field['entity'];
+ $id = $entity->nid;
+ $url = $field['entity_type'] . '-' . $field['view_mode'] . '-' . $id . '-';
+ $switch = array();
+
+ foreach ($field['formatter_settings']['vms'] as $key => $value) {
+ if (!empty($value)) {
+ $class = 'switch-' . $key;
+ if ($key == $field['view_mode']) {
+ $switch[] = '<span class="' . $class . '">' . check_plain(t($value)) . '</span>';
+ }
+ else {
+ $switch[] = '<span class="' . $class . '"><a href="" class="' . $url . $key . '">' . check_plain(t($value)) . '</a></span>';
+ }
+ }
+ }
+
+ if (!empty($switch)) {
+ if (!$added) {
+ $add = TRUE;
+ drupal_add_js(drupal_get_path('module', 'ds_extras') . '/js/ds_extras.js');
+ }
+ $output = '<div class="switch-view-mode-field">' . implode(' ', $switch) . '</div>';
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_extras_form_node_type_form_alter(&$form, $form_state, $form_id) {
+ $node_type = $form['#node_type']->type;
+
+ // Get the view modes.
+ $options = ds_extras_get_bundle_view_modes('node', $node_type);
+
+ // Only fire if we have more than 1 option.
+ if (count($options) > 1) {
+ if (!isset($form['ds_extras'])) {
+ $form['ds_extras'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Display Suite: Extras'), // There's already a 'Display settings' fieldset
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'additional_settings',
+ '#weight' => 100,
+ );
+ }
+
+ $form['ds_extras']['ds_extras_view_modes'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('View modes'),
+ '#options' => $options,
+ '#default_value' => variable_get('ds_extras_view_modes_' . $node_type, array_keys($options)),
+ '#description' => t('Select which view modes will be available to switch to on node edit forms.'),
+ );
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_extras_form_node_form_alter(&$form, $form_state, $form_id) {
+ $node = $form['#node'];
+
+ // Switch full view mode.
+ if (user_access('ds_switch ' . $node->type)) {
+
+ // Get the view modes.
+ $view_modes = ds_extras_get_bundle_view_modes('node', $node->type);
+ $enabled_vm = variable_get('ds_extras_view_modes_' . $node->type, array_keys($view_modes));
+ $layouts = array();
+ $options = array();
+ foreach ($view_modes as $key => $label) {
+ if (in_array($key, $enabled_vm)) {
+ $layout = ds_get_layout('node', $node->type, $key, FALSE);
+ $layouts[$key] = $layout;
+ $options[$key] = $label;
+ }
+ }
+
+ // Only fire if we have more than 1 option.
+ if (count($options) > 1) {
+ if (!isset($form['ds_extras'])) {
+ $form['ds_extras'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Display settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'additional_settings',
+ '#weight' => 100,
+ );
+ }
+
+ $form['ds_extras']['ds_switch'] = array(
+ '#type' => 'select',
+ '#title' => t('View mode'),
+ '#options' => $options,
+ '#default_value' => isset($node->ds_switch) ? $node->ds_switch : 'default',
+ '#description' => t('Switch to a different view mode to display the full page view of this node.'),
+ '#weight' => -1,
+ '#ajax' => array(
+ 'callback' => 'ds_extras_switch_view_mode_preview_callback',
+ 'wrapper' => 'ds_switch_preview_wrapper',
+ ),
+ );
+
+ $form['ds_extras']['preview'] = array(
+ '#type' => 'container',
+ '#prefix' => '<div id="ds_switch_preview_wrapper">',
+ '#suffix' => '</div>',
+ );
+
+ $fallback_image = drupal_get_path('module', 'ds') . '/images/preview.png';
+ $mode = isset($form_state['input']['ds_switch']) ? $form_state['input']['ds_switch'] : (isset($node->ds_switch) ? $node->ds_switch : 'default');
+ if (isset($layouts[$mode])) {
+ $chosen_layout = $layouts[$mode];
+ $image = (isset($chosen_layout['image']) && !empty($chosen_layout['image'])) ? $chosen_layout['path'] . '/' . $chosen_layout['layout'] . '.png' : $fallback_image;
+ if (isset($chosen_layout['panels']) && !empty($chosen_layout['panels']['icon'])) {
+ $image = $chosen_layout['panels']['path'] . '/' . $chosen_layout['panels']['icon'];
+ }
+ }
+ else {
+ $image = $fallback_image;
+ }
+
+ $form['ds_extras']['preview']['image'] = array(
+ '#markup' => '<div class="ds-layout-preview-image"><img src="' . base_path() . $image . '"/></div>',
+ );
+ }
+ }
+}
+
+/**
+ * Get view modes for an entity bundle.
+ */
+function ds_extras_get_bundle_view_modes($type, $bundle) {
+ $view_modes = array('default' => t('Default'));
+
+ $view_mode_settings = field_view_mode_settings($type, $bundle);
+ $ds_vm = ds_entity_view_modes($type);
+
+ foreach ($ds_vm as $key => $item) {
+ $overriden = (!empty($view_mode_settings[$key]['custom_settings']) ? TRUE : FALSE);
+ if ($overriden) {
+ $view_modes[$key] = $item['label'];
+ }
+ }
+
+ return $view_modes;
+}
+
+/**
+ * Ajax callback for _ds_field_ui_table_layouts_preview().
+ */
+function ds_extras_switch_view_mode_preview_callback($form, $form_state) {
+ return $form['ds_extras']['preview'];
+}
+
+/**
+ * Implements hook_block_info().
+ */
+function ds_extras_block_info() {
+
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+
+ if (empty($region_blocks)) {
+ return array();
+ }
+
+ foreach ($region_blocks as $key => $block) {
+ $blocks[$key] = array(
+ 'info' => $block['title'],
+ 'cache' => DRUPAL_NO_CACHE,
+ );
+ }
+ return $blocks;
+}
+
+/**
+ * Implements hook_block_view().
+ */
+function ds_extras_block_view($delta = '') {
+ $data = drupal_static('ds_block_region');
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+
+ if (!empty($data[$delta])) {
+ $block = array();
+ $block['subject'] = $region_blocks[$delta]['title'];
+ $block['content'] = $data[$delta];
+ return $block;
+ }
+}
+
+/**
+ * Implements hook_ds_layout_region_alter().
+ */
+function ds_extras_ds_layout_region_alter($context, &$region_info) {
+
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+
+ // Bail out if region_blocks is empty or we are working on default view mode.
+ if (empty($region_blocks) || $context['view_mode'] == 'default') {
+ return;
+ }
+
+ $entity_type = $context['entity_type'];
+ $bundle = $context['bundle'];
+ $view_mode = $context['view_mode'];
+
+ foreach ($region_blocks as $block_key => $block) {
+ if ($block['info'] == "{$entity_type}_{$bundle}_{$view_mode}") {
+ $region_info['region_options'][$block_key] = $block['title'];
+ if (isset($region_info['table_regions'])) {
+ $region_info['table_regions'][$block_key] = array(
+ 'title' => check_plain($block['title']),
+ 'message' => t('No fields are displayed in this region'),
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function ds_extras_field_extra_fields() {
+ $extra = array();
+
+ // Register a single field so fields for vd are
+ // picked up nicely in the display overview form.
+ if (variable_get('ds_extras_vd')) {
+ ctools_include('export');
+ $vd_settings = ctools_export_crud_load_all('ds_vd');
+ foreach ($vd_settings as $vd) {
+ $extra['ds_views'][$vd->vd] = array(
+ 'display' => array(
+ 'title' => array(
+ 'label' => t('Title'),
+ 'description' => t('Title'),
+ 'weight' => 10,
+ ),
+ ),
+ );
+ }
+ }
+
+ if (variable_get('ds_extras_fields_extra')) {
+ $fields = explode("\n", variable_get('ds_extras_fields_extra_list', FALSE));
+ foreach ($fields as $field) {
+ $field = trim($field);
+ if (!empty($field)) {
+ list($entity, $bundle, $field_name) = explode('|', $field);
+ $extra[check_plain($entity)][check_plain($bundle)]['display'][$field_name] = array(
+ 'label' => drupal_ucfirst(str_replace('_', ' ', check_plain($field_name))),
+ 'description' => drupal_ucfirst(str_replace('_', ' ', check_plain($field_name))),
+ 'weight' => 0,
+ );
+ }
+ }
+ }
+
+ return $extra;
+}
+
+/**
+ * Output flag.
+ */
+function ds_extras_flags_add_flag_link($field) {
+ return flag_create_link($field['properties']['flag'], $field['entity']->nid);
+}
+
+/**
+ * Implements hook_preprocess_views_view().
+ */
+function ds_extras_preprocess_view_layout(&$variables) {
+
+ if ($layout = ds_get_layout('ds_views', $variables['view']->name . '-' . $variables['view']->current_display, 'default')) {
+
+ $variables['elements']['#entity_type'] = $variables['#entity_type'] = 'ds_views';
+ $variables['elements']['#bundle'] = $variables['#bundle'] = $variables['view']->name . '-' . $variables['view']->current_display;
+ $variables['elements']['#view_mode'] = 'default';
+
+ $variables['ds_views'] = $variables['view'];
+ $variables['render_code_fields'] = TRUE;
+ ds_field_attach_view_alter($variables, array('view_mode' => 'default', 'entity' => $variables['view']));
+
+ // Special case for views function fields.
+ if (isset($variables['view']->ds_vd)) {
+ foreach ($variables['view']->ds_vd as $key => $value) {
+ $variables[$key] = $value;
+ }
+ }
+
+ // Don't remove the variables so we don't trigger notices.
+ $variables['preprocess_keep'] = TRUE;
+ ds_entity_variables($variables);
+ if (isset($variables['#attached'])) {
+ drupal_process_attached($variables);
+ }
+ }
+}
+
+/**
+ * Function used for theming the views title, user name etc. Note that
+ * this is a function so it can't be overridden by a phptemplate function.
+ */
+function ds_vd_render_title_field($field) {
+ $output = '';
+ $formatter = explode('_', $field['formatter']);
+ $tag = $formatter[2];
+ $output = '<' . $tag . '>' . $field['entity']->get_title() . '</' . $tag . '>';
+
+ // Views is a special case, we stack title on the entity.
+ $field['entity']->preprocess_fields[] = 'title';
+ $field['entity']->ds_vd['title'] = $output;
+}
+
+/**
+ * Implements hook_entity_view_alter().
+ */
+function ds_extras_entity_view_alter(&$build, $entity_type) {
+ static $loaded = array();
+
+ // If views and core doesn't send information along on the entity,
+ // Display Suite doesn't have a context to render the layout.
+ if (!isset($build['#entity_type']) || !isset($build['#bundle'])) {
+ return;
+ }
+
+ $bundle = $build['#bundle'];
+ $view_mode = $build['#view_mode'];
+ if ($overridden_view_mode = ds_extras_get_view_mode()) {
+ $view_mode = $overridden_view_mode;
+ }
+ $layout = ds_get_layout($entity_type, $bundle, $view_mode);
+
+ // Page title options.
+ if (variable_get('ds_extras_hide_page_title', FALSE)) {
+ $page_title = &drupal_static('ds_page_title');
+ if (isset($layout['settings']['hide_page_title']) && $layout['settings']['hide_page_title'] == 1 && ds_extras_is_entity_page_view($build, $entity_type)) {
+ $page_title['title'] = '';
+ }
+ elseif (isset($layout['settings']['hide_page_title']) && $layout['settings']['hide_page_title'] == 2 && !empty($layout['settings']['page_option_title']) && ds_extras_is_entity_page_view($build, $entity_type)) {
+ $contexts = array();
+ $id = (arg(0) == 'taxonomy') ? arg(2) : arg(1);
+ $entity = entity_load($entity_type, array($id));
+ if (isset($entity[$id])) {
+ ds_create_entity_context($entity_type, $entity[$id], $contexts);
+ $title = $layout['settings']['page_option_title'];
+ $title = filter_xss_admin(ctools_context_keyword_substitute($title, array(), $contexts));
+ $page_title['title'] = t($title);
+ $page_title['head_title'] = t($title);
+ }
+ }
+ }
+
+ // Disable blocks.
+ if (isset($layout['settings']['hide_sidebars']) && !isset($loaded[$entity_type][$bundle][$view_mode])) {
+
+ // Store the setting.
+ $loaded[$entity_type][$bundle][$view_mode] = TRUE;
+
+ // Disable blocks.
+ if (isset($layout['settings']['hide_sidebars']) && $layout['settings']['hide_sidebars']) {
+ ctools_set_no_blocks();
+ }
+ }
+}
+
+/**
+ * Helper function to check if this is a page view.
+ *
+ * The page title option adds the ability to hide or override the page title.
+ * However, it can't happen that an entity bleeds its page title to say a view
+ * or the frontpage.
+ *
+ * See http://drupal.org/node/1526732 and http://drupal.org/node/1446554.
+ */
+function ds_extras_is_entity_page_view($build, $entity_type) {
+ switch ($entity_type) {
+ case 'node':
+ return node_is_page($build['#node']);
+ break;
+ case 'user':
+ $page_account = menu_get_object('user');
+ return (!empty($page_account) ? $page_account->uid == $build['#account']->uid : FALSE);
+ break;
+ case 'taxonomy_term':
+ $page_term = menu_get_object('taxonomy_term', 2);
+ return (!empty($page_term) ? $page_term->tid == $build['#term']->tid : FALSE);
+ break;
+ case 'profile2':
+ return $build['#view_mode'] == 'page';
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Implements hook_ds_field_theme_functions_info().
+ */
+function ds_extras_ds_field_theme_functions_info() {
+ return array(
+ 'theme_field' => t('Drupal default'),
+ 'theme_ds_field_reset' => t('Full Reset'),
+ 'theme_ds_field_minimal' => t('Minimal'),
+ 'theme_ds_field_expert' => t('Expert'),
+ );
+}
+
+/**
+ * Reset all HTML for the field.
+ */
+function theme_ds_field_reset($variables) {
+ $output = '';
+
+ // Render the label.
+ if (!$variables['label_hidden']) {
+ $output .= '<div class="label-' . $variables['element']['#label_display'] . '">' . $variables['label'];
+ if (!variable_get('ft-kill-colon', FALSE)) {
+ $output .= ':&nbsp;';
+ }
+ $output .= '</div>';
+ }
+
+ // Render the items.
+ foreach ($variables['items'] as $delta => $item) {
+ $output .= drupal_render($item);
+ }
+
+ return $output;
+}
+
+/**
+ * Provide minimal HTML for the field.
+ */
+function theme_ds_field_minimal($variables) {
+ $output = '';
+ $config = $variables['ds-config'];
+ $classes = isset($config['classes']) ? ' ' . $config['classes'] : '';
+
+ // Add a simple wrapper.
+ $output .= '<div class="field field-name-' . strtr($variables['element']['#field_name'], '_', '-') . $classes . '">';
+
+ // Render the label.
+ if (!$variables['label_hidden']) {
+ $output .= '<div class="label-' . $variables['element']['#label_display'] . '">' . $variables['label'];
+ if (!isset($config['lb-col'])) {
+ $output .= ':&nbsp;';
+ }
+ $output .= '</div>';
+ }
+
+ // Render the items.
+ foreach ($variables['items'] as $delta => $item) {
+ $output .= drupal_render($item);
+ }
+ $output .="</div>";
+
+ return $output;
+}
+
+/**
+ * Custom output all HTML for the field.
+ */
+function theme_ds_field_expert($variables) {
+ $output = '';
+
+ $config = $variables['ds-config'];
+
+ // Set prefix
+ if (!empty($config['prefix'])) {
+ $output .= $config['prefix'];
+ }
+
+ // Render the label if it's not hidden.
+ if (!$variables['label_hidden']) {
+ $styled_label = $variables['label'];
+ if (!isset($config['lb-col'])) $styled_label .= ':&nbsp;';
+
+ // Style the label it self
+ $label_wrapper = isset($config['lb-el']) ? $config['lb-el'] : 'div';
+ $class = array('label-' . $variables['element']['#label_display']);
+ if (!empty($config['lb-cl'])) $class[] = $config['lb-cl'];
+ $class = !empty($class) ? ' class="' . implode(' ', $class) . '"' : '';
+ $attributes = array();
+ if (!empty($config['lb-at'])) $attributes[] = $config['lb-at'];
+ if (!empty($config['lb-def-at'])) $attributes[] = $variables['title_attributes'];
+ $attributes = (!empty($attributes)) ? ' ' . implode(' ', $attributes) : '';
+ $styled_label = '<' . $label_wrapper . $class . $attributes . '>' . $styled_label . '</' . $label_wrapper . '>';
+
+ // Place it inside a wrapper
+ if (isset($config['lbw'])) {
+ $class = !empty($config['lbw-cl']) ? ' class="' . $config['lbw-cl'] . '"' : '';
+ $attributes = !empty($config['lbw-at']) ? ' ' . $config['lbw-at'] : '';
+ $styled_label = '<' . $config['lbw-el'] . $class . $attributes . '>' . $styled_label . '</' . $config['lbw-el'] . '>';
+ }
+
+ $output .= $styled_label;
+ }
+
+ // Field items wrapper
+ if (isset($config['fis'])) {
+ $class = (!empty($config['fis-cl'])) ? ' class="' . token_replace($config['fis-cl'], array($variables['element']['#entity_type'] => $variables['element']['#object']), array('clear' => TRUE)) . '"' : '';
+ $attributes = array();
+ if (!empty($config['fis-at'])) $attributes[] = token_replace($config['fis-at'], array($variables['element']['#entity_type'] => $variables['element']['#object']), array('clear' => TRUE));;
+ if (!empty($config['fis-def-at'])) $attributes[] = $variables['content_attributes'];
+ $attributes = (!empty($attributes)) ? ' ' . implode(' ', $attributes) : '';
+ $output .= '<' . $config['fis-el'] . $class . $attributes . '>';
+ }
+
+ // Render items.
+ $item_count = count($variables['items']);
+ $item_index = 0;
+ foreach ($variables['items'] as $delta => $item) {
+ // Field item wrapper.
+ if (isset($config['fi'])) {
+ $class = array();
+ if (!empty($config['fi-odd-even'])) {
+ $class[] = $delta % 2 ? 'even' : 'odd';
+ }
+ if (!empty($config['fi-cl'])) {
+ $class[] = $config['fi-cl'];
+ }
+ if(!empty($config['fi-first-last']) && $item_index == 0) {
+ $class[] = "first";
+ }
+ if(!empty($config['fi-first-last']) && $item_index == $item_count - 1) {
+ $class[] = "last";
+ }
+ $class = !empty($class) ? ' class="'. token_replace(implode(' ', $class), array($variables['element']['#entity_type'] => $variables['element']['#object']), array('clear' => TRUE)) .'"' : '';
+ $attributes = array();
+ if (!empty($config['fi-at'])) {
+ $attributes[] = token_replace($config['fi-at'], array($variables['element']['#entity_type'] => $variables['element']['#object']), array('clear' => TRUE));
+ }
+ if (!empty($config['fi-def-at'])) {
+ $attributes[] = $variables['item_attributes'][$delta];
+ }
+ $attributes = (!empty($attributes)) ? ' ' . implode(' ', $attributes) : '';
+ $output .= '<' . $config['fi-el'] . $class . $attributes .'>';
+ }
+
+ // Render field content.
+ $output .= drupal_render($item);
+
+ // Closing field item wrapper.
+ if (isset($config['fi'])) {
+ $output .= '</' . $config['fi-el'] . '>';
+ }
+ ++$item_index;
+ }
+
+ // Closing field items wrapper.
+ if (isset($config['fis'])) {
+ $output .= '</' . $config['fis-el'] . '>';
+ }
+
+ // Outer wrapper.
+ if (isset($config['ow'])) {
+ $class = array();
+ if (!empty($config['ow-cl'])) $class[] = token_replace($config['ow-cl'], array($variables['element']['#entity_type'] => $variables['element']['#object']), array('clear' => TRUE));
+ if (!empty($config['ow-def-cl'])) $class[] = $variables['classes'];
+ $class = (!empty($class)) ? ' class="' . implode(' ', $class) . '"' : '';
+ $attributes = array();
+ if (!empty($config['ow-at'])) $attributes[] = token_replace($config['ow-at'], array($variables['element']['#entity_type'] => $variables['element']['#object']), array('clear' => TRUE));;
+ if (!empty($config['ow-def-at'])) $attributes[] = $variables['attributes'];
+ $attributes = (!empty($attributes)) ? ' ' . implode(' ', $attributes) : '';
+ $output = '<' . $config['ow-el'] . $class . $attributes . '>' . $output . '</' . $config['ow-el'] . '>';
+ }
+
+ // Set suffix
+ if (!empty($config['suffix'])) {
+ $output .= $config['suffix'];
+ }
+
+ return $output;
+}
+
+/**
+ * Implements hook_ds_field_settings_alter().
+ */
+function ds_extras_ds_field_settings_alter(&$field_settings, $form, &$form_state) {
+
+ $fields = $form_state['values']['fields'];
+ $default_field_function = variable_get('ft-default', 'theme_field');
+
+ $wrappers = array(
+ 'ow' => t('Wrapper'),
+ 'fis' => t('Field items'),
+ 'fi' => t('Field item')
+ );
+
+ foreach ($fields as $key => $field) {
+
+ // Make sure we need to save anything for this field.
+ if (_ds_field_valid($key, $field, $form_state)) {
+ continue;
+ }
+
+ // Get the values.
+ $values = isset($form_state['formatter_settings'][$key]['ft']) ? $form_state['formatter_settings'][$key]['ft'] : array();
+ if (empty($values)) {
+ continue;
+ }
+ $field_settings[$key]['formatter_settings']['ft'] = array();
+
+ // Theme output function.
+ $function = isset($values['func']) ? $values['func'] : $default_field_function;
+ if ($function != $default_field_function) {
+ $field_settings[$key]['formatter_settings']['ft']['func'] = $function;
+ }
+
+ // Field classes.
+ if ($function != 'theme_ds_field_expert' && $function != 'theme_ds_field_reset' && isset($values['classes'])) {
+ $classes = is_array($values['classes']) ? implode(' ', $values['classes']) : $values['classes'];
+ if (!empty($classes)) {
+ $field_settings[$key]['formatter_settings']['ft']['classes'] = $classes;
+ }
+ }
+
+ // Label.
+ if (isset($form_state['values']['fields'][$key]['label'])) {
+ if (!empty($values['lb'])) {
+ $field_settings[$key]['formatter_settings']['ft']['lb'] = $values['lb'];
+ }
+ if (!(empty($values['lb-el'])) && $function == 'theme_ds_field_expert') {
+ $field_settings[$key]['formatter_settings']['ft']['lb-el'] = check_plain($values['lb-el']);
+ }
+ if (!(empty($values['lb-cl'])) && $function == 'theme_ds_field_expert') {
+ $field_settings[$key]['formatter_settings']['ft']['lb-cl'] = check_plain($values['lb-cl']);
+ }
+ if (!(empty($values['lb-at'])) && $function == 'theme_ds_field_expert') {
+ $field_settings[$key]['formatter_settings']['ft']['lb-at'] = filter_xss($values['lb-at']);
+ }
+ if (!(empty($values['lb-def-at'])) && $function == 'theme_ds_field_expert') {
+ $field_settings[$key]['formatter_settings']['ft']['lb-def-at'] = TRUE;
+ }
+ if (!(empty($values['lb-col']))) {
+ $field_settings[$key]['formatter_settings']['ft']['lb-col'] = TRUE;
+ }
+
+ // label wrapper
+ if (!empty($values['lbw'])) {
+ $field_settings[$key]['formatter_settings']['ft']['lbw'] = $values['lbw'];
+ }
+ if (!(empty($values['lbw-el'])) && $function == 'theme_ds_field_expert') {
+ $field_settings[$key]['formatter_settings']['ft']['lbw-el'] = check_plain($values['lbw-el']);
+ }
+ if (!(empty($values['lbw-cl'])) && $function == 'theme_ds_field_expert') {
+ $field_settings[$key]['formatter_settings']['ft']['lbw-cl'] = check_plain($values['lbw-cl']);
+ }
+ if (!(empty($values['lbw-at'])) && $function == 'theme_ds_field_expert') {
+ $field_settings[$key]['formatter_settings']['ft']['lbw-at'] = filter_xss($values['lbw-at']);
+ }
+ }
+
+ if (!(empty($values['prefix']))) {
+ $field_settings[$key]['formatter_settings']['ft']['prefix'] = $values['prefix'];
+ }
+ if (!(empty($values['suffix']))) {
+ $field_settings[$key]['formatter_settings']['ft']['suffix'] = $values['suffix'];
+ }
+
+ // Custom field configuration.
+ if ($function == 'theme_ds_field_expert') {
+ foreach ($wrappers as $wrapper_key => $title) {
+ if (!empty($values[$wrapper_key])) {
+ // Enable.
+ $field_settings[$key]['formatter_settings']['ft'][$wrapper_key] = TRUE;
+ // Element.
+ $field_settings[$key]['formatter_settings']['ft'][$wrapper_key . '-el'] = !(empty($values[$wrapper_key . '-el'])) ? check_plain($values[$wrapper_key . '-el']) : 'div';
+ // Classes.
+ $field_settings[$key]['formatter_settings']['ft'][$wrapper_key . '-cl'] = !(empty($values[$wrapper_key . '-cl'])) ? check_plain($values[$wrapper_key . '-cl']) : '';
+ // Default Classes.
+ if (in_array($wrapper_key, array('ow', 'lb'))) {
+ $field_settings[$key]['formatter_settings']['ft'][$wrapper_key . '-def-cl'] = !(empty($values[$wrapper_key . '-def-cl'])) ? TRUE : FALSE;
+ }
+ // Attributes.
+ $field_settings[$key]['formatter_settings']['ft'][$wrapper_key . '-at'] = !(empty($values[$wrapper_key . '-at'])) ? filter_xss($values[$wrapper_key . '-at']) : '';
+ // Default attributes.
+ $field_settings[$key]['formatter_settings']['ft'][$wrapper_key . '-def-at'] = !(empty($values[$wrapper_key . '-def-at'])) ? TRUE : FALSE;
+ // Odd even class.
+ if ($wrapper_key == 'fi') {
+ $field_settings[$key]['formatter_settings']['ft'][$wrapper_key . '-odd-even'] = !(empty($values[$wrapper_key . '-odd-even'])) ? TRUE : FALSE;
+ $field_settings[$key]['formatter_settings']['ft'][$wrapper_key . '-first-last'] = !(empty($values[$wrapper_key . '-first-last'])) ? TRUE : FALSE;
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_preprocess_field().
+ */
+function ds_extras_preprocess_field(&$variables) {
+
+ // We need to be sure this field is in a layout which is rendered by DS.
+ if (!ds_get_layout($variables['element']['#entity_type'], $variables['element']['#bundle'], $variables['element']['#view_mode'])) {
+ return;
+ }
+
+ $entity_type = $variables['element']['#entity_type'];
+ $bundle = $variables['element']['#bundle'];
+ $view_mode = $variables['element']['#view_mode'];
+
+ $config = array();
+ static $field_settings = array();
+ if (!isset($field_settings[$entity_type][$bundle][$view_mode])) {
+ $layout = ds_get_layout($entity_type, $bundle, $view_mode);
+ $field_settings[$entity_type][$bundle][$view_mode] = ds_get_field_settings($entity_type, $bundle, $view_mode);
+ }
+
+ // Get the field name and field instance info - if available.
+ $field_name = $variables['element']['#field_name'];
+ $field_instance_info = field_info_instance($entity_type, $field_name, $bundle);
+
+ // Check if this field has custom output settings.
+ $variables['ds-config'] = array();
+ if (isset($field_settings[$entity_type][$bundle][$view_mode][$field_name]['formatter_settings']['ft'])) {
+ $config = $field_settings[$entity_type][$bundle][$view_mode][$field_name]['formatter_settings']['ft'];
+ $variables['ds-config'] = $config;
+ }
+
+ // CSS classes
+ if (isset($config['classes'])) {
+ $variables['classes_array'][] = $config['classes'];
+ }
+
+ // Alter the label if configured.
+ if (!$variables['label_hidden']) {
+ if (isset($config['lb'])) {
+ $variables['label'] = t(check_plain($config['lb']));
+ }
+ }
+
+ // Determine the field template. In case it's something different
+ // than theme_field, we'll add that function as a suggestion.
+ if (isset($config['func']) && $config['func'] != 'theme_field') {
+ $variables['ds-config'] = $config;
+ $variables['theme_hook_suggestions'] = array();
+ // Either it uses the function.
+ $variables['theme_hook_suggestions'][] = str_replace('theme_', '', $config['func']);
+ $variables['theme_hook_suggestions'][] = $config['func'];
+ // Or the template file(s).
+ $suggestion = 'field__' . str_replace('theme_ds_field_', '', $config['func']);
+ $variables['theme_hook_suggestions'][] = $suggestion;
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $field_name;
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $variables['element']['#bundle'];
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $field_name . '__' . $variables['element']['#bundle'];
+ }
+ // Check if we have a default field template on instance level.
+ elseif (isset($field_instance_info['ds_extras_field_template']) && !empty($field_instance_info['ds_extras_field_template']) && $field_instance_info['ds_extras_field_template'] != 'theme_field') {
+ $variables['theme_hook_suggestions'] = array();
+ // Either it uses the function.
+ $variables['theme_hook_suggestions'][] = str_replace('theme_', '', $field_instance_info['ds_extras_field_template']);
+ $variables['theme_hook_suggestions'][] = $field_instance_info['ds_extras_field_template'];
+ // Or the template file(s).
+ $suggestion = 'field__' . str_replace('theme_ds_field_', '', $field_instance_info['ds_extras_field_template']);
+ $variables['theme_hook_suggestions'][] = $suggestion;
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $field_name;
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $variables['element']['#bundle'];
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $field_name . '__' . $variables['element']['#bundle'];
+ }
+ // Use Display Suite Extras Default theming function.
+ elseif (!isset($config['func']) || empty($config['func'])) {
+ $field_theme_function = variable_get('ft-default', 'theme_field');
+ if ($field_theme_function != 'theme_field') {
+ $variables['theme_hook_suggestions'] = array();
+ $variables['ds-config'] = $config;
+ // Either it uses the function.
+ $variables['theme_hook_suggestions'][] = str_replace('theme_', '', $field_theme_function);
+ $variables['theme_hook_suggestions'][] = $field_theme_function;
+ // Or the template file(s).
+ $suggestion = 'field__' . str_replace('theme_ds_field_', '', $field_theme_function);
+ $variables['theme_hook_suggestions'][] = $suggestion;
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $field_name;
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $variables['element']['#bundle'];
+ $variables['theme_hook_suggestions'][] = $suggestion . '__' . $field_name . '__' . $variables['element']['#bundle'];
+ }
+ }
+
+ // Sanitize the output of field templates
+ $fields = array(
+ 'prefix',
+ 'suffix',
+ );
+
+ foreach ($fields as $field) {
+ if (isset($variables['ds-config'][$field])) {
+ $variables['ds-config'][$field] = filter_xss_admin($variables['ds-config'][$field]);
+ }
+ }
+}
diff --git a/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.admin.inc b/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.admin.inc
new file mode 100644
index 000000000..8102443fa
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.admin.inc
@@ -0,0 +1,670 @@
+<?php
+
+/**
+ * @file
+ * Display Suite Extras administrative functions.
+ */
+
+/**
+ * Menu callback: Display Suite extras settings.
+ */
+function ds_extras_settings($form) {
+
+ $form['additional_settings'] = array(
+ '#type' => 'vertical_tabs',
+ '#theme_wrappers' => array('vertical_tabs'),
+ '#prefix' => '<div>',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+
+ $form['additional_settings']['fs1'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Field Templates'),
+ );
+
+ $form['additional_settings']['fs1']['ds_extras_field_template'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable Field Templates'),
+ '#description' => t('Customize the labels and the HTML output of your fields.'),
+ '#default_value' => variable_get('ds_extras_field_template', FALSE),
+ );
+
+ $theme_functions = module_invoke_all('ds_field_theme_functions_info');
+ $form['additional_settings']['fs1']['ft-default'] = array(
+ '#type' => 'select',
+ '#title' => t('Default Field Template'),
+ '#options' => $theme_functions,
+ '#default_value' => variable_get('ft-default', 'theme_field'),
+ '#description' => t('Default will output the field as defined in Drupal Core.<br />Reset will strip all HTML.<br />Minimal adds a simple wrapper around the field.<br/>There is also an Expert Field Template that gives full control over the HTML, but can only be set per field.<br /><br />You can override this setting per field on the "Manage display" screens or when creating fields on the instance level.<br /><br /><strong>Template suggestions</strong><br />You can create .tpl files as well for these field theme functions, e.g. field--reset.tpl.php, field--minimal.tpl.php<br /><br /><label>CSS classes</label>You can add custom CSS classes on the <a href="!url">classes form</a>. Display Suite UI needs to be enabled for this. These classes can be added to fields using the Default Field Template.<br /><br /><label>Advanced</label>You can create your own custom field templates which need to be defined with hook_ds_field_theme_functions_info(). See ds.api.php for an example.', array('!url' => url('admin/structure/ds/classes'))),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="additional_settings[fs1][ds_extras_field_template]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $form['additional_settings']['fs1']['ft-kill-colon'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide colon'),
+ '#default_value' => variable_get('ft-kill-colon', FALSE),
+ '#description' => t('Hide the colon on the reset field template.'),
+ '#states' => array(
+ 'visible' => array(
+ 'select[name="additional_settings[fs1][ft-default]"]' => array('value' => 'theme_ds_field_reset'),
+ 'input[name="additional_settings[fs1][ds_extras_field_template]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+
+ $form['additional_settings']['fs2'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Extra fields'),
+ );
+
+ $form['additional_settings']['fs2']['ds_extras_fields_extra'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable extra fields'),
+ '#description' => t('Make fields from other modules available on the "Manage display" screens.'),
+ '#default_value' => variable_get('ds_extras_fields_extra', FALSE),
+ );
+
+ $form['additional_settings']['fs2']['ds_extras_fields_extra_list'] = array(
+ '#type' => 'textarea',
+ '#description' => t('Enter fields line by line, where each line is a combination of entity type, bundle and field name. E.g. node|article|tweetbutton. It might be possible that the actual content of the field depends on configuration of that field/module.'),
+ '#default_value' => variable_get('ds_extras_fields_extra_list', FALSE),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="additional_settings[fs2][ds_extras_fields_extra]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $form['additional_settings']['fs4'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Other'),
+ );
+
+ $form['additional_settings']['fs4']['ds_extras_switch_view_mode'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('View mode per node'),
+ '#description' => t('Change view modes for individual nodes. A new tab \'Display settings\' will appear on the content create form.<br />You can also pass the name of a view mode through the URL, eg node/x?v=full.<br />If you install the Page manager module and override the node view, Page manager will win.'),
+ '#default_value' => variable_get('ds_extras_switch_view_mode', FALSE),
+ );
+
+ $form['additional_settings']['fs4']['ds_extras_hide_page_title'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Page title options'),
+ '#description' => t('Hide or manually set the page title of the "Full content" view mode.'),
+ '#default_value' => variable_get('ds_extras_hide_page_title', FALSE),
+ );
+
+ $form['additional_settings']['fs4']['ds_extras_hide_page_sidebars'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Disable Drupal blocks/regions'),
+ '#description' => t('Add ability to disable all sidebar regions displayed in the theme. Note that some themes support this setting better than others. If in doubt, try with stock themes to see.'),
+ '#default_value' => variable_get('ds_extras_hide_page_sidebars', FALSE),
+ );
+
+ $form['additional_settings']['fs4']['ds_extras_field_permissions'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Field permissions'),
+ '#description' => t('Enables view permissions on all Display Suite fields.'),
+ '#default_value' => variable_get('ds_extras_field_permissions', FALSE),
+ );
+
+ $form['additional_settings']['fs4']['ds_extras_region_to_block'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Region to block'),
+ '#description' => t('Create additional regions exposed as block. Note: this will not work on the default view mode.'),
+ '#default_value' => variable_get('ds_extras_region_to_block', FALSE),
+ );
+
+ if (module_exists('views')) {
+ $form['additional_settings']['fs4']['ds_extras_vd'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Views displays'),
+ '#description' => t('Manage the layout of your Views layout with Field UI at !url.', array('!url' => l(url('admin/structure/ds/vd', array('absolute' => TRUE)), 'admin/structure/ds/vd'))),
+ '#default_value' => variable_get('ds_extras_vd', FALSE),
+ );
+ }
+
+ if (module_exists('flag')) {
+ $form['additional_settings']['fs4']['ds_extras_flag'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Flag'),
+ '#description' => t('Expose flags as fields on nodes.'),
+ '#default_value' => variable_get('ds_extras_flag', FALSE),
+ );
+ }
+
+ $form['additional_settings']['fs4']['ds_extras_switch_field'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('View mode switcher'),
+ '#description' => t('Adds a field with links to switch view modes inline with Ajax. Only works for nodes at this time. It does not work in combination with the reset layout.'),
+ '#default_value' => variable_get('ds_extras_switch_field', FALSE),
+ );
+
+ $form['additional_settings']['fs4']['ds_extras_hidden_region'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hidden region'),
+ '#description' => t('Add a hidden region to the layouts. Fields will be built but not printed.'),
+ '#default_value' => variable_get('ds_extras_hidden_region', FALSE),
+ );
+
+ $form['#attached']['js'][] = drupal_get_path('module', 'ds_extras') . '/js/ds_extras.admin.js';
+
+ $form = system_settings_form($form);
+ $form['#submit'][] = 'ds_extras_settings_submit';
+ return $form;
+}
+
+/**
+ * Validate callback: Extras settings screen.
+ */
+function ds_extras_settings_validate($form, &$form_state) {
+ foreach ($form_state['values']['additional_settings'] as $tab => $value) {
+ if (is_array($value)) {
+ foreach ($value as $variable => $val) {
+ $form_state['values'][$variable] = $val;
+ }
+ }
+ unset($form_state['values']['additional_settings'][$tab]);
+ }
+ unset($form_state['values']['additional_settings']);
+}
+
+/**
+ * Submit callback: Extras settings screen.
+ */
+function ds_extras_settings_submit($form, &$form_state) {
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+ cache_clear_all('entity_info:', 'cache', TRUE);
+ cache_clear_all('theme_registry:', 'cache', TRUE);
+ cache_clear_all('module_implements', 'cache_bootstrap');
+ cache_clear_all('field_info_fields', 'cache_field');
+ variable_set('menu_rebuild_needed', TRUE);
+}
+
+/**
+ * Alter Manage display screen.
+ */
+function ds_extras_field_ui_alter(&$form, &$form_state) {
+ // Attach js.
+ $form['#attached']['js'][] = drupal_get_path('module', 'ds_extras') . '/js/ds_extras.admin.js';
+
+ // Views displays.
+ if ($form['#entity_type'] == 'ds_views') {
+ // Add an additional submit callback so we can ditch the extra title
+ // which is added by ds_extras_field_extra_fields().
+ $form['#submit'] = array_merge(array('ds_extras_vd_field_ui_submit'), $form['#submit']);
+ }
+
+ // Page title functionality, currently only works on nodes, users and taxonomy terms.
+ if (variable_get('ds_extras_hide_page_title', FALSE) && isset($form['#ds_layout']) && (in_array($form['#entity_type'], array('node', 'user', 'taxonomy_term', 'profile2')) && ($form['#view_mode'] == 'full' || $form['#view_mode'] == 'revision' || (variable_get('ds_extras_switch_view_mode', FALSE) && $form['#entity_type'] == 'node')) || $form['#entity_type'] == 'ds_views' || $form['#entity_type'] == 'profile2')) {
+ $form['additional_settings']['ds_page_title'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Custom page title'),
+ );
+ $form['additional_settings']['ds_page_title']['ds_page_title_options'] = _ds_extras_page_title_options($form['#ds_layout']->settings, $form['#entity_type']);
+ }
+
+ // Disable page regions.
+ if (variable_get('ds_extras_hide_page_sidebars', FALSE) && isset($form['#ds_layout']) && $form['#view_mode'] != 'form') {
+ $form['additional_settings']['ds_layouts']['hide_sidebars'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Disable Drupal blocks/regions'),
+ '#default_value' => isset($form['#ds_layout']->settings['hide_sidebars']) ? $form['#ds_layout']->settings['hide_sidebars'] : FALSE,
+ '#weight' => 3,
+ );
+ }
+
+ // Region to block only fires if there is a layout and we're working on the
+ // a view mode which is not equal to default.
+ if (isset($form['#ds_layout']) && $form['#view_mode'] != 'default' && variable_get('ds_extras_region_to_block', FALSE)) {
+
+ $layout = $form['#ds_layout'];
+
+ // Get the entity_type, bundle and view mode.
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ $region_blocks_options = array();
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+ foreach ($region_blocks as $key => $block) {
+ if ($block['info'] == "{$entity_type}_{$bundle}_{$view_mode}") {
+ $region_blocks_options[$key] = t('Remove') . ' ' . $block['title'];
+ }
+ }
+
+ $form['additional_settings']['region_to_block'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Block regions'),
+ '#description' => t('Create additional regions in this layout which will be exposed as blocks. Note that preprocess fields will fail to print.')
+ );
+
+ $form['additional_settings']['region_to_block']['new_block_region'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Region name'),
+ '#description' => t('Enter a name to create a new region.'),
+ );
+ $form['additional_settings']['region_to_block']['new_block_region_key'] = array(
+ '#title' => t('Machine name'),
+ '#type' => 'machine_name',
+ '#default_value' => '',
+ '#maxlength' => 32,
+ '#required' => FALSE,
+ '#description' => t('The machine-readable name of this block region. This name must contain only lowercase letters and underscores. This name must be unique.'),
+ '#disabled' => FALSE,
+ '#machine_name' => array(
+ 'exists' => 'ds_extras_region_to_block_unique',
+ 'source' => array('additional_settings', 'region_to_block', 'new_block_region'),
+ ),
+ );
+
+ if (!empty($region_blocks_options)) {
+ $form['additional_settings']['region_to_block']['remove_block_region'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Existing block regions'),
+ '#options' => $region_blocks_options,
+ '#description' => t('Check the regions you want to remove.'),
+ );
+ }
+
+ $form['#submit'][] = 'ds_extras_block_submit';
+ }
+}
+
+/**
+ * Field template settings form
+ */
+function ds_extras_field_template_settings_form(array &$form, array &$form_state, array $context) {
+ $functions = module_invoke_all('ds_field_theme_functions_info');
+ $default_field_function = variable_get('ft-default', 'theme_field');
+ $key = $context['instance']['field_name'];
+
+ $field_settings = isset($form_state['formatter_settings'][$key]) ? $form_state['formatter_settings'][$key]['ft'] : array();
+
+ $field_function = isset($field_settings['func']) ? $field_settings['func'] : $default_field_function;
+ $field_classes = _ds_classes('ds_classes_fields');
+
+ $form['ft'] = array(
+ '#weight' => 20,
+ );
+
+ // Functions.
+ $form['ft']['func'] = array(
+ '#title' => t('Choose a Field Template'),
+ '#type' => 'select',
+ '#options' => $functions,
+ '#default_value' => $field_function,
+ '#attributes' => array(
+ 'class' => array('ds-extras-field-template'),
+ ),
+ );
+
+ // Field classes.
+ if (!empty($field_classes)) {
+ $field_classes_select = array(
+ '#type' => 'select',
+ '#multiple' => TRUE,
+ '#options' => $field_classes,
+ '#title' => t('Choose additional CSS classes for the field'),
+ '#default_value' => isset($field_settings['classes']) ? explode(' ', $field_settings['classes']) : array(),
+ '#prefix' => '<div class="field-classes">',
+ '#suffix' => '</div>',
+ );
+ $form['ft']['classes'] = $field_classes_select;
+ }
+ else {
+ $form['ft']['classes'] = array(
+ '#type' => 'value',
+ '#value' => array(''),
+ );
+ }
+
+ // Add prefix
+ $form['ft']['prefix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Prefix'),
+ '#size' => '100',
+ '#description' => t('You can enter any html in here.'),
+ '#default_value' => isset($field_settings['prefix']) ? $field_settings['prefix'] : '',
+ '#prefix' => '<div class="field-prefix">',
+ '#suffix' => '</div>',
+ );
+
+ // Wrappers and label.
+ $wrappers = array(
+ 'lb' => array('title' => t('Label')),
+ 'lbw' => array('title' => t('Label wrapper')),
+ 'ow' => array('title' => t('Outer wrapper')),
+ 'fis' => array('title' => t('Field items')),
+ 'fi' => array('title' => t('Field item')),
+ );
+
+ foreach ($wrappers as $wrapper_key => $value) {
+
+ $classes = array(
+ 'field-name-' . strtr($key, '_', '-'),
+ );
+ $form['ft'][$wrapper_key] = array(
+ '#type' => 'checkbox',
+ '#title' => $value['title'],
+ '#prefix' => '<div class="ft-group ' . $wrapper_key . '">',
+ '#default_value' => isset($field_settings[$wrapper_key]) ? $field_settings[$wrapper_key] : FALSE,
+ );
+ $form['ft'][$wrapper_key . '-el'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Element'),
+ '#size' => '10',
+ '#description' => t('E.g. div, span, h2 etc.'),
+ '#default_value' => isset($field_settings[$wrapper_key . '-el']) ? $field_settings[$wrapper_key . '-el'] : '',
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[ft][' . $wrapper_key . ']"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ $form['ft'][$wrapper_key . '-cl'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Classes'),
+ '#size' => '10',
+ '#default_value' => isset($field_settings[$wrapper_key . '-cl']) ? $field_settings[$wrapper_key . '-cl'] : '',
+ '#description' => t('E.g.') .' ' . implode(', ', $classes),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[ft][' . $wrapper_key . ']"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ $form['ft'][$wrapper_key . '-at'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Attributes'),
+ '#size' => '20',
+ '#default_value' => isset($field_settings[$wrapper_key . '-at']) ? $field_settings[$wrapper_key . '-at'] : '',
+ '#description' => t('E.g. name="anchor"'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[ft][' . $wrapper_key . ']"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ // Hide colon.
+ if ($wrapper_key == 'lb') {
+ $form['ft']['lb-col'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide label colon'),
+ '#default_value' => isset($field_settings['lb-col']) ? $field_settings['lb-col'] : FALSE,
+ '#attributes' => array(
+ 'class' => array('colon-checkbox'),
+ ),
+ );
+ }
+ if ($wrapper_key == 'fi') {
+ $form['ft']['fi-odd-even'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add odd/even classes'),
+ '#default_value' => isset($field_settings['fi-odd-even']) ? $field_settings['fi-odd-even'] : FALSE,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[ft][' . $wrapper_key . ']"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ $form['ft']['fi-first-last'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add first/last classes'),
+ '#default_value' => isset($field_settings['fi-first-last']) ? $field_settings['fi-first-last'] : FALSE,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[ft][' . $wrapper_key . ']"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+ if ($wrapper_key != 'lbw') {
+ $form['ft'][$wrapper_key . '-def-at'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add default attributes'),
+ '#default_value' => isset($field_settings[$wrapper_key . '-def-at']) ? $field_settings[$wrapper_key . '-def-at'] : FALSE,
+ '#suffix' => ($wrapper_key == 'ow') ? '' : '</div>',
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[ft][' . $wrapper_key . ']"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+ else {
+ $form['ft'][$wrapper_key . '-def-at'] = array(
+ '#markup' => '</div><div class="clearfix"></div>',
+ );
+ }
+
+ // Default classes for outer wrapper.
+ if ($wrapper_key == 'ow') {
+ $form['ft'][$wrapper_key . '-def-cl'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add default classes'),
+ '#default_value' => isset($field_settings[$wrapper_key . '-def-cl']) ? $field_settings[$wrapper_key . '-def-cl'] : FALSE,
+ '#suffix' => '</div>',
+ '#states' => array(
+ 'visible' => array(
+ ':input[name$="[ft][' . $wrapper_key . ']"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+ }
+
+ // Add suffix
+ $form['ft']['suffix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Suffix'),
+ '#size' => '100',
+ '#description' => t('You can enter any html in here.'),
+ '#default_value' => isset($field_settings['suffix']) ? $field_settings['suffix'] : '',
+ '#prefix' => '<div class="field-prefix">',
+ '#suffix' => '</div>',
+ );
+
+ // Another label needs some other stuff.
+ unset($form['ft']['lb']['#description']);
+ $form['ft']['lb']['#type'] = 'textfield';
+ $form['ft']['lb']['#size'] = '10';
+ $form['ft']['lb']['#attributes'] = array('class' => array('label-change'));
+ $form['ft']['lb']['#default_value'] = isset($field_settings['lb']) ? $field_settings['lb'] : '';
+
+ // Let other modules make modifications to the settings form as needed.
+ drupal_alter('ds_field_theme_functions_settings_form_alter', $form, $field_settings);
+}
+
+/**
+ * Implements hook_ds_field_format_summary().
+ */
+function ds_extras_ds_field_format_summary($field) {
+ if (isset($field['formatter_settings'])) {
+ foreach ($field['formatter_settings'] as $key => $value) {
+ if (!empty($value)) {
+ return t('Configured');
+ break;
+ }
+ }
+ }
+ return t('Not configured');
+}
+
+/**
+ * Implements hook_ds_field_settings_form().
+ */
+function ds_extras_ds_field_settings_form($field) {
+ $form = array();
+
+ // Switch field.
+ if (variable_get('ds_extras_switch_field') && $field['name'] == 'ds_switch_field') {
+ $entity_type = $field['entity_type'];
+ $bundle = $field['bundle'];
+ $view_mode = $field['view_mode'];
+ $settings = isset($field['formatter_settings']['vms']) ? $field['formatter_settings']['vms'] : array();
+ $view_modes = ds_entity_view_modes($entity_type);
+
+ $form['info'] = array(
+ '#markup' => t('Enter a label for the link for the view modes you want to switch to.<br />Leave empty to hide link. They will be localized.'),
+ );
+
+ foreach ($view_modes as $key => $value) {
+
+ $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
+ $visible = !empty($view_mode_settings[$key]['custom_settings']);
+
+ if ($visible) {
+ $form['vms'][$key] = array(
+ '#type' => 'textfield',
+ '#default_value' => isset($settings[$key]) ? $settings[$key] : '',
+ '#size' => 20,
+ '#title' => check_plain($value['label']),
+ );
+ }
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Submit callback after Field UI submission of a views display.
+ */
+function ds_extras_vd_field_ui_submit($form, &$form_state) {
+ // Add the 'type' key to the extra title key so we can ditch the notice.
+ $form_state['values']['fields']['title']['type'] = 'hidden';
+}
+
+/**
+ * Submit callback: manage block regions.
+ */
+function ds_extras_block_submit($form, &$form_state) {
+
+ // Create new region.
+ if (!empty($form_state['values']['additional_settings']['region_to_block']['new_block_region'])) {
+
+ // Get the entity_type, bundle and view mode.
+ $entity_type = $form['#entity_type'];
+ $bundle = $form['#bundle'];
+ $view_mode = $form['#view_mode'];
+
+ $block = array(
+ 'title' => $form_state['values']['additional_settings']['region_to_block']['new_block_region'],
+ 'info' => "{$entity_type}_{$bundle}_{$view_mode}",
+ );
+
+ $block_key = $form_state['values']['additional_settings']['region_to_block']['new_block_region_key'];
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+ $region_blocks[$block_key] = $block;
+ variable_set('ds_extras_region_blocks', $region_blocks);
+ }
+
+ // Remove a region.
+ if (isset($form_state['values']['additional_settings']['region_to_block']['remove_block_region'])) {
+ $variable_set = FALSE;
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+ $remove = $form_state['values']['additional_settings']['region_to_block']['remove_block_region'];
+ foreach ($remove as $key => $value) {
+ if ($value !== 0 && $key == $value) {
+ $variable_set = TRUE;
+ if (module_exists('block')) {
+ db_delete('block')
+ ->condition('delta', $key)
+ ->condition('module', 'ds_extras')
+ ->execute();
+ }
+ unset($region_blocks[$key]);
+ }
+ }
+
+ if ($variable_set) {
+ variable_set('ds_extras_region_blocks', $region_blocks);
+ }
+ }
+}
+
+/**
+ * Return unique region to block.
+ */
+function ds_extras_region_to_block_unique($name) {
+ $region_blocks = variable_get('ds_extras_region_blocks', array());
+ $value = strtr($name, array('-' => '_'));
+ return isset($region_blocks[$value]) ? TRUE : FALSE;
+}
+
+/**
+ * Helper function to show the page title options.
+ */
+function _ds_extras_page_title_options($settings, $entity_type) {
+
+ $return['page_option_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Page title'),
+ '#options' => array(
+ '0' => t('Show'),
+ '1' => t('Hide'),
+ '2' => t('Manually set'),
+ ),
+ '#default_value' => isset($settings['hide_page_title']) ? $settings['hide_page_title'] : FALSE,
+ '#weight' => 100,
+ );
+
+ // Display Suite Views currently only supports hiding the page title.
+ if ($entity_type == 'ds_views') {
+ unset($return['page_option_type']['#options'][2]);
+ }
+
+ $contexts = ds_get_entity_context($entity_type);
+ $rows = array();
+ foreach ($contexts as $context) {
+ foreach (ctools_context_get_converters('%' . check_plain($context->keyword) . ':', $context) as $keyword => $title) {
+ $rows[] = array(
+ check_plain($keyword),
+ t('@identifier: @title', array('@title' => $title, '@identifier' => $context->identifier)),
+ );
+ }
+ }
+
+ $return['page_option_title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Title'),
+ '#default_value' => isset($settings['page_option_title']) ? $settings['page_option_title'] : '',
+ '#description' => t('The title, you may use substitutions in this title.'),
+ '#weight' => 101,
+ '#access' => $entity_type != 'ds_views',
+ '#states' => array(
+ 'visible' => array(
+ array(':input[name="page_option_type"]' => array('value' => '2')),
+ array(':input[name="additional_settings[ds_page_title][ds_page_title_options][page_option_type]"]' => array('value' => '2')),
+ ),
+ ),
+ );
+
+ $header = array(t('Keyword'), t('Value'));
+ $return['page_option_contexts'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Substitutions'),
+ // Doesn't work because of http://drupal.org/node/1015798
+ //'#collapsible' => TRUE,
+ //'#collapsed' => TRUE,
+ '#value' => theme('table', array('header' => $header, 'rows' => $rows)),
+ '#weight' => 102,
+ '#access' => $entity_type != 'ds_views',
+ '#states' => array(
+ 'visible' => array(
+ array(':input[name="page_option_type"]' => array('value' => '2')),
+ array(':input[name="additional_settings[ds_page_title][ds_page_title_options][page_option_type]"]' => array('value' => '2')),
+ ),
+ ),
+ );
+
+ return $return;
+}
diff --git a/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.pages.inc b/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.pages.inc
new file mode 100644
index 000000000..5f728738e
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.pages.inc
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Display Suite Extras page functions.
+ */
+
+/**
+ * Menu callback: show an individual node with the Switch field.
+ */
+function ds_extras_node_page_view($node) {
+
+ // If there is a menu link to this node, the link becomes the last part
+ // of the active trail, and the link name becomes the page title.
+ // Thus, we must explicitly set the page title to be the node title.
+ drupal_set_title($node->title);
+ $uri = entity_uri('node', $node);
+ // Set the node path as the canonical URL to prevent duplicate content.
+ drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
+ // Set the non-aliased path as a default shortlink.
+ drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
+
+ // Update the history table, stating that this user viewed this node.
+ node_tag_new($node);
+
+ if (empty($node->ds_switch)) {
+ // When not set fall back to full
+ $view_mode = 'full';
+ }
+ elseif ($node->ds_switch == 'default') {
+ // When default set fall back to full
+ $view_mode = 'full';
+ }
+ else {
+ $view_mode = $node->ds_switch;
+ }
+
+ // It's also possible to use $_GET['v'] to switch view modes.
+ if (isset($_GET['v']) && !empty($_GET['v'])) {
+ $view_mode = $_GET['v'];
+ }
+ drupal_static('ds_extras_view_mode', $view_mode);
+
+ // For markup consistency with other pages, use node_view_multiple() rather than node_view().
+ return node_view_multiple(array($node->nid => $node), $view_mode);
+}
+
+/**
+ * Menu callback: switches to another view mode inline.
+ */
+function ds_switch_view_mode_inline() {
+
+ $content = '';
+ $status = TRUE;
+ $error = FALSE;
+
+ $id = $_REQUEST['id'];
+ $view_mode = $_REQUEST['view_mode'];
+ $entity_type = $_REQUEST['entity_type'];
+ $entity = entity_load($entity_type, array($id));
+
+ if (!isset($entity[$id])) {
+ $status = FALSE;
+ $error = t('Content was not found.');
+ }
+ else {
+ if (node_access('view', $entity[$id])) {
+ $element = node_view($entity[$id], $view_mode);
+ $content = drupal_render($element);
+ }
+ else {
+ $error = t('Access denied');
+ }
+ }
+
+ drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
+ print drupal_json_encode(array(
+ 'status' => $status,
+ 'content' => $content,
+ 'errorMessage' => $error,
+ ));
+ exit();
+}
diff --git a/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.registry.inc b/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.registry.inc
new file mode 100644
index 000000000..38b1ac539
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.registry.inc
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * @file
+ * Display Suite Extras registry file.
+ */
+
+/**
+ * Implements hook_menu_alter().
+ */
+function _ds_extras_menu_alter(&$items) {
+
+ // Switch view mode.
+ if (variable_get('ds_extras_switch_view_mode', FALSE)) {
+
+ // Check if page manager is overriding.
+ $skip_node_override = FALSE;
+ if (module_exists('page_manager')) {
+ if ($task = page_manager_get_task('node_view')) {
+ if (isset($task['disabled']) && !$task['disabled']) {
+ $skip_node_override = TRUE;
+ }
+ }
+ }
+
+ if (!$skip_node_override) {
+ $items['node/%node']['page callback'] = 'ds_extras_node_page_view';
+ $items['node/%node']['file'] = 'includes/ds_extras.pages.inc';
+ $items['node/%node']['file path'] = drupal_get_path('module', 'ds_extras');
+ }
+ }
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function _ds_extras_entity_info() {
+
+ if (!variable_get('ds_extras_vd', FALSE)) {
+ return;
+ }
+
+ $bundles = array();
+ ctools_include('export');
+ $vd_settings = ctools_export_crud_load_all('ds_vd');
+ foreach ($vd_settings as $key => $vd) {
+ $bundles[$vd->vd] = array(
+ 'label' => check_plain($vd->label),
+ 'admin' => array('path' => 'admin/structure/ds/vd/manage/' . $vd->vd),
+ );
+ }
+
+ // Register a views entity on behalf of Views.
+ $return = array(
+ 'ds_views' => array(
+ 'label' => t('Display Suite Views'),
+ 'bundles' => $bundles,
+ 'ds_display' => TRUE,
+ 'base table' => 'views_view',
+ 'entity keys' => array(
+ 'id' => 'vid',
+ 'label' => 'name',
+ ),
+ ),
+ );
+
+ return $return;
+}
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function _ds_extras_theme_registry_alter(&$theme_registry) {
+
+ // Add views preprocess layout.
+ if (variable_get('ds_extras_vd', FALSE)) {
+ $theme_registry['views_view']['preprocess functions'][] = 'ds_extras_preprocess_view_layout';
+ }
+
+ // Add process page function.
+ if (variable_get('ds_extras_hide_page_title', FALSE)) {
+ $theme_registry['page']['process functions'][] = 'ds_extras_process_page_title';
+ }
+
+ // Remove ds_preprocess_field in case field templates is not enabled.
+ if (!variable_get('ds_extras_field_template', FALSE) && isset($theme_registry['field']['preprocess functions'])) {
+ $key = array_search('ds_extras_preprocess_field', $theme_registry['field']['preprocess functions']);
+ if (!empty($key)) {
+ unset($theme_registry['field']['preprocess functions'][$key]);
+ }
+ }
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function _ds_extras_module_implements_alter(&$implementations, $hook) {
+
+ // Because it's possible to turn on/off features for DS extras,
+ // we'll unset hooks here if necessary which otherwhise do nothing at all.
+
+ // Field template
+ $ft_hooks = array(
+ 'ds_field_settings_alter',
+ 'form_ds_classes_form_alter',
+ 'form_field_ui_field_edit_form_alter',
+ 'theme',
+ );
+ if (!variable_get('ds_extras_field_template', FALSE) && in_array($hook, $ft_hooks)) {
+ unset($implementations['ds_extras']);
+ }
+
+ // Region to block
+ $region_hooks = array(
+ 'ds_layout_region_alter',
+ 'field_attach_view_alter',
+ 'block_info',
+ 'block_view'
+ );
+ if (!variable_get('ds_extras_region_to_block', FALSE) && in_array($hook, $region_hooks)) {
+ unset($implementations['ds_extras']);
+ }
+
+ // Switch view mode
+ $switch_hooks = array(
+ 'form_node_form_alter',
+ );
+ if (!variable_get('ds_extras_switch_view_mode', FALSE) && in_array($hook, $switch_hooks)) {
+ unset($implementations['ds_extras']);
+ }
+
+ // Views displays
+ $vd_hooks = array(
+ 'entity_info',
+ 'ctools_plugin_api',
+ 'ds_fields_ui_alter',
+ );
+ if (!variable_get('ds_extras_vd', FALSE) && in_array($hook, $vd_hooks)) {
+ unset($implementations['ds_extras']);
+ }
+}
diff --git a/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.vd.inc b/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.vd.inc
new file mode 100644
index 000000000..c5e6ec3a3
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/includes/ds_extras.vd.inc
@@ -0,0 +1,274 @@
+<?php
+
+/**
+ * @file
+ * Views displays functions.
+ */
+
+/**
+ * Show the overview form and the selection list.
+ */
+function ds_extras_vd_overview() {
+
+ $entity_info = entity_get_info('ds_views');
+ $build = $rows = array();
+
+ ctools_include('export');
+ $vd_settings = ctools_export_crud_load_all('ds_vd');
+
+ if ($entity_info) {
+ foreach ($entity_info['bundles'] as $key => $value) {
+
+ // If no initial bundles were created, entity API will create
+ // one by default, so make sure we do not list that one.
+ if ($key == 'ds_views') {
+ continue;
+ }
+
+ $row = array();
+ $row[] = $value['label'];
+ $operations = l(t('Manage layout'), 'admin/structure/ds/vd/manage/' . $key . '/display');
+
+ if (isset($vd_settings[$key]) && $vd_settings[$key]->export_type == 1) {
+ $operations .= ' - ' . l(t('Remove'), 'admin/structure/ds/vd/manage/' . $key . '/remove');
+ }
+ $row[] = $operations;
+
+ $rows[$key] = $row;
+ }
+ }
+
+ if (empty($rows)) {
+ $rows = array(
+ array(array('data' => t('No views selected.'), 'colspan' => '2')),
+ );
+ }
+
+ $variables = array(
+ 'header' => array(t('Title'), t('Operations')),
+ 'rows' => $rows,
+ );
+ $build['list'] = array('#markup' => theme('table', $variables));
+ $build['form'] = drupal_get_form('ds_extras_vd_bundle_form', $rows);
+
+ return $build;
+}
+
+/**
+ * Return the views select form to create a bundle.
+ */
+function ds_extras_vd_bundle_form($form, $form_state, $rows) {
+
+ $options = array();
+ $views = views_get_all_views();
+ foreach ($views as $view_key => $view) {
+
+ // Ignore disabled views.
+ if (isset($view->disabled) && $view->disabled) {
+ continue;
+ }
+
+ $get_view = views_get_view($view->name);
+
+ // Loop through all displays.
+ foreach ($view->display as $display_key => $display) {
+ // Ignore default displays.
+ if ($display_key == 'default') {
+ continue;
+ }
+
+ $key = $view_key . '-' . $display_key;
+ $name = drupal_ucfirst($view->name) . ': ' . $display->display_title;
+ if (!isset($rows[$key])) {
+ $options[$key] = $name . ' (Views template)';
+ }
+ $get_view->set_display($display_key);
+ if ($get_view->display_handler->uses_fields() && !isset($rows[$key . '-fields'])) {
+ $options[$key . '-fields'] = $name . ' (Fields)';
+ }
+ }
+ }
+
+ $form['vd'] = array(
+ '#title' => t('Select view'),
+ '#description' => t('Select a View that you want to manage with Display Suite. If a View uses fields you can also select that view to select a layout and position the fields. Note that html for the label and field are limited.'),
+ '#type' => 'select',
+ '#options' => $options,
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Add'),
+ );
+
+ return $form;
+}
+
+/**
+ * Submit callback: save the new bundle.
+ */
+function ds_extras_vd_bundle_form_submit($form, &$form_state) {
+
+ // Save new bundle.
+ $record = new stdClass();
+ $record->vd = $form_state['values']['vd'];
+ $record->label = $form['vd']['#options'][$record->vd];
+ drupal_write_record('ds_vd', $record);
+
+ // Clear entity cache and field info fields cache.
+ cache_clear_all('field_info_fields', 'cache_field');
+ cache_clear_all('entity_info', 'cache', TRUE);
+
+ // Message and redirect.
+ drupal_set_message(t('Bundle @label has been added.', array('@label' => $record->label)));
+ $form_state['redirect'] = 'admin/structure/ds/vd';
+}
+
+/**
+ * Edit the display or remove a views display.
+ *
+ * @param $bundle
+ * The name of the bundle
+ * @param $action
+ * The action to take (edit or remove)
+ */
+function ds_extras_vd_manage($bundle = '', $action = '') {
+ $entity_info = entity_get_info('ds_views');
+
+ if (!empty($bundle) && isset($entity_info['bundles'][$bundle]) && $action == 'remove') {
+ return drupal_get_form('ds_extras_vd_bundle_remove', $bundle, $entity_info['bundles'][$bundle]['label']);
+ }
+
+ if (!empty($bundle) && isset($entity_info['bundles'][$bundle]) && $action == 'display') {
+ return ds_extras_vd_field_ui($bundle);
+ }
+
+ // Redirect to overview.
+ drupal_set_message(t('No view found to layout.'));
+ drupal_goto('admin/structure/ds/vd');
+}
+
+/**
+ * Return Field UI display screen for a view and bundle.
+ *
+ * @param $bundle
+ * The name of the bundle
+ */
+function ds_extras_vd_field_ui($bundle) {
+
+ global $conf;
+
+ // Use drupal_build_form instead of drupal_get_form.
+ $form_state = array();
+ $arguments = array('ds_views', $bundle, 'default');
+ $form_state['build_info']['args'] = $arguments;
+ $form_state['no_view_mode_suggestions'] = TRUE;
+ $form_state['no_panels'] = TRUE;
+ form_load_include($form_state, 'inc', 'field_ui', 'field_ui.admin');
+ form_load_include($form_state, 'inc', 'ds_extras', 'includes/ds_extras.vd');
+
+ // Deny access to field_group if it exists.
+ if (module_exists('field_group')) {
+ $form_state['no_field_group'] = TRUE;
+ }
+
+ // Build form.
+ $build = drupal_build_form('field_ui_display_overview_form', $form_state);
+
+ // Deny access to view modes.
+ $build['additional_settings']['modes']['#access'] = FALSE;
+
+ // Deny access to disabling blocks and regions.
+ $build['additional_settings']['ds_layouts']['hide_sidebars']['#access'] = FALSE;
+
+ // Deny access to fields table if there's no layout.
+ if (!ds_get_layout('ds_views', $bundle, 'default')) {
+ $build['fields']['#access'] = FALSE;
+ }
+
+ // Add additional validate function so we can remove notices.
+ if (module_exists('field_group')) {
+ array_unshift($build['#validate'], 'ds_vd_field_ui_fix_notices');
+ }
+
+ return $build;
+}
+
+/**
+ * Implements hook_ds_fields_ui_alter().
+ */
+function ds_extras_ds_fields_ui_alter(&$fields, $context) {
+
+ // Check on the $bundle string to see if the key 2 exists. If it exists,
+ // we'll call the view to put the fields of this view on and remove
+ // the fields which comes default by ds_extras_field_extra_fields()
+ // and only work for the views template.
+ $bundle = $context['bundle'];
+ $fields_check = explode('-', $bundle);
+
+ if (isset($fields_check[2])) {
+
+ $field_copy = array(
+ 'title' => 'dummy',
+ 'field_type' => DS_FIELD_TYPE_IGNORE,
+ );
+
+ // Remove the view template fields.
+ $remove_fields = ds_extras_ds_fields_info('ds_views');
+ foreach ($remove_fields['ds_views'] as $key => $properties) {
+ unset($fields[$key]);
+ }
+
+ // Get the view and its fields.
+ $view = views_get_view($fields_check[0]);
+ $view->set_display($fields_check[1]);
+ $fields = $view->display_handler->get_field_labels();
+ foreach ($fields as $field_key => $field_label) {
+ $field_properties = $field_copy;
+ $field_properties['title'] = $field_label;
+ $fields[$field_key] = $field_properties;
+ }
+ }
+}
+
+/**
+ * Return confirm form to remove a views bundle
+ */
+function ds_extras_vd_bundle_remove($form, $form_state, $bundle, $label) {
+ $form['#bundle'] = $bundle;
+ $form['#label'] = $label;
+ return confirm_form($form, t('Are you sure you want to remove bundle @label ?', array('@label' => $label)), 'admin/structure/ds/vd');
+}
+
+/**
+ * Submit callback: remove a views bundle
+ */
+function ds_extras_vd_bundle_remove_submit($form, &$form_state) {
+
+ $bundle = $form['#bundle'];
+ $label = $form['#label'];
+
+ // Remove bundle.
+ db_delete('ds_vd')
+ ->condition('vd', $bundle)
+ ->execute();
+
+ // Remove layout.
+ db_delete('ds_layout_settings')
+ ->condition('bundle', $bundle)
+ ->execute();
+
+ // Remove settings.
+ db_delete('ds_field_settings')
+ ->condition('bundle', $bundle)
+ ->execute();
+
+ // Remove from bundle settings.
+ variable_del('field_bundle_settings_ds_views__' . $bundle);
+
+ // Clear entity cache and field info fields cache.
+ cache_clear_all('field_info_fields', 'cache_field');
+ cache_clear_all('entity_info', 'cache', TRUE);
+
+ drupal_set_message(t('Bundle @label has been removed.', array('@label' => $label)));
+}
diff --git a/sites/all/modules/ds/modules/ds_extras/js/ds_extras.admin.js b/sites/all/modules/ds/modules/ds_extras/js/ds_extras.admin.js
new file mode 100644
index 000000000..f178d9549
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/js/ds_extras.admin.js
@@ -0,0 +1,126 @@
+/**
+ * @file
+ * Javascript functionality for the Display Suite Extras administration UI.
+ */
+
+(function ($) {
+
+Drupal.behaviors.DSExtrasSummaries = {
+ attach: function (context) {
+
+ $('#edit-additional-settings-fs1', context).drupalSetSummary(function (context) {
+ var fieldtemplates = $('#edit-additional-settings-fs1-ds-extras-field-template', context);
+
+ if (fieldtemplates.is(':checked')) {
+ var fieldtemplate = $('#edit-additional-settings-fs1-ft-default option:selected').text();
+ return Drupal.t('Enabled') + ': ' + Drupal.t(fieldtemplate);
+ }
+
+ return Drupal.t('Disabled');
+ });
+
+ $('#edit-additional-settings-fs2', context).drupalSetSummary(function (context) {
+ var extra_fields = $('#edit-additional-settings-fs2-ds-extras-fields-extra', context);
+
+ if (extra_fields.is(':checked')) {
+ return Drupal.t('Enabled');
+ }
+
+ return Drupal.t('Disabled');
+ });
+
+ $('#edit-additional-settings-fs4', context).drupalSetSummary(function (context) {
+ var vals = [];
+
+ $('input:checked', context).parent().each(function () {
+ vals.push(Drupal.checkPlain($.trim($('.option', this).text())));
+ });
+
+ if (vals.length > 0) {
+ return vals.join(', ');
+ }
+ return Drupal.t('Disabled');
+ });
+ }
+};
+
+/**
+ * Field template.
+ */
+Drupal.behaviors.settingsToggle = {
+ attach: function (context) {
+
+ // Bind on click.
+ $('.field-formatter-settings-edit-form', context).once('ds-ft', function() {
+
+ var fieldTemplate = $(this);
+
+ // Bind on field template select button.
+ fieldTemplate.find('.ds-extras-field-template').change(function() {
+ ds_show_expert_settings(fieldTemplate);
+ });
+
+ ds_show_expert_settings(fieldTemplate);
+
+ });
+
+ // Show / hide settings on field template form.
+ function ds_show_expert_settings(element, open) {
+ field = element;
+ ft = $('.ds-extras-field-template', field).val();
+
+ if (ft == 'theme_ds_field_expert') {
+ // Show second, third, fourth, fifth and sixth label.
+ if ($('.lb .form-item:nth-child(1)', field).is(':visible')) {
+ $('.lb .form-item:nth-child(2), .lb .form-item:nth-child(3), .lb .form-item:nth-child(4), .lb .form-item:nth-child(5), .lb .form-item:nth-child(6)', field).show();
+ }
+ // Remove margin from update button.
+ $('.ft-update', field).css({'margin-top': '-10px'});
+ // Show wrappers.
+ $('.lbw, .ow, .fis, .fi', field).show();
+ // Show prefix and suffix
+ $('.field-prefix', field).show();
+ $('.field-suffix', field).show();
+ }
+ else {
+ // Hide second, third, fourth, fifth and sixth label.
+ $('.lb .form-item:nth-child(2), .lb .form-item:nth-child(3), .lb .form-item:nth-child(4), .lb .form-item:nth-child(5), .lb .form-item:nth-child(6)', field).hide();
+ // Add margin on update button.
+ $('.ft-update', field).css({'margin-top': '10px'});
+ // Hide wrappers.
+ $('.lbw, .ow, .fis, .fi', field).hide();
+ // Hide prefix and suffix
+ $('.field-prefix', field).hide();
+ $('.field-suffix', field).hide();
+ }
+
+ // Colon.
+ if (ft == 'theme_field' || ft == 'theme_ds_field_reset') {
+ $('.colon-checkbox', field).parent().hide();
+ }
+ else if ($('.lb .form-item:nth-child(1)', field).is(':visible')) {
+ $('.colon-checkbox', field).parent().show();
+ }
+
+ // CSS classes.
+ if (ft != 'theme_ds_field_expert' && ft != 'theme_ds_field_reset') {
+ $('.field-classes', field).show();
+ }
+ else {
+ $('.field-classes', field).hide();
+ }
+ }
+
+ $('.label-change').change(function() {
+ var field = $(this).parents('tr');
+ if ($('.field-template', field).length > 0) {
+ ft = $('.ds-extras-field-template', field).val();
+ if (ft == 'theme_field' || ft == 'theme_ds_field_reset') {
+ $('.colon-checkbox', field).parent().hide();
+ }
+ }
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/ds/modules/ds_extras/js/ds_extras.js b/sites/all/modules/ds/modules/ds_extras/js/ds_extras.js
new file mode 100644
index 000000000..f9215bd52
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_extras/js/ds_extras.js
@@ -0,0 +1,59 @@
+/**
+ * @file
+ * Javascript functionality for the Display Suite Extras module.
+ */
+
+(function ($) {
+
+// Switch view mode inline with AJAX, for the 'View mode switcher' option.
+Drupal.behaviors.DSExtrasSwitchViewmode = {
+ attach: function (context) {
+
+ if ($('.switch-view-mode-field').length > 0) {
+ $('.switch-view-mode-field a').click(function() {
+
+ // Create an object.
+ var link = $(this);
+
+ // Get params from the class.
+ var params = $(this).attr('class').split('-');
+
+ $.ajax({
+ type: 'GET',
+ url: Drupal.settings.basePath + 'ds-switch-view-mode',
+ data: {entity_type: params[0], view_mode: params[3], id: params[2]},
+ dataType: 'json',
+ success: function (data) {
+ if (data.status) {
+ old_view_mode = params[1];
+ wrapper = link.parents('.view-mode-' + old_view_mode);
+ Drupal.theme('DisplaySuiteSwitchViewmode', wrapper, data.content);
+ Drupal.attachBehaviors();
+ }
+ else {
+ alert(data.errorMessage);
+ }
+ },
+ error: function (xmlhttp) {
+ alert(Drupal.t('An HTTP error @status occurred.', {'@status': xmlhttp.status}));
+ }
+ });
+ return false;
+ });
+ }
+ }
+};
+
+/**
+ * Theme function for a replacing content of Display Suite content wrapper.
+ *
+ * @param wrapper
+ * The HTML object which needs to be replaced
+ * @param content
+ * The new content
+ */
+Drupal.theme.prototype.DisplaySuiteSwitchViewmode = function (wrapper, content) {
+ wrapper.replaceWith(content);
+};
+
+})(jQuery);
diff --git a/sites/all/modules/ds/modules/ds_format/ds_format.info b/sites/all/modules/ds/modules/ds_format/ds_format.info
new file mode 100644
index 000000000..05900077b
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_format/ds_format.info
@@ -0,0 +1,13 @@
+name = "Display Suite Format"
+description = "Provides the Display Suite Code format filter."
+core = "7.x"
+package = "Display Suite"
+dependencies[] = ds
+configure = admin/structure/ds/list/extras
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/modules/ds_format/ds_format.install b/sites/all/modules/ds/modules/ds_format/ds_format.install
new file mode 100644
index 000000000..a8f45ec9c
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_format/ds_format.install
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Display Suite Format install file.
+ */
+
+/**
+ * Implements hook_enable().
+ */
+function ds_format_enable() {
+ $format_exists = (bool) db_query_range('SELECT 1 FROM {filter_format} WHERE format = :format', 0, 1, array(':format' => 'ds_code'))->fetchField();
+ // Add a Display Suite code text format, if it does not exist. Do this only for the
+ // first install (or if the format has been manually deleted) as there is no
+ // reliable method to identify the format in an uninstall hook or in
+ // subsequent clean installs.
+ if (!$format_exists) {
+ $ds_format = array(
+ 'format' => 'ds_code',
+ 'name' => 'Display Suite code',
+ // 'Plain text' format is installed with a weight of 10 by default. Use a
+ // higher weight here to ensure that this format will not be the default
+ // format for anyone.
+ 'weight' => 12,
+ 'filters' => array(
+ // Enable the DS evaluator filter.
+ 'ds_code' => array(
+ 'weight' => 0,
+ 'status' => 1,
+ ),
+ ),
+ );
+ $ds_format = (object) $ds_format;
+ filter_format_save($ds_format);
+
+ drupal_set_message(t('A <a href="@ds-code">Display Suite code</a> text format has been created.', array('@ds-code' => url('admin/config/content/formats/' . $ds_format->format))));
+ }
+}
+
+/**
+ * Implements hook_disable().
+ */
+function ds_format_disable() {
+ drupal_set_message(t('The Display Suite Format module has been disabled. Any existing content that was using the Display Suite filter will now be visible in plain text. This might pose a security risk by exposing sensitive information, if any, used in the PHP code.'));
+}
+
diff --git a/sites/all/modules/ds/modules/ds_format/ds_format.module b/sites/all/modules/ds/modules/ds_format/ds_format.module
new file mode 100644
index 000000000..d7c9956b9
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_format/ds_format.module
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Display Suite Format
+ */
+
+/**
+ * Implements hook_filter_info().
+ */
+function ds_format_filter_info() {
+ $filters['ds_code'] = array(
+ 'title' => t('Display Suite evaluator'),
+ 'description' => t('This filter will only work in the Display Suite text format, machine name is <em>ds_code</em>. No other filters can be enabled either.'),
+ 'process callback' => 'ds_format_php_eval',
+ 'tips callback' => 'ds_format_filter_tips',
+ 'cache' => FALSE,
+ );
+ return $filters;
+}
+
+/**
+ * Tips callback for Display Suite php filter.
+ */
+function ds_format_filter_tips($filter, $format, $long = FALSE) {
+ global $base_url;
+ if ($long) {
+ $output = '<h4>' . t('Using custom code with Display Suite') . '</h4>';
+ $output .= t('Include &lt;?php ?&gt; tags when using PHP. The $entity object is available.');
+ return $output;
+ }
+ else {
+ return t('You may post Display Suite code. You should include &lt;?php ?&gt; tags when using PHP. The $entity object is available.');
+ }
+}
+
+/**
+ * Wrapper function around PHP eval(). We don't use php_eval from
+ * the PHP module because custom fields might need properties from
+ * the current entity.
+ *
+ * @param $code
+ * The code to evaluate from the custom field.
+ * @param $object
+ * An object to use for evaluation.
+ * @return $output
+ * The output from eval.
+ */
+function ds_format_php_eval($code, $entity, $build = array()) {
+ global $theme_path, $theme_info, $conf;
+
+ // Store current theme path.
+ $old_theme_path = $theme_path;
+
+ // Restore theme_path to the theme, as long as ds_php_eval() executes,
+ // so code evaluted will not see the caller module as the current theme.
+ // If theme info is not initialized get the path from theme_default.
+ if (!isset($theme_info)) {
+ $theme_path = drupal_get_path('theme', $conf['theme_default']);
+ }
+ else {
+ $theme_path = dirname($theme_info->filename);
+ }
+
+ ob_start();
+ print eval('?>' . $code);
+ $output = ob_get_contents();
+ ob_end_clean();
+
+ // Recover original theme path.
+ $theme_path = $old_theme_path;
+
+ return $output;
+}
+
diff --git a/sites/all/modules/ds/modules/ds_forms/css/ds_forms.admin.css b/sites/all/modules/ds/modules/ds_forms/css/ds_forms.admin.css
new file mode 100644
index 000000000..92e9c6372
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_forms/css/ds_forms.admin.css
@@ -0,0 +1,6 @@
+/**
+ * Refresh button on manage fields form.
+ */
+#edit-refresh {
+ display:none;
+} \ No newline at end of file
diff --git a/sites/all/modules/ds/modules/ds_forms/ds_forms.info b/sites/all/modules/ds/modules/ds_forms/ds_forms.info
new file mode 100644
index 000000000..daa25617d
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_forms/ds_forms.info
@@ -0,0 +1,12 @@
+name = "Display Suite Forms"
+description = "Manage the layout of forms in Display Suite."
+core = "7.x"
+package = "Display Suite"
+dependencies[] = ds
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/modules/ds_forms/ds_forms.install b/sites/all/modules/ds/modules/ds_forms/ds_forms.install
new file mode 100644
index 000000000..51b1e36fe
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_forms/ds_forms.install
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Display Suite forms install file.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function ds_forms_install() {
+ db_update('system')
+ ->fields(array('weight' => 20))
+ ->condition('name', 'ds_forms')
+ ->execute();
+}
diff --git a/sites/all/modules/ds/modules/ds_forms/ds_forms.module b/sites/all/modules/ds/modules/ds_forms/ds_forms.module
new file mode 100644
index 000000000..ba8003b3f
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_forms/ds_forms.module
@@ -0,0 +1,239 @@
+<?php
+
+/**
+ * @file
+ * Display Suite forms integration.
+ */
+
+/**
+ * Implements hook_theme().
+ */
+function ds_forms_theme() {
+ $theme_functions = array();
+
+ $theme_functions['ds_forms_custom_form'] = array(
+ 'render element' => 'form',
+ );
+
+ return $theme_functions;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_forms_form_field_ui_field_overview_form_alter(&$form, &$form_state) {
+
+ // Determine if this entity type is supported
+ if (_ds_forms_is_entity_type_supported($form['#entity_type'])) {
+
+ // Add necessary variables for DS Field UI.
+ $form['#view_mode'] = 'form';
+ $form_state['no_panels'] = TRUE;
+ $form_state['no_view_mode_suggestions'] = TRUE;
+
+ // Make sure the refresh works.
+ if (!module_exists('field_group')) {
+ // This key is used to store the current updated field.
+ $form_state += array(
+ 'formatter_settings_edit' => NULL,
+ );
+ // Add AJAX wrapper.
+ $form['fields']['#prefix'] = '<div id="field-display-overview-wrapper">';
+ $form['fields']['#suffix'] = '</div>';
+
+ // See field_ui.admin.inc for more details on refresh rows.
+ $form['refresh_rows'] = array('#type' => 'hidden');
+ $form['refresh'] = array(
+ '#type' => 'submit',
+ '#value' => t('Refresh'),
+ '#op' => 'refresh_table',
+ '#submit' => array('field_ui_display_overview_multistep_submit'),
+ '#ajax' => array(
+ 'callback' => 'field_ui_display_overview_multistep_js',
+ 'wrapper' => 'field-display-overview-wrapper',
+ 'effect' => 'fade',
+ // The button stays hidden, so we hide the AJAX spinner too. Ad-hoc
+ // spinners will be added manually by the client-side script.
+ 'progress' => 'none',
+ ),
+ );
+ $form['#attached']['css'][] = drupal_get_path('module', 'ds_forms') . '/css/ds_forms.admin.css';
+ }
+
+ // Attach js.
+ $form['#attached']['js'][] = drupal_get_path('module', 'ds_forms') . '/js/ds_forms.admin.js';
+
+ // Load Display Suite.
+ form_load_include($form_state, 'inc', 'ds', 'includes/ds.field_ui');
+ ds_field_ui_fields_layouts($form, $form_state);
+ }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function ds_forms_form_alter(&$form, &$form_state, $form_id) {
+ if ($ds_form = ds_build_load($form, $form_id)) {
+ if ($layout = ds_get_layout($ds_form->entity_type, $ds_form->bundle, 'form', FALSE)) {
+ // Add the theming function and add the layout as a class.
+ $form['#theme'] = array('ds_forms_custom_form');
+ $class = strtr($layout['layout'], '_', '-');
+ if ((isset($form['#attributes']['class']) && is_array($form['#attributes']['class'])) || !(isset($form['#attributes']['class']))) {
+ $form['#attributes']['class'][] = $class;
+ }
+ elseif (isset($form['#attributes']['class']) && is_string($form['#attributes']['class'])) {
+ $form['#attributes']['class'] .= ' ' . $class . ' ';
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_field_widget_WIDGET_TYPE_form_alter().
+ */
+function ds_forms_field_widget_field_collection_embed_form_alter(&$element, &$form_state, $context){
+ if ($ds_form = ds_build_load($element, 'field_collection_embed')) {
+ if ($layout = ds_get_layout($ds_form->entity_type, $ds_form->bundle, 'form', FALSE)) {
+ // Add the theming function and add the layout as a class.
+ $element['#theme'] = array('ds_forms_custom_form');
+ $element['#form_id'] = 'field_collection_embed';
+ $class = strtr($layout['layout'], '_', '-');
+ if ((isset($element['#attributes']['class']) && is_array($element['#attributes']['class'])) || !(isset($element['#attributes']['class']))) {
+ $element['#attributes']['class'][] = $class;
+ }
+ elseif (isset($element['#attributes']['class']) && is_string($element['#attributes']['class'])) {
+ $element['#attributes']['class'] .= ' ' . $class . ' ';
+ }
+ }
+ }
+}
+/**
+ * Helper function to determine if this form can be loaded.
+ */
+function ds_build_load($form, $form_id) {
+ $ds_form = FALSE;
+
+ if (isset($form['#entity_type']) && isset($form['#bundle']) && $form_id != 'field_ui_field_overview_form' && $form_id != 'field_ui_display_overview_form'
+ && $form_id != 'field_ui_field_settings_form' && $form_id != 'field_ui_widget_type_form' && $form_id != 'field_ui_field_edit_form' && !preg_match('/^editablefields_form_/', $form_id)) {
+ $ds_form = new stdClass();
+ $ds_form->entity_type = $form['#entity_type'];
+ $ds_form->bundle = $form['#bundle'];
+ }
+
+ return $ds_form;
+}
+
+/**
+ * Implements hook_preprocess_ds_forms_custom_form().
+ */
+function ds_forms_preprocess_ds_forms_custom_form(&$vars) {
+
+ $form = ds_build_load($vars['form'], $vars['form']['#form_id']);
+ if (!$form) {
+ return;
+ }
+
+ $entity_type = $form->entity_type;
+ $bundle = $form->bundle;
+
+ if ($layout = ds_get_layout($entity_type, $bundle, 'form', FALSE)) {
+
+ // Theme hook suggestions.
+ $vars['theme_hook_suggestions'][] = $layout['layout'];
+ $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $entity_type;
+ $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $entity_type . '_' . $bundle;
+
+ $form = &$vars['form'];
+
+ // Add path to css file.
+ if (isset($layout['css'])) {
+ drupal_add_css($layout['path'] . '/' . $layout['layout'] . '.css');
+ }
+
+ // Add the hidden region.
+ $layout['regions']['hidden'] = 'Hidden';
+
+ // Create region variables based on the layout settings.
+ foreach ($layout['regions'] as $region_name => $region) {
+
+ // Create the region content.
+ if ($region_name == 'hidden') {
+ ds_forms_render_region($form, $region_name, $layout);
+ }
+ else {
+ $vars[$region_name] = ds_forms_render_region($form, $region_name, $layout);
+ }
+
+ // Add extras classes to the region.
+ $vars[$region_name . '_classes'] = !empty($layout['settings']['classes'][$region_name]) ? ' ' . implode(' ', $layout['settings']['classes'][$region_name]) : '';
+
+ // Add a wrapper to the region.
+ if (empty($layout['flexible'])) {
+ $vars[$region_name . '_wrapper'] = isset($layout['settings']['wrappers'][$region_name]) ? $layout['settings']['wrappers'][$region_name] : 'div';
+ }
+ }
+
+ // Add layout attributes if any
+ if (!empty($layout['settings']['layout_attributes'])) {
+ $vars['layout_attributes'] = ' ' . $layout['settings']['layout_attributes'];
+ }
+ else {
+ $vars['layout_attributes'] = '';
+ }
+
+ if (isset($layout['settings']['classes']['layout_class'])) {
+ foreach ($layout['settings']['classes']['layout_class'] as $layout_class) {
+ $vars['classes_array'][] = $layout_class;
+ }
+ }
+
+ // Ensure there is a class
+ $vars['classes_array'][] = 'ds-form';
+
+ // Merge the classes into a string
+ $vars['classes'] = implode(' ', $vars['classes_array']);
+
+ // Add a layout wrapper
+ $vars['layout_wrapper'] = isset($layout['settings']['layout_wrapper']) ? $layout['settings']['layout_wrapper'] : 'div';
+
+ // Add the rest of the form elements
+ $vars['drupal_render_children'] = drupal_render_children($vars['form']);
+ }
+}
+
+/**
+ * Render a form region.
+ *
+ * @param $content
+ * An array of content fields.
+ * @param $region
+ * The name of region to render.
+ * @param $layout
+ * The layout definition.
+ */
+function ds_forms_render_region(&$content, $region, $layout) {
+ $output = '';
+
+ if (isset($layout['settings']['regions'][$region])) {
+ foreach ($layout['settings']['regions'][$region] as $key => $field) {
+ if ($region == 'hidden') {
+ $content[$field]['#access'] = FALSE;
+ }
+ else {
+ $output .= drupal_render($content[$field]);
+ }
+ }
+ }
+ return $output;
+}
+
+/**
+ * Determines if this entity type is supported by ds_forms.
+ *
+ * Currently supports all fieldable entity types.
+ */
+function _ds_forms_is_entity_type_supported($entity_type) {
+ $info = entity_get_info($entity_type);
+ return !empty($info['fieldable']);
+}
+
diff --git a/sites/all/modules/ds/modules/ds_forms/js/ds_forms.admin.js b/sites/all/modules/ds/modules/ds_forms/js/ds_forms.admin.js
new file mode 100644
index 000000000..02def7066
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_forms/js/ds_forms.admin.js
@@ -0,0 +1,79 @@
+
+(function($) {
+
+/**
+ * Attach behaviors.
+ */
+Drupal.behaviors.fieldUIFieldsFormsOverview = {
+ attach: function (context, settings) {
+ $('table#field-overview', context).once('field-field-overview', function() {
+ Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIFieldOverview);
+ });
+ }
+};
+
+/**
+ * Row handlers for the 'Manage fields' screen.
+ */
+Drupal.fieldUIFieldOverview = Drupal.fieldUIFieldOverview || {};
+
+Drupal.fieldUIFieldOverview.ds = function (row, data) {
+
+ this.row = row;
+ this.name = data.name;
+ this.region = data.region;
+ this.tableDrag = data.tableDrag;
+
+ this.$regionSelect = $('select.ds-field-region', row);
+ this.$regionSelect.change(Drupal.fieldUIOverview.onChange);
+
+ return this;
+};
+
+Drupal.fieldUIFieldOverview.ds.prototype = {
+
+ /**
+ * Returns the region corresponding to the current form values of the row.
+ */
+ getRegion: function () {
+ return this.$regionSelect.val();
+ },
+
+ /**
+ * Reacts to a row being changed regions.
+ *
+ * This function is called when the row is moved to a different region, as a
+ * result of either :
+ * - a drag-and-drop action
+ * - user input in one of the form elements watched by the
+ * Drupal.fieldUIOverview.onChange change listener.
+ *
+ * @param region
+ * The name of the new region for the row.
+ * @return
+ * A hash object indicating which rows should be AJAX-updated as a result
+ * of the change, in the format expected by
+ * Drupal.fieldOverview.AJAXRefreshRows().
+ */
+ regionChange: function (region, recurse) {
+
+ // Replace dashes with underscores.
+ region = region.replace(/-/g, '_');
+
+ // Set the region of the select list.
+ this.$regionSelect.val(region);
+
+ // Prepare rows to be refreshed in the form.
+ var refreshRows = {};
+ refreshRows[this.name] = this.$regionSelect.get(0);
+
+ // If a row is handled by field_group module, loop through the children.
+ if ($(this.row).hasClass('field-group') && $.isFunction(Drupal.fieldUIFieldOverview.group.prototype.regionChangeFields)) {
+ Drupal.fieldUIFieldOverview.group.prototype.regionChangeFields(region, this, refreshRows);
+ }
+
+ return refreshRows;
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/ds/modules/ds_search/css/ds_search.theme.css b/sites/all/modules/ds/modules/ds_search/css/ds_search.theme.css
new file mode 100644
index 000000000..5f9259549
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_search/css/ds_search.theme.css
@@ -0,0 +1,4 @@
+
+.ds-search-highlight {
+ background-color: yellow;
+} \ No newline at end of file
diff --git a/sites/all/modules/ds/modules/ds_search/ds_search.info b/sites/all/modules/ds/modules/ds_search/ds_search.info
new file mode 100644
index 000000000..788c4b4b8
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_search/ds_search.info
@@ -0,0 +1,13 @@
+name = "Display Suite Search"
+description = "Extend the display options for search results for Drupal Core or Apache Solr."
+core = "7.x"
+package = "Display Suite"
+dependencies[] = ds
+configure = admin/structure/ds/list/search
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/modules/ds_search/ds_search.install b/sites/all/modules/ds/modules/ds_search/ds_search.install
new file mode 100644
index 000000000..2ed207768
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_search/ds_search.install
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Display Suite search install file.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function ds_search_uninstall() {
+ variable_del('ds_search_type');
+ variable_del('ds_search_path');
+ variable_del('ds_search_view_mode');
+ variable_del('ds_search_file_render');
+ variable_del('ds_search_variables');
+ variable_del('ds_search_show_title');
+ variable_del('ds_search_language');
+ variable_del('ds_search_highlight');
+ variable_del('ds_search_highlight_selector');
+ variable_del('ds_search_group_by_type');
+ variable_del('ds_search_group_by_type_settings');
+ variable_del('ds_search_group_by_type_other');
+ variable_del('ds_search_group_by_type_other_wrapper');
+ variable_del('ds_search_node_form_alter');
+ variable_del('ds_search_apachesolr_hide_current_filters');
+ variable_del('ds_search_apachesolr_current_filters_default');
+ variable_del('ds_search_apachesolr_multisite');
+ variable_del('ds_search_apachesolr_multisite_sort');
+ variable_del('ds_search_apachesolr_multisite_group');
+ variable_del('ds_search_apachesolr_multisite_group_config');
+}
diff --git a/sites/all/modules/ds/modules/ds_search/ds_search.module b/sites/all/modules/ds/modules/ds_search/ds_search.module
new file mode 100644
index 000000000..c0b60af77
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_search/ds_search.module
@@ -0,0 +1,763 @@
+<?php
+
+/**
+ * @file
+ * Display Suite search.
+ */
+
+/**
+ * Implements hook_help().
+ */
+function ds_search_help($path, $arg) {
+ switch ($path) {
+ case 'admin/structure/ds/list/search':
+ $output = '<dl>';
+ $output .= '<dt>' . t('Display Suite defines its own search type for search. You need to enable it at !url when you are going to use Drupal core search. You do not have to enable and use it when using the Apachesolr module. Search results will be themed on the default Apachesolr pages.', array('!url' => l('search settings', 'admin/config/search/settings'))) . '</dt>';
+ $output .= '</dl>';
+ return $output;
+ }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function ds_search_menu() {
+ $items = array();
+
+ $items['admin/structure/ds/list/search'] = array(
+ 'title' => 'Search',
+ 'description' => 'Configure search settings.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_search_settings'),
+ 'access arguments' => array('admin_display_suite'),
+ 'file' => 'includes/ds_search.admin.inc',
+ 'type' => MENU_LOCAL_TASK,
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function ds_search_theme() {
+ return array(
+ 'ds_search_page' => array(),
+ 'ds_search_group_by_type_settings' => array(
+ 'render element' => 'element',
+ 'file' => 'includes/ds_search.admin.inc',
+ ),
+ );
+}
+
+/**
+ * Search page theming.
+ */
+function theme_ds_search_page($build) {
+ // fix for Drupal 7.33+
+ if(isset($build['theme_hook_original'])) {
+ unset($build['theme_hook_original']);
+ }
+
+ // Check on empty search results.
+ if (empty($build['search_results'])) {
+
+ // Alter the title and extra variables.
+ if (!empty($build['search_title'])) {
+ $build['search_title']['#markup'] = '<h2>' . t('Your search yielded no results') . '</h2>';
+ unset($build['search_extra']);
+ }
+
+ $build['search_empty'] = array('#markup' => search_help('search#noresults', drupal_help_arg()));
+ }
+
+ $build['search_results']['#sorted'] = TRUE;
+
+ return $build;
+}
+
+/**
+ * Implements hook_ds_fields_info().
+ */
+function ds_search_ds_fields_info($entity_type) {
+ $fields = array();
+
+ if ($entity_type == 'node') {
+ $fields['node']['search_snippet'] = array(
+ 'title' => t('Search snippet'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_search_snippet',
+ 'ui_limit' => array('*|' . variable_get('ds_search_view_mode', 'search_result')),
+ );
+ $fields['node']['search_info'] = array(
+ 'title' => t('Search info'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'ds_search_extra_info',
+ 'ui_limit' => array('*|' . variable_get('ds_search_view_mode', 'search_result')),
+ );
+ }
+
+ if (isset($fields[$entity_type])) {
+ return array($entity_type => $fields[$entity_type]);
+ }
+
+ return;
+}
+
+/**
+ * Returns the snippet field.
+ */
+function ds_search_snippet($field) {
+ // Apache Solr
+ if (isset($field['entity']->search_snippet)) {
+ return $field['entity']->search_snippet;
+ }
+ // Original node snippet
+ elseif (isset($field['entity']->snippet)) {
+ return $field['entity']->snippet;
+ }
+}
+
+/**
+ * Returns the info field, just like default search.
+ */
+function ds_search_extra_info($field) {
+ $info = array();
+ $info['user'] = theme('username', array('account' => $field['entity']));
+ $info['date'] = format_date($field['entity']->changed, 'short');
+ if (isset($field['entity']->search_extra) && is_array($field['entity']->search_extra)) {
+ $info = array_merge($info, $field['entity']->search_extra);
+ }
+ return implode(' - ', $info);
+}
+
+/**
+ * Implements hook_search_info().
+ */
+function ds_search_search_info() {
+ return array(
+ 'title' => 'Content',
+ 'path' => variable_get('ds_search_path', 'content'),
+ );
+}
+
+/**
+ * Implements hook_node_update_index().
+ */
+function ds_search_update_index() {
+ ds_search_invoke_node_search('update_index');
+}
+
+/**
+ * Implements hook_search_status().
+ */
+function ds_search_search_status() {
+ return ds_search_invoke_node_search('search_status');
+}
+
+/**
+ * Implements hook_search_execute().
+ */
+function ds_search_search_execute($keys = NULL, $conditions = NULL) {
+ // Save the keys in case we need them later on.
+ ds_search_get_keys($keys);
+
+ // We will call an extra function which handles the actual search.
+ // In some cases, we simply copied a lot from the original hook,
+ // because some modules already called drupal_render and were unsetting
+ // the #theme key. By using our own search info type, we can call
+ // hook_search_page ourselves and be as flexible as we need to be.
+ $ds_search_type = variable_get('ds_search_type', 'node') . '_ds_search_execute';
+
+ // Make sure the function exists.
+ if (function_exists($ds_search_type)) {
+ return $ds_search_type($keys, $conditions);
+ }
+}
+
+/**
+ * Save or get the search keys.
+ */
+function ds_search_get_keys($keys = NULL) {
+ static $run, $saved_keys = FALSE;
+
+ if (!$run) {
+ $run = TRUE;
+ $saved_keys = $keys;
+ }
+ else {
+ return $saved_keys;
+ }
+}
+
+/**
+ * Invoke a given search hook on the node module.
+ *
+ * @param $hook
+ * Hook to invoke.
+ */
+function ds_search_invoke_node_search($hook) {
+
+ $enabled_search_modules = variable_get('search_active_modules', array());
+
+ // If node search is enabled, core is invoking it.
+ if (isset($enabled_search_modules['node']) && $enabled_search_modules['node'] === 'node') {
+ return;
+ }
+ else {
+ $ds_search_type = variable_get('ds_search_type', 'node');
+ if ($ds_search_type != 'node') {
+ return;
+ }
+ }
+
+ return module_invoke('node', $hook);
+}
+
+/**
+ * DS entity view callback.
+ *
+ * Straight copy from Entity API module with fallback to Drupal core
+ * view callbacks for nodes, files and maybe others later.
+ */
+function ds_entity_view_fallback($entity_type, $entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
+
+ // Use the entity module in case it's enabled.
+ if (module_exists('entity')) {
+ return entity_view($entity_type, $entities, $view_mode, $langcode, $page);
+ }
+ else {
+ if ($entity_type == 'node') {
+ return node_view_multiple($entities, $view_mode);
+ }
+ elseif ($entity_type == 'file' && function_exists('file_view_multiple')) {
+ return file_view_multiple($entities, $view_mode);
+ }
+ }
+}
+
+/**
+ * Implements hook_search_page().
+ */
+function ds_search_search_page($results) {
+
+ // Build shared variables.
+ $build = array('#type' => 'node');
+ ds_build_shared_page_variables($build);
+
+ $i = 0;
+ // Multi site Apache Solr support.
+ if (variable_get('ds_search_apachesolr_multisite') && variable_get('ds_search_type', 'node') == 'apachesolr_search') {
+ $build['search_results'] = $results;
+ }
+ else {
+ foreach ($results as $id => $result) {
+ // Use default search result theming for file in case it's configured.
+ if ($result->entity_type == 'file' && variable_get('ds_search_file_render', FALSE)) {
+ // Get the file type from the file entity module. We'll overwrite
+ // the bundle here then as that makes more sense as a suggestion.
+ if (function_exists('file_get_type')) {
+ $type = file_get_type($result);
+ $result->original_result['bundle'] = $type;
+ }
+ $build['search_results'][] = array(
+ '#weight' => $i++,
+ '#markup' => theme('search_result', array('result' => $result->original_result, 'module' => 'apachesolr_search')),
+ );
+ continue;
+ }
+ $entity_type = isset($result->entity_type) ? $result->entity_type : 'node';
+ $data = ds_entity_view_fallback($entity_type, array($result->entity_id => $result), variable_get('ds_search_view_mode', 'search_result'));
+ // Check that we got an actual result back.
+ if ($data) {
+ $data = reset($data);
+ $data[$result->entity_id]['#weight'] = $i++;
+ $build['search_results'][] = $data[$result->entity_id];
+ }
+ }
+ }
+
+ // Group by type.
+ if (variable_get('ds_search_group_by_type') && variable_get('ds_search_group_by_type_settings') && !empty($build['search_results'])) {
+ _ds_search_group_by_type($build);
+ }
+ else {
+ // Provide zebra striping for results that are not grouped.
+ $parity = 'odd';
+ foreach ($build['search_results'] as $id => $result) {
+ // We need to check on the entity type, as the container
+ // where the object is stored in doesn't necessarily reflect
+ // the name of the entity type.
+ if (!empty($build['search_results'][$id]['#entity_type'])) {
+ switch ($build['search_results'][$id]['#entity_type']) {
+ case 'taxonomy_term':
+ $key = '#term';
+ break;
+
+ default:
+ $key = '#' . $build['search_results'][$id]['#entity_type'];
+ break;
+ }
+
+ $build['search_results'][$id][$key]->ds_search_zebra = $parity;
+ }
+
+ // Let parity change always.
+ $parity = $parity == 'odd' ? 'even' : 'odd';
+ }
+ }
+
+ // Apache Solr multisearch grouping.
+ if (variable_get('ds_search_apachesolr_multisite') && variable_get('ds_search_apachesolr_multisite_group') && variable_get('ds_search_type', 'node') == 'apachesolr_search') {
+ _ds_search_group_by_type_multisearch($build);
+ }
+
+ return theme('ds_search_page', $build);
+}
+
+/**
+ * Helper function to group by type.
+ */
+function _ds_search_group_by_type(&$build) {
+ $settings = variable_get('ds_search_group_by_type_settings');
+ foreach ($build['search_results'] as $id => $result) {
+ if ($settings[$result['#bundle']]['status']) {
+
+ // Type group.
+ if (!isset($build['search_results'][$result['#bundle']])) {
+ $type = $settings[$result['#bundle']]['wrapper'];
+ $title = check_plain(t($settings[$result['#bundle']]['label']));
+ $class = 'group-result group-result-' . strtr($result['#bundle'], '_', '-');
+ $parity[$result['#bundle']] = 'odd';
+ $build['search_results'][$result['#bundle']] = array(
+ '#type' => $type,
+ '#title' => $title,
+ '#weight' => $settings[$result['#bundle']]['weight'],
+ '#attributes' => array(
+ 'class' => array($class),
+ ),
+ );
+
+ if ($type == 'markup') {
+ $build['search_results'][$result['#bundle']]['#prefix'] = '<div class="' . $class . '">' . ((!empty($title)) ? ' <h2>' . $title . '</h2>' : '');
+ $build['search_results'][$result['#bundle']]['#suffix'] = '</div>';
+ }
+ }
+
+ // Move result into the wrapper of its type and unset previous.
+ $build['search_results'][$result['#bundle']][$id] = $result;
+ unset($build['search_results'][$id]);
+
+ // Add the parity to the result to enable correct zebra striping.
+ $build['search_results'][$result['#bundle']][$id]['#node']->ds_search_zebra = $parity[$result['#bundle']];
+ $parity[$result['#bundle']] = $parity[$result['#bundle']] == 'odd' ? 'even' : 'odd';
+ }
+ else {
+
+ // Other group.
+ if (!isset($build['search_results']['ds-other'])) {
+ $title = check_plain(t(variable_get('ds_search_group_by_type_other', 'Other')));
+ $type = variable_get('ds_search_group_by_type_other_wrapper', 'fieldset');
+ $class = 'group-result group-result-other';
+ $parity['ds-other'] = 'odd';
+ $build['search_results']['ds-other'] = array(
+ '#type' => $type,
+ '#title' => $title,
+ '#weight' => 100,
+ '#attributes' => array(
+ 'class' => array($class),
+ ),
+ );
+
+ if ($type == 'markup') {
+ $build['search_results']['ds-other']['#prefix'] = '<div class="' . $class . '">' . ((!empty($title)) ? '<h2>' . $title . '</h2>' : '');
+ $build['search_results']['ds-other']['#suffix'] = '</div>';
+ }
+ }
+
+ // Move result into other wrapper and unset previous.
+ $build['search_results']['ds-other'][$id] = $result;
+ unset($build['search_results'][$id]);
+
+ // Add the parity to the result to enable correct zebra striping.
+ $build['search_results']['ds-other'][$id]['#node']->ds_search_parity = $parity['ds-other'];
+ $parity['ds-other'] = $parity['ds-other'] == 'odd' ? 'even' : 'odd';
+ }
+ }
+}
+
+/**
+ * Helper function to perform grouping on Apache Solr multisearch.
+ */
+function _ds_search_group_by_type_multisearch(&$build) {
+ $site_counter = array();
+ $conf_array = array();
+ $config = explode("\n", variable_get('ds_search_apachesolr_multisite_group_config'));
+ foreach ($config as $weight => $conf) {
+ $conf = trim($conf);
+ if (empty($conf)) {
+ continue;
+ }
+ $site_conf = explode('|', $conf);
+ $conf_array[$site_conf[0]] = array(
+ 'label' => $site_conf[1],
+ 'wrapper' => $site_conf[2],
+ 'weight' => $weight,
+ );
+ }
+
+ // Iterate over results.
+ foreach ($build['search_results'] as $id => $result) {
+ if (!isset($build['search_results'][$result['#site_hash']])) {
+ $class = 'group-result group-result-' . strtr($result['#site_hash'], '_', '-');
+ $build['search_results'][$result['#site_hash']] = array(
+ '#type' => 'fieldset',
+ '#weight' => $conf_array[$result['#site_hash']]['weight'],
+ '#attributes' => array(
+ 'class' => array($class),
+ ),
+ );
+
+ // Create site counter.
+ $site_counter[$result['#site_hash']] = array(
+ 'counter' => 0,
+ 'title' => $conf_array[$result['#site_hash']]['label'],
+ 'type' => $conf_array[$result['#site_hash']]['wrapper'],
+ 'class' => $class,
+ );
+ }
+
+ // Move result into other wrapper and unset previous. Also count for
+ // every site so we can populate @total_per_site later on.
+ $site_counter[$result['#site_hash']]['counter']++;
+ $build['search_results'][$result['#site_hash']][$id] = $result;
+ unset($build['search_results'][$id]);
+ }
+
+ // Site counter.
+ foreach ($site_counter as $hash => $values) {
+ $title = check_plain(t($values['title'], array('!total_per_site' => format_plural($values['counter'], '1 result', '@count results'))));
+ if ($values['type'] == 'div') {
+ $build['search_results'][$hash]['#prefix'] = '<div class="' . $values['class'] . '">' . ((!empty($title)) ? '<h2>' . $title . '</h2>' : '');
+ $build['search_results'][$hash]['#suffix'] = '</div>';
+ }
+ else {
+ $build['search_results'][$hash]['#title'] = $title;
+ }
+ }
+}
+
+/**
+ * Search on behalf of Drupal Core.
+ */
+function node_ds_search_execute($keys = NULL, $conditions = NULL) {
+ // Build matching conditions
+ $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
+ $query->join('node', 'n', 'n.nid = i.sid');
+ $query
+ ->condition('n.status', 1)
+ ->addTag('node_access')
+ ->searchExpression($keys, 'node');
+
+ // Language.
+ if (variable_get('ds_search_language', FALSE)) {
+ global $language;
+ $query->condition('n.language', $language->language);
+ }
+
+ // Insert special keywords.
+ $query->setOption('type', 'n.type');
+ $query->setOption('language', 'n.language');
+ if ($query->setOption('term', 'ti.tid')) {
+ $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid');
+ }
+ // Only continue if the first pass query matches.
+ if (!$query->executeFirstPass()) {
+ return array();
+ }
+
+ // Add the ranking expressions.
+ _node_rankings($query);
+
+ $limit = variable_get('ds_search_node_limit', 10);
+ $query->limit($limit);
+
+ // Load results.
+ $find = $query->execute();
+ $results = array();
+ foreach ($find as $item) {
+ $node = node_load($item->sid);
+ $node->entity_type = 'node';
+ $node->entity_id = $item->sid;
+ $node->search_extra = module_invoke_all('node_search_result', $node);
+ // Only build a node search snippet if this field is actually being used.
+ $fields = ds_get_field_settings($node->entity_type, $node->type, 'search_result');
+ if (!empty($fields) && isset($fields['search_snippet'])) {
+ // Because the 'search_result' display is being built right now (and because it is being overridden by Display Suite),
+ // it is necessary to use the 'search_index' display for rendered field content.
+ $build = node_view($node, 'search_index');
+ unset($build['#theme']);
+ // Render the node.
+ $rendered = drupal_render($build);
+ // Attach extra information to the rendered output.
+ $rendered .= ' ' . implode('', $node->search_extra);
+ // Generate the snippet based on rendered content.
+ $node->snippet = search_excerpt($keys, $rendered);
+ }
+ $results[$item->sid] = $node;
+ }
+ return $results;
+}
+
+/**
+ * Override search results page for users.
+ */
+if (variable_get('ds_user_override_search_page', FALSE)) {
+ function user_search_page($results) {
+ $build = array('#type' => 'user');
+ global $base_url;
+
+ ds_build_shared_page_variables($build);
+
+ $uids = array();
+ foreach ($results as $key => $result) {
+ $uid = FALSE;
+
+ // Try to get the uid from the $result['link'];
+ $path = explode('/', $result['link']);
+ $uid = end($path);
+
+ // Lookup drupal path, we are most likely having an alias.
+ if (!is_numeric($uid)) {
+ $path = str_replace($base_url . '/', '', $result['link']);
+ $alias = drupal_get_normal_path($path);
+ $path = explode('/', $alias);
+ $uid = end($path);
+ }
+
+ if (is_numeric($uid)) {
+ $uids[] = $uid;
+ }
+
+ // Return all uids.
+ if (!empty($uids)) {
+ $accounts = user_load_multiple($uids);
+ foreach ($accounts as $account) {
+ $build['search_results'][$account->uid] = user_view($account, variable_get('ds_search_view_mode', 'search_result'));
+ }
+ }
+ }
+
+ // Return output.
+ return theme('ds_search_page', $build);
+ }
+}
+
+/**
+ * Build shared page variables.
+ *
+ * @param $build
+ * The build array.
+ */
+function ds_build_shared_page_variables(&$build) {
+ // Search results title.
+ if (variable_get('ds_search_show_title', FALSE)) {
+ $build['search_title'] = array('#markup' => '<h2>' . t('Search results') . '</h2>');
+ }
+
+ // Extra variables.
+ if (variable_get('ds_search_variables', 'none') != 'none') {
+ $build['search_extra'] = array('#markup' => '<div class="ds-search-extra">' . ds_search_extra_variables(arg(2)) . '</div>');
+ }
+
+ // Search results.
+ $build['search_results'] = array();
+
+ // Pager.
+ $build['search_pager'] = array('#markup' => theme('pager', array('tags' => NULL)));
+
+ // CSS and JS.
+ if (variable_get('ds_search_highlight', FALSE)) {
+ drupal_add_css(drupal_get_path('module', 'ds_search') . '/css/ds_search.theme.css');
+ drupal_add_js(drupal_get_path('module', 'ds_search') . '/js/ds_search.js');
+ drupal_add_js(array(
+ 'ds_search' => array(
+ 'selector' => check_plain(variable_get('ds_search_highlight_selector', '.view-mode-search_result')),
+ 'search' => check_plain(arg(2)),
+ ),
+ ), 'setting');
+ }
+}
+
+/**
+ * Return the extra variables.
+ */
+function ds_search_extra_variables($arg_keys = NULL) {
+ $type = variable_get('ds_search_variables', 'none');
+
+ // Define the number of results being shown on a page.
+ // We rely on the apache solr rows for now.
+ $items_per_page = variable_get('apachesolr_rows', 10);
+
+ // Get the current page.
+ $current_page = isset($_REQUEST['page']) ? $_REQUEST['page']+1 : 1;
+
+ // Get the total number of results from the $GLOBALS.
+ $total = isset($GLOBALS['pager_total_items'][0]) ? $GLOBALS['pager_total_items'][0] : 0;
+
+ // Perform calculation
+ $start = $items_per_page * $current_page - ($items_per_page - 1);
+ $end = $items_per_page * $current_page;
+ if ($end > $total) $end = $total;
+
+ // Get the search keys.
+ $keys = empty($arg_keys) ? trim(ds_search_get_keys()) : $arg_keys;
+
+ // Send the right extra variable.
+ switch ($type) {
+ case 'search_totals':
+ return format_plural($total, 'One result', 'Total results: @total.', array('@total' => $total));
+ break;
+
+ case 'search_totals_plus_keywords':
+ return format_plural($total, 'Your search for "<strong>@search</strong>" gave back 1 result.',
+ 'Your search for "<strong>@search</strong>" gave back @count results.',
+ array('@search' => $keys));
+ break;
+
+ case 'search_totals_from_to_end':
+ return format_plural($total, 'Displaying @start - @end of 1 result.',
+ 'Displaying @start - @end of @count results.',
+ array('@start' => $start, '@end' => $end));
+ break;
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_search_form_search_form_alter(&$form, $form_state) {
+ if (variable_get('ds_search_type', 'node') == 'node' && isset($form['module']) && $form['module']['#value'] == 'ds_search') {
+ if (variable_get('ds_search_node_form_alter', FALSE)) {
+ $form['module']['#value'] = 'node';
+ node_form_search_form_alter($form, $form_state);
+ }
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_search_form_apachesolr_search_custom_page_search_form_alter(&$form, $form_state) {
+ if (variable_get('ds_search_apachesolr_hide_current_filters', FALSE)) {
+ $form['basic']['retain-filters']['#type'] = 'value';
+ $form['basic']['retain-filters']['#value'] = variable_get('ds_search_apachesolr_current_filters_default', FALSE);
+ }
+}
+
+/**
+ * Implements hook_apachesolr_index_document_build().
+ */
+function ds_search_apachesolr_index_document_build(ApacheSolrDocument $document, $entity) {
+ // Apache Solr multisite support. Render the node already here.
+ if (variable_get('ds_search_apachesolr_multisite')) {
+ ob_start();
+ $element = node_view($entity, variable_get('ds_search_view_mode', 'search_result'));
+ print drupal_render($element);
+ $output = ob_get_contents();
+ ob_end_clean();
+ $document->addField('tm_ds_search_result', $output);
+ }
+}
+
+/**
+ * Implements hook_apachesolr_query_alter().
+ */
+function ds_search_apachesolr_query_alter($query) {
+
+ // Apache Solr multisite support.
+ if (variable_get('ds_search_apachesolr_multisite') && variable_get('ds_search_type', 'node') == 'apachesolr_search') {
+ // Site hash.
+ $query->addParam('fl', 'hash');
+ // Rendered search result.
+ $query->addParam('fl', 'tm_ds_search_result');
+
+ // Make sure this site's search results are first.
+ if (variable_get('ds_search_apachesolr_multisite_boost')) {
+ $hash = apachesolr_site_hash();
+ $query->addParam('bq', 'hash:' . $hash . '^' . variable_get('ds_search_apachesolr_multisite_boost_nr', 100));
+ }
+ }
+
+ // Search per language.
+ if (variable_get('ds_search_language', FALSE)) {
+ global $language;
+ $query->addFilter('ss_language', $language->language);
+ }
+}
+
+/**
+ * Process results on behalf of Apache Solr.
+ */
+function ds_search_process_results($results) {
+
+ $processed_results = array();
+
+ if (is_array($results) && !empty($results)) {
+ foreach ($results as $result) {
+
+ $load = entity_load($result['fields']['entity_type'], array($result['fields']['entity_id']));
+ $entity = reset($load);
+ if (!$entity) {
+ watchdog('ds_search', "Empty entity loaded from results, possibly corrupt solr index? (@entity_type, @id)", array('@entity_type' => $result['fields']['entity_type'], '@id' => $result['fields']['entity_id']), WATCHDOG_DEBUG);
+ continue;
+ }
+
+ // Add the snippet, url and extra info on the object.
+ $entity->search_snippet = $result['snippet'];
+ $entity->search_extra = $result['extra'];
+ $entity->search_as_url = $result['fields']['url'];
+ $entity->entity_type = $result['fields']['entity_type'];
+ $entity->entity_id = $result['fields']['entity_id'];
+
+ // Add the original result on the entity too in case this is a file
+ // entity. Attachments have brittle support as the file entity only
+ // exists in media 1.x or file entity 2.x. Because of that, we're
+ // most likely will render files through theme('search_result').
+ if ($result['fields']['entity_type'] == 'file') {
+ $entity->original_result = $result;
+ }
+
+ // Apache Solr multisite support.
+ if (variable_get('ds_search_apachesolr_multisite')) {
+
+ // Pass along the uri path in case some people want to
+ // do cool stuff themselves.
+ $entity->uri['path'] = $entity->search_as_url;
+ $entity->uri['options'] = array();
+
+ // Prefix with site hash so we don't override same id's.
+ $markup = $result['fields']['tm_ds_search_result'][0];
+ $processed_results[$result['fields']['id'] . '-' . $result['fields']['entity_id']] = array(
+ '#markup' => $markup,
+ '#site_hash' => $result['fields']['hash'],
+ );
+ }
+ else {
+ $processed_results[$result['fields']['id'] . '-' . $result['fields']['entity_id']] = $entity;
+ }
+ }
+ }
+
+ return $processed_results;
+}
+
+/**
+ * Implements hook_apachesolr_search_page_alter(&$build, $search_page).
+ */
+function ds_search_apachesolr_search_page_alter(&$build, $search_page) {
+ if (!empty($build['search_results']['#results'])) {
+ $results = ds_search_process_results($build['search_results']['#results']);
+ $build['search_results'] = ds_search_search_page($results);
+ }
+}
diff --git a/sites/all/modules/ds/modules/ds_search/includes/ds_search.admin.inc b/sites/all/modules/ds/modules/ds_search/includes/ds_search.admin.inc
new file mode 100644
index 000000000..60390af6b
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_search/includes/ds_search.admin.inc
@@ -0,0 +1,362 @@
+<?php
+
+/**
+ * @file
+ * Administrative functions for Display Suite search.
+ */
+
+/**
+ * Menu callback: Display Suite search settings.
+ */
+function ds_search_settings($form, $form_state) {
+
+ $form['general'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('General'),
+ );
+
+ $engine_options = array();
+ foreach (module_implements('search_info') as $module) {
+ if ($module != 'ds_search' && $module != 'user') {
+ $engine_options[$module] = drupal_ucfirst(str_replace('_', ' ', $module));
+ }
+ }
+
+ $form['general']['ds_search_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Search engine'),
+ '#description' => t('Select the search engine as the query backend.'),
+ '#options' => $engine_options,
+ '#default_value' => variable_get('ds_search_type', 'node'),
+ );
+
+ $form['general']['ds_search_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Search path'),
+ '#field_prefix' => 'search/',
+ '#description' => t('Make sure you don\'t override an existing search path.'),
+ '#default_value' => variable_get('ds_search_path', 'content'),
+ '#required' => TRUE,
+ );
+
+ $entity_view_modes = ds_entity_view_modes('node');
+ foreach ($entity_view_modes as $key => $view_mode) {
+ $view_mode_options[$key] = $view_mode['label'];
+ }
+ $form['general']['ds_search_view_mode'] = array(
+ '#type' => 'select',
+ '#title' => t('View mode'),
+ '#description' => 'Select another view mode in favor of the default search view mode.',
+ '#default_value' => variable_get('ds_search_view_mode', 'search_result'),
+ '#options' => $view_mode_options,
+ );
+
+ $form['general']['ds_search_file_render'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use default search theming for files'),
+ '#description' => t('File/attachment results are best supported when the file entity module is enabled. Toggle the checkbox in case it is not available or you want to use default search theming.'),
+ '#default_value' => variable_get('ds_search_file_render', FALSE),
+ );
+
+ $form['general']['ds_search_variables'] = array(
+ '#type' => 'radios',
+ '#options' => array(
+ 'none' => t('None'),
+ 'search_totals' => t('Total results'),
+ 'search_totals_plus_keywords' => t('Total results + keywords'),
+ 'search_totals_from_to_end' => t('Totals + start to end')
+ ),
+ '#title' => t('Extra variables'),
+ '#description' => t('Choose an extra variable to display on the results screen.'),
+ '#default_value' => variable_get('ds_search_variables', 'none'),
+ );
+
+ $form['general']['ds_search_show_title'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show title'),
+ '#description' => t('Display the "Search results" title.'),
+ '#default_value' => variable_get('ds_search_show_title'),
+ );
+
+ $form['general']['ds_search_language'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Language'),
+ '#description' => t('Search in the language the site is currently in. Enable this if you have at least 2 languages.'),
+ '#default_value' => variable_get('ds_search_language'),
+ );
+
+ $form['general']['ds_search_highlight'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Highlight search word'),
+ '#description' => t('Use jQuery to highlight the word in the results.'),
+ '#default_value' => variable_get('ds_search_highlight'),
+ );
+
+ $form['general']['ds_search_highlight_selector'] = array(
+ '#type' => 'textfield',
+ '#title' => t('HTML selector'),
+ '#description' => t('Enter the css selector, if not sure, leave this by default.'),
+ '#default_value' => variable_get('ds_search_highlight_selector', '.view-mode-search_result'),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="ds_search_highlight"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $form['general']['ds_search_group_by_type'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Group by type.'),
+ '#description' => t('Group the search results per page by type. Note that this only works if you only display nodes on the search result page.'),
+ '#default_value' => variable_get('ds_search_group_by_type'),
+ );
+
+ // Group by type order.
+ $form['ds_search_group_by_type_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Group by type settings'),
+ '#description' => t('Set the order of the node types which can be grouped. Node types which are not enabled will be showed into a "Other group". Note that this does not work with Apache Solr multisite support.'),
+ '#theme' => 'ds_search_group_by_type_settings',
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="ds_search_group_by_type"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $node_types = node_type_get_names();
+ $ds_search_group_by_type_settings = variable_get('ds_search_group_by_type_settings');
+ $wrapper_options = array(
+ 'fieldset' => t('Fieldset'),
+ 'markup' => t('Div with H3 headline'),
+ );
+ foreach ($node_types as $name => $label) {
+
+ $form['ds_search_group_by_type_settings'][$name]['name'] = array(
+ '#markup' => check_plain($label),
+ );
+
+ $form['ds_search_group_by_type_settings'][$name]['status'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Weight for @title', array('@title' => $label)),
+ '#title_display' => 'invisible',
+ '#default_value' => isset($ds_search_group_by_type_settings[$name]) ? $ds_search_group_by_type_settings[$name]['status'] : TRUE,
+ '#parents' => array('ds_search_group_by_type_settings', $name, 'status'),
+ );
+
+ $form['ds_search_group_by_type_settings'][$name]['label'] = array(
+ '#type' => 'textfield',
+ '#size' => 30,
+ '#title' => t('Group label for @title', array('@title' => $label)),
+ '#title_display' => 'invisible',
+ '#description' => t('Group label. The label will be translatable. Leave empty for no label.'),
+ '#default_value' => isset($ds_search_group_by_type_settings[$name]) ? $ds_search_group_by_type_settings[$name]['label'] : t('Results for @type', array('@type' => drupal_strtolower($node_types[$name]))),
+ '#parents' => array('ds_search_group_by_type_settings', $name, 'label'),
+ );
+
+ $form['ds_search_group_by_type_settings'][$name]['wrapper'] = array(
+ '#type' => 'select',
+ '#options' => $wrapper_options,
+ '#title' => t('Wrapper for @title', array('@title' => $label)),
+ '#title_display' => 'invisible',
+ '#default_value' => isset($ds_search_group_by_type_settings[$name]) ? $ds_search_group_by_type_settings[$name]['wrapper'] : 'fieldset',
+ '#parents' => array('ds_search_group_by_type_settings', $name, 'wrapper'),
+ );
+
+ $weight = isset($ds_search_group_by_type_settings[$name]) ? $ds_search_group_by_type_settings[$name]['weight'] : 0;
+ $form['ds_search_group_by_type_settings'][$name]['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight for @title', array('@title' => $label)),
+ '#title_display' => 'invisible',
+ '#delta' => 30,
+ '#default_value' => $weight,
+ '#parents' => array('ds_search_group_by_type_settings', $name, 'weight'),
+ );
+ $form['ds_search_group_by_type_settings'][$name]['#weight'] = $weight;
+ }
+
+ $form['ds_search_group_by_type_settings']['ds_search_group_by_type_other'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Other label'),
+ '#description' => t('The label of the other group. Leave empty for no label.'),
+ '#default_value' => variable_get('ds_search_group_by_type_other', 'Other'),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="ds_search_group_by_type"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ $form['ds_search_group_by_type_settings']['ds_search_group_by_type_other_wrapper'] = array(
+ '#type' => 'select',
+ '#options' => $wrapper_options,
+ '#title' => t('Other wrapper'),
+ '#description' => t('The wrapper of the other group.'),
+ '#default_value' => variable_get('ds_search_group_by_type_other_wrapper', 'fieldset'),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="ds_search_group_by_type"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $form['node'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Drupal core'),
+ '#states' => array(
+ 'visible' => array(
+ 'select[name="ds_search_type"]' => array('value' => 'node'),
+ ),
+ ),
+ );
+
+ $form['node']['ds_search_node_form_alter'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Advanced'),
+ '#description' => t('Enable the advanced search form.'),
+ '#default_value' => variable_get('ds_search_node_form_alter'),
+ );
+
+ $form['node']['ds_search_node_limit'] = array(
+ '#type' => 'textfield',
+ '#default_value' => variable_get('ds_search_node_limit', 10),
+ '#title' => t('Node search limit'),
+ '#description' => t('The number of items to display per page. Enter 0 for no limit.'),
+ );
+
+ if (module_exists('apachesolr')) {
+
+ $form['apachesolr_search'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Apache Solr'),
+ '#states' => array(
+ 'visible' => array(
+ 'select[name="ds_search_type"]' => array('value' => 'apachesolr_search'),
+ ),
+ ),
+ );
+
+ $form['apachesolr_search']['ds_search_apachesolr_hide_current_filters'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide Retain filters'),
+ '#description' => t('Hide the "Retain current filters" checkbox on search results.'),
+ '#default_value' => variable_get('ds_search_apachesolr_hide_current_filters'),
+ );
+
+ $form['apachesolr_search']['ds_search_apachesolr_current_filters_default'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remember filters'),
+ '#description' => t('Remember the filters when you hide the "Retain current filters" checkbox.'),
+ '#default_value' => variable_get('ds_search_apachesolr_current_filters_default'),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="ds_search_apachesolr_hide_current_filters"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $form['apachesolr_search']['ds_search_apachesolr_multisite'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Multisite support'),
+ '#description' => t('Enable this in case multiple sites share one index. Note that sometimes you need to make sure that your formatters are creating absolute paths for links or images. Implementing hook_url_outbound_alter() is a good option in which you can set $options[\'absolute\'] to TRUE.'),
+ '#default_value' => variable_get('ds_search_apachesolr_multisite'),
+ );
+
+ $form['apachesolr_search']['ds_search_apachesolr_multisite_boost'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Sort by site'),
+ '#description' => t('Enable this to rank the results of the current site first, than all the rest.'),
+ '#default_value' => variable_get('ds_search_apachesolr_multisite_boost'),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="ds_search_apachesolr_multisite"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $form['apachesolr_search']['ds_search_apachesolr_multisite_group'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Group results by site'),
+ '#default_value' => variable_get('ds_search_apachesolr_multisite_group'),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="ds_search_apachesolr_multisite"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $form['apachesolr_search']['ds_search_apachesolr_multisite_group_config'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Group by site configuration'),
+ '#default_value' => variable_get('ds_search_apachesolr_multisite_group_config'),
+ '#description' => t('Enter sites line by line with following configuration: site-hash|label|wrapper. The <em>site-hash</em> is per site which you can get from Solr. The <em>label</em> is translatable per site. Use !total_per_site in this label for number of results per site. Depending on the number, you will either get "1 result" or "x results" which are translatable. The <em>wrapper</em> can either be "fieldset" or "div".'),
+ '#states' => array(
+ 'visible' => array(
+ 'input[name="ds_search_apachesolr_multisite_group"]' => array('checked' => TRUE),
+ 'input[name="ds_search_apachesolr_multisite"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+
+ $active = variable_get('search_active_modules', array('node', 'user'));
+ if (isset($active['user']) && $active['user'] !== 0) {
+ $form['user'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('User search'),
+ );
+
+ $form['user']['ds_user_override_search_page'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Override user search page'),
+ '#description' => t('Toggle this checkbox if you want Display Suite to render user search results.'),
+ '#default_value' => variable_get('ds_user_override_search_page'),
+ );
+ }
+
+ $form = system_settings_form($form);
+ $form['#submit'][] = 'ds_search_settings_submit';
+ return $form;
+}
+
+/**
+ * Submit callback: Search settings submit.
+ */
+function ds_search_settings_submit($form, $form_state) {
+ // Rebuild the menu.
+ variable_set('menu_rebuild_needed', TRUE);
+}
+
+/**
+ * Returns HTML for the Group by type settings.
+ */
+function theme_ds_search_group_by_type_settings($variables) {
+ $element = $variables['element'];
+
+ // Type order (tabledrag).
+ $rows = array();
+ foreach (element_children($element, TRUE) as $name) {
+
+ if (!isset($element[$name]['name'])) {
+ continue;
+ }
+
+ $element[$name]['weight']['#attributes']['class'][] = 'type-order-weight';
+ $rows[] = array(
+ 'data' => array(
+ drupal_render($element[$name]['name']),
+ drupal_render($element[$name]['status']),
+ drupal_render($element[$name]['label']),
+ drupal_render($element[$name]['wrapper']),
+ drupal_render($element[$name]['weight']),
+ ),
+ 'class' => array('draggable'),
+ );
+ }
+
+ $header = array(t('Type'), t('Status'), t('Group label'), t('Wrapper'), t('Weight'));
+ $output = drupal_render_children($element);
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'group-by-order')));
+ drupal_add_tabledrag('group-by-order', 'order', 'sibling', 'type-order-weight', NULL, NULL, TRUE);
+
+ return $output;
+}
diff --git a/sites/all/modules/ds/modules/ds_search/js/ds_search.js b/sites/all/modules/ds/modules/ds_search/js/ds_search.js
new file mode 100644
index 000000000..1b43706a6
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_search/js/ds_search.js
@@ -0,0 +1,69 @@
+
+(function($) {
+
+/**
+ * Highlight words in search results with jQuery.
+ */
+Drupal.behaviors.DSSearchHighlight = {
+ attach: function (context) {
+ var selector = Drupal.settings.ds_search['selector'];
+ var search = Drupal.settings.ds_search['search'];
+ var $selector = $(selector);
+ // Split word.
+
+ words = search.split(' ');
+ for (i = 0; i < words.length; i++) {
+ // Match only valid words. Do not match special search operators or words less than three characters.
+ if (words[i] != '' && words[i] != 'AND' && words[i] != 'OR' && words[i].length >= 3) {
+ $selector.highlight(words[i]);
+ }
+ }
+ }
+};
+
+/*
+
+highlight v3
+
+Highlights arbitrary terms.
+
+<http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html>
+
+MIT license.
+
+Johann Burkard
+<http://johannburkard.de>
+<mailto:jb@eaio.com>
+
+*/
+
+jQuery.fn.highlight = function(pat) {
+ function innerHighlight(node, pat) {
+ var skip = 0;
+ if (node.nodeType == 3) {
+ var pos = node.data.toUpperCase().indexOf(pat);
+ if (pos >= 0) {
+ var spannode = document.createElement('span');
+ spannode.className = 'ds-search-highlight';
+ var middlebit = node.splitText(pos);
+ var endbit = middlebit.splitText(pat.length);
+ var middleclone = middlebit.cloneNode(true);
+ spannode.appendChild(middleclone);
+ middlebit.parentNode.replaceChild(spannode, middlebit);
+ skip = 1;
+ }
+ }
+ else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
+ for (var i = 0; i < node.childNodes.length; ++i) {
+ i += innerHighlight(node.childNodes[i], pat);
+ }
+ }
+ return skip;
+ }
+ return this.each(function() {
+ innerHighlight(this, pat.toUpperCase());
+ });
+};
+
+})(jQuery);
+
diff --git a/sites/all/modules/ds/modules/ds_ui/ds_ui.info b/sites/all/modules/ds/modules/ds_ui/ds_ui.info
new file mode 100644
index 000000000..739884025
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_ui/ds_ui.info
@@ -0,0 +1,12 @@
+name = "Display Suite UI"
+description = "User interface for managing fields, view modes and classes."
+core = "7.x"
+package = "Display Suite"
+dependencies[] = ds
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/modules/ds_ui/ds_ui.module b/sites/all/modules/ds/modules/ds_ui/ds_ui.module
new file mode 100644
index 000000000..087c6cf3b
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_ui/ds_ui.module
@@ -0,0 +1,170 @@
+<?php
+
+/**
+ * @file
+ * Display Suite UI.
+ */
+
+/**
+ * Implements hook_permission().
+ */
+function ds_ui_permission() {
+ return array(
+ 'admin_view_modes' => array(
+ 'title' => t('Administer view modes'),
+ 'description' => t('Manage custom view modes for every entity.')
+ ),
+ 'admin_fields' => array(
+ 'title' => t('Administer fields'),
+ 'description' => t('Manage fields for every entity.')
+ ),
+ 'admin_classes' => array(
+ 'title' => t('Administer classes'),
+ 'description' => t('Manage classes.')
+ ),
+ );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function ds_ui_menu() {
+ $items = array();
+
+ // Custom CSS classes.
+ $items['admin/structure/ds/classes'] = array(
+ 'title' => 'CSS classes',
+ 'description' => 'Define extra CSS class names for regions and fields.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_classes_form'),
+ 'file' => 'includes/ds.classes.inc',
+ 'access arguments' => array('admin_classes'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+
+ // View modes.
+ $items['admin/structure/ds/view_modes'] = array(
+ 'title' => 'View modes',
+ 'description' => 'Manage view modes for all content.',
+ 'page callback' => 'ds_view_modes_list',
+ 'file' => 'includes/ds.view_modes.inc',
+ 'access arguments' => array('admin_view_modes'),
+ 'weight' => -9,
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items['admin/structure/ds/view_modes/manage'] = array(
+ 'title' => 'Add a view mode',
+ 'description' => 'Manage a view mode',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_edit_view_mode_form'),
+ 'file' => 'includes/ds.view_modes.inc',
+ 'access arguments' => array('admin_view_modes'),
+ 'type' => MENU_LOCAL_ACTION,
+ );
+ $items['admin/structure/ds/view_modes/delete'] = array(
+ 'title' => 'Delete a view mode',
+ 'description' => 'Delete a custom view mode.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_delete_view_mode_confirm'),
+ 'file' => 'includes/ds.view_modes.inc',
+ 'access arguments' => array('admin_view_modes'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+ $items['admin/structure/ds/view_modes/revert'] = array(
+ 'title' => 'Revert a view mode',
+ 'description' => 'Revert a view mode.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_revert_view_mode_confirm'),
+ 'file' => 'includes/ds.view_modes.inc',
+ 'access arguments' => array('admin_view_modes'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+
+ // Fields.
+ $items['admin/structure/ds/fields'] = array(
+ 'title' => 'Fields',
+ 'description' => 'Manage fields for all content.',
+ 'page callback' => 'ds_custom_fields_list',
+ 'file' => 'includes/ds.fields.inc',
+ 'access arguments' => array('admin_fields'),
+ 'weight' => -8,
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items['admin/structure/ds/fields/manage'] = array(
+ 'title' => 'Fields edit',
+ 'page callback' => 'ds_custom_manage',
+ 'file' => 'includes/ds.fields.inc',
+ 'access arguments' => array('admin_fields'),
+ 'type' => MENU_CALLBACK,
+ );
+ $items['admin/structure/ds/fields/manage_custom'] = array(
+ 'title' => 'Add a code field',
+ 'description' => 'Manage a field',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_edit_custom_field_form'),
+ 'file' => 'includes/ds.fields.inc',
+ 'access arguments' => array('admin_fields'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'weight' => 0,
+ );
+ $items['admin/structure/ds/fields/manage_ctools'] = array(
+ 'title' => 'Add a dynamic field',
+ 'description' => 'Manage a dynamic field',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_edit_ctools_field_form'),
+ 'file' => 'includes/ds.fields.inc',
+ 'access arguments' => array('admin_fields'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'weight' => 1,
+ );
+ $items['admin/structure/ds/fields/manage_preprocess'] = array(
+ 'title' => 'Add a preprocess field',
+ 'description' => 'Manage a preprocess field',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_edit_preprocess_field_form'),
+ 'file' => 'includes/ds.fields.inc',
+ 'access arguments' => array('admin_fields'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'weight' => 3,
+ );
+ $items['admin/structure/ds/fields/delete'] = array(
+ 'title' => 'Delete a field',
+ 'description' => 'Delete a field.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_delete_field_confirm'),
+ 'file' => 'includes/ds.fields.inc',
+ 'access arguments' => array('admin_fields'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+ $items['admin/structure/ds/fields/revert'] = array(
+ 'title' => 'Revert a custom field',
+ 'description' => 'Revert a custom field.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_revert_field_confirm'),
+ 'file' => 'includes/ds.fields.inc',
+ 'access arguments' => array('admin_fields'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+ $items['admin/structure/ds/fields/manage_block'] = array(
+ 'title' => 'Add a block field',
+ 'description' => 'Manage a block field',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ds_edit_block_field_form'),
+ 'file' => 'includes/ds.fields.inc',
+ 'access arguments' => array('admin_fields'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'weight' => 2,
+ );
+
+ // CTools Modal add field.
+ $items['admin/structure/ds/%ctools_js/add_field/%'] = array(
+ 'title' => 'Add field',
+ 'page callback' => 'ds_ajax_add_field',
+ 'page arguments' => array(3, 5),
+ 'access arguments' => array('admin_fields'),
+ 'file' => 'includes/ds.fields.inc',
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
diff --git a/sites/all/modules/ds/modules/ds_ui/includes/ds.classes.inc b/sites/all/modules/ds/modules/ds_ui/includes/ds.classes.inc
new file mode 100644
index 000000000..6362ed119
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_ui/includes/ds.classes.inc
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Administrative functions for managing CSS classes.
+ */
+
+/**
+ * Menu callback, show CSS classes form.
+ */
+function ds_classes_form($form, $form_state) {
+ $form = array();
+
+ $form['ds_classes_regions'] = array(
+ '#type' => 'textarea',
+ '#title' => t('CSS classes for regions'),
+ '#default_value' => variable_get('ds_classes_regions', ''),
+ '#description' => t('Configure CSS classes which you can add to regions on the "manage display" screens. Add multiple CSS classes line by line.<br />If you want to have a friendly name, separate class and friendly name by |, but this is not required. eg:<br /><em>class_name_1<br />class_name_2|Friendly name<br />class_name_3</em>')
+ );
+
+ if (module_exists('token')) {
+ $tokens_mapping = token_get_entity_mapping();
+ $tokens_list = array();
+ foreach($tokens_mapping as $token_map => $entity_map) {
+ $tokens_list[] = $token_map;
+ }
+ $form['token'] = array(
+ '#type' => 'container',
+ '#theme' => 'token_tree',
+ '#token_types' => $tokens_list,
+ '#dialog' => TRUE,
+ );
+ }
+
+ // Only show field classes if DS extras module is enabled
+ if (module_exists('ds_extras')) {
+ $form['ds_classes_fields'] = array(
+ '#type' => 'textarea',
+ '#title' => t('CSS classes for fields'),
+ '#default_value' => variable_get('ds_classes_fields', ''),
+ '#description' => t('Configure CSS classes which you can add to fields on the "manage display" screens. Add multiple CSS classes line by line.<br />If you want to have a friendly name, separate class and friendly name by |, but this is not required. eg:<br /><em>class_name_1<br />class_name_2|Friendly name<br />class_name_3</em>')
+ );
+ }
+
+ return system_settings_form($form);
+}
diff --git a/sites/all/modules/ds/modules/ds_ui/includes/ds.fields.inc b/sites/all/modules/ds/modules/ds_ui/includes/ds.fields.inc
new file mode 100644
index 000000000..188463902
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_ui/includes/ds.fields.inc
@@ -0,0 +1,623 @@
+<?php
+
+/**
+ * @file
+ * Administrative functions for managing custom fields for every entity.
+ */
+
+/**
+ * Shows the list of custom fields.
+ */
+function ds_custom_fields_list() {
+ $output = '';
+
+ ctools_include('export');
+ $custom_fields = ctools_export_crud_load_all('ds_fields');
+ if (!empty($custom_fields)) {
+
+ $rows = array();
+ foreach ($custom_fields as $field_key => $field_value) {
+ $row = array();
+ $row[] = check_plain($field_value->label);
+ $row[] = ds_field_human_name($field_value->field_type);
+ $row[] = $field_key;
+ $row[] = ucwords(str_replace('_', ' ', implode(', ', $field_value->entities)));
+ $operations = l(t('Edit'), 'admin/structure/ds/fields/manage/' . $field_key, array('alias' => TRUE));
+
+ if ($field_value->export_type == 3) {
+ $operations .= ' - ' . l(t('Revert'), 'admin/structure/ds/fields/revert/' . $field_key, array('alias' => TRUE));
+ }
+ elseif ($field_value->export_type == 1) {
+ $operations .= ' - ' . l(t('Delete'), 'admin/structure/ds/fields/delete/' . $field_key, array('alias' => TRUE));
+ }
+ $row[] = $operations;
+ $rows[] = $row;
+ }
+
+ $table = array(
+ 'header' => array(
+ 'Label',
+ 'Type',
+ 'Machine name',
+ 'Entities',
+ 'Operations',
+ ),
+ 'rows' => $rows,
+ );
+
+ $output = theme('table', $table);
+ }
+ else {
+ $output = t('No custom fields have been defined.');
+ }
+
+ return $output;
+}
+
+/**
+ * Return the human name of a field.
+ *
+ * @return $human_name
+ * The human name of a field.
+ */
+function ds_field_human_name($type) {
+
+ switch ($type) {
+ case DS_FIELD_TYPE_CODE:
+ return t('Code field');
+ case DS_FIELD_TYPE_BLOCK:
+ return t('Block field');
+ case DS_FIELD_TYPE_CTOOLS:
+ return t('Dynamic field');
+ case DS_FIELD_TYPE_PREPROCESS:
+ return t('Preprocess field');
+ }
+
+ // Fallback
+ return t('Unknown');
+}
+
+/**
+ * Manage a field. This will redirect to the exact form.
+ */
+function ds_custom_manage($field_key = '') {
+ $redirect = '';
+ ctools_include('export');
+ $custom_fields = ctools_export_crud_load_all('ds_fields');
+ if (isset($custom_fields[$field_key])) {
+ $field = $custom_fields[$field_key];
+ switch ($field->field_type) {
+ case DS_FIELD_TYPE_CODE:
+ $redirect = 'admin/structure/ds/fields/manage_custom/' . $field_key;
+ break;
+
+ case DS_FIELD_TYPE_BLOCK:
+ $redirect = 'admin/structure/ds/fields/manage_block/' . $field_key;
+ break;
+
+ case DS_FIELD_TYPE_CTOOLS:
+ $redirect = 'admin/structure/ds/fields/manage_ctools/' . $field_key;
+ break;
+
+ case DS_FIELD_TYPE_PREPROCESS:
+ $redirect = 'admin/structure/ds/fields/manage_preprocess/' . $field_key;
+ break;
+
+ default:
+ drupal_set_message(t('Field not found'));
+ $redirect = 'admin/structure/ds/fields';
+ break;
+ }
+ }
+
+ drupal_goto($redirect);
+}
+
+/**
+ * Shared form for all fields.
+ */
+function ds_shared_form(&$form, $field) {
+ ctools_include('export');
+ $custom_fields = ctools_export_crud_load_all('ds_fields');
+ if (isset($custom_fields[$field])) {
+ $field = $custom_fields[$field];
+ if (!isset($field->ui_limit)) {
+ $field->ui_limit = '';
+ }
+ }
+ else {
+ $field = new stdClass;
+ $field->label = '';
+ $field->field = '';
+ $field->ui_limit = '';
+ $field->entities = array();
+ $field->properties = array();
+ }
+
+ $form['name'] = array(
+ '#title' => t('Label'),
+ '#type' => 'textfield',
+ '#default_value' => $field->label,
+ '#description' => t('The human-readable label of the field.'),
+ '#maxlength' => 128,
+ '#required' => TRUE,
+ '#size' => 30,
+ );
+
+ $form['field'] = array(
+ '#type' => 'machine_name',
+ '#default_value' => $field->field,
+ '#maxlength' => 32,
+ '#description' => t('The machine-readable name of this field. This name must contain only lowercase letters and underscores. This name must be unique.'),
+ '#disabled' => !empty($field->field),
+ '#machine_name' => array(
+ 'exists' => 'ds_field_unique',
+ ),
+ );
+
+ $entity_options = array();
+ $entities = entity_get_info();
+ foreach ($entities as $entity_type => $entity_info) {
+ if ((isset($entity_info['fieldable']) && $entity_info['fieldable']) || $entity_type == 'ds_views') {
+ $entity_options[$entity_type] = drupal_ucfirst(str_replace('_', ' ', $entity_type));
+ }
+ }
+ $form['entities'] = array(
+ '#title' => t('Entities'),
+ '#description' => t('Select the entities for which this field will be made available.'),
+ '#type' => 'checkboxes',
+ '#required' => TRUE,
+ '#options' => $entity_options,
+ '#default_value' => $field->entities,
+ );
+
+ $form['ui_limit'] = array(
+ '#title' => t('Limit field'),
+ '#description' => t('Limit this field on field UI per bundles and/or view modes. The values are in the form of $bundle|$view_mode, where $view_mode may be either a view mode set to use custom settings, or \'default\'. You may use * to select all, e.g article|*, *|full or *|*. Enter one value per line.'),
+ '#type' => 'textarea',
+ '#default_value' => $field->ui_limit,
+ '#element_validate' => array('ds_ui_limit_validate'),
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#weight' => 100,
+ );
+
+ // Validate & submit are also the same.
+ $form['#validate'][] = 'ds_shared_form_validate';
+ $form['#submit'][] = 'ds_shared_form_submit';
+
+ return $field;
+}
+
+/**
+ * Element validation function for ui limit field.
+ */
+function ds_ui_limit_validate($element, &$form_state, $form) {
+ // Get all enabled entity types.
+ $entity_types = array_filter($form_state['values']['entities']);
+
+ if (!empty($element['#value'])) {
+ $ui_limit = $element['#value'];
+
+ $lines = explode("\n", str_replace("\r", "\n", $ui_limit));
+ // Ensure that all strings are trimmed and filter out empty lines.
+ $lines = array_filter(array_map('trim', $lines));
+
+ $error = FALSE;
+ foreach ($lines as $line) {
+ // Each line should hold a pipe character to seperate bundle and view_mode.
+ if (strpos($line, '|') === FALSE) {
+ $error = TRUE;
+ continue;
+ }
+
+ list($bundle, $view_mode) = explode('|', $line);
+
+ if (empty($bundle) || empty($view_mode) || !_ds_check_existing_ui_limit($entity_types, $bundle, $view_mode)) {
+ $error = TRUE;
+ }
+ }
+ if ($error) {
+ form_error($element, t('The values are in the form of $bundle|$view_mode, where $view_mode may be either a view mode set to use custom settings, or \'default\'.'));
+ }
+
+ // Set trimmed and validated entry back as value.
+ form_set_value($element, implode("\n", $lines), $form_state);
+ }
+}
+
+/**
+ * Helper function to check if bundle + view_mode combination exists.
+ */
+function _ds_check_existing_ui_limit($entity_types, $bundle, $view_mode) {
+ $exists = FALSE;
+ foreach ($entity_types as $entity_type) {
+ $info = entity_get_info($entity_type);
+
+ // Combine allowed bundles and entity specific ones.
+ $bundle_allowed = array('*');
+ $bundles = array_merge($bundle_allowed, array_keys($info['bundles']));
+
+ // Combine allowed view_modes and entity specific ones.
+ $view_mode_allowed = array('*', 'default');
+ $view_modes = array_merge($view_mode_allowed, array_keys($info['view modes']));
+
+ if (in_array($bundle, $bundles) &&
+ in_array($view_mode, $view_modes)) {
+
+ $exists = TRUE;
+ break;
+ }
+ }
+ if (!$exists) {
+ drupal_set_message(t('Incorrect field limit combination: @bundle|@view_mode', array('@bundle' => $bundle, '@view_mode' => $view_mode)), 'error');
+ }
+
+ return $exists;
+}
+
+/**
+ * Return whether a field machine name is unique.
+ */
+function ds_field_unique($name) {
+ ctools_include('export');
+ $custom_fields = ctools_export_crud_load_all('ds_fields');
+ $value = strtr($name, array('-' => '_'));
+ return isset($custom_fields[$value]) ? TRUE : FALSE;
+}
+
+/**
+ * Shared field form validation.
+ */
+function ds_shared_form_validate($form, &$form_state) {
+ $field = new stdClass;
+ $field->properties = array();
+ $field->field = $form_state['values']['field'];
+ $field->label = $form_state['values']['name'];
+ $field->ui_limit = $form_state['values']['ui_limit'];
+
+ $entities = $form_state['values']['entities'];
+ foreach ($entities as $key => $value) {
+ if ($key !== $value) {
+ unset($entities[$key]);
+ }
+ }
+ $field->entities = $entities;
+ $form_state['field'] = $field;
+}
+
+/**
+ * Save any field.
+ */
+function ds_shared_form_submit($form, &$form_state) {
+ $field = $form_state['field'];
+
+ // Delete previous field configuration.
+ db_delete('ds_fields')
+ ->condition('field', $field->field)
+ ->execute();
+
+ // Save field and clear ds_fields.
+ drupal_write_record('ds_fields', $field);
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+
+ // Redirect.
+ $form_state['redirect'] = 'admin/structure/ds/fields';
+ drupal_set_message(t('The field %field has been saved', array('%field' => $field->label)));
+}
+
+/**
+ * Manage a custom field.
+ */
+function ds_edit_custom_field_form($form, &$form_state, $custom_field = '') {
+ drupal_set_title(empty($custom_field) ? t('Add a code field') : t('Edit code field'));
+
+ $custom_field = ds_shared_form($form, $custom_field);
+
+ $form['code'] = array(
+ '#type' => 'text_format',
+ '#title' => t('Field code'),
+ '#default_value' => isset($custom_field->properties['code']['value']) ? $custom_field->properties['code']['value'] : '',
+ '#format' => isset($custom_field->properties['code']['format']) ? $custom_field->properties['code']['format'] : 'ds_code',
+ '#base_type' => 'textarea',
+ '#required' => TRUE,
+ );
+
+ $form['use_token'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Token'),
+ '#description' => t('Toggle this checkbox if you are using tokens in this field.'),
+ '#default_value' => isset($custom_field->properties['use_token']) ? $custom_field->properties['use_token'] : '',
+ );
+
+ // Token support.
+ if (module_exists('token')) {
+
+ $form['tokens'] = array(
+ '#title' => t('Tokens'),
+ '#type' => 'container',
+ '#states' => array(
+ 'invisible' => array(
+ 'input[name="use_token"]' => array('checked' => FALSE),
+ ),
+ ),
+ );
+ $form['tokens']['help'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => 'all',
+ '#global_types' => FALSE,
+ '#dialog' => TRUE,
+ );
+ }
+ else {
+ $form['use_token']['#description'] = t('Toggle this checkbox if you are using tokens in this field. If the token module is installed, you get a nice list of all tokens available in your site.');
+ }
+
+ $form['#validate'][] = 'ds_custom_field_form_validate';
+
+ return $form;
+}
+
+/**
+ * Custom field form validation.
+ */
+function ds_custom_field_form_validate($form, &$form_state) {
+ $form_state['field']->field_type = DS_FIELD_TYPE_CODE;
+ $form_state['field']->properties['code'] = $form_state['values']['code'];
+ $form_state['field']->properties['use_token'] = $form_state['values']['use_token'];
+}
+
+/**
+ * Manage a CTools field.
+ */
+function ds_edit_ctools_field_form($form, &$form_state, $ctools_field = '') {
+ drupal_set_title(empty($ctools_field) ? t('Add a dynamic field') : t('Edit dynamic field'));
+
+ $custom_field = ds_shared_form($form, $ctools_field);
+ $form['info'] = array(
+ '#markup' => t('The content of this field is configurable on the "Manage display" screens.'),
+ '#weight' => -10,
+ );
+ $form['#validate'][] = 'ds_ctools_field_form_validate';
+ return $form;
+}
+
+/**
+ * CTools field form validation.
+ */
+function ds_ctools_field_form_validate($form, &$form_state) {
+ $form_state['field']->field_type = DS_FIELD_TYPE_CTOOLS;
+ $form_state['field']->properties['default'] = array();
+ $form_state['field']->properties['settings'] = array('show_title' => array('type' => 'checkbox'), 'title_wrapper' => array('type' => 'textfield', 'description' => t('Eg: h1, h2, p')), 'ctools' => array('type' => 'ctools'));
+}
+
+/**
+ * Manage a Preprocess field.
+ */
+function ds_edit_preprocess_field_form($form, &$form_state, $preprocess_field = '') {
+ drupal_set_title(empty($preprocess_field) ? t('Add a preprocess field') : t('Edit preprocess field'));
+
+ $custom_field = ds_shared_form($form, $preprocess_field);
+ $form['info'] = array(
+ '#markup' => t('The machine name of this field must reflect the key in the variables, e.g. "submitted". So in most cases, it is very likely you will have to manually edit the machine name as well, which can not be changed anymore after saving. Note that this field type works best on Nodes.'),
+ '#weight' => -10,
+ );
+ $form['#validate'][] = 'ds_preprocess_field_form_validate';
+ return $form;
+}
+
+/**
+ * CTools field form validation.
+ */
+function ds_preprocess_field_form_validate($form, &$form_state) {
+ $form_state['field']->field_type = DS_FIELD_TYPE_PREPROCESS;
+}
+
+/**
+ * Manage a custom block.
+ */
+function ds_edit_block_field_form($form, &$form_state, $custom_block = '') {
+ drupal_set_title(empty($custom_block) ? t('Add a block field') : t('Edit block field'));
+
+ $custom_block = ds_shared_form($form, $custom_block);
+
+ $blocks = array();
+ foreach (module_implements('block_info') as $module) {
+ $module_blocks = module_invoke($module, 'block_info');
+ if ($module_blocks) {
+ foreach ($module_blocks as $module_key => $info) {
+ $blocks[drupal_ucfirst($module)][$module . '|' . $module_key] = $info['info'];
+ }
+ }
+ }
+ ksort($blocks);
+ foreach($blocks as &$subarray) {
+ asort($subarray);
+ }
+
+ $form['block_identity']['block'] = array(
+ '#type' => 'select',
+ '#options' => $blocks,
+ '#title' => t('Block'),
+ '#required' => TRUE,
+ '#default_value' => isset($custom_block->properties['block']) ? $custom_block->properties['block'] : '',
+ );
+ $form['block_identity']['block_render'] = array(
+ '#type' => 'select',
+ '#options' => array(
+ DS_BLOCK_TEMPLATE => t('Default'),
+ DS_BLOCK_TITLE_CONTENT => t('Show block title + content'),
+ DS_BLOCK_CONTENT => t('Show only block content'),
+ ),
+ '#title' => t('Layout'),
+ '#required' => TRUE,
+ '#default_value' => isset($custom_block->properties['block_render']) ? $custom_block->properties['block_render'] : '',
+ );
+
+ $form['#validate'][] = 'ds_block_field_form_validate';
+
+ return $form;
+}
+
+/**
+ * Custom field form validation.
+ */
+function ds_block_field_form_validate($form, &$form_state) {
+ $form_state['field']->field_type = DS_FIELD_TYPE_BLOCK;
+ $form_state['field']->properties = array();
+ $form_state['field']->properties['block'] = $form_state['values']['block'];
+ $form_state['field']->properties['block_render'] = $form_state['values']['block_render'];
+}
+
+/**
+ * Menu callback: Confirmation custom field delete form.
+ */
+function ds_delete_field_confirm($form, &$form_state, $field = '') {
+ return ds_remove_fields_form($form, $form_state, $field, 'delete');
+}
+
+/**
+ * Menu callback: Confirmation custom field delete form.
+ */
+function ds_revert_field_confirm($form, &$form_state, $field = '') {
+ return ds_remove_fields_form($form, $form_state, $field, 'revert');
+}
+
+/**
+ * Confirmation delete or revert form.
+ */
+function ds_remove_fields_form($form, &$form_state, $field = '', $action = 'delete') {
+ ctools_include('export');
+ $custom_fields = ctools_export_crud_load_all('ds_fields');
+ if (isset($custom_fields[$field])) {
+ $field = $custom_fields[$field];
+ $form['#ds_field'] = $field;
+ return confirm_form($form,
+ t('Are you sure you want to ' . $action . ' %field?', array('%field' => $field->label)),
+ 'admin/structure/ds/fields',
+ t('This action cannot be undone.'),
+ t(drupal_ucfirst($action)),
+ t('Cancel')
+ );
+ }
+ else {
+ drupal_set_message(t('Unknown field'));
+ drupal_goto('admin/structure/ds/fields');
+ }
+}
+
+/**
+ * Submit callback: confirmed delete submit.
+ */
+function ds_delete_field_confirm_submit($form, &$form_state) {
+ ds_remove_field_confirm_submit($form, $form_state, 'deleted');
+}
+
+/**
+ * Submit callback: confirmed revert submit.
+ */
+function ds_revert_field_confirm_submit($form, &$form_state) {
+ ds_remove_field_confirm_submit($form, $form_state, 'reverted');
+}
+
+/**
+ * Confirmed field delete or revert submit callback.
+ */
+function ds_remove_field_confirm_submit($form, &$form_state, $action = 'deleted') {
+
+ $field = $form['#ds_field'];
+
+ // Remove field.
+ db_delete('ds_fields')
+ ->condition('field', $field->field)
+ ->execute();
+
+ // Clear cache.
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+
+ // Redirect.
+ $form_state['redirect'] = 'admin/structure/ds/fields';
+ drupal_set_message(t('The field %field has been ' . $action, array('%field' => $field->label)));
+}
+
+/**
+ * Handles ctools modal Add field
+ *
+ * @param $js
+ * Whether js is used or not.
+ * @param $field_type
+ * The name of the field type.
+ */
+function ds_ajax_add_field($js, $field_type) {
+
+ if (!$js) {
+ // We don't support degrading this from js because we're not
+ // using the server to remember the state of the table.
+ drupal_goto("admin/structure/ds/fields/" . $field_type);
+ return;
+ }
+
+ ctools_include('ajax');
+ ctools_include('modal');
+
+ switch ($field_type) {
+
+ case "manage_ctools":
+ $title = t('Add a dynamic field');
+ $form_name = "ds_edit_ctools_field_form";
+ break;
+
+ case "manage_preprocess":
+ $title = t('Add a preprocess field');
+ $form_name = "ds_edit_preprocess_field_form";
+ break;
+
+ case "manage_block":
+ $title = t('Add a block field');
+ $form_name = "ds_edit_block_field_form";
+ break;
+
+ default:
+ $title = t('Add a code field');
+ $form_name = "ds_edit_custom_field_form";
+ $field_type = 'manage_custom';
+ break;
+ }
+
+ $form_state = array();
+ $form_state['build_info']['args'] = array();
+ $form_state += array(
+ 'title' => $title,
+ 'ajax' => TRUE,
+ 're_render' => FALSE,
+ );
+
+ $output = NULL;
+ form_load_include($form_state, 'inc', 'ds_ui', 'includes/ds.fields');
+
+ $output = ctools_modal_form_wrapper($form_name, $form_state);
+
+ // Field is saved.
+ if ($form_state['executed']) {
+
+ $output = array();
+
+ // Do not print messages on screen.
+ if ($messages = theme('status_messages')) {
+ $output[] = ajax_command_append('#console', $messages);
+ }
+
+ // Close the modal.
+ $output[] = ctools_modal_command_dismiss();
+
+ // Call our own javascript function which will trigger a refresh of the table.
+ $output[] = ajax_command_invoke('#field-display-overview', 'dsRefreshDisplayTable');
+ }
+
+ drupal_add_http_header('Content-Type', 'application/json');
+ print ajax_render($output);
+ exit;
+}
diff --git a/sites/all/modules/ds/modules/ds_ui/includes/ds.view_modes.inc b/sites/all/modules/ds/modules/ds_ui/includes/ds.view_modes.inc
new file mode 100644
index 000000000..bc85746f8
--- /dev/null
+++ b/sites/all/modules/ds/modules/ds_ui/includes/ds.view_modes.inc
@@ -0,0 +1,277 @@
+<?php
+
+/**
+ * @file
+ * Administrative functions for managing view modes for every entity.
+ */
+
+/**
+ * Shows the list of view modes.
+ */
+function ds_view_modes_list() {
+ $output = '';
+
+ ctools_include('export');
+ $view_modes = ctools_export_crud_load_all('ds_view_modes');
+ if (!empty($view_modes)) {
+
+ $rows = array();
+ foreach ($view_modes as $view_mode_key => $view_mode_value) {
+
+ $row = array();
+ $row[] = check_plain($view_mode_value->label);
+ $row[] = $view_mode_key;
+ $row[] = ucwords(str_replace('_', ' ', implode(', ', $view_mode_value->entities)));
+ $operations = l(t('Edit'), 'admin/structure/ds/view_modes/manage/' . $view_mode_key, array('alias' => TRUE));
+
+ if ($view_mode_value->export_type == 3) {
+ $operations .= ' - ' . l(t('Revert'), 'admin/structure/ds/view_modes/revert/' . $view_mode_key, array('alias' => TRUE));
+ }
+ elseif ($view_mode_value->export_type == 1) {
+ $operations .= ' - ' . l(t('Delete'), 'admin/structure/ds/view_modes/delete/' . $view_mode_key, array('alias' => TRUE));
+ }
+ $row[] = $operations;
+ $rows[] = $row;
+ }
+
+ $table = array(
+ 'header' => array(
+ 'Label',
+ 'Machine name',
+ 'Entities',
+ 'Operations',
+ ),
+ 'rows' => $rows,
+ );
+
+ $output = theme('table', $table);
+ }
+ else {
+ $output = t('No custom view modes have been defined.');
+ }
+
+ return $output;
+}
+
+/**
+ * Manage a custom view mode.
+ */
+function ds_edit_view_mode_form($form, &$form_state, $view_mode = '') {
+
+ ctools_include('export');
+ $view_modes = ctools_export_crud_load_all('ds_view_modes');
+ if (isset($view_modes[$view_mode])) {
+ $view_mode = $view_modes[$view_mode];
+ }
+ else {
+ $view_mode = new stdClass;
+ $view_mode->label = '';
+ $view_mode->view_mode = '';
+ $view_mode->entities = array();
+ }
+
+ $form['name'] = array(
+ '#title' => t('Label'),
+ '#type' => 'textfield',
+ '#default_value' => $view_mode->label,
+ '#description' => t('The human-readable label of the view mode. This name must be unique.'),
+ '#required' => TRUE,
+ '#maxlength' => 32,
+ '#size' => 30,
+ );
+
+ $form['view_mode'] = array(
+ '#title' => t('Machine name'),
+ '#type' => 'machine_name',
+ '#default_value' => $view_mode->view_mode,
+ '#maxlength' => 32,
+ '#description' => t('The machine-readable name of this view mode. This name must contain only lowercase letters and underscores. This name must be unique.'),
+ '#disabled' => !empty($view_mode->view_mode),
+ '#machine_name' => array(
+ 'exists' => 'ds_view_mode_unique',
+ ),
+ );
+
+ $entity_options = array();
+ $entities = entity_get_info();
+ foreach ($entities as $entity_type => $entity_info) {
+ if (isset($entity_info['fieldable']) && $entity_info['fieldable']) {
+ $entity_options[$entity_type] = drupal_ucfirst(str_replace('_', ' ', $entity_type));
+ }
+ }
+ $form['entities'] = array(
+ '#title' => t('Entities'),
+ '#description' => t('Select the entities for which this view mode will be made available.'),
+ '#type' => 'checkboxes',
+ '#required' => TRUE,
+ '#options' => $entity_options,
+ '#default_value' => $view_mode->entities,
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save')
+ );
+
+ $form['existing'] = array('#type' => 'value', '#value' => !empty($view_mode->view_mode));
+
+ return $form;
+}
+
+/**
+ * Return whether a view mode machine name is unique.
+ */
+function ds_view_mode_unique($name) {
+ ctools_include('export');
+ $view_modes = ctools_export_crud_load_all('ds_view_modes');
+ $value = strtr($name, array('-' => '_'));
+ return isset($view_modes[$value]) ? TRUE : FALSE;
+}
+
+/**
+ * Validates the view mode submission form generated by ds_edit_view_mode_form().
+ */
+function ds_edit_view_mode_form_validate($form, &$form_state) {
+
+ $view_mode = new stdClass;
+ $view_mode->view_mode = $form_state['values']['view_mode'];
+ $view_mode->label = $form_state['values']['name'];
+
+ $reserved = array();
+ $entities = $form_state['values']['entities'];
+ foreach ($entities as $key => $value) {
+ if ($key !== $value) {
+ unset($entities[$key]);
+ }
+ else {
+ $reserved += ds_entity_view_modes($key);
+ }
+ }
+
+ if (array_key_exists($view_mode->view_mode, $reserved) && !isset($form_state['values']['existing'])) {
+ form_set_error('type', t('The machine-readable name %view_mode is reserved.', array('%view_mode' => $view_mode->view_mode)));
+ }
+ else {
+ $view_mode->entities = $entities;
+ $form_state['view_mode'] = $view_mode;
+ }
+}
+
+/**
+ * Saves the view mode.
+ */
+function ds_edit_view_mode_form_submit($form, &$form_state) {
+ $view_mode = $form_state['view_mode'];
+
+ // Delete previous view_mode configuration (if any)
+ db_delete('ds_view_modes')
+ ->condition('view_mode', $view_mode->view_mode)
+ ->execute();
+
+ // Save the new view mode.
+ drupal_write_record('ds_view_modes', $view_mode);
+
+ // Clear entity info cache and trigger menu build on next request.
+ cache_clear_all('entity_info', 'cache', TRUE);
+ variable_set('menu_rebuild_needed', TRUE);
+
+ // Redirect.
+ $form_state['redirect'] = 'admin/structure/ds/view_modes';
+ drupal_set_message(t('The view mode %view_mode has been saved', array('%view_mode' => $view_mode->label)));
+}
+
+/**
+ * Menu callback: Confirmation view mode delete form.
+ */
+function ds_delete_view_mode_confirm($form, &$form_state, $view_mode = '') {
+ return ds_remove_view_mode_confirm($form, $form_state, $view_mode, 'delete');
+}
+
+/**
+ * Menu callback: Confirmation view mode revert form.
+ */
+function ds_revert_view_mode_confirm($form, &$form_state, $view_mode = '') {
+ return ds_remove_view_mode_confirm($form, $form_state, $view_mode, 'revert');
+}
+
+/**
+ * Confirmation revert or remove form.
+ */
+function ds_remove_view_mode_confirm($form, &$form_state, $view_mode = '', $action = 'delete') {
+ ctools_include('export');
+ $view_modes = ctools_export_crud_load_all('ds_view_modes');
+ if (isset($view_modes[$view_mode])) {
+ $view_mode = $view_modes[$view_mode];
+ }
+
+ $confirm = FALSE;
+ if (isset($view_mode->export_type)) {
+ if ($action == 'delete' && $view_mode->export_type != 2) {
+ $confirm = TRUE;
+ }
+ elseif ($action == 'revert' && $view_mode->export_type == 3) {
+ $confirm = TRUE;
+ }
+ }
+
+ if ($confirm) {
+ $form['#view_mode'] = $view_mode;
+ return confirm_form($form,
+ t('Are you sure you want to ' . $action . ' %view_mode?', array('%view_mode' => $view_mode->label)),
+ 'admin/structure/ds/view_modes',
+ t('This action cannot be undone.'),
+ t(drupal_ucfirst($action)),
+ t('Cancel')
+ );
+ }
+ else {
+ drupal_set_message(t('This operation is not possible.'));
+ drupal_goto('admin/structure/ds/view_modes');
+ }
+}
+
+/**
+ * Confirmed view mode delete submit callback.
+ */
+function ds_delete_view_mode_confirm_submit($form, &$form_state) {
+ ds_remove_view_mode($form, $form_state, 'deleted');
+}
+
+/**
+ * Confirmed view mode revert submit callback.
+ */
+function ds_revert_view_mode_confirm_submit($form, &$form_state) {
+ ds_remove_view_mode($form, $form_state, 'reverted');
+}
+
+/**
+ * Remove a view mode from the database.
+ *
+ * @param $action
+ * Whether we delete or remove the view mode.
+ */
+function ds_remove_view_mode($form, &$form_state, $action = 'deleted') {
+
+ $view_mode = $form['#view_mode'];
+
+ // Remove view mode from database.
+ db_delete('ds_view_modes')
+ ->condition('view_mode', $view_mode->view_mode)
+ ->execute();
+
+ // Remove layout and field settings for this view mode.
+ db_delete('ds_field_settings')
+ ->condition('view_mode', $view_mode->view_mode)
+ ->execute();
+ db_delete('ds_layout_settings')
+ ->condition('view_mode', $view_mode->view_mode)
+ ->execute();
+
+ // Clear entity info cache and trigger menu build on next request.
+ cache_clear_all('entity_info', 'cache', TRUE);
+ variable_set('menu_rebuild_needed', TRUE);
+
+ // Redirect.
+ $form_state['redirect'] = 'admin/structure/ds/view_modes';
+ drupal_set_message(t('The view mode %view_mode has been ' . $action . '.', array('%view_mode' => $view_mode->label)));
+}
diff --git a/sites/all/modules/ds/plugins/content_types/dsc/dsc.inc b/sites/all/modules/ds/plugins/content_types/dsc/dsc.inc
new file mode 100644
index 000000000..ead5bf425
--- /dev/null
+++ b/sites/all/modules/ds/plugins/content_types/dsc/dsc.inc
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Plugin to handle the Display Suite content type.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'title' => t('Display Suite content'),
+ 'category' => t('Display Suite'),
+ 'single' => TRUE,
+ 'defaults' => array(
+ 'title' => '',
+ 'body' => '',
+ 'format' => 'ds_code',
+ 'context' => array(),
+ ),
+ 'description' => t('Add custom content with the possibility to use the entity object when using the Display Suite format.'),
+ 'js' => array('misc/textarea.js', 'misc/collapse.js'),
+ 'all contexts' => TRUE,
+);
+
+/**
+ * Output function for the 'Display Suite' content type.
+ */
+function ds_dsc_content_type_render($subtype, $conf, $panel_args, $context, $incoming) {
+ $block = new stdClass();
+
+ $field = array();
+
+ $entity = array_shift($context);
+ $field['entity'] = isset($entity->data) ? $entity->data : NULL;
+ $field['properties']['code']['format'] = $conf['format'];
+ $field['properties']['code']['value'] = $conf['body'];
+
+ $block->module = 'ds';
+ $block->title = $conf['title'];
+ $block->content = ds_render_code_field($field);
+
+ return $block;
+}
+
+/**
+ * The form to add or edit Display Suite content.
+ */
+function ds_dsc_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => isset($conf['title']) ? $conf['title'] : '',
+ '#title' => t('Title'),
+ );
+
+ $form['body'] = array(
+ '#type' => 'text_format',
+ '#title' => t('Body'),
+ '#default_value' => isset($conf['body']) ? $conf['body'] : '',
+ '#format' => isset($conf['format']) ? $conf['format'] : 'ds_code',
+ );
+
+ return $form;
+}
+
+/**
+ * Save the Display Suite content.
+ */
+function ds_dsc_content_type_edit_form_submit($form, &$form_state) {
+ $form_state['conf']['title'] = $form_state['values']['title'];
+ $form_state['conf']['body'] = $form_state['values']['body']['value'];
+ $form_state['conf']['format'] = $form_state['values']['body']['format'];
+ $form_state['conf']['context'] = array();
+}
+
+/**
+ * Returns the administrative title for a Display Suite content.
+ */
+function ds_dsc_content_type_admin_title($subtype, $conf) {
+ return (!empty($conf['title'])) ? $conf['title'] : t('Display Suite content');
+}
+
+/**
+ * Display the administrative information for a Display Suite content pane.
+ */
+function ds_dsc_content_type_admin_info($subtype, $conf) {
+ return 'Display Suite content can only be rendered on the frontend.';
+}
diff --git a/sites/all/modules/ds/tests/ds.base.test b/sites/all/modules/ds/tests/ds.base.test
new file mode 100644
index 000000000..4bfdfc309
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds.base.test
@@ -0,0 +1,874 @@
+<?php
+
+/**
+ * @file
+ * Base functions and tests for Display Suite.
+ */
+
+class dsBaseTest extends DrupalWebTestCase {
+
+ /**
+ * Implementation of setUp().
+ */
+ function setUp() {
+ parent::setUp('ctools', 'ds', 'ds_extras', 'search', 'ds_search', 'ds_format', 'ds_forms', 'ds_ui', 'ds_test', 'panels', 'views');
+
+ variable_set('search_active_modules', array('node' => '', 'user' => 'user', 'ds_search' => 'ds_search'));
+ menu_rebuild();
+
+ $this->admin_user = $this->drupalCreateUser(array('admin_classes', 'admin_view_modes', 'admin_fields', 'admin_display_suite', 'ds_switch article', 'use text format ds_code', 'access administration pages', 'administer content types', 'administer users', 'administer comments', 'administer nodes', 'bypass node access', 'administer blocks', 'search content', 'use advanced search', 'administer search', 'access user profiles', 'administer permissions'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Select a layout.
+ */
+ function dsSelectLayout($edit = array(), $assert = array(), $url = 'admin/structure/types/manage/article/display', $options = array()) {
+
+ $edit += array(
+ 'additional_settings[layout]' => 'ds_2col_stacked',
+ );
+
+ $this->drupalPost($url, $edit, t('Save'), $options);
+
+ $assert += array(
+ 'regions' => array(
+ 'header' => '<td colspan="8">' . t('Header') . '</td>',
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ 'footer' => '<td colspan="8">' . t('Footer') . '</td>',
+ ),
+ );
+
+ foreach ($assert['regions'] as $region => $raw) {
+ $this->assertRaw($region, t('Region !region found', array('!region' => $region)));
+ }
+ }
+
+ /**
+ * Configure classes
+ */
+ function dsConfigureClasses($edit = array()) {
+
+ $edit += array(
+ 'ds_classes_regions' => "class_name_1\nclass_name_2|Friendly name"
+ );
+
+ $this->drupalPost('admin/structure/ds/classes', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), t('CSS classes configuration saved'));
+ $this->assertRaw('class_name_1', 'Class name 1 found');
+ $this->assertRaw('class_name_2', 'Class name 1 found');
+ }
+
+ /**
+ * Configure classes on a layout.
+ */
+ function dsSelectClasses($edit = array(), $url = 'admin/structure/types/manage/article/display') {
+
+ $edit += array(
+ "additional_settings[header][]" => 'class_name_1',
+ "additional_settings[footer][]" => 'class_name_2',
+ );
+
+ $this->drupalPost($url, $edit, t('Save'));
+ }
+
+ /**
+ * Configure Field UI.
+ */
+ function dsConfigureUI($edit, $url = 'admin/structure/types/manage/article/display') {
+ $this->drupalPost($url, $edit, t('Save'));
+ }
+
+ /**
+ * Edit field formatter settings
+ */
+ function dsEditFormatterSettings($edit, $url = 'admin/structure/types/manage/article/display', $element_value = 'edit body') {
+ $this->drupalPost($url, array(), $element_value);
+ $this->drupalPost(NULL, $edit, t('Update'));
+ $this->drupalPost(NULL, array(), t('Save'));
+ }
+
+ /**
+ * Create a view mode.
+ *
+ * @param $edit
+ * An optional array of view mode properties.
+ */
+ function dsCreateViewMode($edit = array()) {
+
+ $edit += array(
+ 'name' => 'Testing',
+ 'view_mode' => 'testing',
+ 'entities[node]' => '1'
+ );
+
+ $this->drupalPost('admin/structure/ds/view_modes/manage', $edit, t('Save'));
+ $this->assertText(t('The view mode ' . $edit['name'] . ' has been saved'), t('!name view mode has been saved', array('!name' => $edit['name'])));
+ }
+
+ /**
+ * Create a code field.
+ *
+ * @param $edit
+ * An optional array of field properties.
+ */
+ function dsCreateCodeField($edit = array(), $url = 'admin/structure/ds/fields/manage_custom') {
+
+ $edit += array(
+ 'name' => 'Test field',
+ 'field' => 'test_field',
+ 'entities[node]' => '1',
+ 'code[value]' => 'Test field',
+ 'use_token' => '0',
+ );
+
+ $this->drupalPost($url, $edit, t('Save'));
+ $this->assertText(t('The field ' . $edit['name'] . ' has been saved'), t('!name field has been saved', array('!name' => $edit['name'])));
+ }
+
+ /**
+ * Create a block field.
+ *
+ * @param $edit
+ * An optional array of field properties.
+ */
+ function dsCreateBlockField($edit = array(), $url = 'admin/structure/ds/fields/manage_block', $first = TRUE) {
+
+ $edit += array(
+ 'name' => 'Test block field',
+ 'entities[node]' => '1',
+ 'block' => 'node|recent',
+ 'block_render' => DS_BLOCK_TEMPLATE,
+ );
+
+ if ($first) {
+ $edit += array('field' => 'test_block_field');
+ }
+
+ $this->drupalPost($url, $edit, t('Save'));
+ $this->assertText(t('The field ' . $edit['name'] . ' has been saved'), t('!name field has been saved', array('!name' => $edit['name'])));
+ }
+
+ /**
+ * Create a block field.
+ *
+ * @param $edit
+ * An optional array of field properties.
+ */
+ function dsCreatePreprocessField($edit = array(), $url = 'admin/structure/ds/fields/manage_preprocess', $first = TRUE) {
+
+ $edit += array(
+ 'name' => 'Submitted',
+ 'entities[node]' => '1',
+ );
+
+ if ($first) {
+ $edit += array('field' => 'submitted');
+ }
+
+ $this->drupalPost($url, $edit, t('Save'));
+ $this->assertText(t('The field ' . $edit['name'] . ' has been saved'), t('!name field has been saved', array('!name' => $edit['name'])));
+ }
+
+ /**
+ * Create a dynamic field.
+ *
+ * @param $edit
+ * An optional array of field properties.
+ */
+ function dsCreateDynamicField($edit = array(), $url = 'admin/structure/ds/fields/manage_ctools', $first = TRUE) {
+
+ $edit += array(
+ 'name' => 'Dynamic',
+ 'entities[node]' => '1',
+ );
+
+ if ($first) {
+ $edit += array('field' => 'dynamic');
+ }
+
+ $this->drupalPost($url, $edit, t('Save'));
+ $this->assertText(t('The field ' . $edit['name'] . ' has been saved'), t('!name field has been saved', array('!name' => $edit['name'])));
+ }
+}
+
+/**
+ * Test managing of custom fields.
+ */
+class dsFieldsTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Fields'),
+ 'description' => t('Tests for managing custom code, dynamic, preprocess and block fields.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ /**
+ * Test Display fields.
+ */
+ function testDSFields() {
+
+ $edit = array(
+ 'name' => 'Test field',
+ 'field' => 'test_field',
+ 'entities[node]' => '1',
+ 'code[value]' => 'Test field',
+ 'use_token' => '0',
+ );
+
+ $this->dsCreateCodeField($edit);
+
+ // Create the same and assert it already exists.
+ $this->drupalPost('admin/structure/ds/fields/manage_custom', $edit, t('Save'));
+ $this->assertText(t('The machine-readable name is already in use. It must be unique.'), t('Field testing already exists.'));
+
+ $this->dsSelectLayout();
+
+ // Assert it's found on the Field UI for article.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('fields[test_field][weight]', t('Test field found on node article.'));
+
+ // Assert it's not found on the Field UI for users.
+ $this->drupalGet('admin/config/people/accounts/display');
+ $this->assertNoRaw('fields[test_field][weight]', t('Test field not found on user.'));
+
+ // Update testing label
+ $edit = array(
+ 'name' => 'Test field 2',
+ );
+ $this->drupalPost('admin/structure/ds/fields/manage_custom/test_field', $edit, t('Save'));
+ $this->assertText(t('The field Test field 2 has been saved'), t('Test field label updated'));
+
+ // Use the Field UI limit option.
+ $this->dsSelectLayout(array(), array(), 'admin/structure/types/manage/page/display');
+ $this->dsSelectLayout(array(), array(), 'admin/structure/types/manage/article/display/teaser');
+ $edit = array(
+ 'ui_limit' => 'article|default',
+ );
+ $this->drupalPost('admin/structure/ds/fields/manage_custom/test_field', $edit, t('Save'));
+
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('fields[test_field][weight]', t('Test field field found on node article, default.'));
+ $this->drupalGet('admin/structure/types/manage/article/display/teaser');
+ $this->assertNoRaw('fields[test_field][weight]', t('Test field field not found on node article, teaser.'));
+ $this->drupalGet('admin/structure/types/manage/page/display');
+ $this->assertNoRaw('fields[test_field][weight]', t('Test field field not found on node page, default.'));
+ $edit = array(
+ 'ui_limit' => 'article|*',
+ );
+ $this->drupalPost('admin/structure/ds/fields/manage_custom/test_field', $edit, t('Save'));
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('fields[test_field][weight]', t('Test field field found on node article, default.'));
+ $this->drupalGet('admin/structure/types/manage/article/display/teaser');
+ $this->assertRaw('fields[test_field][weight]', t('Test field field found on node article, teaser.'));
+
+
+
+ // Remove the field.
+ $this->drupalPost('admin/structure/ds/fields/delete/test_field', array(), t('Delete'));
+ $this->assertText(t('The field Test field 2 has been deleted'), t('Test field removed'));
+
+ // Assert the field is gone at the manage display screen.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoRaw('fields[test_field][weight]', t('Test field field not found on node article.'));
+
+ // Block fields.
+ $edit = array(
+ 'name' => 'Test block field',
+ 'field' => 'test_block_field',
+ 'entities[node]' => '1',
+ 'block' => 'node|recent',
+ 'block_render' => DS_BLOCK_TEMPLATE,
+ );
+
+ $this->dsCreateBlockField($edit);
+
+ // Create the same and assert it already exists.
+ $this->drupalPost('admin/structure/ds/fields/manage_block', $edit, t('Save'));
+ $this->assertText(t('The machine-readable name is already in use. It must be unique.'), t('Block test field already exists.'));
+
+ $this->dsSelectLayout();
+
+ // Assert it's found on the Field UI for article.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('fields[test_block_field][weight]', t('Test block field found on node article.'));
+
+ // Assert it's not found on the Field UI for users.
+ $this->drupalGet('admin/config/people/accounts/display');
+ $this->assertNoRaw('fields[test_block_field][weight]', t('Test block field not found on user.'));
+
+ // Update testing label
+ $edit = array(
+ 'name' => 'Test block field 2',
+ );
+ $this->drupalPost('admin/structure/ds/fields/manage_block/test_block_field', $edit, t('Save'));
+ $this->assertText(t('The field Test block field 2 has been saved'), t('Test field label updated'));
+
+ // Remove the block field.
+ $this->drupalPost('admin/structure/ds/fields/delete/test_block_field', array(), t('Delete'));
+ $this->assertText(t('The field Test block field 2 has been deleted'), t('Test field removed'));
+
+ // Assert the block field is gone at the manage display screen.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoRaw('fields[test_block_field][weight]', t('Test block field not found on node article.'));
+
+ // Preprocess fields.
+ $edit = array(
+ 'name' => 'Submitted',
+ 'field' => 'submitted',
+ 'entities[node]' => '1',
+ );
+
+ $this->dsCreatePreprocessField($edit);
+
+ // Create the same and assert it already exists.
+ $this->drupalPost('admin/structure/ds/fields/manage_custom', $edit, t('Save'));
+ $this->assertText(t('The machine-readable name is already in use. It must be unique.'), t('Submitted already exists.'));
+
+ $this->dsSelectLayout();
+
+ // Assert it's found on the Field UI for article.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('fields[submitted][weight]', t('Submitted found on node article.'));
+
+ // Assert it's not found on the Field UI for users.
+ $this->drupalGet('admin/config/people/accounts/display');
+ $this->assertNoRaw('fields[submitted][weight]', t('Submitted not found on user.'));
+
+ // Update testing label
+ $edit = array(
+ 'name' => 'Submitted by',
+ );
+ $this->drupalPost('admin/structure/ds/fields/manage_preprocess/submitted', $edit, t('Save'));
+ $this->assertText(t('The field Submitted by has been saved'), t('Submitted label updated'));
+
+ // Remove a field.
+ $this->drupalPost('admin/structure/ds/fields/delete/submitted', array(), t('Delete'));
+ $this->assertText(t('The field Submitted by has been deleted'), t('Submitted removed'));
+
+ // Assert the field is gone at the manage display screen.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoRaw('fields[submitted][weight]', t('Submitted field not found on node article.'));
+
+ // Dynamic fields.
+ $edit = array(
+ 'name' => 'Dynamic',
+ 'field' => 'dynamic',
+ 'entities[node]' => '1',
+ );
+
+ $this->dsCreateDynamicField($edit);
+
+ // Create the same and assert it already exists.
+ $this->drupalPost('admin/structure/ds/fields/manage_ctools', $edit, t('Save'));
+ $this->assertText(t('The machine-readable name is already in use. It must be unique.'), t('Dynamic already exists.'));
+
+ $this->dsSelectLayout();
+
+ // Assert it's found on the Field UI for article.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('fields[dynamic][weight]', t('Dynamic found on node article.'));
+
+ // Assert it's not found on the Field UI for users.
+ $this->drupalGet('admin/config/people/accounts/display');
+ $this->assertNoRaw('fields[dynamic][weight]', t('Dynamic not found on user.'));
+
+ // Update testing label
+ $edit = array(
+ 'name' => 'Uber dynamic',
+ );
+ $this->drupalPost('admin/structure/ds/fields/manage_ctools/dynamic', $edit, t('Save'));
+ $this->assertText(t('The field Uber dynamic has been saved'), t('Dynamic label updated'));
+
+ // Remove a field.
+ $this->drupalPost('admin/structure/ds/fields/delete/dynamic', array(), t('Delete'));
+ $this->assertText(t('The field Uber dynamic has been deleted'), t('Dynamic removed'));
+
+ // Assert the field is gone at the manage display screen.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoRaw('fields[dynamic][weight]', t('Dynamic field not found on node article.'));
+ }
+}
+
+/**
+ * Test managing of view modes.
+ */
+class dsViewModesTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('View modes'),
+ 'description' => t('Tests for managing custom view modes.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ /**
+ * Test managing view modes.
+ */
+ function testDSManageViewModes() {
+
+ $edit = array(
+ 'name' => 'Testing',
+ 'view_mode' => 'testing',
+ 'entities[node]' => '1'
+ );
+
+ $this->dsCreateViewMode($edit);
+
+ // Create the same and assert it already exists.
+ $this->drupalPost('admin/structure/ds/view_modes/manage', $edit, t('Save'));
+ $this->assertText(t('The machine-readable name is already in use. It must be unique.'), t('View mode testing already exists.'));
+
+ // Assert it's found on the Field UI for article.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('additional_settings[modes][view_modes_custom][testing]', t('Testing view mode found on node article.'));
+
+ // Assert it's not found on the Field UI for article.
+ $this->drupalGet('admin/config/people/accounts/display');
+ $this->assertNoRaw('additional_settings[modes][view_modes_custom][testing]', t('Testing view mode not found on user.'));
+
+ // Update testing label
+ $edit = array(
+ 'name' => 'Testing 2',
+ );
+ $this->drupalPost('admin/structure/ds/view_modes/manage/testing', $edit, t('Save'));
+ $this->assertText(t('The view mode Testing 2 has been saved'), t('Testing label updated'));
+
+ // Remove a view mode.
+ $this->drupalPost('admin/structure/ds/view_modes/delete/testing', array(), t('Delete'));
+ $this->assertText(t('The view mode Testing 2 has been deleted'), t('Testing view mode removed'));
+
+ // Assert the view mode is gone at the manage display screen.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoRaw('additional_settings[modes][view_modes_custom][testing]', t('Testing view mode found on node article.'));
+ }
+}
+
+/**
+ * Test managing of layouts and CSS classes
+ */
+class dsLayoutsClassesTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Layouts'),
+ 'description' => t('Tests for managing layouts and classes on Field UI screen.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ /**
+ * Test selecting layouts, classes, region to block and fields.
+ */
+ function testDStestLayouts() {
+
+ // Check that the ds_3col_equal_width layout is not available (through the alter).
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoRaw('ds_3col_stacked_equal_width', 'ds_3col_stacked_equal_width not available');
+
+ // Create code, dynamic, preprocess block field.
+ $this->dsCreateCodeField();
+ $this->dsCreateBlockField();
+ $this->dsCreateDynamicField();
+ $this->dsCreatePreprocessField();
+
+ $layout = array(
+ 'additional_settings[layout]' => 'ds_2col_stacked',
+ );
+
+ $assert = array(
+ 'regions' => array(
+ 'header' => '<td colspan="8">' . t('Header') . '</td>',
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ 'footer' => '<td colspan="8">' . t('Footer') . '</td>',
+ ),
+ );
+
+ $fields = array(
+ 'fields[post_date][region]' => 'header',
+ 'fields[author][region]' => 'left',
+ 'fields[links][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ 'fields[comments][region]' => 'footer',
+ 'fields[test_field][region]' => 'left',
+ 'fields[test_block_field][region]' => 'left',
+ 'fields[submitted][region]' => 'left',
+ 'fields[dynamic][region]' => 'left',
+ 'fields[ds_extras_extra_test_field][region]' => 'header',
+ );
+
+ // Setup first layout.
+ $this->dsSelectLayout($layout, $assert);
+ $this->dsConfigureClasses();
+ $this->dsSelectClasses();
+ $this->dsConfigureUI($fields);
+
+ // Assert the two extra fields are found.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('ds_extras_extra_test_field');
+ $this->assertRaw('ds_extras_second_field');
+
+ // Assert we have some configuration in our database.
+ $count = db_query("SELECT COUNT(settings) FROM {ds_layout_settings} WHERE entity_type = 'node' AND bundle = 'article' AND view_mode = 'default'")->fetchField();
+ $this->assertEqual($count, 1, t('1 record found for layout serttings for node article'));
+
+ // Lookup settings and verify.
+ $data = unserialize(db_query("SELECT settings FROM {ds_layout_settings} WHERE entity_type = 'node' AND bundle = 'article' AND view_mode = 'default'")->fetchField());
+ $this->assertTrue(in_array('ds_extras_extra_test_field', $data['regions']['header']), t('Extra field is in header'));
+ $this->assertTrue(in_array('post_date', $data['regions']['header']), t('Post date is in header'));
+ $this->assertTrue(in_array('test_field', $data['regions']['left']), t('Test field is in left'));
+ $this->assertTrue(in_array('author', $data['regions']['left']), t('Author is in left'));
+ $this->assertTrue(in_array('links', $data['regions']['left']), t('Links is in left'));
+ $this->assertTrue(in_array('test_block_field', $data['regions']['left']), t('Test block field is in left'));
+ $this->assertTrue(in_array('submitted', $data['regions']['left']), t('Submitted field is in left'));
+ $this->assertTrue(in_array('dynamic', $data['regions']['left']), t('Dynamic field is in left'));
+ $this->assertTrue(in_array('body', $data['regions']['right']), t('Body is in right'));
+ $this->assertTrue(in_array('comments', $data['regions']['footer']), t('Comments is in footer'));
+ $this->assertTrue(in_array('class_name_1', $data['classes']['header']), t('Class name 1 is in header'));
+ $this->assertTrue(empty($data['classes']['left']), t('Left has no classes'));
+ $this->assertTrue(empty($data['classes']['right']), t('Right has classes'));
+ $this->assertTrue(in_array('class_name_2', $data['classes']['footer']), t('Class name 2 is in header'));
+
+ // Extra save for the dynamic field.
+ $field_settings = ds_get_field_settings('node', 'article', 'default');
+ $formatter_settings = array(
+ 'show_title' => 0,
+ 'title_wrapper' => '',
+ 'ctools' => 'a:3:{s:4:"conf";a:3:{s:7:"context";s:25:"argument_entity_id:node_1";s:14:"override_title";i:0;s:19:"override_title_text";s:0:"";}s:4:"type";s:14:"node_type_desc";s:7:"subtype";s:14:"node_type_desc";}',
+ );
+ $field_settings['dynamic']['formatter_settings'] = $formatter_settings;
+ $record = new stdClass();
+ $record->id = 'node|article|default';
+ $record->entity_type = 'node';
+ $record->bundle = 'article';
+ $record->view_mode = 'default';
+ $record->settings = $field_settings;
+ drupal_write_record('ds_field_settings', $record, array('id'));
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+ cache_clear_all('ds_field_settings', 'cache');
+
+ // Create a article node and verify settings.
+ $settings = array(
+ 'type' => 'article',
+ );
+ $node = $this->drupalCreateNode($settings);
+ $this->drupalGet('node/' . $node->nid);
+
+ // Assert regions.
+ $this->assertRaw('group-header', 'Template found (region header)');
+ $this->assertRaw('group-header class_name_1', 'Class found (class_name_1)');
+ $this->assertRaw('group-left', 'Template found (region left)');
+ $this->assertRaw('group-right', 'Template found (region right)');
+ $this->assertRaw('group-footer', 'Template found (region footer)');
+ $this->assertRaw('group-footer class_name_2', 'Class found (class_name_2)');
+
+ // Assert custom fields.
+ $this->assertRaw('field-name-test-field', t('Custom field found'));
+ $this->assertRaw('Test field', t('Custom field found'));
+ $this->assertRaw('field-name-test-block-field', t('Custom block field found'));
+ $this->assertRaw('<h2>Recent content</h2>', t('Custom block field found'));
+ $this->assertRaw('Submitted by', t('Submitted field found'));
+ $this->assertText('Use articles for time-sensitive content like news, press releases or blog posts.', t('Dynamic field found'));
+ $this->assertText('This is an extra field made available through "Extra fields" functionality.');
+
+ // Test disable sidebar regions.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('sidebar-first', 'Left sidebar found.');
+ $hide = array(
+ 'additional_settings[hide_sidebars]' => '1',
+ );
+ $this->dsConfigureUI($hide);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoRaw('sidebar-first', 'Left sidebar not found.');
+
+ // Test HTML5 wrappers
+ $this->assertNoRaw('<header', 'Header not found.');
+ $this->assertNoRaw('<footer', 'Footer not found.');
+ $this->assertNoRaw('<article', 'Article not found.');
+ $wrappers = array(
+ 'additional_settings[region_wrapper][header]' => 'header',
+ 'additional_settings[region_wrapper][right]' => 'footer',
+ 'additional_settings[region_wrapper][layout_wrapper]' => 'article',
+ );
+ $this->dsConfigureUI($wrappers);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('<header', 'Header found.');
+ $this->assertRaw('<footer', 'Footer found.');
+ $this->assertRaw('<article', 'Article found.');
+
+ // Let's create a block field, enable the full mode first.
+ $edit = array('additional_settings[modes][view_modes_custom][full]' => '1');
+ $this->drupalPost('admin/structure/types/manage/article/display', $edit, t('Save'));
+
+ // Select layout.
+ $layout = array(
+ 'additional_settings[layout]' => 'ds_2col',
+ );
+
+ $assert = array(
+ 'regions' => array(
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ ),
+ );
+ $this->dsSelectLayout($layout, $assert, 'admin/structure/types/manage/article/display/full');
+
+ // Create new block field.
+ $edit = array(
+ 'additional_settings[region_to_block][new_block_region]' => 'Block region',
+ 'additional_settings[region_to_block][new_block_region_key]' => 'block_region',
+ );
+ $this->drupalPost('admin/structure/types/manage/article/display/full', $edit, t('Save'));
+ $this->assertRaw('<td colspan="8">' . t('Block region') . '</td>', 'Block region found');
+
+ // Configure fields
+ $fields = array(
+ 'fields[author][region]' => 'left',
+ 'fields[links][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ 'fields[ds_test_field][region]' => 'block_region',
+ );
+ $this->dsConfigureUI($fields, 'admin/structure/types/manage/article/display/full');
+
+ // Set block in sidebar
+ $edit = array(
+ 'blocks[ds_extras_block_region][region]' => 'sidebar_first',
+ );
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+ // Assert the block is on the node page.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('<h2>Block region</h2>', 'Block region found');
+ $this->assertText('Test code field on node ' . $node->nid, 'Post date in block');
+
+ // Change layout via admin/structure/ds/layout-change.
+ // First verify that header and footer are not here.
+ $this->drupalGet('admin/structure/types/manage/article/display/full');
+ $this->assertNoRaw('<td colspan="8">' . t('Header') . '</td>', 'Header region not found');
+ $this->assertNoRaw('<td colspan="8">' . t('Footer') . '</td>', 'Footer region not found');
+
+ // Remap the regions.
+ $edit = array(
+ 'ds_left' => 'header',
+ 'ds_right' => 'footer',
+ 'ds_block_region' => 'footer',
+ );
+ $this->drupalPost('admin/structure/ds/change-layout/node/article/full/ds_2col_stacked', $edit, t('Save'), array('query' => array('destination' => 'admin/structure/types/manage/article/display/full')));
+
+ // Verify new regions.
+ $this->assertRaw('<td colspan="8">' . t('Header') . '</td>', 'Header region found');
+ $this->assertRaw('<td colspan="8">' . t('Footer') . '</td>', 'Footer region found');
+ $this->assertRaw('<td colspan="8">' . t('Block region') . '</td>', 'Block region found');
+
+ // Verify settings.
+ $data = unserialize(db_query("SELECT settings FROM {ds_layout_settings} WHERE entity_type = 'node' AND bundle = 'article' AND view_mode = 'full'")->fetchField());
+ $this->assertTrue(in_array('author', $data['regions']['header']), t('Author is in header'));
+ $this->assertTrue(in_array('links', $data['regions']['header']), t('Links field is in header'));
+ $this->assertTrue(in_array('body', $data['regions']['footer']), t('Body field is in footer'));
+ $this->assertTrue(in_array('ds_test_field', $data['regions']['footer']), t('Test field is in footer'));
+
+ // Test that a default view mode with no layout is not affected by a disabled view mode.
+ $edit = array(
+ 'additional_settings[layout]' => '',
+ 'additional_settings[modes][view_modes_custom][full]' => FALSE,
+ );
+ $this->drupalPost('admin/structure/types/manage/article/display', $edit, t('Save'));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoText('Test code field on node 1', 'No ds field from full view mode layout');
+ }
+}
+
+/**
+ * Tests for Display Suite field permissions.
+ */
+class dsFieldPermissionTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Field permissions'),
+ 'description' => t('Tests for testing field permissions.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ function testFieldPermissions() {
+
+ $fields = array(
+ 'fields[body][region]' => 'right',
+ 'fields[ds_test_field][region]' => 'left',
+ );
+
+ variable_set('ds_extras_field_permissions', TRUE);
+ $this->refreshVariables();
+ module_implements(FALSE, FALSE, TRUE);
+
+ $this->dsSelectLayout();
+ $this->dsConfigureUI($fields);
+
+ // Create a node.
+ $settings = array('type' => 'article');
+ $node = $this->drupalCreateNode($settings);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('group-right', 'Template found (region right)');
+ $this->assertNoText('Test code field on node ' . $node->nid, 'Test code field not found');
+
+ // Give permissions.
+ $edit = array(
+ '2[view author on node]' => 1,
+ '2[view ds_test_field on node]' => 1,
+ );
+ $this->drupalPost('admin/people/permissions', $edit, t('Save permissions'));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('group-left', 'Template found (region left)');
+ $this->assertText('Test code field on node ' . $node->nid, 'Test code field found');
+ }
+}
+
+/**
+ * Tests for Display Suite hooks.
+ */
+class dsHooksTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Hooks'),
+ 'description' => t('Tests for hooks: ds_fields, ds_fields_alter, ds_layouts.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ /**
+ * Test fields hooks.
+ */
+ function testDSFields() {
+
+ $this->dsSelectLayout();
+
+ // Find the two extra fields from the test module on the node type.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertText('Test code field from hook', 'Test field found on node.');
+ $this->assertText('Field altered', 'Test field altered found on node.');
+
+ $empty = array();
+ $edit = array('additional_settings[layout]' => 'ds_2col_stacked');
+ $this->dsSelectLayout($edit, $empty, 'admin/config/people/accounts/display');
+
+ // Fields can not be found on user.
+ $this->drupalGet('admin/config/people/accounts/display');
+ $this->assertNoText('Test code field from hook', 'Test field not found on user.');
+ $this->assertNoText('Field altered', 'Test field altered not found on user.');
+
+ // Select layout.
+ $this->dsSelectLayout();
+
+ $fields = array(
+ 'fields[author][region]' => 'left',
+ 'fields[links][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ 'fields[ds_test_field][region]' => 'right',
+ 'fields[ds_test_field_empty_string][region]' => 'right',
+ 'fields[ds_test_field_empty_string][label]' => 'inline',
+ 'fields[ds_test_field_false][region]' => 'right',
+ 'fields[ds_test_field_false][label]' => 'inline',
+ 'fields[ds_test_field_null][region]' => 'right',
+ 'fields[ds_test_field_null][label]' => 'inline',
+ 'fields[ds_test_field_nothing][region]' => 'right',
+ 'fields[ds_test_field_nothing][label]' => 'inline',
+ 'fields[ds_test_field_zero_int][region]' => 'right',
+ 'fields[ds_test_field_zero_int][label]' => 'inline',
+ 'fields[ds_test_field_zero_string][region]' => 'right',
+ 'fields[ds_test_field_zero_string][label]' => 'inline',
+ 'fields[ds_test_field_zero_float][region]' => 'right',
+ 'fields[ds_test_field_zero_float][label]' => 'inline',
+ );
+
+ $this->dsSelectLayout();
+ $this->dsConfigureUI($fields);
+
+ // Create a node.
+ $settings = array('type' => 'article');
+ $node = $this->drupalCreateNode($settings);
+ $this->drupalGet('node/' . $node->nid);
+
+ $this->assertRaw('group-left', 'Template found (region left)');
+ $this->assertRaw('group-right', 'Template found (region right)');
+ $this->assertText('Test code field on node ' . $node->nid, 'Test code field found');
+ $this->assertNoText('Test code field that returns an empty string', 'Test code field that returns an empty string is not visible.');
+ $this->assertNoText('Test code field that returns FALSE', 'Test code field that returns FALSE is not visible.');
+ $this->assertNoText('Test code field that returns NULL', 'Test code field that returns NULL is not visible.');
+ $this->assertNoText('Test code field that returns nothing', 'Test code field that returns nothing is not visible.');
+ $this->assertNoText('Test code field that returns an empty array', 'Test code field that returns an empty array is not visible.');
+ $this->assertText('Test code field that returns zero as an integer', 'Test code field that returns zero as an integer is visible.');
+ $this->assertText('Test code field that returns zero as a string', 'Test code field that returns zero as a string is visible.');
+ $this->assertText('Test code field that returns zero as a floating point number', 'Test code field that returns zero as a floating point number is visible.');
+ }
+
+ /**
+ * Test layouts hook.
+ */
+ function testDSLayouts() {
+
+ // Assert our 2 tests layouts are found.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertRaw('Test One column', 'Test One column layout found');
+ $this->assertRaw('Test Two column', 'Test Two column layout found');
+
+ $layout = array(
+ 'additional_settings[layout]' => 'dstest_2col',
+ );
+
+ $assert = array(
+ 'regions' => array(
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ ),
+ );
+
+ $fields = array(
+ 'fields[author][region]' => 'left',
+ 'fields[links][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ );
+
+ $this->dsSelectLayout($layout, $assert);
+ $this->dsConfigureUI($fields);
+
+ // Create a node.
+ $settings = array('type' => 'article');
+ $node = $this->drupalCreateNode($settings);
+
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('group-left', 'Template found (region left)');
+ $this->assertRaw('group-right', 'Template found (region right)');
+ $this->assertRaw('dstest_2col.css', 'Css file included');
+
+ // Alter a region
+ $settings = array(
+ 'type' => 'article',
+ 'title' => 'Alter me!',
+ );
+ $node = $this->drupalCreateNode($settings);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('cool!', 'Region altered');
+ }
+}
diff --git a/sites/all/modules/ds/tests/ds.entities.test b/sites/all/modules/ds/tests/ds.entities.test
new file mode 100644
index 000000000..163560912
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds.entities.test
@@ -0,0 +1,698 @@
+<?php
+
+/**
+ * @file
+ * Entities tests
+ */
+
+class dsNodeTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Node display'),
+ 'description' => t('Tests for display of nodes and fields.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ /**
+ * Utility function to setup for all kinds of tests.
+ *
+ * @param $label
+ * How the body label must be set.
+ */
+ function entitiesTestSetup($label = 'above') {
+
+ // Create a node.
+ $settings = array('type' => 'article', 'promote' => 1);
+ $node = $this->drupalCreateNode($settings);
+
+ // Create field CSS classes.
+ $edit = array('ds_classes_fields' => "test_field_class\ntest_field_class_2|Field class 2");
+ $this->drupalPost('admin/structure/ds/classes', $edit, t('Save configuration'));
+
+ // Create a token and php field.
+ $token_field = array(
+ 'name' => 'Token field',
+ 'field' => 'token_field',
+ 'entities[node]' => '1',
+ 'code[value]' => '<div class="token-class">[node:title]</span>',
+ 'use_token' => '1',
+ );
+ $php_field = array(
+ 'name' => 'PHP field',
+ 'field' => 'php_field',
+ 'entities[node]' => '1',
+ 'code[value]' => "<?php echo 'I am a PHP field'; ?>",
+ 'use_token' => '0',
+ );
+ $this->dsCreateCodeField($token_field);
+ $this->dsCreateCodeField($php_field);
+
+ // Select layout.
+ $this->dsSelectLayout();
+
+ // Configure fields.
+ $fields = array(
+ 'fields[token_field][region]' => 'header',
+ 'fields[php_field][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ 'fields[node_link][region]' => 'footer',
+ 'fields[body][label]' => $label,
+ 'fields[submitted_by][region]' => 'header',
+ );
+ $this->dsConfigureUI($fields);
+
+ return $node;
+ }
+
+ /**
+ * Utility function to clear field settings.
+ */
+ function entitiesClearFieldSettings() {
+ db_query('TRUNCATE {ds_field_settings}');
+ cache_clear_all('ds_fields:', 'cache', TRUE);
+ cache_clear_all('ds_field_settings', 'cache');
+ }
+
+ /**
+ * Set the label.
+ */
+ function entitiesSetLabelClass($label, $text = '', $class = '', $hide_colon = FALSE) {
+ $edit = array(
+ 'fields[body][label]' => $label,
+ );
+ if (!empty($text)) {
+ $edit['fields[body][settings_edit_form][settings][ft][lb]'] = $text;
+ }
+ if (!empty($class)) {
+ $edit['fields[body][settings_edit_form][settings][ft][classes][]'] = $class;
+ }
+ if ($hide_colon) {
+ $edit['fields[body][settings_edit_form][settings][ft][lb-col]'] = '1';
+ }
+ $this->dsEditFormatterSettings($edit);
+ }
+
+ /**
+ * Test basic node display fields.
+ */
+ function testDSNodeEntity() {
+
+ $node = $this->entitiesTestSetup();
+ $node_author = user_load($node->uid);
+
+ // Test theme_hook_suggestions in ds_entity_variables().
+ $this->drupalGet('node/' . $node->nid, array('query' => array('store' => 1)));
+ $cache = cache_get('ds_test');
+ $this->assertTrue(!empty($cache));
+ $hook_suggestions = $cache->data['theme_hook_suggestions'];
+ $expected_hook_suggestions = array(
+ 'node__article',
+ 'node__1',
+ 'ds_2col_stacked',
+ 'ds_2col_stacked__node',
+ 'ds_2col_stacked__node_full',
+ 'ds_2col_stacked__node_article',
+ 'ds_2col_stacked__node_article_full',
+ 'ds_2col_stacked__node__1'
+ );
+ $this->assertEqual($hook_suggestions, $expected_hook_suggestions);
+
+ // Look at node and verify token and block field.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('view-mode-full', 'Template file found (in full view mode)');
+ $this->assertRaw('<div class="token-class">' . $node->title . '</span>', t('Token field found'));
+ $this->assertRaw('I am a PHP field', t('PHP field found'));
+ $this->assertRaw('group-header', 'Template found (region header)');
+ $this->assertRaw('group-footer', 'Template found (region footer)');
+ $this->assertRaw('group-left', 'Template found (region left)');
+ $this->assertRaw('group-right', 'Template found (region right)');
+ $this->assertPattern('/<div[^>]*>Submitted[^<]*<a[^>]+href="' . preg_quote(base_path(), '/') . 'user\/' . $node_author->uid . '"[^>]*>' . check_plain($node_author->name) . '<\/a>.<\/div>/', t('Submitted by line found'));
+
+ // Configure teaser layout.
+ $teaser = array(
+ 'additional_settings[layout]' => 'ds_2col',
+ );
+ $teaser_assert = array(
+ 'regions' => array(
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ ),
+ );
+ $this->dsSelectLayout($teaser, $teaser_assert, 'admin/structure/types/manage/article/display/teaser');
+
+ $fields = array(
+ 'fields[token_field][region]' => 'left',
+ 'fields[php_field][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ 'fields[links][region]' => 'right',
+ );
+ $this->dsConfigureUI($fields, 'admin/structure/types/manage/article/display/teaser');
+
+ // Switch view mode on full node page.
+ $edit = array('ds_switch' => 'teaser');
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertRaw('view-mode-teaser', 'Switched to teaser mode');
+ $this->assertRaw('group-left', 'Template found (region left)');
+ $this->assertRaw('group-right', 'Template found (region right)');
+ $this->assertNoRaw('group-header', 'Template found (no region header)');
+ $this->assertNoRaw('group-footer', 'Template found (no region footer)');
+
+ $edit = array('ds_switch' => '');
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertRaw('view-mode-full', 'Switched to full mode again');
+
+ // Test all options of a block field.
+ $block = array(
+ 'name' => 'Test block field',
+ 'field' => 'test_block_field',
+ 'entities[node]' => '1',
+ 'block' => 'node|recent',
+ 'block_render' => DS_BLOCK_TEMPLATE,
+ );
+ $this->dsCreateBlockField($block);
+ $fields = array(
+ 'fields[test_block_field][region]' => 'left',
+ 'fields[token_field][region]' => 'hidden',
+ 'fields[php_field][region]' => 'hidden',
+ 'fields[body][region]' => 'hidden',
+ 'fields[links][region]' => 'hidden',
+ );
+ $this->dsConfigureUI($fields);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('<h2>Recent content</h2>');
+
+ $block = array(
+ 'block_render' => DS_BLOCK_TITLE_CONTENT,
+ );
+ $this->dsCreateBlockField($block, 'admin/structure/ds/fields/manage_block/test_block_field', FALSE);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoRaw('<h2>Recent content</h2>');
+ $this->assertRaw('Recent content');
+
+ $block = array(
+ 'block_render' => DS_BLOCK_CONTENT,
+ );
+ $this->dsCreateBlockField($block, 'admin/structure/ds/fields/manage_block/test_block_field', FALSE);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoRaw('<h2>Recent content</h2>');
+ $this->assertNoRaw('Recent content');
+
+ // Remove the page title (we'll use the switch view mode functionality too for this).
+ $edit = array('additional_settings[ds_page_title][ds_page_title_options][page_option_type]' => '1');
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display/teaser');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('<h1 class="title" id="page-title">
+ '. $node->title . ' </h1>');
+ $edit = array('ds_switch' => 'teaser');
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoRaw('<h1 class="title" id="page-title">
+ '. $node->title . ' </h1>');
+
+ // Use page title substitutions.
+ $edit = array('additional_settings[ds_page_title][ds_page_title_options][page_option_type]' => '2', 'additional_settings[ds_page_title][ds_page_title_options][page_option_title]' => 'Change title: %node:type');
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display/teaser');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('<h1 class="title" id="page-title">
+ Change title: '. $node->type . ' </h1>');
+ $edit = array('additional_settings[ds_page_title][ds_page_title_options][page_option_type]' => '0');
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display/teaser');
+
+ // Go to home page, page title shouldn't bleed here
+ // see http://drupal.org/node/1446554.
+ $edit = array('ds_switch' => '');
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $edit = array('additional_settings[ds_page_title][ds_page_title_options][page_option_type]' => '2', 'additional_settings[ds_page_title][ds_page_title_options][page_option_title]' => 'Bleed title: %node:type');
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('<h1 class="title" id="page-title">
+ Bleed title: article </h1>');
+ $this->drupalGet('node');
+ $this->assertNoText('Bleed title');
+
+ // Test revisions. Enable the revision view mode
+ $edit = array('additional_settings[modes][view_modes_custom][revision]' => '1');
+ $this->drupalPost('admin/structure/types/manage/article/display', $edit, t('Save'));
+
+ // Select layout and configure fields.
+ $edit = array(
+ 'additional_settings[layout]' => 'ds_2col',
+ );
+ $assert = array(
+ 'regions' => array(
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ ),
+ );
+ $this->dsSelectLayout($edit, $assert, 'admin/structure/types/manage/article/display/revision');
+ $edit = array(
+ 'fields[body][region]' => 'left',
+ 'fields[links][region]' => 'right',
+ 'fields[author][region]' => 'right',
+ );
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display/revision');
+
+ // Create revision of the node.
+ $edit = array(
+ 'revision' => TRUE,
+ 'log' => 'Test revision',
+ );
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertText('Revisions');
+
+ // Assert revision is using 2 col template.
+ $this->drupalGet('node/' . $node->nid . '/revisions/1/view');
+ $this->assertText('Body:', 'Body label');
+
+ // Change title of revision.
+ $edit = array('additional_settings[ds_page_title][ds_page_title_options][page_option_type]' => '2', 'additional_settings[ds_page_title][ds_page_title_options][page_option_title]' => 'Custom revision title');
+
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display/revision');
+ $this->drupalGet('node/' . $node->nid . '/revisions/1/view');
+ $this->assertText('Custom revision title', 'Custom title on revision view mode');
+
+ // Assert full view is using stacked template.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoText('Body:', 'Body label');
+
+ // Test formatter limit on article with tags.
+ $edit = array(
+ 'ds_switch' => '',
+ 'field_tags[und]' => 'Tag 1, Tag 2'
+ );
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $edit = array(
+ 'fields[field_tags][region]' => 'right',
+ );
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText('Tag 1');
+ $this->assertText('Tag 2');
+ $edit = array(
+ 'fields[field_tags][format][limit]' => '1',
+ );
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText('Tag 1');
+ $this->assertNoText('Tag 2');
+
+ // Test check_plain() on ds_render_field() with the title field.
+ $edit = array(
+ 'fields[title][region]' => 'right',
+ );
+ $this->dsConfigureUI($edit, 'admin/structure/types/manage/article/display');
+ $edit = array(
+ 'title' => 'Hi, I am an article <script>alert(\'with a javascript tag in the title\');</script>',
+ );
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('<h2>Hi, I am an article &lt;script&gt;alert(&#039;with a javascript tag in the title&#039;);&lt;/script&gt;</h2>');
+ }
+
+ /**
+ * Tests on field templates.
+ */
+ function testDSFieldTemplate() {
+
+ // Get a node.
+ $node = $this->entitiesTestSetup('hidden');
+ $body_field = $node->body[$node->language][0]['value'];
+
+ // -------------------------
+ // Default theming function.
+ // -------------------------
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"field field-name-body field-type-text-with-summary field-label-hidden\"><div class=\"field-items\"><div class=\"field-item even\" property=\"content:encoded\"><p>" . $body_field . "</p>
+</div></div></div>");
+
+ $this->entitiesSetLabelClass('above');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"field field-name-body field-type-text-with-summary field-label-above\"><div class=\"field-label\">Body:&nbsp;</div><div class=\"field-items\"><div class=\"field-item even\" property=\"content:encoded\"><p>" . $body_field . "</p>
+</div></div></div>");
+
+ $this->entitiesSetLabelClass('above', 'My body');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"field field-name-body field-type-text-with-summary field-label-above\"><div class=\"field-label\">My body:&nbsp;</div><div class=\"field-items\"><div class=\"field-item even\" property=\"content:encoded\"><p>" . $body_field . "</p>
+</div></div></div>");
+
+ $this->entitiesSetLabelClass('hidden', '', 'test_field_class');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"field field-name-body field-type-text-with-summary field-label-hidden test_field_class\"><div class=\"field-items\"><div class=\"field-item even\" property=\"content:encoded\"><p>" . $body_field . "</p>
+</div></div></div>");
+
+ $this->entitiesClearFieldSettings();
+
+ // -----------------------
+ // Reset theming function.
+ // -----------------------
+ $edit = array(
+ 'additional_settings[fs1][ft-default]' => 'theme_ds_field_reset',
+ );
+ $this->drupalPost('admin/structure/ds/list/extras', $edit, t('Save configuration'));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <p>" . $body_field . "</p>");
+
+ $this->entitiesSetLabelClass('above');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"label-above\">Body:&nbsp;</div><p>" . $body_field . "</p>");
+
+ $this->entitiesSetLabelClass('inline');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"label-inline\">Body:&nbsp;</div><p>" . $body_field . "</p>");
+
+ $this->entitiesSetLabelClass('above', 'My body');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"label-above\">My body:&nbsp;</div><p>" . $body_field . "</p>");
+
+ $this->entitiesSetLabelClass('inline', 'My body');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"label-inline\">My body:&nbsp;</div><p>" . $body_field . "</p>");
+
+ variable_set('ft-kill-colon', TRUE);
+ $this->refreshVariables();
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"label-inline\">My body</div><p>" . $body_field . "</p>");
+
+ $this->entitiesSetLabelClass('hidden');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <p>" . $body_field . "</p>");
+ $this->entitiesClearFieldSettings();
+
+ // ----------------------
+ // Custom field function.
+ // ----------------------
+
+ // With outer wrapper.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][func]' => 'theme_ds_field_expert',
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ );
+ $this->dsEditFormatterSettings($edit);
+
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div><p>" . $body_field . "</p>
+</div> </div>");
+
+ // With outer div wrapper and class.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class'
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><p>" . $body_field . "</p>
+</div> </div>");
+
+ // With outer span wrapper and class.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class-2'
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <span class=\"ow-class-2\"><p>" . $body_field . "</p>
+</span> </div>");
+
+ // Clear field settings.
+ $this->entitiesClearFieldSettings();
+
+ // With outer wrapper and field items wrapper.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][func]' => 'theme_ds_field_expert',
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'div'
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div><div><p>" . $body_field . "</p>
+</div></div> </div>");
+
+ // With outer wrapper and field items div wrapper with class.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fis-cl]' => 'fi-class'
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div><div class=\"fi-class\"><p>" . $body_field . "</p>
+</div></div> </div>");
+
+ // With outer wrapper and field items span wrapper and class.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][fis-cl]' => 'fi-class'
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div><span class=\"fi-class\"><p>" . $body_field . "</p>
+</span></div> </div>");
+
+ // With outer wrapper class and field items span wrapper and class.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class',
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][fis-cl]' => 'fi-class'
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><span class=\"fi-class\"><p>" . $body_field . "</p>
+</span></div> </div>");
+
+ // With outer wrapper span class and field items span wrapper and class.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class',
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][fis-cl]' => 'fi-class-2'
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <span class=\"ow-class\"><span class=\"fi-class-2\"><p>" . $body_field . "</p>
+</span></span> </div>");
+
+ // Clear field settings.
+ $this->entitiesClearFieldSettings();
+
+ // With field item div wrapper.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][func]' => 'theme_ds_field_expert',
+ 'fields[body][settings_edit_form][settings][ft][fi]' => '1',
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div><p>" . $body_field . "</p>
+</div> </div>");
+
+ // With field item span wrapper.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][fi]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-el]' => 'span',
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <span><p>" . $body_field . "</p>
+</span> </div>");
+
+ // With field item span wrapper and class and odd even.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][fi]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][fi-cl]' => 'fi-class',
+ 'fields[body][settings_edit_form][settings][ft][fi-odd-even]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-first-last]' => '1',
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span> </div>");
+
+ // With fis and fi.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fis-cl]' => 'fi-class-2',
+ 'fields[body][settings_edit_form][settings][ft][fi]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fi-cl]' => 'fi-class',
+ 'fields[body][settings_edit_form][settings][ft][fi-odd-even]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-first-last]' => '1',
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"fi-class-2\"><div class=\"even fi-class\"><p>" . $body_field . "</p>
+</div></div> </div>");
+ // With all wrappers.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class',
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fis-cl]' => 'fi-class-2',
+ 'fields[body][settings_edit_form][settings][ft][fi]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][fi-cl]' => 'fi-class',
+ 'fields[body][settings_edit_form][settings][ft][fi-odd-even]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-first-last]' => '1',
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><div class=\"fi-class-2\"><span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ // With all wrappers and attributes.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class',
+ 'fields[body][settings_edit_form][settings][ft][ow-at]' => 'name="ow-att"',
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fis-cl]' => 'fi-class-2',
+ 'fields[body][settings_edit_form][settings][ft][fis-at]' => 'name="fis-att"',
+ 'fields[body][settings_edit_form][settings][ft][fi]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][fi-cl]' => 'fi-class',
+ 'fields[body][settings_edit_form][settings][ft][fi-at]' => 'name="fi-at"',
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\" name=\"ow-att\"><div class=\"fi-class-2\" name=\"fis-att\"><span class=\"even fi-class\" name=\"fi-at\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ // Remove attributes.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class',
+ 'fields[body][settings_edit_form][settings][ft][ow-at]' => '',
+ 'fields[body][settings_edit_form][settings][ft][fis]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fis-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][fis-cl]' => 'fi-class-2',
+ 'fields[body][settings_edit_form][settings][ft][fis-at]' => '',
+ 'fields[body][settings_edit_form][settings][ft][fi]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-el]' => 'span',
+ 'fields[body][settings_edit_form][settings][ft][fi-cl]' => 'fi-class',
+ 'fields[body][settings_edit_form][settings][ft][fi-at]' => '',
+ );
+ $this->dsEditFormatterSettings($edit);
+
+ // Label tests with custom function.
+ $this->entitiesSetLabelClass('above');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><div class=\"label-above\">Body:&nbsp;</div><div class=\"fi-class-2\"><span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ $this->entitiesSetLabelClass('inline');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><div class=\"label-inline\">Body:&nbsp;</div><div class=\"fi-class-2\"><span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ $this->entitiesSetLabelClass('above', 'My body');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><div class=\"label-above\">My body:&nbsp;</div><div class=\"fi-class-2\"><span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ $this->entitiesSetLabelClass('inline', 'My body');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><div class=\"label-inline\">My body:&nbsp;</div><div class=\"fi-class-2\"><span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ $this->entitiesSetLabelClass('inline', 'My body', '', TRUE);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><div class=\"label-inline\">My body</div><div class=\"fi-class-2\"><span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ $this->entitiesSetLabelClass('hidden');
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class\"><div class=\"fi-class-2\"><span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ // Test default classes on outer wrapper.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class',
+ 'fields[body][settings_edit_form][settings][ft][ow-def-cl]' => '1',
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class field field-name-body field-type-text-with-summary field-label-hidden\"><div class=\"fi-class-2\"><span class=\"even fi-class\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ // Test default attributes on field item.
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][ow]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][ow-el]' => 'div',
+ 'fields[body][settings_edit_form][settings][ft][ow-cl]' => 'ow-class',
+ 'fields[body][settings_edit_form][settings][ft][ow-def-cl]' => '1',
+ 'fields[body][settings_edit_form][settings][ft][fi-def-at]' => '1',
+ );
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ <div class=\"ow-class field field-name-body field-type-text-with-summary field-label-hidden\"><div class=\"fi-class-2\"><span class=\"even fi-class\" property=\"content:encoded\"><p>" . $body_field . "</p>
+</span></div></div> </div>");
+
+ // Use the test field theming function to test that this function is
+ // registered in the theme registry through ds_extras_theme().
+ $edit = array(
+ 'fields[body][settings_edit_form][settings][ft][func]' => 'ds_test_theming_function',
+ );
+
+ $this->dsEditFormatterSettings($edit);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw("<div class=\"group-right\">
+ Testing field output through custom function </div>");
+ }
+}
diff --git a/sites/all/modules/ds/tests/ds.exportables.test b/sites/all/modules/ds/tests/ds.exportables.test
new file mode 100644
index 000000000..95b95d6af
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds.exportables.test
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @file
+ * Base functions and tests for Display Suite.
+ */
+
+class dsExportablesTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Exportables'),
+ 'description' => t('Tests for exportables in Display Suite.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ /**
+ * Enables the exportables module.
+ */
+ function dsExportablesSetup() {
+ module_enable(array('ds_exportables_test'));
+ drupal_flush_all_caches();
+ }
+
+ // Test view modes.
+ function testDSExportablesViewmodes() {
+ $this->dsExportablesSetup();
+
+ // Find a default view mode on admin screen.
+ $this->drupalGet('admin/structure/ds/view_modes');
+ $this->assertText('Test exportables', t('Exportables view mode found on admin screen.'));
+
+ // Find default view mode on layout screen.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertText('Test exportables', t('Exportables view mode found on display screen.'));
+
+ // Override default view mode.
+ $edit = array(
+ 'name' => 'Testing 2',
+ );
+ $this->drupalPost('admin/structure/ds/view_modes/manage/test_exportables', $edit, t('Save'));
+ $this->assertText(t('The view mode Testing 2 has been saved'), t('Exportables label updated'));
+ $this->assertText(t('Revert'), t('Revert button found.'));
+
+ // Find default view mode on layout screen.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertText('Testing 2', t('Updated exportables view mode found on display screen.'));
+
+ // Revert the view mode.
+ $this->drupalPost('admin/structure/ds/view_modes/revert/test_exportables', array(), t('Revert'));
+ $this->assertText(t('The view mode Testing 2 has been reverted'), t('Testing view mode reverted'));
+ $this->assertText('Test exportables', t('Exportables view mode found on admin screen.'));
+
+ // Assert the view mode is gone at the manage display screen.
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoText('Testing 2', t('Overrided exportables view mode not found on display screen.'));
+ $this->assertText('Test exportables', t('Default exportables view mode found on display screen.'));
+ }
+
+ // Test layout and field settings configuration.
+ function testDSExportablesLayoutFieldsettings() {
+ $this->dsExportablesSetup();
+
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoText(t('This layout is overridden. Click to revert to default settings.'));
+
+ $settings = array(
+ 'type' => 'article',
+ 'title' => 'Exportable'
+ );
+ $node = $this->drupalCreateNode($settings);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('group-left', 'Left region found');
+ $this->assertRaw('group-right', 'Right region found');
+ $this->assertNoRaw('group-header', 'No header region found');
+ $this->assertNoRaw('group-footer', 'No footer region found');
+ $this->assertRaw('<h3><a href="'. url('node/1') . '" class="active">Exportable</a></h3>', t('Default title with h3 found'));
+ $this->assertRaw('<a href="' . url('node/1') . '" class="active">Read more</a>', t('Default read more found'));
+
+ // Override default layout.
+ $layout = array(
+ 'additional_settings[layout]' => 'ds_2col_stacked',
+ );
+
+ $assert = array(
+ 'regions' => array(
+ 'header' => '<td colspan="8">' . t('Header') . '</td>',
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ 'footer' => '<td colspan="8">' . t('Footer') . '</td>',
+ ),
+ );
+
+ $fields = array(
+ 'fields[post_date][region]' => 'header',
+ 'fields[author][region]' => 'left',
+ 'fields[links][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ 'fields[comments][region]' => 'footer',
+ );
+
+ $this->dsSelectLayout($layout, $assert);
+ $this->assertText(t('This layout is overridden. Click to revert to default settings.'));
+ $this->dsConfigureUI($fields);
+
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('group-left', 'Left region found');
+ $this->assertRaw('group-right', 'Left region found');
+ $this->assertRaw('group-header', 'Left region found');
+ $this->assertRaw('group-footer', 'Left region found');
+
+ // Revert.
+ $edit = array();
+ $this->drupalPost('admin/structure/ds/revert-layout/node|article|default', $edit, t('Revert'), array('query' => array('destination' => 'admin/structure/types/manage/article/display')));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw('group-left', 'Left region found');
+ $this->assertRaw('group-right', 'Left region found');
+ $this->assertNoRaw('group-header', 'Left region found');
+ $this->assertNoRaw('group-footer', 'Left region found');
+ $this->assertRaw('<h3><a href="'. url('node/1') . '" class="active">Exportable</a></h3>', t('Default title with h3 found'));
+ $this->assertRaw('<a href="' . url('node/1') . '" class="active">Read more</a>', t('Default read more found'));
+ }
+
+ // Test custom field exportables.
+ function testDSExportablesCustomFields() {
+ $this->dsExportablesSetup();
+
+ // Look for default custom field.
+ $this->drupalGet('admin/structure/ds/fields');
+ $this->assertText('Exportable field');
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertText('Exportable field');
+
+ // Override custom field.
+ // Update testing label
+ $edit = array(
+ 'name' => 'Overridden field',
+ );
+ $this->drupalPost('admin/structure/ds/fields/manage_custom/ds_exportable_field', $edit, t('Save'));
+ $this->assertText(t('The field Overridden field has been saved'), t('Default exportable field label updated'));
+ $this->assertText('Overridden field');
+ $this->assertNoText('Exportable field');
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertText('Overridden field');
+ $this->assertNoText('Exportable field');
+
+ // Revert.
+ $edit = array();
+ $this->drupalPost('admin/structure/ds/fields/revert/ds_exportable_field', $edit, t('Revert'));
+ $this->assertText('The field Overridden field has been reverted', t('Field reverted'));
+ $this->assertText('Exportable field');
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertNoText('Overridden field');
+ $this->assertText('Exportable field');
+ }
+}
diff --git a/sites/all/modules/ds/tests/ds.forms.test b/sites/all/modules/ds/tests/ds.forms.test
new file mode 100644
index 000000000..85069ed46
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds.forms.test
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Form layout tests.
+ */
+
+class dsFormTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Forms'),
+ 'description' => t('Tests for managing layouts on forms.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ /**
+ * Forms tests.
+ */
+ function testDSForms() {
+
+ // Create a node.
+ $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => FALSE));
+
+ // Configure teaser layout.
+ $form = array(
+ 'additional_settings[layout]' => 'ds_2col_stacked',
+ );
+ $form_assert = array(
+ 'regions' => array(
+ 'header' => '<td colspan="8">' . t('Header') . '</td>',
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ 'footer' => '<td colspan="8">' . t('Footer') . '</td>',
+ ),
+ );
+ $this->dsSelectLayout($form, $form_assert, 'admin/structure/types/manage/article/fields');
+
+ $fields = array(
+ 'fields[title][region]' => 'header',
+ 'fields[body][region]' => 'left',
+ 'fields[field_image][region]' => 'right',
+ 'fields[field_tags][region]' => 'right',
+ );
+ $this->dsConfigureUI($fields, 'admin/structure/types/manage/article/fields');
+
+ // Inspect the node.
+ $this->drupalGet('node/' . $node->nid . '/edit');
+ $this->assertRaw('ds_2col_stacked', 'ds-form class added');
+ $this->assertRaw('group-header', 'Template found (region header)');
+ $this->assertRaw('group-left', 'Template found (region left)');
+ $this->assertRaw('group-right', 'Template found (region right)');
+ $this->assertRaw('group-footer', 'Template found (region footer)');
+ $this->assertRaw('edit-title', 'Title field found');
+ $this->assertRaw('edit-submit', 'Submit field found');
+ $this->assertRaw('edit-field-tags-und', 'Tags field found');
+ $this->assertRaw('edit-log', 'Revision log found');
+ }
+}
diff --git a/sites/all/modules/ds/tests/ds.search.test b/sites/all/modules/ds/tests/ds.search.test
new file mode 100644
index 000000000..22989b8ef
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds.search.test
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @file
+ * Search tests
+ */
+
+class dsSearchTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Search'),
+ 'description' => t('Tests for display of search results for nodes and users.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ function testDSSearch() {
+
+ // Create nodes.
+ $i = 15;
+ while ($i > 0) {
+ $settings = array(
+ 'title' => 'title' . $i,
+ 'type' => 'article',
+ 'promote' => 1,
+ );
+ $this->drupalCreateNode($settings);
+ $i--;
+ }
+
+ // Set default search.
+ $edit = array(
+ 'search_default_module' => 'ds_search',
+ );
+ $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration'));
+
+ // Run cron.
+ $this->cronRun();
+ $this->drupalGet('admin/config/search/settings');
+ $this->assertText(t('100% of the site has been indexed. There are 0 items left to index.'), 'Site has been indexed');
+
+ // Configure search result view mode.
+ $svm = array('additional_settings[modes][view_modes_custom][search_result]' => 'search_result');
+ $this->dsConfigureUI($svm);
+ $layout = array(
+ 'additional_settings[layout]' => 'ds_2col_stacked',
+ );
+ $assert = array(
+ 'regions' => array(
+ 'header' => '<td colspan="8">' . t('Header') . '</td>',
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ 'footer' => '<td colspan="8">' . t('Footer') . '</td>',
+ ),
+ );
+ $this->dsSelectLayout($layout, $assert, 'admin/structure/types/manage/article/display/search_result');
+ $fields = array(
+ 'fields[title][region]' => 'header',
+ 'fields[post_date][region]' => 'header',
+ 'fields[author][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ 'fields[node_link][region]' => 'footer',
+ );
+ $this->dsConfigureUI($fields, 'admin/structure/types/manage/article/display/search_result');
+
+ // Configure ds search.
+ $edit = array('ds_user_override_search_page' => '1');
+ $this->drupalPost('admin/structure/ds/list/search', $edit, t('Save configuration'));
+
+ // Let's search.
+ $this->drupalGet('search/content/title1');
+ $this->assertNoRaw('/search/node/title1');
+ $this->assertRaw('view-mode-search_result', 'Search view mode found');
+ $this->assertRaw('group-left', 'Search template found');
+ $this->assertRaw('group-right', 'Search template found');
+ $this->assertNoText(t('Advanced search'), 'No advanced search found');
+
+
+ $edit = array('ds_search_node_form_alter' => '1');
+ $this->drupalPost('admin/structure/ds/list/search', $edit, t('Save configuration'));
+ $this->drupalGet('search/content/title1');
+ $this->assertText(t('Advanced search'), 'Advanced search found');
+
+ // Search on user.
+ // Configure user. We'll just do default.
+ $layout = array(
+ 'additional_settings[layout]' => 'ds_2col_stacked',
+ );
+ $assert = array(
+ 'regions' => array(
+ 'header' => '<td colspan="8">' . t('Header') . '</td>',
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ 'footer' => '<td colspan="8">' . t('Footer') . '</td>',
+ ),
+ );
+ $this->dsSelectLayout($layout, $assert, 'admin/config/people/accounts/display');
+ $fields = array(
+ 'fields[name][region]' => 'left',
+ 'fields[summary][region]' => 'right',
+ );
+ $this->dsConfigureUI($fields, 'admin/config/people/accounts/display');
+
+ $this->drupalGet('search/user/' . $this->admin_user->name);
+ $this->assertRaw('view-mode-search_result', 'Search view mode found');
+ $this->assertRaw('group-left', 'Search template found');
+ $this->assertRaw('group-right', 'Search template found');
+
+ // Test the group by settings.
+ $article = array(
+ 'title' => 'group article 1',
+ 'type' => 'article',
+ 'promote' => 1,
+ );
+ $this->drupalCreateNode($article);
+
+ $page = array(
+ 'title' => 'group page 1',
+ 'type' => 'page',
+ 'promote' => 1,
+ );
+ $this->drupalCreateNode($page);
+ $this->cronRun();
+
+ $edit = array(
+ 'ds_search_group_by_type' => '1'
+ );
+ $this->drupalPost('admin/structure/ds/list/search', $edit, t('Save configuration'));
+
+ // Let's search.
+ $this->drupalGet('search/content/group');
+ $this->assertRaw('Results for article');
+ $this->assertRaw('Results for basic page');
+
+ $edit = array(
+ 'ds_search_group_by_type_settings[article][label]' => 'Article results',
+ );
+ $this->drupalPost('admin/structure/ds/list/search', $edit, t('Save configuration'));
+ $this->drupalGet('search/content/group');
+ $this->assertNoRaw('Results for article');
+ $this->assertRaw('Article results');
+ $this->assertRaw('Results for basic page');
+
+ $edit = array(
+ 'ds_search_group_by_type_settings[page][status]' => FALSE,
+ 'ds_search_group_by_type_settings[article][label]' => '',
+ );
+ $this->drupalPost('admin/structure/ds/list/search', $edit, t('Save configuration'));
+ $this->drupalGet('search/content/group');
+ $this->assertNoRaw('Article results');
+ $this->assertNoRaw('Results for basic page');
+ $this->assertRaw('Other');
+ }
+}
diff --git a/sites/all/modules/ds/tests/ds.views.test b/sites/all/modules/ds/tests/ds.views.test
new file mode 100644
index 000000000..039a99f5b
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds.views.test
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @file
+ * Entities tests
+ */
+
+class dsViewsTests extends dsBaseTest {
+
+ /**
+ * Implements getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Views'),
+ 'description' => t('Tests for Display Suite Views integration.'),
+ 'group' => t('Display Suite'),
+ );
+ }
+
+ /**
+ * Test views integration.
+ */
+ function testDSViews() {
+
+ $edit_tag_1 = array(
+ 'field_tags[und]' => 'Tag 1',
+ );
+ $edit_tag_2 = array(
+ 'field_tags[und]' => 'Tag 2',
+ );
+
+ // Create 3 nodes.
+ $settings_1 = array(
+ 'type' => 'article',
+ 'title' => 'Article 1',
+ 'created' => REQUEST_TIME,
+ );
+ $node_1 = $this->drupalCreateNode($settings_1);
+ $this->drupalPost('node/' . $node_1->nid . '/edit', $edit_tag_1, t('Save'));
+ $settings_2 = array(
+ 'type' => 'article',
+ 'title' => 'Article 2',
+ 'created' => REQUEST_TIME + 3600,
+ );
+ $node_2 = $this->drupalCreateNode($settings_2);
+ $this->drupalPost('node/' . $node_2->nid . '/edit', $edit_tag_1, t('Save'));
+ $settings_3 = array(
+ 'type' => 'article',
+ 'title' => 'Article 3',
+ 'created' => REQUEST_TIME + 7200,
+ );
+ $node_3 = $this->drupalCreateNode($settings_3);
+ $this->drupalPost('node/' . $node_3->nid . '/edit', $edit_tag_2, t('Save'));
+
+ // Configure teaser and full layout.
+ $layout = array(
+ 'additional_settings[layout]' => 'ds_2col',
+ );
+ $fields = array(
+ 'fields[title][region]' => 'left',
+ 'fields[body][region]' => 'right',
+ );
+ $assert = array(
+ 'regions' => array(
+ 'left' => '<td colspan="8">' . t('Left') . '</td>',
+ 'right' => '<td colspan="8">' . t('Right') . '</td>',
+ ),
+ );
+ $this->dsSelectLayout($layout, $assert, 'admin/structure/types/manage/article/display/teaser');
+ $this->dsConfigureUI($fields, 'admin/structure/types/manage/article/display/teaser');
+ $layout = array(
+ 'additional_settings[layout]' => 'ds_4col',
+ );
+ $fields = array(
+ 'fields[post_date][region]' => 'first',
+ 'fields[body][region]' => 'second',
+ 'fields[author][region]' => 'third',
+ 'fields[node_link][region]' => 'fourth',
+ );
+ $assert = array(
+ 'regions' => array(
+ 'first' => '<td colspan="8">' . t('First') . '</td>',
+ 'second' => '<td colspan="8">' . t('Second') . '</td>',
+ 'third' => '<td colspan="8">' . t('Third') . '</td>',
+ 'fourth' => '<td colspan="8">' . t('Fourth') . '</td>',
+ ),
+ );
+ $this->dsSelectLayout($layout, $assert);
+ $this->dsConfigureUI($fields);
+
+ // Get default teaser view.
+ $this->drupalGet('ds-testing');
+ foreach (array('group-left', 'group-right') as $region) {
+ $this->assertRaw($region, t('Region !region found', array('!region' => $region)));
+ }
+ $this->assertRaw('Article 1');
+ $this->assertRaw('Article 2');
+ $this->assertRaw('Article 3');
+
+ // Get alternating view.
+ $this->drupalGet('ds-testing-2');
+ foreach (array('group-left', 'group-right', 'first', 'second', 'third', 'fourth') as $region) {
+ $this->assertRaw($region, t('Region !region found', array('!region' => $region)));
+ }
+ $this->assertNoRaw('Article 1');
+ $this->assertRaw('Article 2');
+ $this->assertRaw('Article 3');
+
+ // Get grouping view (without changing header function).
+ $this->drupalGet('ds-testing-3');
+ foreach (array('group-left', 'group-right') as $region) {
+ $this->assertRaw($region, t('Region !region found', array('!region' => $region)));
+ }
+ $this->assertRaw('Article 1');
+ $this->assertRaw('Article 2');
+ $this->assertRaw('Article 3');
+ $this->assertRaw('<h2 class="grouping-title">1</h2>');
+ $this->assertRaw('<h2 class="grouping-title">2</h2>');
+
+ // Get grouping view (with changing header function).
+ $this->drupalGet('ds-testing-4');
+ foreach (array('group-left', 'group-right') as $region) {
+ $this->assertRaw($region, t('Region !region found', array('!region' => $region)));
+ }
+ $this->assertRaw('Article 1');
+ $this->assertRaw('Article 2');
+ $this->assertRaw('Article 3');
+ $this->assertRaw('<h2 class="grouping-title">Tag 1</h2>');
+ $this->assertRaw('<h2 class="grouping-title">Tag 2</h2>');
+
+ // Get advanced function view.
+ $this->drupalGet('ds-testing-5');
+ $this->assertRaw('Advanced display for id 1');
+ $this->assertRaw('Advanced display for id 2');
+ $this->assertRaw('Advanced display for id 3');
+
+ // Test views templates overrides.
+ $this->drupalGet('admin/structure/ds/vd/manage/ds_testing-page_5/display');
+ $this->assertText('No view found to layout.');
+ $edit = array(
+ 'vd' => 'ds_testing-page_5',
+ );
+ $this->drupalPost('admin/structure/ds/vd', $edit, t('Add'));
+ $this->dsSelectLayout(array(), array(), 'admin/structure/ds/vd/manage/ds_testing-page_5/display');
+ $edit = array(
+ 'fields[pager][region]' => 'header',
+ 'fields[footer][region]' => 'header',
+ 'fields[rows][region]' => 'left',
+ 'fields[exposed][region]' => 'right',
+ 'fields[header][region]' => 'footer',
+ );
+ $this->dsConfigureUI($edit, 'admin/structure/ds/vd/manage/ds_testing-page_5/display');
+
+ // Test on the views page itself.
+ $this->drupalGet('ds-testing-6');
+ $this->assertRaw('<div class="group-header">
+ <p>Footer text</p>
+ </div>');
+ $this->assertRaw('<div class="group-left">
+ <div class="views-row views-row-1 views-row-odd views-row-first">');
+ $this->assertRaw('<div class="group-right">
+ <form action="' . url('ds-testing-6') . '"');
+ $this->assertRaw('<div class="group-footer">
+ <p>Header text</p>');
+ }
+}
diff --git a/sites/all/modules/ds/tests/ds_exportables_test/ds_exportables_test.info b/sites/all/modules/ds/tests/ds_exportables_test/ds_exportables_test.info
new file mode 100644
index 000000000..20dca2f41
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds_exportables_test/ds_exportables_test.info
@@ -0,0 +1,12 @@
+name = Display Suite exportables test
+description = Tests for exportables with Display Suite.
+package = "Display Suite"
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/tests/ds_exportables_test/ds_exportables_test.module b/sites/all/modules/ds/tests/ds_exportables_test/ds_exportables_test.module
new file mode 100644
index 000000000..52b914abe
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds_exportables_test/ds_exportables_test.module
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Bulk export of objects generated by Bulk export module.
+ */
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function ds_exportables_test_ctools_plugin_api($module, $api) {
+ if (($module == 'ds' && $api == 'ds') || ($module == 'ds_extras' && $api == 'ds_extras')) {
+ return array('version' => 1);
+ }
+}
+
+/**
+ * Implements hook_ds_custom_fields_info().
+ */
+function ds_exportables_test_ds_custom_fields_info() {
+ $ds_fields = array();
+
+ $ds_field = new stdClass;
+ $ds_field->api_version = 1;
+ $ds_field->field = 'ds_exportable_field';
+ $ds_field->label = 'Exportable field';
+ $ds_field->field_type = 5;
+ $ds_field->entities = array(
+ 'node' => 'node',
+ );
+ $ds_field->properties = array(
+ 'code' => array(
+ 'value' => '<?php print "This is an exportable field"; ?>',
+ 'format' => 'ds_code',
+ ),
+ 'use_token' => 0,
+ );
+ $ds_fields['ds_exportable_field'] = $ds_field;
+
+ return $ds_fields;
+}
+
+/**
+ * Implements hook_ds_field_settings_info().
+ */
+function ds_exportables_test_ds_field_settings_info() {
+ $ds_fieldsettings = array();
+
+ $ds_fieldsetting = new stdClass;
+ $ds_fieldsetting->disabled = FALSE; /* Edit this to true to make a default dsfieldsetting disabled initially */
+ $ds_fieldsetting->api_version = 1;
+ $ds_fieldsetting->id = 'node|article|default';
+ $ds_fieldsetting->entity_type = 'node';
+ $ds_fieldsetting->bundle = 'article';
+ $ds_fieldsetting->view_mode = 'default';
+ $ds_fieldsetting->settings = array(
+ 'title' => array(
+ 'weight' => '0',
+ 'label' => 'hidden',
+ 'format' => 'default',
+ 'formatter_settings' => array(
+ 'link' => '1',
+ 'wrapper' => 'h3',
+ 'class' => '',
+ ),
+ ),
+ 'node_link' => array(
+ 'weight' => '1',
+ 'label' => 'hidden',
+ 'format' => 'default',
+ ),
+ );
+ $ds_fieldsettings['node|article|default'] = $ds_fieldsetting;
+
+ return $ds_fieldsettings;
+}
+
+/**
+ * Implements hook_ds_layout_settings_info().
+ */
+function ds_exportables_test_ds_layout_settings_info() {
+ $ds_layouts = array();
+
+ $ds_layout = new stdClass;
+ $ds_layout->disabled = FALSE; /* Edit this to true to make a default dslayout disabled initially */
+ $ds_layout->api_version = 1;
+ $ds_layout->id = 'node|article|default';
+ $ds_layout->entity_type = 'node';
+ $ds_layout->bundle = 'article';
+ $ds_layout->view_mode = 'default';
+ $ds_layout->layout = 'ds_2col';
+ $ds_layout->settings = array(
+ 'regions' => array(
+ 'left' => array(
+ 0 => 'title',
+ 1 => 'node_link',
+ ),
+ 'right' => array(
+ 0 => 'body',
+ ),
+ ),
+ 'fields' => array(
+ 'title' => 'left',
+ 'node_link' => 'left',
+ 'body' => 'right',
+ ),
+ 'classes' => array(),
+ );
+ $ds_layouts['node|article|default'] = $ds_layout;
+
+ return $ds_layouts;
+}
+
+/**
+ * Implements hook_ds_view_modes_info().
+ */
+function ds_exportables_test_ds_view_modes_info() {
+ $ds_view_modes = array();
+
+ $ds_view_mode = new stdClass;
+ $ds_view_mode->disabled = FALSE; /* Edit this to true to make a default ds_view_mode disabled initially */
+ $ds_view_mode->api_version = 1;
+ $ds_view_mode->view_mode = 'test_exportables';
+ $ds_view_mode->label = 'Test exportables';
+ $ds_view_mode->entities = array(
+ 'node' => 'node',
+ );
+ $ds_view_modes['test_exportables'] = $ds_view_mode;
+
+ return $ds_view_modes;
+}
+
+/**
+ * Implements hook_ds_vd_info().
+ */
+function ds_exportables_test_ds_vd_info() {
+ $ds_vds = array();
+
+ $ds_vd = new stdClass;
+ $ds_vd->api_version = 1;
+ $ds_vd->vd = 'frontpage-page';
+ $ds_vd->label = 'Frontpage: Views displays';
+ $ds_vds['frontpage-page'] = $ds_vd;
+
+ return $ds_vds;
+}
diff --git a/sites/all/modules/ds/tests/ds_test.info b/sites/all/modules/ds/tests/ds_test.info
new file mode 100644
index 000000000..79b345f4f
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds_test.info
@@ -0,0 +1,13 @@
+name = "Display Suite Test"
+description = "Test module for Display Suite"
+core = "7.x"
+package = "Display Suite"
+dependencies[] = ds_extras
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2016-02-11
+version = "7.x-2.13"
+core = "7.x"
+project = "ds"
+datestamp = "1455211441"
+
diff --git a/sites/all/modules/ds/tests/ds_test.module b/sites/all/modules/ds/tests/ds_test.module
new file mode 100644
index 000000000..2bde1d934
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds_test.module
@@ -0,0 +1,321 @@
+<?php
+
+/**
+ * @file
+ * Display Suite test module.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function ds_test_install() {
+ variable_set('ds_extras_region_to_block', TRUE);
+ variable_set('ds_extras_switch_view_mode', TRUE);
+ variable_set('ds_extras_hide_page_title', TRUE);
+ variable_set('ds_extras_field_template', TRUE);
+ variable_set('ds_extras_fields_extra', TRUE);
+ variable_set('ds_extras_hide_page_sidebars', TRUE);
+ variable_set('ds_extras_fields_extra_list', "node|article|ds_extras_extra_test_field\nnode|article|ds_extras_second_field");
+ variable_set('ds_extras_vd', TRUE);
+
+ db_update('system')
+ ->fields(array('weight' => 2))
+ ->condition('name', 'ds_test')
+ ->execute();
+}
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function ds_test_theme_registry_alter(&$theme_registry) {
+
+ // Inject ds_entity_variables in all entity theming functions.
+ $entity_info = entity_get_info();
+ foreach ($entity_info as $entity => $info) {
+ if (isset($entity_info[$entity]['fieldable']) && $entity_info[$entity]['fieldable']) {
+
+ // User uses user_profile for theming.
+ if ($entity == 'user') $entity = 'user_profile';
+
+ // Only add preprocess functions if entity exposes theme function.
+ if (isset($theme_registry[$entity])) {
+ $theme_registry[$entity]['preprocess functions'][] = 'ds_test_entity_variables';
+ }
+ }
+ }
+
+ // Support for File Entity.
+ if (isset($theme_registry['file_entity'])) {
+ $theme_registry['file_entity']['preprocess functions'][] = 'ds_test_entity_variables';
+ }
+
+ // Support for Entity API.
+ if (isset($theme_registry['entity'])) {
+ $theme_registry['entity']['preprocess functions'][] = 'ds_test_entity_variables';
+ }
+}
+
+/**
+ * Theming function, added after ds_entity_variables().
+ *
+ * @param $variables
+ */
+function ds_test_entity_variables(&$variables) {
+ if (isset($_GET['store'])) {
+ cache_set('ds_test', $variables);
+ }
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function ds_test_views_api($module, $api) {
+ if ($module == 'views' && $api == 'views_default') {
+ return array('version' => 3);
+ }
+}
+
+/**
+ * Helper function to return the tag name basid on tid.
+ */
+function ds_test_get_tag_name($raw_value, $object) {
+ $term = taxonomy_term_load($raw_value);
+ return $term->name;
+}
+
+/**
+ * Helper function to return advanced view mode.
+ */
+function ds_views_row_adv_ds_testing($entity, $view_mode, $load_comments) {
+ return 'Advanced display for id ' . $entity->nid;
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function ds_test_field_extra_fields() {
+ $extra = array();
+
+ // Register a single field to test that
+ // extra fields in the hidden region are really hidden.
+ $extra['node']['article']['display']['heavy_field'] = array(
+ 'label' => t('Heavy extra test field'),
+ 'weight' => 10,
+ );
+
+ return $extra;
+}
+
+/**
+ * Implements hook_node_view().
+ */
+function ds_test_node_view($node, $view_mode, $langcode) {
+ $node->content['ds_extras_extra_test_field'] = array(
+ '#markup' => 'This is an extra field made available through "Extra fields" functionality.',
+ '#weight' => 10,
+ );
+
+ // Check whether the heavy extra field is rendered or not.
+ if ($node->type == 'article') {
+ $fields = field_extra_fields_get_display('node', 'article', $view_mode);
+ if (isset($fields['heavy_field']) && $fields['heavy_field']['visible']) {
+ $node->content['heavy_field'] = array(
+ '#markup' => 'Heavy field',
+ '#weight' => $fields['heavy_field']['weight'],
+ );
+ }
+ }
+}
+
+/**
+ * Implements hook_ds_layout_info_alter().
+ */
+function ds_test_ds_layout_info_alter(&$layouts) {
+ unset($layouts['ds_3col_stacked_equal_width']);
+}
+
+/**
+ * Implements hook_ds_fields_info().
+ */
+function ds_test_ds_fields_info($entity_type) {
+ if ($entity_type == 'node') {
+ $fields['node']['ds_test_field'] = array(
+ 'title' => t('Test code field from hook'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_test_field',
+ );
+ $fields['node']['ds_test_field_2'] = array(
+ 'title' => t('Test code field from hook 2'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_test_field',
+ );
+ $fields['node']['ds_test_field_empty_string'] = array(
+ 'title' => t('Test code field that returns an empty string'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_empty_string',
+ );
+ $fields['node']['ds_test_field_false'] = array(
+ 'title' => t('Test code field that returns FALSE'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_false',
+ );
+ $fields['node']['ds_test_field_null'] = array(
+ 'title' => t('Test code field that returns NULL'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_null',
+ );
+ $fields['node']['ds_test_field_nothing'] = array(
+ 'title' => t('Test code field that returns nothing'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_nothing',
+ );
+ $fields['node']['ds_test_field_zero_int'] = array(
+ 'title' => t('Test code field that returns zero as an integer'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_zero_int',
+ );
+ $fields['node']['ds_test_field_zero_string'] = array(
+ 'title' => t('Test code field that returns zero as a string'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_zero_string',
+ );
+ $fields['node']['ds_test_field_zero_float'] = array(
+ 'title' => t('Test code field that returns zero as a floating point number'),
+ 'field_type' => DS_FIELD_TYPE_FUNCTION,
+ 'function' => 'dstest_render_zero_float',
+ );
+
+ return $fields;
+ }
+}
+
+/**
+ * Implements hook_ds_field_theme_functions_info().
+ */
+function ds_test_ds_field_theme_functions_info() {
+ return array('ds_test_theming_function' => t('Field test function'));
+}
+
+/**
+ * Theme field test function.
+ */
+function ds_test_theming_function($variables) {
+ return 'Testing field output through custom function';
+}
+
+/**
+ * Render the test code field.
+ */
+function dstest_render_test_field($field) {
+ return 'Test code field on node ' . $field['entity']->nid;
+}
+
+/**
+ * Test code field that returns an empty string.
+ */
+function dstest_render_empty_string() {
+ return '';
+}
+
+/**
+ * Test code field that returns FALSE.
+ */
+function dstest_render_false() {
+ return FALSE;
+}
+
+/**
+ * Test code field that returns NULL.
+ */
+function dstest_render_null() {
+ return NULL;
+}
+
+/**
+ * Test code field that returns nothing.
+ */
+function dstest_render_nothing() {
+ return;
+}
+
+/**
+ * Test code field that returns zero as an integer.
+ */
+function dstest_render_zero_int() {
+ return 0;
+}
+
+/**
+ * Test code field that returns zero as a string.
+ */
+function dstest_render_zero_string() {
+ return '0';
+}
+
+/**
+ * Test code field that returns zero as a floating point number.
+ */
+function dstest_render_zero_float() {
+ return 0.0;
+}
+
+/**
+ * Implements hook_ds_fields_info_alter().
+ */
+function ds_test_ds_fields_info_alter(&$fields, $entity_type) {
+ if (isset($fields['ds_test_field_2'])) {
+ $fields['ds_test_field_2']['title'] = 'Field altered';
+ }
+}
+
+/**
+ * Implements hook_ds_layouts_info().
+ */
+function ds_test_ds_layout_info() {
+ $path = drupal_get_path('module', 'ds_test');
+ $layouts = array(
+ 'dstest_1col' => array(
+ 'label' => t('Test One column'),
+ 'path' => $path . '/dstest_1col',
+ 'regions' => array(
+ 'ds_content' => t('Content'),
+ ),
+ ),
+ 'dstest_2col' => array(
+ 'label' => t('Test Two column'),
+ 'path' => $path . '/dstest_2col',
+ 'regions' => array(
+ 'left' => t('Left'),
+ 'right' => t('Right')
+ ),
+ 'css' => TRUE,
+ ),
+ );
+
+ return $layouts;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function ds_test_form_field_ui_display_overview_form_alter(&$form, &$form_state) {
+ foreach (element_children($form['fields']) as $key) {
+ if (isset($form['fields'][$key]['settings_edit'])) {
+ $settings = $form['fields'][$key]['settings_edit'];
+ if (!empty($settings)) {
+ $form['fields'][$key]['settings_edit']['#type'] = 'submit';
+ $form['fields'][$key]['settings_edit']['#value'] = 'edit ' . $key;
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_ds_pre_render_alter().
+ */
+function ds_test_ds_pre_render_alter(&$layout_render_array, $context) {
+ $entity = $context['entity'];
+ if (isset($entity->title) && $entity->title === 'Alter me!') {
+ $layout_render_array['left'][] = array('#markup' => 'cool!', '#weight' => 20);
+ }
+}
diff --git a/sites/all/modules/ds/tests/ds_test.views_default.inc b/sites/all/modules/ds/tests/ds_test.views_default.inc
new file mode 100644
index 000000000..b0ecb82f9
--- /dev/null
+++ b/sites/all/modules/ds/tests/ds_test.views_default.inc
@@ -0,0 +1,361 @@
+<?php
+
+/**
+ * @file
+ * Default views used for testing.
+ */
+
+/**
+ * Implements hook_views_default_views().
+ */
+function ds_test_views_default_views() {
+ $views = array();
+
+ $view = new view;
+ $view->name = 'ds_testing';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'ds-testing';
+ $view->core = 7;
+ $view->api_version = '3.0-alpha1';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'ds-testing';
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'ds';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+ /* Sort criterion: Content: Post date */
+ $handler->display->display_options['sorts']['created']['id'] = 'created';
+ $handler->display->display_options['sorts']['created']['table'] = 'node';
+ $handler->display->display_options['sorts']['created']['field'] = 'created';
+ /* Sort criterion: Content: Tags (field_tags) */
+ $handler->display->display_options['sorts']['field_tags_tid']['id'] = 'field_tags_tid';
+ $handler->display->display_options['sorts']['field_tags_tid']['table'] = 'field_data_field_tags';
+ $handler->display->display_options['sorts']['field_tags_tid']['field'] = 'field_tags_tid';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['value'] = array(
+ 'article' => 'article',
+ );
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'ds';
+ $handler->display->display_options['row_options']['alternating'] = 0;
+ $handler->display->display_options['row_options']['grouping'] = 0;
+ $handler->display->display_options['row_options']['advanced'] = 0;
+ $handler->display->display_options['row_options']['grouping_fieldset'] = array(
+ 'grouping' => 0,
+ 'group_field' => 'node_created',
+ 'group_field_function' => '',
+ );
+ $handler->display->display_options['row_options']['default_fieldset'] = array(
+ 'view_mode' => 'teaser',
+ 'load_comments' => 0,
+ );
+ $handler->display->display_options['row_options']['alternating_fieldset'] = array(
+ 'alternating' => 0,
+ 'allpages' => 0,
+ 'item_0' => 'teaser',
+ 'item_1' => 'teaser',
+ 'item_2' => 'teaser',
+ 'item_3' => 'teaser',
+ 'item_4' => 'teaser',
+ 'item_5' => 'teaser',
+ 'item_6' => 'teaser',
+ 'item_7' => 'teaser',
+ 'item_8' => 'teaser',
+ 'item_9' => 'teaser',
+ );
+ $handler->display->display_options['row_options']['advanced_fieldset'] = array(
+ 'advanced' => 0,
+ );
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['path'] = 'ds-testing';
+
+ /* Display: Page 1 */
+ $handler = $view->new_display('page', 'Page 1', 'page_1');
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'ds';
+ $handler->display->display_options['row_options']['alternating'] = 1;
+ $handler->display->display_options['row_options']['grouping'] = 0;
+ $handler->display->display_options['row_options']['advanced'] = 0;
+ $handler->display->display_options['row_options']['grouping_fieldset'] = array(
+ 'grouping' => 0,
+ 'group_field' => 'node_created',
+ 'group_field_function' => '',
+ );
+ $handler->display->display_options['row_options']['default_fieldset'] = array(
+ 'view_mode' => 'teaser',
+ 'load_comments' => 0,
+ );
+ $handler->display->display_options['row_options']['alternating_fieldset'] = array(
+ 'alternating' => 1,
+ 'allpages' => 0,
+ 'item_0' => 'full',
+ 'item_1' => 'teaser',
+ 'item_2' => 'teaser',
+ 'item_3' => 'teaser',
+ 'item_4' => 'teaser',
+ 'item_5' => 'teaser',
+ 'item_6' => 'teaser',
+ 'item_7' => 'teaser',
+ 'item_8' => 'teaser',
+ 'item_9' => 'teaser',
+ );
+ $handler->display->display_options['row_options']['advanced_fieldset'] = array(
+ 'advanced' => 0,
+ );
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['path'] = 'ds-testing-2';
+
+ /* Display: Page 2 */
+ $handler = $view->new_display('page', 'Page 2', 'page_2');
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'ds';
+ $handler->display->display_options['row_options']['alternating'] = 0;
+ $handler->display->display_options['row_options']['grouping'] = 1;
+ $handler->display->display_options['row_options']['advanced'] = 0;
+ $handler->display->display_options['row_options']['grouping_fieldset'] = array(
+ 'grouping' => 1,
+ 'group_field' => 'field_data_field_tags_field_tags_tid',
+ 'group_field_function' => '',
+ );
+ $handler->display->display_options['row_options']['default_fieldset'] = array(
+ 'view_mode' => 'teaser',
+ 'load_comments' => 0,
+ );
+ $handler->display->display_options['row_options']['alternating_fieldset'] = array(
+ 'alternating' => 0,
+ 'allpages' => 0,
+ 'item_0' => 'teaser',
+ 'item_1' => 'teaser',
+ 'item_2' => 'teaser',
+ 'item_3' => 'teaser',
+ 'item_4' => 'teaser',
+ 'item_5' => 'teaser',
+ 'item_6' => 'teaser',
+ 'item_7' => 'teaser',
+ 'item_8' => 'teaser',
+ 'item_9' => 'teaser',
+ );
+ $handler->display->display_options['row_options']['advanced_fieldset'] = array(
+ 'advanced' => 0,
+ );
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['defaults']['sorts'] = FALSE;
+ /* Sort criterion: Content: Tags (field_tags) */
+ $handler->display->display_options['sorts']['field_tags_tid']['id'] = 'field_tags_tid';
+ $handler->display->display_options['sorts']['field_tags_tid']['table'] = 'field_data_field_tags';
+ $handler->display->display_options['sorts']['field_tags_tid']['field'] = 'field_tags_tid';
+ $handler->display->display_options['path'] = 'ds-testing-3';
+
+ /* Display: Page 3 */
+ $handler = $view->new_display('page', 'Page 3', 'page_3');
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'ds';
+ $handler->display->display_options['row_options']['alternating'] = 0;
+ $handler->display->display_options['row_options']['grouping'] = 1;
+ $handler->display->display_options['row_options']['advanced'] = 0;
+ $handler->display->display_options['row_options']['grouping_fieldset'] = array(
+ 'grouping' => 1,
+ 'group_field' => 'field_data_field_tags_field_tags_tid',
+ 'group_field_function' => 'ds_test_get_tag_name',
+ );
+ $handler->display->display_options['row_options']['default_fieldset'] = array(
+ 'view_mode' => 'teaser',
+ 'load_comments' => 0,
+ );
+ $handler->display->display_options['row_options']['alternating_fieldset'] = array(
+ 'alternating' => 0,
+ 'allpages' => 0,
+ 'item_0' => 'teaser',
+ 'item_1' => 'teaser',
+ 'item_2' => 'teaser',
+ 'item_3' => 'teaser',
+ 'item_4' => 'teaser',
+ 'item_5' => 'teaser',
+ 'item_6' => 'teaser',
+ 'item_7' => 'teaser',
+ 'item_8' => 'teaser',
+ 'item_9' => 'teaser',
+ );
+ $handler->display->display_options['row_options']['advanced_fieldset'] = array(
+ 'advanced' => 0,
+ );
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['defaults']['sorts'] = FALSE;
+ /* Sort criterion: Content: Tags (field_tags) */
+ $handler->display->display_options['sorts']['field_tags_tid']['id'] = 'field_tags_tid';
+ $handler->display->display_options['sorts']['field_tags_tid']['table'] = 'field_data_field_tags';
+ $handler->display->display_options['sorts']['field_tags_tid']['field'] = 'field_tags_tid';
+ $handler->display->display_options['path'] = 'ds-testing-4';
+
+ /* Display: Page 4 */
+ $handler = $view->new_display('page', 'Page 4', 'page_4');
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'ds';
+ $handler->display->display_options['row_options']['alternating'] = 0;
+ $handler->display->display_options['row_options']['grouping'] = 0;
+ $handler->display->display_options['row_options']['advanced'] = 1;
+ $handler->display->display_options['row_options']['grouping_fieldset'] = array(
+ 'grouping' => 0,
+ 'group_field' => 'field_data_field_tags_field_tags_tid',
+ 'group_field_function' => '',
+ );
+ $handler->display->display_options['row_options']['default_fieldset'] = array(
+ 'view_mode' => 'teaser',
+ 'load_comments' => 0,
+ );
+ $handler->display->display_options['row_options']['alternating_fieldset'] = array(
+ 'alternating' => 0,
+ 'allpages' => 0,
+ 'item_0' => 'teaser',
+ 'item_1' => 'teaser',
+ 'item_2' => 'teaser',
+ 'item_3' => 'teaser',
+ 'item_4' => 'teaser',
+ 'item_5' => 'teaser',
+ 'item_6' => 'teaser',
+ 'item_7' => 'teaser',
+ 'item_8' => 'teaser',
+ 'item_9' => 'teaser',
+ );
+ $handler->display->display_options['row_options']['advanced_fieldset'] = array(
+ 'advanced' => 1,
+ );
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['path'] = 'ds-testing-5';
+
+ /* Display: Page 5 */
+ $handler = $view->new_display('page', 'Page 5', 'page_5');
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'ds';
+ $handler->display->display_options['row_options']['alternating'] = 0;
+ $handler->display->display_options['row_options']['grouping'] = 0;
+ $handler->display->display_options['row_options']['advanced'] = 0;
+ $handler->display->display_options['row_options']['grouping_fieldset'] = array(
+ 'grouping' => 0,
+ 'group_field' => 'field_data_field_tags_field_tags_tid',
+ 'group_field_function' => '',
+ );
+ $handler->display->display_options['row_options']['default_fieldset'] = array(
+ 'view_mode' => 'teaser',
+ 'load_comments' => 0,
+ );
+ $handler->display->display_options['row_options']['alternating_fieldset'] = array(
+ 'alternating' => 0,
+ 'allpages' => 0,
+ 'item_0' => 'teaser',
+ 'item_1' => 'teaser',
+ 'item_2' => 'teaser',
+ 'item_3' => 'teaser',
+ 'item_4' => 'teaser',
+ 'item_5' => 'teaser',
+ 'item_6' => 'teaser',
+ 'item_7' => 'teaser',
+ 'item_8' => 'teaser',
+ 'item_9' => 'teaser',
+ );
+ $handler->display->display_options['row_options']['advanced_fieldset'] = array(
+ 'advanced' => 0,
+ );
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['defaults']['header'] = FALSE;
+ /* Header: Global: Text area */
+ $handler->display->display_options['header']['area']['id'] = 'area';
+ $handler->display->display_options['header']['area']['table'] = 'views';
+ $handler->display->display_options['header']['area']['field'] = 'area';
+ $handler->display->display_options['header']['area']['empty'] = FALSE;
+ $handler->display->display_options['header']['area']['content'] = 'Header text';
+ $handler->display->display_options['header']['area']['format'] = 'filtered_html';
+ $handler->display->display_options['header']['area']['tokenize'] = 0;
+ $handler->display->display_options['defaults']['footer'] = FALSE;
+ /* Footer: Global: Text area */
+ $handler->display->display_options['footer']['area']['id'] = 'area';
+ $handler->display->display_options['footer']['area']['table'] = 'views';
+ $handler->display->display_options['footer']['area']['field'] = 'area';
+ $handler->display->display_options['footer']['area']['empty'] = FALSE;
+ $handler->display->display_options['footer']['area']['content'] = 'Footer text';
+ $handler->display->display_options['footer']['area']['format'] = 'filtered_html';
+ $handler->display->display_options['footer']['area']['tokenize'] = 0;
+ $handler->display->display_options['defaults']['filters'] = FALSE;
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = '1';
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['value'] = array(
+ 'article' => 'article',
+ 'page' => 'page',
+ );
+ $handler->display->display_options['filters']['type']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['label'] = 'Type';
+ $handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+ $handler->display->display_options['filters']['type']['expose']['reduce'] = 0;
+ $handler->display->display_options['path'] = 'ds-testing-6';
+ $views['ds_testing'] = $view;
+
+ return $views;
+}
diff --git a/sites/all/modules/ds/tests/dstest_1col/dstest-1col.tpl.php b/sites/all/modules/ds/tests/dstest_1col/dstest-1col.tpl.php
new file mode 100644
index 000000000..657c20962
--- /dev/null
+++ b/sites/all/modules/ds/tests/dstest_1col/dstest-1col.tpl.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Display Suite Test 1 column template.
+ */
+?>
+<div class="<?php print $classes;?> clearfix <?php print $ds_content_classes;?>">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <?php print $ds_content; ?>
+</div>
diff --git a/sites/all/modules/ds/tests/dstest_2col/dstest-2col.tpl.php b/sites/all/modules/ds/tests/dstest_2col/dstest-2col.tpl.php
new file mode 100644
index 000000000..ffa42a7c0
--- /dev/null
+++ b/sites/all/modules/ds/tests/dstest_2col/dstest-2col.tpl.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Display Suite 2 column template.
+ */
+?>
+<div class="<?php print $classes;?> clearfix">
+
+ <?php if (isset($title_suffix['contextual_links'])): ?>
+ <?php print render($title_suffix['contextual_links']); ?>
+ <?php endif; ?>
+
+ <?php if ($left): ?>
+ <div class="group-left<?php print $left_classes; ?>">
+ <?php print $left; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($right): ?>
+ <div class="group-right<?php print $right_classes; ?>">
+ <?php print $right; ?>
+ </div>
+ <?php endif; ?>
+
+</div> \ No newline at end of file
diff --git a/sites/all/modules/ds/tests/dstest_2col/dstest_2col.css b/sites/all/modules/ds/tests/dstest_2col/dstest_2col.css
new file mode 100644
index 000000000..28b230cf7
--- /dev/null
+++ b/sites/all/modules/ds/tests/dstest_2col/dstest_2col.css
@@ -0,0 +1,10 @@
+
+.group-left {
+ float: left;
+ width: 50%;
+}
+
+.group-right {
+ float: left;
+ width: 50%;
+}
diff --git a/sites/all/modules/ds/views/ds-row-fields.tpl.php b/sites/all/modules/ds/views/ds-row-fields.tpl.php
new file mode 100644
index 000000000..0e0abcd8d
--- /dev/null
+++ b/sites/all/modules/ds/views/ds-row-fields.tpl.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @file
+ * Dummy template file for Display Suite views fields.
+ */
+
+$url = url('admin/structure/ds/vd', array('absolute' => TRUE));
+$link_url = l($url, $url, array('alias' => TRUE));
+print t('The layout selection and positioning of fields happens at !url.', array('!url' => $link_url)); \ No newline at end of file
diff --git a/sites/all/modules/ds/views/views_plugin_ds_entity_view.inc b/sites/all/modules/ds/views/views_plugin_ds_entity_view.inc
new file mode 100644
index 000000000..cfbdd5cf7
--- /dev/null
+++ b/sites/all/modules/ds/views/views_plugin_ds_entity_view.inc
@@ -0,0 +1,565 @@
+<?php
+
+/**
+ * @file
+ * Provides the Display Suite views entity style plugin.
+ */
+
+/**
+ * Plugin which defines the view mode on the resulting entity object.
+ */
+class views_plugin_ds_entity_view extends views_plugin_row {
+
+ protected $group_count;
+
+ function init(&$view, &$display, $options = NULL) {
+ parent::init($view, $display, $options);
+ $this->base_table = $view->base_table;
+ // Special case for apachesolr_views.
+ if ($this->base_table == 'apachesolr') {
+ $this->base_table = 'node';
+ }
+ $this->base_field = $this->ds_views_3_support();
+
+ $this->group_count = 0;
+ }
+
+ // Return base_field based on base_table. It might not be
+ // the cleanest solution, it's the fastest though.
+ function ds_views_3_support() {
+ if (strpos($this->base_table, 'eck_') === 0) {
+ // Base tables of entities created by entity construction kit (eck)
+ // are prefixed with 'eck_' and the base field is always 'id'.
+ $this->entity_type = str_replace('eck_', '', $this->base_table);
+ return 'id';
+ }
+
+ $base_table_fields = array(
+ 'node' => array('field' => 'nid', 'entity_type' => 'node'),
+ 'comment' => array('field' => 'cid', 'entity_type' => 'comment'),
+ 'users' => array('field' => 'uid', 'entity_type' => 'user'),
+ 'apachesolr' => array('field' => 'nid', 'entity_type' => 'node'),
+ 'taxonomy_term_data' => array('field' => 'tid', 'entity_type' => 'taxonomy_term'),
+ 'file_managed' => array('field' => 'fid', 'entity_type' => 'file'),
+ 'micro' => array('field' => 'mid', 'entity_type' => 'micro'),
+ );
+ $this->entity_type = isset($base_table_fields[$this->base_table]) ? $base_table_fields[$this->base_table]['entity_type'] : 'node';
+ return isset($base_table_fields[$this->base_table]) ? $base_table_fields[$this->base_table]['field'] : 'nid';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['view_mode'] = array('default' => 'teaser');
+ $options['load_comments'] = array('default' => FALSE);
+ $options['alternating'] = array('default' => FALSE);
+ $options['changing'] = array('default' => FALSE);
+ $options['grouping'] = array('default' => FALSE);
+ $options['advanced'] = array('default' => FALSE);
+ $options['delta_fieldset'] = array(
+ 'contains' => array(
+ 'delta_fields' => array('default' => FALSE),
+ ),
+ );
+ $options['grouping_fieldset'] = array(
+ 'contains' => array(
+ 'grouping' => array('default' => FALSE, 'bool' => TRUE),
+ 'group_odd_even' => array('default' => FALSE, 'bool' => TRUE),
+ 'group_field' => array('default' => ''),
+ 'group_field_function' => array('default' => ''),
+ ),
+ );
+ $options['default_fieldset'] = array(
+ 'contains' => array(
+ 'view_mode' => array('default' => ''),
+ ),
+ );
+ $options['switch_fieldset'] = array(
+ 'contains' => array(
+ 'switch' => array('default' => FALSE, 'bool' => TRUE),
+ ),
+ );
+ $options['alternating_fieldset'] = array(
+ 'contains' => array(
+ 'alternating' => array('default' => FALSE, 'bool' => TRUE),
+ 'allpages' => array('default' => FALSE, 'bool' => TRUE),
+ 'item' => array(
+ 'default' => array(),
+ 'export' => 'ds_item_export_option',
+ ),
+ ),
+ );
+ $options['advanced_fieldset'] = array(
+ 'contains' => array(
+ 'advanced' => array('default' => FALSE, 'bool' => TRUE),
+ ),
+ );
+ return $options;
+ }
+
+ /**
+ * Custom export function for alternating_fieldset items.
+ */
+ function ds_item_export_option($indent, $prefix, $storage, $option, $definition, $parents) {
+ $output = '';
+ $definition = array('default' => 'teaser');
+ foreach ($storage as $key => $value) {
+ if (strstr($key, 'item_') !== FALSE) {
+ $output .= parent::export_option($indent, $prefix, $storage, $key, $definition, $parents);
+ }
+ }
+ return $output;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $view_mode_options = array();
+ $entity_type = $this->view->base_table;
+ // In case we're working with users or managed files, change the entity type variable.
+ if ($entity_type == 'users') $entity_type = 'user';
+ if ($entity_type == 'file_managed') $entity_type = 'file';
+ $entity_view_modes = ds_entity_view_modes($entity_type);
+ foreach ($entity_view_modes as $key => $view_mode) {
+ $view_mode_options[$key] = $view_mode['label'];
+ }
+
+ // Default view mode & load comments.
+ $form['default_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Default view mode'),
+ '#collapsible' => TRUE,
+ '#collapsed' => ($this->options['advanced']),
+ );
+ $form['default_fieldset']['view_mode'] = array(
+ '#type' => 'select',
+ '#default_value' => $this->options['view_mode'],
+ '#options' => $view_mode_options,
+ '#description' => t('Select the default view mode for this view.')
+ );
+ if ($entity_type == 'node') {
+ $form['default_fieldset']['load_comments'] = array(
+ '#title' => t('Comments'),
+ '#type' => 'checkbox',
+ '#description' => t('Load comments for every node to display.'),
+ '#default_value' => isset($this->options['load_comments']) ? $this->options['load_comments'] : FALSE,
+ '#access' => module_exists('comment'),
+ );
+ }
+
+ // Use view mode of display settings.
+ if ($entity_type == 'node' && variable_get('ds_extras_switch_view_mode', FALSE)) {
+ $form['switch_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Use view mode of display settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => !$this->options['switch_fieldset']['switch'],
+ );
+ $form['switch_fieldset']['switch'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use view mode of display settings'),
+ '#default_value' => $this->options['switch_fieldset']['switch'],
+ '#description' => t('Use the alternative view mode selected in the display settings tab.')
+ );
+ }
+
+ // Changing view modes.
+ $form['alternating_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Alternating view mode'),
+ '#collapsible' => TRUE,
+ '#collapsed' => !$this->options['alternating'],
+ );
+ $form['alternating_fieldset']['alternating'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use the changing view mode selector'),
+ '#default_value' => $this->options['alternating'],
+ );
+ $form['alternating_fieldset']['allpages'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use this configuration on every page. Otherwhise the default view mode is used as soon you browse away from the first page of this view.'),
+ '#default_value' => (isset($this->options['alternating_fieldset']['allpages'])) ? $this->options['alternating_fieldset']['allpages'] : FALSE,
+ );
+
+ $limit = $this->view->display_handler->get_option('items_per_page');
+ $pager = $this->view->display_handler->get_plugin('pager');
+ $limit = (isset($pager->options['items_per_page'])) ? $pager->options['items_per_page'] : 0;
+ if ($limit == 0 || $limit > 20) {
+ $form['alternating_fieldset']['disabled'] = array(
+ '#markup' => t('This option is disabled because you have unlimited items or listing more than 20 items.'),
+ );
+ $form['alternating_fieldset']['alternating']['#disabled'] = TRUE;
+ $form['alternating_fieldset']['allpages']['#disabled'] = TRUE;
+ }
+ else {
+ $i = 1;
+ $a = 0;
+ while ($limit != 0) {
+ $form['alternating_fieldset']['item_' . $a] = array(
+ '#title' => t('Item @nr', array('@nr' => $i)),
+ '#type' => 'select',
+ '#default_value' => (isset($this->options['alternating_fieldset']['item_' . $a])) ? $this->options['alternating_fieldset']['item_' . $a] : 'teaser',
+ '#options' => $view_mode_options,
+ );
+ $limit--;
+ $a++;
+ $i++;
+ }
+ }
+
+ // Grouping rows.
+ $sorts = $this->view->display_handler->get_option('sorts');
+ $groupable = !empty($sorts) && $this->options['grouping'];
+ $form['grouping_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Group data'),
+ '#collapsible' => TRUE,
+ '#collapsed' => !$groupable,
+ );
+ $form['grouping_fieldset']['grouping'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Group data on a field. The value of this field will be displayed too.'),
+ '#default_value' => $groupable,
+ );
+
+ if (!empty($sorts)) {
+ $sort_options = array();
+ foreach ($sorts as $key => $sort) {
+ $sort_name = drupal_ucfirst($sort['field']);
+ $sort_options[$sort['table'] . '|' . $sort['field']] = $sort_name;
+ }
+
+ $form['grouping_fieldset']['group_field'] = array(
+ '#type' => 'select',
+ '#options' => $sort_options,
+ '#default_value' => isset($this->options['grouping_fieldset']['group_field']) ? $this->options['grouping_fieldset']['group_field'] : '',
+ );
+
+ $form['grouping_fieldset']['group_field_function'] = array(
+ '#type' => 'textfield',
+ '#title' => 'Heading function',
+ '#description' => check_plain(t('The value of the field can be in a very raw format (eg, date created). Enter a custom function which you can use to format that value. The value and the object will be passed into that function eg. custom_function($raw_value, $object);')),
+ '#default_value' => isset($this->options['grouping_fieldset']['group_field_function']) ? $this->options['grouping_fieldset']['group_field_function'] : '',
+ );
+ }
+ else {
+ $form['grouping_fieldset']['grouping']['#disabled'] = TRUE;
+ $form['grouping_fieldset']['grouping']['#description'] = t('Grouping is disabled because you do not have any sort fields.');
+ }
+
+ $form['grouping_fieldset']['group_odd_even'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add odd/even group classes to the individual group elements'),
+ '#default_value' => FALSE,
+ );
+
+ // Advanced function.
+ $delta_fields = array();
+ $field_api_fields = field_info_instances($this->entity_type);
+ foreach ($field_api_fields as $bundle => $fields) {
+ foreach ($fields as $field_name => $instance_info) {
+ $field_info = field_info_field($field_name);
+ if ($field_info['cardinality'] != 1) {
+ $delta_fields[$field_name] = $field_name;
+ }
+ }
+ }
+ $form['delta_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Delta fields'),
+ '#collapsible' => TRUE,
+ '#collapsed' => empty($this->options['delta_fields']),
+ );
+ $form['delta_fieldset']['delta_fields'] = array(
+ '#type' => 'select',
+ '#title' => t('Select fields'),
+ '#description' => t('Select fields which "delta" value should be added to the result row. On the manage display of an entity you can decide to look for this delta value to only print that row.'),
+ '#options' => $delta_fields,
+ '#multiple' => TRUE,
+ '#default_value' => !empty($this->options['delta_fields']) ? $this->options['delta_fields'] : '',
+ );
+
+ // Advanced function.
+ $form['advanced_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Advanced view mode'),
+ '#collapsible' => TRUE,
+ '#collapsed' => !$this->options['advanced'],
+ );
+ $form['advanced_fieldset']['advanced'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use the advanced view mode selector'),
+ '#description' => t('This gives you the opportunity to have full control of a list for really advanced features.<br /> There is no UI for this, you need to create a function named like this: ds_views_row_adv_@VIEWSNAME($entity, $view_mode, $load_comments).<br />See <a href="http://drupal.org/node/697320#ds_views_row_adv_VIEWSNAME">http://drupal.org/node/697320#ds_views_row_adv_VIEWSNAME</a> for an example.', array('@VIEWSNAME' => $this->view->name)),
+ '#default_value' => $this->options['advanced'],
+ );
+ }
+
+ /**
+ * Validate view mode type selector.
+ */
+ function options_validate(&$form, &$form_state) {
+ if (($form_state['values']['row_options']['alternating_fieldset']['alternating'] || $form_state['values']['row_options']['grouping_fieldset']['grouping']) && $form_state['values']['row_options']['advanced_fieldset']['advanced']) {
+ form_set_error('advanced', t('You can not have changing/grouping and advanced enabled at the same time'));
+ }
+ }
+
+ /**
+ * Reset all fieldsets except for changing.
+ */
+ function options_submit(&$form, &$form_state) {
+ $form_state['values']['row_options']['load_comments'] = $form_state['values']['row_options']['default_fieldset']['load_comments'];
+ $form_state['values']['row_options']['view_mode'] = $form_state['values']['row_options']['default_fieldset']['view_mode'];
+ $form_state['values']['row_options']['switch'] = isset($form_state['values']['row_options']['switch_fieldset']) ? $form_state['values']['row_options']['switch_fieldset']['switch'] : FALSE;
+ $form_state['values']['row_options']['alternating'] = $form_state['values']['row_options']['alternating_fieldset']['alternating'];
+ $form_state['values']['row_options']['grouping'] = $form_state['values']['row_options']['grouping_fieldset']['grouping'];
+ $form_state['values']['row_options']['advanced'] = $form_state['values']['row_options']['advanced_fieldset']['advanced'];
+ $form_state['values']['row_options']['delta_fields'] = $form_state['values']['row_options']['delta_fieldset']['delta_fields'];
+ }
+
+ /**
+ * Query method.
+ */
+ function query() {
+ parent::query();
+ $this->delta_fields = array();
+ $delta_fields = $this->options['delta_fieldset']['delta_fields'];
+ if (!empty($delta_fields)) {
+ foreach ($delta_fields as $field) {
+ $field_name = 'field_data_' . $field;
+ $field_name_delta = $field_name . '_delta';
+ $this->view->query->add_field($field_name, 'delta');
+ $this->delta_fields[$field] = $field_name_delta;
+ }
+ }
+ }
+
+ /**
+ * Preload all entities.
+ */
+ function pre_render($values) {
+ $ids = array();
+ foreach ($values as $row) {
+ $ids[] = $row->{$this->field_alias};
+ }
+
+ switch ($this->base_table) {
+ case 'node':
+ $this->entities = node_load_multiple($ids);
+ break;
+ case 'comment':
+ $this->entities = comment_load_multiple($ids);
+ break;
+ case 'users':
+ $this->entities = user_load_multiple($ids);
+ break;
+ case 'taxonomy_term_data':
+ $this->entities = taxonomy_term_load_multiple($ids);
+ if (function_exists('i18n_taxonomy_localize_terms')) {
+ global $language;
+ foreach($this->entities as $index => &$entity) {
+ $entity = i18n_taxonomy_localize_terms($entity, $language->language);
+ }
+ }
+ break;
+ case 'file_managed':
+ $this->entities = file_load_multiple($ids);
+ break;
+ case 'micro':
+ $this->entities = entity_load($this->base_table, $ids);
+ break;
+ }
+ }
+
+ /**
+ * Render each $row.
+ */
+ function render($row) {
+ // Set a variable to indicate if comments need to be loaded or not.
+ $load_comments = isset($this->options['load_comments']) ? $this->options['load_comments'] : FALSE;
+
+ // The advanced selector searches for a function called
+ // ds_views_row_adv_VIEWSNAME. Return the row immediately.
+ if ($this->options['advanced']) {
+ $row_function = 'ds_views_row_adv_' . $this->view->name;
+ if (function_exists($row_function)) {
+ return $row_function($this->entities[$row->{$this->field_alias}], $this->options['view_mode'], $load_comments);
+ }
+ }
+
+ // Keep a static group array.
+ static $grouping = array();
+ $view_name = $this->view->name . '_' . $this->view->current_display;
+ $group_value_content = '';
+
+ // Default view mode.
+ $view_mode = $this->options['view_mode'];
+
+ // Display settings view mode.
+ if ($this->options['switch_fieldset']['switch']) {
+ if (!empty($this->entities[$row->{$this->field_alias}]->ds_switch)) {
+ $view_mode = $this->entities[$row->{$this->field_alias}]->ds_switch;
+ }
+ }
+
+ // Change the view mode per row.
+ if ($this->options['alternating']) {
+ // Check for paging to determine the view mode.
+ if (isset($_GET['page']) && isset($this->options['alternating_fieldset']['allpages']) && !$this->options['alternating_fieldset']['allpages']) {
+ $view_mode = $this->options['view_mode'];
+ }
+ else {
+ $view_mode = isset($this->options['alternating_fieldset']['item_' . $this->view->row_index]) ? $this->options['alternating_fieldset']['item_' . $this->view->row_index] : $this->options['view_mode'];
+ }
+ }
+
+ // Give modules a chance to alter the $view_mode. Use $view_mode by ref.
+ $context = array(
+ 'entity' => $this->entities[$row->{$this->field_alias}],
+ 'view_name' => $this->view->name,
+ 'display' => $this->view->current_display
+ );
+ drupal_alter('ds_views_view_mode', $view_mode, $context);
+ // Call the row render function.
+ $content = $this->ds_views_row_render_entity($view_mode, $row, $load_comments);
+
+ // Keep a static grouping for this view.
+ if ($this->options['grouping']) {
+
+ $group_field = $this->options['grouping_fieldset']['group_field'];
+
+ // New way of creating the alias.
+ if (strpos($group_field, '|') !== FALSE) {
+ list($ftable, $ffield) = explode('|', $group_field);
+ $group_field = $this->view->sort[$ffield]->table_alias . '_' . $this->view->sort[$ffield]->real_field;
+ }
+
+ // Note, the keys in the $row object are cut of at 60 chars.
+ // see views_plugin_query_default.inc.
+ if (drupal_strlen($group_field) > 60) {
+ $group_field = drupal_substr($group_field, 0, 60);
+ }
+
+ $raw_group_value = isset($row->{$group_field}) ? $row->{$group_field} : '';
+ $group_value = $raw_group_value;
+ // Special function to format the heading value.
+ if (!empty($this->options['grouping_fieldset']['group_field_function'])) {
+ $function = $this->options['grouping_fieldset']['group_field_function'];
+ if (function_exists($function)) {
+ $group_value = $function($raw_group_value, $this->entities[$row->{$this->field_alias}]);
+ }
+ }
+ if (!isset($grouping[$view_name][$group_value])) {
+ $group_value_content = '<h2 class="grouping-title">' . $group_value . '</h2>';
+ $grouping[$view_name][$group_value] = $group_value;
+ $this->group_count++;
+ }
+ }
+
+ // Grouping.
+ if (!empty($grouping[$view_name])) {
+ if (!empty($group_value_content)) {
+ $content = $group_value_content . $content;
+ }
+ $classes = array('grouping-content');
+ if ($this->options['grouping_fieldset']['group_odd_even']) {
+ if ($this->group_count % 2 == 0) {
+ $classes[] = 'even';
+ }
+ else {
+ $classes[] = 'odd';
+ }
+ }
+
+ $content = '<div class="' . implode(' ', $classes) . '">' . $content . '</div>';
+ }
+
+ // Return the content.
+ return $content;
+ }
+
+ /**
+ * Render a discrete entity based with the selected view mode.
+ *
+ * @param $view_mode
+ * The view mode which is set in the Views' options.
+ * @param $row
+ * The current active row object being rendered.
+ *
+ * @return $content
+ * An entity view rendered as HTML
+ */
+ function ds_views_row_render_entity($view_mode, $row, $load_comments) {
+
+ // Add delta fields if necessary.
+ if (!empty($this->delta_fields)) {
+ $ds_delta = array();
+ foreach ($this->delta_fields as $field_name => $delta_field) {
+ $ds_delta[$field_name] = $row->{$delta_field};
+ }
+ $this->entities[$row->{$this->field_alias}]->ds_delta = $ds_delta;
+ }
+
+ $row_function = 'ds_views_row_render_' . $this->base_table;
+ $content = $row_function($this->entities[$row->{$this->field_alias}], $view_mode, $load_comments);
+ // Allow other modules to modify the entity render array in context.
+ $context = array(
+ 'row' => $row,
+ 'view' => &$this->view,
+ 'view_mode' => $view_mode,
+ 'load_comments' => $load_comments,
+ );
+ drupal_alter('ds_views_row_render_entity', $content, $context);
+ return drupal_render($content);
+ }
+}
+
+/**
+ * Render the node through the entity plugin.
+ */
+function ds_views_row_render_node($entity, $view_mode, $load_comments) {
+ $node_display = node_view($entity, $view_mode);
+ if ($load_comments && module_exists('comment')) {
+ $node_display['comments'] = comment_node_page_additions($entity);
+ }
+ return $node_display;
+}
+
+/**
+ * Render the comment through the entity plugin.
+ */
+function ds_views_row_render_comment($entity, $view_mode, $load_comments) {
+ $node = node_load($entity->nid);
+ $element = comment_view($entity, $node, $view_mode);
+ return $element;
+}
+
+/**
+ * Render the user through the entity plugin.
+ */
+function ds_views_row_render_users($entity, $view_mode, $load_comments) {
+ $element = user_view($entity, $view_mode);
+ return $element;
+}
+
+/**
+ * Render the taxonomy term through the entity plugin.
+ */
+function ds_views_row_render_taxonomy_term_data($entity, $view_mode, $load_comments) {
+ $element = taxonomy_term_view($entity, $view_mode);
+ return $element;
+}
+
+/**
+ * Render the file through the entity plugin.
+ */
+function ds_views_row_render_file_managed($entity, $view_mode, $load_comments) {
+ $element = file_view($entity, $view_mode);
+ return $element;
+}
+
+/**
+ * Render the micro through the entity plugin.
+ */
+function ds_views_row_render_micro($entity, $view_mode, $load_comments) {
+ $element = micro_view($entity, $view_mode);
+ return $element;
+}
diff --git a/sites/all/modules/ds/views/views_plugin_ds_fields_view.inc b/sites/all/modules/ds/views/views_plugin_ds_fields_view.inc
new file mode 100644
index 000000000..d457f81e8
--- /dev/null
+++ b/sites/all/modules/ds/views/views_plugin_ds_fields_view.inc
@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * @file
+ * Provides the Display Suite views fields style plugin.
+ */
+
+/**
+ * Plugin which defines the view mode on the resulting entity object.
+ */
+class views_plugin_ds_fields_view extends views_plugin_row {
+
+ function init(&$view, &$display, $options = NULL) {
+ parent::init($view, $display, $options);
+ $this->base_table = $view->base_table;
+ // Special case for apachesolr_views.
+ if ($this->base_table == 'apachesolr') {
+ $this->base_table = 'node';
+ }
+ $this->base_field = $this->ds_views_3_support();
+ }
+
+ // Return base_field based on base_table. It might not be
+ // the cleanest solution, it's the fastest though.
+ function ds_views_3_support() {
+ if (strpos($this->base_table, 'eck_') === 0) {
+ // Base tables of entities created by entity construction kit (eck)
+ // are prefixed with 'eck_' and the base field is always 'id'.
+ return 'id';
+ }
+
+ $base_table_fields = array(
+ 'node' => 'nid',
+ 'comment' => 'cid',
+ 'users' => 'uid',
+ 'apachesolr' => 'nid',
+ 'taxonomy_term_data' => 'tid',
+ 'file_managed' => 'fid',
+ 'micro' => 'mid',
+ 'drealty_listing' => 'id',
+ 'commerce_product' => 'product_id',
+ 'commerce_line_item' => 'line_item_id',
+ 'civicrm_event' => 'id',
+ 'field_collection_item' => 'item_id',
+ );
+ return isset($base_table_fields[$this->base_table]) ? $base_table_fields[$this->base_table] : 'nid';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $url = url('admin/structure/ds/vd', array('absolute' => TRUE));
+ $link_url = l($url, $url, array('alias' => TRUE));
+ $form['info'] = array(
+ '#markup' => t('The layout selection and positioning of fields happens at !url.', array('!url' => $link_url)),
+ );
+ }
+}
+
+/**
+ * Preprocess function for ds_fields_view().
+ */
+function template_preprocess_ds_row_fields(&$vars) {
+ static $row_layout = array();
+
+ // Check if we have layout configured for this bundle.
+ $bundle = $vars['view']->name . '-' . $vars['view']->current_display . '-fields';
+ if (!isset($row_layout[$bundle])) {
+
+ // Register the layout, even if when it returns false.
+ $row_layout[$bundle] = ds_get_layout('ds_views', $bundle, 'default');
+
+ // Add css.
+ if ($row_layout[$bundle]) {
+ drupal_add_css($row_layout[$bundle]['path'] . '/' . $row_layout[$bundle]['layout'] . '.css');
+ }
+ }
+
+ // Render the views fields into the template layout.
+ // This code is more or less copied from ds_entity_variables().
+ if (isset($row_layout[$bundle])) {
+
+ $view = $vars['view'];
+ $layout = $row_layout[$bundle];
+ if (!$layout) {
+ return;
+ }
+
+ // Classes array.
+ $vars['classes_array'] = array();
+ $vars['classes_array'][] = strtr($bundle, '_', '-');
+
+ // Template suggestions.
+ $vars['theme_hook_suggestions'][] = $layout['layout'];
+ $vars['theme_hook_suggestions'][] = $layout['layout'] . '__ds_views_' . $bundle;
+
+ // Row index.
+ $row_index = $view->row_index;
+
+ // Layout wrapper.
+ $vars['layout_wrapper'] = isset($layout['settings']['layout_wrapper']) ? $layout['settings']['layout_wrapper'] : 'div';
+
+ // Layout attributes
+ if (!empty($layout['settings']['layout_attributes'])) {
+ $vars['layout_attributes'] = ' ' . $layout['settings']['layout_attributes'];
+ }
+ else {
+ $vars['layout_attributes'] = '';
+ }
+
+ // Create region variables based on the layout settings.
+ foreach ($layout['regions'] as $region_name => $region) {
+
+ // Create the region content.
+ $region_content = '';
+ if (isset($layout['settings']['regions'][$region_name])) {
+ foreach ($layout['settings']['regions'][$region_name] as $key => $field) {
+ // Do not render the field when empty and configured to be hidden.
+ if ($view->field[$field]->options['hide_empty'] && empty($view->style_plugin->rendered_fields[$row_index][$field])) {
+ continue;
+ }
+ $region_content .= '<div class="views-field-' . $field . '">';
+ if (!empty($view->field[$field]->options['label'])) {
+ $region_content .= '<div class="field-label">';
+ $region_content .= check_plain($view->field[$field]->options['label']);
+ if ($view->field[$field]->options['element_label_colon']) {
+ $region_content .= ':';
+ }
+ $region_content .= '</div>';
+ }
+ $region_content .= $view->style_plugin->rendered_fields[$row_index][$field];
+ $region_content .= '</div>';
+ }
+ }
+ $vars[$region_name] = $region_content;
+
+ // Region wrapper.
+ $vars[$region_name . '_wrapper'] = isset($layout['settings']['wrappers'][$region_name]) ? $layout['settings']['wrappers'][$region_name] : 'div';
+
+ // Add extras classes to the region.
+ $vars[$region_name . '_classes'] = !empty($layout['settings']['classes'][$region_name]) ? ' ' . implode(' ', $layout['settings']['classes'][$region_name]) : '';
+ }
+ }
+}
diff --git a/sites/all/modules/entity/LICENSE.txt b/sites/all/modules/entity/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/entity/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/entity/README.txt b/sites/all/modules/entity/README.txt
new file mode 100644
index 000000000..745dd7fe8
--- /dev/null
+++ b/sites/all/modules/entity/README.txt
@@ -0,0 +1,164 @@
+
+Entity API module
+-----------------
+by Wolfgang Ziegler, nuppla@zites.net
+
+This module extends the entity API of Drupal core in order to provide a unified
+way to deal with entities and their properties. Additionally, it provides an
+entity CRUD controller, which helps simplifying the creation of new entity types.
+
+
+This is an API module. You only need to enable it if a module depends on it or
+you are interested in using it for development.
+
+Advanced usage:
+---------------
+You can optimize cache clearing performance by setting the variable
+'entity_rebuild_on_flush' to FALSE. This skips rebuilding of feature
+components and exported entities during cache flushing. Instead, it is triggered
+by the features module only; e.g., when features are reverted.
+
+
+The README below is for interested developers. If you are not interested in
+developing, you may stop reading now.
+
+--------------------------------------------------------------------------------
+ Entity API
+--------------------------------------------------------------------------------
+
+ * The module provides API functions allowing modules to create, save, delete
+ or to determine access for entities based on any entity type, for which the
+ necessary metadata is available. The module comes with integration for all
+ core entity types, as well as for entities provided via the Entity CRUD API
+ (see below). However, for any other entity type implemented by a contrib
+ module, the module integration has to be provided by the contrib module
+ itself.
+
+ * Thus the module provides API functions like entity_save(), entity_create(),
+ entity_delete(), entity_revision_delete(), entity_view() and entity_access()
+ among others.
+ entity_load(), entity_label() and entity_uri() are already provided by
+ Drupal core.
+
+ * For more information about how to provide this metadata, have a look at the
+ API documentation, i.e. entity_metadata_hook_entity_info().
+
+--------------------------------------------------------------------------------
+ Entity CRUD API - Providing new entity types
+--------------------------------------------------------------------------------
+
+ * This API helps you when defining a new entity type. It provides an entity
+ controller, which implements full CRUD functionality for your entities.
+
+ * To make use of the CRUD functionality you may just use the API functions
+ entity_create(), entity_delete() and entity_save().
+
+ Alternatively you may specify a class to use for your entities, for which the
+ "Entity" class is provided. In particular, it is useful to extend this class
+ in order to easily customize the entity type, e.g. saving.
+
+ * The controller supports fieldable entities and revisions. There is also a
+ controller which supports implementing exportable entities.
+
+ * The Entity CRUD API helps with providing additional module integration too,
+ e.g. exportable entities are automatically integrated with the Features
+ module. These module integrations are implemented in separate controller
+ classes, which may be overridden and deactivated on their own.
+
+ * There is also an optional ui controller class, which assists with providing
+ an administrative UI for managing entities of a certain type.
+
+ * For more details check out the documentation in the drupal.org handbook
+ http://drupal.org/node/878804 as well as the API documentation, i.e.
+ entity_crud_hook_entity_info().
+
+
+ Basic steps to add a new entity type:
+---------------------------------------
+
+ * You might want to study the code of the "entity_test.module".
+
+ * Describe your entities db table as usual in hook_schema().
+
+ * Just use the "Entity" directly or extend it with your own class.
+ To see how to provide a separate class have a look at the "EntityClass" from
+ the "entity_test.module".
+
+ * Implement hook_entity_info() for your entity. At least specifiy the
+ controller class (EntityAPIController, EntityAPIControllerExportable or your
+ own), your db table and your entity's keys.
+ Again just look at "entity_test.module"'s hook_entity_info() for guidance.
+
+ * If you want your entity to be fieldable just set 'fieldable' in
+ hook_entity_info() to TRUE. The field API attachers are then called
+ automatically in the entity CRUD functions.
+
+ * The entity API is able to deal with bundle objects too (e.g. the node type
+ object). For that just specify another entity type for the bundle objects
+ and set the 'bundle of' property for it.
+ Again just look at "entity_test.module"'s hook_entity_info() for guidance.
+
+ * Schema fields marked as 'serialized' are automatically unserialized upon
+ loading as well as serialized on saving. If the 'merge' attribute is also
+ set to TRUE the unserialized data is automatically "merged" into the entity.
+
+ * Further details can be found at http://drupal.org/node/878804.
+
+
+
+--------------------------------------------------------------------------------
+ Entity Properties & Entity metadata wrappers
+--------------------------------------------------------------------------------
+
+ * This module introduces a unique place for metadata about entity properties:
+ hook_entity_property_info(), whereas hook_entity_property_info() may be
+ placed in your module's {YOUR_MODULE}.info.inc include file. For details
+ have a look at the API documentation, i.e. hook_entity_property_info() and
+ at http://drupal.org/node/878876.
+
+ * The information about entity properties contains the data type and callbacks
+ for how to get and set the data of the property. That way the data of an
+ entity can be easily re-used, e.g. to export it into other data formats like
+ XML.
+
+ * For making use of this information (metadata) the module provides some
+ wrapper classes which ease getting and setting values. The wrapper supports
+ chained usage for retrieving wrappers of entity properties, e.g. to get a
+ node author's mail address one could use:
+
+ $wrapper = entity_metadata_wrapper('node', $node);
+ $wrapper->author->mail->value();
+
+ To update the user's mail address one could use
+
+ $wrapper->author->mail->set('sepp@example.com');
+
+ or
+
+ $wrapper->author->mail = 'sepp@example.com';
+
+ The wrappers always return the data as described in the property
+ information, which may be retrieved directly via entity_get_property_info()
+ or from the wrapper:
+
+ $mail_info = $wrapper->author->mail->info();
+
+ In order to force getting a textual value sanitized for output one can use,
+ e.g.
+
+ $wrapper->title->value(array('sanitize' => TRUE));
+
+ to get the sanitized node title. When a property is already returned
+ sanitized by default, like the node body, one possibly wants to get the
+ not-sanitized data as it would appear in a browser for other use-cases.
+ To do so one can enable the 'decode' option, which ensures for any sanitized
+ data the tags are stripped and HTML entities are decoded before the property
+ is returned:
+
+ $wrapper->body->value->value(array('decode' => TRUE));
+
+ That way one always gets the data as shown to the user. However if you
+ really want to get the raw, unprocessed value, even for sanitized textual
+ data, you can do so via:
+
+ $wrapper->body->value->raw();
diff --git a/sites/all/modules/entity/ctools/content_types/entity_view.inc b/sites/all/modules/entity/ctools/content_types/entity_view.inc
new file mode 100644
index 000000000..e50563328
--- /dev/null
+++ b/sites/all/modules/entity/ctools/content_types/entity_view.inc
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Content type plugin to expose rendered entities, view mode configuration
+ * still available.
+ */
+
+$plugin = array(
+ 'title' => t('Rendered entity'),
+ 'defaults' => array('view_mode' => 'full'),
+ 'content type' => 'entity_entity_view_content_type_info',
+);
+
+/**
+ * Get the entity content type info.
+ */
+function entity_entity_view_content_type_info($entity_type) {
+ $types = entity_entity_view_content_type_content_types();
+ if (isset($types[$entity_type])) {
+ return $types[$entity_type];
+ }
+}
+
+/**
+ * Implements hook_PLUGIN_content_type_content_types().
+ *
+ * Rendered entity use entity types machine name as subtype name.
+ */
+function entity_entity_view_content_type_content_types() {
+ $types = array();
+ $entities = entity_get_info();
+
+ foreach ($entities as $entity_type => $info) {
+ if (entity_type_supports($entity_type, 'view')) {
+ $types[$entity_type] = array(
+ 'title' => t('Rendered @entity_type', array('@entity_type' => $info['label'])),
+ 'category' => t('Entity'),
+ 'required context' => new ctools_context_required(t('Entity'), $entity_type),
+ );
+ }
+ }
+
+ return $types;
+}
+
+/**
+ * Returns an edit form for a entity.
+ *
+ * Rendered entity use entity types machine name as subtype name.
+ *
+ * @see entity_entity_view_get_content_types()
+ */
+function entity_entity_view_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $entity_type = $form_state['subtype_name'];
+ $entity_info = entity_get_info($entity_type);
+
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+
+ if (count($options) > 1) {
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $conf['view_mode'],
+ );
+ }
+ else {
+ $form['view_mode_info'] = array(
+ '#type' => 'item',
+ '#title' => t('View mode'),
+ '#description' => t('Only one view mode is available for this entity type.'),
+ '#markup' => $options ? current($options) : t('Default'),
+ );
+
+ $form['view_mode'] = array(
+ '#type' => 'value',
+ '#value' => $options ? key($options) : 'default',
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Save selected view mode.
+ */
+function entity_entity_view_content_type_edit_form_submit(&$form, &$form_state) {
+ if (isset($form_state['values']['view_mode'])) {
+ $form_state['conf']['view_mode'] = $form_state['values']['view_mode'];
+ }
+}
+
+/**
+ * Implements hook_PLUGIN_content_type_render().
+ *
+ * Ctools requires us to return a block.
+ *
+ * @see ctools_content_render()
+ */
+function entity_entity_view_content_type_render($entity_type, $conf, $panel_args, $context) {
+ if ($context->empty) {
+ return;
+ }
+ $block = new stdClass();
+ $block->module = 'entity';
+ $block->delta = $entity_type . '-' . str_replace('-', '_', $conf['view_mode']);
+
+ $entity_id = $context->argument;
+ $entity = entity_load_single($entity_type, $entity_id);
+ $block->content = entity_view($entity_type, array($entity_id => $entity), $conf['view_mode']);
+ return $block;
+}
+
+/**
+ * Implements hook_PLUGIN_content_type_admin_title().
+ *
+ * Returns the administrative title for a type.
+ */
+function entity_entity_view_content_type_admin_title($entity_type, $conf, $contexts) {
+ $entity_info = entity_get_info($entity_type);
+ $view_mode = $conf['view_mode'];
+ if (isset($entity_info['view modes'][$view_mode])) {
+ $view_mode = $entity_info['view modes'][$view_mode]['label'];
+ }
+ return t('Rendered @entity_type using view mode "@view_mode"', array('@entity_type' => $entity_info['label'], '@view_mode' => $view_mode));
+}
diff --git a/sites/all/modules/entity/entity.api.php b/sites/all/modules/entity/entity.api.php
new file mode 100644
index 000000000..3f7775265
--- /dev/null
+++ b/sites/all/modules/entity/entity.api.php
@@ -0,0 +1,467 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the entity API.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Provide an entity type via the entity CRUD API.
+ *
+ * This is a placeholder for describing further keys for hook_entity_info(),
+ * which are introduced the entity API for providing a new entity type with the
+ * entity CRUD API. For that the entity API provides two controllers:
+ * - EntityAPIController: A regular CRUD controller.
+ * - EntityAPIControllerExportable: Extends the regular controller to
+ * additionally support exportable entities and/or entities making use of a
+ * name key.
+ * See entity_metadata_hook_entity_info() for the documentation of additional
+ * keys for hook_entity_info() as introduced by the entity API and supported for
+ * any entity type.
+ *
+ * The entity CRUD API supports the following keys:
+ * - entity class: (optional) A class the controller will use for instantiating
+ * entities. It is suggested to make use of the provided "Entity" class or to
+ * extend it.
+ * - bundle of: (optional) Entity types can be used as bundles for
+ * other entity types. To enable this functionality, use the 'bundle of' key
+ * to indicate which entity type this entity serves as a bundle for. But note
+ * that the other entity type will still need to declare entities of this
+ * type as bundles, as described by the documentation of hook_entity_info().
+ * If the other entity type is fieldable, the entity API controller takes
+ * care of invoking the field API bundle attachers. Note that
+ * field_attach_delete_bundle() has to be invoked manually upon module
+ * uninstallation. See entity_test_entity_info() and entity_test_uninstall()
+ * for examples.
+ * - module: (optional) The module providing the entity type. This is optional,
+ * but strongly suggested.
+ * - exportable: (optional) Whether the entity is exportable. Defaults to FALSE.
+ * If enabled, a name key should be specified and db columns for the module
+ * and status key as defined by entity_exportable_schema_fields() have to
+ * exist in the entity's base table. Also see 'entity keys' below.
+ * This option requires the EntityAPIControllerExportable to work.
+ * - entity keys: An array of keys as defined by Drupal core. The following
+ * additional keys are used by the entity CRUD API:
+ * - name: (optional) The key of the entity property containing the unique,
+ * machine readable name of the entity. If specified, this is used as
+ * identifier of the entity, while the usual 'id' key is still required and
+ * may be used when modules deal with entities generically, or to refer to
+ * the entity internally, i.e. in the database.
+ * If a name key is given, the name is used as entity identifier by the
+ * entity API module, metadata wrappers and entity-type specific hooks.
+ * However note that for consistency all generic entity hooks like
+ * hook_entity_load() are invoked with the entities keyed by numeric id,
+ * while entity-type specific hooks like hook_{entity_type}_load() are
+ * invoked with the entities keyed by name.
+ * Also, just as entity_load_single() entity_load() may be called
+ * with names passed as the $ids parameter, while the results of
+ * entity_load() are always keyed by numeric id. Thus, it is suggested to
+ * make use of entity_load_multiple_by_name() to implement entity-type
+ * specific loading functions like {entity_type}_load_multiple(), as this
+ * function returns the entities keyed by name. See entity_test_get_types()
+ * for an example.
+ * For exportable entities, it is strongly recommended to make use of a
+ * machine name as names are portable across systems.
+ * This option requires the EntityAPIControllerExportable to work.
+ * - module: (optional) A key for the module property used by the entity CRUD
+ * API to save the source module name for exportable entities that have been
+ * provided in code. Defaults to 'module'.
+ * - status: (optional) The name of the entity property used by the entity
+ * CRUD API to save the exportable entity status using defined bit flags.
+ * Defaults to 'status'. See entity_has_status().
+ * - default revision: (optional) The name of the entity property used by
+ * the entity CRUD API to determine if a newly-created revision should be
+ * set as the default revision. Defaults to 'default_revision'.
+ * Note that on entity insert the created revision will be always default
+ * regardless of the value of this entity property.
+ * - export: (optional) An array of information used for exporting. For ctools
+ * exportables compatibility any export-keys supported by ctools may be added
+ * to this array too.
+ * - default hook: What hook to invoke to find exportable entities that are
+ * currently defined. This hook is automatically called by the CRUD
+ * controller during entity_load(). Defaults to 'default_' . $entity_type.
+ * - admin ui: (optional) An array of optional information used for providing an
+ * administrative user interface. To enable the UI at least the path must be
+ * given. Apart from that, the 'access callback' (see below) is required for
+ * the entity, as well as the 'ENTITY_TYPE_form' for editing, adding and
+ * cloning. The form gets the entity and the operation ('edit', 'add' or
+ * 'clone') passed. See entity_ui_get_form() for more details.
+ * Known keys are:
+ * - path: A path where the UI should show up as expected by hook_menu().
+ * - controller class: (optional) A controller class name for providing the
+ * UI. Defaults to EntityDefaultUIController, which implements an admin UI
+ * suiting for managing configuration entities. Other provided controllers
+ * suiting for content entities are EntityContentUIController or
+ * EntityBundleableUIController (which work fine despite the poorly named
+ * 'admin ui' key).
+ * For customizing the UI inherit from the default class and override
+ * methods as suiting and specify your class as controller class.
+ * - file: (optional) The name of the file in which the entity form resides
+ * as it is required by hook_menu().
+ * - file path: (optional) The path to the file as required by hook_menu. If
+ * not set, it defaults to entity type's module's path, thus the entity
+ * types 'module' key is required.
+ * - menu wildcard: The wildcard to use in paths of the hook_menu() items.
+ * Defaults to %entity_object which is the loader provided by Entity API.
+ * - rules controller class: (optional) A controller class for providing Rules
+ * integration, or FALSE to disable this feature. The given class has to
+ * inherit from the class EntityDefaultRulesController, which serves as
+ * default in case the entity type is not marked as configuration. For
+ * configuration entities it defaults to FALSE.
+ * - metadata controller class: (optional) A controller class for providing
+ * entity property info. By default some info is generated out of the
+ * information provided in your hook_schema() implementation, while only read
+ * access is granted to that properties by default. Based upon that the
+ * Entity tokens module also generates token replacements for your entity
+ * type, once activated.
+ * Override the controller class to adapt the defaults and to improve and
+ * complete the generated metadata. Set it to FALSE to disable this feature.
+ * Defaults to the EntityDefaultMetadataController class.
+ * - extra fields controller class: (optional) A controller class for providing
+ * field API extra fields. Defaults to none.
+ * The class must implement the EntityExtraFieldsControllerInterface. Display
+ * extra fields that are exposed that way are rendered by default by the
+ * EntityAPIController. The EntityDefaultExtraFieldsController class may be
+ * used to generate extra fields based upon property metadata, which in turn
+ * get rendered by default by the EntityAPIController.
+ * - features controller class: (optional) A controller class for providing
+ * Features module integration for exportable entities. The given class has to
+ * inherit from the default class being EntityDefaultFeaturesController. Set
+ * it to FALSE to disable this feature.
+ * - i18n controller class: (optional) A controller class for providing
+ * i18n module integration for (exportable) entities. The given class has to
+ * inherit from the class EntityDefaultI18nStringController. Defaults to
+ * FALSE (disabled). See EntityDefaultI18nStringController for more
+ * information.
+ * - views controller class: (optional) A controller class for providing views
+ * integration. The given class has to inherit from the class
+ * EntityDefaultViewsController, which is set as default in case the providing
+ * module has been specified (see 'module') and the module does not provide
+ * any views integration. Else it defaults to FALSE, which disables this
+ * feature. See EntityDefaultViewsController.
+ * - access callback: (optional) Specify a callback that returns access
+ * permissions for the operations 'create', 'update', 'delete' and 'view'.
+ * The callback gets optionally the entity and the user account to check for
+ * passed. See entity_access() for more details on the arguments and
+ * entity_metadata_no_hook_node_access() for an example.
+ * This is optional, but suggested for the Rules integration, and required for
+ * the admin ui (see above).
+ * - form callback: (optional) Specfiy a callback that returns a fully built
+ * edit form for your entity type. See entity_form().
+ * In case the 'admin ui' is used, no callback needs to be specified.
+ * - entity cache: (optional) Whether entities should be cached using the cache
+ * system. Requires the entitycache module to be installed and enabled and the
+ * module key to be specified. As cached entities are only retrieved by id key,
+ * the cache would not apply to exportable entities retrieved by name key.
+ * If enabled and the entitycache module is active, 'field cache' is obsolete
+ * and is automatically disabled. Defaults to FALSE.
+ *
+ * @see hook_entity_info()
+ * @see entity_metadata_hook_entity_info()
+ */
+function entity_crud_hook_entity_info() {
+ $return = array(
+ 'entity_test' => array(
+ 'label' => t('Test Entity'),
+ 'entity class' => 'Entity',
+ 'controller class' => 'EntityAPIController',
+ 'base table' => 'entity_test',
+ 'module' => 'entity_test',
+ 'fieldable' => TRUE,
+ 'entity keys' => array(
+ 'id' => 'pid',
+ 'name' => 'name',
+ 'bundle' => 'type',
+ ),
+ 'bundles' => array(),
+ ),
+ );
+ foreach (entity_test_get_types() as $name => $info) {
+ $return['entity_test']['bundles'][$name] = array(
+ 'label' => $info['label'],
+ );
+ }
+ return $return;
+}
+
+/**
+ * Provide additional metadata for entities.
+ *
+ * This is a placeholder for describing further keys for hook_entity_info(),
+ * which are introduced the entity API in order to support any entity type; e.g.
+ * to make entity_save(), entity_create(), entity_view() and others work.
+ * See entity_crud_hook_entity_info() for the documentation of additional keys
+ * for hook_entity_info() as introduced by the entity API for providing new
+ * entity types with the entity CRUD API.
+ *
+ * Additional keys are:
+ * - plural label: (optional) The human-readable, plural name of the entity
+ * type. As 'label' it should start capitalized.
+ * - description: (optional) A human-readable description of the entity type.
+ * - access callback: (optional) Specify a callback that returns access
+ * permissions for the operations 'create', 'update', 'delete' and 'view'.
+ * The callback gets optionally the entity and the user account to check for
+ * passed. See entity_access() for more details on the arguments and
+ * entity_metadata_no_hook_node_access() for an example.
+ * - creation callback: (optional) A callback that creates a new instance of
+ * this entity type. See entity_metadata_create_node() for an example.
+ * - save callback: (optional) A callback that permanently saves an entity of
+ * this type.
+ * - deletion callback: (optional) A callback that permanently deletes an
+ * entity of this type.
+ * - revision deletion callback: (optional) A callback that deletes a revision
+ * of the entity.
+ * - view callback: (optional) A callback to render a list of entities.
+ * See entity_metadata_view_node() as example.
+ * - form callback: (optional) A callback that returns a fully built edit form
+ * for the entity type.
+ * - token type: (optional) A type name to use for token replacements. Set it
+ * to FALSE if there aren't any token replacements for this entity type.
+ * - configuration: (optional) A boolean value that specifies whether the entity
+ * type should be considered as configuration. Modules working with entities
+ * may use this value to decide whether they should deal with a certain entity
+ * type. Defaults to TRUE to for entity types that are exportable, else to
+ * FALSE.
+ *
+ * @see hook_entity_info()
+ * @see entity_crud_hook_entity_info()
+ * @see entity_access()
+ * @see entity_create()
+ * @see entity_save()
+ * @see entity_delete()
+ * @see entity_view()
+ * @see entity_form()
+ */
+function entity_metadata_hook_entity_info() {
+ return array(
+ 'node' => array(
+ 'label' => t('Node'),
+ 'access callback' => 'entity_metadata_no_hook_node_access',
+ // ...
+ ),
+ );
+}
+
+/**
+ * Allow modules to define metadata about entity properties.
+ *
+ * Modules providing properties for any entities defined in hook_entity_info()
+ * can implement this hook to provide metadata about this properties.
+ * For making use of the metadata have a look at the provided wrappers returned
+ * by entity_metadata_wrapper().
+ * For providing property information for fields see entity_hook_field_info().
+ *
+ * @return
+ * An array whose keys are entity type names and whose values are arrays
+ * containing the keys:
+ * - properties: The array describing all properties for this entity. Entries
+ * are keyed by the property name and contain an array of metadata for each
+ * property. The name may only contain alphanumeric lowercase characters
+ * and underscores. Known keys are:
+ * - label: A human readable, translated label for the property.
+ * - description: (optional) A human readable, translated description for
+ * the property.
+ * - type: The data type of the property. To make the property actually
+ * useful it is important to map your properties to one of the known data
+ * types, which currently are:
+ * - text: Any text.
+ * - token: A string containing only lowercase letters, numbers, and
+ * underscores starting with a letter; e.g. this type is useful for
+ * machine readable names.
+ * - integer: A usual PHP integer value.
+ * - decimal: A PHP float or integer.
+ * - date: A full date and time, as timestamp.
+ * - duration: A duration as number of seconds.
+ * - boolean: A usual PHP boolean value.
+ * - uri: An absolute URI or URL.
+ * - entities - You may use the type of each entity known by
+ * hook_entity_info(), e.g. 'node' or 'user'. Internally entities are
+ * represented by their identifieres. In case of single-valued
+ * properties getter callbacks may return full entity objects as well,
+ * while a value of FALSE is interpreted like a NULL value as "property
+ * is not set".
+ * - entity: A special type to be used generically for entities where the
+ * entity type is not known beforehand. The entity has to be
+ * represented using an EntityMetadataWrapper.
+ * - struct: This as well as any else not known type may be used for
+ * supporting arbitrary data structures. For that additional metadata
+ * has to be specified with the 'property info' key. New type names
+ * have to be properly prefixed with the module name.
+ * - list: A list of values, represented as numerically indexed array.
+ * The list<TYPE> notation may be used to specify the type of the
+ * contained items, where TYPE may be any valid type expression.
+ * - bundle: (optional) If the property is an entity, you may specify the
+ * bundle of the referenced entity.
+ * - options list: (optional) A callback that returns a list of possible
+ * values for the property. The callback has to return an array as
+ * used by hook_options_list().
+ * Note that it is possible to return a different set of options depending
+ * whether they are used in read or in write context. See
+ * EntityMetadataWrapper::optionsList() for more details on that.
+ * - getter callback: (optional) A callback used to retrieve the value of
+ * the property. Defaults to entity_property_verbatim_get().
+ * It is important that your data is represented, as documented for your
+ * data type, e.g. a date has to be a timestamp. Thus if necessary, the
+ * getter callback has to do the necessary conversion. In case of an empty
+ * or not set value, the callback has to return NULL.
+ * - setter callback: (optional) A callback used to set the value of the
+ * property. In many cases entity_property_verbatim_set() can be used.
+ * - validation callback: (optional) A callback that returns whether the
+ * passed data value is valid for the property. May be used to implement
+ * additional validation checks, such as to ensure the value is a valid
+ * mail address.
+ * - access callback: (optional) An access callback to allow for checking
+ * 'view' and 'edit' access for the described property. If no callback
+ * is specified, a 'setter permission' may be specified instead.
+ * - setter permission: (optional) A permission that describes whether
+ * a user has permission to set ('edit') this property. This permission
+ * is only be taken into account, if no 'access callback' is given.
+ * - schema field: (optional) In case the property is directly based upon
+ * a field specified in the entity's hook_schema(), the name of the field.
+ * - queryable: (optional) Whether a property is queryable with
+ * EntityFieldQuery. Defaults to TRUE if a 'schema field' is specified, or
+ * if the deprecated 'query callback' is set to
+ * 'entity_metadata_field_query'. Otherwise it defaults to FALSE.
+ * - query callback: (deprecated) A callback for querying for entities
+ * having the given property value. See entity_property_query().
+ * Generally, properties should be queryable via EntityFieldQuery. If
+ * that is the case, just set 'queryable' to TRUE.
+ * - required: (optional) Whether this property is required for the creation
+ * of a new instance of its entity. See
+ * entity_property_values_create_entity().
+ * - field: (optional) A boolean indicating whether a property is stemming
+ * from a field.
+ * - computed: (optional) A boolean indicating whether a property is
+ * computed, i.e. the property value is not stored or loaded by the
+ * entity's controller but determined on the fly by the getter callback.
+ * Defaults to FALSE.
+ * - entity views field: (optional) If enabled, the property is
+ * automatically exposed as views field available to all views query
+ * backends listing this entity-type. As the property value will always be
+ * generated from a loaded entity object, this is particularly useful for
+ * 'computed' properties. Defaults to FALSE.
+ * - sanitized: (optional) For textual properties only, whether the text is
+ * already sanitized. In this case you might want to also specify a raw
+ * getter callback. Defaults to FALSE.
+ * - sanitize: (optional) For textual properties, that are not sanitized
+ * yet, specify a function for sanitizing the value. Defaults to
+ * check_plain().
+ * - raw getter callback: (optional) For sanitized textual properties, a
+ * separate callback which can be used to retrieve the raw, unprocessed
+ * value.
+ * - clear: (optional) An array of property names, of which the cache should
+ * be cleared too once this property is updated. As a rule of thumb any
+ * duplicated properties should be avoided though.
+ * - property info: (optional) An array of info for an arbitrary data
+ * structure together with any else not defined type, see data type
+ * 'struct'. Specify metadata in the same way as defined for this hook.
+ * - property info alter: (optional) A callback for altering the property
+ * info before it is used by the metadata wrappers.
+ * - property defaults: (optional) An array of property info defaults for
+ * each property derived of the wrapped data item (e.g. an entity).
+ * Applied by the metadata wrappers.
+ * - auto creation: (optional) Properties of type 'struct' may specify
+ * this callback which is used to automatically create the data structure
+ * (e.g. an array) if necessary. This is necessary in order to support
+ * setting a property of a not yet initialized data structure.
+ * See entity_metadata_field_file_callback() for an example.
+ * - translatable: (optional) Whether the property is translatable, defaults
+ * to FALSE.
+ * - entity token: (optional) If Entity tokens module is enabled, the
+ * module provides a token for the property if one does not exist yet.
+ * Specify FALSE to disable this functionality for the property.
+ * - bundles: An array keyed by bundle name containing further metadata
+ * related to the bundles only. This array may contain the key 'properties'
+ * with an array of info about the bundle specific properties, structured in
+ * the same way as the entity properties array.
+ *
+ * @see hook_entity_property_info_alter()
+ * @see entity_get_property_info()
+ * @see entity_metadata_wrapper()
+ */
+function hook_entity_property_info() {
+ $info = array();
+ $properties = &$info['node']['properties'];
+
+ $properties['nid'] = array(
+ 'label' => t("Content ID"),
+ 'type' => 'integer',
+ 'description' => t("The unique content ID."),
+ );
+ return $info;
+}
+
+/**
+ * Allow modules to alter metadata about entity properties.
+ *
+ * @see hook_entity_property_info()
+ */
+function hook_entity_property_info_alter(&$info) {
+ $properties = &$info['node']['bundles']['poll']['properties'];
+
+ $properties['poll-votes'] = array(
+ 'label' => t("Poll votes"),
+ 'description' => t("The number of votes that have been cast on a poll node."),
+ 'type' => 'integer',
+ 'getter callback' => 'entity_property_poll_node_get_properties',
+ );
+}
+
+/**
+ * Provide entity property information for fields.
+ *
+ * This is a placeholder for describing further keys for hook_field_info(),
+ * which are introduced by the entity API.
+ *
+ * For providing entity property info for fields each field type may specify a
+ * property type to map to using the key 'property_type'. With that info in
+ * place useful defaults are generated, which suffice for a lot of field
+ * types.
+ * However it is possible to specify further callbacks that may alter the
+ * generated property info. To do so use the key 'property_callbacks' and set
+ * it to an array of function names. Apart from that any property info provided
+ * for a field instance using the key 'property info' is added in too.
+ *
+ * @see entity_field_info_alter()
+ * @see entity_metadata_field_text_property_callback()
+ */
+function entity_hook_field_info() {
+ return array(
+ 'text' => array(
+ 'label' => t('Text'),
+ 'property_type' => 'text',
+ // ...
+ ),
+ );
+}
+
+/**
+ * Alter the handlers used by the data selection tables provided by this module.
+ *
+ * @param array $field_handlers
+ * An array of the field handler classes to use for specific types. The keys
+ * are the types, mapped to their respective classes. Contained types are:
+ * - All primitive types known by the entity API (see
+ * hook_entity_property_info()).
+ * - options: Special type for fields having an options list.
+ * - field: Special type for Field API fields.
+ * - entity: Special type for entity-valued fields.
+ * - relationship: Views relationship handler to use for relationships.
+ * Values for all specific entity types can be additionally added.
+ *
+ * @see entity_views_field_definition()
+ * @see entity_views_get_field_handlers()
+ */
+function hook_entity_views_field_handlers_alter(array &$field_handlers) {
+ $field_handlers['duration'] = 'example_duration_handler';
+ $field_handlers['node'] = 'example_node_handler';
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/sites/all/modules/entity/entity.features.inc b/sites/all/modules/entity/entity.features.inc
new file mode 100644
index 000000000..e394169b6
--- /dev/null
+++ b/sites/all/modules/entity/entity.features.inc
@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * @file
+ * Provides Features integration for entity types using the CRUD API.
+ */
+
+/**
+ * Returns the configured entity features controller.
+ *
+ * @param string $type
+ * The entity type to get the controller for.
+ *
+ * @return EntityDefaultFeaturesController
+ * The configured entity features controller.
+ */
+function entity_features_get_controller($type) {
+ $static = &drupal_static(__FUNCTION__);
+ if (!isset($static[$type])) {
+ $info = entity_get_info($type);
+ $info += array('features controller class' => 'EntityDefaultFeaturesController');
+ $static[$type] = $info['features controller class'] ? new $info['features controller class']($type) : FALSE;
+ }
+ return $static[$type];
+}
+
+/**
+ * Default controller handling features integration.
+ */
+class EntityDefaultFeaturesController {
+
+ protected $type, $info;
+
+ public function __construct($type) {
+ $this->type = $type;
+ $this->info = entity_get_info($type);
+ $this->info['entity keys'] += array('module' => 'module', 'status' => 'status');
+ $this->statusKey = $this->info['entity keys']['status'];
+ $this->moduleKey = $this->info['entity keys']['module'];
+ if (!empty($this->info['bundle of'])) {
+ $entity_info = entity_get_info($this->info['bundle of']);
+ $this->bundleKey = $entity_info['bundle keys']['bundle'];
+ }
+ }
+
+ /**
+ * Defines the result for hook_features_api().
+ */
+ public function api() {
+ return array(
+ // The entity type has to be the features component name.
+ $this->type => array(
+ 'name' => $this->info['label'],
+ 'feature_source' => TRUE,
+ 'default_hook' => isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type,
+ // Use the provided component callbacks making use of the controller.
+ 'base' => 'entity',
+ 'file' => drupal_get_path('module', 'entity') . '/entity.features.inc',
+ ),
+ );
+ }
+
+ /**
+ * Generates the result for hook_features_export_options().
+ */
+ public function export_options() {
+ $options = array();
+ foreach (entity_load_multiple_by_name($this->type, FALSE) as $name => $entity) {
+ $options[$name] = entity_label($this->type, $entity);
+ }
+ return $options;
+ }
+
+ /**
+ * Generates the result for hook_features_export().
+ */
+ public function export($data, &$export, $module_name = '') {
+ $pipe = array();
+ foreach (entity_load_multiple_by_name($this->type, $data) as $name => $entity) {
+ // If this entity is provided by a different module, add it as dependency.
+ if (($entity->{$this->statusKey} & ENTITY_IN_CODE) && $entity->{$this->moduleKey} != $module_name) {
+ $module = $entity->{$this->moduleKey};
+ $export['dependencies'][$module] = $module;
+ }
+ // Otherwise export the entity.
+ else {
+ $export['features'][$this->type][$name] = $name;
+
+ // If this is a bundle of a fieldable entity, add its fields to the pipe.
+ if (!empty($this->info['bundle of'])) {
+ $fields = field_info_instances($this->info['bundle of'], $entity->{$this->bundleKey});
+ foreach ($fields as $name => $field) {
+ $pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}";
+ }
+ }
+ }
+ }
+ // Add the module providing the entity type as dependency.
+ if ($data && !empty($this->info['module'])) {
+ $export['dependencies'][$this->info['module']] = $this->info['module'];
+ // In case entity is not already an indirect dependency, add it.
+ // We can do so without causing redundant dependencies because,
+ // if entity is an indirect dependency, Features will filter it out.
+ $export['dependencies']['entity'] = 'entity';
+ }
+ return $pipe;
+ }
+
+ /**
+ * Generates the result for hook_features_export_render().
+ */
+ function export_render($module, $data, $export = NULL) {
+ $output = array();
+ $output[] = ' $items = array();';
+ foreach (entity_load_multiple_by_name($this->type, $data) as $name => $entity) {
+ $export = " \$items['$name'] = entity_import('{$this->type}', '";
+ // Make sure to escape the characters \ and '.
+ $export .= addcslashes(entity_export($this->type, $entity, ' '), '\\\'');
+ $export .= "');";
+ $output[] = $export;
+ }
+ $output[] = ' return $items;';
+ $output = implode("\n", $output);
+
+ $hook = isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type;
+ return array($hook => $output);
+ }
+
+ /**
+ * Generates the result for hook_features_revert().
+ */
+ function revert($module = NULL) {
+ if ($defaults = features_get_default($this->type, $module)) {
+ foreach ($defaults as $name => $entity) {
+ entity_delete($this->type, $name);
+ }
+ }
+ }
+}
+
+/**
+ * Implements of hook_features_api().
+ */
+function entity_features_api() {
+ $items = array();
+ foreach (entity_crud_get_info() as $type => $info) {
+ if (!empty($info['exportable']) && $controller = entity_features_get_controller($type)) {
+ $items += $controller->api();
+ }
+ }
+ return $items;
+}
+
+/**
+ * Implements hook_features_export_options().
+ *
+ * Features component callback.
+ */
+function entity_features_export_options($a1, $a2 = NULL) {
+ // Due to a change in the Features API the first parameter might be a feature
+ // object or an entity type, depending on the Features version. This check is
+ // for backwards compatibility.
+ $entity_type = is_string($a1) ? $a1 : $a2;
+ return entity_features_get_controller($entity_type)->export_options();
+}
+
+/**
+ * Implements hook_features_export().
+ *
+ * Features component callback.
+ */
+function entity_features_export($data, &$export, $module_name = '', $entity_type) {
+ return entity_features_get_controller($entity_type)->export($data, $export, $module_name);
+}
+
+/**
+ * Implements hook_features_export_render().
+ *
+ * Features component callback.
+ */
+function entity_features_export_render($module, $data, $export = NULL, $entity_type) {
+ return entity_features_get_controller($entity_type)->export_render($module, $data, $export);
+}
+
+/**
+ * Implements hook_features_revert().
+ *
+ * Features component callback.
+ */
+function entity_features_revert($module = NULL, $entity_type) {
+ return entity_features_get_controller($entity_type)->revert($module);
+}
+
+/**
+ * Implements hook_features_post_restore().
+ *
+ * Rebuild all defaults when a features rebuild is triggered - even the ones not
+ * handled by features itself.
+ */
+function entity_features_post_restore($op, $items = array()) {
+ if ($op == 'rebuild') {
+ // Use features rebuild to rebuild the features independent exports too.
+ entity_defaults_rebuild();
+ }
+}
diff --git a/sites/all/modules/entity/entity.i18n.inc b/sites/all/modules/entity/entity.i18n.inc
new file mode 100644
index 000000000..8347eaa7c
--- /dev/null
+++ b/sites/all/modules/entity/entity.i18n.inc
@@ -0,0 +1,210 @@
+<?php
+/**
+ * @file
+ * Internationalization (i18n) integration.
+ */
+
+/**
+ * Gets the i18n controller for a given entity type.
+ *
+ * @return EntityDefaultI18nStringController|array|false
+ * If a type is given, the controller for the given entity type. Else an array
+ * of all enabled controllers keyed by entity type is returned.
+ */
+function entity_i18n_controller($type = NULL) {
+ $static = &drupal_static(__FUNCTION__);
+
+ if (!isset($type)) {
+ // Invoke the function for each type to ensure we have fully populated the
+ // static variable.
+ foreach (entity_get_info() as $entity_type => $info) {
+ entity_i18n_controller($entity_type);
+ }
+ return array_filter($static);
+ }
+
+ if (!isset($static[$type])) {
+ $info = entity_get_info($type);
+ // Do not activate it by default. Modules have to explicitly enable it by
+ // specifying EntityDefaultI18nStringController or their customization.
+ $class = isset($info['i18n controller class']) ? $info['i18n controller class'] : FALSE;
+ $static[$type] = $class ? new $class($type, $info) : FALSE;
+ }
+
+ return $static[$type];
+}
+
+/**
+ * Implements hook_i18n_string_info().
+ */
+function entity_i18n_string_info() {
+ $groups = array();
+ foreach (entity_i18n_controller() as $entity_type => $controller) {
+ $groups += $controller->hook_string_info();
+ }
+ return $groups;
+}
+
+/**
+ * Implements hook_i18n_object_info().
+ */
+function entity_i18n_object_info() {
+ $info = array();
+ foreach (entity_i18n_controller() as $entity_type => $controller) {
+ $info += $controller->hook_object_info();
+ }
+ return $info;
+}
+
+/**
+ * Implements hook_i18n_string_objects().
+ */
+function entity_i18n_string_objects($type) {
+ if ($controller = entity_i18n_controller($type)) {
+ return $controller->hook_string_objects();
+ }
+}
+
+/**
+ * Default controller handling i18n integration.
+ *
+ * Implements i18n string translation for all non-field properties marked as
+ * 'translatable' and having the flag 'i18n string' set. This translation
+ * approach fits in particular for translating configuration, i.e. exportable
+ * entities.
+ *
+ * Requirements for the default controller:
+ * - The entity type providing module must be specified using the 'module' key
+ * in hook_entity_info().
+ * - An 'entity class' derived from the provided class 'Entity' must be used.
+ * - Properties must be declared as 'translatable' and the 'i18n string' flag
+ * must be set to TRUE using hook_entity_property_info().
+ * - i18n must be notified about changes manually by calling
+ * i18n_string_object_update(), i18n_string_object_remove() and
+ * i18n_string_update_context(). Ideally, this is done in a small integration
+ * module depending on the entity API and i18n_string. Look at the provided
+ * testing module "entity_test_i18n" for an example.
+ * - If the entity API admin UI is used, the "translate" tab will be
+ * automatically enabled and linked from the UI.
+ * - There are helpers for getting translated values which work regardless
+ * whether the i18n_string module is enabled, i.e. entity_i18n_string()
+ * and Entity::getTranslation().
+ *
+ * Current limitations:
+ * - Translatable property values cannot be updated via the metadata wrapper,
+ * however reading works fine. See Entity::getTranslation().
+ */
+class EntityDefaultI18nStringController {
+
+ protected $entityType, $entityInfo;
+
+ /**
+ * The i18n textgroup we are using.
+ */
+ protected $textgroup;
+
+ public function __construct($type) {
+ $this->entityType = $type;
+ $this->entityInfo = entity_get_info($type);
+ // By default we go with the module name as textgroup.
+ $this->textgroup = $this->entityInfo['module'];
+ }
+
+ /**
+ * Implements hook_i18n_string_info() via entity_i18n_string_info().
+ */
+ public function hook_string_info() {
+ $list = system_list('module_enabled');
+ $info = $list[$this->textgroup]->info;
+
+ $groups[$this->textgroup] = array(
+ 'title' => $info['name'],
+ 'description' => !empty($info['description']) ? $info['description'] : NULL,
+ 'format' => FALSE,
+ 'list' => TRUE,
+ );
+ return $groups;
+ }
+
+ /**
+ * Implements hook_i18n_object_info() via entity_i18n_object_info().
+ *
+ * Go with the same default values as the admin UI as far as possible.
+ */
+ public function hook_object_info() {
+ $wildcard = $this->menuWildcard();
+ $id_key = !empty($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->entityInfo['entity keys']['id'];
+
+ $info[$this->entityType] = array(
+ // Generic object title.
+ 'title' => $this->entityInfo['label'],
+ // The object key field.
+ 'key' => $id_key,
+ // Placeholders for automatic paths.
+ 'placeholders' => array(
+ $wildcard => $id_key,
+ ),
+
+ // Properties for string translation.
+ 'string translation' => array(
+ // Text group that will handle this object's strings.
+ 'textgroup' => $this->textgroup,
+ // Object type property for string translation.
+ 'type' => $this->entityType,
+ // Translatable properties of these objects.
+ 'properties' => $this->translatableProperties(),
+ ),
+ );
+
+ // Integrate the translate tab into the admin-UI if enabled.
+ if ($base_path = $this->menuBasePath()) {
+ $info[$this->entityType] += array(
+ // To produce edit links automatically.
+ 'edit path' => $base_path . '/manage/' . $wildcard,
+ // Auto-generate translate tab.
+ 'translate tab' => $base_path . '/manage/' . $wildcard . '/translate',
+ );
+ $info[$this->entityType]['string translation'] += array(
+ // Path to translate strings to every language.
+ 'translate path' => $base_path . '/manage/' . $wildcard . '/translate/%i18n_language',
+ );
+ }
+ return $info;
+ }
+
+ /**
+ * Defines the menu base path used by self::hook_object_info().
+ */
+ protected function menuBasePath() {
+ return !empty($this->entityInfo['admin ui']['path']) ? $this->entityInfo['admin ui']['path'] : FALSE;
+ }
+
+ /**
+ * Defines the menu wildcard used by self::hook_object_info().
+ */
+ protected function menuWildcard() {
+ return isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object';
+ }
+
+ /**
+ * Defines translatable properties used by self::hook_object_info().
+ */
+ protected function translatableProperties() {
+ $list = array();
+ foreach (entity_get_all_property_info($this->entityType) as $name => $info) {
+ if (!empty($info['translatable']) && !empty($info['i18n string'])) {
+ $list[$name] = array(
+ 'title' => $info['label'],
+ );
+ }
+ }
+ return $list;
+ }
+
+ /**
+ * Implements hook_i18n_string_objects() via entity_i18n_string_objects().
+ */
+ public function hook_string_objects() {
+ return entity_load_multiple_by_name($this->entityType, FALSE);
+ }
+}
diff --git a/sites/all/modules/entity/entity.info b/sites/all/modules/entity/entity.info
new file mode 100644
index 000000000..a27c41274
--- /dev/null
+++ b/sites/all/modules/entity/entity.info
@@ -0,0 +1,33 @@
+name = Entity API
+description = Enables modules to work with any entity type and to provide entities.
+core = 7.x
+files[] = entity.features.inc
+files[] = entity.i18n.inc
+files[] = entity.info.inc
+files[] = entity.rules.inc
+files[] = entity.test
+files[] = includes/entity.inc
+files[] = includes/entity.controller.inc
+files[] = includes/entity.ui.inc
+files[] = includes/entity.wrapper.inc
+files[] = views/entity.views.inc
+files[] = views/handlers/entity_views_field_handler_helper.inc
+files[] = views/handlers/entity_views_handler_area_entity.inc
+files[] = views/handlers/entity_views_handler_field_boolean.inc
+files[] = views/handlers/entity_views_handler_field_date.inc
+files[] = views/handlers/entity_views_handler_field_duration.inc
+files[] = views/handlers/entity_views_handler_field_entity.inc
+files[] = views/handlers/entity_views_handler_field_field.inc
+files[] = views/handlers/entity_views_handler_field_numeric.inc
+files[] = views/handlers/entity_views_handler_field_options.inc
+files[] = views/handlers/entity_views_handler_field_text.inc
+files[] = views/handlers/entity_views_handler_field_uri.inc
+files[] = views/handlers/entity_views_handler_relationship_by_bundle.inc
+files[] = views/handlers/entity_views_handler_relationship.inc
+files[] = views/plugins/entity_views_plugin_row_entity_view.inc
+; Information added by Drupal.org packaging script on 2016-03-17
+version = "7.x-1.7"
+core = "7.x"
+project = "entity"
+datestamp = "1458222244"
+
diff --git a/sites/all/modules/entity/entity.info.inc b/sites/all/modules/entity/entity.info.inc
new file mode 100644
index 000000000..30da6552a
--- /dev/null
+++ b/sites/all/modules/entity/entity.info.inc
@@ -0,0 +1,265 @@
+<?php
+
+/**
+ * @file
+ * Provides basic entity property info for entities provided via the CRUD API,
+ * as well as property info for all entity types defined by core. For that
+ * the respective modules/MODULE.info.inc files are included.
+ */
+
+/**
+ * Implements hook_entity_property_info().
+ */
+function entity_entity_property_info() {
+ $items = array();
+ // Add in info about entities provided by the CRUD API.
+ foreach (entity_crud_get_info() as $type => $info) {
+ // Automatically enable the controller only if the module does not implement
+ // the hook itself.
+ if (!isset($info['metadata controller class']) && !empty($info['base table']) && (!isset($info['module']) || !module_hook($info['module'], 'entity_property_info'))) {
+ $info['metadata controller class'] = 'EntityDefaultMetadataController';
+ }
+ if (!empty($info['metadata controller class'])) {
+ $controller = new $info['metadata controller class']($type);
+ $items += $controller->entityPropertyInfo();
+ }
+ }
+ // Add in info for all core entities.
+ foreach (_entity_metadata_core_modules() as $module) {
+ module_load_include('inc', 'entity', "modules/$module.info");
+ if (function_exists($function = "entity_metadata_{$module}_entity_property_info")) {
+ if ($return = $function()) {
+ $items = array_merge_recursive($items, $return);
+ }
+ }
+ }
+ return $items;
+}
+
+/**
+ * Implements hook_entity_property_info_alter().
+ */
+function entity_entity_property_info_alter(&$entity_info) {
+ // Add in info for all core entities.
+ foreach (_entity_metadata_core_modules() as $module) {
+ module_load_include('inc', 'entity', "modules/$module.info");
+ if (function_exists($function = "entity_metadata_{$module}_entity_property_info_alter")) {
+ $function($entity_info);
+ }
+ }
+}
+
+function _entity_metadata_core_modules() {
+ return array_filter(array('book', 'comment', 'field', 'locale', 'node', 'taxonomy', 'user', 'system', 'statistics'), 'module_exists');
+}
+
+/**
+ * Default controller for generating some basic metadata for CRUD entity types.
+ */
+class EntityDefaultMetadataController {
+
+ protected $type, $info;
+
+ public function __construct($type) {
+ $this->type = $type;
+ $this->info = entity_get_info($type);
+ }
+
+ public function entityPropertyInfo() {
+ $entity_label = drupal_strtolower($this->info['label']);
+
+ // Provide defaults based on the schema.
+ $info['properties'] = $this->convertSchema();
+ foreach ($info['properties'] as $name => &$property) {
+ // Add a description.
+ $property['description'] = t('@entity "@property" property.', array('@entity' => drupal_ucfirst($entity_label), '@property' => $name));
+ }
+
+ // Set better metadata for known entity keys.
+ $id_key = $this->info['entity keys']['id'];
+
+ if (!empty($this->info['entity keys']['name']) && $key = $this->info['entity keys']['name']) {
+ $info['properties'][$key]['type'] = 'token';
+ $info['properties'][$key]['label'] = t('Machine-readable name');
+ $info['properties'][$key]['description'] = t('The machine-readable name identifying this @entity.', array('@entity' => $entity_label));
+ $info['properties'][$id_key]['label'] = t('Internal, numeric @entity ID', array('@entity' => $entity_label));
+ $info['properties'][$id_key]['description'] = t('The ID used to identify this @entity internally.', array('@entity' => $entity_label));
+ }
+ else {
+ $info['properties'][$id_key]['label'] = t('@entity ID', array('@entity' => drupal_ucfirst($entity_label)));
+ $info['properties'][$id_key]['description'] = t('The unique ID of the @entity.', array('@entity' => $entity_label));
+ }
+ // Care for the bundle.
+ if (!empty($this->info['entity keys']['bundle']) && $key = $this->info['entity keys']['bundle']) {
+ $info['properties'][$key]['type'] = 'token';
+ $info['properties'][$key]['options list'] = array(get_class($this), 'bundleOptionsList');
+ }
+ // Care for the label.
+ if (!empty($this->info['entity keys']['label']) && $key = $this->info['entity keys']['label']) {
+ $info['properties'][$key]['label'] = t('Label');
+ $info['properties'][$key]['description'] = t('The human readable label.');
+ }
+
+ // Add a computed property for the entity URL and expose it to views.
+ if (empty($info['properties']['url']) && !empty($this->info['uri callback'])) {
+ $info['properties']['url'] = array(
+ 'label' => t('URL'),
+ 'description' => t('The URL of the entity.'),
+ 'getter callback' => 'entity_metadata_entity_get_properties',
+ 'type' => 'uri',
+ 'computed' => TRUE,
+ 'entity views field' => TRUE,
+ );
+ }
+
+ return array($this->type => $info);
+ }
+
+ /**
+ * A options list callback returning all bundles for an entity type.
+ */
+ public static function bundleOptionsList($name, $info) {
+ if (!empty($info['parent']) && $type = $info['parent']) {
+ $entity_info = $info['parent']->entityInfo();
+ $options = array();
+ foreach ($entity_info['bundles'] as $name => $bundle_info) {
+ $options[$name] = $bundle_info['label'];
+ }
+ return $options;
+ }
+ }
+
+ /**
+ * Return a set of properties for an entity based on the schema definition
+ */
+ protected function convertSchema() {
+ return entity_metadata_convert_schema($this->info['base table']);
+ }
+}
+
+/**
+ * Converts the schema information available for the given table to property info.
+ *
+ * @param $table
+ * The name of the table as used in hook_schema().
+ * @return
+ * An array of property info as suiting for hook_entity_property_info().
+ */
+function entity_metadata_convert_schema($table) {
+ $schema = drupal_get_schema($table);
+ $properties = array();
+ foreach ($schema['fields'] as $name => $info) {
+ if ($type = _entity_metadata_convert_schema_type($info['type'])) {
+ $properties[$name] = array(
+ 'type' => $type,
+ 'label' => drupal_ucfirst($name),
+ 'schema field' => $name,
+ // As we cannot know about any setter access, leave out the setter
+ // callback. For getting usually no further access callback is needed.
+ );
+ if ($info['type'] == 'serial') {
+ $properties[$name]['validation callback'] = 'entity_metadata_validate_integer_positive';
+ }
+ }
+ }
+ return $properties;
+}
+
+function _entity_metadata_convert_schema_type($type) {
+ switch ($type) {
+ case 'int':
+ case 'serial':
+ case 'date':
+ return 'integer';
+ case 'float':
+ case 'numeric':
+ return 'decimal';
+ case 'char':
+ case 'varchar':
+ case 'text':
+ return 'text';
+ }
+}
+
+/**
+ * Interface for extra fields controller.
+ *
+ * Note: Displays extra fields exposed by this controller are rendered by
+ * default by the EntityAPIController.
+ */
+interface EntityExtraFieldsControllerInterface {
+
+ /**
+ * Returns extra fields for this entity type.
+ *
+ * @see hook_field_extra_fields().
+ */
+ public function fieldExtraFields();
+}
+
+/**
+ * Default controller for generating extra fields based on property metadata.
+ *
+ * By default a display extra field for each property not being a field, ID or
+ * bundle is generated.
+ */
+class EntityDefaultExtraFieldsController implements EntityExtraFieldsControllerInterface {
+
+ /**
+ * @var string
+ */
+ protected $entityType;
+
+ /**
+ * @var array
+ */
+ protected $entityInfo;
+
+ /**
+ * Constructor.
+ */
+ public function __construct($type) {
+ $this->entityType = $type;
+ $this->entityInfo = entity_get_info($type);
+ $this->propertyInfo = entity_get_property_info($type);
+ }
+
+ /**
+ * Implements EntityExtraFieldsControllerInterface::fieldExtraFields().
+ */
+ public function fieldExtraFields() {
+ $extra = array();
+ foreach ($this->propertyInfo['properties'] as $name => $property_info) {
+ // Skip adding the ID or bundle.
+ if ($this->entityInfo['entity keys']['id'] == $name || $this->entityInfo['entity keys']['bundle'] == $name) {
+ continue;
+ }
+ $extra[$this->entityType][$this->entityType]['display'][$name] = $this->generateExtraFieldInfo($name, $property_info);
+ }
+
+ // Handle bundle properties.
+ $this->propertyInfo += array('bundles' => array());
+ foreach ($this->propertyInfo['bundles'] as $bundle_name => $info) {
+ foreach ($info['properties'] as $name => $property_info) {
+ if (empty($property_info['field'])) {
+ $extra[$this->entityType][$bundle_name]['display'][$name] = $this->generateExtraFieldInfo($name, $property_info);
+ }
+ }
+ }
+ return $extra;
+ }
+
+ /**
+ * Generates the display field info for a given property.
+ */
+ protected function generateExtraFieldInfo($name, $property_info) {
+ $info = array(
+ 'label' => $property_info['label'],
+ 'weight' => 0,
+ );
+ if (!empty($property_info['description'])) {
+ $info['description'] = $property_info['description'];
+ }
+ return $info;
+ }
+}
diff --git a/sites/all/modules/entity/entity.install b/sites/all/modules/entity/entity.install
new file mode 100644
index 000000000..118820b80
--- /dev/null
+++ b/sites/all/modules/entity/entity.install
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * @file
+ * Install file for the entity API.
+ */
+
+/**
+ * Implements hook_enable().
+ */
+function entity_enable() {
+ // Create cache tables for entities that support Entity cache module.
+ entity_entitycache_installed_modules();
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function entity_uninstall() {
+ // Delete variables.
+ variable_del('entity_rebuild_on_flush');
+}
+
+/**
+ * The entity API modules have been merged into a single module.
+ */
+function entity_update_7000() {
+ // This empty update is required such that all caches are cleared as
+ // necessary.
+}
+
+/**
+ * Remove the deprecated 'entity_defaults_built' variable.
+ */
+function entity_update_7001() {
+ variable_del('entity_defaults_built');
+}
+
+/**
+ * Clear caches and rebuild registry.
+ */
+function entity_update_7002() {
+ // Do nothing, update.php clears cache for us in case there is an update.
+}
+
+/**
+ * Create cache tables for entities that support Entity cache module.
+ */
+function entity_update_7003() {
+ entity_entitycache_installed_modules();
+}
+
+/**
+ * Create cache tables for entities of modules that support Entity cache module.
+ *
+ * @param $modules
+ * (optional) An array of module names that have been installed.
+ * If not specified, try to add cache tables for all modules.
+ */
+function entity_entitycache_installed_modules($modules = NULL) {
+ if (!module_exists('entitycache')) {
+ return;
+ }
+
+ // If no modules are specified or if entitycache is being installed,
+ // try to add entitycache tables for supporting entities of all modules.
+ if (!isset($modules) || in_array('entitycache', $modules)) {
+ $modules = module_list();
+ }
+
+ // Get all installed modules that support entity cache.
+ $entitycache_module_info = _entity_entitycache_get_module_info($modules);
+
+ // For uninstallation of modules, we need to keep a list of tables we created
+ // per module providing the entity type.
+ $tables_created = variable_get('entity_cache_tables_created');
+
+ foreach ($entitycache_module_info as $module => $module_entitycache_entities) {
+ foreach ($module_entitycache_entities as $entity_type => $entity_info) {
+ // Do not break modules that create the cache tables for themselves.
+ if (!db_table_exists('cache_entity_' . $entity_type)) {
+ $schema = drupal_get_schema_unprocessed('system', 'cache');
+ $schema['description'] = 'Cache table used to store' . $entity_type . ' entity records.';
+ db_create_table('cache_entity_' . $entity_type, $schema);
+ $tables_created[$module][] = 'cache_entity_' . $entity_type;
+ }
+ }
+ }
+ variable_set('entity_cache_tables_created', $tables_created);
+}
+
+/**
+ * Remove entity cache tables for entity types of uninstalled modules.
+ *
+ * @param $modules
+ * (optional) An array of uninstalled modules. If not specified, try to remove
+ * cache tables for all modules.
+ */
+function entity_entitycache_uninstalled_modules($modules = NULL) {
+ // If no modules are specified or if entitycache is being uninstalled,
+ // try to remove entitycache tables for supporting entities of all modules.
+ if (!isset($modules) || in_array('entitycache', $modules)) {
+ $modules = module_list();
+ }
+ $tables_created = variable_get('entity_cache_tables_created');
+ foreach ($modules as $module) {
+ if (!empty($tables_created[$module])) {
+ foreach ($tables_created[$module] as $table) {
+ db_drop_table($table);
+ }
+ unset($tables_created[$module]);
+ }
+ }
+ variable_set('entity_cache_tables_created', $tables_created);
+}
+
+/**
+ * Helper to fetch entity info about entity types that use caching.
+ */
+function _entity_entitycache_get_module_info($modules) {
+ // Prepare a keyed array of all modules with their entity types and infos.
+ // Structure: [module][entity][info]
+ $entity_crud_info = entity_crud_get_info();
+ $info = array();
+ foreach ($entity_crud_info as $entity_name => $entity_info) {
+ // Make sure that the entity info specifies a module and supports entitycache.
+ if (!isset($entity_info['module']) || empty($entity_info['entity cache'])) {
+ continue;
+ }
+ $module = $entity_info['module'];
+ // Only treat installed modules.
+ if (!in_array($module, $modules)) {
+ continue;
+ }
+ // Add the entity info to the module key.
+ if (!isset($info[$module])) {
+ $info[$module] = array();
+ }
+ $info[$module][$entity_name] = $entity_info;
+ }
+ return $info;
+}
diff --git a/sites/all/modules/entity/entity.module b/sites/all/modules/entity/entity.module
new file mode 100644
index 000000000..36e5dc320
--- /dev/null
+++ b/sites/all/modules/entity/entity.module
@@ -0,0 +1,1574 @@
+<?php
+
+/**
+ * @file
+ * Module file for the entity API.
+ */
+
+module_load_include('inc', 'entity', 'modules/callbacks');
+module_load_include('inc', 'entity', 'includes/entity.property');
+
+
+/**
+ * Defines status codes used for exportable entities.
+ */
+
+/**
+ * A bit flag used to let us know if an entity is in the database.
+ */
+
+
+/**
+ * A bit flag used to let us know if an entity has been customly defined.
+ */
+define('ENTITY_CUSTOM', 0x01);
+
+/**
+ * Deprecated, but still here for backward compatibility.
+ */
+define('ENTITY_IN_DB', 0x01);
+
+/**
+ * A bit flag used to let us know if an entity is a 'default' in code.
+ */
+define('ENTITY_IN_CODE', 0x02);
+
+/**
+ * A bit flag used to mark entities as overridden, e.g. they were originally
+ * definded in code and are saved now in the database. Same as
+ * (ENTITY_CUSTOM | ENTITY_IN_CODE).
+ */
+define('ENTITY_OVERRIDDEN', 0x03);
+
+/**
+ * A bit flag used to mark entities as fixed, thus not changeable for any
+ * user.
+ */
+define('ENTITY_FIXED', 0x04 | 0x02);
+
+
+
+/**
+ * Determines whether for the given entity type a given operation is available.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $op
+ * One of 'create', 'view', 'save', 'delete', 'revision delete', 'access' or
+ * 'form'.
+ *
+ * @return boolean
+ * Whether the entity type supports the given operation.
+ */
+function entity_type_supports($entity_type, $op) {
+ $info = entity_get_info($entity_type);
+ $keys = array(
+ 'view' => 'view callback',
+ 'create' => 'creation callback',
+ 'delete' => 'deletion callback',
+ 'revision delete' => 'revision deletion callback',
+ 'save' => 'save callback',
+ 'access' => 'access callback',
+ 'form' => 'form callback'
+ );
+ if (isset($info[$keys[$op]])) {
+ return TRUE;
+ }
+ if ($op == 'revision delete') {
+ return in_array('EntityAPIControllerInterface', class_implements($info['controller class']));
+ }
+ if ($op == 'form') {
+ return (bool) entity_ui_controller($entity_type);
+ }
+ if ($op != 'access') {
+ return in_array('EntityAPIControllerInterface', class_implements($info['controller class']));
+ }
+ return FALSE;
+}
+
+/**
+ * Menu loader function: load an entity from its path.
+ *
+ * This can be used to load entities of all types in menu paths:
+ *
+ * @code
+ * $items['myentity/%entity_object'] = array(
+ * 'load arguments' => array('myentity'),
+ * 'title' => ...,
+ * 'page callback' => ...,
+ * 'page arguments' => array(...),
+ * 'access arguments' => array(...),
+ * );
+ * @endcode
+ *
+ * @param $entity_id
+ * The ID of the entity to load, passed by the menu URL.
+ * @param $entity_type
+ * The type of the entity to load.
+ * @return
+ * A fully loaded entity object, or FALSE in case of error.
+ */
+function entity_object_load($entity_id, $entity_type) {
+ $entities = entity_load($entity_type, array($entity_id));
+ return reset($entities);
+}
+
+/**
+ * Page callback to show links to add an entity of a specific bundle.
+ *
+ * Entity modules that provide a further description to their bundles may wish
+ * to implement their own version of this to show these.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ */
+function entity_ui_bundle_add_page($entity_type) {
+ // Set the title, as we're a MENU_LOCAL_ACTION and hence just get tab titles.
+ module_load_include('inc', 'entity', 'includes/entity.ui');
+ drupal_set_title(entity_ui_get_action_title('add', $entity_type));
+
+ // Get entity info for our bundles.
+ $info = entity_get_info($entity_type);
+ $items = array();
+ foreach ($info['bundles'] as $bundle_name => $bundle_info) {
+ // Create an empty entity with just the bundle set to check for access.
+ $dummy_entity = entity_create($entity_type, array(
+ $info['entity keys']['bundle'] => $bundle_name,
+ ));
+ // If modules use a uid, they can default to the current-user
+ // in their create() method on the storage controller.
+ if (entity_access('create', $entity_type, $dummy_entity, $account = NULL)) {
+ $add_path = $info['admin ui']['path'] . '/add/' . $bundle_name;
+ $items[] = l(t('Add @label', array('@label' => $bundle_info['label'])), $add_path);
+ }
+ }
+ return theme('item_list', array('items' => $items));
+}
+
+/**
+ * Page callback to add an entity of a specific bundle.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $bundle_name
+ * The bundle machine name.
+ */
+function entity_ui_get_bundle_add_form($entity_type, $bundle_name) {
+ $info = entity_get_info($entity_type);
+ $bundle_key = $info['entity keys']['bundle'];
+
+ // Make a stub entity of the right bundle to pass to the entity_ui_get_form().
+ $values = array(
+ $bundle_key => $bundle_name,
+ );
+ $entity = entity_create($entity_type, $values);
+
+ return entity_ui_get_form($entity_type, $entity, 'add');
+}
+
+/**
+ * Page callback for viewing an entity.
+ *
+ * @param Entity $entity
+ * The entity to be rendered.
+ *
+ * @return array
+ * A renderable array of the entity in full view mode.
+ */
+function entity_ui_entity_page_view($entity) {
+ module_load_include('inc', 'entity', 'includes/entity.ui');
+ return $entity->view('full', NULL, TRUE);
+}
+
+/**
+ * Gets the page title for the passed operation.
+ */
+function entity_ui_get_page_title($op, $entity_type, $entity = NULL) {
+ if (isset($entity)) {
+ module_load_include('inc', 'entity', 'includes/entity.ui');
+ $label = entity_label($entity_type, $entity);
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+ }
+ else {
+ $info = entity_get_info($entity_type);
+ $label = $info['label'];
+ $bundle = NULL;
+ }
+
+ switch ($op) {
+ case 'view':
+ return $label;
+ case 'edit':
+ return t('Edit @label', array('@label' => $label));
+ case 'clone':
+ return t('Clone @label', array('@label' => $label));
+ case 'revert':
+ return t('Revert @label', array('@label' => $label));
+ case 'delete':
+ return t('Delete @label', array('@label' => $label));
+ case 'export':
+ return t('Export @label', array('@label' => $label));
+ }
+
+ return entity_ui_get_action_title($op, $entity_type, $bundle);
+}
+
+/**
+ * A wrapper around entity_load() to load a single entity by name or numeric id.
+ *
+ * @todo: Re-name entity_load() to entity_load_multiple() in d8 core and this
+ * to entity_load().
+ *
+ * @param $entity_type
+ * The entity type to load, e.g. node or user.
+ * @param $id
+ * The entity id, either the numeric id or the entity name. In case the entity
+ * type has specified a name key, both the numeric id and the name may be
+ * passed.
+ *
+ * @return
+ * The entity object, or FALSE.
+ *
+ * @see entity_load()
+ */
+function entity_load_single($entity_type, $id) {
+ $entities = entity_load($entity_type, array($id));
+ return reset($entities);
+}
+
+/**
+ * A wrapper around entity_load() to return entities keyed by name key if existing.
+ *
+ * @param $entity_type
+ * The entity type to load, e.g. node or user.
+ * @param $names
+ * An array of entity names or ids, or FALSE to load all entities.
+ * @param $conditions
+ * (deprecated) An associative array of conditions on the base table, where
+ * the keys are the database fields and the values are the values those
+ * fields must have. Instead, it is preferable to use EntityFieldQuery to
+ * retrieve a list of entity IDs loadable by this function.
+ *
+ * @return
+ * An array of entity objects indexed by their names (or ids if the entity
+ * type has no name key).
+ *
+ * @see entity_load()
+ */
+function entity_load_multiple_by_name($entity_type, $names = FALSE, $conditions = array()) {
+ $entities = entity_load($entity_type, $names, $conditions);
+ $info = entity_get_info($entity_type);
+ if (!isset($info['entity keys']['name'])) {
+ return $entities;
+ }
+ return entity_key_array_by_property($entities, $info['entity keys']['name']);
+}
+
+/**
+ * Permanently save an entity.
+ *
+ * In case of failures, an exception is thrown.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entity
+ * The entity to save.
+ *
+ * @return
+ * For entity types provided by the CRUD API, SAVED_NEW or SAVED_UPDATED is
+ * returned depending on the operation performed. If there is no information
+ * how to save the entity, FALSE is returned.
+ *
+ * @see entity_type_supports()
+ */
+function entity_save($entity_type, $entity) {
+ $info = entity_get_info($entity_type);
+ if (method_exists($entity, 'save')) {
+ return $entity->save();
+ }
+ elseif (isset($info['save callback'])) {
+ $info['save callback']($entity);
+ }
+ elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ return entity_get_controller($entity_type)->save($entity);
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Permanently delete the given entity.
+ *
+ * In case of failures, an exception is thrown.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $id
+ * The uniform identifier of the entity to delete.
+ *
+ * @return
+ * FALSE, if there were no information how to delete the entity.
+ *
+ * @see entity_type_supports()
+ */
+function entity_delete($entity_type, $id) {
+ return entity_delete_multiple($entity_type, array($id));
+}
+
+/**
+ * Permanently delete multiple entities.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $ids
+ * An array of entity ids of the entities to delete. In case the entity makes
+ * use of a name key, both the names or numeric ids may be passed.
+ * @return
+ * FALSE if the given entity type isn't compatible to the CRUD API.
+ */
+function entity_delete_multiple($entity_type, $ids) {
+ $info = entity_get_info($entity_type);
+ if (isset($info['deletion callback'])) {
+ foreach ($ids as $id) {
+ $info['deletion callback']($id);
+ }
+ }
+ elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ entity_get_controller($entity_type)->delete($ids);
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Loads an entity revision.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $revision_id
+ * The id of the revision to load.
+ *
+ * @return
+ * The entity object, or FALSE if there is no entity with the given revision
+ * id.
+ */
+function entity_revision_load($entity_type, $revision_id) {
+ $info = entity_get_info($entity_type);
+ if (!empty($info['entity keys']['revision'])) {
+ $entity_revisions = entity_load($entity_type, FALSE, array($info['entity keys']['revision'] => $revision_id));
+ return reset($entity_revisions);
+ }
+ return FALSE;
+}
+
+/**
+ * Deletes an entity revision.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $revision_id
+ * The revision ID to delete.
+ *
+ * @return
+ * TRUE if the entity revision could be deleted, FALSE otherwise.
+ */
+function entity_revision_delete($entity_type, $revision_id) {
+ $info = entity_get_info($entity_type);
+ if (isset($info['revision deletion callback'])) {
+ return $info['revision deletion callback']($revision_id, $entity_type);
+ }
+ elseif (in_array('EntityAPIControllerRevisionableInterface', class_implements($info['controller class']))) {
+ return entity_get_controller($entity_type)->deleteRevision($revision_id);
+ }
+ return FALSE;
+}
+
+/**
+ * Checks whether the given entity is the default revision.
+ *
+ * Note that newly created entities will always be created in default revision,
+ * thus TRUE is returned for not yet saved entities.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entity
+ * The entity object to check.
+ *
+ * @return boolean
+ * A boolean indicating whether the entity is in default revision is returned.
+ * If the entity is not revisionable or is new, TRUE is returned.
+ *
+ * @see entity_revision_set_default()
+ */
+function entity_revision_is_default($entity_type, $entity) {
+ $info = entity_get_info($entity_type);
+ if (empty($info['entity keys']['revision'])) {
+ return TRUE;
+ }
+ // Newly created entities will always be created in default revision.
+ if (!empty($entity->is_new) || empty($entity->{$info['entity keys']['id']})) {
+ return TRUE;
+ }
+ if (in_array('EntityAPIControllerRevisionableInterface', class_implements($info['controller class']))) {
+ $key = !empty($info['entity keys']['default revision']) ? $info['entity keys']['default revision'] : 'default_revision';
+ return !empty($entity->$key);
+ }
+ else {
+ // Else, just load the default entity and compare the ID. Usually, the
+ // entity should be already statically cached anyway.
+ $default = entity_load_single($entity_type, $entity->{$info['entity keys']['id']});
+ return $default->{$info['entity keys']['revision']} == $entity->{$info['entity keys']['revision']};
+ }
+}
+
+/**
+ * Sets a given entity revision as default revision.
+ *
+ * Note that the default revision flag will only be supported by entity types
+ * based upon the EntityAPIController, i.e. implementing the
+ * EntityAPIControllerRevisionableInterface.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entity
+ * The entity revision to update.
+ *
+ * @see entity_revision_is_default()
+ */
+function entity_revision_set_default($entity_type, $entity) {
+ $info = entity_get_info($entity_type);
+ if (!empty($info['entity keys']['revision'])) {
+ $key = !empty($info['entity keys']['default revision']) ? $info['entity keys']['default revision'] : 'default_revision';
+ $entity->$key = TRUE;
+ }
+}
+
+/**
+ * Create a new entity object.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $values
+ * An array of values to set, keyed by property name. If the entity type has
+ * bundles the bundle key has to be specified.
+ * @return
+ * A new instance of the entity type or FALSE if there is no information for
+ * the given entity type.
+ *
+ * @see entity_type_supports()
+ */
+function entity_create($entity_type, array $values) {
+ $info = entity_get_info($entity_type);
+ if (isset($info['creation callback'])) {
+ return $info['creation callback']($values, $entity_type);
+ }
+ elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ return entity_get_controller($entity_type)->create($values);
+ }
+ return FALSE;
+}
+
+/**
+ * Exports an entity.
+ *
+ * Note: Currently, this only works for entity types provided with the entity
+ * CRUD API.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entity
+ * The entity to export.
+ * @param $prefix
+ * An optional prefix for each line.
+ * @return
+ * The exported entity as serialized string. The format is determined by the
+ * respective entity controller, e.g. it is JSON for the EntityAPIController.
+ * The output is suitable for entity_import().
+ */
+function entity_export($entity_type, $entity, $prefix = '') {
+ if (method_exists($entity, 'export')) {
+ return $entity->export($prefix);
+ }
+ $info = entity_get_info($entity_type);
+ if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ return entity_get_controller($entity_type)->export($entity, $prefix);
+ }
+}
+
+/**
+ * Imports an entity.
+ *
+ * Note: Currently, this only works for entity types provided with the entity
+ * CRUD API.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param string $export
+ * The string containing the serialized entity as produced by
+ * entity_export().
+ * @return
+ * The imported entity object not yet saved.
+ */
+function entity_import($entity_type, $export) {
+ $info = entity_get_info($entity_type);
+ if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ return entity_get_controller($entity_type)->import($export);
+ }
+}
+
+/**
+ * Checks whether an entity type is fieldable.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ *
+ * @return
+ * TRUE if the entity type is fieldable, FALSE otherwise.
+ */
+function entity_type_is_fieldable($entity_type) {
+ $info = entity_get_info($entity_type);
+ return !empty($info['fieldable']);
+}
+
+/**
+ * Builds a structured array representing the entity's content.
+ *
+ * The content built for the entity will vary depending on the $view_mode
+ * parameter.
+ *
+ * Note: Currently, this only works for entity types provided with the entity
+ * CRUD API.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entity
+ * An entity object.
+ * @param $view_mode
+ * A view mode as used by this entity type, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ * @return
+ * The renderable array.
+ */
+function entity_build_content($entity_type, $entity, $view_mode = 'full', $langcode = NULL) {
+ $info = entity_get_info($entity_type);
+ if (method_exists($entity, 'buildContent')) {
+ return $entity->buildContent($view_mode, $langcode);
+ }
+ elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ return entity_get_controller($entity_type)->buildContent($entity, $view_mode, $langcode);
+ }
+}
+
+/**
+ * Returns the entity identifier, i.e. the entities name or numeric id.
+ *
+ * Unlike entity_extract_ids() this function returns the name of the entity
+ * instead of the numeric id, in case the entity type has specified a name key.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entity
+ * An entity object.
+ *
+ * @see entity_extract_ids()
+ */
+function entity_id($entity_type, $entity) {
+ if (method_exists($entity, 'identifier')) {
+ return $entity->identifier();
+ }
+ $info = entity_get_info($entity_type);
+ $key = isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'];
+ return isset($entity->$key) ? $entity->$key : NULL;
+}
+
+/**
+ * Generate an array for rendering the given entities.
+ *
+ * Entities being viewed, are generally expected to be fully-loaded entity
+ * objects, thus have their name or id key set. However, it is possible to
+ * view a single entity without any id, e.g. for generating a preview during
+ * creation.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entities
+ * An array of entities to render.
+ * @param $view_mode
+ * A view mode as used by this entity type, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ * @param $page
+ * (optional) If set will control if the entity is rendered: if TRUE
+ * the entity will be rendered without its title, so that it can be embeded
+ * in another context. If FALSE the entity will be displayed with its title
+ * in a mode suitable for lists.
+ * If unset, the page mode will be enabled if the current path is the URI
+ * of the entity, as returned by entity_uri().
+ * This parameter is only supported for entities which controller is a
+ * EntityAPIControllerInterface.
+ * @return
+ * The renderable array, keyed by the entity type and by entity identifiers,
+ * for which the entity name is used if existing - see entity_id(). If there
+ * is no information on how to view an entity, FALSE is returned.
+ */
+function entity_view($entity_type, $entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
+ $info = entity_get_info($entity_type);
+ if (isset($info['view callback'])) {
+ $entities = entity_key_array_by_property($entities, $info['entity keys']['id']);
+ return $info['view callback']($entities, $view_mode, $langcode, $entity_type);
+ }
+ elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ return entity_get_controller($entity_type)->view($entities, $view_mode, $langcode, $page);
+ }
+ return FALSE;
+}
+
+/**
+ * Determines whether the given user can perform actions on an entity.
+ *
+ * For create operations, the pattern is to create an entity and then
+ * check if the user has create access.
+ *
+ * @code
+ * $node = entity_create('node', array('type' => 'page'));
+ * $access = entity_access('create', 'node', $node, $account);
+ * @endcode
+ *
+ * @param $op
+ * The operation being performed. One of 'view', 'update', 'create' or
+ * 'delete'.
+ * @param $entity_type
+ * The entity type of the entity to check for.
+ * @param $entity
+ * Optionally an entity to check access for. If no entity is given, it will be
+ * determined whether access is allowed for all entities of the given type.
+ * @param $account
+ * The user to check for. Leave it to NULL to check for the global user.
+ *
+ * @return boolean
+ * Whether access is allowed or not. If the entity type does not specify any
+ * access information, NULL is returned.
+ *
+ * @see entity_type_supports()
+ */
+function entity_access($op, $entity_type, $entity = NULL, $account = NULL) {
+ if (($info = entity_get_info()) && isset($info[$entity_type]['access callback'])) {
+ return $info[$entity_type]['access callback']($op, $entity, $account, $entity_type);
+ }
+}
+
+/**
+ * Gets the edit form for any entity.
+ *
+ * This helper makes use of drupal_get_form() and the regular form builder
+ * function of the entity type to retrieve and process the form as usual.
+ *
+ * In order to use this helper to show an entity add form, the new entity object
+ * can be created via entity_create() or entity_property_values_create_entity().
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entity
+ * The entity to show the edit form for.
+ * @return
+ * The renderable array of the form. If there is no entity form or missing
+ * metadata, FALSE is returned.
+ *
+ * @see entity_type_supports()
+ */
+function entity_form($entity_type, $entity) {
+ $info = entity_get_info($entity_type);
+ if (isset($info['form callback'])) {
+ return $info['form callback']($entity, $entity_type);
+ }
+ // If there is an UI controller, the providing module has to implement the
+ // entity form using entity_ui_get_form().
+ elseif (entity_ui_controller($entity_type)) {
+ return entity_metadata_form_entity_ui($entity, $entity_type);
+ }
+ return FALSE;
+}
+
+/**
+ * Converts an array of entities to be keyed by the values of a given property.
+ *
+ * @param array $entities
+ * The array of entities to convert.
+ * @param $property
+ * The name of entity property, by which the array should be keyed. To get
+ * reasonable results, the property has to have unique values.
+ *
+ * @return array
+ * The same entities in the same order, but keyed by their $property values.
+ */
+function entity_key_array_by_property(array $entities, $property) {
+ $ret = array();
+ foreach ($entities as $entity) {
+ $key = isset($entity->$property) ? $entity->$property : NULL;
+ $ret[$key] = $entity;
+ }
+ return $ret;
+}
+
+/**
+ * Get the entity info for the entity types provided via the entity CRUD API.
+ *
+ * @return
+ * An array in the same format as entity_get_info(), containing the entities
+ * whose controller class implements the EntityAPIControllerInterface.
+ */
+function entity_crud_get_info() {
+ $types = array();
+ foreach (entity_get_info() as $type => $info) {
+ if (isset($info['controller class']) && in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ $types[$type] = $info;
+ }
+ }
+ return $types;
+}
+
+/**
+ * Checks if a given entity has a certain exportable status.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $entity
+ * The entity to check the status on.
+ * @param $status
+ * The constant status like ENTITY_CUSTOM, ENTITY_IN_CODE, ENTITY_OVERRIDDEN
+ * or ENTITY_FIXED.
+ *
+ * @return
+ * TRUE if the entity has the status, FALSE otherwise.
+ */
+function entity_has_status($entity_type, $entity, $status) {
+ $info = entity_get_info($entity_type);
+ $status_key = empty($info['entity keys']['status']) ? 'status' : $info['entity keys']['status'];
+ return isset($entity->{$status_key}) && ($entity->{$status_key} & $status) == $status;
+}
+
+/**
+ * Export a variable. Copied from ctools.
+ *
+ * This is a replacement for var_export(), allowing us to more nicely
+ * format exports. It will recurse down into arrays and will try to
+ * properly export bools when it can.
+ */
+function entity_var_export($var, $prefix = '') {
+ if (is_array($var)) {
+ if (empty($var)) {
+ $output = 'array()';
+ }
+ else {
+ $output = "array(\n";
+ foreach ($var as $key => $value) {
+ $output .= " '$key' => " . entity_var_export($value, ' ') . ",\n";
+ }
+ $output .= ')';
+ }
+ }
+ elseif (is_bool($var)) {
+ $output = $var ? 'TRUE' : 'FALSE';
+ }
+ else {
+ $output = var_export($var, TRUE);
+ }
+
+ if ($prefix) {
+ $output = str_replace("\n", "\n$prefix", $output);
+ }
+ return $output;
+}
+
+/**
+ * Export a variable in pretty formatted JSON.
+ */
+function entity_var_json_export($var, $prefix = '') {
+ if (is_array($var) && $var) {
+ // Defines whether we use a JSON array or object.
+ $use_array = ($var == array_values($var));
+ $output = $use_array ? "[" : "{";
+
+ foreach ($var as $key => $value) {
+ if ($use_array) {
+ $values[] = entity_var_json_export($value, ' ');
+ }
+ else {
+ $values[] = entity_var_json_export((string) $key, ' ') . ' : ' . entity_var_json_export($value, ' ');
+ }
+ }
+ // Use several lines for long content. However for objects with a single
+ // entry keep the key in the first line.
+ if (strlen($content = implode(', ', $values)) > 70 && ($use_array || count($values) > 1)) {
+ $output .= "\n " . implode(",\n ", $values) . "\n";
+ }
+ elseif (strpos($content, "\n") !== FALSE) {
+ $output .= " " . $content . "\n";
+ }
+ else {
+ $output .= " " . $content . ' ';
+ }
+ $output .= $use_array ? ']' : '}';
+ }
+ else {
+ $output = drupal_json_encode($var);
+ }
+
+ if ($prefix) {
+ $output = str_replace("\n", "\n$prefix", $output);
+ }
+ return $output;
+}
+
+/**
+ * Rebuild the default entities provided in code.
+ *
+ * Exportable entities provided in code get saved to the database once a module
+ * providing defaults in code is activated. This allows module and entity_load()
+ * to easily deal with exportable entities just by relying on the database.
+ *
+ * The defaults get rebuilt if the cache is cleared or new modules providing
+ * defaults are enabled, such that the defaults in the database are up to date.
+ * A default entity gets updated with the latest defaults in code during rebuild
+ * as long as the default has not been overridden. Once a module providing
+ * defaults is disabled, its default entities get removed from the database
+ * unless they have been overridden. In that case the overridden entity is left
+ * in the database, but its status gets updated to 'custom'.
+ *
+ * @param $entity_types
+ * (optional) If specified, only the defaults of the given entity types are
+ * rebuilt.
+ */
+function entity_defaults_rebuild($entity_types = NULL) {
+ if (!isset($entity_types)) {
+ $entity_types = array();
+ foreach (entity_crud_get_info() as $type => $info) {
+ if (!empty($info['exportable'])) {
+ $entity_types[] = $type;
+ }
+ };
+ }
+ foreach ($entity_types as $type) {
+ _entity_defaults_rebuild($type);
+ }
+}
+
+/**
+ * Actually rebuild the defaults of a given entity type.
+ */
+function _entity_defaults_rebuild($entity_type) {
+ if (lock_acquire('entity_rebuild_' . $entity_type)) {
+ $info = entity_get_info($entity_type);
+ $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type;
+ $keys = $info['entity keys'] + array('module' => 'module', 'status' => 'status', 'name' => $info['entity keys']['id']);
+
+ // Check for the existence of the module and status columns.
+ if (!in_array($keys['status'], $info['schema_fields_sql']['base table']) || !in_array($keys['module'], $info['schema_fields_sql']['base table'])) {
+ trigger_error("Missing database columns for the exportable entity $entity_type as defined by entity_exportable_schema_fields(). Update the according module and run update.php!", E_USER_WARNING);
+ return;
+ }
+
+ // Invoke the hook and collect default entities.
+ $entities = array();
+ foreach (module_implements($hook) as $module) {
+ foreach ((array) module_invoke($module, $hook) as $name => $entity) {
+ $entity->{$keys['name']} = $name;
+ $entity->{$keys['module']} = $module;
+ $entities[$name] = $entity;
+ }
+ }
+ drupal_alter($hook, $entities);
+
+ // Check for defaults that disappeared.
+ $existing_defaults = entity_load_multiple_by_name($entity_type, FALSE, array($keys['status'] => array(ENTITY_OVERRIDDEN, ENTITY_IN_CODE, ENTITY_FIXED)));
+
+ foreach ($existing_defaults as $name => $entity) {
+ if (empty($entities[$name])) {
+ $entity->is_rebuild = TRUE;
+ if (entity_has_status($entity_type, $entity, ENTITY_OVERRIDDEN)) {
+ $entity->{$keys['status']} = ENTITY_CUSTOM;
+ entity_save($entity_type, $entity);
+ }
+ else {
+ entity_delete($entity_type, $name);
+ }
+ unset($entity->is_rebuild);
+ }
+ }
+
+ // Load all existing entities.
+ $existing_entities = entity_load_multiple_by_name($entity_type, array_keys($entities));
+
+ foreach ($existing_entities as $name => $entity) {
+ if (entity_has_status($entity_type, $entity, ENTITY_CUSTOM)) {
+ // If the entity already exists but is not yet marked as overridden, we
+ // have to update the status.
+ if (!entity_has_status($entity_type, $entity, ENTITY_OVERRIDDEN)) {
+ $entity->{$keys['status']} |= ENTITY_OVERRIDDEN;
+ $entity->{$keys['module']} = $entities[$name]->{$keys['module']};
+ $entity->is_rebuild = TRUE;
+ entity_save($entity_type, $entity);
+ unset($entity->is_rebuild);
+ }
+
+ // The entity is overridden, so we do not need to save the default.
+ unset($entities[$name]);
+ }
+ }
+
+ // Save defaults.
+ $originals = array();
+ foreach ($entities as $name => $entity) {
+ if (!empty($existing_entities[$name])) {
+ // Make sure we are updating the existing default.
+ $entity->{$keys['id']} = $existing_entities[$name]->{$keys['id']};
+ unset($entity->is_new);
+ }
+ // Pre-populate $entity->original as we already have it. So we avoid
+ // loading it again.
+ $entity->original = !empty($existing_entities[$name]) ? $existing_entities[$name] : FALSE;
+ // Keep original entities for hook_{entity_type}_defaults_rebuild()
+ // implementations.
+ $originals[$name] = $entity->original;
+
+ if (!isset($entity->{$keys['status']})) {
+ $entity->{$keys['status']} = ENTITY_IN_CODE;
+ }
+ else {
+ $entity->{$keys['status']} |= ENTITY_IN_CODE;
+ }
+ $entity->is_rebuild = TRUE;
+ entity_save($entity_type, $entity);
+ unset($entity->is_rebuild);
+ }
+
+ // Invoke an entity type-specific hook so modules may apply changes, e.g.
+ // efficiently rebuild caches.
+ module_invoke_all($entity_type . '_defaults_rebuild', $entities, $originals);
+
+ lock_release('entity_rebuild_' . $entity_type);
+ }
+}
+
+/**
+ * Implements hook_modules_installed().
+ */
+function entity_modules_installed($modules) {
+ module_load_install('entity');
+ entity_entitycache_installed_modules($modules);
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ */
+function entity_modules_uninstalled($modules) {
+ module_load_install('entity');
+ entity_entitycache_uninstalled_modules($modules);
+}
+
+/**
+ * Implements hook_modules_enabled().
+ */
+function entity_modules_enabled($modules) {
+ foreach (_entity_modules_get_default_types($modules) as $type) {
+ _entity_defaults_rebuild($type);
+ }
+}
+
+/**
+ * Implements hook_modules_disabled().
+ */
+function entity_modules_disabled($modules) {
+ foreach (_entity_modules_get_default_types($modules) as $entity_type) {
+ $info = entity_get_info($entity_type);
+
+ // Do nothing if the module providing the entity type has been disabled too.
+ if (isset($info['module']) && in_array($info['module'], $modules)) {
+ return;
+ }
+
+ $keys = $info['entity keys'] + array('module' => 'module', 'status' => 'status', 'name' => $info['entity keys']['id']);
+ // Remove entities provided in code by one of the disabled modules.
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', $entity_type, '=')
+ ->propertyCondition($keys['module'], $modules, 'IN')
+ ->propertyCondition($keys['status'], array(ENTITY_IN_CODE, ENTITY_FIXED), 'IN');
+ $result = $query->execute();
+ if (isset($result[$entity_type])) {
+ $entities = entity_load($entity_type, array_keys($result[$entity_type]));
+ entity_delete_multiple($entity_type, array_keys($entities));
+ }
+
+ // Update overridden entities to be now custom.
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', $entity_type, '=')
+ ->propertyCondition($keys['module'], $modules, 'IN')
+ ->propertyCondition($keys['status'], ENTITY_OVERRIDDEN, '=');
+ $result = $query->execute();
+ if (isset($result[$entity_type])) {
+ foreach (entity_load($entity_type, array_keys($result[$entity_type])) as $name => $entity) {
+ $entity->{$keys['status']} = ENTITY_CUSTOM;
+ $entity->{$keys['module']} = NULL;
+ entity_save($entity_type, $entity);
+ }
+ }
+
+ // Rebuild the remaining defaults so any alterations of the disabled modules
+ // are gone.
+ _entity_defaults_rebuild($entity_type);
+ }
+}
+
+/**
+ * Gets all entity types for which defaults are provided by the $modules.
+ */
+function _entity_modules_get_default_types($modules) {
+ $types = array();
+ foreach (entity_crud_get_info() as $entity_type => $info) {
+ if (!empty($info['exportable'])) {
+ $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type;
+ foreach ($modules as $module) {
+ if (module_hook($module, $hook) || module_hook($module, $hook . '_alter')) {
+ $types[] = $entity_type;
+ }
+ }
+ }
+ }
+ return $types;
+}
+
+/**
+ * Defines schema fields required for exportable entities.
+ *
+ * Warning: Do not call this function in your module's hook_schema()
+ * implementation or update functions. It is not safe to call functions of
+ * dependencies at this point. Instead of calling the function, just copy over
+ * the content.
+ * For more details see the issue http://drupal.org/node/1122812.
+ */
+function entity_exportable_schema_fields($module_col = 'module', $status_col = 'status') {
+ return array(
+ $status_col => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ // Set the default to ENTITY_CUSTOM without using the constant as it is
+ // not safe to use it at this point.
+ 'default' => 0x01,
+ 'size' => 'tiny',
+ 'description' => 'The exportable status of the entity.',
+ ),
+ $module_col => array(
+ 'description' => 'The name of the providing module if the entity has been defined in code.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ),
+ );
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function entity_flush_caches() {
+ entity_property_info_cache_clear();
+ // Re-build defaults in code, however skip it on the admin modules page. In
+ // case of enabling or disabling modules we already rebuild defaults in
+ // entity_modules_enabled() and entity_modules_disabled(), so we do not need
+ // to do it again.
+ // Also check if rebuilding on cache flush is explicitly disabled.
+ if (current_path() != 'admin/modules/list/confirm' && variable_get('entity_rebuild_on_flush', TRUE)) {
+ entity_defaults_rebuild();
+ }
+
+ // Care about entitycache tables.
+ if (module_exists('entitycache')) {
+ $tables = array();
+ foreach (entity_crud_get_info() as $entity_type => $entity_info) {
+ if (isset($entity_info['module']) && !empty($entity_info['entity cache'])) {
+ $tables[] = 'cache_entity_' . $entity_type;
+ }
+ }
+ return $tables;
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function entity_theme() {
+ // Build a pattern in the form of "(type1|type2|...)(\.|__)" such that all
+ // templates starting with an entity type or named like the entity type
+ // are found.
+ // This has to match the template suggestions provided in
+ // template_preprocess_entity().
+ $types = array_keys(entity_crud_get_info());
+ $pattern = '(' . implode('|', $types) . ')(\.|__)';
+
+ return array(
+ 'entity_status' => array(
+ 'variables' => array('status' => NULL, 'html' => TRUE),
+ 'file' => 'theme/entity.theme.inc',
+ ),
+ 'entity' => array(
+ 'render element' => 'elements',
+ 'template' => 'entity',
+ 'pattern' => $pattern,
+ 'path' => drupal_get_path('module', 'entity') . '/theme',
+ 'file' => 'entity.theme.inc',
+ ),
+ 'entity_property' => array(
+ 'render element' => 'elements',
+ 'file' => 'theme/entity.theme.inc',
+ ),
+ 'entity_ui_overview_item' => array(
+ 'variables' => array('label' => NULL, 'entity_type' => NULL, 'url' => FALSE, 'name' => FALSE),
+ 'file' => 'includes/entity.ui.inc'
+ ),
+ );
+}
+
+/**
+ * Label callback that refers to the entity classes label method.
+ */
+function entity_class_label($entity) {
+ return $entity->label();
+}
+
+/**
+ * URI callback that refers to the entity classes uri method.
+ */
+function entity_class_uri($entity) {
+ return $entity->uri();
+}
+
+/**
+ * Implements hook_file_download_access() for entity types provided by the CRUD API.
+ */
+function entity_file_download_access($field, $entity_type, $entity) {
+ $info = entity_get_info($entity_type);
+ if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ return entity_access('view', $entity_type, $entity);
+ }
+}
+
+/**
+ * Determines the UI controller class for a given entity type.
+ *
+ * @return EntityDefaultUIController
+ * If a type is given, the controller for the given entity type. Else an array
+ * of all enabled UI controllers keyed by entity type is returned.
+ */
+function entity_ui_controller($type = NULL) {
+ $static = &drupal_static(__FUNCTION__);
+
+ if (!isset($type)) {
+ // Invoke the function for each type to ensure we have fully populated the
+ // static variable.
+ foreach (entity_get_info() as $entity_type => $info) {
+ entity_ui_controller($entity_type);
+ }
+ return array_filter($static);
+ }
+
+ if (!isset($static[$type])) {
+ $info = entity_get_info($type);
+ $class = isset($info['admin ui']['controller class']) ? $info['admin ui']['controller class'] : 'EntityDefaultUIController';
+ $static[$type] = (isset($info['admin ui']['path']) && $class) ? new $class($type, $info) : FALSE;
+ }
+
+ return $static[$type];
+}
+
+/**
+ * Implements hook_menu().
+ *
+ * @see EntityDefaultUIController::hook_menu()
+ */
+function entity_menu() {
+ $items = array();
+ foreach (entity_ui_controller() as $controller) {
+ $items += $controller->hook_menu();
+ }
+ return $items;
+}
+
+/**
+ * Implements hook_forms().
+ *
+ * @see EntityDefaultUIController::hook_forms()
+ * @see entity_ui_get_form()
+ */
+function entity_forms($form_id, $args) {
+ // For efficiency only invoke an entity types controller, if a form of it is
+ // requested. Thus if the first (overview and operation form) or the third
+ // argument (edit form) is an entity type name, add in the types forms.
+ if (isset($args[0]) && is_string($args[0]) && entity_get_info($args[0])) {
+ $type = $args[0];
+ }
+ elseif (isset($args[2]) && is_string($args[2]) && entity_get_info($args[2])) {
+ $type = $args[2];
+ }
+ if (isset($type) && $controller = entity_ui_controller($type)) {
+ return $controller->hook_forms();
+ }
+}
+
+/**
+ * A wrapper around drupal_get_form() that helps building entity forms.
+ *
+ * This function may be used by entities to build their entity form. It has to
+ * be used instead of calling drupal_get_form().
+ * Entity forms built with this helper receive useful defaults suiting for
+ * editing a single entity, whereas the special cases of adding and cloning
+ * of entities are supported too.
+ *
+ * While this function is intended to be used to get entity forms for entities
+ * using the entity ui controller, it may be used for entity types not using
+ * the ui controller too.
+ *
+ * @param $entity_type
+ * The entity type for which to get the form.
+ * @param $entity
+ * The entity for which to return the form.
+ * If $op is 'add' the entity has to be either initialized before calling this
+ * function, or NULL may be passed. If NULL is passed, an entity will be
+ * initialized with empty values using entity_create(). Thus entities, for
+ * which this is problematic have to care to pass in an initialized entity.
+ * @param $op
+ * (optional) One of 'edit', 'add' or 'clone'. Defaults to edit.
+ * @param $form_state
+ * (optional) A pre-populated form state, e.g. to add in form include files.
+ * See entity_metadata_form_entity_ui().
+ *
+ * @return
+ * The fully built and processed form, ready to be rendered.
+ *
+ * @see EntityDefaultUIController::hook_forms()
+ * @see entity_ui_form_submit_build_entity()
+ */
+function entity_ui_get_form($entity_type, $entity, $op = 'edit', $form_state = array()) {
+ if (isset($entity)) {
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+ }
+ $form_id = (!isset($bundle) || $bundle == $entity_type) ? $entity_type . '_form' : $entity_type . '_edit_' . $bundle . '_form';
+
+ if (!isset($entity) && $op == 'add') {
+ $entity = entity_create($entity_type, array());
+ }
+
+ // Do not use drupal_get_form(), but invoke drupal_build_form() ourself so
+ // we can prepulate the form state.
+ $form_state['wrapper_callback'] = 'entity_ui_main_form_defaults';
+ $form_state['entity_type'] = $entity_type;
+ form_load_include($form_state, 'inc', 'entity', 'includes/entity.ui');
+
+ // Handle cloning. We cannot do that in the wrapper callback as it is too late
+ // for changing arguments.
+ if ($op == 'clone') {
+ $entity = entity_ui_clone_entity($entity_type, $entity);
+ }
+
+ // We don't pass the entity type as first parameter, as the implementing
+ // module knows the type anyway. However, in order to allow for efficient
+ // hook_forms() implementiations we append the entity type as last argument,
+ // which the module implementing the form constructor may safely ignore.
+ // @see entity_forms()
+ $form_state['build_info']['args'] = array($entity, $op, $entity_type);
+ return drupal_build_form($form_id, $form_state);
+}
+
+
+/**
+ * Gets the page/menu title for local action operations.
+ *
+ * @param $op
+ * The current operation. One of 'add' or 'import'.
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle_name
+ * (Optional) The name of the bundle. May be NULL if the bundle name is not
+ * relevant to the current page. If the entity type has only one bundle, or no
+ * bundles, this will be the same as the entity type.
+ */
+function entity_ui_get_action_title($op, $entity_type, $bundle_name = NULL) {
+ $info = entity_get_info($entity_type);
+ switch ($op) {
+ case 'add':
+ if (isset($bundle_name) && $bundle_name != $entity_type) {
+ return t('Add @bundle_name @entity_type', array(
+ '@bundle_name' => drupal_strtolower($info['bundles'][$bundle_name]['label']),
+ '@entity_type' => drupal_strtolower($info['label']),
+ ));
+ }
+ else {
+ return t('Add @entity_type', array('@entity_type' => drupal_strtolower($info['label'])));
+ }
+ case 'import':
+ return t('Import @entity_type', array('@entity_type' => drupal_strtolower($info['label'])));
+ }
+}
+
+/**
+ * Helper for using i18n_string().
+ *
+ * @param $name
+ * Textgroup and context glued with ':'.
+ * @param $default
+ * String in default language. Default language may or may not be English.
+ * @param $langcode
+ * (optional) The code of a certain language to translate the string into.
+ * Defaults to the i18n_string() default, i.e. the current language.
+ *
+ * @see i18n_string()
+ */
+function entity_i18n_string($name, $default, $langcode = NULL) {
+ return function_exists('i18n_string') ? i18n_string($name, $default, array('langcode' => $langcode)) : $default;
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function entity_views_api() {
+ return array(
+ 'api' => '3.0-alpha1',
+ 'path' => drupal_get_path('module', 'entity') . '/views',
+ );
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function entity_field_extra_fields() {
+ // Invoke specified controllers for entity types provided by the CRUD API.
+ $items = array();
+ foreach (entity_crud_get_info() as $type => $info) {
+ if (!empty($info['extra fields controller class'])) {
+ $items = array_merge_recursive($items, entity_get_extra_fields_controller($type)->fieldExtraFields());
+ }
+ }
+ return $items;
+}
+
+/**
+ * Gets the extra field controller class for a given entity type.
+ *
+ * @return EntityExtraFieldsControllerInterface|false
+ * The controller for the given entity type or FALSE if none is specified.
+ */
+function entity_get_extra_fields_controller($type = NULL) {
+ $static = &drupal_static(__FUNCTION__);
+
+ if (!isset($static[$type])) {
+ $static[$type] = FALSE;
+ $info = entity_get_info($type);
+ if (!empty($info['extra fields controller class'])) {
+ $static[$type] = new $info['extra fields controller class']($type);
+ }
+ }
+ return $static[$type];
+}
+
+/**
+ * Returns a property wrapper for the given data.
+ *
+ * If an entity is wrapped, the wrapper can be used to retrieve further wrappers
+ * for the entitity properties. For that the wrapper support chaining, e.g. you
+ * can use a node wrapper to get the node authors mail address:
+ *
+ * @code
+ * echo $wrappedNode->author->mail->value();
+ * @endcode
+ *
+ * @param $type
+ * The type of the passed data.
+ * @param $data
+ * The data to wrap. It may be set to NULL, so the wrapper can be used
+ * without any data for getting information about properties.
+ * @param $info
+ * (optional) Specify additional information for the passed data:
+ * - langcode: (optional) If the data is language specific, its langauge
+ * code. Defaults to NULL, what means language neutral.
+ * - bundle: (optional) If an entity is wrapped but not passed, use this key
+ * to specify the bundle to return a wrapper for.
+ * - property info: (optional) May be used to use a wrapper with an arbitrary
+ * data structure (type 'struct'). Use this key for specifying info about
+ * properties in the same structure as used by hook_entity_property_info().
+ * - property info alter: (optional) A callback for altering the property
+ * info before it is utilized by the wrapper.
+ * - property defaults: (optional) An array of defaults for the info of
+ * each property of the wrapped data item.
+ * @return EntityMetadataWrapper
+ * Dependend on the passed data the right wrapper is returned.
+ */
+function entity_metadata_wrapper($type, $data = NULL, array $info = array()) {
+ if ($type == 'entity' || (($entity_info = entity_get_info()) && isset($entity_info[$type]))) {
+ // If the passed entity is the global $user, we load the user object by only
+ // passing on the user id. The global user is not a fully loaded entity.
+ if ($type == 'user' && is_object($data) && $data == $GLOBALS['user']) {
+ $data = $data->uid;
+ }
+ return new EntityDrupalWrapper($type, $data, $info);
+ }
+ elseif ($type == 'list' || entity_property_list_extract_type($type)) {
+ return new EntityListWrapper($type, $data, $info);
+ }
+ elseif (isset($info['property info'])) {
+ return new EntityStructureWrapper($type, $data, $info);
+ }
+ else {
+ return new EntityValueWrapper($type, $data, $info);
+ }
+}
+
+/**
+ * Returns a metadata wrapper for accessing site-wide properties.
+ *
+ * Although there is no 'site' entity or such, modules may provide info about
+ * site-wide properties using hook_entity_property_info(). This function returns
+ * a wrapper for making use of this properties.
+ *
+ * @return EntityMetadataWrapper
+ * A wrapper for accessing site-wide properties.
+ *
+ * @see entity_metadata_system_entity_property_info()
+ */
+function entity_metadata_site_wrapper() {
+ $site_info = entity_get_property_info('site');
+ $info['property info'] = $site_info['properties'];
+ return entity_metadata_wrapper('site', FALSE, $info);
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ *
+ * Moves the hook_entity_info_alter() implementation to the bottom so it is
+ * invoked after all modules relying on the entity API.
+ * That way we ensure to run last and clear the field-info cache after the
+ * others added in their bundle information.
+ *
+ * @see entity_entity_info_alter()
+ */
+function entity_module_implements_alter(&$implementations, $hook) {
+ if ($hook == 'entity_info_alter') {
+ // Move our hook implementation to the bottom.
+ $group = $implementations['entity'];
+ unset($implementations['entity']);
+ $implementations['entity'] = $group;
+ }
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * @see entity_module_implements_alter()
+ */
+function entity_entity_info_alter(&$entity_info) {
+ _entity_info_add_metadata($entity_info);
+
+ // Populate a default value for the 'configuration' key of all entity types.
+ foreach ($entity_info as $type => $info) {
+ if (!isset($info['configuration'])) {
+ $entity_info[$type]['configuration'] = !empty($info['exportable']);
+ }
+
+ if (isset($info['controller class']) && in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
+ // Automatically disable field cache when entity cache is used.
+ if (!empty($info['entity cache']) && module_exists('entitycache')) {
+ $entity_info[$type]['field cache'] = FALSE;
+ }
+ }
+ }
+}
+
+/**
+ * Adds metadata and callbacks for core entities to the entity info.
+ */
+function _entity_info_add_metadata(&$entity_info) {
+ // Set plural labels.
+ $entity_info['node']['plural label'] = t('Nodes');
+ $entity_info['user']['plural label'] = t('Users');
+ $entity_info['file']['plural label'] = t('Files');
+
+ // Set descriptions.
+ $entity_info['node']['description'] = t('Nodes represent the main site content items.');
+ $entity_info['user']['description'] = t('Users who have created accounts on your site.');
+ $entity_info['file']['description'] = t('Uploaded file.');
+
+ // Set access callbacks.
+ $entity_info['node']['access callback'] = 'entity_metadata_no_hook_node_access';
+ $entity_info['user']['access callback'] = 'entity_metadata_user_access';
+ // File entity has it's own entity_access function.
+ if (!module_exists('file_entity')) {
+ $entity_info['file']['access callback'] = 'entity_metadata_file_access';
+ }
+
+ // CRUD function callbacks.
+ $entity_info['node']['creation callback'] = 'entity_metadata_create_node';
+ $entity_info['node']['save callback'] = 'node_save';
+ $entity_info['node']['deletion callback'] = 'node_delete';
+ $entity_info['node']['revision deletion callback'] = 'node_revision_delete';
+ $entity_info['user']['creation callback'] = 'entity_metadata_create_object';
+ $entity_info['user']['save callback'] = 'entity_metadata_user_save';
+ $entity_info['user']['deletion callback'] = 'user_delete';
+ $entity_info['file']['save callback'] = 'file_save';
+ $entity_info['file']['deletion callback'] = 'entity_metadata_delete_file';
+
+ // Form callbacks.
+ $entity_info['node']['form callback'] = 'entity_metadata_form_node';
+ $entity_info['user']['form callback'] = 'entity_metadata_form_user';
+
+ // URI callbacks.
+ if (!isset($entity_info['file']['uri callback'])) {
+ $entity_info['file']['uri callback'] = 'entity_metadata_uri_file';
+ }
+
+ // View callbacks.
+ $entity_info['node']['view callback'] = 'entity_metadata_view_node';
+ $entity_info['user']['view callback'] = 'entity_metadata_view_single';
+
+ if (module_exists('comment')) {
+ $entity_info['comment']['plural label'] = t('Comments');
+ $entity_info['comment']['description'] = t('Remark or note that refers to a node.');
+ $entity_info['comment']['access callback'] = 'entity_metadata_comment_access';
+ $entity_info['comment']['creation callback'] = 'entity_metadata_create_comment';
+ $entity_info['comment']['save callback'] = 'comment_save';
+ $entity_info['comment']['deletion callback'] = 'comment_delete';
+ $entity_info['comment']['view callback'] = 'entity_metadata_view_comment';
+ $entity_info['comment']['form callback'] = 'entity_metadata_form_comment';
+ }
+ if (module_exists('taxonomy')) {
+ $entity_info['taxonomy_term']['plural label'] = t('Taxonomy terms');
+ $entity_info['taxonomy_term']['description'] = t('Taxonomy terms are used for classifying content.');
+ $entity_info['taxonomy_term']['access callback'] = 'entity_metadata_taxonomy_access';
+ $entity_info['taxonomy_term']['creation callback'] = 'entity_metadata_create_object';
+ $entity_info['taxonomy_term']['save callback'] = 'taxonomy_term_save';
+ $entity_info['taxonomy_term']['deletion callback'] = 'taxonomy_term_delete';
+ $entity_info['taxonomy_term']['view callback'] = 'entity_metadata_view_single';
+ $entity_info['taxonomy_term']['form callback'] = 'entity_metadata_form_taxonomy_term';
+
+ $entity_info['taxonomy_vocabulary']['plural label'] = t('Taxonomy vocabularies');
+ $entity_info['taxonomy_vocabulary']['description'] = t('Vocabularies contain related taxonomy terms, which are used for classifying content.');
+ $entity_info['taxonomy_vocabulary']['access callback'] = 'entity_metadata_taxonomy_access';
+ $entity_info['taxonomy_vocabulary']['creation callback'] = 'entity_metadata_create_object';
+ $entity_info['taxonomy_vocabulary']['save callback'] = 'taxonomy_vocabulary_save';
+ $entity_info['taxonomy_vocabulary']['deletion callback'] = 'taxonomy_vocabulary_delete';
+ $entity_info['taxonomy_vocabulary']['form callback'] = 'entity_metadata_form_taxonomy_vocabulary';
+ // Token type mapping.
+ $entity_info['taxonomy_term']['token type'] = 'term';
+ $entity_info['taxonomy_vocabulary']['token type'] = 'vocabulary';
+ }
+}
+
+/**
+ * Implements hook_ctools_plugin_directory().
+ */
+function entity_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'ctools' && $plugin == 'content_types') {
+ return 'ctools/content_types';
+ }
+}
diff --git a/sites/all/modules/entity/entity.rules.inc b/sites/all/modules/entity/entity.rules.inc
new file mode 100644
index 000000000..c3ec8dc87
--- /dev/null
+++ b/sites/all/modules/entity/entity.rules.inc
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Provides Rules integration for entities provided via the CRUD API.
+ *
+ * Rules automatically provides us with actions for CRUD and a suiting entity
+ * data type. For events the controller automatically invokes Rules events once
+ * Rules is active, so we just have to provide the appropriate info.
+ */
+
+/**
+ * Default controller for generating Rules integration.
+ */
+class EntityDefaultRulesController {
+
+ protected $type, $info;
+
+ public function __construct($type) {
+ $this->type = $type;
+ $this->info = entity_get_info($type);
+ }
+
+ public function eventInfo() {
+ $info = $this->info;
+ $type = $this->type;
+
+ $label = $info['label'];
+ $defaults = array(
+ 'module' => isset($info['module']) ? $info['module'] : 'entity',
+ 'group' => $label,
+ 'access callback' => 'entity_rules_integration_event_access',
+ );
+
+ $items[$type . '_insert'] = $defaults + array(
+ 'label' => t('After saving a new @entity', array('@entity' => drupal_strtolower($label))),
+ 'variables' => entity_rules_events_variables($type, t('created @entity', array('@entity' => drupal_strtolower($label)))),
+ );
+ $items[$type . '_update'] = $defaults + array(
+ 'label' => t('After updating an existing @entity', array('@entity' => drupal_strtolower($label))),
+ 'variables' => entity_rules_events_variables($type, t('updated @entity', array('@entity' => drupal_strtolower($label))), TRUE),
+ );
+ $items[$type . '_presave'] = $defaults + array(
+ 'label' => t('Before saving a @entity', array('@entity' => drupal_strtolower($label))),
+ 'variables' => entity_rules_events_variables($type, t('saved @entity', array('@entity' => drupal_strtolower($label))), TRUE),
+ );
+ $items[$type . '_delete'] = $defaults + array(
+ 'label' => t('After deleting a @entity', array('@entity' => drupal_strtolower($label))),
+ 'variables' => entity_rules_events_variables($type, t('deleted @entity', array('@entity' => drupal_strtolower($label)))),
+ );
+ if (count($info['view modes'])) {
+ $items[$type . '_view'] = $defaults + array(
+ 'label' => t('@entity is viewed', array('@entity' => $label)),
+ 'variables' => entity_rules_events_variables($type, t('viewed @entity', array('@entity' => drupal_strtolower($label)))) + array(
+ 'view_mode' => array(
+ 'type' => 'text',
+ 'label' => t('view mode'),
+ 'options list' => 'rules_get_entity_view_modes',
+ // Add the entity-type for the options list callback.
+ 'options list entity type' => $type,
+ ),
+ ),
+ );
+ }
+ // Specify that on presave the entity is saved anyway.
+ $items[$type . '_presave']['variables'][$type]['skip save'] = TRUE;
+ return $items;
+ }
+
+}
+
+/**
+ * Returns some parameter info suiting for the specified entity type.
+ */
+function entity_rules_events_variables($type, $label, $update = FALSE) {
+ $args = array(
+ $type => array('type' => $type, 'label' => $label),
+ );
+ if ($update) {
+ $args += array(
+ $type . '_unchanged' => array(
+ 'type' => $type,
+ 'label' => t('unchanged entity'),
+ 'handler' => 'rules_events_entity_unchanged',
+ ),
+ );
+ }
+ return $args;
+}
+
+/**
+ * Implements hook_rules_event_info().
+ */
+function entity_rules_event_info() {
+ $items = array();
+ foreach (entity_crud_get_info() as $type => $info) {
+ // By default we enable the controller only for non-configuration.
+ $configuration = !empty($info['configuration']) || !empty($info['exportable']);
+ $info += array('rules controller class' => $configuration ? FALSE : 'EntityDefaultRulesController');
+ if ($info['rules controller class']) {
+ $controller = new $info['rules controller class']($type);
+ $items += $controller->eventInfo();
+ }
+ }
+ return $items;
+}
+
+/**
+ * Rules integration access callback.
+ */
+function entity_rules_integration_event_access($type, $event_name) {
+ // Cut of _insert/_update/.. from the event name.
+ $entity_type = substr($event_name, 0, strrpos($event_name, '_'));
+
+ $result = entity_access('view', $entity_type);
+ // If no access callback is given, just grant access for viewing.
+ return isset($result) ? $result : TRUE;
+}
diff --git a/sites/all/modules/entity/entity.test b/sites/all/modules/entity/entity.test
new file mode 100644
index 000000000..03c2551a5
--- /dev/null
+++ b/sites/all/modules/entity/entity.test
@@ -0,0 +1,2050 @@
+<?php
+
+/**
+ * @file
+ * Entity CRUD API tests.
+ */
+
+/**
+ * Common parent class containing common helpers.
+ */
+abstract class EntityWebTestCase extends DrupalWebTestCase {
+
+ /**
+ * Creates a new vocabulary.
+ */
+ protected function createVocabulary() {
+ $vocab = entity_create('taxonomy_vocabulary', array(
+ 'name' => $this->randomName(),
+ 'machine_name' => drupal_strtolower($this->randomName()),
+ 'description' => $this->randomName(),
+ ));
+ entity_save('taxonomy_vocabulary', $vocab);
+ return $vocab;
+ }
+
+ /**
+ * Creates a random file of the given type.
+ */
+ protected function createFile($file_type = 'text') {
+ // Create a managed file.
+ $file = current($this->drupalGetTestFiles($file_type));
+
+ // Set additional file properties and save it.
+ $file->filemime = file_get_mimetype($file->filename);
+ $file->uid = 1;
+ $file->timestamp = REQUEST_TIME;
+ $file->filesize = filesize($file->uri);
+ $file->status = 0;
+ file_save($file);
+ return $file;
+ }
+}
+
+/**
+ * Test basic API.
+ */
+class EntityAPITestCase extends EntityWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity CRUD',
+ 'description' => 'Tests basic CRUD API functionality.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('entity', 'entity_test');
+ }
+
+ /**
+ * Tests CRUD.
+ */
+ function testCRUD() {
+ module_enable(array('entity_feature'));
+
+ $user1 = $this->drupalCreateUser();
+ // Create test entities for the user1 and unrelated to a user.
+ $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid));
+ $entity->save();
+ $entity = entity_create('entity_test', array('name' => 'test2', 'uid' => $user1->uid));
+ $entity->save();
+ $entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL));
+ $entity->save();
+
+ $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test')));
+
+ $this->assertEqual($entities[0]->name, 'test', 'Created and loaded entity.');
+ $this->assertEqual($entities[1]->name, 'test', 'Created and loaded entity.');
+
+ $results = entity_test_load_multiple(array($entity->pid));
+ $loaded = array_pop($results);
+ $this->assertEqual($loaded->pid, $entity->pid, 'Loaded the entity unrelated to a user.');
+
+ $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2')));
+ $entities[0]->delete();
+ $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2')));
+ $this->assertEqual($entities, array(), 'Entity successfully deleted.');
+
+ $entity->save();
+ $this->assertEqual($entity->pid, $loaded->pid, 'Entity successfully updated.');
+
+ // Try deleting multiple test entities by deleting all.
+ $pids = array_keys(entity_test_load_multiple(FALSE));
+ entity_test_delete_multiple($pids);
+ }
+
+ /**
+ * Tests CRUD for entities supporting revisions.
+ */
+ function testCRUDRevisisions() {
+ module_enable(array('entity_feature'));
+
+ // Add text field to entity.
+ $field_info = array(
+ 'field_name' => 'field_text',
+ 'type' => 'text',
+ 'entity_types' => array('entity_test2'),
+ );
+ field_create_field($field_info);
+
+ $instance = array(
+ 'label' => 'Text Field',
+ 'field_name' => 'field_text',
+ 'entity_type' => 'entity_test2',
+ 'bundle' => 'entity_test2',
+ 'settings' => array(),
+ 'required' => FALSE,
+ );
+ field_create_instance($instance);
+
+ // Create a test entity.
+ $entity_first_revision = entity_create('entity_test2', array('title' => 'first revision', 'name' => 'main', 'uid' => 1));
+ $entity_first_revision->field_text[LANGUAGE_NONE][0]['value'] = 'first revision text';
+ entity_save('entity_test2', $entity_first_revision);
+
+ $entities = array_values(entity_load('entity_test2', FALSE));
+ $this->assertEqual(count($entities), 1, 'Entity created.');
+ $this->assertTrue($entities[0]->default_revision, 'Initial entity revision is marked as default revision.');
+
+ // Saving the entity in revision mode should create a new revision.
+ $entity_second_revision = clone $entity_first_revision;
+ $entity_second_revision->title = 'second revision';
+ $entity_second_revision->is_new_revision = TRUE;
+ $entity_second_revision->default_revision = TRUE;
+ $entity_second_revision->field_text[LANGUAGE_NONE][0]['value'] = 'second revision text';
+
+ entity_save('entity_test2', $entity_second_revision);
+ $this->assertNotEqual($entity_second_revision->revision_id, $entity_first_revision->revision_id, 'Saving an entity in new revision mode creates a revision.');
+ $this->assertTrue($entity_second_revision->default_revision, 'New entity revision is marked as default revision.');
+
+ // Check the saved entity.
+ $entity = current(entity_load('entity_test2', array($entity_first_revision->pid), array(), TRUE));
+ $this->assertNotEqual($entity->title, $entity_first_revision->title, 'Default revision was changed.');
+
+ // Create third revision that is not default.
+ $entity_third_revision = clone $entity_first_revision;
+ $entity_third_revision->title = 'third revision';
+ $entity_third_revision->is_new_revision = TRUE;
+ $entity_third_revision->default_revision = FALSE;
+ $entity_third_revision->field_text[LANGUAGE_NONE][0]['value'] = 'third revision text';
+ entity_save('entity_test2', $entity_third_revision);
+ $this->assertNotEqual($entity_second_revision->revision_id, $entity_third_revision->revision_id, 'Saving an entity in revision mode creates a revision.');
+ $this->assertFalse($entity_third_revision->default_revision, 'Entity revision is not marked as default revision.');
+
+ $entity = current(entity_load('entity_test2', array($entity_first_revision->pid), array(), TRUE));
+ $this->assertEqual($entity->title, $entity_second_revision->title, 'Default revision was not changed.');
+ $this->assertEqual($entity->field_text[LANGUAGE_NONE][0]['value'], $entity_second_revision->field_text[LANGUAGE_NONE][0]['value'], 'Default revision text field was not changed.');
+
+ // Load not default revision.
+ $revision = entity_revision_load('entity_test2', $entity_third_revision->revision_id);
+ $this->assertEqual($revision->revision_id, $entity_third_revision->revision_id, 'Revision successfully loaded.');
+ $this->assertFalse($revision->default_revision, 'Entity revision is not marked as default revision after loading.');
+
+ // Save not default revision.
+ $entity_third_revision->title = 'third revision updated';
+ $entity_third_revision->field_text[LANGUAGE_NONE][0]['value'] = 'third revision text updated';
+ entity_save('entity_test2', $entity_third_revision);
+
+ // Ensure that not default revision has been changed.
+ $entity = entity_revision_load('entity_test2', $entity_third_revision->revision_id);
+ $this->assertEqual($entity->title, 'third revision updated', 'Not default revision was updated successfully.');
+ $this->assertEqual($entity->field_text[LANGUAGE_NONE][0]['value'], 'third revision text updated', 'Not default revision field was updated successfully.');
+
+ // Ensure that default revision has not been changed.
+ $entity = current(entity_load('entity_test2', array($entity_first_revision->pid), array(), TRUE));
+ $this->assertEqual($entity->title, $entity_second_revision->title, 'Default revision was not changed.');
+
+ // Try to delete default revision.
+ $result = entity_revision_delete('entity_test2', $entity_second_revision->revision_id);
+ $this->assertFalse($result, 'Default revision cannot be deleted.');
+
+ // Make sure default revision is still set after trying to delete it.
+ $entity = current(entity_load('entity_test2', array($entity_first_revision->pid), array(), TRUE));
+ $this->assertEqual($entity->revision_id, $entity_second_revision->revision_id, 'Second revision is still default.');
+
+ // Delete first revision.
+ $result = entity_revision_delete('entity_test2', $entity_first_revision->revision_id);
+ $this->assertTrue($result, 'Not default revision deleted.');
+
+ $entity = entity_revision_load('entity_test2', $entity_first_revision->revision_id);
+ $this->assertFalse($entity, 'First revision deleted.');
+
+ // Delete the entity and make sure third revision has been deleted as well.
+ entity_delete('entity_test2', $entity_second_revision->pid);
+ $entity_info = entity_get_info('entity_test2');
+ $result = db_select($entity_info['revision table'])
+ ->condition('revision_id', $entity_third_revision->revision_id)
+ ->countQuery()
+ ->execute()
+ ->fetchField();
+ $this->assertEqual($result, 0, 'Entity deleted with its all revisions.');
+ }
+
+ /**
+ * Tests CRUD API functions: entity_(create|delete|save)
+ */
+ function testCRUDAPIfunctions() {
+ module_enable(array('entity_feature'));
+
+ $user1 = $this->drupalCreateUser();
+ // Create test entities for the user1 and unrelated to a user.
+ $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid));
+ entity_save('entity_test', $entity);
+ $entity = entity_create('entity_test', array('name' => 'test2', 'uid' => $user1->uid));
+ entity_save('entity_test', $entity);
+ $entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL));
+ entity_save('entity_test', $entity);
+
+ $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test')));
+ $this->assertEqual($entities[0]->name, 'test', 'Created and loaded entity.');
+ $this->assertEqual($entities[1]->name, 'test', 'Created and loaded entity.');
+
+ // Test getting the entity label, which is the used test-type's label.
+ $label = entity_label('entity_test', $entities[0]);
+ $this->assertEqual($label, 'label', 'Default label returned.');
+
+ $results = entity_test_load_multiple(array($entity->pid));
+ $loaded = array_pop($results);
+ $this->assertEqual($loaded->pid, $entity->pid, 'Loaded the entity unrelated to a user.');
+
+ $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2')));
+
+ entity_delete('entity_test', $entities[0]->pid);
+ $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2')));
+ $this->assertEqual($entities, array(), 'Entity successfully deleted.');
+
+ entity_save('entity_test', $entity);
+ $this->assertEqual($entity->pid, $loaded->pid, 'Entity successfully updated.');
+
+ // Try deleting multiple test entities by deleting all.
+ $pids = array_keys(entity_test_load_multiple(FALSE));
+ entity_delete_multiple('entity_test', $pids);
+ }
+
+ /**
+ * Test loading entities defined in code.
+ */
+ function testExportables() {
+ module_enable(array('entity_feature'));
+
+ $types = entity_load_multiple_by_name('entity_test_type', array('test2', 'test'));
+
+ $this->assertEqual(array_keys($types), array('test2', 'test'), 'Entities have been loaded in the order as specified.');
+ $this->assertEqual($types['test']->label, 'label', 'Default type loaded.');
+ $this->assertTrue($types['test']->status & ENTITY_IN_CODE && !($types['test']->status & ENTITY_CUSTOM), 'Default type status is correct.');
+
+ // Test using a condition, which has to be applied on the defaults.
+ $types = entity_load_multiple_by_name('entity_test_type', FALSE, array('label' => 'label'));
+ $this->assertEqual($types['test']->label, 'label', 'Condition to default type applied.');
+
+ $types['test']->label = 'modified';
+ $types['test']->save();
+
+ // Ensure loading the changed entity works.
+ $types = entity_load_multiple_by_name('entity_test_type', FALSE, array('label' => 'modified'));
+ $this->assertEqual($types['test']->label, 'modified', 'Modified type loaded.');
+
+ // Clear the cache to simulate a new page load.
+ entity_get_controller('entity_test_type')->resetCache();
+
+ // Test loading using a condition again, now they default may not appear any
+ // more as it's overridden by an entity with another label.
+ $types = entity_load_multiple_by_name('entity_test_type', FALSE, array('label' => 'label'));
+ $this->assertTrue(empty($types), 'Conditions are applied to the overridden entity only.');
+
+ // But the overridden entity has to appear with another condition.
+ $types = entity_load_multiple_by_name('entity_test_type', FALSE, array('label' => 'modified'));
+ $this->assertEqual($types['test']->label, 'modified', 'Modified default type loaded by condition.');
+
+ $types = entity_load_multiple_by_name('entity_test_type', array('test', 'test2'));
+ $this->assertEqual($types['test']->label, 'modified', 'Modified default type loaded by id.');
+ $this->assertTrue(entity_has_status('entity_test_type', $types['test'], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.');
+
+ // Test rebuilding the defaults and make sure overridden entities stay.
+ entity_defaults_rebuild();
+ $types = entity_load_multiple_by_name('entity_test_type', array('test', 'test2'));
+ $this->assertEqual($types['test']->label, 'modified', 'Overridden entity is still overridden.');
+ $this->assertTrue(entity_has_status('entity_test_type', $types['test'], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.');
+
+ // Test reverting.
+ $types['test']->delete();
+ $types = entity_load_multiple_by_name('entity_test_type', array('test', 'test2'));
+ $this->assertEqual($types['test']->label, 'label', 'Entity has been reverted.');
+
+ // Test loading an exportable by its numeric id.
+ $result = entity_load_multiple_by_name('entity_test_type', array($types['test']->id));
+ $this->assertTrue(isset($result['test']), 'Exportable entity loaded by the numeric id.');
+
+ // Test exporting an entity to JSON.
+ $serialized_string = $types['test']->export();
+ $data = drupal_json_decode($serialized_string);
+ $this->assertNotNull($data, 'Exported entity is valid JSON.');
+ $import = entity_import('entity_test_type', $serialized_string);
+ $this->assertTrue(get_class($import) == get_class($types['test']) && $types['test']->label == $import->label, 'Successfully exported entity to code.');
+ $this->assertTrue(!isset($import->status), 'Exportable status has not been exported to code.');
+
+ // Test disabling the module providing the defaults in code.
+ $types = entity_load_multiple_by_name('entity_test_type', array('test', 'test2'));
+ $types['test']->label = 'modified';
+ $types['test']->save();
+
+ module_disable(array('entity_feature'));
+
+ // Make sure the overridden entity stays and the other one is deleted.
+ entity_get_controller('entity_test_type')->resetCache();
+ $test = entity_load_single('entity_test_type', 'test');
+ $this->assertTrue(!empty($test) && $test->label == 'modified', 'Overidden entity is still available.');
+ $this->assertTrue(!empty($test) && !entity_has_status('entity_test_type', $test, ENTITY_IN_CODE) && entity_has_status('entity_test_type', $test, ENTITY_CUSTOM), 'Overidden entity is now marked as custom.');
+
+ $test2 = entity_load_single('entity_test_type', 'test2');
+ $this->assertFalse($test2, 'Default entity has disappeared.');
+ }
+
+ /**
+ * Make sure insert() and update() hooks for exportables are invoked.
+ */
+ function testExportableHooks() {
+ $_SESSION['entity_hook_test'] = array();
+ // Enabling the module should invoke the enabled hook for the other
+ // entities provided in code.
+ module_enable(array('entity_feature'));
+
+ $insert = array('main', 'test', 'test2');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_insert'] == $insert, 'Hook entity_insert has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_insert'] == $insert, 'Hook entity_test_type_insert has been invoked.');
+
+ // Load a default entity and make sure the rebuilt logic only ran once.
+ entity_load_single('entity_test_type', 'test');
+ $this->assertTrue(!isset($_SESSION['entity_hook_test']['entity_test_type_update']), '"Entity-test-type" defaults have been rebuilt only once.');
+
+ // Add a new test entity in DB and make sure the hook is invoked too.
+ $test3 = entity_create('entity_test_type', array(
+ 'name' => 'test3',
+ 'label' => 'label',
+ 'weight' => 0,
+ ));
+ $test3->save();
+
+ $insert[] = 'test3';
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_insert'] == $insert, 'Hook entity_insert has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_insert'] == $insert, 'Hook entity_test_type_insert has been invoked.');
+
+ // Now override the 'test' entity and make sure it invokes the update hook.
+ $result = entity_load_multiple_by_name('entity_test_type', array('test'));
+ $result['test']->label = 'modified';
+ $result['test']->save();
+
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_update'] == array('test'), 'Hook entity_update has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_update'] == array('test'), 'Hook entity_test_type_update has been invoked.');
+
+ // 'test' has to remain enabled, as it has been overridden.
+ $delete = array('main', 'test2');
+ module_disable(array('entity_feature'));
+
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_delete'] == $delete, 'Hook entity_deleted has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_delete'] == $delete, 'Hook entity_test_type_deleted has been invoked.');
+
+ // Now make sure 'test' is not overridden any more, but custom.
+ $result = entity_load_multiple_by_name('entity_test_type', array('test'));
+ $this->assertTrue(!$result['test']->hasStatus(ENTITY_OVERRIDDEN), 'Entity is not marked as overridden any more.');
+ $this->assertTrue(entity_has_status('entity_test_type', $result['test'], ENTITY_CUSTOM), 'Entity is marked as custom.');
+
+ // Test deleting the remaining entities from DB.
+ entity_delete_multiple('entity_test_type', array('test', 'test3'));
+ $delete[] = 'test';
+ $delete[] = 'test3';
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_delete'] == $delete, 'Hook entity_deleted has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_delete'] == $delete, 'Hook entity_test_type_deleted has been invoked.');
+ }
+
+ /**
+ * Tests determining changes.
+ */
+ function testChanges() {
+ module_enable(array('entity_feature'));
+ $types = entity_load_multiple_by_name('entity_test_type');
+
+ // Override the default entity, such it gets saved in the DB.
+ $types['test']->label ='test_changes';
+ $types['test']->save();
+
+ // Now test an update without applying any changes.
+ $types['test']->save();
+ $this->assertEqual($types['test']->label, 'test_changes', 'No changes have been determined.');
+
+ // Apply changes.
+ $types['test']->label = 'updated';
+ $types['test']->save();
+
+ // The hook implementations entity_test_entity_test_type_presave() and
+ // entity_test_entity_test_type_update() determine changes and change the
+ // label.
+ $this->assertEqual($types['test']->label, 'updated_presave_update', 'Changes have been determined.');
+
+ // Test the static load cache to be cleared.
+ $types = entity_load_multiple_by_name('entity_test_type');
+ $this->assertEqual($types['test']->label, 'updated_presave', 'Static cache has been cleared.');
+ }
+
+
+ /**
+ * Tests viewing entites.
+ */
+ function testRendering() {
+ module_enable(array('entity_feature'));
+
+ $user1 = $this->drupalCreateUser();
+ // Create test entities for the user1 and unrelated to a user.
+ $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid));
+
+ $render = $entity->view();
+ $output = drupal_render($render);
+ // The entity class adds the user name to the output. Verify it is there.
+ $this->assertTrue(strpos($output, format_username($user1)) !== FALSE, 'Entity has been rendered');
+ }
+
+ /**
+ * Test uninstall of the entity_test module.
+ */
+ function testUninstall() {
+ // Add a test type and add a field instance, uninstall, then re-install and
+ // make sure the field instance can be re-created.
+ $test_type = entity_create('entity_test_type', array(
+ 'name' => 'test',
+ 'label' => 'label',
+ 'weight' => 0,
+ ));
+ $test_type->save();
+
+ $field = array(
+ 'field_name' => 'field_test_fullname',
+ 'type' => 'text',
+ 'cardinality' => 1,
+ 'translatable' => FALSE,
+ );
+ field_create_field($field);
+
+ $instance = array(
+ 'entity_type' => 'entity_test',
+ 'field_name' => 'field_test_fullname',
+ 'bundle' => 'test',
+ 'label' => 'Full name',
+ 'description' => 'Specify your first and last name.',
+ 'widget' => array(
+ 'type' => 'text_textfield',
+ 'weight' => 0,
+ ),
+ );
+ field_create_instance($instance);
+
+ // Uninstallation has to remove all bundles, thus also field instances.
+ module_disable(array('entity_test'));
+ require_once DRUPAL_ROOT . '/includes/install.inc';
+ drupal_uninstall_modules(array('entity_test'));
+
+ // Make sure the instance has been deleted.
+ $instance_read = field_read_instance('entity_test', 'field_test_fullname', 'test', array('include_inactive' => 1));
+ $this->assertFalse((bool) $instance_read, 'Field instance has been deleted.');
+
+ // Ensure re-creating the same instance now works.
+ module_enable(array('entity_test'));
+ $test_type = entity_create('entity_test_type', array(
+ 'name' => 'test',
+ 'label' => 'label',
+ 'weight' => 0,
+ ));
+ $test_type->save();
+ field_create_field($field);
+ field_create_instance($instance);
+
+ $instance_read = field_info_instance('entity_test', 'field_test_fullname', 'test');
+ $this->assertTrue((bool) $instance_read, 'Field instance has been re-created.');
+ }
+}
+
+/**
+ * Test the generated Rules integration.
+ */
+class EntityAPIRulesIntegrationTestCase extends EntityWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity CRUD Rules integration',
+ 'description' => 'Tests the Rules integration provided by the Entity CRUD API.',
+ 'group' => 'Entity API',
+ 'dependencies' => array('rules'),
+ );
+ }
+
+ function setUp() {
+ parent::setUp('entity', 'entity_test', 'rules');
+ // Make sure the logger is enabled so the debug log is saved.
+ variable_set('rules_debug_log', 1);
+ }
+
+ /**
+ * Test the events.
+ */
+ function testEvents() {
+ $rule = rules_reaction_rule();
+ $rule->event('entity_test_presave');
+ $rule->event('entity_test_insert');
+ $rule->event('entity_test_update');
+ $rule->event('entity_test_delete');
+ $rule->action('drupal_message', array('message' => 'hello!'));
+ $rule->save();
+ rules_clear_cache(TRUE);
+
+ // Let the events occur.
+ $user1 = $this->drupalCreateUser();
+ RulesLog::logger()->clear();
+
+ $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid));
+ $entity->save();
+ $entity->name = 'update';
+ $entity->save();
+ $entity->delete();
+
+ // Now there should have been 5 events, 2 times presave and once insert,
+ // update and delete.
+ $count = substr_count(RulesLog::logger()->render(), '0 ms Reacting on event');
+ $this->assertTrue($count == 5, 'Events have been properly invoked.');
+ RulesLog::logger()->checkLog();
+ }
+}
+
+/**
+ * Tests comments with node access.
+ */
+class EntityAPICommentNodeAccessTestCase extends CommentHelperCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity API comment node access',
+ 'description' => 'Test viewing comments on nodes with node access.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ function setUp() {
+ DrupalWebTestCase::setUp('comment', 'entity', 'node_access_test');
+ node_access_rebuild();
+
+ // Create test node and user with simple node access permission. The
+ // 'node test view' permission is implemented and granted by the
+ // node_access_test module.
+ $this->accessUser = $this->drupalCreateUser(array('access comments', 'post comments', 'edit own comments', 'node test view'));
+ $this->noAccessUser = $this->drupalCreateUser(array('administer comments'));
+ $this->node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $this->accessUser->uid));
+ }
+
+ /**
+ * Tests comment access when node access is enabled.
+ */
+ function testCommentNodeAccess() {
+ // Post comment.
+ $this->drupalLogin($this->accessUser);
+ $comment_text = $this->randomName();
+ $comment = $this->postComment($this->node, $comment_text);
+ $comment_loaded = comment_load($comment->id);
+ $this->assertTrue($this->commentExists($comment), 'Comment found.');
+ $this->drupalLogout();
+
+ // Check access to node and associated comment for access user.
+ $this->assertTrue(entity_access('view', 'node', $this->node, $this->accessUser), 'Access to view node was granted for access user');
+ $this->assertTrue(entity_access('view', 'comment', $comment_loaded, $this->accessUser), 'Access to view comment was granted for access user');
+ $this->assertTrue(entity_access('update', 'comment', $comment_loaded, $this->accessUser), 'Access to update comment was granted for access user');
+ $this->assertFalse(entity_access('delete', 'comment', $comment_loaded, $this->accessUser), 'Access to delete comment was denied for access user');
+
+ // Check access to node and associated comment for no access user.
+ $this->assertFalse(entity_access('view', 'node', $this->node, $this->noAccessUser), 'Access to view node was denied for no access user');
+ $this->assertFalse(entity_access('view', 'comment', $comment_loaded, $this->noAccessUser), 'Access to view comment was denied for no access user');
+ $this->assertFalse(entity_access('update', 'comment', $comment_loaded, $this->noAccessUser), 'Access to update comment was denied for no access user');
+ $this->assertFalse(entity_access('delete', 'comment', $comment_loaded, $this->noAccessUser), 'Access to delete comment was denied for no access user');
+ }
+}
+
+/**
+ * Test the i18n integration.
+ */
+class EntityAPIi18nItegrationTestCase extends EntityWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity CRUD i18n integration',
+ 'description' => 'Tests the i18n integration provided by the Entity CRUD API.',
+ 'group' => 'Entity API',
+ 'dependencies' => array('i18n_string'),
+ );
+ }
+
+ function setUp() {
+ parent::setUp('entity_test_i18n');
+ $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages'));
+ $this->drupalLogin($this->admin_user);
+ $this->addLanguage('de');
+ }
+
+ /**
+ * Copied from i18n module (class Drupali18nTestCase).
+ *
+ * We cannot extend from Drupali18nTestCase as else the test-bot would die.
+ */
+ public function addLanguage($language_code) {
+ // Check to make sure that language has not already been installed.
+ $this->drupalGet('admin/config/regional/language');
+
+ if (strpos($this->drupalGetContent(), 'enabled[' . $language_code . ']') === FALSE) {
+ // Doesn't have language installed so add it.
+ $edit = array();
+ $edit['langcode'] = $language_code;
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+
+ // Make sure we are not using a stale list.
+ drupal_static_reset('language_list');
+ $languages = language_list('language');
+ $this->assertTrue(array_key_exists($language_code, $languages), t('Language was installed successfully.'));
+
+ if (array_key_exists($language_code, $languages)) {
+ $this->assertRaw(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => $languages[$language_code]->name, '@locale-help' => url('admin/help/locale'))), t('Language has been created.'));
+ }
+ }
+ elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'enabled[' . $language_code . ']'))) {
+ // It's installed and enabled. No need to do anything.
+ $this->assertTrue(true, 'Language [' . $language_code . '] already installed and enabled.');
+ }
+ else {
+ // It's installed but not enabled. Enable it.
+ $this->assertTrue(true, 'Language [' . $language_code . '] already installed.');
+ $this->drupalPost(NULL, array('enabled[' . $language_code . ']' => TRUE), t('Save configuration'));
+ $this->assertRaw(t('Configuration saved.'), t('Language successfully enabled.'));
+ }
+ }
+
+ /**
+ * Tests the provided default controller.
+ */
+ function testDefaultController() {
+ // Create test entities for the user1 and unrelated to a user.
+ $entity = entity_create('entity_test_type', array(
+ 'name' => 'test',
+ 'uid' => $GLOBALS['user']->uid,
+ 'label' => 'label-en',
+ ));
+ $entity->save();
+
+ // Add a translation.
+ i18n_string_textgroup('entity_test')->update_translation("entity_test_type:{$entity->name}:label", 'de', 'label-de');
+
+ $default = entity_i18n_string("entity_test:entity_test_type:{$entity->name}:label", 'label-en');
+ $translation = entity_i18n_string("entity_test:entity_test_type:{$entity->name}:label", 'label-en', 'de');
+
+ $this->assertEqual($translation, 'label-de', 'Label has been translated.');
+ $this->assertEqual($default, 'label-en', 'Default label retrieved.');
+
+ // Test the helper method.
+ $translation = $entity->getTranslation('label', 'de');
+ $default = $entity->getTranslation('label');
+ $this->assertEqual($translation, 'label-de', 'Label has been translated via the helper method.');
+ $this->assertEqual($default, 'label-en', 'Default label retrieved via the helper method.');
+
+ // Test updating and make sure the translation stays.
+ $entity->name = 'test2';
+ $entity->save();
+ $translation = $entity->getTranslation('label', 'de');
+ $this->assertEqual($translation, 'label-de', 'Translation survives a name change.');
+
+ // Test using the wrapper to retrieve a translation.
+ $wrapper = entity_metadata_wrapper('entity_test_type', $entity);
+ $translation = $wrapper->language('de')->label->value();
+ $this->assertEqual($translation, 'label-de', 'Translation retrieved via the wrapper.');
+
+ // Test deleting.
+ $entity->delete();
+ $translation = entity_i18n_string("entity_test:entity_test_type:{$entity->name}:label", 'label-en', 'de');
+ $this->assertEqual($translation, 'label-en', 'Translation has been deleted.');
+ }
+}
+
+/**
+ * Tests metadata wrappers.
+ */
+class EntityMetadataTestCase extends EntityWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Metadata Wrapper',
+ 'description' => 'Makes sure metadata wrapper are working right.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('entity', 'entity_test', 'locale');
+ // Create a field having 4 values for testing multiple value support.
+ $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $this->field = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4);
+ $this->field = field_create_field($this->field);
+ $this->field_id = $this->field['id'];
+ $this->instance = array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => 'node',
+ 'bundle' => 'page',
+ 'label' => $this->randomName() . '_label',
+ 'description' => $this->randomName() . '_description',
+ 'weight' => mt_rand(0, 127),
+ 'settings' => array(
+ 'text_processing' => FALSE,
+ ),
+ 'widget' => array(
+ 'type' => 'text_textfield',
+ 'label' => 'Test Field',
+ 'settings' => array(
+ 'size' => 64,
+ )
+ )
+ );
+ field_create_instance($this->instance);
+
+ // Make the body field and the node type 'page' translatable.
+ $field = field_info_field('body');
+ $field['translatable'] = TRUE;
+ field_update_field($field);
+ variable_set('language_content_type_page', 1);
+ }
+
+ /**
+ * Creates a user and a node, then tests getting the properties.
+ */
+ function testEntityMetadataWrapper() {
+ $account = $this->drupalCreateUser();
+ // For testing sanitizing give the user a malicious user name
+ $account = user_save($account, array('name' => '<b>BadName</b>'));
+ $title = '<b>Is it bold?<b>';
+ $body[LANGUAGE_NONE][0] = array('value' => '<b>The body & nothing.</b>', 'summary' => '<b>The body.</b>');
+ $node = $this->drupalCreateNode(array('uid' => $account->uid, 'name' => $account->name, 'body' => $body, 'title' => $title, 'summary' => '', 'type' => 'page'));
+
+ // First test without sanitizing.
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $this->assertEqual('<b>Is it bold?<b>', $wrapper->title->value(), 'Getting a field value.');
+ $this->assertEqual($node->title, $wrapper->title->raw(), 'Getting a raw property value.');
+
+ // Test chaining.
+ $this->assertEqual($account->mail, $wrapper->author->mail->value(), 'Testing chained usage.');
+ $this->assertEqual($account->name, $wrapper->author->name->value(), 'Testing chained usage with callback and sanitizing.');
+
+ // Test sanitized output.
+ $options = array('sanitize' => TRUE);
+ $this->assertEqual(check_plain('<b>Is it bold?<b>'), $wrapper->title->value($options), 'Getting sanitized field.');
+ $this->assertEqual(filter_xss($node->name), $wrapper->author->name->value($options), 'Getting sanitized property with getter callback.');
+
+ // Test getting an not existing property.
+ try {
+ echo $wrapper->dummy;
+ $this->fail('Getting an not existing property.');
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $this->pass('Getting an not existing property.');
+ }
+
+ // Test setting.
+ $wrapper->author = 0;
+ $this->assertEqual(0, $wrapper->author->uid->value(), 'Setting a property.');
+ try {
+ $wrapper->url = 'dummy';
+ $this->fail('Setting an unsupported property.');
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $this->pass('Setting an unsupported property.');
+ }
+
+ // Test value validation.
+ $this->assertFalse($wrapper->author->name->validate(array(3)), 'Validation correctly checks for valid data types.');
+ try {
+ $wrapper->author->mail = 'foo';
+ $this->fail('An invalid mail address has been set.');
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $this->pass('Setting an invalid mail address throws exception.');
+ }
+ // Test unsetting a required property.
+ try {
+ $wrapper->author = NULL;
+ $this->fail('The required node author has been unset.');
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $this->pass('Unsetting the required node author throws an exception.');
+ }
+
+ // Test setting a referenced entity by id.
+ $wrapper->author->set($GLOBALS['user']->uid);
+ $this->assertEqual($wrapper->author->getIdentifier(), $GLOBALS['user']->uid, 'Get the identifier of a referenced entity.');
+ $this->assertEqual($wrapper->author->uid->value(), $GLOBALS['user']->uid, 'Successfully set referenced entity using the identifier.');
+ // Set by object.
+ $wrapper->author->set($GLOBALS['user']);
+ $this->assertEqual($wrapper->author->uid->value(), $GLOBALS['user']->uid, 'Successfully set referenced entity using the entity.');
+
+
+ // Test getting by the field API processed values like the node body.
+ $body_value = $wrapper->body->value;
+ $this->assertEqual("<p>The body &amp; nothing.</p>\n", $body_value->value(), "Getting processed value.");
+ $this->assertEqual("The body & nothing.\n", $body_value->value(array('decode' => TRUE)), "Decoded value.");
+ $this->assertEqual("<b>The body & nothing.</b>", $body_value->raw(), "Raw body returned.");
+
+ // Test getting the summary.
+ $this->assertEqual("<p>The body.</p>\n", $wrapper->body->summary->value(), "Getting body summary.");
+
+ $wrapper->body->set(array('value' => "<b>The second body.</b>"));
+ $this->assertEqual("<p>The second body.</p>\n", $wrapper->body->value->value(), "Setting a processed field value and reading it again.");
+ $this->assertEqual($node->body[LANGUAGE_NONE][0]['value'], "<b>The second body.</b>", 'Update appears in the wrapped entity.');
+ $this->assert(isset($node->body[LANGUAGE_NONE][0]['safe_value']), 'Formatted text has been processed.');
+
+ // Test translating the body on an English node.
+ locale_add_language('de');
+ $body['en'][0] = array('value' => '<b>English body.</b>', 'summary' => '<b>The body.</b>');
+ $node = $this->drupalCreateNode(array('body' => $body, 'language' => 'en', 'type' => 'page'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $wrapper->language('de');
+
+ $languages = language_list();
+ $this->assertEqual($wrapper->getPropertyLanguage(), $languages['de'], 'Wrapper language has been set to German');
+ $this->assertEqual($wrapper->body->value->value(), "<p>English body.</p>\n", 'Language fallback on default language.');
+
+ // Set a German text using the wrapper.
+ $wrapper->body->set(array('value' => "<b>Der zweite Text.</b>"));
+ $this->assertEqual($wrapper->body->value->value(), "<p>Der zweite Text.</p>\n", 'German body set and retrieved.');
+
+ $wrapper->language(LANGUAGE_NONE);
+ $this->assertEqual($wrapper->body->value->value(), "<p>English body.</p>\n", 'Default language text is still there.');
+
+ // Test iterator.
+ $type_info = entity_get_property_info('node');
+ $this->assertFalse(array_diff_key($type_info['properties'], iterator_to_array($wrapper->getIterator())), 'Iterator is working.');
+ foreach ($wrapper as $property) {
+ $this->assertTrue($property instanceof EntityMetadataWrapper, 'Iterate over wrapper properties.');
+ }
+
+ // Test setting a new node.
+ $node->title = 'foo';
+ $wrapper->set($node);
+ $this->assertEqual($wrapper->title->value(), 'foo', 'Changed the wrapped node.');
+
+ // Test getting options lists.
+ $this->assertEqual($wrapper->type->optionsList(), node_type_get_names(), 'Options list returned.');
+
+ // Test making use of a generic 'entity' reference property the
+ // 'entity_test' module provides. The property defaults to the node author.
+ $this->assertEqual($wrapper->reference->uid->value(), $wrapper->author->getIdentifier(), 'Used generic entity reference property.');
+ // Test updating a property of the generic entity reference.
+ $wrapper->reference->name->set('foo');
+ $this->assertEqual($wrapper->reference->name->value(), 'foo', 'Updated property of generic entity reference');
+ // For testing, just point the reference to the node itself now.
+ $wrapper->reference->set($wrapper);
+ $this->assertEqual($wrapper->reference->nid->value(), $wrapper->getIdentifier(), 'Correctly updated the generic entity referenced property.');
+
+ // Test saving and deleting.
+ $wrapper->save();
+ $wrapper->delete();
+ $return = node_load($wrapper->getIdentifier());
+ $this->assertFalse($return, "Node has been successfully deleted.");
+
+ // Ensure changing the bundle changes available wrapper properties.
+ $wrapper->type->set('article');
+ $this->assertTrue(isset($wrapper->field_tags), 'Changing bundle changes available wrapper properties.');
+
+ // Test labels.
+ $user = $this->drupalCreateUser();
+ user_save($user, array('roles' => array()));
+ $wrapper->author = $user->uid;
+ $this->assertEqual($wrapper->label(), $node->title, 'Entity label returned.');
+ $this->assertEqual($wrapper->author->roles[0]->label(), t('authenticated user'), 'Label from options list returned');
+ $this->assertEqual($wrapper->author->roles->label(), t('authenticated user'), 'Label for a list from options list returned');
+ }
+
+ /**
+ * Test supporting multi-valued fields.
+ */
+ function testListMetadataWrappers() {
+ $property = $this->field_name;
+ $values = array();
+ $values[LANGUAGE_NONE][0] = array('value' => '<b>2009-09-05</b>');
+ $values[LANGUAGE_NONE][1] = array('value' => '2009-09-05');
+ $values[LANGUAGE_NONE][2] = array('value' => '2009-08-05');
+
+ $node = $this->drupalCreateNode(array('type' => 'page', $property => $values));
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $this->assertEqual('<b>2009-09-05</b>', $wrapper->{$property}[0]->value(), 'Getting array entry.');
+ $this->assertEqual('2009-09-05', $wrapper->{$property}->get(1)->value(), 'Getting array entry.');
+ $this->assertEqual(3, count($wrapper->{$property}->value()), 'Getting the whole array.');
+
+ // Test sanitizing
+ $this->assertEqual(check_plain('<b>2009-09-05</b>'), $wrapper->{$property}[0]->value(array('sanitize' => TRUE)), 'Getting array entry.');
+
+ // Test iterator
+ $this->assertEqual(array_keys(iterator_to_array($wrapper->$property->getIterator())), array_keys($wrapper->$property->value()), 'Iterator is working.');
+ foreach ($wrapper->$property as $p) {
+ $this->assertTrue($p instanceof EntityMetadataWrapper, 'Iterate over list wrapper properties.');
+ }
+
+ // Make sure changing the array changes the actual entity property.
+ $wrapper->{$property}[0] = '2009-10-05';
+ unset($wrapper->{$property}[1], $wrapper->{$property}[2]);
+ $this->assertEqual($wrapper->{$property}->value(), array('2009-10-05'), 'Setting multiple property values.');
+
+ // Test setting an arbitrary list item.
+ $list = array(0 => REQUEST_TIME);
+ $wrapper = entity_metadata_wrapper('list<date>', $list);
+ $wrapper[1] = strtotime('2009-09-05');
+ $this->assertEqual($wrapper->value(), array(REQUEST_TIME, strtotime('2009-09-05')), 'Setting a list item.');
+ $this->assertEqual($wrapper->count(), 2, 'List count is correct.');
+
+ // Test using a list wrapper without data.
+ $wrapper = entity_metadata_wrapper('list<date>');
+ $info = array();
+ foreach ($wrapper as $item) {
+ $info[] = $item->info();
+ }
+ $this->assertTrue($info[0]['type'] == 'date', 'Iterated over empty list wrapper.');
+
+ // Test using a list of entities with a list of term objects.
+ $list = array();
+ $list[] = entity_property_values_create_entity('taxonomy_term', array(
+ 'name' => 'term 1',
+ 'vocabulary' => 1,
+ ))->save()->value();
+ $list[] = entity_property_values_create_entity('taxonomy_term', array(
+ 'name' => 'term 2',
+ 'vocabulary' => 1,
+ ))->save()->value();
+ $wrapper = entity_metadata_wrapper('list<taxonomy_term>', $list);
+ $this->assertTrue($wrapper[0]->name->value() == 'term 1', 'Used a list of entities.');
+ // Test getting a list of identifiers.
+ $ids = $wrapper->value(array('identifier' => TRUE));
+ $this->assertTrue(!is_object($ids[0]), 'Get a list of entity ids.');
+
+ $wrapper = entity_metadata_wrapper('list<taxonomy_term>', $ids);
+ $this->assertTrue($wrapper[0]->name->value() == 'term 1', 'Created a list of entities with ids.');
+
+ // Test with a list of generic entities. The list is expected to be a list
+ // of entity wrappers, otherwise the entity type is unknown.
+ $node = $this->drupalCreateNode(array('title' => 'node 1'));
+ $list = array();
+ $list[] = entity_metadata_wrapper('node', $node);
+ $wrapper = entity_metadata_wrapper('list<entity>', $list);
+ $this->assertEqual($wrapper[0]->title->value(), 'node 1', 'Wrapped node was found in generic list of entities.');
+ }
+
+ /**
+ * Tests using the wrapper without any data.
+ */
+ function testWithoutData() {
+ $wrapper = entity_metadata_wrapper('node', NULL, array('bundle' => 'page'));
+ $this->assertTrue(isset($wrapper->title), 'Bundle properties have been added.');
+ $info = $wrapper->author->mail->info();
+ $this->assertTrue(!empty($info) && is_array($info) && isset($info['label']), 'Property info returned.');
+ }
+
+ /**
+ * Test using access() method.
+ */
+ function testAccess() {
+ // Test without data.
+ $account = $this->drupalCreateUser(array('bypass node access'));
+ $this->assertTrue(entity_access('view', 'node', NULL, $account), 'Access without data checked.');
+
+ // Test with actual data.
+ $values[LANGUAGE_NONE][0] = array('value' => '<b>2009-09-05</b>');
+ $values[LANGUAGE_NONE][1] = array('value' => '2009-09-05');
+ $node = $this->drupalCreateNode(array('type' => 'page', $this->field_name => $values));
+ $this->assertTrue(entity_access('delete', 'node', $node, $account), 'Access with data checked.');
+
+ // Test per property access without data.
+ $account2 = $this->drupalCreateUser(array('bypass node access', 'administer nodes'));
+ $wrapper = entity_metadata_wrapper('node', NULL, array('bundle' => 'page'));
+ $this->assertTrue($wrapper->access('edit', $account), 'Access to node granted.');
+ $this->assertFalse($wrapper->status->access('edit', $account), 'Access for admin property denied.');
+ $this->assertTrue($wrapper->status->access('edit', $account2), 'Access for admin property allowed for the admin.');
+
+ // Test per property access with data.
+ $wrapper = entity_metadata_wrapper('node', $node, array('bundle' => 'page'));
+ $this->assertFalse($wrapper->status->access('edit', $account), 'Access for admin property denied.');
+ $this->assertTrue($wrapper->status->access('edit', $account2), 'Access for admin property allowed for the admin.');
+
+ // Test field level access.
+ $this->assertTrue($wrapper->{$this->field_name}->access('view'), 'Field access granted.');
+
+ // Create node owned by anonymous and test access() method on each of its
+ // properties.
+ $node = $this->drupalCreateNode(array('type' => 'page', 'uid' => 0));
+ $wrapper = entity_metadata_wrapper('node', $node->nid);
+ foreach ($wrapper as $name => $property) {
+ $property->access('view');
+ }
+
+ // Property access of entity references takes entity access into account.
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+ $unprivileged_user = $this->drupalCreateUser();
+ $privileged_user = $this->drupalCreateUser(array('access user profiles'));
+
+ $this->assertTrue($wrapper->author->access('view', $privileged_user));
+ $this->assertFalse($wrapper->author->access('view', $unprivileged_user));
+
+ // Ensure the same works with multiple entity references by testing the
+ // $node->field_tags example.
+ $privileged_user = $this->drupalCreateUser(array('administer taxonomy'));
+ // Terms are view-able with access content, so make sure to remove this
+ // permission first.
+ user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access content'));
+ $unprivileged_user = drupal_anonymous_user();
+
+ $this->assertTrue($wrapper->field_tags->access('view', $privileged_user), 'Privileged user has access.');
+ $this->assertTrue($wrapper->field_tags->access('view', $unprivileged_user), 'Unprivileged user has access.');
+ $this->assertTrue($wrapper->field_tags[0]->access('view', $privileged_user), 'Privileged user has access.');
+ $this->assertFalse($wrapper->field_tags[0]->access('view', $unprivileged_user), 'Unprivileged user has no access.');
+ }
+
+ /**
+ * Tests using a data structure with passed in metadata.
+ */
+ function testDataStructureWrapper() {
+ $log_entry = array(
+ 'type' => 'entity',
+ 'message' => $this->randomName(8),
+ 'variables' => array(),
+ 'severity' => WATCHDOG_NOTICE,
+ 'link' => '',
+ 'user' => $GLOBALS['user'],
+ );
+ $info['property info'] = array(
+ 'type' => array('type' => 'text', 'label' => 'The category to which this message belongs.'),
+ 'message' => array('type' => 'text', 'label' => 'The log message.'),
+ 'user' => array('type' => 'user', 'label' => 'The user causing the log entry.'),
+ );
+ $wrapper = entity_metadata_wrapper('log_entry', $log_entry, $info);
+ $this->assertEqual($wrapper->user->name->value(), $GLOBALS['user']->name, 'Wrapped custom entity.');
+ }
+
+ /**
+ * Tests using entity_property_query().
+ */
+ function testEntityQuery() {
+ // Creat a test node.
+ $title = '<b>Is it bold?<b>';
+ $values[LANGUAGE_NONE][0] = array('value' => 'foo');
+ $node = $this->drupalCreateNode(array($this->field_name => $values, 'title' => $title, 'uid' => $GLOBALS['user']->uid));
+
+ $results = entity_property_query('node', 'title', $title);
+ $this->assertEqual($results, array($node->nid), 'Queried nodes with a given title.');
+
+ $results = entity_property_query('node', $this->field_name, 'foo');
+ $this->assertEqual($results, array($node->nid), 'Queried nodes with a given field value.');
+
+ $results = entity_property_query('node', $this->field_name, array('foo', 'bar'));
+ $this->assertEqual($results, array($node->nid), 'Queried nodes with a list of possible values.');
+
+ $results = entity_property_query('node', 'author', $GLOBALS['user']);
+ $this->assertEqual($results, array($node->nid), 'Queried nodes with a given auhtor.');
+
+ // Create another test node and try querying for tags.
+ $tag = entity_property_values_create_entity('taxonomy_term', array(
+ 'name' => $this->randomName(),
+ 'vocabulary' => 1,
+ ))->save();
+ $field_tag_value[LANGUAGE_NONE][0]['tid'] = $tag->getIdentifier();
+ $node = $this->drupalCreateNode(array('type' => 'article', 'field_tags' => $field_tag_value));
+
+ // Try query-ing with a single value.
+ $results = entity_property_query('node', 'field_tags', $tag->getIdentifier());
+ $this->assertEqual($results, array($node->nid), 'Queried nodes with a given term id.');
+
+ $results = entity_property_query('node', 'field_tags', $tag->value());
+ $this->assertEqual($results, array($node->nid), 'Queried nodes with a given term object.');
+
+ // Try query-ing with a list of possible values.
+ $results = entity_property_query('node', 'field_tags', array($tag->getIdentifier()));
+ $this->assertEqual($results, array($node->nid), 'Queried nodes with a list of term ids.');
+ }
+
+ /**
+ * Tests serializing data wrappers, in particular for EntityDrupalWrapper.
+ */
+ function testWrapperSerialization() {
+ $node = $this->drupalCreateNode();
+ $wrapper = entity_metadata_wrapper('node', $node);
+ $this->assertTrue($wrapper->value() == $node, 'Data correctly wrapped.');
+
+ // Test serializing and make sure only the node id is stored.
+ $this->assertTrue(strpos(serialize($wrapper), $node->title) === FALSE, 'Node has been correctly serialized.');
+ $this->assertEqual(unserialize(serialize($wrapper))->title->value(), $node->title, 'Serializing works right.');
+
+ $wrapper2 = unserialize(serialize($wrapper));
+ // Test serializing the unloaded wrapper.
+ $this->assertEqual(unserialize(serialize($wrapper2))->title->value(), $node->title, 'Serializing works right.');
+
+ // Test loading a not more existing node.
+ $s = serialize($wrapper2);
+ node_delete($node->nid);
+ $this->assertFalse(node_load($node->nid), 'Node deleted.');
+
+ $value = unserialize($s)->value();
+ $this->assertNull($value, 'Tried to load not existing node.');
+ }
+}
+
+/**
+ * Tests basic entity_access() functionality for nodes.
+ *
+ * This code is a modified version of NodeAccessTestCase.
+ *
+ * @see NodeAccessTestCase
+ */
+class EntityMetadataNodeAccessTestCase extends EntityWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity Metadata Node Access',
+ 'description' => 'Test entity_access() for nodes',
+ 'group' => 'Entity API',
+ );
+ }
+
+ /**
+ * Asserts node_access() correctly grants or denies access.
+ */
+ function assertNodeMetadataAccess($ops, $node, $account) {
+ foreach ($ops as $op => $result) {
+ $msg = t("entity_access() returns @result with operation '@op'.", array('@result' => $result ? 'TRUE' : 'FALSE', '@op' => $op));
+ $access = entity_access($op, 'node', $node, $account);
+ $this->assertEqual($result, $access, $msg);
+ }
+ }
+
+ function setUp() {
+ parent::setUp('entity', 'node');
+ // Clear permissions for authenticated users.
+ db_delete('role_permission')
+ ->condition('rid', DRUPAL_AUTHENTICATED_RID)
+ ->execute();
+ }
+
+ /**
+ * Runs basic tests for entity_access() function.
+ */
+ function testNodeMetadataAccess() {
+ // Author user.
+ $node_author_account = $this->drupalCreateUser(array());
+ // Make a node object.
+ $settings = array(
+ 'uid' => $node_author_account->uid,
+ 'type' => 'page',
+ 'title' => 'Node ' . $this->randomName(32),
+ );
+ $node = $this->drupalCreateNode($settings);
+
+ // Ensures user without 'access content' permission can do nothing.
+ $web_user1 = $this->drupalCreateUser(array('create page content', 'edit any page content', 'delete any page content'));
+ $this->assertNodeMetadataAccess(array('create' => FALSE, 'view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node, $web_user1);
+
+ // Ensures user with 'bypass node access' permission can do everything.
+ $web_user2 = $this->drupalCreateUser(array('bypass node access'));
+ $this->assertNodeMetadataAccess(array('create' => TRUE, 'view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node, $web_user2);
+
+ // User cannot 'view own unpublished content'.
+ $web_user3 = $this->drupalCreateUser(array('access content'));
+ // Create an unpublished node.
+ $settings = array('type' => 'page', 'status' => 0, 'uid' => $web_user3->uid);
+ $node_unpublished = $this->drupalCreateNode($settings);
+ $this->assertNodeMetadataAccess(array('view' => FALSE), $node_unpublished, $web_user3);
+ // User cannot create content without permission.
+ $this->assertNodeMetadataAccess(array('create' => FALSE), $node, $web_user3);
+
+ // User can 'view own unpublished content', but another user cannot.
+ $web_user4 = $this->drupalCreateUser(array('access content', 'view own unpublished content'));
+ $web_user5 = $this->drupalCreateUser(array('access content', 'view own unpublished content'));
+ $node4 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user4->uid));
+ $this->assertNodeMetadataAccess(array('view' => TRUE, 'update' => FALSE), $node4, $web_user4);
+ $this->assertNodeMetadataAccess(array('view' => FALSE), $node4, $web_user5);
+
+ // Tests the default access provided for a published node.
+ $node5 = $this->drupalCreateNode();
+ $this->assertNodeMetadataAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE, 'create' => FALSE), $node5, $web_user3);
+ }
+}
+
+/**
+ * Test user permissions for node creation.
+ */
+class EntityMetadataNodeCreateAccessTestCase extends EntityWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity Metadata Node Create Access',
+ 'description' => 'Test entity_access() for nodes',
+ 'group' => 'Entity API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('entity', 'node');
+ }
+
+ /**
+ * Addresses the special case of 'create' access for nodes.
+ */
+ public function testNodeMetadataCreateAccess() {
+ // Create some users. One with super-powers, one with create perms,
+ // and one with no perms, and a different one to author the node.
+ $admin_account = $this->drupalCreateUser(array(
+ 'bypass node access',
+ ));
+ $creator_account = $this->drupalCreateUser(array(
+ 'create page content',
+ ));
+ $auth_only_account = $this->drupalCreateUser(array());
+ $node_author_account = $this->drupalCreateUser(array());
+
+ // Make a node object with Entity API (contrib)
+ $settings = array(
+ 'uid' => $node_author_account->uid,
+ 'type' => 'page',
+ 'title' => $this->randomName(32),
+ 'body' => array(LANGUAGE_NONE => array(array($this->randomName(64)))),
+ );
+ $node = entity_create('node', $settings);
+
+ // Test the populated wrapper.
+ $wrapper = entity_metadata_wrapper('node', $node);
+ $this->assertTrue($wrapper->entityAccess('create', $admin_account), 'Create access allowed for ADMIN, for populated wrapper.');
+ $this->assertTrue($wrapper->entityAccess('create', $creator_account), 'Create access allowed for CREATOR, for populated wrapper.');
+ $this->assertFalse($wrapper->entityAccess('create', $auth_only_account), 'Create access denied for USER, for populated wrapper.');
+
+ // Test entity_acces().
+ $this->assertTrue(entity_access('create', 'node', $node, $admin_account), 'Create access allowed for ADMIN, for entity_access().');
+ $this->assertTrue(entity_access('create', 'node', $node, $creator_account), 'Create access allowed for CREATOR, for entity_access().');
+ $this->assertFalse(entity_access('create', 'node', $node, $auth_only_account), 'Create access denied for USER, for entity_access().');
+ }
+}
+
+/**
+ * Tests user permissions for node revisions.
+ *
+ * Based almost entirely on NodeRevisionPermissionsTestCase.
+ */
+class EntityMetadataNodeRevisionAccessTestCase extends DrupalWebTestCase {
+ protected $node_revisions = array();
+ protected $accounts = array();
+
+ // Map revision permission names to node revision access ops.
+ protected $map = array(
+ 'view' => 'view revisions',
+ 'update' => 'revert revisions',
+ 'delete' => 'delete revisions',
+ );
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity Metadata Node Revision Access',
+ 'description' => 'Tests user permissions for node revision operations.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('entity', 'node');
+
+ // Create a node with several revisions.
+ $node = $this->drupalCreateNode();
+ $this->node_revisions[] = $node;
+
+ for ($i = 0; $i < 3; $i++) {
+ // Create a revision for the same nid and settings with a random log.
+ $revision = node_load($node->nid);
+ $revision->revision = 1;
+ $revision->log = $this->randomName(32);
+ node_save($revision);
+ $this->node_revisions[] = node_load($revision->nid);
+ }
+
+ // Create three users, one with each revision permission.
+ foreach ($this->map as $op => $permission) {
+ // Create the user.
+ $account = $this->drupalCreateUser(
+ array(
+ 'access content',
+ 'edit any page content',
+ 'delete any page content',
+ $permission,
+ )
+ );
+ $account->op = $op;
+ $this->accounts[] = $account;
+ }
+
+ // Create an admin account (returns TRUE for all revision permissions).
+ $admin_account = $this->drupalCreateUser(array('access content', 'administer nodes'));
+ $admin_account->is_admin = TRUE;
+ $this->accounts['admin'] = $admin_account;
+
+ // Create a normal account (returns FALSE for all revision permissions).
+ $normal_account = $this->drupalCreateUser();
+ $normal_account->op = FALSE;
+ $this->accounts[] = $normal_account;
+ }
+
+ /**
+ * Tests the entity_access() function for revisions.
+ */
+ function testNodeRevisionAccess() {
+ // $node_revisions[1] won't be the latest revision.
+ $revision = $this->node_revisions[1];
+
+ $parameters = array(
+ 'op' => array_keys($this->map),
+ 'account' => $this->accounts,
+ );
+
+ $permutations = $this->generatePermutations($parameters);
+ $entity_type = 'node';
+ foreach ($permutations as $case) {
+ if (!empty($case['account']->is_admin) || $case['op'] == $case['account']->op) {
+ $access = entity_access($case['op'], $entity_type, $revision, $case['account']);
+ $this->assertTrue($access, "{$this->map[$case['op']]} granted on $entity_type.");
+ }
+ else {
+ $access = entity_access($case['op'], $entity_type, $revision, $case['account']);
+ $this->assertFalse($access, "{$this->map[$case['op']]} NOT granted on $entity_type.");
+ }
+ }
+ }
+}
+
+/**
+ * Tests provided entity property info of the core modules.
+ */
+class EntityTokenTestCase extends EntityWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity tokens',
+ 'description' => 'Tests provided tokens for entity properties.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('entity_token');
+ }
+
+ /**
+ * Tests whether token support is basically working.
+ */
+ function testTokenSupport() {
+ // Test basic tokens.
+ $node = $this->drupalCreateNode(array('sticky' => TRUE, 'promote' => FALSE));
+ $text = "Sticky: [node:sticky] Promote: [node:promote] User: [site:current-user:name]";
+ $true = t('true');
+ $false = t('false');
+ $user_name = $GLOBALS['user']->name;
+ $target = "Sticky: $true Promote: $false User: $user_name";
+ $replace = token_replace($text, array('node' => $node));
+ $this->assertEqual($replace, $target, 'Provided tokens basically work.');
+
+ // Test multiple-value tokens using the tags field of articles.
+ for ($i = 0; $i < 4; $i++) {
+ $tags[$i] = entity_property_values_create_entity('taxonomy_term', array(
+ 'name' => $this->randomName(),
+ 'vocabulary' => 1,
+ ))->save();
+ $field_value[LANGUAGE_NONE][$i]['tid'] = $tags[$i]->getIdentifier();
+ $labels[$i] = $tags[$i]->label();
+ }
+ $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', 'field_tags' => $field_value));
+
+ $text = "Tags: [node:field-tags] First: [node:field-tags:0] 2nd name: [node:field-tags:1:name] 1st vocab [node:field-tags:0:vocabulary]";
+ $tag_labels = implode(', ', $labels);
+ $target = "Tags: $tag_labels First: $labels[0] 2nd name: $labels[1] 1st vocab {$tags[0]->vocabulary->label()}";
+ $replace = token_replace($text, array('node' => $node));
+ $this->assertEqual($replace, $target, 'Multiple-value token replacements have been replaced.');
+
+ // Make sure not existing values are not handled.
+ $replace = token_replace("[node:field-tags:43]", array('node' => $node));
+ $this->assertEqual($replace, "[node:field-tags:43]", 'Not existing values are not replaced.');
+
+ // Test data-structure tokens like [site:current-page:url].
+ $replace = token_replace("[site:current-page:url]", array());
+ $this->assertEqual($replace, $GLOBALS['base_root'] . request_uri(), 'Token replacements of data structure properties replaced.');
+
+ // Test chaining of data-structure tokens using an image-field.
+ $file = $this->createFile('image');
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $wrapper->field_image = array('fid' => $file->fid);
+ $replace = token_replace("[node:field-image:file:name]", array('node' => $node));
+ $this->assertEqual($replace, $wrapper->field_image->file->name->value(), 'Token replacements of an image field have been replaced.');
+ }
+}
+
+/**
+ * Tests provided entity property info of the core modules.
+ */
+class EntityMetadataIntegrationTestCase extends EntityWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Property info core integration',
+ 'description' => 'Tests using metadata wrapper for drupal core.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('entity', 'book', 'statistics', 'locale');
+ }
+
+ protected function assertException($wrapper, $name, $text = NULL) {
+ $this->assertTrue(isset($wrapper->$name), 'Property wrapper ' . check_plain($name) . ' exists.');
+ $text = isset($text) ? $text : 'Getting the not existing property ' . $name . ' throws exception.';
+ try {
+ $wrapper->$name->value();
+ $this->fail($text);
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $this->pass($text);
+ }
+ }
+
+ protected function assertEmpty($wrapper, $name) {
+ $this->assertTrue(isset($wrapper->$name), 'Property ' . check_plain($name) . ' exists.');
+ $this->assertTrue($wrapper->$name->value() === NULL, 'Property ' . check_plain($name) . ' is empty.');
+ }
+
+ protected function assertEmptyArray($wrapper, $name) {
+ $this->assertTrue(isset($wrapper->$name), 'Property ' . check_plain($name) . ' exists.');
+ $this->assertTrue($wrapper->$name->value() === array(), 'Property ' . check_plain($name) . ' is an empty array.');
+ }
+
+ protected function assertValue($wrapper, $key) {
+ $this->assertTrue($wrapper->$key->value() !== NULL, check_plain($key) . ' property returned.');
+ $info = $wrapper->$key->info();
+ if (!empty($info['raw getter callback'])) {
+ // Also test getting the raw value
+ $this->assertTrue($wrapper->$key->raw() !== NULL, check_plain($key) . ' raw value returned.');
+ }
+ }
+
+ /**
+ * Test book module integration.
+ */
+ function testBookModule() {
+ $title = 'Book 1';
+ $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'book', 'book' => array('bid' => 'new')));
+ $book = array('bid' => $node->nid, 'plid' => $node->book['mlid']);
+ $node2 = $this->drupalCreateNode(array('type' => 'book', 'book' => $book));
+ $node3 = $this->drupalCreateNode(array('type' => 'page'));
+
+ // Test whether the properties work.
+ $wrapper = entity_metadata_wrapper('node', $node2);
+ $this->assertEqual($title, $wrapper->book->title->value(), "Book title returned.");
+ $this->assertEqual(array($node->nid), $wrapper->book_ancestors->value(array('identifier' => TRUE)), "Book ancestors returned.");
+ $this->assertEqual($node->nid, $wrapper->book->nid->value(), "Book id returned.");
+
+ // Try using book properties for no book nodes.
+ $wrapper = entity_metadata_wrapper('node', $node3);
+ $this->assertEmpty($wrapper, 'book');
+ $this->assertEmptyArray($wrapper, 'book_ancestors');
+ }
+
+ /**
+ * Test properties of a comment.
+ */
+ function testComments() {
+ $title = 'Node 1';
+ $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'page'));
+ $author = $this->drupalCreateUser(array('access comments', 'post comments', 'edit own comments'));
+ $comment = (object)array(
+ 'subject' => 'topic',
+ 'nid' => $node->nid,
+ 'uid' => $author->uid,
+ 'cid' => FALSE,
+ 'pid' => 0,
+ 'homepage' => '',
+ 'language' => LANGUAGE_NONE,
+ 'hostname' => ip_address(),
+ );
+ $comment->comment_body[LANGUAGE_NONE][0] = array('value' => 'text', 'format' => 0);
+ comment_save($comment);
+ $wrapper = entity_metadata_wrapper('comment', $comment);
+ foreach ($wrapper as $key => $value) {
+ if ($key != 'parent') {
+ $this->assertValue($wrapper, $key);
+ }
+ }
+ $this->assertEmpty($wrapper, 'parent');
+
+ // Test comment entity access.
+ $admin_user = $this->drupalCreateUser(array('access comments', 'administer comments', 'access user profiles'));
+ // Also grant access to view user accounts to test the comment author
+ // property.
+ $unprivileged_user = $this->drupalCreateUser(array('access comments', 'access user profiles'));
+ // Published comments can be viewed and edited by the author.
+ $this->assertTrue($wrapper->access('view', $author), 'Comment author is allowed to view the published comment.');
+ $this->assertTrue($wrapper->access('edit', $author), 'Comment author is allowed to edit the published comment.');
+ // We cannot use $wrapper->access('delete') here because it only understands
+ // view and edit.
+ $this->assertFalse(entity_access('delete', 'comment', $comment, $author), 'Comment author is not allowed to delete the published comment.');
+
+ // Administrators can do anything with published comments.
+ $this->assertTrue($wrapper->access('view', $admin_user), 'Comment administrator is allowed to view the published comment.');
+ $this->assertTrue($wrapper->access('edit', $admin_user), 'Comment administrator is allowed to edit the published comment.');
+ $this->assertTrue(entity_access('delete', 'comment', $comment, $admin_user), 'Comment administrator is allowed to delete the published comment.');
+
+ // Unpriviledged users can only view the published comment.
+ $this->assertTrue($wrapper->access('view', $unprivileged_user), 'Unprivileged user is allowed to view the published comment.');
+ $this->assertFalse($wrapper->access('edit', $unprivileged_user), 'Unprivileged user is not allowed to edit the published comment.');
+ $this->assertFalse(entity_access('delete', 'comment', $comment, $unprivileged_user), 'Unprivileged user is not allowed to delete the published comment.');
+
+ // Test property view access.
+ $view_access = array('name', 'homepage', 'subject', 'created', 'author', 'node', 'parent', 'url', 'edit_url');
+ foreach ($view_access as $property_name) {
+ $this->assertTrue($wrapper->{$property_name}->access('view', $unprivileged_user), "Unpriviledged user can view the $property_name property.");
+ }
+
+ $view_denied = array('hostname', 'mail', 'status');
+ foreach ($view_denied as $property_name) {
+ $this->assertFalse($wrapper->{$property_name}->access('view', $unprivileged_user), "Unpriviledged user can not view the $property_name property.");
+ $this->assertTrue($wrapper->{$property_name}->access('view', $admin_user), "Admin user can view the $property_name property.");
+ }
+
+ // The author is allowed to edit the comment subject if they have the
+ // 'edit own comments' permission.
+ $this->assertTrue($wrapper->subject->access('edit', $author), "Author can edit the subject property.");
+ $this->assertFalse($wrapper->subject->access('edit', $unprivileged_user), "Unpriviledged user cannot edit the subject property.");
+ $this->assertTrue($wrapper->subject->access('edit', $admin_user), "Admin user can edit the subject property.");
+
+ $edit_denied = array('hostname', 'mail', 'status', 'name', 'homepage', 'created', 'parent', 'node', 'author');
+ foreach ($edit_denied as $property_name) {
+ $this->assertFalse($wrapper->{$property_name}->access('edit', $author), "Author cannot edit the $property_name property.");
+ $this->assertTrue($wrapper->{$property_name}->access('edit', $admin_user), "Admin user can edit the $property_name property.");
+ }
+
+ // Test access to unpublished comments.
+ $comment->status = COMMENT_NOT_PUBLISHED;
+ comment_save($comment);
+
+ // Unpublished comments cannot be accessed by the author.
+ $this->assertFalse($wrapper->access('view', $author), 'Comment author is not allowed to view the unpublished comment.');
+ $this->assertFalse($wrapper->access('edit', $author), 'Comment author is not allowed to edit the unpublished comment.');
+ $this->assertFalse(entity_access('delete', 'comment', $comment, $author), 'Comment author is not allowed to delete the unpublished comment.');
+
+ // Administrators can do anything with unpublished comments.
+ $this->assertTrue($wrapper->access('view', $admin_user), 'Comment administrator is allowed to view the unpublished comment.');
+ $this->assertTrue($wrapper->access('edit', $admin_user), 'Comment administrator is allowed to edit the unpublished comment.');
+ $this->assertTrue(entity_access('delete', 'comment', $comment, $admin_user), 'Comment administrator is allowed to delete the unpublished comment.');
+
+ // Unpriviledged users cannot access unpublished comments.
+ $this->assertFalse($wrapper->access('view', $unprivileged_user), 'Unprivileged user is not allowed to view the unpublished comment.');
+ $this->assertFalse($wrapper->access('edit', $unprivileged_user), 'Unprivileged user is not allowed to edit the unpublished comment.');
+ $this->assertFalse(entity_access('delete', 'comment', $comment, $unprivileged_user), 'Unprivileged user is not allowed to delete the unpublished comment.');
+ }
+
+ /**
+ * Test all properties of a node.
+ */
+ function testNodeProperties() {
+ $title = 'Book 1';
+ $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'page'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+ foreach ($wrapper as $key => $value) {
+ if ($key != 'book' && $key != 'book_ancestors' && $key != 'source' && $key != 'last_view') {
+ $this->assertValue($wrapper, $key);
+ }
+ }
+ $this->assertEmpty($wrapper, 'book');
+ $this->assertEmptyArray($wrapper, 'book_ancestors');
+ $this->assertEmpty($wrapper, 'source');
+ $this->assertException($wrapper->source, 'title');
+ $this->assertEmpty($wrapper, 'last_view');
+
+ // Test statistics module integration access.
+ $unpriviledged_user = $this->drupalCreateUser(array('access content'));
+ $this->assertTrue($wrapper->access('view', $unpriviledged_user), 'Unpriviledged user can view the node.');
+ $this->assertFalse($wrapper->access('edit', $unpriviledged_user), 'Unpriviledged user can not edit the node.');
+ $count_access_user = $this->drupalCreateUser(array('view post access counter'));
+ $admin_user = $this->drupalCreateUser(array('access content', 'view post access counter', 'access statistics'));
+
+ $this->assertFalse($wrapper->views->access('view', $unpriviledged_user), "Unpriviledged user cannot view the statistics counter property.");
+ $this->assertTrue($wrapper->views->access('view', $count_access_user), "Count access user can view the statistics counter property.");
+ $this->assertTrue($wrapper->views->access('view', $admin_user), "Admin user can view the statistics counter property.");
+
+ $admin_properties = array('day_views', 'last_view');
+ foreach ($admin_properties as $property_name) {
+ $this->assertFalse($wrapper->{$property_name}->access('view', $unpriviledged_user), "Unpriviledged user cannot view the $property_name property.");
+ $this->assertFalse($wrapper->{$property_name}->access('view', $count_access_user), "Count access user cannot view the $property_name property.");
+ $this->assertTrue($wrapper->{$property_name}->access('view', $admin_user), "Admin user can view the $property_name property.");
+ }
+ }
+
+ /**
+ * Tests properties provided by the taxonomy module.
+ */
+ function testTaxonomyProperties() {
+ $vocab = $this->createVocabulary();
+ $term_parent = entity_property_values_create_entity('taxonomy_term', array(
+ 'name' => $this->randomName(),
+ 'vocabulary' => $vocab,
+ ))->save()->value();
+ $term_parent2 = entity_property_values_create_entity('taxonomy_term', array(
+ 'name' => $this->randomName(),
+ 'vocabulary' => $vocab,
+ ))->save()->value();
+ $term = entity_property_values_create_entity('taxonomy_term', array(
+ 'name' => $this->randomName(),
+ 'vocabulary' => $vocab,
+ 'description' => $this->randomString(),
+ 'weight' => mt_rand(0, 10),
+ 'parent' => array($term_parent->tid),
+ ))->save()->value();
+
+ $wrapper = entity_metadata_wrapper('taxonomy_term', $term);
+ foreach ($wrapper as $key => $value) {
+ $this->assertValue($wrapper, $key);
+ }
+ // Test setting another parent using the full object.
+ $wrapper->parent[] = $term_parent2;
+ $this->assertEqual($wrapper->parent[1]->getIdentifier(), $term_parent2->tid, 'Term parent added.');
+
+ $parents = $wrapper->parent->value();
+ $tids = $term_parent->tid . ':' . $term_parent2->tid;
+ $this->assertEqual($parents[0]->tid . ':' . $parents[1]->tid, $tids, 'Parents returned.');
+ $this->assertEqual(implode(':', $wrapper->parent->value(array('identifier' => TRUE))), $tids, 'Parent ids returned.');
+
+ // Test vocabulary.
+ foreach ($wrapper->vocabulary as $key => $value) {
+ $this->assertValue($wrapper->vocabulary, $key);
+ }
+ // Test field integration.
+ $tags[LANGUAGE_NONE][0]['tid'] = $term->tid;
+ $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', 'field_tags' => $tags));
+ $wrapper = entity_metadata_wrapper('node', $node);
+ $this->assertEqual($wrapper->field_tags[0]->name->value(), $term->name, 'Get an associated tag of a node with the wrapper.');
+
+ $wrapper->field_tags[1] = $term_parent;
+ $tags = $wrapper->field_tags->value();
+ $this->assertEqual($tags[1]->tid, $term_parent->tid, 'Associated a new tag with a node.');
+ $this->assertEqual($tags[0]->tid, $term->tid, 'Previsous set association kept.');
+
+ // Test getting a list of identifiers.
+ $tags = $wrapper->field_tags->value(array('identifier' => TRUE));
+ $this->assertEqual($tags, array($term->tid, $term_parent->tid), 'List of referenced term identifiers returned.');
+
+ // Test setting tags by using ids.
+ $wrapper->field_tags->set(array(2));
+ $this->assertEqual($wrapper->field_tags[0]->tid->value(), 2, 'Specified tags by a list of term ids.');
+
+ // Test unsetting all tags.
+ $wrapper->field_tags = NULL;
+ $this->assertFalse($wrapper->field_tags->value(), 'Unset all tags from a node.');
+
+ // Test setting entity references to NULL.
+ // Create a taxonomy term field for that purpose.
+ $field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $field = array('field_name' => $field_name, 'type' => 'taxonomy_term_reference', 'cardinality' => 1);
+ $field = field_create_field($field);
+ $field_id = $field['id'];
+ $field_instance = array(
+ 'field_name' => $field_name,
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'label' => $this->randomName() . '_label',
+ 'description' => $this->randomName() . '_description',
+ 'weight' => mt_rand(0, 127),
+ 'widget' => array(
+ 'type' => 'options_select',
+ 'label' => 'Test term field',
+ )
+ );
+ field_create_instance($field_instance);
+ $term_field[LANGUAGE_NONE][0]['tid'] = $term->tid;
+ $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', $field_name => $term_field));
+ $wrapper = entity_metadata_wrapper('node', $node);
+ $wrapper->$field_name->set(NULL);
+ $termref = $wrapper->$field_name->value();
+ $this->assertNull($termref, 'Unset of a term reference successful.');
+ }
+
+ /**
+ * Test all properties of a user.
+ */
+ function testUserProperties() {
+ $account = $this->drupalCreateUser(array('access user profiles', 'change own username'));
+ $account->login = REQUEST_TIME;
+ $account->access = REQUEST_TIME;
+ $wrapper = entity_metadata_wrapper('user', $account);
+ foreach ($wrapper as $key => $value) {
+ $this->assertValue($wrapper, $key);
+ }
+
+ // Test property view access.
+ $unpriviledged_user = $this->drupalCreateUser(array('access user profiles'));
+ $admin_user = $this->drupalCreateUser(array('administer users'));
+ $this->assertTrue($wrapper->access('view', $unpriviledged_user), 'Unpriviledged account can view the user.');
+ $this->assertFalse($wrapper->access('edit', $unpriviledged_user), 'Unpriviledged account can not edit the user.');
+
+ $view_access = array('name', 'url', 'edit_url', 'created');
+ foreach ($view_access as $property_name) {
+ $this->assertTrue($wrapper->{$property_name}->access('view', $unpriviledged_user), "Unpriviledged user can view the $property_name property.");
+ }
+
+ $view_denied = array('mail', 'last_access', 'last_login', 'roles', 'status', 'theme');
+ foreach ($view_denied as $property_name) {
+ $this->assertFalse($wrapper->{$property_name}->access('view', $unpriviledged_user), "Unpriviledged user can not view the $property_name property.");
+ $this->assertTrue($wrapper->{$property_name}->access('view', $admin_user), "Admin user can view the $property_name property.");
+ }
+
+ // Test property edit access.
+ $edit_own_allowed = array('name', 'mail');
+ foreach ($edit_own_allowed as $property_name) {
+ $this->assertTrue($wrapper->{$property_name}->access('edit', $account), "Account owner can edit the $property_name property.");
+ }
+
+ $this->assertTrue($wrapper->roles->access('view', $account), "Account owner can view their own roles.");
+
+ $edit_denied = array('last_access', 'last_login', 'created', 'roles', 'status', 'theme');
+ foreach ($edit_denied as $property_name) {
+ $this->assertFalse($wrapper->{$property_name}->access('edit', $account), "Account owner cannot edit the $property_name property.");
+ $this->assertTrue($wrapper->{$property_name}->access('edit', $admin_user), "Admin user can edit the $property_name property.");
+ }
+ }
+
+ /**
+ * Test properties provided by system module.
+ */
+ function testSystemProperties() {
+ $wrapper = entity_metadata_site_wrapper();
+ foreach ($wrapper as $key => $value) {
+ $this->assertValue($wrapper, $key);
+ }
+ // Test page request related properties.
+ foreach ($wrapper->current_page as $key => $value) {
+ $this->assertValue($wrapper->current_page, $key);
+ }
+
+ // Test files.
+ $file = $this->createFile();
+
+ $wrapper = entity_metadata_wrapper('file', $file);
+ foreach ($wrapper as $key => $value) {
+ $this->assertValue($wrapper, $key);
+ }
+ }
+
+ /**
+ * Runs some generic tests on each entity.
+ */
+ function testCRUDfunctions() {
+ $info = entity_get_info();
+ foreach ($info as $entity_type => $entity_info) {
+ // Test using access callback.
+ entity_access('view', $entity_type);
+ entity_access('update', $entity_type);
+ entity_access('create', $entity_type);
+ entity_access('delete', $entity_type);
+
+ // Test creating the entity.
+ if (!isset($entity_info['creation callback'])) {
+ continue;
+ }
+
+ // Populate $values with all values that are setable. They will be set
+ // with an metadata wrapper, so we also test setting that way.
+ $values = array();
+ foreach (entity_metadata_wrapper($entity_type) as $name => $wrapper) {
+ $info = $wrapper->info();
+ if (!empty($info['setter callback'])) {
+ $values[$name] = $this->createValue($wrapper);
+ }
+ }
+ $entity = entity_property_values_create_entity($entity_type, $values)->value();
+ $this->assertTrue($entity, "Created $entity_type and set all setable values.");
+
+ // Save the new entity.
+ $return = entity_save($entity_type, $entity);
+ if ($return === FALSE) {
+ continue; // No support for saving.
+ }
+ $id = entity_metadata_wrapper($entity_type, $entity)->getIdentifier();
+ $this->assertTrue($id, "$entity_type has been successfully saved.");
+
+ // And delete it.
+ $return = entity_delete($entity_type, $id);
+ if ($return === FALSE) {
+ continue; // No support for deleting.
+ }
+ $return = entity_load_single($entity_type, $id);
+ $this->assertFalse($return, "$entity_type has been successfully deleted.");
+ }
+ }
+
+ /**
+ * Test making use of a text fields.
+ */
+ function testTextFields() {
+ // Create a simple text field without text processing.
+ $field = array(
+ 'field_name' => 'field_text',
+ 'type' => 'text',
+ 'cardinality' => 2,
+ );
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => 'field_text',
+ 'entity_type' => 'node',
+ 'label' => 'test',
+ 'bundle' => 'article',
+ 'widget' => array(
+ 'type' => 'text_textfield',
+ 'weight' => -1,
+ ),
+ );
+ field_create_instance($instance);
+
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $wrapper->field_text[0] = 'the text';
+
+ // Try saving the node and make sure the information is still there after
+ // loading the node again, thus the correct data structure has been written.
+ node_save($node);
+ $node = node_load($node->nid, NULL, TRUE);
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $this->assertEqual('the text', $wrapper->field_text[0]->value(), 'Text has been specified.');
+
+ // Now activate text processing.
+ $instance['settings']['text_processing'] = 1;
+ field_update_instance($instance);
+
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $wrapper->field_text[0]->set(array('value' => "<b>The second body.</b>"));
+ $this->assertEqual("<p>The second body.</p>\n", $wrapper->field_text[0]->value->value(), "Setting a processed text field value and reading it again.");
+
+ // Assert the summary property is correctly removed.
+ $this->assertFalse(isset($wrapper->field_text[0]->summary), 'Processed text has no summary.');
+
+ // Create a text field with summary but without text processing.
+ $field = array(
+ 'field_name' => 'field_text2',
+ 'type' => 'text_with_summary',
+ 'cardinality' => 1,
+ );
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => 'field_text2',
+ 'entity_type' => 'node',
+ 'label' => 'test',
+ 'bundle' => 'article',
+ 'settings' => array('text_processing' => 0),
+ 'widget' => array(
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -1,
+ ),
+ );
+ field_create_instance($instance);
+
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $wrapper->field_text2->summary = 'the summary';
+ $wrapper->field_text2->value = 'the text';
+
+ // Try saving the node and make sure the information is still there after
+ // loading the node again, thus the correct data structure has been written.
+ node_save($node);
+ $node = node_load($node->nid, NULL, TRUE);
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $this->assertEqual('the text', $wrapper->field_text2->value->value(), 'Text has been specified.');
+ $this->assertEqual('the summary', $wrapper->field_text2->summary->value(), 'Summary has been specified.');
+ }
+
+ /**
+ * Test making use of a file field.
+ */
+ function testFileFields() {
+ $file = $this->createFile();
+
+ // Create a file field.
+ $field = array(
+ 'field_name' => 'field_file',
+ 'type' => 'file',
+ 'cardinality' => 2,
+ 'settings' => array('display_field' => TRUE),
+ );
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => 'field_file',
+ 'entity_type' => 'node',
+ 'label' => 'File',
+ 'bundle' => 'article',
+ 'settings' => array('description_field' => TRUE),
+ 'required' => FALSE,
+ 'widget' => array(
+ 'type' => 'file_generic',
+ 'weight' => -1,
+ ),
+ );
+ field_create_instance($instance);
+
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $wrapper->field_file[0] = array('fid' => $file->fid, 'display' => FALSE);
+ $this->assertEqual($file->filename, $wrapper->field_file[0]->file->name->value(), 'File has been specified.');
+
+ $wrapper->field_file[0]->description = 'foo';
+ $wrapper->field_file[0]->display = TRUE;
+
+ $this->assertEqual($wrapper->field_file[0]->description->value(), 'foo', 'File description has been correctly set.');
+
+ // Try saving the node and make sure the information is still there after
+ // loading the node again, thus the correct data structure has been written.
+ node_save($node);
+ $node = node_load($node->nid, NULL, TRUE);
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $this->assertEqual($wrapper->field_file[0]->description->value(), 'foo', 'File description has been correctly set.');
+ $this->assertEqual($wrapper->field_file[0]->display->value(), TRUE, 'File display value has been correctly set.');
+
+ // Test adding a new file, the display-property has to be created
+ // automatically.
+ $wrapper->field_file[1]->file = $file;
+ node_save($node);
+ $node = node_load($node->nid, NULL, TRUE);
+ $this->assertEqual($file->fid, $wrapper->field_file[1]->file->getIdentifier(), 'New file has been added.');
+
+ // Test adding an invalid file-field item, i.e. without any file.
+ try {
+ $wrapper->field_file[] = array('description' => 'test');
+ $this->fail('Exception not thrown.');
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $this->pass('Not valid file-field item has thrown an exception.');
+ }
+
+ // Test remove all file-field items.
+ $wrapper->field_file = NULL;
+ $this->assertFalse($wrapper->field_file->value(), 'Removed multiple file-field items.');
+ }
+
+ /**
+ * Test making use of an image field.
+ */
+ function testImageFields() {
+ $file = $this->createFile('image');
+
+ // Just use the image field on the article node.
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $wrapper->field_image = array('fid' => $file->fid);
+ $this->assertEqual($file->filename, $wrapper->field_image->file->name->value(), 'File has been specified.');
+
+ $wrapper->field_image->alt = 'foo';
+ $this->assertEqual($wrapper->field_image->alt->value(), 'foo', 'Image alt attribute has been correctly set.');
+
+ // Try saving the node and make sure the information is still there after
+ // loading the node again, thus the correct data structure has been written.
+ node_save($node);
+ $node = node_load($node->nid, NULL, TRUE);
+ $wrapper = entity_metadata_wrapper('node', $node);
+
+ $this->assertEqual($wrapper->field_image->alt->value(), 'foo', 'File description has been correctly set.');
+
+ // Test adding a new image.
+ $wrapper->field_image->file = $file;
+ node_save($node);
+ $node = node_load($node->nid, NULL, TRUE);
+ $this->assertEqual($file->fid, $wrapper->field_image->file->getIdentifier(), 'New file has been added.');
+
+ // Test adding an invalid image-field item, i.e. without any file.
+ try {
+ $wrapper->field_image = array();
+ $this->fail('Exception not thrown.');
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $this->pass('Not valid image-field item has thrown an exception.');
+ }
+ }
+
+ /**
+ * Creates a value for the given property.
+ */
+ protected function createValue($wrapper) {
+ if (!isset($this->node)) {
+ $this->node = $this->drupalCreateNode(array('type' => 'page'));
+ $this->user = $this->drupalCreateUser();
+ $this->taxonomy_vocabulary = $this->createVocabulary();
+ }
+
+ if ($options = $wrapper->optionsList()) {
+ $options = entity_property_options_flatten($options);
+ return $wrapper instanceof EntityListWrapper ? array(key($options)) : key($options);
+ }
+
+ // For mail addresses properly pass an mail address.
+ $info = $wrapper->info();
+ if ($info['name'] == 'mail') {
+ return 'webmaster@example.com';
+ }
+
+ switch ($wrapper->type()) {
+ case 'decimal':
+ case 'integer':
+ case 'duration':
+ return 1;
+ case 'date':
+ return REQUEST_TIME;
+ case 'boolean':
+ return TRUE;
+ case 'token':
+ return drupal_strtolower($this->randomName(8));
+ case 'text':
+ return $this->randomName(32);
+ case 'text_formatted':
+ return array('value' => $this->randomName(16));
+ case 'list<taxonomy_term>':
+ return array();
+
+ default:
+ return $this->{$wrapper->type()};
+ }
+ }
+}
diff --git a/sites/all/modules/entity/entity_token.info b/sites/all/modules/entity/entity_token.info
new file mode 100644
index 000000000..f37e2a057
--- /dev/null
+++ b/sites/all/modules/entity/entity_token.info
@@ -0,0 +1,13 @@
+name = Entity tokens
+description = Provides token replacements for all properties that have no tokens and are known to the entity API.
+core = 7.x
+files[] = entity_token.tokens.inc
+files[] = entity_token.module
+dependencies[] = entity
+
+; Information added by Drupal.org packaging script on 2016-03-17
+version = "7.x-1.7"
+core = "7.x"
+project = "entity"
+datestamp = "1458222244"
+
diff --git a/sites/all/modules/entity/entity_token.module b/sites/all/modules/entity/entity_token.module
new file mode 100644
index 000000000..f0abe59e3
--- /dev/null
+++ b/sites/all/modules/entity/entity_token.module
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Module file for the entity tokens module. Drupal needs this file.
+ */
diff --git a/sites/all/modules/entity/entity_token.tokens.inc b/sites/all/modules/entity/entity_token.tokens.inc
new file mode 100644
index 000000000..77a170d72
--- /dev/null
+++ b/sites/all/modules/entity/entity_token.tokens.inc
@@ -0,0 +1,343 @@
+<?php
+
+/**
+ * @file
+ * Provides tokens for entity properties which have no token yet.
+ */
+
+/**
+ * Defines the types of properties to be added as token.
+ *
+ * @return
+ * An array mapping token types to the usual (entity) type names.
+ */
+function entity_token_types() {
+ $return = entity_token_types_chained();
+ return $return + drupal_map_assoc(array('text', 'integer', 'decimal', 'duration', 'boolean', 'uri'));
+}
+
+/**
+ * Defines a list of token types that need to be chained.
+ *
+ * @return
+ * If a (token) type is given, whether the given type needs to be chained.
+ * Else a full list of token types to be chained as returned by
+ * entity_token_token_types().
+ */
+function entity_token_types_chained($type = NULL) {
+ // This functions gets called rather often when replacing tokens, thus
+ // we statically cache $types using the advanced drupal static pattern.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['types'] = &drupal_static(__FUNCTION__, array());
+ }
+ $types = &$drupal_static_fast['types'];
+
+ if (!$types) {
+ // Add entities.
+ foreach (entity_get_info() as $entity_type => $info) {
+ if ($token_type = isset($info['token type']) ? $info['token type'] : $entity_type) {
+ $types[$token_type] = $entity_type;
+ }
+ }
+ // Add 'date' and 'site' tokens.
+ $types['date'] = 'date';
+ $types['site'] = 'site';
+ // Add a 'struct' type.
+ $types['struct'] = 'struct';
+ }
+
+ if (isset($type)) {
+ return isset($types[$type]) || entity_property_list_extract_type($type);
+ }
+ return $types;
+}
+
+/**
+ * Gets the right token type for a given property info array.
+ */
+function _entity_token_map_to_token_type($property_info) {
+ $lookup = &drupal_static(__FUNCTION__);
+
+ if (!$lookup) {
+ // Initialize a lookup array mapping property types to token types.
+ $lookup = array_flip(entity_token_types());
+ }
+
+ $type = isset($property_info['type']) ? $property_info['type'] : 'text';
+ // Just use the type 'struct' for all structures.
+ if (!empty($property_info['property info'])) {
+ $type = 'struct';
+ }
+
+ if ($item_type = entity_property_list_extract_type($type)) {
+ return isset($lookup[$item_type]) ? "list<$lookup[$item_type]>" : FALSE;
+ }
+ return isset($lookup[$type]) ? $lookup[$type] : FALSE;
+}
+
+/**
+ * Implements hook_token_info_alter().
+ */
+function entity_token_token_info_alter(&$info) {
+ $entity_info = entity_get_info();
+ $token_types = entity_token_types_chained();
+
+ // Loop over all chain-able token types, as those may contain further tokens,
+ // e.g. entity types or 'site'.
+ foreach ($token_types as $token_type => $type) {
+ // Just add all properties regardless whether it's in a bundle, but only if
+ // there is no token of the property yet.
+ foreach (entity_get_all_property_info($type) as $name => $property) {
+ $name = str_replace('_', '-', $name);
+ $property += array('type' => 'text', 'description' => $property['label']);
+ $property_token_type = _entity_token_map_to_token_type($property);
+
+ if (!isset($info['tokens'][$token_type][$name]) && $property_token_type) {
+
+ $info['tokens'][$token_type][$name] = array(
+ 'name' => $property['label'],
+ 'description' => $property['description'],
+ 'type' => $property_token_type,
+ // Mark the token so we know we have to provide the value afterwards.
+ 'entity-token' => TRUE,
+ );
+ }
+ if ($property_token_type == 'struct' && !empty($property['property info'])) {
+ $info['tokens'][$token_type][$name]['dynamic'] = TRUE;
+ $help = array();
+ foreach ($property['property info'] as $key => $property_info) {
+ $help[] = $key . ' (' . $property_info['label'] . ')';
+ }
+ $info['tokens'][$token_type][$name]['description'] .= ' ' . t('The following properties may be appended to the token: @keys',
+ array('@keys' => implode(', ', $help))
+ );
+ }
+ }
+ }
+
+ // Make sure all chain-able token types we support are registered.
+ foreach ($token_types as $token_type => $type) {
+
+ if (!empty($info['tokens'][$token_type]) && !isset($info['types'][$token_type])) {
+ if (isset($entity_info[$type])) {
+ $info['types'][$token_type] = array(
+ 'name' => $entity_info[$type]['label'],
+ 'description' => t('Tokens related to the "@name" entities.', array('@name' => $entity_info[$type]['label'])),
+ 'needs-data' => $token_type,
+ );
+ }
+ else {
+ $info['types'][$token_type] = array(
+ 'name' => drupal_strtoupper($token_type),
+ 'description' => t('@name tokens.', array('@name' => drupal_strtoupper($token_type))),
+ 'needs-data' => $token_type,
+ );
+ }
+ }
+ if (!empty($info['tokens'][$token_type]) && !isset($info['types']["list<$token_type>"]) && $token_type != 'site') {
+ if (isset($entity_info[$type])) {
+ $info['types']["list<$token_type>"] = array(
+ 'name' => t('List of @entities', array('@entities' => isset($entity_info[$type]['plural label']) ? $entity_info[$type]['plural label'] : $entity_info[$type]['label'] . 's')),
+ 'description' => t('Tokens related to the "@name" entities.', array('@name' => $entity_info[$type]['label'])),
+ 'needs-data' => "list<$token_type>",
+ );
+ }
+ else {
+ $info['types']["list<$token_type>"] = array(
+ 'name' => t('List of @type values', array('@type' => $token_type)),
+ 'description' => t('Tokens for lists of @type values.', array('@type' => $token_type)),
+ 'needs-data' => "list<$token_type>",
+ );
+ }
+ // Also add some basic token replacements for lists...
+ for ($i = 0; $i < 4; $i++) {
+ $info['tokens']["list<$token_type>"][$i] = array(
+ 'name' => t('@type with delta @delta', array('@delta' => $i, '@type' => $info['types'][$token_type]['name'])),
+ 'description' => t('The list item with delta @delta. Delta values start from 0 and are incremented by one per list item.', array('@delta' => $i)),
+ 'type' => $token_type,
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function entity_token_tokens($type, $tokens, array $data = array(), array $options = array()) {
+ $token_types = entity_token_types_chained();
+ $replacements = array();
+
+ if (isset($token_types[$type]) && (!empty($data[$type]) || $type == 'site')) {
+ $data += array($type => FALSE);
+
+ // Make use of token module's token cache if available.
+ $info = module_exists('token') ? token_get_info() : token_info();
+ foreach ($tokens as $name => $original) {
+ // Provide the token for all properties marked to stem from us.
+ if (!empty($info['tokens'][$type][$name]['entity-token']) || $type == 'struct') {
+ $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper;
+ $property_name = str_replace('-', '_', $name);
+ try {
+ if (isset($wrapper->$property_name)) {
+ $replacement = _entity_token_get_token($wrapper->$property_name, $options);
+ if (isset($replacement)) {
+ $replacements[$original] = $replacement;
+ }
+ }
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // If tokens for not existing values are requested, just do nothing.
+ }
+ }
+ }
+
+ // Properly chain everything of a type marked as needs chaining.
+ $info['tokens'] += array($type => array());
+ foreach ($info['tokens'][$type] as $name => $token_info) {
+ if (!empty($token_info['entity-token']) && isset($token_info['type']) && entity_token_types_chained($token_info['type'])) {
+
+ if ($chained_tokens = token_find_with_prefix($tokens, $name)) {
+ $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper;
+ $property_name = str_replace('-', '_', $name);
+
+ try {
+ // Pass on 'struct' properties wrapped, else un-wrap the data.
+ $value = ($token_info['type'] == 'struct') ? $wrapper->$property_name : $wrapper->$property_name->value();
+ $replacements += token_generate($token_info['type'], $chained_tokens, array($token_info['type'] => $value), $options);
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // If tokens for not existing values are requested, just do nothing.
+ }
+ }
+ }
+ }
+ }
+ // Add support for evaluating tokens for "list<type"> types.
+ elseif ($item_token_type = entity_property_list_extract_type($type)) {
+ foreach ($tokens as $name => $original) {
+ // Care about getting entries of a list.
+ if (is_numeric($name)) {
+ $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, "list<$token_types[$item_token_type]>", $data[$type], $options) : $wrapper;
+ try {
+ $replacement = _entity_token_get_token($wrapper->get($name), $options);
+ if (isset($replacement)) {
+ $replacements[$original] = $replacement;
+ }
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // If tokens for not existing values are requested, just do nothing.
+ }
+ }
+ // Care about generating chained tokens for list-items.
+ else {
+ $parts = explode(':', $name, 2);
+ $delta = $parts[0];
+
+ if (is_numeric($delta) && $chained_tokens = token_find_with_prefix($tokens, $delta)) {
+ $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, "list<$token_types[$item_token_type]>", $data[$type], $options) : $wrapper;
+ try {
+ $replacements += token_generate($item_token_type, $chained_tokens, array($item_token_type => $wrapper->get($delta)->value()), $options);
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // If tokens for not existing values are requested, just do nothing.
+ }
+ }
+ }
+ }
+ }
+
+ // Add support for chaining struct data. As struct data has no registered
+ // tokens, we have to chain based upon wrapper property info.
+ if ($type == 'struct') {
+ $wrapper = $data[$type];
+ foreach ($wrapper as $name => $property) {
+ $token_type = _entity_token_map_to_token_type($property->info());
+
+ if (entity_token_types_chained($token_type) && $chained_tokens = token_find_with_prefix($tokens, $name)) {
+ try {
+ // Pass on 'struct' properties wrapped, else un-wrap the data.
+ $value = ($token_type == 'struct') ? $property : $property->value();
+ $replacements += token_generate($token_type, $chained_tokens, array($token_type => $value), $options);
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // If tokens for not existing values are requested, just do nothing.
+ }
+ }
+ }
+ }
+
+ return $replacements;
+}
+
+/**
+ * Wraps the given data by correctly obeying the options.
+ */
+function _entity_token_wrap_data($token_type, $type, $data, $options) {
+ if ($type == 'site') {
+ $wrapper = entity_metadata_site_wrapper();
+ }
+ elseif ($type == 'struct') {
+ // 'struct' data items are passed on wrapped.
+ $wrapper = $data;
+ }
+ else {
+ $wrapper = entity_metadata_wrapper($type, $data);
+ }
+ if (isset($options['language']) && $wrapper instanceof EntityStructureWrapper) {
+ $wrapper->language($options['language']->language);
+ }
+ return $wrapper;
+}
+
+/**
+ * Gets the token replacement by correctly obeying the options.
+ */
+function _entity_token_get_token($wrapper, $options) {
+
+ if (!$wrapper || $wrapper->value() === NULL) {
+ // Do not provide a replacement if there is no value.
+ return NULL;
+ }
+
+ if (empty($options['sanitize'])) {
+ // When we don't need sanitized tokens decode already sanitizied texts.
+ $options['decode'] = TRUE;
+ }
+ $langcode = isset($options['language']) ? $options['language']->language : NULL;
+
+ // If there is a label for a property, e.g. defined by an options list or an
+ // entity label, make use of it.
+ if ($label = $wrapper->label()) {
+ return empty($options['sanitize']) ? $label : check_plain($label);
+ }
+
+ switch ($wrapper->type()) {
+ case 'integer':
+ return $wrapper->value();
+ case 'decimal':
+ return number_format($wrapper->value(), 2);
+ case 'date':
+ return format_date($wrapper->value(), 'medium', '', NULL, $langcode);
+ case 'duration':
+ return format_interval($wrapper->value(), 2, $langcode);
+ case 'boolean':
+ return $wrapper->value() ? t('true') : t('false');
+ case 'uri':
+ case 'text':
+ return $wrapper->value($options);
+ }
+
+ // Care for outputing list values.
+ if ($wrapper instanceof EntityListWrapper) {
+ $output = array();
+ foreach ($wrapper as $item) {
+ $output[] = _entity_token_get_token($item, $options);
+ }
+ return implode(', ', $output);
+ }
+ // Else we do not have a good string to output, e.g. for struct values. Just
+ // output the string representation of the wrapper.
+ return (string) $wrapper;
+}
diff --git a/sites/all/modules/entity/includes/entity.controller.inc b/sites/all/modules/entity/includes/entity.controller.inc
new file mode 100644
index 000000000..f675a63a4
--- /dev/null
+++ b/sites/all/modules/entity/includes/entity.controller.inc
@@ -0,0 +1,981 @@
+<?php
+
+/**
+ * @file
+ * Provides a controller building upon the core controller but providing more
+ * features like full CRUD functionality.
+ */
+
+/**
+ * Interface for EntityControllers compatible with the entity API.
+ */
+interface EntityAPIControllerInterface extends DrupalEntityControllerInterface {
+
+ /**
+ * Delete permanently saved entities.
+ *
+ * In case of failures, an exception is thrown.
+ *
+ * @param $ids
+ * An array of entity IDs.
+ */
+ public function delete($ids);
+
+ /**
+ * Invokes a hook on behalf of the entity. For hooks that have a respective
+ * field API attacher like insert/update/.. the attacher is called too.
+ */
+ public function invoke($hook, $entity);
+
+ /**
+ * Permanently saves the given entity.
+ *
+ * In case of failures, an exception is thrown.
+ *
+ * @param $entity
+ * The entity to save.
+ *
+ * @return
+ * SAVED_NEW or SAVED_UPDATED is returned depending on the operation
+ * performed.
+ */
+ public function save($entity);
+
+ /**
+ * Create a new entity.
+ *
+ * @param array $values
+ * An array of values to set, keyed by property name.
+ * @return
+ * A new instance of the entity type.
+ */
+ public function create(array $values = array());
+
+ /**
+ * Exports an entity as serialized string.
+ *
+ * @param $entity
+ * The entity to export.
+ * @param $prefix
+ * An optional prefix for each line.
+ *
+ * @return
+ * The exported entity as serialized string. The format is determined by
+ * the controller and has to be compatible with the format that is accepted
+ * by the import() method.
+ */
+ public function export($entity, $prefix = '');
+
+ /**
+ * Imports an entity from a string.
+ *
+ * @param string $export
+ * An exported entity as serialized string.
+ *
+ * @return
+ * An entity object not yet saved.
+ */
+ public function import($export);
+
+ /**
+ * Builds a structured array representing the entity's content.
+ *
+ * The content built for the entity will vary depending on the $view_mode
+ * parameter.
+ *
+ * @param $entity
+ * An entity object.
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ * @return
+ * The renderable array.
+ */
+ public function buildContent($entity, $view_mode = 'full', $langcode = NULL);
+
+ /**
+ * Generate an array for rendering the given entities.
+ *
+ * @param $entities
+ * An array of entities to render.
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ * @param $page
+ * (optional) If set will control if the entity is rendered: if TRUE
+ * the entity will be rendered without its title, so that it can be embeded
+ * in another context. If FALSE the entity will be displayed with its title
+ * in a mode suitable for lists.
+ * If unset, the page mode will be enabled if the current path is the URI
+ * of the entity, as returned by entity_uri().
+ * This parameter is only supported for entities which controller is a
+ * EntityAPIControllerInterface.
+ * @return
+ * The renderable array, keyed by entity name or numeric id.
+ */
+ public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL);
+}
+
+/**
+ * Interface for EntityControllers of entities that support revisions.
+ */
+interface EntityAPIControllerRevisionableInterface extends EntityAPIControllerInterface {
+
+ /**
+ * Delete an entity revision.
+ *
+ * Note that the default revision of an entity cannot be deleted.
+ *
+ * @param $revision_id
+ * The ID of the revision to delete.
+ *
+ * @return boolean
+ * TRUE if the entity revision could be deleted, FALSE otherwise.
+ */
+ public function deleteRevision($revision_id);
+
+}
+
+/**
+ * A controller implementing EntityAPIControllerInterface for the database.
+ */
+class EntityAPIController extends DrupalDefaultEntityController implements EntityAPIControllerRevisionableInterface {
+
+ protected $cacheComplete = FALSE;
+ protected $bundleKey;
+ protected $defaultRevisionKey;
+
+ /**
+ * Overridden.
+ * @see DrupalDefaultEntityController#__construct()
+ */
+ public function __construct($entityType) {
+ parent::__construct($entityType);
+ // If this is the bundle of another entity, set the bundle key.
+ if (isset($this->entityInfo['bundle of'])) {
+ $info = entity_get_info($this->entityInfo['bundle of']);
+ $this->bundleKey = $info['bundle keys']['bundle'];
+ }
+ $this->defaultRevisionKey = !empty($this->entityInfo['entity keys']['default revision']) ? $this->entityInfo['entity keys']['default revision'] : 'default_revision';
+ }
+
+ /**
+ * Overrides DrupalDefaultEntityController::buildQuery().
+ */
+ protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+ $query = parent::buildQuery($ids, $conditions, $revision_id);
+ if ($this->revisionKey) {
+ // Compare revision id of the base and revision table, if equal then this
+ // is the default revision.
+ $query->addExpression('CASE WHEN base.' . $this->revisionKey . ' = revision.' . $this->revisionKey . ' THEN 1 ELSE 0 END', $this->defaultRevisionKey);
+ }
+ return $query;
+ }
+
+ /**
+ * Builds and executes the query for loading.
+ *
+ * @return The results in a Traversable object.
+ */
+ public function query($ids, $conditions, $revision_id = FALSE) {
+ // Build the query.
+ $query = $this->buildQuery($ids, $conditions, $revision_id);
+ $result = $query->execute();
+ if (!empty($this->entityInfo['entity class'])) {
+ $result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
+ }
+ return $result;
+ }
+
+ /**
+ * Overridden.
+ * @see DrupalDefaultEntityController#load($ids, $conditions)
+ *
+ * In contrast to the parent implementation we factor out query execution, so
+ * fetching can be further customized easily.
+ */
+ public function load($ids = array(), $conditions = array()) {
+ $entities = array();
+
+ // Revisions are not statically cached, and require a different query to
+ // other conditions, so separate the revision id into its own variable.
+ if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
+ $revision_id = $conditions[$this->revisionKey];
+ unset($conditions[$this->revisionKey]);
+ }
+ else {
+ $revision_id = FALSE;
+ }
+
+ // Create a new variable which is either a prepared version of the $ids
+ // array for later comparison with the entity cache, or FALSE if no $ids
+ // were passed. The $ids array is reduced as items are loaded from cache,
+ // and we need to know if it's empty for this reason to avoid querying the
+ // database when all requested entities are loaded from cache.
+ $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
+
+ // Try to load entities from the static cache.
+ if ($this->cache && !$revision_id) {
+ $entities = $this->cacheGet($ids, $conditions);
+ // If any entities were loaded, remove them from the ids still to load.
+ if ($passed_ids) {
+ $ids = array_keys(array_diff_key($passed_ids, $entities));
+ }
+ }
+
+ // Support the entitycache module if activated.
+ if (!empty($this->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) {
+ $cached_entities = EntityCacheControllerHelper::entityCacheGet($this, $ids, $conditions);
+ // If any entities were loaded, remove them from the ids still to load.
+ $ids = array_diff($ids, array_keys($cached_entities));
+ $entities += $cached_entities;
+
+ // Add loaded entities to the static cache if we are not loading a
+ // revision.
+ if ($this->cache && !empty($cached_entities) && !$revision_id) {
+ $this->cacheSet($cached_entities);
+ }
+ }
+
+ // Load any remaining entities from the database. This is the case if $ids
+ // is set to FALSE (so we load all entities), if there are any ids left to
+ // load or if loading a revision.
+ if (!($this->cacheComplete && $ids === FALSE && !$conditions) && ($ids === FALSE || $ids || $revision_id)) {
+ $queried_entities = array();
+ foreach ($this->query($ids, $conditions, $revision_id) as $record) {
+ // Skip entities already retrieved from cache.
+ if (isset($entities[$record->{$this->idKey}])) {
+ continue;
+ }
+
+ // For DB-based entities take care of serialized columns.
+ if (!empty($this->entityInfo['base table'])) {
+ $schema = drupal_get_schema($this->entityInfo['base table']);
+
+ foreach ($schema['fields'] as $field => $info) {
+ if (!empty($info['serialize']) && isset($record->$field)) {
+ $record->$field = unserialize($record->$field);
+ // Support automatic merging of 'data' fields into the entity.
+ if (!empty($info['merge']) && is_array($record->$field)) {
+ foreach ($record->$field as $key => $value) {
+ $record->$key = $value;
+ }
+ unset($record->$field);
+ }
+ }
+ }
+ }
+
+ $queried_entities[$record->{$this->idKey}] = $record;
+ }
+ }
+
+ // Pass all entities loaded from the database through $this->attachLoad(),
+ // which attaches fields (if supported by the entity type) and calls the
+ // entity type specific load callback, for example hook_node_load().
+ if (!empty($queried_entities)) {
+ $this->attachLoad($queried_entities, $revision_id);
+ $entities += $queried_entities;
+ }
+
+ // Entitycache module support: Add entities to the entity cache if we are
+ // not loading a revision.
+ if (!empty($this->entityInfo['entity cache']) && !empty($queried_entities) && !$revision_id) {
+ EntityCacheControllerHelper::entityCacheSet($this, $queried_entities);
+ }
+
+ if ($this->cache) {
+ // Add entities to the cache if we are not loading a revision.
+ if (!empty($queried_entities) && !$revision_id) {
+ $this->cacheSet($queried_entities);
+
+ // Remember if we have cached all entities now.
+ if (!$conditions && $ids === FALSE) {
+ $this->cacheComplete = TRUE;
+ }
+ }
+ }
+ // Ensure that the returned array is ordered the same as the original
+ // $ids array if this was passed in and remove any invalid ids.
+ if ($passed_ids && $passed_ids = array_intersect_key($passed_ids, $entities)) {
+ foreach ($passed_ids as $id => $value) {
+ $passed_ids[$id] = $entities[$id];
+ }
+ $entities = $passed_ids;
+ }
+ return $entities;
+ }
+
+ /**
+ * Overrides DrupalDefaultEntityController::resetCache().
+ */
+ public function resetCache(array $ids = NULL) {
+ $this->cacheComplete = FALSE;
+ parent::resetCache($ids);
+ // Support the entitycache module.
+ if (!empty($this->entityInfo['entity cache'])) {
+ EntityCacheControllerHelper::resetEntityCache($this, $ids);
+ }
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ */
+ public function invoke($hook, $entity) {
+ // entity_revision_delete() invokes hook_entity_revision_delete() and
+ // hook_field_attach_delete_revision() just as node module does. So we need
+ // to adjust the name of our revision deletion field attach hook in order to
+ // stick to this pattern.
+ $field_attach_hook = ($hook == 'revision_delete' ? 'delete_revision' : $hook);
+ if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $field_attach_hook)) {
+ $function($this->entityType, $entity);
+ }
+
+ if (!empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of'])) {
+ $type = $this->entityInfo['bundle of'];
+ // Call field API bundle attachers for the entity we are a bundle of.
+ if ($hook == 'insert') {
+ field_attach_create_bundle($type, $entity->{$this->bundleKey});
+ }
+ elseif ($hook == 'delete') {
+ field_attach_delete_bundle($type, $entity->{$this->bundleKey});
+ }
+ elseif ($hook == 'update' && $entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
+ field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
+ }
+ }
+ // Invoke the hook.
+ module_invoke_all($this->entityType . '_' . $hook, $entity);
+ // Invoke the respective entity level hook.
+ if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
+ module_invoke_all('entity_' . $hook, $entity, $this->entityType);
+ }
+ // Invoke rules.
+ if (module_exists('rules')) {
+ rules_invoke_event($this->entityType . '_' . $hook, $entity);
+ }
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ *
+ * @param $transaction
+ * Optionally a DatabaseTransaction object to use. Allows overrides to pass
+ * in their transaction object.
+ */
+ public function delete($ids, DatabaseTransaction $transaction = NULL) {
+ $entities = $ids ? $this->load($ids) : FALSE;
+ if (!$entities) {
+ // Do nothing, in case invalid or no ids have been passed.
+ return;
+ }
+ $transaction = isset($transaction) ? $transaction : db_transaction();
+
+ try {
+ $ids = array_keys($entities);
+
+ db_delete($this->entityInfo['base table'])
+ ->condition($this->idKey, $ids, 'IN')
+ ->execute();
+
+ if (isset($this->revisionTable)) {
+ db_delete($this->revisionTable)
+ ->condition($this->idKey, $ids, 'IN')
+ ->execute();
+ }
+ // Reset the cache as soon as the changes have been applied.
+ $this->resetCache($ids);
+
+ foreach ($entities as $id => $entity) {
+ $this->invoke('delete', $entity);
+ }
+ // Ignore slave server temporarily.
+ db_ignore_slave();
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception($this->entityType, $e);
+ throw $e;
+ }
+ }
+
+ /**
+ * Implements EntityAPIControllerRevisionableInterface::deleteRevision().
+ */
+ public function deleteRevision($revision_id) {
+ if ($entity_revision = entity_revision_load($this->entityType, $revision_id)) {
+ // Prevent deleting the default revision.
+ if (entity_revision_is_default($this->entityType, $entity_revision)) {
+ return FALSE;
+ }
+
+ db_delete($this->revisionTable)
+ ->condition($this->revisionKey, $revision_id)
+ ->execute();
+
+ $this->invoke('revision_delete', $entity_revision);
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ *
+ * @param $transaction
+ * Optionally a DatabaseTransaction object to use. Allows overrides to pass
+ * in their transaction object.
+ */
+ public function save($entity, DatabaseTransaction $transaction = NULL) {
+ $transaction = isset($transaction) ? $transaction : db_transaction();
+ try {
+ // Load the stored entity, if any.
+ if (!empty($entity->{$this->idKey}) && !isset($entity->original)) {
+ // In order to properly work in case of name changes, load the original
+ // entity using the id key if it is available.
+ $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey});
+ }
+ $entity->is_new = !empty($entity->is_new) || empty($entity->{$this->idKey});
+ $this->invoke('presave', $entity);
+
+ if ($entity->is_new) {
+ $return = drupal_write_record($this->entityInfo['base table'], $entity);
+ if ($this->revisionKey) {
+ $this->saveRevision($entity);
+ }
+ $this->invoke('insert', $entity);
+ }
+ else {
+ // Update the base table if the entity doesn't have revisions or
+ // we are updating the default revision.
+ if (!$this->revisionKey || !empty($entity->{$this->defaultRevisionKey})) {
+ $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
+ }
+ if ($this->revisionKey) {
+ $return = $this->saveRevision($entity);
+ }
+ $this->resetCache(array($entity->{$this->idKey}));
+ $this->invoke('update', $entity);
+
+ // Field API always saves as default revision, so if the revision saved
+ // is not default we have to restore the field values of the default
+ // revision now by invoking field_attach_update() once again.
+ if ($this->revisionKey && !$entity->{$this->defaultRevisionKey} && !empty($this->entityInfo['fieldable'])) {
+ field_attach_update($this->entityType, $entity->original);
+ }
+ }
+
+ // Ignore slave server temporarily.
+ db_ignore_slave();
+ unset($entity->is_new);
+ unset($entity->is_new_revision);
+ unset($entity->original);
+
+ return $return;
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception($this->entityType, $e);
+ throw $e;
+ }
+ }
+
+ /**
+ * Saves an entity revision.
+ *
+ * @param Entity $entity
+ * Entity revision to save.
+ */
+ protected function saveRevision($entity) {
+ // Convert the entity into an array as it might not have the same properties
+ // as the entity, it is just a raw structure.
+ $record = (array) $entity;
+ // File fields assumes we are using $entity->revision instead of
+ // $entity->is_new_revision, so we also support it and make sure it's set to
+ // the same value.
+ $entity->is_new_revision = !empty($entity->is_new_revision) || !empty($entity->revision) || $entity->is_new;
+ $entity->revision = &$entity->is_new_revision;
+ $entity->{$this->defaultRevisionKey} = !empty($entity->{$this->defaultRevisionKey}) || $entity->is_new;
+
+
+
+ // When saving a new revision, set any existing revision ID to NULL so as to
+ // ensure that a new revision will actually be created.
+ if ($entity->is_new_revision && isset($record[$this->revisionKey])) {
+ $record[$this->revisionKey] = NULL;
+ }
+
+ if ($entity->is_new_revision) {
+ drupal_write_record($this->revisionTable, $record);
+ $update_default_revision = $entity->{$this->defaultRevisionKey};
+ }
+ else {
+ drupal_write_record($this->revisionTable, $record, $this->revisionKey);
+ // @todo: Fix original entity to be of the same revision and check whether
+ // the default revision key has been set.
+ $update_default_revision = $entity->{$this->defaultRevisionKey} && $entity->{$this->revisionKey} != $entity->original->{$this->revisionKey};
+ }
+ // Make sure to update the new revision key for the entity.
+ $entity->{$this->revisionKey} = $record[$this->revisionKey];
+
+ // Mark this revision as the default one.
+ if ($update_default_revision) {
+ db_update($this->entityInfo['base table'])
+ ->fields(array($this->revisionKey => $record[$this->revisionKey]))
+ ->condition($this->idKey, $entity->{$this->idKey})
+ ->execute();
+ }
+ return $entity->is_new_revision ? SAVED_NEW : SAVED_UPDATED;
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ */
+ public function create(array $values = array()) {
+ // Add is_new property if it is not set.
+ $values += array('is_new' => TRUE);
+ if (isset($this->entityInfo['entity class']) && $class = $this->entityInfo['entity class']) {
+ return new $class($values, $this->entityType);
+ }
+ return (object) $values;
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ *
+ * @return
+ * A serialized string in JSON format suitable for the import() method.
+ */
+ public function export($entity, $prefix = '') {
+ $vars = get_object_vars($entity);
+ unset($vars['is_new']);
+ return entity_var_json_export($vars, $prefix);
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ *
+ * @param $export
+ * A serialized string in JSON format as produced by the export() method.
+ */
+ public function import($export) {
+ $vars = drupal_json_decode($export);
+ if (is_array($vars)) {
+ return $this->create($vars);
+ }
+ return FALSE;
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ *
+ * @param $content
+ * Optionally. Allows pre-populating the built content to ease overridding
+ * this method.
+ */
+ public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {
+ // Remove previously built content, if exists.
+ $entity->content = $content;
+ $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
+
+ // Allow modules to change the view mode.
+ $context = array(
+ 'entity_type' => $this->entityType,
+ 'entity' => $entity,
+ 'langcode' => $langcode,
+ );
+ drupal_alter('entity_view_mode', $view_mode, $context);
+ // Make sure the used view-mode gets stored.
+ $entity->content += array('#view_mode' => $view_mode);
+
+ // By default add in properties for all defined extra fields.
+ if ($extra_field_controller = entity_get_extra_fields_controller($this->entityType)) {
+ $wrapper = entity_metadata_wrapper($this->entityType, $entity);
+ $extra = $extra_field_controller->fieldExtraFields();
+ $type_extra = &$extra[$this->entityType][$this->entityType]['display'];
+ $bundle_extra = &$extra[$this->entityType][$wrapper->getBundle()]['display'];
+
+ foreach ($wrapper as $name => $property) {
+ if (isset($type_extra[$name]) || isset($bundle_extra[$name])) {
+ $this->renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, $entity->content);
+ }
+ }
+ }
+
+ // Add in fields.
+ if (!empty($this->entityInfo['fieldable'])) {
+ // Perform the preparation tasks if they have not been performed yet.
+ // An internal flag prevents the operation from running twice.
+ $key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
+ field_attach_prepare_view($this->entityType, array($key => $entity), $view_mode);
+ $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
+ }
+ // Invoke hook_ENTITY_view() to allow modules to add their additions.
+ if (module_exists('rules')) {
+ rules_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
+ }
+ else {
+ module_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
+ }
+ module_invoke_all('entity_view', $entity, $this->entityType, $view_mode, $langcode);
+ $build = $entity->content;
+ unset($entity->content);
+ return $build;
+ }
+
+ /**
+ * Renders a single entity property.
+ */
+ protected function renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, &$content) {
+ $info = $property->info();
+
+ $content[$name] = array(
+ '#label_hidden' => FALSE,
+ '#label' => $info['label'],
+ '#entity_wrapped' => $wrapper,
+ '#theme' => 'entity_property',
+ '#property_name' => $name,
+ '#access' => $property->access('view'),
+ '#entity_type' => $this->entityType,
+ );
+ $content['#attached']['css']['entity.theme'] = drupal_get_path('module', 'entity') . '/theme/entity.theme.css';
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ */
+ public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
+ // For Field API and entity_prepare_view, the entities have to be keyed by
+ // (numeric) id.
+ $entities = entity_key_array_by_property($entities, $this->idKey);
+ if (!empty($this->entityInfo['fieldable'])) {
+ field_attach_prepare_view($this->entityType, $entities, $view_mode);
+ }
+ entity_prepare_view($this->entityType, $entities);
+ $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
+
+ $view = array();
+ foreach ($entities as $entity) {
+ $build = entity_build_content($this->entityType, $entity, $view_mode, $langcode);
+ $build += array(
+ // If the entity type provides an implementation, use this instead the
+ // generic one.
+ // @see template_preprocess_entity()
+ '#theme' => 'entity',
+ '#entity_type' => $this->entityType,
+ '#entity' => $entity,
+ '#view_mode' => $view_mode,
+ '#language' => $langcode,
+ '#page' => $page,
+ );
+ // Allow modules to modify the structured entity.
+ drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType);
+ $key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
+ $view[$this->entityType][$key] = $build;
+ }
+ return $view;
+ }
+}
+
+/**
+ * A controller implementing exportables stored in the database.
+ */
+class EntityAPIControllerExportable extends EntityAPIController {
+
+ protected $entityCacheByName = array();
+ protected $nameKey, $statusKey, $moduleKey;
+
+ /**
+ * Overridden.
+ *
+ * Allows specifying a name key serving as uniform identifier for this entity
+ * type while still internally we are using numeric identifieres.
+ */
+ public function __construct($entityType) {
+ parent::__construct($entityType);
+ // Use the name key as primary identifier.
+ $this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey;
+ if (!empty($this->entityInfo['exportable'])) {
+ $this->statusKey = isset($this->entityInfo['entity keys']['status']) ? $this->entityInfo['entity keys']['status'] : 'status';
+ $this->moduleKey = isset($this->entityInfo['entity keys']['module']) ? $this->entityInfo['entity keys']['module'] : 'module';
+ }
+ }
+
+ /**
+ * Support loading by name key.
+ */
+ protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+ // Add the id condition ourself, as we might have a separate name key.
+ $query = parent::buildQuery(array(), $conditions, $revision_id);
+ if ($ids) {
+ // Support loading by numeric ids as well as by machine names.
+ $key = is_numeric(reset($ids)) ? $this->idKey : $this->nameKey;
+ $query->condition("base.$key", $ids, 'IN');
+ }
+ return $query;
+ }
+
+ /**
+ * Overridden to support passing numeric ids as well as names as $ids.
+ */
+ public function load($ids = array(), $conditions = array()) {
+ $entities = array();
+
+ // Only do something if loaded by names.
+ if (!$ids || $this->nameKey == $this->idKey || is_numeric(reset($ids))) {
+ return parent::load($ids, $conditions);
+ }
+
+ // Revisions are not statically cached, and require a different query to
+ // other conditions, so separate the revision id into its own variable.
+ if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
+ $revision_id = $conditions[$this->revisionKey];
+ unset($conditions[$this->revisionKey]);
+ }
+ else {
+ $revision_id = FALSE;
+ }
+ $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
+
+ // Care about the static cache.
+ if ($this->cache && !$revision_id) {
+ $entities = $this->cacheGetByName($ids, $conditions);
+ }
+ // If any entities were loaded, remove them from the ids still to load.
+ if ($entities) {
+ $ids = array_keys(array_diff_key($passed_ids, $entities));
+ }
+
+ $entities_by_id = parent::load($ids, $conditions);
+ $entities += entity_key_array_by_property($entities_by_id, $this->nameKey);
+
+ // Ensure that the returned array is keyed by numeric id and ordered the
+ // same as the original $ids array and remove any invalid ids.
+ $return = array();
+ foreach ($passed_ids as $name => $value) {
+ if (isset($entities[$name])) {
+ $return[$entities[$name]->{$this->idKey}] = $entities[$name];
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Overridden.
+ * @see DrupalDefaultEntityController::cacheGet()
+ */
+ protected function cacheGet($ids, $conditions = array()) {
+ if (!empty($this->entityCache) && $ids !== array()) {
+ $entities = $ids ? array_intersect_key($this->entityCache, array_flip($ids)) : $this->entityCache;
+ return $this->applyConditions($entities, $conditions);
+ }
+ return array();
+ }
+
+ /**
+ * Like cacheGet() but keyed by name.
+ */
+ protected function cacheGetByName($names, $conditions = array()) {
+ if (!empty($this->entityCacheByName) && $names !== array() && $names) {
+ // First get the entities by ids, then apply the conditions.
+ // Generally, we make use of $this->entityCache, but if we are loading by
+ // name, we have to use $this->entityCacheByName.
+ $entities = array_intersect_key($this->entityCacheByName, array_flip($names));
+ return $this->applyConditions($entities, $conditions);
+ }
+ return array();
+ }
+
+ protected function applyConditions($entities, $conditions = array()) {
+ if ($conditions) {
+ foreach ($entities as $key => $entity) {
+ $entity_values = (array) $entity;
+ // We cannot use array_diff_assoc() here because condition values can
+ // also be arrays, e.g. '$conditions = array('status' => array(1, 2))'
+ foreach ($conditions as $condition_key => $condition_value) {
+ if (is_array($condition_value)) {
+ if (!isset($entity_values[$condition_key]) || !in_array($entity_values[$condition_key], $condition_value)) {
+ unset($entities[$key]);
+ }
+ }
+ elseif (!isset($entity_values[$condition_key]) || $entity_values[$condition_key] != $condition_value) {
+ unset($entities[$key]);
+ }
+ }
+ }
+ }
+ return $entities;
+ }
+
+ /**
+ * Overridden.
+ * @see DrupalDefaultEntityController::cacheSet()
+ */
+ protected function cacheSet($entities) {
+ $this->entityCache += $entities;
+ // If we have a name key, also support static caching when loading by name.
+ if ($this->nameKey != $this->idKey) {
+ $this->entityCacheByName += entity_key_array_by_property($entities, $this->nameKey);
+ }
+ }
+
+ /**
+ * Overridden.
+ * @see DrupalDefaultEntityController::attachLoad()
+ *
+ * Changed to call type-specific hook with the entities keyed by name if they
+ * have one.
+ */
+ protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
+ // Attach fields.
+ if ($this->entityInfo['fieldable']) {
+ if ($revision_id) {
+ field_attach_load_revision($this->entityType, $queried_entities);
+ }
+ else {
+ field_attach_load($this->entityType, $queried_entities);
+ }
+ }
+
+ // Call hook_entity_load().
+ foreach (module_implements('entity_load') as $module) {
+ $function = $module . '_entity_load';
+ $function($queried_entities, $this->entityType);
+ }
+ // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
+ // always the queried entities, followed by additional arguments set in
+ // $this->hookLoadArguments.
+ // For entities with a name key, pass the entities keyed by name to the
+ // specific load hook.
+ if ($this->nameKey != $this->idKey) {
+ $entities_by_name = entity_key_array_by_property($queried_entities, $this->nameKey);
+ }
+ else {
+ $entities_by_name = $queried_entities;
+ }
+ $args = array_merge(array($entities_by_name), $this->hookLoadArguments);
+ foreach (module_implements($this->entityInfo['load hook']) as $module) {
+ call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
+ }
+ }
+
+ public function resetCache(array $ids = NULL) {
+ $this->cacheComplete = FALSE;
+ if (isset($ids)) {
+ foreach (array_intersect_key($this->entityCache, array_flip($ids)) as $id => $entity) {
+ unset($this->entityCacheByName[$this->entityCache[$id]->{$this->nameKey}]);
+ unset($this->entityCache[$id]);
+ }
+ }
+ else {
+ $this->entityCache = array();
+ $this->entityCacheByName = array();
+ }
+ }
+
+ /**
+ * Overridden to care about reverted entities.
+ */
+ public function delete($ids, DatabaseTransaction $transaction = NULL) {
+ $entities = $ids ? $this->load($ids) : FALSE;
+ if ($entities) {
+ parent::delete($ids, $transaction);
+
+ foreach ($entities as $id => $entity) {
+ if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
+ entity_defaults_rebuild(array($this->entityType));
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Overridden to care about reverted bundle entities and to skip Rules.
+ */
+ public function invoke($hook, $entity) {
+ if ($hook == 'delete') {
+ // To ease figuring out whether this is a revert, make sure that the
+ // entity status is updated in case the providing module has been
+ // disabled.
+ if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && !module_exists($entity->{$this->moduleKey})) {
+ $entity->{$this->statusKey} = ENTITY_CUSTOM;
+ }
+ $is_revert = entity_has_status($this->entityType, $entity, ENTITY_IN_CODE);
+ }
+
+ if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
+ $function($this->entityType, $entity);
+ }
+
+ if (isset($this->entityInfo['bundle of']) && $type = $this->entityInfo['bundle of']) {
+ // Call field API bundle attachers for the entity we are a bundle of.
+ if ($hook == 'insert') {
+ field_attach_create_bundle($type, $entity->{$this->bundleKey});
+ }
+ elseif ($hook == 'delete' && !$is_revert) {
+ field_attach_delete_bundle($type, $entity->{$this->bundleKey});
+ }
+ elseif ($hook == 'update' && $id = $entity->{$this->nameKey}) {
+ if ($entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
+ field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
+ }
+ }
+ }
+ // Invoke the hook.
+ module_invoke_all($this->entityType . '_' . $hook, $entity);
+ // Invoke the respective entity level hook.
+ if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
+ module_invoke_all('entity_' . $hook, $entity, $this->entityType);
+ }
+ }
+
+ /**
+ * Overridden to care exportables that are overridden.
+ */
+ public function save($entity, DatabaseTransaction $transaction = NULL) {
+ // Preload $entity->original by name key if necessary.
+ if (!empty($entity->{$this->nameKey}) && empty($entity->{$this->idKey}) && !isset($entity->original)) {
+ $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->nameKey});
+ }
+ // Update the status for entities getting overridden.
+ if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && empty($entity->is_rebuild)) {
+ $entity->{$this->statusKey} |= ENTITY_CUSTOM;
+ }
+ return parent::save($entity, $transaction);
+ }
+
+ /**
+ * Overridden.
+ */
+ public function export($entity, $prefix = '') {
+ $vars = get_object_vars($entity);
+ unset($vars[$this->statusKey], $vars[$this->moduleKey], $vars['is_new']);
+ if ($this->nameKey != $this->idKey) {
+ unset($vars[$this->idKey]);
+ }
+ return entity_var_json_export($vars, $prefix);
+ }
+
+ /**
+ * Implements EntityAPIControllerInterface.
+ */
+ public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
+ $view = parent::view($entities, $view_mode, $langcode, $page);
+
+ if ($this->nameKey != $this->idKey) {
+ // Re-key the view array to be keyed by name.
+ $return = array();
+ foreach ($view[$this->entityType] as $id => $content) {
+ $key = isset($content['#entity']->{$this->nameKey}) ? $content['#entity']->{$this->nameKey} : NULL;
+ $return[$this->entityType][$key] = $content;
+ }
+ $view = $return;
+ }
+ return $view;
+ }
+}
diff --git a/sites/all/modules/entity/includes/entity.inc b/sites/all/modules/entity/includes/entity.inc
new file mode 100644
index 000000000..2f504f36a
--- /dev/null
+++ b/sites/all/modules/entity/includes/entity.inc
@@ -0,0 +1,423 @@
+<?php
+
+/**
+ * @file
+ * Provides a base class for entities.
+ */
+
+/**
+ * Interface for class based entities.
+ */
+interface EntityInterface {
+
+ /**
+ * Returns the internal, numeric identifier.
+ *
+ * Returns the numeric identifier, even if the entity type has specified a
+ * name key. In the latter case, the numeric identifier is supposed to be used
+ * when dealing generically with entities or internally to refer to an entity,
+ * i.e. in a relational database. If unsure, use Entity:identifier().
+ */
+ public function internalIdentifier();
+
+ /**
+ * Returns the entity identifier, i.e. the entities name or numeric id.
+ *
+ * @return
+ * The identifier of the entity. If the entity type makes use of a name key,
+ * the name is returned, else the numeric id.
+ *
+ * @see entity_id()
+ */
+ public function identifier();
+
+ /**
+ * Returns the info of the type of the entity.
+ *
+ * @see entity_get_info()
+ */
+ public function entityInfo();
+
+ /**
+ * Returns the type of the entity.
+ */
+ public function entityType();
+
+ /**
+ * Returns the bundle of the entity.
+ *
+ * @return
+ * The bundle of the entity. Defaults to the entity type if the entity type
+ * does not make use of different bundles.
+ */
+ public function bundle();
+
+ /**
+ * Returns the EntityMetadataWrapper of the entity.
+ *
+ * @return EntityDrupalWrapper
+ * An EntityMetadataWrapper containing the entity.
+ */
+ public function wrapper();
+
+ /**
+ * Returns the label of the entity.
+ *
+ * Modules may alter the label by specifying another 'label callback' using
+ * hook_entity_info_alter().
+ *
+ * @see entity_label()
+ */
+ public function label();
+
+ /**
+ * Returns the uri of the entity just as entity_uri().
+ *
+ * Modules may alter the uri by specifying another 'uri callback' using
+ * hook_entity_info_alter().
+ *
+ * @see entity_uri()
+ */
+ public function uri();
+
+ /**
+ * Checks if the entity has a certain exportable status.
+ *
+ * @param $status
+ * A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE,
+ * ENTITY_OVERRIDDEN or ENTITY_FIXED.
+ *
+ * @return bool
+ * For exportable entities TRUE if the entity has the status, else FALSE.
+ * In case the entity is not exportable, NULL is returned.
+ *
+ * @see entity_has_status()
+ */
+ public function hasStatus($status);
+
+ /**
+ * Permanently saves the entity.
+ *
+ * @see entity_save()
+ */
+ public function save();
+
+ /**
+ * Permanently deletes the entity.
+ *
+ * @see entity_delete()
+ */
+ public function delete();
+
+ /**
+ * Exports the entity.
+ *
+ * @see entity_export()
+ */
+ public function export($prefix = '');
+
+ /**
+ * Generate an array for rendering the entity.
+ *
+ * @see entity_view()
+ */
+ public function view($view_mode = 'full', $langcode = NULL, $page = NULL);
+
+ /**
+ * Builds a structured array representing the entity's content.
+ *
+ * @see entity_build_content()
+ */
+ public function buildContent($view_mode = 'full', $langcode = NULL);
+
+ /**
+ * Gets the raw, translated value of a property or field.
+ *
+ * Supports retrieving field translations as well as i18n string translations.
+ *
+ * Note that this returns raw data values, which might not reflect what
+ * has been declared for hook_entity_property_info() as no 'getter callbacks'
+ * are invoked or no referenced entities are loaded. For retrieving values
+ * reflecting the property info make use of entity metadata wrappers, see
+ * entity_metadata_wrapper().
+ *
+ * @param $property
+ * The name of the property to return; e.g., 'title'.
+ * @param $langcode
+ * (optional) The language code of the language to which the value should
+ * be translated. If set to NULL, the default display language is being
+ * used.
+ *
+ * @return
+ * The raw, translated property value; or the raw, un-translated value if no
+ * translation is available.
+ *
+ * @todo Implement an analogous setTranslation() method for updating.
+ */
+ public function getTranslation($property, $langcode = NULL);
+
+ /**
+ * Checks whether the entity is the default revision.
+ *
+ * @return Boolean
+ *
+ * @see entity_revision_is_default()
+ */
+ public function isDefaultRevision();
+
+}
+
+/**
+ * A common class for entities.
+ *
+ * It's suggested, but not required, to extend this class and to override
+ * __construct() in order to specify a fixed entity type.
+ *
+ * For providing an entity label and URI it is suggested to override the
+ * defaultLabel() and defaultUri() methods, and to specify the
+ * entity_class_label() and entity_class_uri() as respective callbacks in
+ * hook_entity_info(). That way modules are able to override your defaults
+ * by altering the hook_entity_info() callbacks, while $entity->label() and
+ * $entity->uri() reflect this changes as well.
+ *
+ * Defaults for entity properties can be easily defined by adding class
+ * properties, e.g.:
+ * @code
+ * public $name = '';
+ * public $count = 0;
+ * @endcode
+ */
+class Entity implements EntityInterface {
+
+ protected $entityType;
+ protected $entityInfo;
+ protected $idKey, $nameKey, $statusKey;
+ protected $defaultLabel = FALSE;
+ protected $wrapper;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(array $values = array(), $entityType = NULL) {
+ if (empty($entityType)) {
+ throw new Exception('Cannot create an instance of Entity without a specified entity type.');
+ }
+ $this->entityType = $entityType;
+ $this->setUp();
+ // Set initial values.
+ foreach ($values as $key => $value) {
+ $this->$key = $value;
+ }
+ }
+
+ /**
+ * Set up the object instance on construction or unserializiation.
+ */
+ protected function setUp() {
+ $this->entityInfo = entity_get_info($this->entityType);
+ $this->idKey = $this->entityInfo['entity keys']['id'];
+ $this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey;
+ $this->statusKey = empty($this->entityInfo['entity keys']['status']) ? 'status' : $this->entityInfo['entity keys']['status'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function internalIdentifier() {
+ return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function identifier() {
+ return isset($this->{$this->nameKey}) ? $this->{$this->nameKey} : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function entityInfo() {
+ return $this->entityInfo;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function entityType() {
+ return $this->entityType;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function bundle() {
+ return !empty($this->entityInfo['entity keys']['bundle']) ? $this->{$this->entityInfo['entity keys']['bundle']} : $this->entityType;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function wrapper() {
+ if (empty($this->wrapper)) {
+ $this->wrapper = entity_metadata_wrapper($this->entityType, $this);
+ }
+ elseif ($this->wrapper->value() !== $this) {
+ // Wrapper has been modified outside, so let's better create a new one.
+ $this->wrapper = entity_metadata_wrapper($this->entityType, $this);
+ }
+ return $this->wrapper;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function label() {
+ // If the default label flag is enabled, this is being invoked recursively.
+ // In this case we need to use our default label callback directly. This may
+ // happen if a module provides a label callback implementation different
+ // from ours, but then invokes Entity::label() or entity_class_label() from
+ // there.
+ if ($this->defaultLabel || (isset($this->entityInfo['label callback']) && $this->entityInfo['label callback'] == 'entity_class_label')) {
+ return $this->defaultLabel();
+ }
+ $this->defaultLabel = TRUE;
+ $label = entity_label($this->entityType, $this);
+ $this->defaultLabel = FALSE;
+ return $label;
+ }
+
+ /**
+ * Defines the entity label if the 'entity_class_label' callback is used.
+ *
+ * Specify 'entity_class_label' as 'label callback' in hook_entity_info() to
+ * let the entity label point to this method. Override this in order to
+ * implement a custom default label.
+ */
+ protected function defaultLabel() {
+ // Add in the translated specified label property.
+ return $this->getTranslation($this->entityInfo['entity keys']['label']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function uri() {
+ if (isset($this->entityInfo['uri callback']) && $this->entityInfo['uri callback'] == 'entity_class_uri') {
+ return $this->defaultUri();
+ }
+ return entity_uri($this->entityType, $this);
+ }
+
+ /**
+ * Override this in order to implement a custom default URI and specify
+ * 'entity_class_uri' as 'uri callback' hook_entity_info().
+ */
+ protected function defaultUri() {
+ return array('path' => 'default/' . $this->identifier());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasStatus($status) {
+ if (!empty($this->entityInfo['exportable'])) {
+ return isset($this->{$this->statusKey}) && ($this->{$this->statusKey} & $status) == $status;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save() {
+ return entity_get_controller($this->entityType)->save($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete() {
+ $id = $this->identifier();
+ if (isset($id)) {
+ entity_get_controller($this->entityType)->delete(array($id));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function export($prefix = '') {
+ return entity_get_controller($this->entityType)->export($this, $prefix);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function view($view_mode = 'full', $langcode = NULL, $page = NULL) {
+ return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildContent($view_mode = 'full', $langcode = NULL) {
+ return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTranslation($property, $langcode = NULL) {
+ $all_info = entity_get_all_property_info($this->entityType);
+ // Assign by reference to avoid triggering notices if metadata is missing.
+ $property_info = &$all_info[$property];
+
+ if (!empty($property_info['translatable'])) {
+ if (!empty($property_info['field'])) {
+ return field_get_items($this->entityType, $this, $property, $langcode);
+ }
+ elseif (!empty($property_info['i18n string'])) {
+ $name = $this->entityInfo['module'] . ':' . $this->entityType . ':' . $this->identifier() . ':' . $property;
+ return entity_i18n_string($name, $this->$property, $langcode);
+ }
+ }
+ return $this->$property;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isDefaultRevision() {
+ if (!empty($this->entityInfo['entity keys']['revision'])) {
+ $key = !empty($this->entityInfo['entity keys']['default revision']) ? $this->entityInfo['entity keys']['default revision'] : 'default_revision';
+ return !empty($this->$key);
+ }
+ return TRUE;
+ }
+
+ /**
+ * Magic method to only serialize what's necessary.
+ */
+ public function __sleep() {
+ $vars = get_object_vars($this);
+ unset($vars['entityInfo'], $vars['idKey'], $vars['nameKey'], $vars['statusKey']);
+ // Also key the returned array with the variable names so the method may
+ // be easily overridden and customized.
+ return drupal_map_assoc(array_keys($vars));
+ }
+
+ /**
+ * Magic method to invoke setUp() on unserialization.
+ */
+ public function __wakeup() {
+ $this->setUp();
+ }
+}
+
+/**
+ * These classes are deprecated by "Entity" and are only here for backward
+ * compatibility reasons.
+ */
+class EntityDB extends Entity {}
+class EntityExtendable extends Entity {}
+class EntityDBExtendable extends Entity {}
diff --git a/sites/all/modules/entity/includes/entity.property.inc b/sites/all/modules/entity/includes/entity.property.inc
new file mode 100644
index 000000000..38e4fd7b1
--- /dev/null
+++ b/sites/all/modules/entity/includes/entity.property.inc
@@ -0,0 +1,657 @@
+<?php
+
+/**
+ * @file
+ * Provides API functions around hook_entity_property_info(). Also see
+ * entity.info.inc, which cares for providing entity property info for all core
+ * entity types.
+ */
+
+/**
+ * Get the entity property info array of an entity type.
+ *
+ * @param $entity_type
+ * The entity type, e.g. node, for which the info shall be returned, or NULL
+ * to return an array with info about all types.
+ *
+ * @see hook_entity_property_info()
+ * @see hook_entity_property_info_alter()
+ */
+function entity_get_property_info($entity_type = NULL) {
+ // Use the advanced drupal_static() pattern, since this is called very often.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['info'] = &drupal_static(__FUNCTION__);
+ }
+ $info = &$drupal_static_fast['info'];
+
+ // hook_entity_property_info() includes translated strings, so each language
+ // is cached separately.
+ $langcode = $GLOBALS['language']->language;
+
+ if (empty($info)) {
+ if ($cache = cache_get("entity_property_info:$langcode")) {
+ $info = $cache->data;
+ }
+ else {
+ $info = module_invoke_all('entity_property_info');
+ // Let other modules alter the entity info.
+ drupal_alter('entity_property_info', $info);
+ cache_set("entity_property_info:$langcode", $info);
+ }
+ }
+ return empty($entity_type) ? $info : (isset($info[$entity_type]) ? $info[$entity_type] : array());
+}
+
+/**
+ * Returns the default information for an entity property.
+ *
+ * @return
+ * An array of optional property information keys mapped to their defaults.
+ *
+ * @see hook_entity_property_info()
+ */
+function entity_property_info_defaults() {
+ return array(
+ 'type' => 'text',
+ 'getter callback' => 'entity_property_verbatim_get',
+ );
+}
+
+/**
+ * Gets an array of info about all properties of a given entity type.
+ *
+ * In contrast to entity_get_property_info(), this function returns info about
+ * all properties the entity might have, thus it adds an all properties assigned
+ * to entity bundles.
+ *
+ * @param $entity_type
+ * (optiona) The entity type to return properties for.
+ *
+ * @return
+ * An array of info about properties. If the type is ommitted, all known
+ * properties are returned.
+ */
+function entity_get_all_property_info($entity_type = NULL) {
+ if (!isset($entity_type)) {
+ // Retrieve all known properties.
+ $properties = array();
+ foreach (entity_get_info() as $entity_type => $info) {
+ $properties += entity_get_all_property_info($entity_type);
+ }
+ return $properties;
+ }
+ // Else retrieve the properties of the given entity type only.
+ $info = entity_get_property_info($entity_type);
+ $info += array('properties' => array(), 'bundles' => array());
+ // Add all bundle properties.
+ foreach ($info['bundles'] as $bundle => $bundle_info) {
+ $bundle_info += array('properties' => array());
+ $info['properties'] += $bundle_info['properties'];
+ }
+ return $info['properties'];
+}
+
+/**
+ * Queries for entities having the given property value.
+ *
+ * @param $entity_type
+ * The type of the entity.
+ * @param $property
+ * The name of the property to query for.
+ * @param $value
+ * A single property value or an array of possible values to query for.
+ * @param $limit
+ * Limit the numer of results. Defaults to 30.
+ *
+ * @return
+ * An array of entity ids or NULL if there is no information how to query for
+ * the given property.
+ */
+function entity_property_query($entity_type, $property, $value, $limit = 30) {
+ $properties = entity_get_all_property_info($entity_type);
+ $info = $properties[$property] + array('type' => 'text', 'queryable' => !empty($properties[$property]['schema field']));
+
+ // We still support the deprecated query callback, so just add in EFQ-based
+ // callbacks in case 'queryable' is set to TRUE and make use of the callback.
+ if ($info['queryable'] && empty($info['query callback'])) {
+ $info['query callback'] = !empty($info['field']) ? 'entity_metadata_field_query' : 'entity_metadata_table_query';
+ }
+
+ $type = $info['type'];
+ // Make sure an entity or a list of entities are passed on as identifiers
+ // with the help of the wrappers. For that ensure the data type matches the
+ // passed on value(s).
+ if (is_array($value) && !entity_property_list_extract_type($type)) {
+ $type = 'list<' . $type . '>';
+ }
+ elseif (!is_array($value) && entity_property_list_extract_type($type)) {
+ $type = entity_property_list_extract_type($type);
+ }
+
+ $wrapper = entity_metadata_wrapper($type, $value);
+ $value = $wrapper->value(array('identifier' => TRUE));
+
+ if (!empty($info['query callback'])) {
+ return $info['query callback']($entity_type, $property, $value, $limit);
+ }
+}
+
+/**
+ * Resets the cached information of hook_entity_property_info().
+ */
+function entity_property_info_cache_clear() {
+ drupal_static_reset('entity_get_property_info');
+ // Clear all languages.
+ cache_clear_all('entity_property_info:', 'cache', TRUE);
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function entity_hook_info() {
+ $hook_info['entity_property_info'] = array(
+ 'group' => 'info',
+ );
+ $hook_info['entity_property_info_alter'] = array(
+ 'group' => 'info',
+ );
+ return $hook_info;
+}
+
+/**
+ * Implements hook_field_info_alter().
+ * Defines default property types for core field types.
+ */
+function entity_field_info_alter(&$field_info) {
+ if (module_exists('number')) {
+ $field_info['number_integer']['property_type'] = 'integer';
+ $field_info['number_decimal']['property_type'] = 'decimal';
+ $field_info['number_float']['property_type'] = 'decimal';
+ }
+ if (module_exists('text')) {
+ $field_info['text']['property_type'] = 'text';
+ $field_info['text']['property_callbacks'][] = 'entity_metadata_field_text_property_callback';
+ $field_info['text_long']['property_type'] = 'text';
+ $field_info['text_long']['property_callbacks'][] = 'entity_metadata_field_text_property_callback';
+ $field_info['text_with_summary']['property_type'] = 'field_item_textsummary';
+ $field_info['text_with_summary']['property_callbacks'][] = 'entity_metadata_field_text_property_callback';
+ }
+ if (module_exists('list')) {
+ $field_info['list_integer']['property_type'] = 'integer';
+ $field_info['list_boolean']['property_type'] = 'boolean';
+ $field_info['list_float']['property_type'] = 'decimal';
+ $field_info['list_text']['property_type'] = 'text';
+ }
+ if (module_exists('taxonomy')) {
+ $field_info['taxonomy_term_reference']['property_type'] = 'taxonomy_term';
+ $field_info['taxonomy_term_reference']['property_callbacks'][] = 'entity_metadata_field_term_reference_callback';
+ }
+ if (module_exists('file')) {
+ // The callback specifies a custom data structure matching the file field
+ // items. We introduce a custom type name for this data structure.
+ $field_info['file']['property_type'] = 'field_item_file';
+ $field_info['file']['property_callbacks'][] = 'entity_metadata_field_file_callback';
+ }
+ if (module_exists('image')) {
+ // The callback specifies a custom data structure matching the image field
+ // items. We introduce a custom type name for this data structure.
+ $field_info['image']['property_type'] = 'field_item_image';
+ $field_info['image']['property_callbacks'][] = 'entity_metadata_field_file_callback';
+ $field_info['image']['property_callbacks'][] = 'entity_metadata_field_image_callback';
+ }
+}
+
+/**
+ * Implements hook_field_create_instance().
+ * Clear the cache when a field instance changed.
+ */
+function entity_field_create_instance() {
+ entity_property_info_cache_clear();
+}
+
+/**
+ * Implements hook_field_delete_instance().
+ * Clear the cache when a field instance changed.
+ */
+function entity_field_delete_instance() {
+ entity_property_info_cache_clear();
+}
+
+/**
+ * Implements hook_field_update_instance().
+ * Clear the cache when a field instance changed.
+ */
+function entity_field_update_instance() {
+ entity_property_info_cache_clear();
+}
+
+/**
+ * Verifies that the given data can be safely used as the given type regardless
+ * of the PHP variable type of $data. Example: the string "15" is a valid
+ * integer, but "15nodes" is not.
+ *
+ * @return
+ * Whether the data is valid for the given type.
+ */
+function entity_property_verify_data_type($data, $type) {
+ // As this may be called very often statically cache the entity info using
+ // the fast pattern.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ // Make use of the same static as entity info.
+ entity_get_info();
+ $drupal_static_fast['entity_info'] = &drupal_static('entity_get_info');
+ }
+ $info = &$drupal_static_fast['entity_info'];
+
+ // First off check for entities, which may be represented by their ids too.
+ if (isset($info[$type])) {
+ if (is_object($data)) {
+ return TRUE;
+ }
+ elseif (isset($info[$type]['entity keys']['name'])) {
+ // Read the data type of the name key from the metadata if available.
+ $key = $info[$type]['entity keys']['name'];
+ $property_info = entity_get_property_info($type);
+ $property_type = isset($property_info['properties'][$key]['type']) ? $property_info['properties'][$key]['type'] : 'token';
+ return entity_property_verify_data_type($data, $property_type);
+ }
+ return entity_property_verify_data_type($data, empty($info[$type]['fieldable']) ? 'text' : 'integer');
+ }
+
+ switch ($type) {
+ case 'site':
+ case 'unknown':
+ return TRUE;
+ case 'date':
+ case 'duration':
+ case 'integer':
+ return is_numeric($data) && strpos($data, '.') === FALSE;
+ case 'decimal':
+ return is_numeric($data);
+ case 'text':
+ return is_scalar($data);
+ case 'token':
+ return is_scalar($data) && preg_match('!^[a-z][a-z0-9_]*$!', $data);
+ case 'boolean':
+ return is_scalar($data) && (is_bool($data) || $data == 0 || $data == 1);
+ case 'uri':
+ return valid_url($data, TRUE);
+ case 'list':
+ return (is_array($data) && array_values($data) == $data) || (is_object($data) && $data instanceof EntityMetadataArrayObject);
+ case 'entity':
+ return is_object($data) && $data instanceof EntityDrupalWrapper;
+ default:
+ case 'struct':
+ return is_object($data) || is_array($data);
+ }
+}
+
+/**
+ * Creates the entity object for an array of given property values.
+ *
+ * @param $entity_type
+ * The entity type to create an entity for.
+ * @param $values
+ * An array of values as described by the entity's property info. All entity
+ * properties of the given entity type that are marked as required, must be
+ * present.
+ * If the passed values have no matching property, their value will be
+ * assigned to the entity directly, without the use of the metadata-wrapper
+ * property.
+ *
+ * @return EntityDrupalWrapper
+ * An EntityDrupalWrapper wrapping the newly created entity or FALSE, if
+ * there were no information how to create the entity.
+ */
+function entity_property_values_create_entity($entity_type, $values = array()) {
+ if (entity_type_supports($entity_type, 'create')) {
+ $info = entity_get_info($entity_type);
+ // Create the initial entity by passing the values for all 'entity keys'
+ // to entity_create().
+ $entity_keys = array_filter($info['entity keys']);
+ $creation_values = array_intersect_key($values, array_flip($entity_keys));
+
+ // In case the bundle key does not match the property that sets it, ensure
+ // the bundle key is initialized somehow, so entity_extract_ids()
+ // does not bail out during wrapper creation.
+ if (!empty($info['entity keys']['bundle'])) {
+ $creation_values += array($info['entity keys']['bundle'] => FALSE);
+ }
+ $entity = entity_create($entity_type, $creation_values);
+
+ // Now set the remaining values using the wrapper.
+ $wrapper = entity_metadata_wrapper($entity_type, $entity);
+ foreach ($values as $key => $value) {
+ if (!in_array($key, $info['entity keys'])) {
+ if (isset($wrapper->$key)) {
+ $wrapper->$key->set($value);
+ }
+ else {
+ $entity->$key = $value;
+ }
+ }
+ }
+ // @todo: Once we require Drupal 7.7 or later, verify the entity has
+ // now a valid bundle and throw the EntityMalformedException if not.
+ return $wrapper;
+ }
+ return FALSE;
+}
+
+
+/**
+ * Extracts the contained type for a list type string like list<date>.
+ *
+ * @return
+ * The contained type or FALSE, if the given type string is no list.
+ */
+function entity_property_list_extract_type($type) {
+ if (strpos($type, 'list<') === 0 && $type[strlen($type)-1] == '>') {
+ return substr($type, 5, -1);
+ }
+ return FALSE;
+}
+
+/**
+ * Extracts the innermost type for a type string like list<list<date>>.
+ *
+ * @param $type
+ * The type to examine.
+ *
+ * @return
+ * For list types, the innermost type. The type itself otherwise.
+ */
+function entity_property_extract_innermost_type($type) {
+ while (strpos($type, 'list<') === 0 && $type[strlen($type)-1] == '>') {
+ $type = substr($type, 5, -1);
+ }
+ return $type;
+}
+
+/**
+ * Gets the property just as it is set in the data.
+ */
+function entity_property_verbatim_get($data, array $options, $name, $type, $info) {
+ $name = isset($info['schema field']) ? $info['schema field'] : $name;
+ if ((is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) && isset($data[$name])) {
+ return $data[$name];
+ }
+ elseif (is_object($data) && isset($data->$name)) {
+ // Incorporate i18n_string translations. We may rely on the entity class
+ // here as its usage is required by the i18n integration.
+ if (isset($options['language']) && !empty($info['i18n string'])) {
+ return $data->getTranslation($name, $options['language']->language);
+ }
+ else {
+ return $data->$name;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Date values are converted from ISO strings to timestamp if needed.
+ */
+function entity_property_verbatim_date_get($data, array $options, $name, $type, $info) {
+ $name = isset($info['schema field']) ? $info['schema field'] : $name;
+ if (is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) {
+ return is_numeric($data[$name]) ? $data[$name] : strtotime($data[$name], REQUEST_TIME);
+ }
+ elseif (is_object($data)) {
+ return is_numeric($data->$name) ? $data->$name : strtotime($data->$name, REQUEST_TIME);
+ }
+}
+
+/**
+ * Sets the property to the given value. May be used as 'setter callback'.
+ */
+function entity_property_verbatim_set(&$data, $name, $value, $langcode, $type, $info) {
+ $name = isset($info['schema field']) ? $info['schema field'] : $name;
+ if (is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) {
+ $data[$name] = $value;
+ }
+ elseif (is_object($data)) {
+ $data->$name = $value;
+ }
+}
+
+/**
+ * Gets the property using the getter method (named just like the property).
+ */
+function entity_property_getter_method($object, array $options, $name) {
+ // Remove any underscores as classes are expected to use CamelCase.
+ $method = strtr($name, array('_' => ''));
+ return $object->$method();
+}
+
+/**
+ * Sets the property to the given value using the setter method. May be used as
+ * 'setter callback'.
+ */
+function entity_property_setter_method($object, $name, $value) {
+ // Remove any underscores as classes are expected to use CamelCase.
+ $method = 'set' . strtr($name, array('_' => ''));
+ // Invoke the setProperty() method where 'Property' is the property name.
+ $object->$method($value);
+}
+
+/**
+ * Getter callback for getting an array. Makes sure it's numerically indexed.
+ */
+function entity_property_get_list($data, array $options, $name) {
+ return isset($data->$name) ? array_values($data->$name) : array();
+}
+
+/**
+ * A validation callback ensuring the passed integer is positive.
+ */
+function entity_property_validate_integer_positive($value) {
+ return $value > 0;
+}
+
+/**
+ * A validation callback ensuring the passed integer is non-negative.
+ */
+function entity_property_validate_integer_non_negative($value) {
+ return $value >= 0;
+}
+
+/**
+ * A simple auto-creation callback for array based data structures.
+ */
+function entity_property_create_array($property_name, $context) {
+ return array();
+}
+
+/**
+ * Flattens the given options in single dimensional array.
+ * We don't depend on options module, so we cannot use options_array_flatten().
+ *
+ * @see options_array_flatten()
+ */
+function entity_property_options_flatten($options) {
+ $result = array();
+ foreach ($options as $key => $value) {
+ if (is_array($value)) {
+ $result += $value;
+ }
+ else {
+ $result[$key] = $value;
+ }
+ }
+ return $result;
+}
+
+/**
+ * Defines info for the properties of the text_formatted data structure.
+ */
+function entity_property_text_formatted_info() {
+ return array(
+ 'value' => array(
+ 'type' => 'text',
+ 'label' => t('Text'),
+ 'sanitized' => TRUE,
+ 'getter callback' => 'entity_metadata_field_text_get',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer nodes',
+ 'raw getter callback' => 'entity_property_verbatim_get',
+ ),
+ 'summary' => array(
+ 'type' => 'text',
+ 'label' => t('Summary'),
+ 'sanitized' => TRUE,
+ 'getter callback' => 'entity_metadata_field_text_get',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer nodes',
+ 'raw getter callback' => 'entity_property_verbatim_get',
+ ),
+ 'format' => array(
+ 'type' => 'token',
+ 'label' => t('Text format'),
+ 'options list' => 'entity_metadata_field_text_formats',
+ 'getter callback' => 'entity_property_verbatim_get',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permissions' => 'administer filters',
+ ),
+ );
+}
+
+/**
+ * Defines info for the properties of the field_item_textsummary data structure.
+ */
+function entity_property_field_item_textsummary_info() {
+ return array(
+ 'value' => array(
+ 'type' => 'text',
+ 'label' => t('Text'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ ),
+ 'summary' => array(
+ 'type' => 'text',
+ 'label' => t('Summary'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ ),
+ );
+}
+
+/**
+ * Defines info for the properties of the file-field item data structure.
+ */
+function entity_property_field_item_file_info() {
+ $properties['file'] = array(
+ 'type' => 'file',
+ 'label' => t('The file.'),
+ 'getter callback' => 'entity_metadata_field_file_get',
+ 'setter callback' => 'entity_metadata_field_file_set',
+ 'required' => TRUE,
+ );
+ $properties['description'] = array(
+ 'type' => 'text',
+ 'label' => t('The file description'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ );
+ $properties['display'] = array(
+ 'type' => 'boolean',
+ 'label' => t('Whether the file is being displayed.'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ );
+ return $properties;
+}
+
+/**
+ * Defines info for the properties of the image-field item data structure.
+ */
+function entity_property_field_item_image_info() {
+ $properties['file'] = array(
+ 'type' => 'file',
+ 'label' => t('The image file.'),
+ 'getter callback' => 'entity_metadata_field_file_get',
+ 'setter callback' => 'entity_metadata_field_file_set',
+ 'required' => TRUE,
+ );
+ $properties['alt'] = array(
+ 'type' => 'text',
+ 'label' => t('The "Alt" attribute text'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ );
+ $properties['title'] = array(
+ 'type' => 'text',
+ 'label' => t('The "Title" attribute text'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ );
+ return $properties;
+}
+
+
+/**
+ * Previously, hook_entity_property_info() has been provided by the removed
+ * entity metadata module. To provide backward compatibility for provided
+ * helpers that may be specified in hook_entity_property_info(), the following
+ * (deprecated) functions are provided.
+ */
+
+/**
+ * Deprecated.
+ * Do not make use of this function, instead use the new one.
+ */
+function entity_metadata_verbatim_get($data, array $options, $name) {
+ return entity_property_verbatim_get($data, $options, $name);
+}
+
+/**
+ * Deprecated.
+ * Do not make use of this function, instead use the new one.
+ */
+function entity_metadata_verbatim_set($data, $name, $value) {
+ return entity_property_verbatim_set($data, $name, $value);
+}
+
+/**
+ * Deprecated.
+ * Do not make use of this function, instead use the new one.
+ */
+function entity_metadata_getter_method($object, array $options, $name) {
+ return entity_property_getter_method($object, $options, $name);
+}
+
+/**
+ * Deprecated.
+ * Do not make use of this function, instead use the new one.
+ */
+function entity_metadata_setter_method($object, $name, $value) {
+ entity_property_setter_method($object, $name, $value);
+}
+
+/**
+ * Deprecated.
+ * Do not make use of this function, instead use the new one.
+ */
+function entity_metadata_get_list($data, array $options, $name) {
+ return entity_property_get_list($data, $options, $name);
+}
+
+/**
+ * Deprecated.
+ * Do not make use of this function, instead use the new one.
+ */
+function entity_metadata_validate_integer_positive($value) {
+ return entity_property_validate_integer_positive($value);
+}
+
+/**
+ * Deprecated.
+ * Do not make use of this function, instead use the new one.
+ */
+function entity_metadata_validate_integer_non_negative($value) {
+ return entity_property_validate_integer_non_negative($value);
+}
+
+/**
+ * Deprecated.
+ * Do not make use of this function, instead use the new one.
+ */
+function entity_metadata_text_formatted_properties() {
+ return entity_property_text_formatted_info();
+}
diff --git a/sites/all/modules/entity/includes/entity.ui.inc b/sites/all/modules/entity/includes/entity.ui.inc
new file mode 100644
index 000000000..e115ad973
--- /dev/null
+++ b/sites/all/modules/entity/includes/entity.ui.inc
@@ -0,0 +1,767 @@
+<?php
+
+/**
+ * @file
+ * Provides a controller for building an entity overview form.
+ */
+
+/**
+ * Default UI controller providing admin UI.
+ *
+ * This controller suites best for managing configuration entities.
+ * For a controller suiting content entities, see EntityContentUIController.
+ */
+class EntityDefaultUIController {
+
+ protected $entityType;
+ protected $entityInfo, $path;
+ protected $id_count;
+
+ /**
+ * Defines the number of entries to show per page in overview table.
+ */
+ public $overviewPagerLimit = 25;
+
+ public function __construct($entity_type, $entity_info) {
+ $this->entityType = $entity_type;
+ $this->entityInfo = $entity_info;
+ $this->path = $this->entityInfo['admin ui']['path'];
+ $this->statusKey = empty($this->entityInfo['entity keys']['status']) ? 'status' : $this->entityInfo['entity keys']['status'];
+ }
+
+ /**
+ * Provides definitions for implementing hook_menu().
+ */
+ public function hook_menu() {
+ $items = array();
+ // Set this on the object so classes that extend hook_menu() can use it.
+ $this->id_count = count(explode('/', $this->path));
+ $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object';
+ $plural_label = isset($this->entityInfo['plural label']) ? $this->entityInfo['plural label'] : $this->entityInfo['label'] . 's';
+
+ $items[$this->path] = array(
+ 'title' => $plural_label,
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array($this->entityType . '_overview_form', $this->entityType),
+ 'description' => 'Manage ' . $plural_label . '.',
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('view', $this->entityType),
+ 'file' => 'includes/entity.ui.inc',
+ );
+ $items[$this->path . '/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items[$this->path . '/add'] = array(
+ 'title callback' => 'entity_ui_get_action_title',
+ 'title arguments' => array('add', $this->entityType),
+ 'page callback' => 'entity_ui_get_form',
+ 'page arguments' => array($this->entityType, NULL, 'add'),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('create', $this->entityType),
+ 'type' => MENU_LOCAL_ACTION,
+ );
+ $items[$this->path . '/manage/' . $wildcard] = array(
+ 'title' => 'Edit',
+ 'title callback' => 'entity_label',
+ 'title arguments' => array($this->entityType, $this->id_count + 1),
+ 'page callback' => 'entity_ui_get_form',
+ 'page arguments' => array($this->entityType, $this->id_count + 1),
+ 'load arguments' => array($this->entityType),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('update', $this->entityType, $this->id_count + 1),
+ );
+ $items[$this->path . '/manage/' . $wildcard . '/edit'] = array(
+ 'title' => 'Edit',
+ 'load arguments' => array($this->entityType),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+
+ // Clone form, a special case for the edit form.
+ $items[$this->path . '/manage/' . $wildcard . '/clone'] = array(
+ 'title' => 'Clone',
+ 'page callback' => 'entity_ui_get_form',
+ 'page arguments' => array($this->entityType, $this->id_count + 1, 'clone'),
+ 'load arguments' => array($this->entityType),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('create', $this->entityType),
+ );
+ // Menu item for operations like revert and delete.
+ $items[$this->path . '/manage/' . $wildcard . '/%'] = array(
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $this->id_count + 1, $this->id_count + 2),
+ 'load arguments' => array($this->entityType),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('delete', $this->entityType, $this->id_count + 1),
+ 'file' => 'includes/entity.ui.inc',
+ );
+
+ if (!empty($this->entityInfo['exportable'])) {
+ // Menu item for importing an entity.
+ $items[$this->path . '/import'] = array(
+ 'title callback' => 'entity_ui_get_action_title',
+ 'title arguments' => array('import', $this->entityType),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, NULL, 'import'),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('create', $this->entityType),
+ 'file' => 'includes/entity.ui.inc',
+ 'type' => MENU_LOCAL_ACTION,
+ );
+ }
+
+ if (!empty($this->entityInfo['admin ui']['file'])) {
+ // Add in the include file for the entity form.
+ foreach (array("/manage/$wildcard", "/manage/$wildcard/clone", '/add') as $path_end) {
+ $items[$this->path . $path_end]['file'] = $this->entityInfo['admin ui']['file'];
+ $items[$this->path . $path_end]['file path'] = isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']);
+ }
+ }
+ return $items;
+ }
+
+ /**
+ * Provides definitions for implementing hook_forms().
+ *
+ * Use per bundle form ids if possible, such that easy per bundle alterations
+ * are supported too.
+ *
+ * Note that for performance reasons, this method is invoked only for forms
+ * which receive the entity_type as first argument. Thus any forms added must
+ * follow that pattern.
+ *
+ * @see entity_forms()
+ */
+ public function hook_forms() {
+ // The overview and the operation form are implemented by the controller,
+ // the callback and validation + submit handlers just invoke the controller.
+ $forms[$this->entityType . '_overview_form'] = array(
+ 'callback' => 'entity_ui_overview_form',
+ 'wrapper_callback' => 'entity_ui_form_defaults',
+ );
+ $forms[$this->entityType . '_operation_form'] = array(
+ 'callback' => 'entity_ui_operation_form',
+ 'wrapper_callback' => 'entity_ui_form_defaults',
+ );
+
+ // The entity form (ENTITY_TYPE_form) handles editing, adding and cloning.
+ // For that form, the wrapper callback entity_ui_main_form_defaults() gets
+ // directly invoked via entity_ui_get_form().
+ // If there are bundles though, we use form ids that include the bundle name
+ // (ENTITY_TYPE_edit_BUNDLE_NAME_form) to enable per bundle alterations
+ // as well as alterations based upon the base form id (ENTITY_TYPE_form).
+ if (!(count($this->entityInfo['bundles']) == 1 && isset($this->entityInfo['bundles'][$this->entityType]))) {
+ foreach ($this->entityInfo['bundles'] as $bundle => $bundle_info) {
+ $forms[$this->entityType . '_edit_' . $bundle . '_form']['callback'] = $this->entityType . '_form';
+ // Again the wrapper callback is invoked by entity_ui_get_form() anyway.
+ }
+ }
+ return $forms;
+ }
+
+ /**
+ * Builds the entity overview form.
+ */
+ public function overviewForm($form, &$form_state) {
+ // By default just show a simple overview for all entities.
+ $form['table'] = $this->overviewTable();
+ $form['pager'] = array('#theme' => 'pager');
+ return $form;
+ }
+
+ /**
+ * Overview form validation callback.
+ *
+ * @param $form
+ * The form array of the overview form.
+ * @param $form_state
+ * The overview form state which will be used for validating.
+ */
+ public function overviewFormValidate($form, &$form_state) {}
+
+ /**
+ * Overview form submit callback.
+ *
+ * @param $form
+ * The form array of the overview form.
+ * @param $form_state
+ * The overview form state which will be used for submitting.
+ */
+ public function overviewFormSubmit($form, &$form_state) {}
+
+
+ /**
+ * Generates the render array for a overview table for arbitrary entities
+ * matching the given conditions.
+ *
+ * @param $conditions
+ * An array of conditions as needed by entity_load().
+
+ * @return Array
+ * A renderable array.
+ */
+ public function overviewTable($conditions = array()) {
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', $this->entityType);
+
+ // Add all conditions to query.
+ foreach ($conditions as $key => $value) {
+ $query->propertyCondition($key, $value);
+ }
+
+ if ($this->overviewPagerLimit) {
+ $query->pager($this->overviewPagerLimit);
+ }
+
+ $results = $query->execute();
+
+ $ids = isset($results[$this->entityType]) ? array_keys($results[$this->entityType]) : array();
+ $entities = $ids ? entity_load($this->entityType, $ids) : array();
+ ksort($entities);
+
+ $rows = array();
+ foreach ($entities as $entity) {
+ $rows[] = $this->overviewTableRow($conditions, entity_id($this->entityType, $entity), $entity);
+ }
+
+ $render = array(
+ '#theme' => 'table',
+ '#header' => $this->overviewTableHeaders($conditions, $rows),
+ '#rows' => $rows,
+ '#empty' => t('None.'),
+ );
+ return $render;
+ }
+
+ /**
+ * Generates the table headers for the overview table.
+ */
+ protected function overviewTableHeaders($conditions, $rows, $additional_header = array()) {
+ $header = $additional_header;
+ array_unshift($header, t('Label'));
+ if (!empty($this->entityInfo['exportable'])) {
+ $header[] = t('Status');
+ }
+ // Add operations with the right colspan.
+ $header[] = array('data' => t('Operations'), 'colspan' => $this->operationCount());
+ return $header;
+ }
+
+ /**
+ * Returns the operation count for calculating colspans.
+ */
+ protected function operationCount() {
+ $count = 3;
+ $count += !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui') ? 2 : 0;
+ $count += !empty($this->entityInfo['exportable']) ? 1 : 0;
+ $count += !empty($this->entityInfo['i18n controller class']) ? 1 : 0;
+ return $count;
+ }
+
+ /**
+ * Generates the row for the passed entity and may be overridden in order to
+ * customize the rows.
+ *
+ * @param $additional_cols
+ * Additional columns to be added after the entity label column.
+ */
+ protected function overviewTableRow($conditions, $id, $entity, $additional_cols = array()) {
+ $entity_uri = entity_uri($this->entityType, $entity);
+
+ $row[] = array('data' => array(
+ '#theme' => 'entity_ui_overview_item',
+ '#label' => entity_label($this->entityType, $entity),
+ '#name' => !empty($this->entityInfo['exportable']) ? entity_id($this->entityType, $entity) : FALSE,
+ '#url' => $entity_uri ? $entity_uri : FALSE,
+ '#entity_type' => $this->entityType),
+ );
+
+ // Add in any passed additional cols.
+ foreach ($additional_cols as $col) {
+ $row[] = $col;
+ }
+
+ // Add a row for the exportable status.
+ if (!empty($this->entityInfo['exportable'])) {
+ $row[] = array('data' => array(
+ '#theme' => 'entity_status',
+ '#status' => $entity->{$this->statusKey},
+ ));
+ }
+ // In case this is a bundle, we add links to the field ui tabs.
+ $field_ui = !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui');
+ // For exportable entities we add an export link.
+ $exportable = !empty($this->entityInfo['exportable']);
+ // If i18n integration is enabled, add a link to the translate tab.
+ $i18n = !empty($this->entityInfo['i18n controller class']);
+
+ // Add operations depending on the status.
+ if (entity_has_status($this->entityType, $entity, ENTITY_FIXED)) {
+ $row[] = array('data' => l(t('clone'), $this->path . '/manage/' . $id . '/clone'), 'colspan' => $this->operationCount());
+ }
+ else {
+ $row[] = l(t('edit'), $this->path . '/manage/' . $id);
+
+ if ($field_ui) {
+ $row[] = l(t('manage fields'), $this->path . '/manage/' . $id . '/fields');
+ $row[] = l(t('manage display'), $this->path . '/manage/' . $id . '/display');
+ }
+ if ($i18n) {
+ $row[] = l(t('translate'), $this->path . '/manage/' . $id . '/translate');
+ }
+ if ($exportable) {
+ $row[] = l(t('clone'), $this->path . '/manage/' . $id . '/clone');
+ }
+
+ if (empty($this->entityInfo['exportable']) || !entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
+ $row[] = l(t('delete'), $this->path . '/manage/' . $id . '/delete', array('query' => drupal_get_destination()));
+ }
+ elseif (entity_has_status($this->entityType, $entity, ENTITY_OVERRIDDEN)) {
+ $row[] = l(t('revert'), $this->path . '/manage/' . $id . '/revert', array('query' => drupal_get_destination()));
+ }
+ else {
+ $row[] = '';
+ }
+ }
+ if ($exportable) {
+ $row[] = l(t('export'), $this->path . '/manage/' . $id . '/export');
+ }
+ return $row;
+ }
+
+
+ /**
+ * Builds the operation form.
+ *
+ * For the export operation a serialized string of the entity is directly
+ * shown in the form (no submit function needed).
+ */
+ public function operationForm($form, &$form_state, $entity, $op) {
+ switch ($op) {
+ case 'revert':
+ $label = entity_label($this->entityType, $entity);
+ $confirm_question = t('Are you sure you want to revert the %entity %label?', array('%entity' => $this->entityInfo['label'], '%label' => $label));
+ return confirm_form($form, $confirm_question, $this->path);
+
+ case 'delete':
+ $label = entity_label($this->entityType, $entity);
+ $confirm_question = t('Are you sure you want to delete the %entity %label?', array('%entity' => $this->entityInfo['label'], '%label' => $label));
+ return confirm_form($form, $confirm_question, $this->path);
+
+ case 'export':
+ if (!empty($this->entityInfo['exportable'])) {
+ $export = entity_export($this->entityType, $entity);
+ $form['export'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Export'),
+ '#description' => t('For importing copy the content of the text area and paste it into the import page.'),
+ '#rows' => 25,
+ '#default_value' => $export,
+ );
+ return $form;
+ }
+
+ case 'import':
+ $form['import'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Import'),
+ '#description' => t('Paste an exported %entity_type here.', array('%entity_type' => $this->entityInfo['label'])),
+ '#rows' => 20,
+ );
+ $form['overwrite'] = array(
+ '#title' => t('Overwrite'),
+ '#type' => 'checkbox',
+ '#description' => t('If checked, any existing %entity with the same identifier will be replaced by the import.', array('%entity' => $this->entityInfo['label'])),
+ '#default_value' => FALSE,
+ );
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import'),
+ );
+ return $form;
+ }
+ drupal_not_found();
+ exit;
+ }
+
+ /**
+ * Operation form validation callback.
+ */
+ public function operationFormValidate($form, &$form_state) {
+ if ($form_state['op'] == 'import') {
+ if ($entity = entity_import($this->entityType, $form_state['values']['import'])) {
+ // Store the successfully imported entity in $form_state.
+ $form_state[$this->entityType] = $entity;
+ if (!$form_state['values']['overwrite']) {
+ // Check for existing entities with the same identifier.
+ $id = entity_id($this->entityType, $entity);
+ $entities = entity_load($this->entityType, array($id));
+ if (!empty($entities)) {
+ $label = entity_label($this->entityType, $entity);
+ $vars = array('%entity' => $this->entityInfo['label'], '%label' => $label);
+ form_set_error('import', t('Import of %entity %label failed, a %entity with the same machine name already exists. Check the overwrite option to replace it.', $vars));
+ }
+ }
+ }
+ else {
+ form_set_error('import', t('Import failed.'));
+ }
+ }
+ }
+
+ /**
+ * Operation form submit callback.
+ */
+ public function operationFormSubmit($form, &$form_state) {
+ $msg = $this->applyOperation($form_state['op'], $form_state[$this->entityType]);
+ drupal_set_message($msg);
+ $form_state['redirect'] = $this->path;
+ }
+
+ /**
+ * Applies an operation to the given entity.
+ *
+ * Note: the export operation is directly carried out by the operationForm()
+ * method.
+ *
+ * @param string $op
+ * The operation (revert, delete or import).
+ * @param $entity
+ * The entity to manipulate.
+ *
+ * @return
+ * The status message of what has been applied.
+ */
+ public function applyOperation($op, $entity) {
+ $label = entity_label($this->entityType, $entity);
+ $vars = array('%entity' => $this->entityInfo['label'], '%label' => $label);
+ $id = entity_id($this->entityType, $entity);
+ $edit_link = l(t('edit'), $this->path . '/manage/' . $id . '/edit');
+
+ switch ($op) {
+ case 'revert':
+ entity_delete($this->entityType, $id);
+ watchdog($this->entityType, 'Reverted %entity %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link);
+ return t('Reverted %entity %label to the defaults.', $vars);
+
+ case 'delete':
+ entity_delete($this->entityType, $id);
+ watchdog($this->entityType, 'Deleted %entity %label.', $vars);
+ return t('Deleted %entity %label.', $vars);
+
+ case 'import':
+ // First check if there is any existing entity with the same ID.
+ $id = entity_id($this->entityType, $entity);
+ $entities = entity_load($this->entityType, array($id));
+ if ($existing_entity = reset($entities)) {
+ // Copy DB id and remove the new indicator to overwrite the DB record.
+ $idkey = $this->entityInfo['entity keys']['id'];
+ $entity->{$idkey} = $existing_entity->{$idkey};
+ unset($entity->is_new);
+ }
+ entity_save($this->entityType, $entity);
+ watchdog($this->entityType, 'Imported %entity %label.', $vars);
+ return t('Imported %entity %label.', $vars);
+
+ default:
+ return FALSE;
+ }
+ }
+
+ /**
+ * Entity submit builder invoked via entity_ui_form_submit_build_entity().
+ *
+ * Extracts the form values and updates the entity.
+ *
+ * The provided implementation makes use of the helper function
+ * entity_form_submit_build_entity() provided by core, which already invokes
+ * the field API attacher for fieldable entities.
+ *
+ * @return
+ * The updated entity.
+ *
+ * @see entity_ui_form_submit_build_entity()
+ */
+ public function entityFormSubmitBuildEntity($form, &$form_state) {
+ // Add the bundle property to the entity if the entity type supports bundles
+ // and the form provides a value for the bundle key. Especially new entities
+ // need to have their bundle property pre-populated before we invoke
+ // entity_form_submit_build_entity().
+ if (!empty($this->entityInfo['entity keys']['bundle']) && isset($form_state['values'][$this->entityInfo['entity keys']['bundle']])) {
+ $form_state[$this->entityType]->{$this->entityInfo['entity keys']['bundle']} = $form_state['values'][$this->entityInfo['entity keys']['bundle']];
+ }
+ entity_form_submit_build_entity($this->entityType, $form_state[$this->entityType], $form, $form_state);
+ return $form_state[$this->entityType];
+ }
+}
+
+/**
+ * UI controller providing UI for content entities.
+ *
+ * For a controller providing UI for bundleable content entities, see
+ * EntityBundleableUIController.
+ * For a controller providing admin UI for configuration entities, see
+ * EntityDefaultUIController.
+ */
+class EntityContentUIController extends EntityDefaultUIController {
+
+ /**
+ * Provides definitions for implementing hook_menu().
+ */
+ public function hook_menu() {
+ $items = parent::hook_menu();
+ $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object';
+
+ // Unset the manage entity path, as the provided UI is for admin entities.
+ unset($items[$this->path]);
+
+ $defaults = array(
+ 'file' => $this->entityInfo['admin ui']['file'],
+ 'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']),
+ );
+
+ // Add view, edit and delete menu items for content entities.
+ $items[$this->path . '/' . $wildcard] = array(
+ 'title callback' => 'entity_ui_get_page_title',
+ 'title arguments' => array('view', $this->entityType, $this->id_count),
+ 'page callback' => 'entity_ui_entity_page_view',
+ 'page arguments' => array($this->id_count),
+ 'load arguments' => array($this->entityType),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('view', $this->entityType, $this->id_count),
+ ) + $defaults;
+ $items[$this->path . '/' . $wildcard . '/view'] = array(
+ 'title' => 'View',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'load arguments' => array($this->entityType),
+ 'weight' => -10,
+ ) + $defaults;
+ $items[$this->path . '/' . $wildcard . '/edit'] = array(
+ 'page callback' => 'entity_ui_get_form',
+ 'page arguments' => array($this->entityType, $this->id_count),
+ 'load arguments' => array($this->entityType),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('edit', $this->entityType, $this->id_count),
+ 'title' => 'Edit',
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ ) + $defaults;
+ $items[$this->path . '/' . $wildcard . '/delete'] = array(
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $this->id_count, 'delete'),
+ 'load arguments' => array($this->entityType),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('delete', $this->entityType, $this->id_count),
+ 'title' => 'Delete',
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_INLINE,
+ 'file' => $this->entityInfo['admin ui']['file'],
+ 'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']),
+ ) + $defaults;
+
+ return $items;
+ }
+
+ /**
+ * Operation form submit callback.
+ */
+ public function operationFormSubmit($form, &$form_state) {
+ parent::operationFormSubmit($form, $form_state);
+ // The manage entity path is unset for the content entity UI.
+ $form_state['redirect'] = '<front>';
+ }
+}
+
+/**
+ * UI controller providing UI for bundleable content entities.
+ *
+ * Adds a bundle selection page to the entity/add path, analogously to the
+ * node/add path.
+ */
+class EntityBundleableUIController extends EntityContentUIController {
+
+ /**
+ * Provides definitions for implementing hook_menu().
+ */
+ public function hook_menu() {
+ $items = parent::hook_menu();
+
+ // Extend the 'add' path.
+ $items[$this->path . '/add'] = array(
+ 'title callback' => 'entity_ui_get_action_title',
+ 'title arguments' => array('add', $this->entityType),
+ 'page callback' => 'entity_ui_bundle_add_page',
+ 'page arguments' => array($this->entityType),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('create', $this->entityType),
+ 'type' => MENU_LOCAL_ACTION,
+ );
+ $items[$this->path . '/add/%'] = array(
+ 'title callback' => 'entity_ui_get_action_title',
+ 'title arguments' => array('add', $this->entityType, $this->id_count + 1),
+ 'page callback' => 'entity_ui_get_bundle_add_form',
+ 'page arguments' => array($this->entityType, $this->id_count + 1),
+ 'access callback' => 'entity_access',
+ 'access arguments' => array('create', $this->entityType),
+ );
+
+ if (!empty($this->entityInfo['admin ui']['file'])) {
+ // Add in the include file for the entity form.
+ foreach (array('/add', '/add/%') as $path_end) {
+ $items[$this->path . $path_end]['file'] = $this->entityInfo['admin ui']['file'];
+ $items[$this->path . $path_end]['file path'] = isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']);
+ }
+ }
+
+ return $items;
+ }
+}
+
+/**
+ * Form builder function for the overview form.
+ *
+ * @see EntityDefaultUIController::overviewForm()
+ */
+function entity_ui_overview_form($form, &$form_state, $entity_type) {
+ return entity_ui_controller($entity_type)->overviewForm($form, $form_state);
+}
+
+/**
+ * Form builder for the entity operation form.
+ *
+ * @see EntityDefaultUIController::operationForm()
+ */
+function entity_ui_operation_form($form, &$form_state, $entity_type, $entity, $op) {
+ $form_state['op'] = $op;
+ return entity_ui_controller($entity_type)->operationForm($form, $form_state, $entity, $op);
+}
+
+/**
+ * Form wrapper the main entity form.
+ *
+ * @see entity_ui_form_defaults()
+ */
+function entity_ui_main_form_defaults($form, &$form_state, $entity = NULL, $op = NULL) {
+ // Now equals entity_ui_form_defaults() but is still here to keep backward
+ // compatability.
+ return entity_ui_form_defaults($form, $form_state, $form_state['entity_type'], $entity, $op);
+}
+
+/**
+ * Clones the entity object and makes sure it will get saved as new entity.
+ *
+ * @return
+ * The cloned entity object.
+ */
+function entity_ui_clone_entity($entity_type, $entity) {
+ // Clone the entity and make sure it will get saved as a new entity.
+ $entity = clone $entity;
+
+ $entity_info = entity_get_info($entity_type);
+ $entity->{$entity_info['entity keys']['id']} = FALSE;
+ if (!empty($entity_info['entity keys']['name'])) {
+ $entity->{$entity_info['entity keys']['name']} = FALSE;
+ }
+ $entity->is_new = TRUE;
+
+ // Make sure the status of a cloned exportable is custom.
+ if (!empty($entity_info['exportable'])) {
+ $status_key = isset($entity_info['entity keys']['status']) ? $entity_info['entity keys']['status'] : 'status';
+ $entity->$status_key = ENTITY_CUSTOM;
+ }
+ return $entity;
+}
+
+/**
+ * Form wrapper callback for all entity ui forms.
+ *
+ * This callback makes sure the form state is properly initialized and sets
+ * some useful default titles.
+ *
+ * @see EntityDefaultUIController::hook_forms()
+ */
+function entity_ui_form_defaults($form, &$form_state, $entity_type, $entity = NULL, $op = NULL) {
+ $defaults = array(
+ 'entity_type' => $entity_type,
+ );
+ if (isset($entity)) {
+ $defaults[$entity_type] = $entity;
+ }
+ if (isset($op)) {
+ $defaults['op'] = $op;
+ }
+ $form_state += $defaults;
+ if (isset($op)) {
+ drupal_set_title(entity_ui_get_page_title($op, $entity_type, $entity), PASS_THROUGH);
+ }
+ // Add in handlers pointing to the controller for the forms implemented by it.
+ if (isset($form_state['build_info']['base_form_id']) && $form_state['build_info']['base_form_id'] != $entity_type . '_form') {
+ $form['#validate'][] = 'entity_ui_controller_form_validate';
+ $form['#submit'][] = 'entity_ui_controller_form_submit';
+ }
+ return $form;
+}
+
+/**
+ * Validation callback for forms implemented by the UI controller.
+ */
+function entity_ui_controller_form_validate($form, &$form_state) {
+ // Remove 'entity_ui_' prefix and the '_form' suffix.
+ $base = substr($form_state['build_info']['base_form_id'], 10, -5);
+ $method = $base . 'FormValidate';
+ entity_ui_controller($form_state['entity_type'])->$method($form, $form_state);
+}
+
+/**
+ * Submit callback for forms implemented by the UI controller.
+ */
+function entity_ui_controller_form_submit($form, &$form_state) {
+ // Remove 'entity_ui_' prefix and the '_form' suffix.
+ $base = substr($form_state['build_info']['base_form_id'], 10, -5);
+ $method = $base . 'FormSubmit';
+ entity_ui_controller($form_state['entity_type'])->$method($form, $form_state);
+}
+
+/**
+ * Submit builder for the main entity form, which extracts the form values and updates the entity.
+ *
+ * This is a helper function for entities making use of the entity UI
+ * controller.
+ *
+ * @return
+ * The updated entity.
+ *
+ * @see EntityDefaultUIController::hook_forms()
+ * @see EntityDefaultUIController::entityFormSubmitBuildEntity()
+ */
+function entity_ui_form_submit_build_entity($form, &$form_state) {
+ return entity_ui_controller($form_state['entity_type'])->entityFormSubmitBuildEntity($form, $form_state);
+}
+
+/**
+ * Validation callback for machine names of exportables.
+ *
+ * We don't allow numeric machine names, as entity_load() treats them as the
+ * numeric identifier and they are easily confused with ids in general.
+ */
+function entity_ui_validate_machine_name($element, &$form_state) {
+ if (is_numeric($element['#value'])) {
+ form_error($element, t('Machine-readable names must not consist of numbers only.'));
+ }
+}
+
+/**
+ * Returns HTML for an entity on the entity overview listing.
+ *
+ * @ingroup themeable
+ */
+function theme_entity_ui_overview_item($variables) {
+ $output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']);
+ if ($variables['name']) {
+ $output .= ' <small> (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')</small>';
+ }
+ return $output;
+}
+
diff --git a/sites/all/modules/entity/includes/entity.wrapper.inc b/sites/all/modules/entity/includes/entity.wrapper.inc
new file mode 100644
index 000000000..06b89adf3
--- /dev/null
+++ b/sites/all/modules/entity/includes/entity.wrapper.inc
@@ -0,0 +1,1224 @@
+<?php
+
+/**
+ * @file
+ * Provides wrappers allowing easy usage of the entity metadata.
+ */
+
+/**
+ * A common base class for all wrappers.
+ */
+abstract class EntityMetadataWrapper {
+
+ protected $type;
+ protected $data;
+ protected $info;
+ protected $cache = array();
+
+ /**
+ * Construct a new wrapper object.
+ *
+ * @param $type
+ * The type of the passed data.
+ * @param $data
+ * Optional. The data to wrap.
+ * @param $info
+ * Optional. Used internally to pass info about properties down the tree.
+ */
+ public function __construct($type, $data = NULL, $info = array()) {
+ $this->type = $type;
+ $this->info = $info + array(
+ 'langcode' => NULL,
+ );
+ $this->info['type'] = $type;
+ if (isset($data)) {
+ $this->set($data);
+ }
+ }
+
+ /**
+ * Gets info about the wrapped data.
+ *
+ * @return Array
+ * Keys set are all keys as specified for a property in hook_entity_info()
+ * as well as possible the following keys:
+ * - name: If this wraps a property, the name of the property.
+ * - parent: The parent wrapper, if any.
+ * - langcode: The language code, if this data is language specific.
+ */
+ public function info() {
+ return $this->info;
+ }
+
+ /**
+ * Gets the (entity)type of the wrapped data.
+ */
+ public function type() {
+ return $this->type;
+ }
+
+ /**
+ * Returns the wrapped data. If no options are given the data is returned as
+ * described in the info.
+ *
+ * @param $options
+ * (optional) A keyed array of options:
+ * - sanitize: A boolean flag indicating that textual properties should be
+ * sanitized for display to a web browser. Defaults to FALSE.
+ * - decode: If set to TRUE and some textual data is already sanitized, it
+ * strips HTML tags and decodes HTML entities. Defaults to FALSE.
+ *
+ * @return
+ * The value of the wrapped data. If the data property is not set, NULL
+ * is returned.
+ *
+ * @throws EntityMetadataWrapperException
+ * In case there are no data values available to the wrapper, an exception
+ * is thrown. E.g. if the value for an entity property is to be retrieved
+ * and there is no entity available, the exception is thrown. However, if
+ * an entity is available but the property is not set, NULL is returned.
+ */
+ public function value(array $options = array()) {
+ if (!$this->dataAvailable() && isset($this->info['parent'])) {
+ throw new EntityMetadataWrapperException('Missing data values.');
+ }
+ if (!isset($this->data) && isset($this->info['name'])) {
+ $this->data = $this->info['parent']->getPropertyValue($this->info['name'], $this->info);
+ }
+ return $this->data;
+ }
+
+ /**
+ * Returns the raw, unprocessed data. Most times this is the same as returned
+ * by value(), however for already processed and sanitized textual data, this
+ * will return the unprocessed data in contrast to value().
+ */
+ public function raw() {
+ if (!$this->dataAvailable()) {
+ throw new EntityMetadataWrapperException('Missing data values.');
+ }
+ if (isset($this->info['name']) && isset($this->info['parent'])) {
+ return $this->info['parent']->getPropertyRaw($this->info['name'], $this->info);
+ }
+ // Else return the usual value, which should be raw in this case.
+ return $this->value();
+ }
+
+ /**
+ * Returns whether data is available to work with.
+ *
+ * @return
+ * If we operate without any data FALSE, else TRUE.
+ */
+ protected function dataAvailable() {
+ return isset($this->data) || (isset($this->info['parent']) && $this->info['parent']->dataAvailable());
+ }
+
+ /**
+ * Set a new data value.
+ */
+ public function set($value) {
+ if (!$this->validate($value)) {
+ throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
+ }
+ $this->clear();
+ $this->data = $value;
+ $this->updateParent($value);
+ return $this;
+ }
+
+ /**
+ * Updates the parent data structure of a data property with the latest data value.
+ */
+ protected function updateParent($value) {
+ if (isset($this->info['parent'])) {
+ $this->info['parent']->setProperty($this->info['name'], $value);
+ }
+ }
+
+ /**
+ * Returns whether $value is a valid value to set.
+ */
+ public function validate($value) {
+ if (isset($value) && !entity_property_verify_data_type($value, $this->type)) {
+ return FALSE;
+ }
+ // Only proceed with further checks if this is not a list item. If this is
+ // a list item, the checks are performed on the list property level.
+ if (isset($this->info['parent']) && $this->info['parent'] instanceof EntityListWrapper) {
+ return TRUE;
+ }
+ if (!isset($value) && !empty($this->info['required'])) {
+ // Do not allow NULL values if the property is required.
+ return FALSE;
+ }
+ return !isset($this->info['validation callback']) || call_user_func($this->info['validation callback'], $value, $this->info);
+ }
+
+ public function __toString() {
+ return isset($this->info) ? 'Property ' . $this->info['name'] : $this->type;
+ }
+
+ /**
+ * Clears the data value and the wrapper cache.
+ */
+ protected function clear() {
+ $this->data = NULL;
+ foreach ($this->cache as $wrapper) {
+ $wrapper->clear();
+ }
+ }
+
+ /**
+ * Returns the options list specifying possible values for the property, if
+ * defined.
+ *
+ * @param $op
+ * (optional) One of 'edit' or 'view'. In case the list of possible values
+ * a user could set for a property differs from the list of values a
+ * property could have, $op determines which options should be returned.
+ * Defaults to 'edit'.
+ * E.g. all possible roles a user could have include the anonymous and the
+ * authenticated user roles, while those roles cannot be added to a user
+ * account. So their options would be included for 'view', but for 'edit'
+ * not.
+ *
+ * @return
+ * An array as used by hook_options_list() or FALSE.
+ */
+ public function optionsList($op = 'edit') {
+ if (isset($this->info['options list']) && is_callable($this->info['options list'])) {
+ $name = isset($this->info['name']) ? $this->info['name'] : NULL;
+ return call_user_func($this->info['options list'], $name, $this->info, $op);
+ }
+ return FALSE;
+ }
+
+ /**
+ * Returns the label for the currently set property value if there is one
+ * available, i.e. if an options list has been specified.
+ */
+ public function label() {
+ if ($options = $this->optionsList('view')) {
+ $options = entity_property_options_flatten($options);
+ $value = $this->value();
+ if (is_scalar($value) && isset($options[$value])) {
+ return $options[$value];
+ }
+ }
+ }
+
+ /**
+ * Determines whether the given user has access to view or edit this property.
+ * Apart from relying on access metadata of properties, this takes into
+ * account information about entity level access, if available:
+ * - Referenced entities can only be viewed, when the user also has
+ * permission to view the entity.
+ * - A property may be only edited, if the user has permission to update the
+ * entity containing the property.
+ *
+ * @param $op
+ * The operation being performed. One of 'view' or 'edit.
+ * @param $account
+ * The user to check for. Leave it to NULL to check for the global user.
+ * @return boolean
+ * Whether access to entity property is allowed for the given operation.
+ * However if we wrap no data, it returns whether access is allowed to the
+ * property of all entities of this type.
+ * If there is no access information for this property, TRUE is returned.
+ */
+ public function access($op, $account = NULL) {
+ return !empty($this->info['parent']) ? $this->info['parent']->propertyAccess($this->info['name'], $op, $account) : TRUE;
+ }
+
+ /**
+ * Prepare for serializiation.
+ */
+ public function __sleep() {
+ $vars = get_object_vars($this);
+ unset($vars['cache']);
+ return drupal_map_assoc(array_keys($vars));
+ }
+}
+
+/**
+ * Wraps a single value.
+ */
+class EntityValueWrapper extends EntityMetadataWrapper {
+
+ /**
+ * Overrides EntityMetadataWrapper#value().
+ * Sanitizes or decode textual data if necessary.
+ */
+ public function value(array $options = array()) {
+ $data = parent::value();
+ if ($this->type == 'text' && isset($data)) {
+ $info = $this->info + array('sanitized' => FALSE, 'sanitize' => 'check_plain');
+ $options += array('sanitize' => FALSE, 'decode' => FALSE);
+ if ($options['sanitize'] && !$info['sanitized']) {
+ return call_user_func($info['sanitize'], $data);
+ }
+ elseif ($options['decode'] && $info['sanitized']) {
+ return decode_entities(strip_tags($data));
+ }
+ }
+ return $data;
+ }
+}
+
+/**
+ * Provides a general wrapper for any data structure. For this to work the
+ * metadata has to be passed during construction.
+ */
+class EntityStructureWrapper extends EntityMetadataWrapper implements IteratorAggregate {
+
+ protected $propertyInfo = array(), $propertyInfoAltered = FALSE;
+ protected $langcode = LANGUAGE_NONE;
+
+ protected $propertyInfoDefaults = array(
+ 'type' => 'text',
+ 'getter callback' => 'entity_property_verbatim_get',
+ 'clear' => array(),
+ );
+
+ /**
+ * Construct a new EntityStructureWrapper object.
+ *
+ * @param $type
+ * The type of the passed data.
+ * @param $data
+ * Optional. The data to wrap.
+ * @param $info
+ * Used to for specifying metadata about the data and internally to pass
+ * info about properties down the tree. For specifying metadata known keys
+ * are:
+ * - property info: An array of info about the properties of the wrapped
+ * data structure. It has to contain an array of property info in the same
+ * structure as used by hook_entity_property_info().
+ */
+ public function __construct($type, $data = NULL, $info = array()) {
+ parent::__construct($type, $data, $info);
+ $this->info += array('property defaults' => array());
+ $info += array('property info' => array());
+ $this->propertyInfo['properties'] = $info['property info'];
+ }
+
+ /**
+ * May be used to lazy-load additional info about the data, depending on the
+ * concrete passed data.
+ */
+ protected function spotInfo() {
+ // Apply the callback if set, such that the caller may alter the info.
+ if (!empty($this->info['property info alter']) && !$this->propertyInfoAltered) {
+ $this->propertyInfo = call_user_func($this->info['property info alter'], $this, $this->propertyInfo);
+ $this->propertyInfoAltered = TRUE;
+ }
+ }
+
+ /**
+ * Gets the info about the given property.
+ *
+ * @param $name
+ * The name of the property. If not given, info about all properties will
+ * be returned.
+ * @throws EntityMetadataWrapperException
+ * If there is no such property.
+ * @return
+ * An array of info about the property.
+ */
+ public function getPropertyInfo($name = NULL) {
+ $this->spotInfo();
+ if (!isset($name)) {
+ return $this->propertyInfo['properties'];
+ }
+ if (!isset($this->propertyInfo['properties'][$name])) {
+ throw new EntityMetadataWrapperException('Unknown data property ' . check_plain($name) . '.');
+ }
+ return $this->propertyInfo['properties'][$name] + $this->info['property defaults'] + $this->propertyInfoDefaults;
+ }
+
+ /**
+ * Returns a reference on the property info.
+ *
+ * If possible, use the property info alter callback for spotting metadata.
+ * The reference may be used to alter the property info for any remaining
+ * cases, e.g. if additional metadata has been asserted.
+ */
+ public function &refPropertyInfo() {
+ return $this->propertyInfo;
+ }
+
+ /**
+ * Sets a new language to use for retrieving properties.
+ *
+ * @param $langcode
+ * The language code of the language to set.
+ * @return EntityWrapper
+ */
+ public function language($langcode = LANGUAGE_NONE) {
+ if ($langcode != $this->langcode) {
+ $this->langcode = $langcode;
+ $this->cache = array();
+ }
+ return $this;
+ }
+
+ /**
+ * Gets the language used for retrieving properties.
+ *
+ * @return String
+ * The language object of the language or NULL for the default language.
+ *
+ * @see EntityStructureWrapper::language()
+ */
+ public function getPropertyLanguage() {
+ if ($this->langcode != LANGUAGE_NONE && $list = language_list()) {
+ if (isset($list[$this->langcode])) {
+ return $list[$this->langcode];
+ }
+ }
+ return NULL;
+ }
+
+ /**
+ * Get the wrapper for a property.
+ *
+ * @return
+ * An instance of EntityMetadataWrapper.
+ */
+ public function get($name) {
+ // Look it up in the cache if possible.
+ if (!array_key_exists($name, $this->cache)) {
+ if ($info = $this->getPropertyInfo($name)) {
+ $info += array('parent' => $this, 'name' => $name, 'langcode' => $this->langcode, 'property defaults' => array());
+ $info['property defaults'] += $this->info['property defaults'];
+ $this->cache[$name] = entity_metadata_wrapper($info['type'], NULL, $info);
+ }
+ else {
+ throw new EntityMetadataWrapperException('There is no property ' . check_plain($name) . " for this entity.");
+ }
+ }
+ return $this->cache[$name];
+ }
+
+ /**
+ * Magic method: Get a wrapper for a property.
+ */
+ public function __get($name) {
+ if (strpos($name, 'krumo') === 0) {
+ // #914934 Ugly workaround to allow krumo to write its recursion property.
+ // This is necessary to make dpm() work without throwing exceptions.
+ return NULL;
+ }
+ $get = $this->get($name);
+ return $get;
+ }
+
+ /**
+ * Magic method: Set a property.
+ */
+ public function __set($name, $value) {
+ if (strpos($name, 'krumo') === 0) {
+ // #914934 Ugly workaround to allow krumo to write its recursion property.
+ // This is necessary to make dpm() work without throwing exceptions.
+ $this->$name = $value;
+ }
+ else {
+ $this->get($name)->set($value);
+ }
+ }
+
+ /**
+ * Gets the value of a property.
+ */
+ protected function getPropertyValue($name, &$info) {
+ $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE);
+ $data = $this->value();
+ if (!isset($data)) {
+ throw new EntityMetadataWrapperException('Unable to get the data property ' . check_plain($name) . ' as the parent data structure is not set.');
+ }
+ return $info['getter callback']($data, $options, $name, $this->type, $info);
+ }
+
+ /**
+ * Gets the raw value of a property.
+ */
+ protected function getPropertyRaw($name, &$info) {
+ if (!empty($info['raw getter callback'])) {
+ $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE);
+ $data = $this->value();
+ if (!isset($data)) {
+ throw new EntityMetadataWrapperException('Unable to get the data property ' . check_plain($name) . ' as the parent data structure is not set.');
+ }
+ return $info['raw getter callback']($data, $options, $name, $this->type, $info);
+ }
+ return $this->getPropertyValue($name, $info);
+ }
+
+ /**
+ * Sets a property.
+ */
+ protected function setProperty($name, $value) {
+ $info = $this->getPropertyInfo($name);
+ if (!empty($info['setter callback'])) {
+ $data = $this->value();
+
+ // In case the data structure is not set, support simple auto-creation
+ // for arrays. Else an exception is thrown.
+ if (!isset($data)) {
+ if (!empty($this->info['auto creation']) && !($this instanceof EntityDrupalWrapper)) {
+ $data = $this->info['auto creation']($name, $this->info);
+ }
+ else {
+ throw new EntityMetadataWrapperException('Unable to set the data property ' . check_plain($name) . ' as the parent data structure is not set.');
+ }
+ }
+
+ // Invoke the setter callback for updating our data.
+ $info['setter callback']($data, $name, $value, $this->langcode, $this->type, $info);
+
+ // If the setter has not thrown any exceptions, proceed and apply the
+ // update to the current and any parent wrappers as necessary.
+ $data = $this->info['type'] == 'entity' ? $this : $data;
+ $this->set($data);
+
+ // Clear the cache of properties dependent on this value.
+ foreach ($info['clear'] as $name) {
+ if (isset($this->cache[$name])) {
+ $this->cache[$name]->clear();
+ }
+ }
+ }
+ else {
+ throw new EntityMetadataWrapperException('Entity property ' . check_plain($name) . " doesn't support writing.");
+ }
+ }
+
+ protected function propertyAccess($name, $op, $account = NULL) {
+ $info = $this->getPropertyInfo($name);
+
+ // If a property should be edited and this is part of an entity, make sure
+ // the user has update access for this entity.
+ if ($op == 'edit') {
+ $entity = $this;
+ while (!($entity instanceof EntityDrupalWrapper) && isset($entity->info['parent'])) {
+ $entity = $entity->info['parent'];
+ }
+ if ($entity instanceof EntityDrupalWrapper && $entity->entityAccess('update', $account) === FALSE) {
+ return FALSE;
+ }
+ }
+ if (!empty($info['access callback'])) {
+ $data = $this->dataAvailable() ? $this->value() : NULL;
+ return call_user_func($info['access callback'], $op, $name, $data, $account, $this->type);
+ }
+ elseif ($op == 'edit' && isset($info['setter permission'])) {
+ return user_access($info['setter permission'], $account);
+ }
+ // If access is unknown, we return TRUE.
+ return TRUE;
+ }
+
+ /**
+ * Magic method: Can be used to check if a property is known.
+ */
+ public function __isset($name) {
+ $this->spotInfo();
+ return isset($this->propertyInfo['properties'][$name]);
+ }
+
+ public function getIterator() {
+ $this->spotInfo();
+ return new EntityMetadataWrapperIterator($this, array_keys($this->propertyInfo['properties']));
+ }
+
+ /**
+ * Returns the identifier of the data structure. If there is none, NULL is
+ * returned.
+ */
+ public function getIdentifier() {
+ return isset($this->id) && $this->dataAvailable() ? $this->id->value() : NULL;
+ }
+
+ /**
+ * Prepare for serializiation.
+ */
+ public function __sleep() {
+ $vars = parent::__sleep();
+ unset($vars['propertyInfoDefaults']);
+ return $vars;
+ }
+
+ public function clear() {
+ $this->propertyInfoAltered = FALSE;
+ parent::clear();
+ }
+}
+
+/**
+ * Provides a wrapper for entities registrered in hook_entity_info().
+ *
+ * The wrapper eases applying getter and setter callbacks of entity properties
+ * specified in hook_entity_property_info().
+ */
+class EntityDrupalWrapper extends EntityStructureWrapper {
+
+ /**
+ * Contains the entity id.
+ */
+ protected $id = FALSE;
+ protected $bundle;
+ protected $entityInfo;
+
+ /**
+ * Construct a new EntityDrupalWrapper object.
+ *
+ * @param $type
+ * The type of the passed data.
+ * @param $data
+ * Optional. The entity to wrap or its identifier.
+ * @param $info
+ * Optional. Used internally to pass info about properties down the tree.
+ */
+ public function __construct($type, $data = NULL, $info = array()) {
+ parent::__construct($type, $data, $info);
+ $this->setUp();
+ }
+
+ protected function setUp() {
+ $this->propertyInfo = entity_get_property_info($this->type) + array('properties' => array());
+ $info = $this->info + array('property info' => array(), 'bundle' => NULL);
+ $this->propertyInfo['properties'] += $info['property info'];
+ $this->bundle = $info['bundle'];
+ $this->entityInfo = entity_get_info($this->type);
+ if (isset($this->bundle)) {
+ $this->spotBundleInfo(FALSE);
+ }
+ }
+
+ /**
+ * Sets the entity internally accepting both the entity id and object.
+ */
+ protected function setEntity($data) {
+ // For entities we allow getter callbacks to return FALSE, which we
+ // interpret like NULL values as unset properties.
+ if (isset($data) && $data !== FALSE && !is_object($data)) {
+ $this->id = $data;
+ $this->data = FALSE;
+ }
+ elseif (is_object($data) && $data instanceof EntityDrupalWrapper) {
+ // We got a wrapped entity passed, so take over its values.
+ $this->id = $data->id;
+ $this->data = $data->data;
+ // For generic entity references, also update the entity type accordingly.
+ if ($this->info['type'] == 'entity') {
+ $this->type = $data->type;
+ }
+ }
+ elseif (is_object($data)) {
+ // We got the entity object passed.
+ $this->data = $data;
+ $id = entity_id($this->type, $data);
+ $this->id = isset($id) ? $id : FALSE;
+ }
+ else {
+ $this->id = FALSE;
+ $this->data = NULL;
+ }
+ }
+
+ /**
+ * Used to lazy-load bundle info. So the wrapper can be loaded e.g. just
+ * for setting without the data being loaded.
+ */
+ protected function spotInfo() {
+ if (!$this->propertyInfoAltered) {
+ if ($this->info['type'] == 'entity' && $this->dataAvailable() && $this->value()) {
+ // Add in entity-type specific details.
+ $this->setUp();
+ }
+ $this->spotBundleInfo(TRUE);
+ parent::spotInfo();
+ $this->propertyInfoAltered = TRUE;
+ }
+ }
+
+ /**
+ * Tries to determine the bundle and adds in the according property info.
+ *
+ * @param $load
+ * Whether the entity should be loaded to spot the info if necessary.
+ */
+ protected function spotBundleInfo($load = TRUE) {
+ // Like entity_extract_ids() assume the entity type if no key is given.
+ if (empty($this->entityInfo['entity keys']['bundle']) && $this->type != 'entity') {
+ $this->bundle = $this->type;
+ }
+ // Detect the bundle if not set yet and add in properties from the bundle.
+ elseif (!$this->bundle && $load && $this->dataAvailable()) {
+ try {
+ if ($entity = $this->value()) {
+ list($id, $vid, $bundle) = entity_extract_ids($this->type, $entity);
+ $this->bundle = $bundle;
+ }
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // Loading data failed, so we cannot derive the used bundle.
+ }
+ }
+
+ if ($this->bundle && isset($this->propertyInfo['bundles'][$this->bundle])) {
+ $bundle_info = (array) $this->propertyInfo['bundles'][$this->bundle] + array('properties' => array());
+ // Allow bundles to re-define existing properties, such that the bundle
+ // can add in more bundle-specific details like the bundle of a referenced
+ // entity.
+ $this->propertyInfo['properties'] = $bundle_info['properties'] + $this->propertyInfo['properties'];
+ }
+ }
+
+ /**
+ * Returns the identifier of the wrapped entity.
+ *
+ * @see entity_id()
+ */
+ public function getIdentifier() {
+ return $this->dataAvailable() ? $this->value(array('identifier' => TRUE)) : NULL;
+ }
+
+ /**
+ * Returns the bundle of an entity, or FALSE if it has no bundles.
+ */
+ public function getBundle() {
+ if ($this->dataAvailable()) {
+ $this->spotInfo();
+ return $this->bundle;
+ }
+ }
+
+ /**
+ * Overridden.
+ *
+ * @param $options
+ * An array of options. Known keys:
+ * - identifier: If set to TRUE, the entity identifier is returned.
+ */
+ public function value(array $options = array()) {
+ // Try loading the data via the getter callback if there is none yet.
+ if (!isset($this->data)) {
+ $this->setEntity(parent::value());
+ }
+ if (!empty($options['identifier'])) {
+ return $this->id;
+ }
+ elseif (!$this->data && !empty($this->id)) {
+ // Lazy load the entity if necessary.
+ $return = entity_load($this->type, array($this->id));
+ // In case the entity cannot be loaded, we return NULL just as for empty
+ // properties.
+ $this->data = $return ? reset($return) : NULL;
+ }
+ return $this->data;
+ }
+
+ /**
+ * Returns the entity prepared for rendering.
+ *
+ * @see entity_view()
+ */
+ public function view($view_mode = 'full', $langcode = NULL, $page = NULL) {
+ return entity_view($this->type(), array($this->value()), $view_mode, $langcode, $page);
+ }
+
+ /**
+ * Overridden to support setting the entity by either the object or the id.
+ */
+ public function set($value) {
+ if (!$this->validate($value)) {
+ throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
+ }
+ if ($this->info['type'] == 'entity' && $value === $this) {
+ // Nothing to do.
+ return $this;
+ }
+ $previous_id = $this->id;
+ $previous_type = $this->type;
+ // Set value, so we get the identifier and pass it to the normal setter.
+ $this->clear();
+ $this->setEntity($value);
+ // Generally, we have to update the parent only if the entity reference
+ // has changed. In case of a generic entity reference, we pass the entity
+ // wrapped. Else we just pass the id of the entity to the setter callback.
+ if ($this->info['type'] == 'entity' && ($previous_id != $this->id || $previous_type != $this->type)) {
+ // We need to clone the wrapper we pass through as value, so it does not
+ // get cleared when the current wrapper instance gets cleared.
+ $this->updateParent(clone $this);
+ }
+ // In case the entity has been unset, we cannot properly detect changes as
+ // the previous id defaults to FALSE for unloaded entities too. So in that
+ // case we just always update the parent.
+ elseif ($this->id === FALSE && !$this->data) {
+ $this->updateParent(NULL);
+ }
+ elseif ($previous_id !== $this->id) {
+ $this->updateParent($this->id);
+ }
+ return $this;
+ }
+
+ /**
+ * Overridden.
+ */
+ public function clear() {
+ $this->id = NULL;
+ $this->bundle = isset($this->info['bundle']) ? $this->info['bundle'] : NULL;
+ if ($this->type != $this->info['type']) {
+ // Reset entity info / property info based upon the info provided during
+ // the creation of the wrapper.
+ $this->type = $this->info['type'];
+ $this->setUp();
+ }
+ parent::clear();
+ }
+
+ /**
+ * Overridden.
+ */
+ public function type() {
+ // In case of a generic entity wrapper, load the data first to determine
+ // the type of the concrete entity.
+ if ($this->dataAvailable() && $this->info['type'] == 'entity') {
+ try {
+ $this->value(array('identifier' => TRUE));
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // If loading data fails, we cannot determine the concrete entity type.
+ }
+ }
+ return $this->type;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Note that this method checks property access, but can be used for checking
+ * entity access *only* if the wrapper is not a property (i.e. has no parent
+ * wrapper).
+ * To be safe, better use EntityDrupalWrapper::entityAccess() for checking
+ * entity access.
+ */
+ public function access($op, $account = NULL) {
+ if (!empty($this->info['parent'])) {
+ // If this is a property, make sure the user is able to view the
+ // currently referenced entity also.
+ if ($this->entityAccess('view', $account) === FALSE) {
+ return FALSE;
+ }
+ if (parent::access($op, $account) === FALSE) {
+ return FALSE;
+ }
+ // If access is unknown, we return TRUE.
+ return TRUE;
+ }
+ else {
+ // This is not a property, so fallback on entity access.
+ return $this->entityAccess($op == 'edit' ? 'update' : 'view', $account);
+ }
+ }
+
+ /**
+ * Checks whether the operation $op is allowed on the entity.
+ *
+ * @see entity_access()
+ */
+ public function entityAccess($op, $account = NULL) {
+ $entity = $this->dataAvailable() ? $this->value() : NULL;
+ // The value() method could return FALSE on entities such as user 0, so we
+ // need to use NULL instead to conform to the expectations of
+ // entity_access().
+ if ($entity === FALSE) {
+ $entity = NULL;
+ }
+ return entity_access($op, $this->type, $entity, $account);
+ }
+
+ /**
+ * Permanently save the wrapped entity.
+ *
+ * @throws EntityMetadataWrapperException
+ * If the entity type does not support saving.
+ *
+ * @return EntityDrupalWrapper
+ */
+ public function save() {
+ if ($this->data) {
+ if (!entity_type_supports($this->type, 'save')) {
+ throw new EntityMetadataWrapperException("There is no information about how to save entities of type " . check_plain($this->type) . '.');
+ }
+ entity_save($this->type, $this->data);
+ // On insert, update the identifier afterwards.
+ if (!$this->id) {
+ list($this->id, , ) = entity_extract_ids($this->type, $this->data);
+ }
+ }
+ // If the entity hasn't been loaded yet, don't bother saving it.
+ return $this;
+ }
+
+ /**
+ * Permanently delete the wrapped entity.
+ *
+ * @return EntityDrupalWrapper
+ */
+ public function delete() {
+ if ($this->dataAvailable() && $this->value()) {
+ $return = entity_delete($this->type, $this->id);
+ if ($return === FALSE) {
+ throw new EntityMetadataWrapperException("There is no information about how to delete entities of type " . check_plain($this->type) . '.');
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Gets the info about the wrapped entity.
+ */
+ public function entityInfo() {
+ return $this->entityInfo;
+ }
+
+ /**
+ * Returns the name of the key used by the entity for given entity key.
+ *
+ * @param $name
+ * One of 'id', 'name', 'bundle' or 'revision'.
+ * @return
+ * The name of the key used by the entity.
+ */
+ public function entityKey($name) {
+ return isset($this->entityInfo['entity keys'][$name]) ? $this->entityInfo['entity keys'][$name] : FALSE;
+ }
+
+ /**
+ * Returns the entity label.
+ *
+ * @see entity_label()
+ */
+ public function label() {
+ if ($entity = $this->value()) {
+ return entity_label($this->type, $entity);
+ }
+ }
+
+ /**
+ * Prepare for serializiation.
+ */
+ public function __sleep() {
+ $vars = parent::__sleep();
+ // Don't serialize the loaded entity and its property info.
+ unset($vars['data'], $vars['propertyInfo'], $vars['propertyInfoAltered'], $vars['entityInfo']);
+ // In case the entity is not saved yet, serialize the unsaved data.
+ if ($this->dataAvailable() && $this->id === FALSE) {
+ $vars['data'] = 'data';
+ }
+ return $vars;
+ }
+
+ public function __wakeup() {
+ $this->setUp();
+ if ($this->id !== FALSE) {
+ // Make sure data is set, so the entity will be loaded when needed.
+ $this->data = FALSE;
+ }
+ }
+}
+
+/**
+ * Wraps a list of values.
+ *
+ * If the wrapped data is a list of data, its numerical indexes may be used to
+ * retrieve wrappers for the list items. For that this wrapper implements
+ * ArrayAccess so it may be used like a usual numerically indexed array.
+ */
+class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggregate, ArrayAccess, Countable {
+
+ /**
+ * The type of contained items.
+ */
+ protected $itemType;
+
+ /**
+ * Whether this is a list of entities with a known entity type, i.e. for
+ * generic list of entities (list<entity>) this is FALSE.
+ */
+ protected $isEntityList;
+
+
+ public function __construct($type, $data = NULL, $info = array()) {
+ parent::__construct($type, NULL, $info);
+
+ $this->itemType = entity_property_list_extract_type($this->type);
+ if (!$this->itemType) {
+ $this->itemType = 'unknown';
+ }
+ $this->isEntityList = (bool) entity_get_info($this->itemType);
+
+ if (isset($data)) {
+ $this->set($data);
+ }
+ }
+
+ /**
+ * Get the wrapper for a single item.
+ *
+ * @return
+ * An instance of EntityMetadataWrapper.
+ */
+ public function get($delta) {
+ // Look it up in the cache if possible.
+ if (!array_key_exists($delta, $this->cache)) {
+ if (!isset($delta)) {
+ // The [] operator has been used so point at a new entry.
+ $values = parent::value();
+ $delta = $values ? max(array_keys($values)) + 1 : 0;
+ }
+ if (is_numeric($delta)) {
+ $info = array('parent' => $this, 'name' => $delta) + $this->info;
+ $this->cache[$delta] = entity_metadata_wrapper($this->itemType, NULL, $info);
+ }
+ else {
+ throw new EntityMetadataWrapperException('There can be only numerical keyed items in a list.');
+ }
+ }
+ return $this->cache[$delta];
+ }
+
+ protected function getPropertyValue($delta) {
+ // Make use parent::value() to easily by-pass any entity-loading.
+ $data = parent::value();
+ if (isset($data[$delta])) {
+ return $data[$delta];
+ }
+ }
+
+ protected function getPropertyRaw($delta) {
+ return $this->getPropertyValue($delta);
+ }
+
+ protected function setProperty($delta, $value) {
+ $data = parent::value();
+ if (is_numeric($delta)) {
+ $data[$delta] = $value;
+ $this->set($data);
+ }
+ }
+
+ protected function propertyAccess($delta, $op, $account = NULL) {
+ return $this->access($op, $account);
+ }
+
+ /**
+ * Returns the list as numerically indexed array.
+ *
+ * Note that a list of entities might contain stale entity references. In
+ * that case the wrapper and the identifier of a stale reference would be
+ * still accessible, however the entity object value would be NULL. That way,
+ * there may be NULL values in lists of entity objects due to stale entity
+ * references.
+ *
+ * @param $options
+ * An array of options. Known keys:
+ * - identifier: If set to TRUE for a list of entities, it won't be returned
+ * as list of fully loaded entity objects, but as a list of entity ids.
+ * Note that this list may contain ids of stale entity references.
+ */
+ public function value(array $options = array()) {
+ // For lists of entities fetch full entity objects before returning.
+ // Generic entity-wrappers need to be handled separately though.
+ if ($this->isEntityList && empty($options['identifier']) && $this->dataAvailable()) {
+ $list = parent::value();
+ $entities = $list ? entity_load($this->get(0)->type, $list) : array();
+ // Make sure to keep the array keys as present in the list.
+ foreach ($list as $key => $id) {
+ // In case the entity cannot be loaded, we return NULL just as for empty
+ // properties.
+ $list[$key] = isset($entities[$id]) ? $entities[$id] : NULL;
+ }
+ return $list;
+ }
+ return parent::value();
+ }
+
+ public function set($values) {
+ // Support setting lists of fully loaded entities.
+ if ($this->isEntityList && $values && is_object(reset($values))) {
+ foreach ($values as $key => $value) {
+ // Ignore outdated NULL value references in lists of entities.
+ if (isset($value)) {
+ list($id, $vid, $bundle) = entity_extract_ids($this->itemType, $value);
+ $values[$key] = $id;
+ }
+ }
+ }
+ return parent::set($values);
+ }
+
+ /**
+ * If we wrap a list, we return an iterator over the data list.
+ */
+ public function getIterator() {
+ // In case there is no data available, just iterate over the first item.
+ return new EntityMetadataWrapperIterator($this, $this->dataAvailable() ? array_keys(parent::value()) : array(0));
+ }
+
+ /**
+ * Implements the ArrayAccess interface.
+ */
+ public function offsetGet($delta) {
+ return $this->get($delta);
+ }
+
+ public function offsetExists($delta) {
+ return $this->dataAvailable() && ($data = $this->value()) && array_key_exists($delta, $data);
+ }
+
+ public function offsetSet($delta, $value) {
+ $this->get($delta)->set($value);
+ }
+
+ public function offsetUnset($delta) {
+ if ($this->offsetExists($delta)) {
+ unset($this->data[$delta]);
+ $this->set($this->data);
+ }
+ }
+
+ public function count() {
+ return $this->dataAvailable() ? count($this->value()) : 0;
+ }
+
+ /**
+ * Overridden.
+ */
+ public function validate($value) {
+ // Required lists may not be empty or unset.
+ if (!empty($this->info['required']) && empty($value)) {
+ return FALSE;
+ }
+ return parent::validate($value);
+ }
+
+ /**
+ * Returns the label for the list of set values if available.
+ */
+ public function label() {
+ if ($options = $this->optionsList('view')) {
+ $options = entity_property_options_flatten($options);
+ $labels = array_intersect_key($options, array_flip((array) parent::value()));
+ }
+ else {
+ // Get each label on its own, e.g. to support getting labels of a list
+ // of entities.
+ $labels = array();
+ foreach ($this as $key => $property) {
+ $label = $property->label();
+ if (!$label) {
+ return NULL;
+ }
+ $labels[] = $label;
+ }
+ }
+ return isset($labels) ? implode(', ', $labels) : NULL;
+ }
+}
+
+/**
+ * Provide a separate Exception so it can be caught separately.
+ */
+class EntityMetadataWrapperException extends Exception { }
+
+
+/**
+ * Allows to easily iterate over existing child wrappers.
+ */
+class EntityMetadataWrapperIterator implements RecursiveIterator {
+
+ protected $position = 0;
+ protected $wrapper, $keys;
+
+ public function __construct(EntityMetadataWrapper $wrapper, array $keys) {
+ $this->wrapper = $wrapper;
+ $this->keys = $keys;
+ }
+
+ function rewind() {
+ $this->position = 0;
+ }
+
+ function current() {
+ return $this->wrapper->get($this->keys[$this->position]);
+ }
+
+ function key() {
+ return $this->keys[$this->position];
+ }
+
+ function next() {
+ $this->position++;
+ }
+
+ function valid() {
+ return isset($this->keys[$this->position]);
+ }
+
+ public function hasChildren() {
+ return $this->current() instanceof IteratorAggregate;
+ }
+
+ public function getChildren() {
+ return $this->current()->getIterator();
+ }
+}
+
+/**
+ * An array object implementation keeping the reference on the given array so
+ * changes to the object are reflected in the passed array.
+ */
+class EntityMetadataArrayObject implements ArrayAccess, Countable, IteratorAggregate {
+
+ protected $data;
+
+ public function __construct(&$array) {
+ $this->data =& $array;
+ }
+
+ public function &getArray() {
+ return $this->data;
+ }
+
+ /**
+ * Implements the ArrayAccess interface.
+ */
+ public function offsetGet($delta) {
+ return $this->data[$delta];
+ }
+
+ public function offsetExists($delta) {
+ return array_key_exists($delta, $this->data);
+ }
+
+ public function offsetSet($delta, $value) {
+ $this->data[$delta] = $value;
+ }
+
+ public function offsetUnset($delta) {
+ unset($this->data[$delta]);
+ }
+
+ public function count() {
+ return count($this->data);
+ }
+
+ public function getIterator() {
+ return new ArrayIterator($this->data);
+ }
+}
diff --git a/sites/all/modules/entity/modules/book.info.inc b/sites/all/modules/entity/modules/book.info.inc
new file mode 100644
index 000000000..96629c365
--- /dev/null
+++ b/sites/all/modules/entity/modules/book.info.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Provides info about book nodes.
+ */
+
+/**
+ * Implements hook_entity_property_info_alter() on top of book module.
+ *
+ * @see entity_entity_property_info_alter()
+ */
+function entity_metadata_book_entity_property_info_alter(&$info) {
+ // Add meta-data about the added node properties.
+ $properties = &$info['node']['properties'];
+
+ $properties['book'] = array(
+ 'label' => t("Book"),
+ 'type' => 'node',
+ 'description' => t("If part of a book, the book to which this book page belongs."),
+ 'getter callback' => 'entity_metadata_book_get_properties',
+ );
+ $properties['book_ancestors'] = array(
+ 'label' => t("Book ancestors"),
+ 'type' => 'list<node>',
+ 'computed' => TRUE,
+ 'description' => t("If part of a book, a list of all book pages upwards in the book hierarchy."),
+ 'getter callback' => 'entity_metadata_book_get_properties',
+ );
+}
diff --git a/sites/all/modules/entity/modules/callbacks.inc b/sites/all/modules/entity/modules/callbacks.inc
new file mode 100644
index 000000000..26f802e63
--- /dev/null
+++ b/sites/all/modules/entity/modules/callbacks.inc
@@ -0,0 +1,1076 @@
+<?php
+
+/**
+ * @file
+ * Provides various callbacks for the whole core module integration.
+ */
+
+/**
+ * Callback for getting properties of an entity.
+ */
+function entity_metadata_entity_get_properties($entity, array $options, $name, $entity_type) {
+ if ($name == 'url') {
+ $return = entity_uri($entity_type, $entity);
+ return url($return['path'], $return['options'] + $options);
+ }
+}
+
+/**
+ * Callback for getting book node properties.
+ * @see entity_metadata_book_entity_info_alter()
+ */
+function entity_metadata_book_get_properties($node, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'book':
+ if (isset($node->book['bid'])) {
+ return $node->book['bid'];
+ }
+ return NULL;
+
+ case 'book_ancestors':
+ $ancestors = array();
+ while (!empty($node->book['plid'])) {
+ $link = book_link_load($node->book['plid']);
+ array_unshift($ancestors, $link['nid']);
+ $node = node_load($link['nid']);
+ }
+ return $ancestors;
+ }
+}
+
+/**
+ * Callback for getting comment properties.
+ * @see entity_metadata_comment_entity_info_alter()
+ */
+function entity_metadata_comment_get_properties($comment, array $options, $name) {
+ switch ($name) {
+ case 'name':
+ return $comment->name;
+
+ case 'mail':
+ if ($comment->uid != 0) {
+ $account = user_load($comment->uid);
+ return $account->mail;
+ }
+ return $comment->mail;
+
+ case 'edit_url':
+ return url('comment/edit/' . $comment->cid, $options);
+
+ case 'parent':
+ if (!empty($comment->pid)) {
+ return $comment->pid;
+ }
+ // There is no parent comment.
+ return NULL;
+ }
+}
+
+/**
+ * Callback for setting comment properties.
+ * @see entity_metadata_comment_entity_info_alter()
+ */
+function entity_metadata_comment_setter($comment, $name, $value) {
+ switch ($name) {
+ case 'node':
+ $comment->nid = $value;
+ // Also set the bundle name.
+ $node = node_load($value);
+ $comment->node_type = 'comment_node_' . $node->type;
+ break;
+ }
+}
+
+/**
+ * Callback for getting comment related node properties.
+ * @see entity_metadata_comment_entity_info_alter()
+ */
+function entity_metadata_comment_get_node_properties($node, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'comment_count':
+ return isset($node->comment_count) ? $node->comment_count : 0;
+
+ case 'comment_count_new':
+ return comment_num_new($node->nid);
+
+ case 'comments':
+ $select = db_select('comment', 'c')
+ ->fields('c', array('cid'))
+ ->condition('c.nid', $node->nid);
+ return array_keys($select->execute()->fetchAllKeyed(0, 0));
+ }
+}
+
+/**
+ * Getter callback for getting global languages.
+ */
+function entity_metadata_locale_get_languages($data, array $options, $name) {
+ return isset($GLOBALS[$name]) ? $GLOBALS[$name]->language : NULL;
+}
+
+/**
+ * Getter callback for getting the preferred user language.
+ */
+function entity_metadata_locale_get_user_language($account, array $options, $name) {
+ return user_preferred_language($account)->language;
+}
+
+/**
+ * Return the options lists for the node and comment status property.
+ */
+function entity_metadata_status_options_list() {
+ return array(
+ NODE_PUBLISHED => t('Published'),
+ NODE_NOT_PUBLISHED => t('Unpublished'),
+ );
+}
+
+/**
+ * Callback for getting node properties.
+ *
+ * @see entity_metadata_node_entity_info_alter()
+ */
+function entity_metadata_node_get_properties($node, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'is_new':
+ return empty($node->nid) || !empty($node->is_new);
+
+ case 'source':
+ if (!empty($node->tnid) && $source = node_load($node->tnid)) {
+ return $source;
+ }
+ return NULL;
+
+ case 'edit_url':
+ return url('node/' . $node->nid . '/edit', $options);
+
+ case 'author':
+ return !empty($node->uid) ? $node->uid : drupal_anonymous_user();
+ }
+}
+
+/**
+ * Callback for determing access for node revision related properties.
+ */
+function entity_metadata_node_revision_access($op, $name, $entity = NULL, $account = NULL) {
+ return $op == 'view' ? user_access('view revisions', $account) : user_access('administer nodes', $account);
+}
+
+/**
+ * Callback for getting poll properties.
+ * @see entity_metadata_poll_entity_info_alter()
+ */
+function entity_metadata_poll_node_get_properties($node, array $options, $name) {
+ $total_votes = $highest_votes = 0;
+ foreach ($node->choice as $choice) {
+ if ($choice['chvotes'] > $highest_votes) {
+ $winner = $choice;
+ $highest_votes = $choice['chvotes'];
+ }
+ $total_votes = $total_votes + $choice['chvotes'];
+ }
+
+ if ($name == 'poll_duration') {
+ return $node->runtime;
+ }
+ elseif ($name == 'poll_votes') {
+ return $total_votes;
+ }
+ elseif (!isset($winner)) {
+ // There is no poll winner yet.
+ return NULL;
+ }
+ switch ($name) {
+ case 'poll_winner_votes':
+ return $winner['chvotes'];
+
+ case 'poll_winner':
+ return $winner['chtext'];
+
+ case 'poll_winner_percent':
+ return ($winner['chvotes'] / $total_votes) * 100;
+ }
+}
+
+/**
+ * Callback for getting statistics properties.
+ * @see entity_metadata_statistics_entity_info_alter()
+ */
+function entity_metadata_statistics_node_get_properties($node, array $options, $name) {
+ $statistics = (array) statistics_get($node->nid);
+ $statistics += array('totalcount' => 0, 'daycount' => 0, 'timestamp' => NULL);
+
+ switch ($name) {
+ case 'views':
+ return $statistics['totalcount'];
+
+ case 'day_views':
+ return $statistics['daycount'];
+
+ case 'last_view':
+ return $statistics['timestamp'];
+ }
+}
+
+/**
+ * Access callback for restricted node statistics properties.
+ */
+function entity_metadata_statistics_properties_access($op, $property, $entity = NULL, $account = NULL) {
+ if ($property == 'views' && user_access('view post access counter', $account)) {
+ return TRUE;
+ }
+ return user_access('access statistics', $account);
+}
+
+/**
+ * Callback for getting site-wide properties.
+ * @see entity_metadata_system_entity_info_alter()
+ */
+function entity_metadata_system_get_properties($data = FALSE, array $options, $name) {
+ switch ($name) {
+ case 'name':
+ return variable_get('site_name', 'Drupal');
+
+ case 'url':
+ return url('<front>', $options);
+
+ case 'login_url':
+ return url('user', $options);
+
+ case 'current_user':
+ return $GLOBALS['user']->uid ? $GLOBALS['user']->uid : drupal_anonymous_user();
+
+ case 'current_date':
+ return REQUEST_TIME;
+
+ case 'current_page':
+ // Subsequent getters of the struct retrieve the actual values.
+ return array();
+
+ default:
+ return variable_get('site_' . $name, '');
+ }
+}
+
+/**
+ * Callback for getting properties for the current page request.
+ * @see entity_metadata_system_entity_info_alter()
+ */
+function entity_metadata_system_get_page_properties($data = array(), array $options, $name) {
+ switch ($name) {
+ case 'url':
+ return $GLOBALS['base_root'] . request_uri();
+ }
+}
+
+/**
+ * Callback for getting file properties.
+ * @see entity_metadata_system_entity_info_alter()
+ */
+function entity_metadata_system_get_file_properties($file, array $options, $name) {
+ switch ($name) {
+ case 'name':
+ return $file->filename;
+
+ case 'mime':
+ return $file->filemime;
+
+ case 'size':
+ return $file->filesize;
+
+ case 'url':
+ return url(file_create_url($file->uri), $options);
+
+ case 'owner':
+ return $file->uid;
+ }
+}
+
+/**
+ * Callback for getting term properties.
+ *
+ * @see entity_metadata_taxonomy_entity_info_alter()
+ */
+function entity_metadata_taxonomy_term_get_properties($term, array $options, $name) {
+ switch ($name) {
+ case 'node_count':
+ return count(taxonomy_select_nodes($term->tid));
+
+ case 'description':
+ return check_markup($term->description, isset($term->format) ? $term->format : NULL, '', TRUE);
+
+ case 'parent':
+ if (isset($term->parent[0]) && !is_array(isset($term->parent[0]))) {
+ return $term->parent;
+ }
+ return array_keys(taxonomy_get_parents($term->tid));
+
+ case 'parents_all':
+ // We have to return an array of ids.
+ $tids = array();
+ foreach (taxonomy_get_parents_all($term->tid) as $parent) {
+ $tids[] = $parent->tid;
+ }
+ return $tids;
+ }
+}
+
+/**
+ * Callback for setting term properties.
+ *
+ * @see entity_metadata_taxonomy_entity_info_alter()
+ */
+function entity_metadata_taxonomy_term_setter($term, $name, $value) {
+ switch ($name) {
+ case 'vocabulary':
+ // Make sure to update the taxonomy bundle key, so load the vocabulary.
+ // Support both, loading by name or ID.
+ $vocabulary = is_numeric($value) ? taxonomy_vocabulary_load($value) : taxonomy_vocabulary_machine_name_load($value);
+ $term->vocabulary_machine_name = $vocabulary->machine_name;
+ return $term->vid = $vocabulary->vid;
+ case 'parent':
+ return $term->parent = $value;
+ }
+}
+
+/**
+ * Callback for getting vocabulary properties.
+ * @see entity_metadata_taxonomy_entity_info_alter()
+ */
+function entity_metadata_taxonomy_vocabulary_get_properties($vocabulary, array $options, $name) {
+ switch ($name) {
+ case 'term_count':
+ $sql = "SELECT COUNT (1) FROM {taxonomy_term_data} td WHERE td.vid = :vid";
+ return db_query($sql, array(':vid' => $vocabulary->vid))->fetchField();
+ }
+}
+
+/**
+ * Callback for getting user properties.
+ * @see entity_metadata_user_entity_info_alter()
+ */
+function entity_metadata_user_get_properties($account, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'last_access':
+ // In case there was no access the value is 0, but we have to return NULL.
+ return empty($account->access) ? NULL : $account->access;
+
+ case 'last_login':
+ return empty($account->login) ? NULL : $account->login;
+
+ case 'name':
+ return empty($account->uid) ? variable_get('anonymous', t('Anonymous')) : $account->name;
+
+ case 'url':
+ if (empty($account->uid)) {
+ return NULL;
+ }
+ $return = entity_uri('user', $account);
+ return $return ? url($return['path'], $return['options'] + $options) : '';
+
+ case 'edit_url':
+ return empty($account->uid) ? NULL : url("user/$account->uid/edit", $options);
+
+ case 'roles':
+ return isset($account->roles) ? array_keys($account->roles) : array();
+
+ case 'theme':
+ return empty($account->theme) ? variable_get('theme_default', 'bartik') : $account->theme;
+ }
+}
+
+/**
+ * Callback for setting user properties.
+ * @see entity_metadata_user_entity_info_alter()
+ */
+function entity_metadata_user_set_properties($account, $name, $value) {
+ switch ($name) {
+ case 'roles':
+ $account->roles = array_intersect_key(user_roles(), array_flip($value));
+ break;
+ }
+}
+
+/**
+ * Options list callback returning all user roles.
+ */
+function entity_metadata_user_roles($property_name = 'roles', $info = array(), $op = 'edit') {
+ $roles = user_roles();
+ if ($op == 'edit') {
+ unset($roles[DRUPAL_AUTHENTICATED_RID], $roles[DRUPAL_ANONYMOUS_RID]);
+ }
+ return $roles;
+}
+
+/**
+ * Return the options lists for user status property.
+ */
+function entity_metadata_user_status_options_list() {
+ return array(
+ 0 => t('Blocked'),
+ 1 => t('Active'),
+ );
+}
+
+/**
+ * Callback defining an options list for language properties.
+ */
+function entity_metadata_language_list() {
+ $list = array();
+ $list[LANGUAGE_NONE] = t('Language neutral');
+ foreach (language_list() as $language) {
+ $list[$language->language] = t($language->name);
+ }
+ return $list;
+}
+
+/**
+ * Callback for getting field property values.
+ */
+function entity_metadata_field_property_get($entity, array $options, $name, $entity_type, $info) {
+ $field = field_info_field($name);
+ $columns = array_keys($field['columns']);
+ $langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE;
+ $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode, TRUE);
+ $values = array();
+ if (isset($entity->{$name}[$langcode])) {
+ foreach ($entity->{$name}[$langcode] as $delta => $data) {
+ $values[$delta] = $data[$columns[0]];
+ if ($info['type'] == 'boolean' || $info['type'] == 'list<boolean>') {
+ // Ensure that we have a clean boolean data type.
+ $values[$delta] = (boolean) $values[$delta];
+ }
+ }
+ }
+ // For an empty single-valued field, we have to return NULL.
+ return $field['cardinality'] == 1 ? ($values ? reset($values) : NULL) : $values;
+}
+
+/**
+ * Callback for setting field property values.
+ */
+function entity_metadata_field_property_set($entity, $name, $value, $langcode, $entity_type, $info) {
+ $field = field_info_field($name);
+ $columns = array_keys($field['columns']);
+ $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode);
+ $values = $field['cardinality'] == 1 ? array($value) : (array) $value;
+
+ $items = array();
+ foreach ($values as $delta => $value) {
+ if (isset($value)) {
+ $items[$delta][$columns[0]] = $value;
+ if ($info['type'] == 'boolean' || $info['type'] == 'list<boolean>') {
+ // Convert boolean values back to an integer for writing.
+ $items[$delta][$columns[0]] = (integer) $items[$delta][$columns[0]] = $value;
+ }
+ }
+ }
+ $entity->{$name}[$langcode] = $items;
+ // Empty the static field language cache, so the field system picks up any
+ // possible new languages.
+ drupal_static_reset('field_language');
+}
+
+/**
+ * Callback returning the options list of a field.
+ */
+function entity_metadata_field_options_list($name, $info) {
+ $field_property_info = $info;
+ if (is_numeric($name) && isset($info['parent'])) {
+ // The options list is to be returned for a single item of a multiple field.
+ $field_property_info = $info['parent']->info();
+ $name = $field_property_info['name'];
+ }
+ if (($field = field_info_field($name)) && isset($field_property_info['parent'])) {
+ // Retrieve the wrapped entity holding the field.
+ $wrapper = $field_property_info['parent'];
+ try {
+ $entity = $wrapper->value();
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // No data available.
+ $entity = NULL;
+ }
+
+ // Support translating labels via i18n field.
+ if (module_exists('i18n_field') && ($translate = i18n_field_type_info($field['type'], 'translate_options'))) {
+ return $translate($field);
+ }
+ else {
+ $instance = $wrapper->getBundle() ? field_info_instance($wrapper->type(), $name, $wrapper->getBundle()) : NULL;
+ return (array) module_invoke($field['module'], 'options_list', $field, $instance, $wrapper->type(), $entity);
+ }
+ }
+}
+
+/**
+ * Callback to verbatim get the data structure of a field. Useful for fields
+ * that add metadata for their own data structure.
+ */
+function entity_metadata_field_verbatim_get($entity, array $options, $name, $entity_type, &$context) {
+ // Set contextual info useful for getters of any child properties.
+ $context['instance'] = field_info_instance($context['parent']->type(), $name, $context['parent']->getBundle());
+ $context['field'] = field_info_field($name);
+ $langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE;
+ $langcode = entity_metadata_field_get_language($entity_type, $entity, $context['field'], $langcode, TRUE);
+
+ if ($context['field']['cardinality'] == 1) {
+ return isset($entity->{$name}[$langcode][0]) ? $entity->{$name}[$langcode][0] : NULL;
+ }
+ return isset($entity->{$name}[$langcode]) ? $entity->{$name}[$langcode] : array();
+}
+
+/**
+ * Writes the passed field items in the object. Useful as field level setter
+ * to set the whole data structure at once.
+ */
+function entity_metadata_field_verbatim_set($entity, $name, $items, $langcode, $entity_type) {
+ $field = field_info_field($name);
+ $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode);
+ $value = $field['cardinality'] == 1 ? array($items) : (array) $items;
+ // Filter out any items set to NULL.
+ $entity->{$name}[$langcode] = array_filter($value);
+
+ // Empty the static field language cache, so the field system picks up any
+ // possible new languages.
+ drupal_static_reset('field_language');
+}
+
+/**
+ * Helper for determining the field language to be used.
+ *
+ * Note that we cannot use field_language() as we are not about to display
+ * values, but generally read/write values.
+ *
+ * @param $fallback
+ * (optional) Whether to fall back to the entity default language, if no
+ * value is available for the given language code yet.
+ *
+ * @return
+ * The language code to use.
+ */
+function entity_metadata_field_get_language($entity_type, $entity, $field, $langcode = LANGUAGE_NONE, $fallback = FALSE) {
+ // Try to figure out the default language used by the entity.
+ // With Drupal >= 7.15 we can use entity_language().
+ if (function_exists('entity_language')) {
+ $default_langcode = entity_language($entity_type, $entity);
+ }
+ else {
+ $default_langcode = !empty($entity->language) ? $entity->language : LANGUAGE_NONE;
+ }
+
+ // Determine the right language to use.
+ if ($default_langcode != LANGUAGE_NONE && field_is_translatable($entity_type, $field)) {
+ $langcode = ($langcode != LANGUAGE_NONE) ? field_valid_language($langcode, $default_langcode) : $default_langcode;
+ if (!isset($entity->{$field['field_name']}[$langcode]) && $fallback) {
+ $langcode = $default_langcode;
+ }
+ return $langcode;
+ }
+ else {
+ return LANGUAGE_NONE;
+ }
+}
+
+/**
+ * Callback for getting the sanitized text of 'text_formatted' properties.
+ * This callback is used for both the 'value' and the 'summary'.
+ */
+function entity_metadata_field_text_get($item, array $options, $name, $type, $context) {
+ // $name is either 'value' or 'summary'.
+ if (!isset($item['safe_' . $name])) {
+ // Apply input formats.
+ $langcode = isset($options['language']) ? $options['language']->language : '';
+ $format = isset($item['format']) ? $item['format'] : filter_default_format();
+ $item['safe_' . $name] = check_markup($item[$name], $format, $langcode);
+ // To speed up subsequent calls, update $item with the 'safe_value'.
+ $context['parent']->set($item);
+ }
+ return $item['safe_' . $name];
+}
+
+/**
+ * Defines the list of all available text formats.
+ */
+function entity_metadata_field_text_formats() {
+ foreach (filter_formats() as $key => $format) {
+ $formats[$key] = $format->name;
+ }
+ return $formats;
+}
+
+/**
+ * Callback for getting the file entity of file fields.
+ */
+function entity_metadata_field_file_get($item) {
+ return $item['fid'];
+}
+
+/**
+ * Callback for setting the file entity of file fields.
+ */
+function entity_metadata_field_file_set(&$item, $property_name, $value) {
+ $item['fid'] = $value;
+}
+
+/**
+ * Callback for auto-creating file field $items.
+ */
+function entity_metadata_field_file_create_item($property_name, $context) {
+ // 'fid' is required, so 'file' has to be set as initial property.
+ return array('display' => isset($context['field']['settings']['display_default']) ? $context['field']['settings']['display_default'] : 0);
+}
+
+/**
+ * Callback for validating file field $items.
+ */
+function entity_metadata_field_file_validate_item($items, $context) {
+ // Allow NULL values.
+ if (!isset($items)) {
+ return TRUE;
+ }
+
+ // Stream-line $items for multiple vs non-multiple fields.
+ $items = !entity_property_list_extract_type($context['type']) ? array($items) : (array) $items;
+
+ foreach ($items as $item) {
+ // File-field items require a valid file.
+ if (!isset($item['fid']) || !file_load($item['fid'])) {
+ return FALSE;
+ }
+ if (isset($context['property info']['display']) && !isset($item['display'])) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Access callback for the node entity.
+ *
+ * This function does not implement hook_node_access(), thus it may not be
+ * called entity_metadata_node_access().
+ *
+ * @see entity_access()
+ *
+ * @param $op
+ * The operation being performed. One of 'view', 'update', 'create' or
+ * 'delete'.
+ * @param $node
+ * A node to check access for. Must be a node object. Must have nid,
+ * except in the case of 'create' operations.
+ * @param $account
+ * The user to check for. Leave it to NULL to check for the global user.
+ *
+ * @throws EntityMalformedException
+ *
+ * @return boolean
+ * TRUE if access is allowed, FALSE otherwise.
+ */
+function entity_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) {
+ // First deal with the case where a $node is provided.
+ if (isset($node)) {
+ if ($op == 'create') {
+ if (isset($node->type)) {
+ return node_access($op, $node->type, $account);
+ }
+ else {
+ throw new EntityMalformedException('Permission to create a node was requested but no node type was given.');
+ }
+ }
+ // If a non-default revision is given, incorporate revision access.
+ $default_revision = node_load($node->nid);
+ if ($node->vid !== $default_revision->vid) {
+ return _node_revision_access($node, $op, $account);
+ }
+ else {
+ return node_access($op, $node, $account);
+ }
+ }
+ // No node is provided. Check for access to all nodes.
+ if (user_access('bypass node access', $account)) {
+ return TRUE;
+ }
+ if (!user_access('access content', $account)) {
+ return FALSE;
+ }
+ if ($op == 'view' && node_access_view_all_nodes($account)) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the user entity.
+ */
+function entity_metadata_user_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) {
+ $account = isset($account) ? $account : $GLOBALS['user'];
+ // Grant access to the users own user account and to the anonymous one.
+ if (isset($entity->uid) && $op != 'delete' && (($entity->uid == $account->uid && $entity->uid) || (!$entity->uid && $op == 'view'))) {
+ return TRUE;
+ }
+ if (user_access('administer users', $account)
+ || user_access('access user profiles', $account) && $op == 'view' && (empty($entity) || !empty($entity->status))) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for restricted user properties.
+ */
+function entity_metadata_user_properties_access($op, $property, $entity = NULL, $account = NULL) {
+ if (user_access('administer users', $account)) {
+ return TRUE;
+ }
+ $account = isset($account) ? $account : $GLOBALS['user'];
+ // Flag to indicate if this user entity is the own user account.
+ $is_own_account = isset($entity->uid) && $account->uid == $entity->uid;
+ switch ($property) {
+ case 'name':
+ // Allow view access to anyone with access to the entity.
+ if ($op == 'view') {
+ return TRUE;
+ }
+ // Allow edit access for own user name if the permission is satisfied.
+ return $is_own_account && user_access('change own username', $account);
+ case 'mail':
+ // Allow access to own mail address.
+ return $is_own_account;
+ case 'roles':
+ // Allow view access for own roles.
+ return ($op == 'view' && $is_own_account);
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the comment entity.
+ */
+function entity_metadata_comment_access($op, $entity = NULL, $account = NULL) {
+ // When determining access to a comment, 'comment_access' does not take any
+ // access restrictions to the comment's associated node into account. If a
+ // comment has an associated node, the user must be able to view it in order
+ // to access the comment.
+ if (isset($entity->nid)) {
+ if (!entity_access('view', 'node', node_load($entity->nid), $account)) {
+ return FALSE;
+ }
+ }
+
+ // Comment administrators are allowed to perform all operations on all
+ // comments.
+ if (user_access('administer comments', $account)) {
+ return TRUE;
+ }
+
+ // Unpublished comments can never be accessed by non-admins.
+ if (isset($entity->status) && $entity->status == COMMENT_NOT_PUBLISHED) {
+ return FALSE;
+ }
+
+ if (isset($entity) && $op == 'update') {
+ // Because 'comment_access' only checks the current user, we need to do our
+ // own access checking if an account was specified.
+ if (!isset($account)) {
+ return comment_access('edit', $entity);
+ }
+ else {
+ return $account->uid && $account->uid == $entity->uid && user_access('edit own comments', $account);
+ }
+ }
+ if (user_access('access comments', $account) && $op == 'view') {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for restricted comment properties.
+ */
+function entity_metadata_comment_properties_access($op, $property, $entity = NULL, $account = NULL) {
+ return user_access('administer comments', $account);
+}
+
+/**
+ * Access callback for the taxonomy entities.
+ */
+function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) {
+ if ($entity_type == 'taxonomy_vocabulary') {
+ return user_access('administer taxonomy', $account);
+ }
+ if (isset($entity) && $op == 'update' && !isset($account) && taxonomy_term_edit_access($entity)) {
+ return TRUE;
+ }
+ if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for file entities.
+ */
+function entity_metadata_file_access($op, $file = NULL, $account = NULL, $entity_type) {
+ // We can only check access for the current user, so return FALSE on other accounts.
+ global $user;
+ if ($op == 'view' && isset($file) && (!isset($account) || $user->uid == $account->uid)) {
+ // Invoke hook_file_download() to obtain access information.
+ foreach (module_implements('file_download') as $module) {
+ $result = module_invoke($module, 'file_download', $file->uri);
+ if ($result == -1) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ * Callback to determine access for properties which are fields.
+ */
+function entity_metadata_field_access_callback($op, $name, $entity = NULL, $account = NULL, $entity_type) {
+ $field = field_info_field($name);
+ return field_access($op, $field, $entity_type, $entity, $account);
+}
+
+/**
+ * Callback to create entity objects.
+ */
+function entity_metadata_create_object($values = array(), $entity_type) {
+ $info = entity_get_info($entity_type);
+ // Make sure at least the bundle and label properties are set.
+ if (isset($info['entity keys']['bundle']) && $key = $info['entity keys']['bundle']) {
+ $values += array($key => NULL);
+ }
+ if (isset($info['entity keys']['label']) && $key = $info['entity keys']['label']) {
+ $values += array($key => NULL);
+ }
+ $entity = (object) $values;
+ $entity->is_new = TRUE;
+ return $entity;
+}
+
+/**
+ * Callback to create a new comment.
+ */
+function entity_metadata_create_comment($values = array()) {
+ $comment = (object) ($values + array(
+ 'status' => COMMENT_PUBLISHED,
+ 'pid' => 0,
+ 'subject' => '',
+ 'uid' => 0,
+ 'language' => LANGUAGE_NONE,
+ 'node_type' => NULL,
+ 'is_new' => TRUE,
+ ));
+ $comment->cid = FALSE;
+ return $comment;
+}
+
+/**
+ * Callback to create a new node.
+ */
+function entity_metadata_create_node($values = array()) {
+ $node = (object) array(
+ 'type' => $values['type'],
+ 'language' => LANGUAGE_NONE,
+ 'is_new' => TRUE,
+ );
+ // Set some defaults.
+ $node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));
+ foreach (array('status', 'promote', 'sticky') as $key) {
+ $node->$key = (int) in_array($key, $node_options);
+ }
+ if (module_exists('comment') && !isset($node->comment)) {
+ $node->comment = variable_get("comment_$node->type", COMMENT_NODE_OPEN);
+ }
+ // Apply the given values.
+ foreach ($values as $key => $value) {
+ $node->$key = $value;
+ }
+ return $node;
+}
+
+/**
+ * Callback to save a user account.
+ */
+function entity_metadata_user_save($account) {
+ $edit = (array) $account;
+ // Don't save the hashed password as password.
+ unset($edit['pass']);
+ user_save($account, $edit);
+}
+
+/**
+ * Callback to delete a file.
+ * Watch out to not accidentilly implement hook_file_delete().
+ */
+function entity_metadata_delete_file($fid) {
+ file_delete(file_load($fid), TRUE);
+}
+
+/**
+ * Callback to view nodes.
+ */
+function entity_metadata_view_node($entities, $view_mode = 'full', $langcode = NULL) {
+ $result = node_view_multiple($entities, $view_mode, 0, $langcode);
+ // Make sure to key the result with 'node' instead of 'nodes'.
+ return array('node' => reset($result));
+}
+
+/**
+ * Callback to view comments.
+ */
+function entity_metadata_view_comment($entities, $view_mode = 'full', $langcode = NULL) {
+ $build = array();
+ $nodes = array();
+ // The comments, indexed by nid and then by cid.
+ $nid_comments = array();
+ foreach ($entities as $cid => $comment) {
+ $nid = $comment->nid;
+ $nodes[$nid] = $nid;
+ $nid_comments[$nid][$cid] = $comment;
+ }
+ $nodes = node_load_multiple(array_keys($nodes));
+ foreach ($nid_comments as $nid => $comments) {
+ $node = isset($nodes[$nid]) ? $nodes[$nid] : NULL;
+ $build += comment_view_multiple($comments, $node, $view_mode, 0, $langcode);
+ }
+ return array('comment' => $build);
+}
+
+/**
+ * Callback to view an entity, for which just ENTITYTYPE_view() is available.
+ */
+function entity_metadata_view_single($entities, $view_mode = 'full', $langcode = NULL, $entity_type) {
+ $function = $entity_type . '_view';
+ $build = array();
+ foreach ($entities as $key => $entity) {
+ $build[$entity_type][$key] = $function($entity, $view_mode, $langcode);
+ }
+ return $build;
+}
+
+/**
+ * Callback to get the form of a node.
+ */
+function entity_metadata_form_node($node) {
+ // Pre-populate the form-state with the right form include.
+ $form_state['build_info']['args'] = array($node);
+ form_load_include($form_state, 'inc', 'node', 'node.pages');
+ return drupal_build_form($node->type . '_node_form', $form_state);
+}
+
+/**
+ * Callback to get the form of a comment.
+ */
+function entity_metadata_form_comment($comment) {
+ if (!isset($comment->node_type)) {
+ $node = node_load($comment->nid);
+ $comment->node_type = 'comment_node_' . $node->type;
+ }
+ return drupal_get_form($comment->node_type . '_form', $comment);
+}
+
+/**
+ * Callback to get the form of a user account.
+ */
+function entity_metadata_form_user($account) {
+ // If $account->uid is set then we want a user edit form.
+ // Otherwise we want the user register form.
+ if (isset($account->uid)) {
+ $form_id = 'user_profile_form';
+ form_load_include($form_state, 'inc', 'user', 'user.pages');
+ }
+ else {
+ $form_id = 'user_register_form';
+ }
+ $form_state['build_info']['args'] = array($account);
+ return drupal_build_form($form_id, $form_state);
+}
+
+/**
+ * Callback to get the form of a term.
+ */
+function entity_metadata_form_taxonomy_term($term) {
+ // Pre-populate the form-state with the right form include.
+ $form_state['build_info']['args'] = array($term);
+ form_load_include($form_state, 'inc', 'taxonomy', 'taxonomy.admin');
+ return drupal_build_form('taxonomy_form_term', $form_state);
+}
+
+/**
+ * Callback to get the form of a vocabulary.
+ */
+function entity_metadata_form_taxonomy_vocabulary($vocab) {
+ // Pre-populate the form-state with the right form include.
+ $form_state['build_info']['args'] = array($vocab);
+ form_load_include($form_state, 'inc', 'taxonomy', 'taxonomy.admin');
+ return drupal_build_form('taxonomy_form_vocabulary', $form_state);
+}
+
+/**
+ * Callback to get the form for entities using the entity API admin ui.
+ */
+function entity_metadata_form_entity_ui($entity, $entity_type) {
+ $info = entity_get_info($entity_type);
+ $form_state = form_state_defaults();
+ // Add in the include file as the form API does else with the include file
+ // specified for the active menu item.
+ if (!empty($info['admin ui']['file'])) {
+ $path = isset($info['admin ui']['file path']) ? $info['admin ui']['file path'] : drupal_get_path('module', $info['module']);
+ $form_state['build_info']['files']['entity_ui'] = $path . '/' . $info['admin ui']['file'];
+ // Also load the include file.
+ if (file_exists($form_state['build_info']['files']['entity_ui'])) {
+ require_once DRUPAL_ROOT . '/' . $form_state['build_info']['files']['entity_ui'];
+ }
+ }
+ return entity_ui_get_form($entity_type, $entity, $op = 'edit', $form_state);
+}
+
+/**
+ * Callback for querying entity properties having their values stored in the
+ * entities main db table.
+ */
+function entity_metadata_table_query($entity_type, $property, $value, $limit) {
+ $properties = entity_get_all_property_info($entity_type);
+ $info = $properties[$property] + array('schema field' => $property);
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', $entity_type, '=')
+ ->propertyCondition($info['schema field'], $value, is_array($value) ? 'IN' : '=')
+ ->range(0, $limit);
+
+ $result = $query->execute();
+ return !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array();
+}
+
+/**
+ * Callback for querying entities by field values. This function just queries
+ * for the value of the first specified column. Also it is only suitable for
+ * fields that don't process the data, so it's stored the same way as returned.
+ */
+function entity_metadata_field_query($entity_type, $property, $value, $limit) {
+ $query = new EntityFieldQuery();
+ $field = field_info_field($property);
+ $columns = array_keys($field['columns']);
+
+ $query->entityCondition('entity_type', $entity_type, '=')
+ ->fieldCondition($field, $columns[0], $value, is_array($value) ? 'IN' : '=')
+ ->range(0, $limit);
+
+ $result = $query->execute();
+ return !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array();
+}
+
+/**
+ * Implements entity_uri() callback for file entities.
+ */
+function entity_metadata_uri_file($file) {
+ return array(
+ 'path' => file_create_url($file->uri),
+ );
+}
diff --git a/sites/all/modules/entity/modules/comment.info.inc b/sites/all/modules/entity/modules/comment.info.inc
new file mode 100644
index 000000000..663c09c36
--- /dev/null
+++ b/sites/all/modules/entity/modules/comment.info.inc
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * @file
+ * Provides info about the comment entity.
+ */
+
+/**
+ * Implements hook_entity_property_info() on top of comment module.
+ *
+ * @see entity_entity_property_info()
+ */
+function entity_metadata_comment_entity_property_info() {
+ $info = array();
+ // Add meta-data about the basic comment properties.
+ $properties = &$info['comment']['properties'];
+
+ $properties['cid'] = array(
+ 'label' => t("Comment ID"),
+ 'type' => 'integer',
+ 'description' => t("The unique ID of the comment."),
+ 'schema field' => 'cid',
+ );
+ $properties['hostname'] = array(
+ 'label' => t("IP Address"),
+ 'description' => t("The IP address of the computer the comment was posted from."),
+ 'access callback' => 'entity_metadata_comment_properties_access',
+ 'schema field' => 'hostname',
+ );
+ $properties['name'] = array(
+ 'label' => t("Name"),
+ 'description' => t("The name left by the comment author."),
+ 'getter callback' => 'entity_metadata_comment_get_properties',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer comments',
+ 'sanitize' => 'filter_xss',
+ 'schema field' => 'name',
+ );
+ $properties['mail'] = array(
+ 'label' => t("Email address"),
+ 'description' => t("The email address left by the comment author."),
+ 'getter callback' => 'entity_metadata_comment_get_properties',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'validation callback' => 'valid_email_address',
+ 'access callback' => 'entity_metadata_comment_properties_access',
+ 'schema field' => 'mail',
+ );
+ $properties['homepage'] = array(
+ 'label' => t("Home page"),
+ 'description' => t("The home page URL left by the comment author."),
+ 'sanitize' => 'filter_xss_bad_protocol',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer comments',
+ 'schema field' => 'homepage',
+ );
+ $properties['subject'] = array(
+ 'label' => t("Subject"),
+ 'description' => t("The subject of the comment."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'sanitize' => 'filter_xss',
+ 'required' => TRUE,
+ 'schema field' => 'subject',
+ );
+ $properties['url'] = array(
+ 'label' => t("URL"),
+ 'description' => t("The URL of the comment."),
+ 'getter callback' => 'entity_metadata_entity_get_properties',
+ 'type' => 'uri',
+ 'computed' => TRUE,
+ );
+ $properties['edit_url'] = array(
+ 'label' => t("Edit URL"),
+ 'description' => t("The URL of the comment's edit page."),
+ 'getter callback' => 'entity_metadata_comment_get_properties',
+ 'type' => 'uri',
+ 'computed' => TRUE,
+ );
+ $properties['created'] = array(
+ 'label' => t("Date created"),
+ 'description' => t("The date the comment was posted."),
+ 'type' => 'date',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer comments',
+ 'schema field' => 'created',
+ );
+ $properties['parent'] = array(
+ 'label' => t("Parent"),
+ 'description' => t("The comment's parent, if comment threading is active."),
+ 'type' => 'comment',
+ 'getter callback' => 'entity_metadata_comment_get_properties',
+ 'setter permission' => 'administer comments',
+ 'schema field' => 'pid',
+ );
+ $properties['node'] = array(
+ 'label' => t("Node"),
+ 'description' => t("The node the comment was posted to."),
+ 'type' => 'node',
+ 'setter callback' => 'entity_metadata_comment_setter',
+ 'setter permission' => 'administer comments',
+ 'required' => TRUE,
+ 'schema field' => 'nid',
+ );
+ $properties['author'] = array(
+ 'label' => t("Author"),
+ 'description' => t("The author of the comment."),
+ 'type' => 'user',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer comments',
+ 'required' => TRUE,
+ 'schema field' => 'uid',
+ );
+ $properties['status'] = array(
+ 'label' => t("Status"),
+ 'description' => t("Whether the comment is published or unpublished."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ // Although the status is expected to be boolean, its schema suggests
+ // it is an integer, so we follow the schema definition.
+ 'type' => 'integer',
+ 'options list' => 'entity_metadata_status_options_list',
+ 'access callback' => 'entity_metadata_comment_properties_access',
+ 'schema field' => 'status',
+ );
+ return $info;
+}
+
+/**
+ * Implements hook_entity_property_info_alter() on top of comment module.
+ * @see entity_entity_property_info_alter()
+ */
+function entity_metadata_comment_entity_property_info_alter(&$info) {
+ // Add info about comment module related properties to the node entity.
+ $properties = &$info['node']['properties'];
+ $properties['comment'] = array(
+ 'label' => t("Comments allowed"),
+ 'description' => t("Whether comments are allowed on this node: 0 = no, 1 = closed (read only), 2 = open (read/write)."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer comments',
+ 'type' => 'integer',
+ );
+ $properties['comments'] = array(
+ 'label' => t("Comments"),
+ 'type' => 'list<comment>',
+ 'description' => t("The node comments."),
+ 'getter callback' => 'entity_metadata_comment_get_node_properties',
+ 'computed' => TRUE,
+ );
+ $properties['comment_count'] = array(
+ 'label' => t("Comment count"),
+ 'description' => t("The number of comments posted on a node."),
+ 'getter callback' => 'entity_metadata_comment_get_node_properties',
+ 'type' => 'integer',
+ );
+ $properties['comment_count_new'] = array(
+ 'label' => t("New comment count"),
+ 'description' => t("The number of comments posted on a node since the reader last viewed it."),
+ 'getter callback' => 'entity_metadata_comment_get_node_properties',
+ 'type' => 'integer',
+ );
+
+ // The comment body field is usually available for all bundles, so add it
+ // directly to the comment entity.
+ $info['comment']['properties']['comment_body'] = array(
+ 'type' => 'text_formatted',
+ 'label' => t('The main body text'),
+ 'getter callback' => 'entity_metadata_field_verbatim_get',
+ 'setter callback' => 'entity_metadata_field_verbatim_set',
+ 'property info' => entity_property_text_formatted_info(),
+ 'field' => TRUE,
+ 'required' => TRUE,
+ );
+ unset($info['comment']['properties']['comment_body']['property info']['summary']);
+}
diff --git a/sites/all/modules/entity/modules/field.info.inc b/sites/all/modules/entity/modules/field.info.inc
new file mode 100644
index 000000000..aeea79a19
--- /dev/null
+++ b/sites/all/modules/entity/modules/field.info.inc
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * @file
+ * Provides info for fields.
+ */
+
+/**
+ * Implements hook_entity_property_info() on top of field module.
+ *
+ * @see entity_field_info_alter()
+ * @see entity_entity_property_info()
+ */
+function entity_metadata_field_entity_property_info() {
+ $info = array();
+ // Loop over all field instances and add them as property.
+ foreach (field_info_fields() as $field_name => $field) {
+ $field += array('bundles' => array());
+ if ($field_type = field_info_field_types($field['type'])) {
+ // Add in our default callback as the first one.
+ $field_type += array('property_callbacks' => array());
+ array_unshift($field_type['property_callbacks'], 'entity_metadata_field_default_property_callback');
+
+ foreach ($field['bundles'] as $entity_type => $bundles) {
+ foreach ($bundles as $bundle) {
+ $instance = field_info_instance($entity_type, $field_name, $bundle);
+
+ if ($instance && empty($instance['deleted'])) {
+ foreach ($field_type['property_callbacks'] as $callback) {
+ $callback($info, $entity_type, $field, $instance, $field_type);
+ }
+ }
+ }
+ }
+ }
+ }
+ return $info;
+}
+
+/**
+ * Callback to add in property info defaults per field instance.
+ * @see entity_metadata_field_entity_property_info().
+ */
+function entity_metadata_field_default_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
+ if (!empty($field_type['property_type'])) {
+ if ($field['cardinality'] != 1) {
+ $field_type['property_type'] = 'list<' . $field_type['property_type'] . '>';
+ }
+ // Add in instance specific property info, if given and apply defaults.
+ $name = $field['field_name'];
+ $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
+ $instance += array('property info' => array());
+ $property = $instance['property info'] + array(
+ // Since the label will be exposed via hook_token_info() and it is not
+ // clearly defined if that should be sanitized already we prevent XSS
+ // right here (field labels are user provided text).
+ 'label' => filter_xss_admin($instance['label']),
+ 'type' => $field_type['property_type'],
+ 'description' => t('Field "@name".', array('@name' => $name)),
+ 'getter callback' => 'entity_metadata_field_property_get',
+ 'setter callback' => 'entity_metadata_field_property_set',
+ 'access callback' => 'entity_metadata_field_access_callback',
+ 'query callback' => 'entity_metadata_field_query',
+ 'translatable' => !empty($field['translatable']),
+ // Specify that this property stems from a field.
+ 'field' => TRUE,
+ 'required' => !empty($instance['required']),
+ );
+ // For field types of the list module add in the options list callback.
+ if (strpos($field['type'], 'list') === 0) {
+ $property['options list'] = 'entity_metadata_field_options_list';
+ }
+ }
+}
+
+/**
+ * Additional callback to adapt the property info for text fields. If a text
+ * field is processed we make use of a separate data structure so that format
+ * filters are available too. For the text value the sanitized, thus processed
+ * value is returned by default.
+ *
+ * @see entity_metadata_field_entity_property_info()
+ * @see entity_field_info_alter()
+ * @see entity_property_text_formatted_info()
+ */
+function entity_metadata_field_text_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
+ if (!empty($instance['settings']['text_processing']) || $field['type'] == 'text_with_summary') {
+ // Define a data structure for dealing with text that is formatted or has
+ // a summary.
+ $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
+
+ $property['getter callback'] = 'entity_metadata_field_verbatim_get';
+ $property['setter callback'] = 'entity_metadata_field_verbatim_set';
+ unset($property['query callback']);
+
+ if (empty($instance['settings']['text_processing'])) {
+ $property['property info'] = entity_property_field_item_textsummary_info();
+ }
+ else {
+ // For formatted text we use the type name 'text_formatted'.
+ $property['type'] = ($field['cardinality'] != 1) ? 'list<text_formatted>' : 'text_formatted';
+ $property['property info'] = entity_property_text_formatted_info();
+ }
+ // Enable auto-creation of the item, so that it is possible to just set
+ // the textual or summary value.
+ $property['auto creation'] = 'entity_property_create_array';
+
+ if ($field['type'] != 'text_with_summary') {
+ unset($property['property info']['summary']);
+ }
+ }
+}
+
+/**
+ * Additional callback to adapt the property info for term reference fields.
+ * @see entity_metadata_field_entity_property_info().
+ */
+function entity_metadata_field_term_reference_callback(&$info, $entity_type, $field, $instance, $field_type) {
+ $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
+ if (count($field['settings']['allowed_values']) == 1) {
+ $settings = reset($field['settings']['allowed_values']);
+ $property['bundle'] = $settings['vocabulary'];
+ }
+ // Only add the options list callback for controlled vocabularies, thus
+ // vocabularies not using the autocomplete widget.
+ if ($instance['widget']['type'] != 'taxonomy_autocomplete') {
+ $property['options list'] = 'entity_metadata_field_options_list';
+ }
+}
+
+/**
+ * Additional callback to adapt the property info for file fields.
+ * @see entity_metadata_field_entity_property_info().
+ */
+function entity_metadata_field_file_callback(&$info, $entity_type, $field, $instance, $field_type) {
+ $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
+ // Define a data structure so it's possible to deal with files and their
+ // descriptions.
+ $property['getter callback'] = 'entity_metadata_field_verbatim_get';
+ $property['setter callback'] = 'entity_metadata_field_verbatim_set';
+
+ // Auto-create the field $items as soon as a property is set.
+ $property['auto creation'] = 'entity_metadata_field_file_create_item';
+ $property['validation callback'] = 'entity_metadata_field_file_validate_item';
+
+ $property['property info'] = entity_property_field_item_file_info();
+
+ if (empty($instance['settings']['description_field'])) {
+ unset($property['property info']['description']);
+ }
+ if (empty($field['settings']['display_field'])) {
+ unset($property['property info']['display']);
+ }
+ unset($property['query callback']);
+}
+
+/**
+ * Additional callback to adapt the property info for image fields.
+ * This callback gets invoked after entity_metadata_field_file_callback().
+ * @see entity_metadata_field_entity_property_info().
+ */
+function entity_metadata_field_image_callback(&$info, $entity_type, $field, $instance, $field_type) {
+ $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
+ // Update the property info with the info for image fields.
+ $property['property info'] = entity_property_field_item_image_info();
+
+ if (empty($instance['settings']['alt_field'])) {
+ unset($property['property info']['alt']);
+ }
+ if (empty($instance['settings']['title_field'])) {
+ unset($property['property info']['title']);
+ }
+}
diff --git a/sites/all/modules/entity/modules/locale.info.inc b/sites/all/modules/entity/modules/locale.info.inc
new file mode 100644
index 000000000..3c0f36a98
--- /dev/null
+++ b/sites/all/modules/entity/modules/locale.info.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Provides locale-related properties.
+ */
+
+/**
+ * Implements hook_entity_property_info_alter() on top of locale module.
+ *
+ * @see entity_entity_property_info_alter()
+ */
+function entity_metadata_locale_entity_property_info_alter(&$info) {
+
+ $info['user']['properties']['language'] = array(
+ 'label' => t("Language"),
+ 'description' => t("This account's default language for e-mails, and preferred language for site presentation."),
+ 'type' => 'token',
+ 'getter callback' => 'entity_metadata_locale_get_user_language',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'options list' => 'entity_metadata_language_list',
+ 'schema field' => 'language',
+ 'setter permission' => 'administer users',
+ );
+
+ $info['site']['properties']['current_page']['property info']['language'] = array(
+ 'label' => t("Interface language"),
+ 'description' => t("The language code of the current user interface language."),
+ 'type' => 'token',
+ 'getter callback' => 'entity_metadata_locale_get_languages',
+ 'options list' => 'entity_metadata_language_list',
+ );
+ $info['site']['properties']['current_page']['property info']['language_content'] = array(
+ 'label' => t("Content language"),
+ 'description' => t("The language code of the current content language."),
+ 'type' => 'token',
+ 'getter callback' => 'entity_metadata_locale_get_languages',
+ 'options list' => 'entity_metadata_language_list',
+ );
+}
diff --git a/sites/all/modules/entity/modules/node.info.inc b/sites/all/modules/entity/modules/node.info.inc
new file mode 100644
index 000000000..3512d9a0b
--- /dev/null
+++ b/sites/all/modules/entity/modules/node.info.inc
@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * @file
+ * Provides info about the node entity.
+ */
+
+/**
+ * Implements hook_entity_property_info() on top of node module.
+ *
+ * @see entity_entity_property_info()
+ */
+function entity_metadata_node_entity_property_info() {
+ $info = array();
+ // Add meta-data about the basic node properties.
+ $properties = &$info['node']['properties'];
+
+ $properties['nid'] = array(
+ 'label' => t("Node ID"),
+ 'type' => 'integer',
+ 'description' => t("The unique ID of the node."),
+ 'schema field' => 'nid',
+ );
+ $properties['vid'] = array(
+ 'label' => t("Revision ID"),
+ 'type' => 'integer',
+ 'description' => t("The unique ID of the node's revision."),
+ 'schema field' => 'vid',
+ );
+ $properties['is_new'] = array(
+ 'label' => t("Is new"),
+ 'type' => 'boolean',
+ 'description' => t("Whether the node is new and not saved to the database yet."),
+ 'getter callback' => 'entity_metadata_node_get_properties',
+ );
+ $properties['type'] = array(
+ 'label' => t("Content type"),
+ 'type' => 'token',
+ 'description' => t("The type of the node."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer nodes',
+ 'options list' => 'node_type_get_names',
+ 'required' => TRUE,
+ 'schema field' => 'type',
+ );
+ $properties['title'] = array(
+ 'label' => t("Title"),
+ 'description' => t("The title of the node."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'schema field' => 'title',
+ 'required' => TRUE,
+ );
+ $properties['language'] = array(
+ 'label' => t("Language"),
+ 'type' => 'token',
+ 'description' => t("The language the node is written in."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'options list' => 'entity_metadata_language_list',
+ 'schema field' => 'language',
+ 'setter permission' => 'administer nodes',
+ );
+ $properties['url'] = array(
+ 'label' => t("URL"),
+ 'description' => t("The URL of the node."),
+ 'getter callback' => 'entity_metadata_entity_get_properties',
+ 'type' => 'uri',
+ 'computed' => TRUE,
+ );
+ $properties['edit_url'] = array(
+ 'label' => t("Edit URL"),
+ 'description' => t("The URL of the node's edit page."),
+ 'getter callback' => 'entity_metadata_node_get_properties',
+ 'type' => 'uri',
+ 'computed' => TRUE,
+ );
+ $properties['status'] = array(
+ 'label' => t("Status"),
+ 'description' => t("Whether the node is published or unpublished."),
+ // Although the status is expected to be boolean, its schema suggests
+ // it is an integer, so we follow the schema definition.
+ 'type' => 'integer',
+ 'options list' => 'entity_metadata_status_options_list',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer nodes',
+ 'schema field' => 'status',
+ );
+ $properties['promote'] = array(
+ 'label' => t("Promoted to frontpage"),
+ 'description' => t("Whether the node is promoted to the frontpage."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer nodes',
+ 'schema field' => 'promote',
+ 'type' => 'boolean',
+ );
+ $properties['sticky'] = array(
+ 'label' => t("Sticky in lists"),
+ 'description' => t("Whether the node is displayed at the top of lists in which it appears."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer nodes',
+ 'schema field' => 'sticky',
+ 'type' => 'boolean',
+ );
+ $properties['created'] = array(
+ 'label' => t("Date created"),
+ 'type' => 'date',
+ 'description' => t("The date the node was posted."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer nodes',
+ 'schema field' => 'created',
+ );
+ $properties['changed'] = array(
+ 'label' => t("Date changed"),
+ 'type' => 'date',
+ 'schema field' => 'changed',
+ 'description' => t("The date the node was most recently updated."),
+ );
+ $properties['author'] = array(
+ 'label' => t("Author"),
+ 'type' => 'user',
+ 'description' => t("The author of the node."),
+ 'getter callback' => 'entity_metadata_node_get_properties',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer nodes',
+ 'required' => TRUE,
+ 'schema field' => 'uid',
+ );
+ $properties['source'] = array(
+ 'label' => t("Translation source node"),
+ 'type' => 'node',
+ 'description' => t("The original-language version of this node, if one exists."),
+ 'getter callback' => 'entity_metadata_node_get_properties',
+ );
+ $properties['log'] = array(
+ 'label' => t("Revision log message"),
+ 'type' => 'text',
+ 'description' => t("In case a new revision is to be saved, the log entry explaining the changes for this version."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'access callback' => 'entity_metadata_node_revision_access',
+ );
+ $properties['revision'] = array(
+ 'label' => t("Creates revision"),
+ 'type' => 'boolean',
+ 'description' => t("Whether saving this node creates a new revision."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'access callback' => 'entity_metadata_node_revision_access',
+ );
+ return $info;
+}
+
+/**
+ * Implements hook_entity_property_info_alter() on top of node module.
+ * @see entity_metadata_entity_property_info_alter()
+ */
+function entity_metadata_node_entity_property_info_alter(&$info) {
+ // Move the body property to the node by default, as its usually there this
+ // makes dealing with it more convenient.
+ $info['node']['properties']['body'] = array(
+ 'type' => 'text_formatted',
+ 'label' => t('The main body text'),
+ 'getter callback' => 'entity_metadata_field_verbatim_get',
+ 'setter callback' => 'entity_metadata_field_verbatim_set',
+ 'property info' => entity_property_text_formatted_info(),
+ 'auto creation' => 'entity_property_create_array',
+ 'field' => TRUE,
+ );
+}
diff --git a/sites/all/modules/entity/modules/poll.info.inc b/sites/all/modules/entity/modules/poll.info.inc
new file mode 100644
index 000000000..12d6b35e7
--- /dev/null
+++ b/sites/all/modules/entity/modules/poll.info.inc
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Provides info about poll nodes.
+ */
+
+/**
+ * Implements hook_entity_property_info_alter() on top of poll module.
+ *
+ * @see entity_entity_property_info_alter()
+ */
+function entity_metadata_poll_entity_property_info_alter(&$info) {
+ $properties = &$info['node']['bundles']['poll']['properties'];
+
+ $properties['poll_votes'] = array(
+ 'label' => t("Poll votes"),
+ 'description' => t("The number of votes that have been cast on a poll node."),
+ 'type' => 'integer',
+ 'getter callback' => 'entity_metadata_poll_node_get_properties',
+ 'computed' => TRUE,
+ );
+ $properties['poll_winner'] = array(
+ 'label' => t("Poll winner"),
+ 'description' => t("The winning poll answer."),
+ 'getter callback' => 'entity_metadata_poll_node_get_properties',
+ 'sanitize' => 'filter_xss',
+ 'computed' => TRUE,
+ );
+ $properties['poll_winner_votes'] = array(
+ 'label' => t("Poll winner votes"),
+ 'description' => t("The number of votes received by the winning poll answer."),
+ 'type' => 'integer',
+ 'getter callback' => 'entity_metadata_poll_node_get_properties',
+ 'computed' => TRUE,
+ );
+ $properties['poll_winner_percent'] = array(
+ 'label' => t("Poll winner percent"),
+ 'description' => t("The percentage of votes received by the winning poll answer."),
+ 'getter callback' => 'entity_metadata_poll_node_get_properties',
+ 'type' => 'decimal',
+ 'computed' => TRUE,
+ );
+ $properties['poll_duration'] = array(
+ 'label' => t("Poll duration"),
+ 'description' => t("The length of time the poll node is set to run."),
+ 'getter callback' => 'entity_metadata_poll_node_get_properties',
+ 'type' => 'duration',
+ );
+}
diff --git a/sites/all/modules/entity/modules/statistics.info.inc b/sites/all/modules/entity/modules/statistics.info.inc
new file mode 100644
index 000000000..cb3fb3749
--- /dev/null
+++ b/sites/all/modules/entity/modules/statistics.info.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Provides info about statistics.
+ */
+
+/**
+ * Implements hook_entity_property_info_alter() on top of statistics module.
+ *
+ * @see entity_entity_property_info_alter()
+ */
+function entity_metadata_statistics_entity_property_info_alter(&$info) {
+ $properties = &$info['node']['properties'];
+
+ $properties['views'] = array(
+ 'label' => t("Number of views"),
+ 'description' => t("The number of visitors who have read the node."),
+ 'type' => 'integer',
+ 'getter callback' => 'entity_metadata_statistics_node_get_properties',
+ 'computed' => TRUE,
+ 'access callback' => 'entity_metadata_statistics_properties_access',
+ );
+ $properties['day_views'] = array(
+ 'label' => t("Views today"),
+ 'description' => t("The number of visitors who have read the node today."),
+ 'type' => 'integer',
+ 'getter callback' => 'entity_metadata_statistics_node_get_properties',
+ 'computed' => TRUE,
+ 'access callback' => 'entity_metadata_statistics_properties_access',
+ );
+ $properties['last_view'] = array(
+ 'label' => t("Last view"),
+ 'description' => t("The date on which a visitor last read the node."),
+ 'type' => 'date',
+ 'getter callback' => 'entity_metadata_statistics_node_get_properties',
+ 'computed' => TRUE,
+ 'access callback' => 'entity_metadata_statistics_properties_access',
+ );
+}
diff --git a/sites/all/modules/entity/modules/system.info.inc b/sites/all/modules/entity/modules/system.info.inc
new file mode 100644
index 000000000..0c6ba6b06
--- /dev/null
+++ b/sites/all/modules/entity/modules/system.info.inc
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * @file
+ * Provides info about system-wide entities.
+ */
+
+/**
+ * Implements hook_entity_property_info() on top of system module.
+ *
+ * @see entity_entity_property_info()
+ * @see entity_metadata_site_wrapper()
+ */
+function entity_metadata_system_entity_property_info() {
+ $info = array();
+
+ // There is no site entity, but still add metadata for global site properties
+ // here. That way modules can alter and add further properties at this place.
+ // In order to make use of this metadata modules may use the wrapper returned
+ // by entity_metadata_site_wrapper().
+ $properties = &$info['site']['properties'];
+ $properties['name'] = array(
+ 'label' => t("Name"),
+ 'description' => t("The name of the site."),
+ 'getter callback' => 'entity_metadata_system_get_properties',
+ 'sanitize' => 'check_plain',
+ );
+ $properties['slogan'] = array(
+ 'label' => t("Slogan"),
+ 'description' => t("The slogan of the site."),
+ 'getter callback' => 'entity_metadata_system_get_properties',
+ 'sanitize' => 'check_plain',
+ );
+ $properties['mail'] = array(
+ 'label' => t("Email"),
+ 'description' => t("The administrative email address for the site."),
+ 'getter callback' => 'entity_metadata_system_get_properties',
+ );
+ $properties['url'] = array(
+ 'label' => t("URL"),
+ 'description' => t("The URL of the site's front page."),
+ 'getter callback' => 'entity_metadata_system_get_properties',
+ 'type' => 'uri',
+ );
+ $properties['login_url'] = array(
+ 'label' => t("Login page"),
+ 'description' => t("The URL of the site's login page."),
+ 'getter callback' => 'entity_metadata_system_get_properties',
+ 'type' => 'uri',
+ );
+ $properties['current_user'] = array(
+ 'label' => t("Logged in user"),
+ 'description' => t("The currently logged in user."),
+ 'getter callback' => 'entity_metadata_system_get_properties',
+ 'type' => 'user',
+ );
+ $properties['current_date'] = array(
+ 'label' => t("Current date"),
+ 'description' => t("The current date and time."),
+ 'getter callback' => 'entity_metadata_system_get_properties',
+ 'type' => 'date',
+ );
+ $properties['current_page'] = array(
+ 'label' => t("Current page"),
+ 'description' => t("Information related to the current page request."),
+ 'getter callback' => 'entity_metadata_system_get_properties',
+ 'type' => 'struct',
+ 'property info' => array(
+ 'path' => array(
+ 'label' => t("Path"),
+ 'description' => t("The internal Drupal path of the current page request."),
+ 'getter callback' => 'current_path',
+ 'type' => 'text',
+ ),
+ 'url' => array(
+ 'label' => t("URL"),
+ 'description' => t("The full URL of the current page request."),
+ 'getter callback' => 'entity_metadata_system_get_page_properties',
+ 'type' => 'uri',
+ ),
+ ),
+ );
+
+ // Files.
+ $properties = &$info['file']['properties'];
+ $properties['fid'] = array(
+ 'label' => t("File ID"),
+ 'description' => t("The unique ID of the uploaded file."),
+ 'type' => 'integer',
+ 'validation callback' => 'entity_metadata_validate_integer_positive',
+ 'schema field' => 'fid',
+ );
+ $properties['name'] = array(
+ 'label' => t("File name"),
+ 'description' => t("The name of the file on disk."),
+ 'getter callback' => 'entity_metadata_system_get_file_properties',
+ 'schema field' => 'filename',
+ );
+ $properties['mime'] = array(
+ 'label' => t("MIME type"),
+ 'description' => t("The MIME type of the file."),
+ 'getter callback' => 'entity_metadata_system_get_file_properties',
+ 'sanitize' => 'filter_xss',
+ 'schema field' => 'filemime',
+ );
+ $properties['size'] = array(
+ 'label' => t("File size"),
+ 'description' => t("The size of the file, in kilobytes."),
+ 'getter callback' => 'entity_metadata_system_get_file_properties',
+ 'type' => 'integer',
+ 'schema field' => 'filesize',
+ );
+ $properties['url'] = array(
+ 'label' => t("URL"),
+ 'description' => t("The web-accessible URL for the file."),
+ 'getter callback' => 'entity_metadata_system_get_file_properties',
+ );
+ $properties['timestamp'] = array(
+ 'label' => t("Timestamp"),
+ 'description' => t("The date the file was most recently changed."),
+ 'type' => 'date',
+ 'schema field' => 'timestamp',
+ );
+ $properties['owner'] = array(
+ 'label' => t("Owner"),
+ 'description' => t("The user who originally uploaded the file."),
+ 'type' => 'user',
+ 'getter callback' => 'entity_metadata_system_get_file_properties',
+ 'schema field' => 'uid',
+ );
+ return $info;
+}
diff --git a/sites/all/modules/entity/modules/taxonomy.info.inc b/sites/all/modules/entity/modules/taxonomy.info.inc
new file mode 100644
index 000000000..e50c63d15
--- /dev/null
+++ b/sites/all/modules/entity/modules/taxonomy.info.inc
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ * Provides info about the taxonomy entity.
+ */
+
+/**
+ * Implements hook_entity_property_info() on top of taxonomy module.
+ *
+ * @see entity_entity_property_info()
+ */
+function entity_metadata_taxonomy_entity_property_info() {
+ $info = array();
+ // Add meta-data about the basic taxonomy properties.
+ $properties = &$info['taxonomy_term']['properties'];
+
+ $properties['tid'] = array(
+ 'label' => t("Term ID"),
+ 'description' => t("The unique ID of the taxonomy term."),
+ 'type' => 'integer',
+ 'schema field' => 'tid',
+ );
+ $properties['name'] = array(
+ 'label' => t("Name"),
+ 'description' => t("The name of the taxonomy term."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'required' => TRUE,
+ 'schema field' => 'name',
+ );
+ $properties['description'] = array(
+ 'label' => t("Description"),
+ 'description' => t("The optional description of the taxonomy term."),
+ 'sanitized' => TRUE,
+ 'raw getter callback' => 'entity_property_verbatim_get',
+ 'getter callback' => 'entity_metadata_taxonomy_term_get_properties',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'schema field' => 'description',
+ );
+ $properties['weight'] = array(
+ 'label' => t("Weight"),
+ 'type' => 'integer',
+ 'description' => t('The weight of the term, which is used for ordering terms during display.'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'schema field' => 'weight',
+ );
+ $properties['node_count'] = array(
+ 'label' => t("Node count"),
+ 'type' => 'integer',
+ 'description' => t("The number of nodes tagged with the taxonomy term."),
+ 'getter callback' => 'entity_metadata_taxonomy_term_get_properties',
+ 'computed' => TRUE,
+ );
+ $properties['url'] = array(
+ 'label' => t("URL"),
+ 'description' => t("The URL of the taxonomy term."),
+ 'getter callback' => 'entity_metadata_entity_get_properties',
+ 'type' => 'uri',
+ 'computed' => TRUE,
+ );
+ $properties['vocabulary'] = array(
+ 'label' => t("Vocabulary"),
+ 'description' => t("The vocabulary the taxonomy term belongs to."),
+ 'setter callback' => 'entity_metadata_taxonomy_term_setter',
+ 'type' => 'taxonomy_vocabulary',
+ 'required' => TRUE,
+ 'schema field' => 'vid',
+ );
+ $properties['parent'] = array(
+ 'label' => t("Parent terms"),
+ 'description' => t("The parent terms of the taxonomy term."),
+ 'getter callback' => 'entity_metadata_taxonomy_term_get_properties',
+ 'setter callback' => 'entity_metadata_taxonomy_term_setter',
+ 'type' => 'list<taxonomy_term>',
+ );
+ $properties['parents_all'] = array(
+ 'label' => t("All parent terms"),
+ 'description' => t("Ancestors of the term, i.e. parent of all above hierarchy levels."),
+ 'getter callback' => 'entity_metadata_taxonomy_term_get_properties',
+ 'type' => 'list<taxonomy_term>',
+ 'computed' => TRUE,
+ );
+
+ // Add meta-data about the basic vocabulary properties.
+ $properties = &$info['taxonomy_vocabulary']['properties'];
+
+ // Taxonomy vocabulary related variables.
+ $properties['vid'] = array(
+ 'label' => t("Vocabulary ID"),
+ 'description' => t("The unique ID of the taxonomy vocabulary."),
+ 'type' => 'integer',
+ 'schema field' => 'vid',
+ );
+ $properties['name'] = array(
+ 'label' => t("Name"),
+ 'description' => t("The name of the taxonomy vocabulary."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'required' => TRUE,
+ 'schema field' => 'name',
+ );
+ $properties['machine_name'] = array(
+ 'label' => t("Machine name"),
+ 'type' => 'token',
+ 'description' => t("The machine name of the taxonomy vocabulary."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'required' => TRUE,
+ 'schema field' => 'machine_name',
+ );
+ $properties['description'] = array(
+ 'label' => t("Description"),
+ 'description' => t("The optional description of the taxonomy vocabulary."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'sanitize' => 'filter_xss',
+ 'schema field' => 'description',
+ );
+ $properties['term_count'] = array(
+ 'label' => t("Term count"),
+ 'type' => 'integer',
+ 'description' => t("The number of terms belonging to the taxonomy vocabulary."),
+ 'getter callback' => 'entity_metadata_taxonomy_vocabulary_get_properties',
+ 'computed' => TRUE,
+ );
+ return $info;
+}
diff --git a/sites/all/modules/entity/modules/user.info.inc b/sites/all/modules/entity/modules/user.info.inc
new file mode 100644
index 000000000..67a62b547
--- /dev/null
+++ b/sites/all/modules/entity/modules/user.info.inc
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Provides info about the user entity.
+ */
+
+/**
+ * Implements hook_entity_property_info() on top of user module.
+ *
+ * @see entity_entity_property_info()
+ */
+function entity_metadata_user_entity_property_info() {
+ $info = array();
+ // Add meta-data about the user properties.
+ $properties = &$info['user']['properties'];
+
+ $properties['uid'] = array(
+ 'label' => t("User ID"),
+ 'type' => 'integer',
+ 'description' => t("The unique ID of the user account."),
+ 'schema field' => 'uid',
+ );
+ $properties['name'] = array(
+ 'label' => t("Name"),
+ 'description' => t("The login name of the user account."),
+ 'getter callback' => 'entity_metadata_user_get_properties',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'sanitize' => 'filter_xss',
+ 'required' => TRUE,
+ 'access callback' => 'entity_metadata_user_properties_access',
+ 'schema field' => 'name',
+ );
+ $properties['mail'] = array(
+ 'label' => t("Email"),
+ 'description' => t("The email address of the user account."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'validation callback' => 'valid_email_address',
+ 'required' => TRUE,
+ 'access callback' => 'entity_metadata_user_properties_access',
+ 'schema field' => 'mail',
+ );
+ $properties['url'] = array(
+ 'label' => t("URL"),
+ 'description' => t("The URL of the account profile page."),
+ 'getter callback' => 'entity_metadata_user_get_properties',
+ 'type' => 'uri',
+ 'computed' => TRUE,
+ );
+ $properties['edit_url'] = array(
+ 'label' => t("Edit URL"),
+ 'description' => t("The url of the account edit page."),
+ 'getter callback' => 'entity_metadata_user_get_properties',
+ 'type' => 'uri',
+ 'computed' => TRUE,
+ );
+ $properties['last_access'] = array(
+ 'label' => t("Last access"),
+ 'description' => t("The date the user last accessed the site."),
+ 'getter callback' => 'entity_metadata_user_get_properties',
+ 'type' => 'date',
+ 'access callback' => 'entity_metadata_user_properties_access',
+ 'schema field' => 'access',
+ );
+ $properties['last_login'] = array(
+ 'label' => t("Last login"),
+ 'description' => t("The date the user last logged in to the site."),
+ 'getter callback' => 'entity_metadata_user_get_properties',
+ 'type' => 'date',
+ 'access callback' => 'entity_metadata_user_properties_access',
+ 'schema field' => 'login',
+ );
+ $properties['created'] = array(
+ 'label' => t("Created"),
+ 'description' => t("The date the user account was created."),
+ 'type' => 'date',
+ 'schema field' => 'created',
+ 'setter permission' => 'administer users',
+ );
+ $properties['roles'] = array(
+ 'label' => t("User roles"),
+ 'description' => t("The roles of the user."),
+ 'type' => 'list<integer>',
+ 'getter callback' => 'entity_metadata_user_get_properties',
+ 'setter callback' => 'entity_metadata_user_set_properties',
+ 'options list' => 'entity_metadata_user_roles',
+ 'access callback' => 'entity_metadata_user_properties_access',
+ );
+ $properties['status'] = array(
+ 'label' => t("Status"),
+ 'description' => t("Whether the user is active or blocked."),
+ 'setter callback' => 'entity_property_verbatim_set',
+ // Although the status is expected to be boolean, its schema suggests
+ // it is an integer, so we follow the schema definition.
+ 'type' => 'integer',
+ 'options list' => 'entity_metadata_user_status_options_list',
+ 'access callback' => 'entity_metadata_user_properties_access',
+ 'schema field' => 'status',
+ );
+ $properties['theme'] = array(
+ 'label' => t("Default theme"),
+ 'description' => t("The user's default theme."),
+ 'getter callback' => 'entity_metadata_user_get_properties',
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'access callback' => 'entity_metadata_user_properties_access',
+ 'schema field' => 'theme',
+ );
+ return $info;
+}
+
diff --git a/sites/all/modules/entity/tests/entity_feature.info b/sites/all/modules/entity/tests/entity_feature.info
new file mode 100644
index 000000000..39e8e8288
--- /dev/null
+++ b/sites/all/modules/entity/tests/entity_feature.info
@@ -0,0 +1,14 @@
+name = Entity feature module
+description = Provides some entities in code.
+version = VERSION
+core = 7.x
+files[] = entity_feature.module
+dependencies[] = entity_test
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2016-03-17
+version = "7.x-1.7"
+core = "7.x"
+project = "entity"
+datestamp = "1458222244"
+
diff --git a/sites/all/modules/entity/tests/entity_feature.module b/sites/all/modules/entity/tests/entity_feature.module
new file mode 100644
index 000000000..c2c9fbf04
--- /dev/null
+++ b/sites/all/modules/entity/tests/entity_feature.module
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Test module providing some entities in code.
+ */
+
+/**
+ * Implements hook_default_entity_test_type().
+ */
+function entity_feature_default_entity_test_type() {
+ $types['main'] = entity_create('entity_test_type', array(
+ 'name' => 'main',
+ 'label' => t('Main test type'),
+ 'weight' => 0,
+ 'locked' => TRUE,
+ ));
+
+ // Types used during CRUD testing.
+ $types['test'] = entity_create('entity_test_type', array(
+ 'name' => 'test',
+ 'label' => 'label',
+ 'weight' => 0,
+ ));
+ $types['test2'] = entity_create('entity_test_type', array(
+ 'name' => 'test2',
+ 'label' => 'label2',
+ 'weight' => 2,
+ ));
+
+ return $types;
+}
diff --git a/sites/all/modules/entity/tests/entity_test.info b/sites/all/modules/entity/tests/entity_test.info
new file mode 100644
index 000000000..1b2248ac5
--- /dev/null
+++ b/sites/all/modules/entity/tests/entity_test.info
@@ -0,0 +1,15 @@
+name = Entity CRUD test module
+description = Provides entity types based upon the CRUD API.
+version = VERSION
+core = 7.x
+files[] = entity_test.module
+files[] = entity_test.install
+dependencies[] = entity
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2016-03-17
+version = "7.x-1.7"
+core = "7.x"
+project = "entity"
+datestamp = "1458222244"
+
diff --git a/sites/all/modules/entity/tests/entity_test.install b/sites/all/modules/entity/tests/entity_test.install
new file mode 100644
index 000000000..a9693230c
--- /dev/null
+++ b/sites/all/modules/entity/tests/entity_test.install
@@ -0,0 +1,170 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the entity_test module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function entity_test_uninstall() {
+ // Bypass entity_load() as we cannot use it here.
+ $types = db_select('entity_test_type', 'et')
+ ->fields('et')
+ ->execute()
+ ->fetchAllAssoc('name');
+
+ foreach ($types as $name => $type) {
+ field_attach_delete_bundle('entity_test', $name);
+ }
+}
+
+/**
+ * Implements hook_schema().
+ */
+function entity_test_schema() {
+ $schema['entity_test'] = array(
+ 'description' => 'Stores entity_test items.',
+ 'fields' => array(
+ 'pid' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique entity_test item ID.',
+ ),
+ 'name' => array(
+ 'description' => 'The name of the entity_test.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'uid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'default' => NULL,
+ 'description' => "The {users}.uid of the associated user.",
+ ),
+ ),
+ 'indexes' => array(
+ 'uid' => array('uid'),
+ ),
+ 'foreign keys' => array(
+ 'uid' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid')
+ ),
+ 'name' => array(
+ 'table' => 'entity_test_types',
+ 'columns' => array('name' => 'name')
+ ),
+ ),
+ 'primary key' => array('pid'),
+ );
+
+ $schema['entity_test_type'] = array(
+ 'description' => 'Stores information about all defined entity_test types.',
+ 'fields' => array(
+ 'id' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique entity_test type ID.',
+ ),
+ 'name' => array(
+ 'description' => 'The machine-readable name of this entity_test type.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ ),
+ 'label' => array(
+ 'description' => 'The human-readable name of this entity_test type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => 'The weight of this entity_test type in relation to others.',
+ ),
+ 'locked' => array(
+ 'description' => 'A boolean indicating whether the administrator may delete this type.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'data' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ 'description' => 'A serialized array of additional data related to this entity_test type.',
+ 'merge' => TRUE,
+ ),
+ 'status' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ // Set the default to ENTITY_CUSTOM without using the constant as it is
+ // not safe to use it at this point.
+ 'default' => 0x01,
+ 'size' => 'tiny',
+ 'description' => 'The exportable status of the entity.',
+ ),
+ 'module' => array(
+ 'description' => 'The name of the providing module if the entity has been defined in code.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ),
+ ),
+ 'primary key' => array('id'),
+ 'unique keys' => array(
+ 'name' => array('name'),
+ ),
+ );
+
+ // Add schema for the revision-test-entity.
+ $schema['entity_test2'] = $schema['entity_test'];
+ $schema['entity_test2']['fields']['revision_id'] = array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'default' => NULL,
+ 'description' => 'The ID of the entity\'s default revision.',
+ );
+ $schema['entity_test2']['fields']['title'] = array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ );
+
+ $schema['entity_test2_revision'] = $schema['entity_test'];
+ $schema['entity_test2_revision']['fields']['revision_id'] = array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique revision ID.',
+ );
+ $schema['entity_test2_revision']['fields']['pid'] = array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'default' => NULL,
+ 'description' => 'The ID of the attached entity.',
+ );
+ $schema['entity_test2_revision']['fields']['title'] = array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ );
+ $schema['entity_test2_revision']['primary key'] = array('revision_id');
+
+ return $schema;
+}
+
diff --git a/sites/all/modules/entity/tests/entity_test.module b/sites/all/modules/entity/tests/entity_test.module
new file mode 100644
index 000000000..558556884
--- /dev/null
+++ b/sites/all/modules/entity/tests/entity_test.module
@@ -0,0 +1,287 @@
+<?php
+
+/**
+ * @file
+ * Test moduel for the entity API.
+ */
+
+/**
+ * Implements hook_entity_info().
+ */
+function entity_test_entity_info() {
+ $return = array(
+ 'entity_test' => array(
+ 'label' => t('Test Entity'),
+ 'plural label' => t('Test Entities'),
+ 'description' => t('An entity type used by the entity API tests.'),
+ 'entity class' => 'EntityClass',
+ 'controller class' => 'EntityAPIController',
+ 'base table' => 'entity_test',
+ 'fieldable' => TRUE,
+ 'entity keys' => array(
+ 'id' => 'pid',
+ 'bundle' => 'name',
+ ),
+ // Make use the class' label() and uri() implementation by default.
+ 'label callback' => 'entity_class_label',
+ 'uri callback' => 'entity_class_uri',
+ 'bundles' => array(),
+ 'bundle keys' => array(
+ 'bundle' => 'name',
+ ),
+ 'module' => 'entity_test',
+ ),
+ 'entity_test_type' => array(
+ 'label' => t('Test entity type'),
+ 'entity class' => 'Entity',
+ 'controller class' => 'EntityAPIControllerExportable',
+ 'base table' => 'entity_test_type',
+ 'fieldable' => FALSE,
+ 'bundle of' => 'entity_test',
+ 'exportable' => TRUE,
+ 'entity keys' => array(
+ 'id' => 'id',
+ 'name' => 'name',
+ ),
+ 'module' => 'entity_test',
+ ),
+
+ 'entity_test2' => array(
+ 'label' => t('Test Entity (revision support)'),
+ 'entity class' => 'EntityClassRevision',
+ 'controller class' => 'EntityAPIController',
+ 'base table' => 'entity_test2',
+ 'revision table' => 'entity_test2_revision',
+ 'fieldable' => TRUE,
+ 'entity keys' => array(
+ 'id' => 'pid',
+ 'revision' => 'revision_id',
+ ),
+ // Make use of the class label() and uri() implementation by default.
+ 'label callback' => 'entity_class_label',
+ 'uri callback' => 'entity_class_uri',
+ 'bundles' => array(),
+ 'bundle keys' => array(
+ 'bundle' => 'name',
+ ),
+ ),
+ );
+
+ // Add bundle info but bypass entity_load() as we cannot use it here.
+ $types = db_select('entity_test_type', 'et')
+ ->fields('et')
+ ->execute()
+ ->fetchAllAssoc('name');
+
+ foreach ($types as $name => $type) {
+ $return['entity_test']['bundles'][$name] = array(
+ 'label' => $type->label,
+ );
+ }
+
+ // Support entity cache module.
+ if (module_exists('entitycache')) {
+ $return['entity_test']['field cache'] = FALSE;
+ $return['entity_test']['entity cache'] = TRUE;
+ }
+
+ return $return;
+}
+
+/**
+ * Gets an array of all test entity types, keyed by the name.
+ *
+ * @param $name
+ * If set, the type with the given name is returned.
+ */
+function entity_test_get_types($name = NULL) {
+ $types = entity_load_multiple_by_name('entity_test_type', isset($name) ? array($name) : FALSE);
+ return isset($name) ? reset($types) : $types;
+}
+
+/**
+ * Load multiple test entities based on certain conditions.
+ *
+ * @param $pids
+ * An array of entity IDs.
+ * @param $conditions
+ * An array of conditions to match against the {entity} table.
+ * @param $reset
+ * A boolean indicating that the internal cache should be reset.
+ * @return
+ * An array of test entity objects, indexed by pid.
+ */
+function entity_test_load_multiple($pids = array(), $conditions = array(), $reset = FALSE) {
+ return entity_load('entity_test', $pids, $conditions, $reset);
+}
+
+/**
+ * Delete multiple test entities.
+ *
+ * @param $pids
+ * An array of test entity IDs.
+ */
+function entity_test_delete_multiple(array $pids) {
+ entity_get_controller('entity_test')->delete($pids);
+}
+
+
+/**
+ * Main class for test entities.
+ */
+class EntityClass extends Entity {
+
+ public function __construct(array $values = array(), $entityType = NULL) {
+ parent::__construct($values, 'entity_test');
+ }
+
+ /**
+ * Override buildContent() to add the username to the output.
+ */
+ public function buildContent($view_mode = 'full', $langcode = NULL) {
+ $content['user'] = array(
+ '#markup' => "User: ". format_username(user_load($this->uid)),
+ );
+ return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode, $content);
+ }
+
+ /**
+ * Specifies the default label, which is picked up by label() by default.
+ */
+ protected function defaultLabel() {
+ $type = entity_test_get_types($this->name);
+ return $type->label;
+ }
+
+ /**
+ * Specifies the default uri, which is picked up by uri() by default.
+ */
+ protected function defaultURI() {
+ return array('path' => 'custom/' . $this->identifier());
+ }
+}
+
+/**
+ * Main class for test entities (with revision support).
+ */
+class EntityClassRevision extends EntityClass {
+
+ public function __construct(array $values = array(), $entityType = NULL) {
+ Entity::__construct($values, 'entity_test2');
+ }
+
+}
+
+/**
+ *
+ *
+ * Some hook implementations used by the tests.
+ *
+ *
+ */
+
+
+/**
+ * Implements hook_entity_insert().
+ */
+function entity_test_entity_insert($entity, $entity_type) {
+ if ($entity_type == 'entity_test_type') {
+ $_SESSION['entity_hook_test']['entity_insert'][] = entity_id($entity_type, $entity);
+ }
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function entity_test_entity_update($entity, $entity_type) {
+ $_SESSION['entity_hook_test']['entity_update'][] = entity_id($entity_type, $entity);
+}
+
+/**
+ * Implements hook_entity_delete().
+ */
+function entity_test_entity_delete($entity, $entity_type) {
+ if ($entity_type == 'entity_test_type') {
+ $_SESSION['entity_hook_test']['entity_delete'][] = entity_id($entity_type, $entity);
+ }
+}
+
+/**
+ * Implements hook_entity_test_type_insert().
+ */
+function entity_test_entity_test_type_insert($entity) {
+ $_SESSION['entity_hook_test']['entity_test_type_insert'][] = $entity->identifier();
+}
+
+/**
+ * Implements hook_entity_test_type_update().
+ */
+function entity_test_entity_test_type_update($entity) {
+ $_SESSION['entity_hook_test']['entity_test_type_update'][] = $entity->identifier();
+
+ // Determine changes on update.
+ if (!empty($entity->original) && $entity->original->label == 'test_changes') {
+ if ($entity->original->label != $entity->label) {
+ $entity->label .= '_update';
+ }
+ }
+}
+
+/**
+ * Implements hook_entity_test_type_delete().
+ */
+function entity_test_entity_test_type_delete($entity) {
+ $_SESSION['entity_hook_test']['entity_test_type_delete'][] = $entity->identifier();
+}
+
+/**
+ * Implements hook_entity_test_type_presave().
+ */
+function entity_test_entity_test_type_presave($entity) {
+ // Determine changes.
+ if (!empty($entity->original) && $entity->original->label == 'test_changes') {
+ if ($entity->original->label != $entity->label) {
+ $entity->label .= '_presave';
+ }
+ }
+}
+
+/**
+ * Implements hook_entity_property_info_alter() for testing an property of type
+ * 'entity'.
+ */
+function entity_test_entity_property_info_alter(&$info) {
+ $info['node']['properties']['reference'] = array(
+ 'label' => t('Test reference'),
+ 'description' => t('A generic entity reference.'),
+ 'getter callback' => 'entity_test_entity_getter',
+ 'setter callback' => 'entity_test_entity_setter',
+ 'type' => 'entity',
+ );
+}
+
+/**
+ * Getter callback for the 'reference' property.
+ */
+function entity_test_entity_getter($node) {
+ if (empty($node->entity)) {
+ $node->entity = array('type' => 'user', 'id' => $node->uid);
+ }
+
+ // We have to return the entity wrapped.
+ // Special handling for anonymous user.
+ if ($node->entity['type'] === 'user' && empty($node->entity['id'])) {
+ return entity_metadata_wrapper('user', drupal_anonymous_user());
+ }
+ else {
+ return entity_metadata_wrapper($node->entity['type'], $node->entity['id']);
+ }
+}
+
+/**
+ * Setter callback for the 'reference' property.
+ */
+function entity_test_entity_setter($node, $property_name, $wrapper) {
+ // The entity has to be passed wrapped.
+ $node->entity = array('type' => $wrapper->type(), 'id' => $wrapper->getIdentifier());
+}
diff --git a/sites/all/modules/entity/tests/entity_test_i18n.info b/sites/all/modules/entity/tests/entity_test_i18n.info
new file mode 100644
index 000000000..caf869f1b
--- /dev/null
+++ b/sites/all/modules/entity/tests/entity_test_i18n.info
@@ -0,0 +1,13 @@
+name = Entity-test type translation
+description = Allows translating entity-test types.
+dependencies[] = entity_test
+dependencies[] = i18n_string
+package = Multilingual - Internationalization
+core = 7.x
+hidden = TRUE
+; Information added by Drupal.org packaging script on 2016-03-17
+version = "7.x-1.7"
+core = "7.x"
+project = "entity"
+datestamp = "1458222244"
+
diff --git a/sites/all/modules/entity/tests/entity_test_i18n.module b/sites/all/modules/entity/tests/entity_test_i18n.module
new file mode 100644
index 000000000..2aa373688
--- /dev/null
+++ b/sites/all/modules/entity/tests/entity_test_i18n.module
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Entity-test i18n integration module via entity API i18n support.
+ *
+ * @see EntityDefaultI18nController
+ */
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function entity_test_i18n_entity_info_alter(&$info) {
+ // Enable i18n support via the entity API.
+ $info['entity_test_type']['i18n controller class'] = 'EntityDefaultI18nStringController';
+}
+
+/**
+ * Implements hook_entity_property_info_alter().
+ */
+function entity_test_i18n_entity_property_info_alter(&$info) {
+ // Mark some properties as translatable, but also denote that translation
+ // works with i18n_string.
+ foreach (array('label') as $name) {
+ $info['entity_test_type']['properties'][$name]['translatable'] = TRUE;
+ $info['entity_test_type']['properties'][$name]['i18n string'] = TRUE;
+ }
+}
+
+/**
+ * Implements hook_{entity_test_type}_insert().
+ */
+function entity_test_i18n_entity_test_type_insert($test_type) {
+ i18n_string_object_update('entity_test_type', $test_type);
+}
+
+/**
+ * Implements hook_{entity_test_type}_update().
+ */
+function entity_test_i18n_entity_test_type_update($test_type) {
+ // Account for name changes.
+ if ($test_type->original->name != $test_type->name) {
+ i18n_string_update_context("entity_test:entity_test_type:{$test_type->original->name}:*", "entity_test:entity_test_type:{$test_type->name}:*");
+ }
+ i18n_string_object_update('entity_test_type', $test_type);
+}
+
+/**
+ * Implements hook_{entity_test_type}_delete().
+ */
+function entity_test_i18n_entity_test_type_delete($test_type) {
+ i18n_string_object_remove('entity_test_type', $test_type);
+}
diff --git a/sites/all/modules/entity/theme/entity.theme.css b/sites/all/modules/entity/theme/entity.theme.css
new file mode 100644
index 000000000..a57318dfc
--- /dev/null
+++ b/sites/all/modules/entity/theme/entity.theme.css
@@ -0,0 +1,4 @@
+
+.entity-property-label {
+ font-weight: bold;
+}
diff --git a/sites/all/modules/entity/theme/entity.theme.inc b/sites/all/modules/entity/theme/entity.theme.inc
new file mode 100644
index 000000000..fc0ba7c18
--- /dev/null
+++ b/sites/all/modules/entity/theme/entity.theme.inc
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * @file
+ * Holds entity module's theme functions.
+ */
+
+/**
+ * Returns HTML for an entity property.
+ *
+ * This is the default theme implementation to display the value of a property.
+ * This function can be overridden with varying levels of specificity. For
+ * example, for a property named 'title' displayed on the 'article' bundle,
+ * any of the following functions will override this default implementation.
+ * The first of these functions that exists is used:
+ * - THEMENAME_property__body__article()
+ * - THEMENAME_property__article()
+ * - THEMENAME_property__body()
+ * - THEMENAME_property()
+ *
+ * @param $variables
+ * An associative array containing:
+ * - label: A boolean indicating to show or hide the property label.
+ * - title_attributes: A string containing the attributes for the title.
+ * - label: The label for the property.
+ * - content_attributes: A string containing the attributes for the content's
+ * div.
+ * - content: The rendered property value.
+ * - attributes: A string containing the attributes for the wrapping div.
+ *
+ * @ingroup themeable
+ */
+function theme_entity_property($variables) {
+ $output = '';
+
+ // Render the label, if it's not hidden.
+ if (!$variables['label_hidden']) {
+ $output .= '<div' . $variables['title_attributes'] . '>' . $variables['label'] . ':&nbsp;</div>';
+ }
+
+ // Render the content.
+ $content_suffix = '';
+ if (!$variables['label_hidden'] || $variables['content_attributes']) {
+ $output .= '<div' . $variables['content_attributes'] . '>';
+ $content_suffix = '</div>';
+ }
+ $output .= $variables['content'] . $content_suffix;
+
+ // Render the top-level DIV.
+ return '<div' . $variables['attributes'] . '>' . $output . '</div>';
+}
+
+/**
+ * Theme preprocess function for theme_entity_property().
+ *
+ * @see theme_entity_property()
+ */
+function template_preprocess_entity_property(&$variables, $hook) {
+ $element = $variables['elements'];
+
+ $variables += array(
+ 'theme_hook_suggestions' => array(),
+ 'attributes_array' => array(),
+ );
+ // Generate variables from element properties.
+ foreach (array('label_hidden', 'label', 'property_name') as $name) {
+ $variables[$name] = check_plain($element['#' . $name]);
+ }
+ $variables['title_attributes_array']['class'][] = 'entity-property-label';
+ $variables['attributes_array'] = array_merge($variables['attributes_array'], isset($element['#attributes']) ? $element['#attributes'] : array());
+
+ $variables['property_name_css'] = strtr($element['#property_name'], '_', '-');
+ $variables['attributes_array']['class'][] = 'entity-property';
+ $variables['attributes_array']['class'][] = 'entity-property-' . $variables['property_name_css'];
+
+ // Add specific suggestions that can override the default implementation.
+ $variables['theme_hook_suggestions'] += array(
+ 'entity_property__' . $element['#property_name'],
+ 'entity_property__' . $element['#entity_type'] . '__' . $element['#property_name'],
+ );
+
+ // Populate the content with sensible defaults.
+ if (!isset($element['#content'])) {
+ $variables['content'] = entity_property_default_render_value_by_type($element['#entity_wrapped']->{$element['#property_name']});
+ }
+ else {
+ $variables['content'] = $element['#content'];
+ }
+}
+
+/**
+ * Renders a property using simple defaults based upon the property type.
+ *
+ * @return string
+ */
+function entity_property_default_render_value_by_type(EntityMetadataWrapper $property) {
+ // If there is an options list or entity label, render that by default.
+ if ($label = $property->label()) {
+ if ($property instanceof EntityDrupalWrapper && $uri = entity_uri($property->type(), $property->value())) {
+ return l($label, $uri['path'], $uri['options']);
+ }
+ else {
+ return check_plain($label);
+ }
+ }
+ switch ($property->type()) {
+ case 'boolean':
+ return $property->value() ? t('yes') : t('no');
+ default:
+ return check_plain($property->value());
+ }
+}
+
+/**
+ * Theme process function for theme_entity_property().
+ *
+ * Taken over from template_process_field()
+ *
+ * @see theme_entity_property()
+ */
+function template_process_entity_property(&$variables, $hook) {
+ $element = $variables['elements'];
+ // The default theme implementation is a function, so template_process() does
+ // not automatically run, so we need to flatten the classes and attributes
+ // here. For best performance, only call drupal_attributes() when needed, and
+ // note that template_preprocess_field() does not initialize the
+ // *_attributes_array variables.
+ $variables['attributes'] = empty($variables['attributes_array']) ? '' : drupal_attributes($variables['attributes_array']);
+ $variables['title_attributes'] = empty($variables['title_attributes_array']) ? '' : drupal_attributes($variables['title_attributes_array']);
+ $variables['content_attributes'] = empty($variables['content_attributes_array']) ? '' : drupal_attributes($variables['content_attributes_array']);
+}
+
+/**
+ * Themes the exportable status of an entity.
+ */
+function theme_entity_status($variables) {
+ $status = $variables['status'];
+ $html = $variables['html'];
+ if (($status & ENTITY_FIXED) == ENTITY_FIXED) {
+ $label = t('Fixed');
+ $help = t('The configuration is fixed and cannot be changed.');
+ return $html ? "<span class='entity-status-fixed' title='$help'>" . $label . "</span>" : $label;
+ }
+ elseif (($status & ENTITY_OVERRIDDEN) == ENTITY_OVERRIDDEN) {
+ $label = t('Overridden');
+ $help = t('This configuration is provided by a module, but has been changed.');
+ return $html ? "<span class='entity-status-overridden' title='$help'>" . $label . "</span>" : $label;
+ }
+ elseif ($status & ENTITY_IN_CODE) {
+ $label = t('Default');
+ $help = t('A module provides this configuration.');
+ return $html ? "<span class='entity-status-default' title='$help'>" . $label . "</span>" : $label;
+ }
+ elseif ($status & ENTITY_CUSTOM) {
+ $label = t('Custom');
+ $help = t('A custom configuration by a user.');
+ return $html ? "<span class='entity-status-custom' title='$help'>" . $label . "</span>" : $label;
+ }
+}
+
+/**
+ * Process variables for entity.tpl.php.
+ */
+function template_preprocess_entity(&$variables) {
+ $variables['view_mode'] = $variables['elements']['#view_mode'];
+ $entity_type = $variables['elements']['#entity_type'];
+ $variables['entity_type'] = $entity_type;
+ $entity = $variables['elements']['#entity'];
+ $variables[$variables['elements']['#entity_type']] = $entity;
+ $info = entity_get_info($entity_type);
+
+ $variables['title'] = check_plain(entity_label($entity_type, $entity));
+
+ $uri = entity_uri($entity_type, $entity);
+ $variables['url'] = $uri && !empty($uri['path']) ? url($uri['path'], $uri['options']) : FALSE;
+
+ if (isset($variables['elements']['#page'])) {
+ // If set by the caller, respect the page property.
+ $variables['page'] = $variables['elements']['#page'];
+ }
+ else {
+ // Else, try to automatically detect it.
+ $variables['page'] = $uri && !empty($uri['path']) && $uri['path'] == $_GET['q'];
+ }
+
+ // Helpful $content variable for templates.
+ $variables['content'] = array();
+ foreach (element_children($variables['elements']) as $key) {
+ $variables['content'][$key] = $variables['elements'][$key];
+ }
+
+ if (!empty($info['fieldable'])) {
+ // Make the field variables available with the appropriate language.
+ field_attach_preprocess($entity_type, $entity, $variables['content'], $variables);
+ }
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+
+ // Gather css classes.
+ $variables['classes_array'][] = drupal_html_class('entity-' . $entity_type);
+ $variables['classes_array'][] = drupal_html_class($entity_type . '-' . $bundle);
+
+ // Add RDF type and about URI.
+ if (module_exists('rdf')) {
+ $variables['attributes_array']['about'] = empty($uri['path']) ? NULL: url($uri['path']);
+ $variables['attributes_array']['typeof'] = empty($entity->rdf_mapping['rdftype']) ? NULL : $entity->rdf_mapping['rdftype'];
+ }
+
+ // Add suggestions.
+ $variables['theme_hook_suggestions'][] = $entity_type;
+ $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle;
+ $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle . '__' . $variables['view_mode'];
+ if ($id = entity_id($entity_type, $entity)) {
+ $variables['theme_hook_suggestions'][] = $entity_type . '__' . $id;
+ }
+}
diff --git a/sites/all/modules/entity/theme/entity.tpl.php b/sites/all/modules/entity/theme/entity.tpl.php
new file mode 100644
index 000000000..cfe94dd7e
--- /dev/null
+++ b/sites/all/modules/entity/theme/entity.tpl.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation for entities.
+ *
+ * Available variables:
+ * - $content: An array of comment items. Use render($content) to print them all, or
+ * print a subset such as render($content['field_example']). Use
+ * hide($content['field_example']) to temporarily suppress the printing of a
+ * given element.
+ * - $title: The (sanitized) entity label.
+ * - $url: Direct url of the current entity if specified.
+ * - $page: Flag for the full page state.
+ * - $classes: String of classes that can be used to style contextually through
+ * CSS. It can be manipulated through the variable $classes_array from
+ * preprocess functions. By default the following classes are available, where
+ * the parts enclosed by {} are replaced by the appropriate values:
+ * - entity-{ENTITY_TYPE}
+ * - {ENTITY_TYPE}-{BUNDLE}
+ *
+ * Other variables:
+ * - $classes_array: Array of html class attribute values. It is flattened
+ * into a string within the variable $classes.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_entity()
+ * @see template_process()
+ */
+?>
+<div class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>>
+
+ <?php if (!$page): ?>
+ <h2<?php print $title_attributes; ?>>
+ <?php if ($url): ?>
+ <a href="<?php print $url; ?>"><?php print $title; ?></a>
+ <?php else: ?>
+ <?php print $title; ?>
+ <?php endif; ?>
+ </h2>
+ <?php endif; ?>
+
+ <div class="content"<?php print $content_attributes; ?>>
+ <?php
+ print render($content);
+ ?>
+ </div>
+</div>
diff --git a/sites/all/modules/entity/views/entity.views.inc b/sites/all/modules/entity/views/entity.views.inc
new file mode 100644
index 000000000..59ebaa488
--- /dev/null
+++ b/sites/all/modules/entity/views/entity.views.inc
@@ -0,0 +1,701 @@
+<?php
+
+/**
+ * @file
+ * Provide views data for modules making use of the entity CRUD API.
+ */
+
+/**
+ * Implements hook_views_data().
+ *
+ * Provides Views integration for entities if they satisfy one of these
+ * conditions:
+ * - hook_entity_info() specifies a 'views controller class' key.
+ * - hook_entity_info() specifies a 'module' key, and the module does not
+ * implement hook_views_data().
+ *
+ * @see entity_crud_hook_entity_info()
+ * @see entity_views_table_definition()
+ */
+function entity_views_data() {
+ $data = array();
+
+ foreach (entity_crud_get_info() as $type => $info) {
+ // Provide default integration with the basic controller class if we know
+ // the module providing the entity and it does not provide views integration.
+ if (!isset($info['views controller class'])) {
+ $info['views controller class'] = isset($info['module']) && !module_hook($info['module'], 'views_data') ? 'EntityDefaultViewsController' : FALSE;
+ }
+ if ($info['views controller class']) {
+ $controller = new $info['views controller class']($type);
+ // Relationship data may return views data for already existing tables,
+ // so merge results on the second level.
+ foreach ($controller->views_data() as $table => $table_data) {
+ $data += array($table => array());
+ $data[$table] = array_merge($data[$table], $table_data);
+ }
+ }
+ }
+
+ // Add tables based upon data selection "queries" for all entity types.
+ foreach (entity_get_info() as $type => $info) {
+ $table = entity_views_table_definition($type);
+ if ($table) {
+ $data['entity_' . $type] = $table;
+ }
+ // Generally expose properties marked as 'entity views field'.
+ $data['views_entity_' . $type] = array();
+ foreach (entity_get_all_property_info($type) as $key => $property) {
+ if (!empty($property['entity views field'])) {
+ entity_views_field_definition($key, $property, $data['views_entity_' . $type]);
+ }
+ }
+ }
+
+ // Expose generally usable entity-related fields.
+ foreach (entity_get_info() as $entity_type => $info) {
+ if (entity_type_supports($entity_type, 'view')) {
+ // Expose a field allowing to display the rendered entity.
+ $data['views_entity_' . $entity_type]['rendered_entity'] = array(
+ 'title' => t('Rendered @entity-type', array('@entity-type' => $info['label'])),
+ 'help' => t('The @entity-type of the current relationship rendered using a view mode.', array('@entity-type' => $info['label'])),
+ 'field' => array(
+ 'handler' => 'entity_views_handler_field_entity',
+ 'type' => $entity_type,
+ // The EntityFieldHandlerHelper treats the 'entity object' data
+ // selector as special case for loading the base entity.
+ 'real field' => 'entity object',
+ ),
+ );
+ }
+ }
+
+ $data['entity__global']['table']['group'] = t('Entity');
+ $data['entity__global']['table']['join'] = array(
+ // #global let's it appear all the time.
+ '#global' => array(),
+ );
+ $data['entity__global']['entity'] = array(
+ 'title' => t('Rendered entity'),
+ 'help' => t('Displays a single chosen entity.'),
+ 'area' => array(
+ 'handler' => 'entity_views_handler_area_entity',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Helper function for getting data selection based entity Views table definitions.
+ *
+ * This creates extra tables for each entity type that are not associated with a
+ * query plugin (and thus are not base tables) and just rely on the entities to
+ * retrieve the displayed data. To obtain the entities corresponding to a
+ * certain result set, the field handlers defined on the table use a generic
+ * interface defined for query plugins that are based on entity handling, and
+ * which is described in the entity_views_example_query class.
+ *
+ * These tables are called "data selection tables".
+ *
+ * Other modules providing Views integration with new query plugins that are
+ * based on entities can then use these tables as a base for their own tables
+ * (by directly using this method and modifying the returned table) and/or by
+ * specifying relationships to them. The tables returned here already specify
+ * relationships to each other wherever an entity contains a reference to
+ * another (e.g., the node author constructs a relationship from nodes to
+ * users).
+ *
+ * As filtering and other query manipulation is potentially more plugin-specific
+ * than the display, only field handlers and relationships are provided with
+ * these tables. By providing a add_selector_orderby() method, the query plugin
+ * can, however, support click-sorting for the field handlers in these tables.
+ *
+ * For a detailed discussion see http://drupal.org/node/1266036
+ *
+ * For example use see the Search API views module in the Search API project:
+ * http://drupal.org/project/search_api
+ *
+ * @param $type
+ * The entity type whose table definition should be returned.
+ * @param $exclude
+ * Whether properties already exposed as 'entity views field' should be
+ * excluded. Defaults to TRUE, as they are available for all views tables for
+ * the entity type anyways.
+ *
+ * @return
+ * An array containing the data selection Views table definition for the
+ * entity type.
+ *
+ * @see entity_views_field_definition()
+ */
+function entity_views_table_definition($type, $exclude = TRUE) {
+ // As other modules might want to copy these tables as a base for their own
+ // Views integration, we statically cache the tables to save some time.
+ $tables = &drupal_static(__FUNCTION__, array());
+
+ if (!isset($tables[$type])) {
+ // Work-a-round to fix updating, see http://drupal.org/node/1330874.
+ // Views data might be rebuilt on update.php before the registry is rebuilt,
+ // thus the class cannot be auto-loaded.
+ if (!class_exists('EntityFieldHandlerHelper')) {
+ module_load_include('inc', 'entity', 'views/handlers/entity_views_field_handler_helper');
+ }
+
+ $info = entity_get_info($type);
+ $tables[$type]['table'] = array(
+ 'group' => $info['label'],
+ 'entity type' => $type,
+ );
+ foreach (entity_get_all_property_info($type) as $key => $property) {
+ if (!$exclude || empty($property['entity views field'])) {
+ entity_views_field_definition($key, $property, $tables[$type]);
+ }
+ }
+ }
+
+ return $tables[$type];
+}
+
+/**
+ * Helper function for adding a Views field definition to data selection based Views tables.
+ *
+ * @param $field
+ * The data selector of the field to add. E.g. "title" would derive the node
+ * title property, "body:summary" the node body's summary.
+ * @param array $property_info
+ * The property information for which to create a field definition.
+ * @param array $table
+ * The table into which the definition should be inserted.
+ * @param $title_prefix
+ * Internal use only.
+ *
+ * @see entity_views_table_definition()
+ */
+function entity_views_field_definition($field, array $property_info, array &$table, $title_prefix = '') {
+ $additional = array();
+ $additional_field = array();
+
+ // Create a valid Views field identifier (no colons, etc.). Keep the original
+ // data selector as real field though.
+ $key = _entity_views_field_identifier($field, $table);
+ if ($key != $field) {
+ $additional['real field'] = $field;
+ }
+ $field_name = EntityFieldHandlerHelper::get_selector_field_name($field);
+
+ $field_handlers = entity_views_get_field_handlers();
+
+ $property_info += entity_property_info_defaults();
+ $type = entity_property_extract_innermost_type($property_info['type']);
+ $title = $title_prefix . $property_info['label'];
+ if ($info = entity_get_info($type)) {
+ $additional['relationship'] = array(
+ 'handler' => $field_handlers['relationship'],
+ 'base' => 'entity_' . $type,
+ 'base field' => $info['entity keys']['id'],
+ 'relationship field' => $field,
+ 'label' => $title,
+ );
+ if ($property_info['type'] != $type) {
+ // This is a list of entities, so we should mark the relationship as such.
+ $additional['relationship']['multiple'] = TRUE;
+ }
+ // Implementers of the field handlers alter hook could add handlers for
+ // specific entity types.
+ if (!isset($field_handlers[$type])) {
+ $type = 'entity';
+ }
+ }
+ elseif (!empty($property_info['field'])) {
+ $type = 'field';
+ // Views' Field API field handler needs some extra definitions to work.
+ $additional_field['field_name'] = $field_name;
+ $additional_field['entity_tables'] = array();
+ $additional_field['entity type'] = $table['table']['entity type'];
+ $additional_field['is revision'] = FALSE;
+ }
+ // Copied from EntityMetadataWrapper::optionsList()
+ elseif (isset($property_info['options list']) && is_callable($property_info['options list'])) {
+ // If this is a nested property, we need to get rid of all prefixes first.
+ $type = 'options';
+ $additional_field['options callback'] = array(
+ 'function' => $property_info['options list'],
+ 'info' => $property_info,
+ );
+ }
+ elseif ($type == 'decimal') {
+ $additional_field['float'] = TRUE;
+ }
+
+ if (isset($field_handlers[$type])) {
+ $table += array($key => array());
+ $table[$key] += array(
+ 'title' => $title,
+ 'help' => empty($property_info['description']) ? t('(No information available)') : $property_info['description'],
+ 'field' => array(),
+ );
+ $table[$key]['field'] += array(
+ 'handler' => $field_handlers[$type],
+ 'type' => $property_info['type'],
+ );
+ $table[$key] += $additional;
+ $table[$key]['field'] += $additional_field;
+ }
+ if (!empty($property_info['property info'])) {
+ foreach ($property_info['property info'] as $nested_key => $nested_property) {
+ entity_views_field_definition($field . ':' . $nested_key, $nested_property, $table, $title . ' » ');
+ }
+ }
+}
+
+/**
+ * @return array
+ * The handlers to use for the data selection based Views tables.
+ *
+ * @see hook_entity_views_field_handlers_alter()
+ */
+function entity_views_get_field_handlers() {
+ $field_handlers = drupal_static(__FUNCTION__);
+ if (!isset($field_handlers)) {
+ // Field handlers for the entity tables, by type.
+ $field_handlers = array(
+ 'text' => 'entity_views_handler_field_text',
+ 'token' => 'entity_views_handler_field_text',
+ 'integer' => 'entity_views_handler_field_numeric',
+ 'decimal' => 'entity_views_handler_field_numeric',
+ 'date' => 'entity_views_handler_field_date',
+ 'duration' => 'entity_views_handler_field_duration',
+ 'boolean' => 'entity_views_handler_field_boolean',
+ 'uri' => 'entity_views_handler_field_uri',
+ 'options' => 'entity_views_handler_field_options',
+ 'field' => 'entity_views_handler_field_field',
+ 'entity' => 'entity_views_handler_field_entity',
+ 'relationship' => 'entity_views_handler_relationship',
+ );
+ drupal_alter('entity_views_field_handlers', $field_handlers);
+ }
+ return $field_handlers;
+}
+
+/**
+ * Helper function for creating valid Views field identifiers out of data selectors.
+ *
+ * Uses $table to test whether the identifier is already used, and also
+ * recognizes if a definition for the same field is already present and returns
+ * that definition's identifier.
+ *
+ * @return string
+ * A valid Views field identifier that is not yet used as a key in $table.
+ */
+function _entity_views_field_identifier($field, array $table) {
+ $key = $base = preg_replace('/[^a-zA-Z0-9]+/S', '_', $field);
+ $i = 0;
+ // The condition checks whether this sanitized field identifier is already
+ // used for another field in this table (and whether the identifier is
+ // "table", which can never be used).
+ // If $table[$key] is set, the identifier is already used, but this might be
+ // already for the same field. To test that, we need the original field name,
+ // which is either $table[$key]['real field'], if set, or $key. If this
+ // original field name is equal to $field, we can use that key. Otherwise, we
+ // append numeric suffixes until we reach an unused key.
+ while ($key == 'table' || (isset($table[$key]) && (isset($table[$key]['real field']) ? $table[$key]['real field'] : $key) != $field)) {
+ $key = $base . '_' . ++$i;
+ }
+ return $key;
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function entity_views_plugins() {
+ // Have views cache the table list for us so it gets
+ // cleared at the appropriate times.
+ $data = views_cache_get('entity_base_tables', TRUE);
+ if (!empty($data->data)) {
+ $base_tables = $data->data;
+ }
+ else {
+ $base_tables = array();
+ foreach (views_fetch_data() as $table => $data) {
+ if (!empty($data['table']['entity type']) && !empty($data['table']['base'])) {
+ $base_tables[] = $table;
+ }
+ }
+ views_cache_set('entity_base_tables', $base_tables, TRUE);
+ }
+ if (!empty($base_tables)) {
+ return array(
+ 'module' => 'entity',
+ 'row' => array(
+ 'entity' => array(
+ 'title' => t('Rendered entity'),
+ 'help' => t('Renders a single entity in a specific view mode (e.g. teaser).'),
+ 'handler' => 'entity_views_plugin_row_entity_view',
+ 'uses fields' => FALSE,
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'base' => $base_tables,
+ ),
+ ),
+ );
+ }
+}
+
+/**
+ * Default controller for generating basic views integration.
+ *
+ * The controller tries to generate suiting views integration for the entity
+ * based upon the schema information of its base table and the provided entity
+ * property information.
+ * For that it is possible to map a property name to its schema/views field
+ * name by adding a 'schema field' key with the name of the field as value to
+ * the property info.
+ */
+class EntityDefaultViewsController {
+
+ protected $type, $info, $relationships;
+
+ public function __construct($type) {
+ $this->type = $type;
+ $this->info = entity_get_info($type);
+ }
+
+ /**
+ * Defines the result for hook_views_data().
+ */
+ public function views_data() {
+ $data = array();
+ $this->relationships = array();
+
+ if (!empty($this->info['base table'])) {
+ $table = $this->info['base table'];
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data[$table]['table']['group'] = drupal_ucfirst($this->info['label']);
+ $data[$table]['table']['entity type'] = $this->type;
+
+ // If the plural label isn't available, use the regular label.
+ $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label'];
+ $data[$table]['table']['base'] = array(
+ 'field' => $this->info['entity keys']['id'],
+ 'access query tag' => $this->type . '_access',
+ 'title' => drupal_ucfirst($label),
+ 'help' => isset($this->info['description']) ? $this->info['description'] : '',
+ );
+ $data[$table]['table']['entity type'] = $this->type;
+ $data[$table] += $this->schema_fields();
+
+ // Add in any reverse-relationships which have been determined.
+ $data += $this->relationships;
+ }
+ if (!empty($this->info['revision table']) && !empty($this->info['entity keys']['revision'])) {
+ $revision_table = $this->info['revision table'];
+
+ $data[$table]['table']['default_relationship'] = array(
+ $revision_table => array(
+ 'table' => $revision_table,
+ 'field' => $this->info['entity keys']['revision'],
+ ),
+ );
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data[$revision_table]['table']['group'] = drupal_ucfirst($this->info['label']) . ' ' . t('Revisions');
+ $data[$revision_table]['table']['entity type'] = $this->type;
+
+ // If the plural label isn't available, use the regular label.
+ $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label'];
+ $data[$revision_table]['table']['base'] = array(
+ 'field' => $this->info['entity keys']['revision'],
+ 'access query tag' => $this->type . '_access',
+ 'title' => drupal_ucfirst($label) . ' ' . t('Revisions'),
+ 'help' => (isset($this->info['description']) ? $this->info['description'] . ' ' : '') . t('Revisions'),
+ );
+ $data[$revision_table]['table']['entity type'] = $this->type;
+ $data[$revision_table] += $this->schema_revision_fields();
+
+ // Add in any reverse-relationships which have been determined.
+ $data += $this->relationships;
+
+ // For other base tables, explain how we join.
+ $data[$revision_table]['table']['join'] = array(
+ // Directly links to base table.
+ $table => array(
+ 'left_field' => $this->info['entity keys']['revision'],
+ 'field' => $this->info['entity keys']['revision'],
+ ),
+ );
+ $data[$revision_table]['table']['default_relationship'] = array(
+ $table => array(
+ 'table' => $table,
+ 'field' => $this->info['entity keys']['id'],
+ ),
+ );
+ }
+ return $data;
+ }
+
+ /**
+ * Try to come up with some views fields with the help of the schema and
+ * the entity property information.
+ */
+ protected function schema_fields() {
+ $schema = drupal_get_schema($this->info['base table']);
+ $properties = entity_get_property_info($this->type) + array('properties' => array());
+ $data = array();
+
+ foreach ($properties['properties'] as $name => $property_info) {
+ if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) {
+ if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) {
+ $data[$name] = $views_info;
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Try to come up with some views fields with the help of the revision schema
+ * and the entity property information.
+ */
+ protected function schema_revision_fields() {
+ $data = array();
+ if (!empty($this->info['revision table'])) {
+ $schema = drupal_get_schema($this->info['revision table']);
+ $properties = entity_get_property_info($this->type) + array('properties' => array());
+
+ foreach ($properties['properties'] as $name => $property_info) {
+ if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) {
+ if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) {
+ $data[$name] = $views_info;
+ }
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Comes up with views information based on the given schema and property
+ * info.
+ */
+ protected function map_from_schema_info($property_name, $schema_field_info, $property_info) {
+ $type = isset($property_info['type']) ? $property_info['type'] : 'text';
+ $views_field_name = $property_info['schema field'];
+
+ $return = array();
+
+ if (!empty($schema_field_info['serialize'])) {
+ return FALSE;
+ }
+
+ $description = array(
+ 'title' => $property_info['label'],
+ 'help' => isset($property_info['description']) ? $property_info['description'] : NULL,
+ );
+
+ // Add in relationships to related entities.
+ if (($info = entity_get_info($type)) && !empty($info['base table'])) {
+
+ // Prepare reversed relationship data.
+ $label_lowercase = drupal_strtolower($this->info['label'][0]) . drupal_substr($this->info['label'], 1);
+ $property_label_lowercase = drupal_strtolower($property_info['label'][0]) . drupal_substr($property_info['label'], 1);
+
+ // We name the field of the first reverse-relationship just with the
+ // base table to be backward compatible, for subsequents relationships we
+ // append the views field name in order to get a unique name.
+ $name = !isset($this->relationships[$info['base table']][$this->info['base table']]) ? $this->info['base table'] : $this->info['base table'] . '_' . $views_field_name;
+ $this->relationships[$info['base table']][$name] = array(
+ 'title' => $this->info['label'],
+ 'help' => t("Associated @label via the @label's @property.", array('@label' => $label_lowercase, '@property' => $property_label_lowercase)),
+ 'relationship' => array(
+ 'label' => $this->info['label'],
+ 'handler' => $this->getRelationshipHandlerClass($this->type, $type),
+ 'base' => $this->info['base table'],
+ 'base field' => $views_field_name,
+ 'relationship field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'],
+ ),
+ );
+
+ $return['relationship'] = array(
+ 'label' => drupal_ucfirst($info['label']),
+ 'handler' => $this->getRelationshipHandlerClass($type, $this->type),
+ 'base' => $info['base table'],
+ 'base field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'],
+ 'relationship field' => $views_field_name,
+ );
+
+ // Add in direct field/filters/sorts for the id itself too.
+ $type = isset($info['entity keys']['name']) ? 'token' : 'integer';
+ // Append the views-field-name to the title if it is different to the
+ // property name.
+ if ($property_name != $views_field_name) {
+ $description['title'] .= ' ' . $views_field_name;
+ }
+ }
+
+ switch ($type) {
+ case 'token':
+ case 'text':
+ $return += $description + array(
+ 'field' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+ break;
+
+ case 'decimal':
+ case 'integer':
+ $return += $description + array(
+ 'field' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ 'float' => ($type == 'decimal'),
+ ),
+ 'sort' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'argument' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ );
+ break;
+
+ case 'date':
+ $return += $description + array(
+ 'field' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_filter_date',
+ ),
+ 'argument' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_argument_date',
+ ),
+ );
+ break;
+
+ case 'uri':
+ $return += $description + array(
+ 'field' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_field_url',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+ break;
+
+ case 'boolean':
+ $return += $description + array(
+ 'field' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_filter_boolean_operator',
+ ),
+ 'argument' => array(
+ 'real field' => $views_field_name,
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+ break;
+ }
+
+ // If there is an options list callback, add to the filter and field.
+ if (isset($return['filter']) && !empty($property_info['options list'])) {
+ $return['filter']['handler'] = 'views_handler_filter_in_operator';
+ $return['filter']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback');
+ $return['filter']['options arguments'] = array($this->type, $property_name, 'view');
+ }
+ // @todo: This class_exists is needed until views 3.2.
+ if (isset($return['field']) && !empty($property_info['options list']) && class_exists('views_handler_field_machine_name')) {
+ $return['field']['handler'] = 'views_handler_field_machine_name';
+ $return['field']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback');
+ $return['field']['options arguments'] = array($this->type, $property_name, 'view');
+ }
+ return $return;
+ }
+
+ /**
+ * Determines the handler to use for a relationship to an entity type.
+ *
+ * @param $entity_type
+ * The entity type to join to.
+ * @param $left_type
+ * The data type from which to join.
+ */
+ function getRelationshipHandlerClass($entity_type, $left_type) {
+ // Look for an entity type which is used as bundle for the given entity
+ // type. If there is one, allow filtering the relation by bundle by using
+ // our own handler.
+ foreach (entity_get_info() as $type => $info) {
+ // In case we already join from the bundle entity we do not need to filter
+ // by bundle entity any more, so we stay with the general handler.
+ if (!empty($info['bundle of']) && $info['bundle of'] == $entity_type && $type != $left_type) {
+ return 'entity_views_handler_relationship_by_bundle';
+ }
+ }
+ return 'views_handler_relationship';
+ }
+
+ /**
+ * A callback returning property options, suitable to be used as views options callback.
+ */
+ public static function optionsListCallback($type, $selector, $op = 'view') {
+ $wrapper = entity_metadata_wrapper($type, NULL);
+ $parts = explode(':', $selector);
+ foreach ($parts as $part) {
+ $wrapper = $wrapper->get($part);
+ }
+ return $wrapper->optionsList($op);
+ }
+}
diff --git a/sites/all/modules/entity/views/entity_views_example_query.php b/sites/all/modules/entity/views/entity_views_example_query.php
new file mode 100644
index 000000000..7e98e2c26
--- /dev/null
+++ b/sites/all/modules/entity/views/entity_views_example_query.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains an example for a Views query plugin that could use the data selection tables.
+ */
+
+/**
+ * Describes the additional methods looked for on a query plugin if data selection based tables or fields are used.
+ *
+ * Only get_result_entities() needs to be present, so results can be retrieved.
+ * The other methods are optional.
+ *
+ * If the table does not contain entities, however, the get_result_wrappers()
+ * method is necessary, too. If this is the case and there are no relations to
+ * entity tables, the get_result_entities() method is not needed.
+ *
+ * @see entity_views_table_definition()
+ */
+abstract class entity_views_example_query extends views_plugin_query {
+
+ /**
+ * Add a sort to the query.
+ *
+ * This is used to add a sort based on an Entity API data selector instead
+ * of a field alias.
+ *
+ * This method has to be present if click-sorting on fields should be allowed
+ * for some fields using the default Entity API field handlers.
+ *
+ * @param $selector
+ * The field to sort on, as an Entity API data selector.
+ * @param $order
+ * The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
+ */
+ public abstract function add_selector_orderby($selector, $order = 'ASC');
+
+ /**
+ * Returns the according entity objects for the given query results.
+ *
+ * This is compatible to the get_result_entities() method used by Views.
+ *
+ * The method is responsible for resolving the relationship and returning the
+ * entity objects for that relationship. The helper methods
+ * EntityFieldHandlerHelper::construct_property_selector() and
+ * EntityFieldHandlerHelper::extract_property_multiple() can be used to do
+ * this.
+ *
+ * @param $results
+ * The results of the query, as returned by this query plugin.
+ * @param $relationship
+ * (optional) A relationship for which the entities should be returned.
+ * @param $field
+ * (optional) The field for which the entity should be returned. This is
+ * only needed in case a field is derived via a referenced entity without
+ * using a relationship. For example, if the node's field "author:name" is
+ * used, the user entity would be returned instead of the node entity.
+ *
+ * @return
+ * A numerically indexed array containing two items: the entity type of
+ * entities returned by this method; and the array of entities, keyed by the
+ * same indexes as the results.
+ *
+ * @see EntityFieldHandlerHelper::extract_property_multiple()
+ */
+ public abstract function get_result_entities($results, $relationship = NULL, $field = NULL);
+
+ /**
+ * Returns the according metadata wrappers for the given query results.
+ *
+ * This can be used if no entities for the results can be given, but entity
+ * metadata wrappers can be constructed for them.
+ *
+ * @param $results
+ * The results of the query, as returned by this query plugin.
+ * @param $relationship
+ * (optional) A relationship for which the wrappers should be returned.
+ * @param $field
+ * (optional) The field of which a wrapper should be returned.
+ *
+ * @return
+ * A numerically indexed array containing two items: the data type of
+ * the wrappers returned by this method; and the array of retrieved
+ * EntityMetadataWrapper objects, keyed by the same indexes as the results.
+ */
+ public abstract function get_result_wrappers($results, $relationship = NULL, $field = NULL);
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_field_handler_helper.inc b/sites/all/modules/entity/views/handlers/entity_views_field_handler_helper.inc
new file mode 100644
index 000000000..0077f4a3d
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_field_handler_helper.inc
@@ -0,0 +1,527 @@
+<?php
+
+/**
+ * @file
+ * Contains the EntityFieldHandlerHelper class.
+ */
+
+/**
+ * Helper class containing static implementations of common field handler methods.
+ *
+ * Used by the data selection entity field handlers to avoid code duplication.
+ *
+ * @see entity_views_table_definition()
+ */
+class EntityFieldHandlerHelper {
+
+ /**
+ * Provide appropriate default options for a handler.
+ */
+ public static function option_definition($handler) {
+ if (entity_property_list_extract_type($handler->definition['type'])) {
+ $options['list']['contains']['mode'] = array('default' => 'collapse');
+ $options['list']['contains']['separator'] = array('default' => ', ');
+ $options['list']['contains']['type'] = array('default' => 'ul');
+ }
+ $options['link_to_entity'] = array('default' => FALSE);
+
+ return $options;
+ }
+
+ /**
+ * Provide an appropriate default option form for a handler.
+ */
+ public static function options_form($handler, &$form, &$form_state) {
+ if (entity_property_list_extract_type($handler->definition['type'])) {
+ $form['list']['mode'] = array(
+ '#type' => 'select',
+ '#title' => t('List handling'),
+ '#options' => array(
+ 'collapse' => t('Concatenate values using a seperator'),
+ 'list' => t('Output values as list'),
+ 'first' => t('Show first (if present)'),
+ 'count' => t('Show item count'),
+ ),
+ '#default_value' => $handler->options['list']['mode'],
+ );
+ $form['list']['separator'] = array(
+ '#type' => 'textfield',
+ '#title' => t('List seperator'),
+ '#default_value' => $handler->options['list']['separator'],
+ '#dependency' => array('edit-options-list-mode' => array('collapse')),
+ );
+ $form['list']['type'] = array(
+ '#type' => 'select',
+ '#title' => t('List type'),
+ '#options' => array(
+ 'ul' => t('Unordered'),
+ 'ol' => t('Ordered'),
+ ),
+ '#default_value' => $handler->options['list']['type'],
+ '#dependency' => array('edit-options-list-mode' => array('list')),
+ );
+ }
+ $form['link_to_entity'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Link this field to its entity'),
+ '#description' => t("When using this, you should not set any other link on the field."),
+ '#default_value' => $handler->options['link_to_entity'],
+ );
+ }
+
+ /**
+ * Add the field for the entity ID (if necessary).
+ */
+ public static function query($handler) {
+ // Copied over from views_handler_field_entity::query().
+ // Sets table_alias (entity table), base_field (entity id field) and
+ // field_alias (the field's alias).
+ $handler->table_alias = $base_table = $handler->view->base_table;
+ $handler->base_field = $handler->view->base_field;
+
+ if (!empty($handler->relationship)) {
+ foreach ($handler->view->relationship as $relationship) {
+ if ($relationship->alias == $handler->relationship) {
+ $base_table = $relationship->definition['base'];
+ $handler->table_alias = $relationship->alias;
+
+ $table_data = views_fetch_data($base_table);
+ $handler->base_field = empty($relationship->definition['base field']) ? $table_data['table']['base']['field'] : $relationship->definition['base field'];
+ }
+ }
+ }
+
+ // Add the field if the query back-end implements an add_field() method,
+ // just like the default back-end.
+ if (method_exists($handler->query, 'add_field')) {
+ $handler->field_alias = $handler->query->add_field($handler->table_alias, $handler->base_field, '');
+ }
+ else {
+ // To ensure there is an alias just set the field alias to the real field.
+ $handler->field_alias = $handler->real_field;
+ }
+ }
+
+ /**
+ * Extracts the innermost field name from a data selector.
+ *
+ * @param $selector
+ * The data selector.
+ *
+ * @return
+ * The last component of the data selector.
+ */
+ public static function get_selector_field_name($selector) {
+ return ltrim(substr($selector, strrpos($selector, ':')), ':');
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ *
+ * @param $order
+ * Either 'ASC' or 'DESC'.
+ */
+ public static function click_sort($handler, $order) {
+ // The normal orderby() method for this usually won't work here. So we need
+ // query plugins to provide their own method for this.
+ if (method_exists($handler->query, 'add_selector_orderby')) {
+ $selector = self::construct_property_selector($handler, TRUE);
+ $handler->query->add_selector_orderby($selector, $order);
+ }
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ *
+ * Automatically takes care of relationships, including data selection
+ * relationships. Results are written into @code $handler->wrappers @endcode
+ * and @code $handler->entity_type @endcode is set.
+ */
+ public static function pre_render($handler, &$values, $load_always = FALSE) {
+ if (empty($values)) {
+ return;
+ }
+ if (!$load_always && empty($handler->options['link_to_entity'])) {
+ // Check whether we even need to load the entities.
+ $selector = self::construct_property_selector($handler, TRUE);
+ $load = FALSE;
+ foreach ($values as $row) {
+ if (empty($row->_entity_properties) || !array_key_exists($selector, $row->_entity_properties)) {
+ $load = TRUE;
+ break;
+ }
+ }
+ if (!$load) {
+ return;
+ }
+ }
+
+ if (method_exists($handler->query, 'get_result_wrappers')) {
+ list($handler->entity_type, $handler->wrappers) = $handler->query->get_result_wrappers($values, $handler->relationship, $handler->real_field);
+ }
+ else {
+ list($handler->entity_type, $entities) = $handler->query->get_result_entities($values, $handler->relationship, $handler->real_field);
+ $handler->wrappers = array();
+ foreach ($entities as $id => $entity) {
+ $handler->wrappers[$id] = entity_metadata_wrapper($handler->entity_type, $entity);
+ }
+ }
+ }
+
+ /**
+ * Return an Entity API data selector for the given handler's relationship.
+ *
+ * A data selector is a concatenation of properties which should be followed
+ * to arrive at a desired property that may be nested in related entities or
+ * structures. The separate properties are herein concatenated with colons.
+ *
+ * For instance, a data selector of "author:roles" would mean to first
+ * access the "author" property of the given wrapper, and then for this new
+ * wrapper to access and return the "roles" property.
+ *
+ * Lists of entities are handled automatically by always returning only the
+ * first entity.
+ *
+ * @param $handler
+ * The handler for which to construct the selector.
+ * @param $complete
+ * If TRUE, the complete selector for the field is returned, not just the
+ * one for its parent. Defaults to FALSE.
+ *
+ * @return
+ * An Entity API data selector for the given handler's relationship.
+ */
+ public static function construct_property_selector($handler, $complete = FALSE) {
+ $return = '';
+ if ($handler->relationship) {
+ $current_handler = $handler;
+ $view = $current_handler->view;
+ $relationships = array();
+ // Collect all relationships, keyed by alias.
+ foreach ($view->relationship as $key => $relationship) {
+ $key = $relationship->alias ? $relationship->alias : $key;
+ $relationships[$key] = $relationship;
+ }
+ while (!empty($current_handler->relationship) && !empty($relationships[$current_handler->relationship])) {
+ $current_handler = $relationships[$current_handler->relationship];
+ $return = $current_handler->real_field . ($return ? ":$return" : '');
+ }
+ }
+
+ if ($complete) {
+ $return .= ($return ? ':' : '') . $handler->real_field;
+ }
+ elseif ($pos = strrpos($handler->real_field, ':')) {
+ // If we have a selector as the real_field, append this to the returned
+ // relationship selector.
+ $return .= ($return ? ':' : '') . substr($handler->real_field, 0, $pos);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Extracts data from several metadata wrappers based on a data selector.
+ *
+ * All metadata wrappers passed to this function have to be based on the exact
+ * same property information. The data will be returned wrapped by one or more
+ * metadata wrappers.
+ *
+ * Can be used in query plugins for the get_result_entities() and
+ * get_result_wrappers() methods.
+ *
+ * @param array $wrappers
+ * The EntityMetadataWrapper objects from which to extract data.
+ * @param $selector
+ * The selector specifying the data to extract.
+ *
+ * @return array
+ * An array with numeric indices, containing the type of the extracted
+ * wrappers in the first element. The second element of the array contains
+ * the extracted property value(s) for each wrapper, keyed to the same key
+ * that was used for the respecive wrapper in $wrappers. All extracted
+ * properties are returned as metadata wrappers.
+ */
+ public static function extract_property_multiple(array $wrappers, $selector) {
+ $parts = explode(':', $selector, 2);
+ $name = $parts[0];
+
+ $results = array();
+ $entities = array();
+ $type = '';
+ foreach ($wrappers as $i => $wrapper) {
+ try {
+ $property = $wrapper->$name;
+ $type = $property->type();
+ if ($property instanceof EntityDrupalWrapper) {
+ // Remember the entity IDs to later load all at once (so as to
+ // properly utilize multiple load functionality).
+ $id = $property->getIdentifier();
+ // Only accept valid ids. $id can be FALSE for entity values that are
+ // NULL.
+ if ($id) {
+ $entities[$type][$i] = $id;
+ }
+ }
+ elseif ($property instanceof EntityStructureWrapper) {
+ $results[$i] = $property;
+ }
+ elseif ($property instanceof EntityListWrapper) {
+ foreach ($property as $item) {
+ $results[$i] = $item;
+ $type = $item->type();
+ break;
+ }
+ }
+ // Do nothing in case it cannot be applied.
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // Skip single empty properties.
+ }
+ }
+
+ if ($entities) {
+ // Map back the loaded entities back to the results array.
+ foreach ($entities as $type => $id_map) {
+ $loaded = entity_load($type, $id_map);
+ foreach ($id_map as $i => $id) {
+ if (isset($loaded[$id])) {
+ $results[$i] = entity_metadata_wrapper($type, $loaded[$id]);
+ }
+ }
+ }
+ }
+
+ // If there are no further parts in the selector, we are done now.
+ if (empty($parts[1])) {
+ return array($type, $results);
+ }
+ return self::extract_property_multiple($results, $parts[1]);
+ }
+
+ /**
+ * Get the value of a certain data selector.
+ *
+ * Uses $values->_entity_properties to look for already extracted properties.
+ *
+ * @param $handler
+ * The field handler for which to return a value.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ * @param $field
+ * The field to extract. If no value is given, the field of the given
+ * handler is used instead. The special "entity object" value can be used to
+ * get the base entity instead of a special field.
+ * @param $default
+ * The value to return if the entity or field are not present.
+ */
+ public static function get_value($handler, $values, $field = NULL, $default = NULL) {
+ // There is a value cache on each handler so parent handlers rendering a
+ // single field value from a list will get the single value, not the whole
+ // list.
+ if (!isset($field) && isset($handler->current_value)) {
+ return $handler->current_value;
+ }
+ $field = isset($field) ? $field : self::get_selector_field_name($handler->real_field);
+ $selector = self::construct_property_selector($handler);
+ $selector = $selector ? "$selector:$field" : $field;
+ if (!isset($values->_entity_properties)) {
+ $values->_entity_properties = array();
+ }
+ if (!array_key_exists($selector, $values->_entity_properties)) {
+ if (!isset($handler->wrappers[$handler->view->row_index])) {
+ $values->_entity_properties[$selector] = $default;
+ }
+ elseif (is_array($handler->wrappers[$handler->view->row_index])) {
+ $values->_entity_properties[$selector] = self::extract_list_wrapper_values($handler->wrappers[$handler->view->row_index], $field);
+ }
+ else {
+ $wrapper = $handler->wrappers[$handler->view->row_index];
+ try {
+ if ($field === 'entity object') {
+ $values->_entity_properties[$selector] = $wrapper->value();
+ }
+ else {
+ $values->_entity_properties[$selector] = isset($wrapper->$field) ? $wrapper->$field->value(array('identifier' => TRUE, 'sanitize' => TRUE)) : $default;
+ }
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $values->_entity_properties[$selector] = $default;
+ }
+ }
+ }
+ return $values->_entity_properties[$selector];
+ }
+
+ /**
+ * Helper method for extracting the values from an array of wrappers.
+ *
+ * Nested arrays of wrappers are also handled, the values are returned in a
+ * flat (not nested) array.
+ */
+ public static function extract_list_wrapper_values(array $wrappers, $field) {
+ $return = array();
+ foreach ($wrappers as $wrapper) {
+ if (is_array($wrapper)) {
+ $values = self::extract_list_wrapper_values($wrapper, $field);
+ if ($values) {
+ $return = array_merge($return, $values);
+ }
+ }
+ else {
+ try {
+ if ($field == 'entity object') {
+ $return[] = $wrapper->value();
+ }
+ elseif (isset($wrapper->$field)) {
+ $return[] = $wrapper->$field->value(array('identifier' => TRUE));
+ }
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // An exception probably signifies a non-present property, so we just
+ // ignore it.
+ }
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Render the field.
+ *
+ * Implements the entity link functionality and list handling. Basic handling
+ * of the single values is delegated back to the field handler.
+ *
+ * @param $handler
+ * The field handler whose field should be rendered.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ *
+ * @return
+ * The rendered value for the field.
+ */
+ public static function render($handler, $values) {
+ $value = $handler->get_value($values);
+ if (is_array($value)) {
+ return self::render_list($handler, $value, $values);
+ }
+ return self::render_entity_link($handler, $value, $values);
+ }
+
+ /**
+ * Render a list of values.
+ *
+ * @param $handler
+ * The field handler whose field is rendered.
+ * @param $list
+ * The list of values to render.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ *
+ * @return
+ * The rendered value for the given list.
+ */
+ public static function render_list($handler, $list, $values) {
+ // Allow easy overriding of this behaviour in the specific field handler.
+ if (method_exists($handler, 'render_list')) {
+ return $handler->render_list($list, $values);
+ }
+ $mode = isset($handler->options['list']['mode']) ? $handler->options['list']['mode'] : NULL;
+ switch ($mode) {
+ case 'first':
+ $list = count($list) ? array_shift($list) : NULL;
+ if (is_array($list)) {
+ return self::render_list($handler, $list, $values);
+ }
+ elseif (isset($list)) {
+ return self::render_entity_link($handler, $list, $values);
+ }
+ return NULL;
+
+ case 'count':
+ return count($list);
+
+ // Handles both collapse and list output. Fallback is to collapse.
+ default:
+ $inner_values = array();
+ foreach ($list as $value) {
+ $value = is_array($value) ? self::render_list($handler, $value, $values) : self::render_entity_link($handler, $value, $values);
+ if ($value) {
+ $inner_values[] = $value;
+ }
+ }
+
+ // Format output as list.
+ if ($mode == 'list') {
+ $type = isset($handler->options['list']['type']) ? $handler->options['list']['type'] : 'ul';
+ return theme('item_list', array(
+ 'items' => $inner_values,
+ 'type' => $type,
+ ));
+ }
+
+ $separator = isset($handler->options['list']['separator']) ? $handler->options['list']['separator'] : ', ';
+ return implode($separator, $inner_values);
+ }
+ }
+
+ /**
+ * Render a single value as a link to the entity if applicable.
+ *
+ * @param $handler
+ * The field handler whose field is rendered.
+ * @param $value
+ * The single value to render.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ *
+ * @return
+ * The rendered value.
+ */
+ public static function render_entity_link($handler, $value, $values) {
+ // Allow easy overriding of this behaviour in the specific field handler.
+ if (method_exists($handler, 'render_entity_link')) {
+ return $handler->render_entity_link($value, $values);
+ }
+ $render = self::render_single_value($handler, $value, $values);
+ if (!$handler->options['link_to_entity']) {
+ return $render;
+ }
+ $entity = $handler->get_value($values, 'entity object');
+ if (is_object($entity) && ($url = entity_uri($handler->entity_type, $entity))) {
+ return l($render, $url['path'], array('html' => TRUE) + $url['options']);
+ }
+ return $render;
+ }
+
+ /**
+ * Render a single value.
+ *
+ * @param $handler
+ * The field handler whose field is rendered.
+ * @param $value
+ * The single value to render.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ *
+ * @return
+ * The rendered value.
+ */
+ public static function render_single_value($handler, $value, $values) {
+ // Try to use the method in the specific field handler.
+ if (method_exists($handler, 'render_single_value')) {
+ $handler->current_value = $value;
+ $return = $handler->render_single_value($value, $values);
+ unset($handler->current_value);
+ return $return;
+ }
+ // Default fallback in case the field handler doesn't provide the method.
+ return is_scalar($value) ? check_plain($value) : nl2br(check_plain(print_r($value, TRUE)));
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_area_entity.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_area_entity.inc
new file mode 100644
index 000000000..f37469424
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_area_entity.inc
@@ -0,0 +1,120 @@
+<?php
+/**
+ * @file
+ * Renders a full entity in a views area.
+ */
+
+class entity_views_handler_area_entity extends views_handler_area {
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options['entity_type'] = array('default' => 'node');
+ $options['entity_id'] = array('default' => '');
+ $options['view_mode'] = array('default' => 'full');
+ $options['bypass_access'] = array('default' => FALSE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $entity_type_options = array();
+ foreach (entity_get_info() as $entity_type => $entity_info) {
+ $entity_type_options[$entity_type] = $entity_info['label'];
+ }
+
+ $entity_type = $this->options['entity_type'];
+
+ $form['entity_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Entity type'),
+ '#options' => $entity_type_options,
+ '#description' => t('Choose the entity type you want to display in the area.'),
+ '#default_value' => $entity_type,
+ '#ajax' => array(
+ 'path' => views_ui_build_form_url($form_state),
+ ),
+ '#submit' => array('views_ui_config_item_form_submit_temporary'),
+ '#executes_submit_callback' => TRUE,
+ );
+
+ $form['entity_id'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Entity id'),
+ '#description' => t('Choose the entity you want to display in the area.'),
+ '#default_value' => $this->options['entity_id'],
+ );
+
+ if ($entity_type) {
+ $entity_info = entity_get_info($entity_type);
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+
+ if (count($options) > 1) {
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $this->options['view_mode'],
+ );
+ }
+ else {
+ $form['view_mode_info'] = array(
+ '#type' => 'item',
+ '#title' => t('View mode'),
+ '#description' => t('Only one view mode is available for this entity type.'),
+ '#markup' => $options ? current($options) : t('Default'),
+ );
+ $form['view_mode'] = array(
+ '#type' => 'value',
+ '#value' => $options ? key($options) : 'default',
+ );
+ }
+ }
+ $form['bypass_access'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Bypass access checks'),
+ '#description' => t('If enabled, access permissions for rendering the entity are not checked.'),
+ '#default_value' => !empty($this->options['bypass_access']),
+ );
+ return $form;
+ }
+
+ public function admin_summary() {
+ $label = parent::admin_summary();
+ if (!empty($this->options['entity_id'])) {
+ return t('@label @entity_type:@entity_id', array(
+ '@label' => $label,
+ '@entity_type' => $this->options['entity_type'],
+ '@entity_id' => $this->options['entity_id'],
+ ));
+ }
+ }
+
+ public function render($empty = FALSE) {
+ if (!$empty || !empty($this->options['empty'])) {
+ return $this->render_entity($this->options['entity_type'], $this->options['entity_id'], $this->options['view_mode']);
+ }
+ return '';
+ }
+
+ /**
+ * Render an entity using the view mode.
+ */
+ public function render_entity($entity_type, $entity_id, $view_mode) {
+ if (!empty($entity_type) && !empty($entity_id) && !empty($view_mode)) {
+ $entity = entity_load_single($entity_type, $entity_id);
+ if (!empty($this->options['bypass_access']) || entity_access('view', $entity_type, $entity)) {
+ $render = entity_view($entity_type, array($entity), $view_mode);
+ $render_entity = reset($render);
+ return drupal_render($render_entity);
+ }
+ }
+ else {
+ return '';
+ }
+ }
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_boolean.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_boolean.inc
new file mode 100644
index 000000000..42943d9ad
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_boolean.inc
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_boolean class.
+ */
+
+/**
+ * A handler to provide proper displays for booleans.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_boolean extends views_handler_field_boolean {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return parent::render($values);
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_date.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_date.inc
new file mode 100644
index 000000000..89401cd09
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_date.inc
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_date class.
+ */
+
+/**
+ * A handler to provide proper displays for dates.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_date extends views_handler_field_date {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return parent::render($values);
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_duration.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_duration.inc
new file mode 100644
index 000000000..2850e6a79
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_duration.inc
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_duration class.
+ */
+
+/**
+ * A handler to provide proper displays for duration properties retrieved via data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_duration extends views_handler_field {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options += EntityFieldHandlerHelper::option_definition($this);
+
+ $options['format_interval'] = array('default' => TRUE);
+ $options['granularity'] = array('default' => 2);
+ $options['prefix'] = array('default' => '', 'translatable' => TRUE);
+ $options['suffix'] = array('default' => '', 'translatable' => TRUE);
+
+ return $options;
+ }
+
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+
+ $form['format_interval'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Format interval'),
+ '#description' => t('If checked, the value will be formatted as a time interval. Otherwise, just the number of seconds will be displayed.'),
+ '#default_value' => $this->options['format_interval'],
+ );
+ $form['granularity'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Granularity'),
+ '#default_value' => $this->options['granularity'],
+ '#description' => t('Specify how many different units to display.'),
+ '#dependency' => array('edit-options-format-interval' => array(TRUE)),
+ '#size' => 2,
+ );
+ $form['prefix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Prefix'),
+ '#default_value' => $this->options['prefix'],
+ '#description' => t('Text to put before the duration text.'),
+ );
+ $form['suffix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Suffix'),
+ '#default_value' => $this->options['suffix'],
+ '#description' => t('Text to put after the duration text.'),
+ );
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ if ($this->options['format_interval']) {
+ $value = format_interval($value, (int) $this->options['granularity']);
+ }
+ // Value sanitization is handled by the wrapper, see
+ // EntityFieldHandlerHelper::get_value().
+ return $this->sanitize_value($this->options['prefix'], 'xss') .
+ $value .
+ $this->sanitize_value($this->options['suffix'], 'xss');
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_entity.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_entity.inc
new file mode 100644
index 000000000..d9fe73b24
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_entity.inc
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_entity class.
+ */
+
+/**
+ * A handler to provide proper displays for entities retrieved via data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_entity extends views_handler_field {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The entity type of the entity displayed by this field.
+ */
+ public $field_entity_type;
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Initialize the entity type with the field's entity type.
+ */
+ public function init(&$view, &$options) {
+ parent::init($view, $options);
+ $this->field_entity_type = entity_property_extract_innermost_type($this->definition['type']);
+ }
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options += EntityFieldHandlerHelper::option_definition($this);
+
+ $options['display'] = array('default' => 'label');
+ $options['link_to_entity']['default'] = TRUE;
+ $options['view_mode'] = array('default' => 'default');
+ $options['bypass_access'] = array('default' => FALSE);
+
+ return $options;
+ }
+
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ // We want a different form field at a different place.
+ unset($form['link_to_entity']);
+
+ $options = array(
+ 'label' => t('Show entity label'),
+ 'id' => t('Show entity ID'),
+ 'view' => t('Show complete entity'),
+ );
+ $form['display'] = array(
+ '#type' => 'select',
+ '#title' => t('Display'),
+ '#description' => t('Decide how this field will be displayed.'),
+ '#options' => $options,
+ '#default_value' => $this->options['display'],
+ );
+ $form['link_to_entity'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Link to entity'),
+ '#description' => t('Link this field to the entity.'),
+ '#default_value' => $this->options['link_to_entity'],
+ '#dependency' => array('edit-options-display' => array('label', 'id')),
+ );
+
+ // Stolen from entity_views_plugin_row_entity_view.
+ $entity_info = entity_get_info($this->field_entity_type);
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+
+ if (count($options) > 1) {
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $this->options['view_mode'],
+ '#dependency' => array('edit-options-display' => array('view')),
+ );
+ }
+ else {
+ $form['view_mode'] = array(
+ '#type' => 'value',
+ '#value' => $options ? key($options) : 'default',
+ );
+ }
+ $form['bypass_access'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Bypass access checks'),
+ '#description' => t('If enabled, access permissions for rendering the entity are not checked.'),
+ '#default_value' => !empty($this->options['bypass_access']),
+ );
+ }
+
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a value as a link to the entity if applicable.
+ *
+ * @param $value
+ * The value to render.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ */
+ public function render_entity_link($entity, $values) {
+ $type = $this->field_entity_type;
+ if (!is_object($entity) && isset($entity) && $entity !== FALSE) {
+ $entity = entity_load_single($type, $entity);
+ }
+ if (!$entity) {
+ return '';
+ }
+ $render = $this->render_single_value($entity, $values);
+ if (!$this->options['link_to_entity'] || $this->options['display'] == 'view') {
+ return $render;
+ }
+ if (is_object($entity) && ($url = entity_uri($type, $entity))) {
+ return l($render, $url['path'], array('html' => TRUE) + $url['options']);
+ }
+ return $render;
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($entity, $values) {
+ $type = $this->field_entity_type;
+ if (!is_object($entity) && isset($entity) && $entity !== FALSE) {
+ $entity = entity_load_single($type, $entity);
+ }
+ // Make sure the entity exists and access is either given or bypassed.
+ if (!$entity || !(!empty($this->options['bypass_access']) || entity_access('view', $type, $entity))) {
+ return '';
+ }
+
+ if ($this->options['display'] === 'view') {
+ $entity_view = entity_view($type, array($entity), $this->options['view_mode']);
+ return render($entity_view);
+ }
+
+ if ($this->options['display'] == 'label') {
+ $value = entity_label($type, $entity);
+ }
+ // Either $options[display] == 'id', or we have no label.
+ if (empty($value)) {
+ $value = entity_id($type, $entity);
+ }
+ $value = $this->sanitize_value($value);
+
+ return $value;
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_field.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_field.inc
new file mode 100644
index 000000000..f3cd7da26
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_field.inc
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_field class.
+ */
+
+/**
+ * A handler to provide proper displays for Field API fields.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_field extends views_handler_field_field {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The entity for which this field is currently rendered.
+ */
+ public $entity;
+
+ /**
+ * Return TRUE if the user has access to view this field.
+ */
+ public function access() {
+ return field_access('view', $this->field_info, $this->definition['entity type']);
+ }
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query($use_groupby = FALSE) {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Override so it doesn't do any harm (or, anything at all).
+ */
+ public function post_execute(&$values) { }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values, TRUE);
+ }
+
+ /**
+ * Overridden to get the items our way.
+ */
+ public function get_items($values) {
+ $items = array();
+ // Set the entity type for the parent handler.
+ $values->_field_data[$this->field_alias]['entity_type'] = $this->entity_type;
+ // We need special handling for lists of entities as the base.
+ $entities = EntityFieldHandlerHelper::get_value($this, $values, 'entity object');
+ if (!is_array($entities)) {
+ $entities = $entities ? array($entities) : array();
+ }
+ foreach ($entities as $entity) {
+ // Only try to render the field if it is even present on this bundle.
+ // Otherwise, field_view_field() will trigger a fatal.
+ list (, , $bundle) = entity_extract_ids($this->entity_type, $entity);
+ if (field_info_instance($this->entity_type, $this->definition['field_name'], $bundle)) {
+ // Set the currently rendered entity.
+ $values->_field_data[$this->field_alias]['entity'] = $entity;
+ $items = array_merge($items, $this->set_items($values, $this->view->row_index));
+ }
+ }
+ return $items;
+ }
+
+ /**
+ * Overridden to force displaying multiple values in a single row.
+ */
+ function multiple_options_form(&$form, &$form_state) {
+ parent::multiple_options_form($form, $form_state);
+ $form['group_rows']['#default_value'] = TRUE;
+ $form['group_rows']['#disabled'] = TRUE;
+ }
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_numeric.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_numeric.inc
new file mode 100644
index 000000000..bf9814910
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_numeric.inc
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_numeric class.
+ */
+
+/**
+ * Render a field as a numeric value.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_numeric extends views_handler_field_numeric {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return parent::render($values);
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_options.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_options.inc
new file mode 100644
index 000000000..8792c8360
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_options.inc
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_options class.
+ */
+
+/**
+ * A handler to provide proper displays for values chosen from a set of options.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_options extends views_handler_field {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * The key / name mapping for the options.
+ */
+ public $option_list;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Specifies the options this handler uses.
+ */
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options += EntityFieldHandlerHelper::option_definition($this);
+ $options['format_name'] = array('default' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Returns an option form for setting this handler's options.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+
+ $form['format_name'] = array(
+ '#title' => t('Use human-readable name'),
+ '#type' => 'checkbox',
+ '#description' => t("If this is checked, the values' names will be displayed instead of their internal identifiers."),
+ '#default_value' => $this->options['format_name'],
+ '#weight' => -5,
+ );
+ }
+
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ if (!isset($this->option_list)) {
+ $this->option_list = array();
+ $callback = $this->definition['options callback'];
+ if (is_callable($callback['function'])) {
+ // If a selector is used, get the name of the selected field.
+ $field_name = EntityFieldHandlerHelper::get_selector_field_name($this->real_field);
+ $this->option_list = call_user_func($callback['function'], $field_name, $callback['info'], 'view');
+ }
+ }
+ if ($this->options['format_name'] && isset($this->option_list[$value])) {
+ $value = $this->option_list[$value];
+ }
+ // Sanitization is handled by the wrapper, see
+ // EntityFieldHandlerHelper::get_value().
+ return $value;
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_text.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_text.inc
new file mode 100644
index 000000000..425abf27e
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_text.inc
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_text class.
+ */
+
+/**
+ * A handler to display text data.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_text extends views_handler_field {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ // Sanitization is handled by the wrapper, see
+ // EntityFieldHandlerHelper::get_value().
+ return $value;
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_field_uri.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_field_uri.inc
new file mode 100644
index 000000000..c01882af4
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_uri.inc
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_uri class.
+ */
+
+/**
+ * Field handler to provide simple renderer that turns a URL into a clickable link.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_uri extends views_handler_field_url {
+
+ /**
+ * Stores the entity type of the result entities.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return parent::render($values);
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_relationship.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_relationship.inc
new file mode 100644
index 000000000..a15c05660
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_relationship.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_relationship class.
+ */
+
+/**
+ * Relationship handler for data selection tables.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_relationship extends views_handler_relationship {
+
+ /**
+ * Slightly modify the options form provided by the parent handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ // This won't work with data selector-based relationships, as we only
+ // inspect those *after* the results are known.
+ $form['required']['#access'] = FALSE;
+ // Notify the user of our restrictions regarding lists of entities, if
+ // appropriate.
+ if (!empty($this->definition['multiple'])) {
+ $form['multiple_note'] = array(
+ '#markup' => t('<strong>Note:</strong> This is a multi-valued relationship, which is currently not supported. ' .
+ 'Only the first related entity will be shown.'),
+ '#weight' => -5,
+ );
+ }
+ }
+
+ /**
+ * Called to implement a relationship in a query.
+ *
+ * As we don't add any data to the query itself, we don't have to do anything
+ * here. Views just don't thinks we have been called unless we set our
+ * $alias property. Otherwise, this override is just here to keep PHP from
+ * blowing up by calling inexistent methods on the query plugin.
+ */
+ public function query() {
+ $this->alias = $this->options['id'];
+ }
+
+}
diff --git a/sites/all/modules/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc b/sites/all/modules/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc
new file mode 100644
index 000000000..a4d09d3e1
--- /dev/null
+++ b/sites/all/modules/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc
@@ -0,0 +1,117 @@
+<?php
+/**
+ * @file
+ * Contains the entity_views_handler_relationship_by_bundle class.
+ */
+
+/**
+ * Relationship handler for entity relationships that may limit the join to one or more bundles.
+ *
+ * This handler is only applicable for entities that are using bundle entities,
+ * i.e. entities having the 'bundle of' entity info key set.
+ *
+ * For example, this allows a relationship from users to profiles of a certain
+ * profile type.
+ *
+ * @see entity_crud_hook_entity_info()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_relationship_by_bundle extends views_handler_relationship {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['bundle_types'] = array('default' => array());
+
+ return $options;
+ }
+
+ /**
+ * Add an entity type option.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ // Get the entity type and info from the table data for the base on the
+ // right hand side of the relationship join.
+ $table_data = views_fetch_data($this->definition['base']);
+ $entity_type = $table_data['table']['entity type'];
+ $entity_info = entity_get_info($entity_type);
+
+ // Get the info of the bundle entity.
+ foreach (entity_get_info() as $type => $info) {
+ if (isset($info['bundle of']) && $info['bundle of'] == $entity_type) {
+ $entity_bundle_info = $info;
+ break;
+ }
+ }
+
+ $plural_label = isset($entity_bundle_info['plural label']) ? $entity_bundle_info['plural label'] : $entity_bundle_info['label'] . 's';
+ $bundle_options = array();
+ foreach ($entity_info['bundles'] as $name => $info) {
+ $bundle_options[$name] = $info['label'];
+ }
+
+ $form['bundle_types'] = array(
+ '#title' => $plural_label,
+ '#type' => 'checkboxes',
+ '#description' => t('Restrict this relationship to one or more @bundles.', array('@bundles' => strtolower($entity_bundle_info['plural label']))),
+ '#options' => $bundle_options,
+ '#default_value' => $this->options['bundle_types'],
+ );
+ }
+
+ /**
+ * Make sure only checked bundle types are left.
+ */
+ function options_submit(&$form, &$form_state) {
+ $form_state['values']['options']['bundle_types'] = array_filter($form_state['values']['options']['bundle_types']);
+ parent::options_submit($form, $form_state);
+ }
+
+ /**
+ * Called to implement a relationship in a query.
+ *
+ * Mostly the same as the parent method, except we add an extra clause to
+ * the join.
+ */
+ function query() {
+ $table_data = views_fetch_data($this->definition['base']);
+ $base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field'];
+ $this->ensure_my_table();
+
+ $def = $this->definition;
+ $def['table'] = $this->definition['base'];
+ $def['field'] = $base_field;
+ $def['left_table'] = $this->table_alias;
+ $def['left_field'] = $this->field;
+ if (!empty($this->options['required'])) {
+ $def['type'] = 'INNER';
+ }
+
+ // Add an extra clause to the join if there are bundle types selected.
+ if ($this->options['bundle_types']) {
+ $entity_info = entity_get_info($table_data['table']['entity type']);
+ $def['extra'] = array(
+ array(
+ // The table and the IN operator are implicit.
+ 'field' => $entity_info['entity keys']['bundle'],
+ 'value' => $this->options['bundle_types'],
+ ),
+ );
+ }
+
+ if (!empty($def['join_handler']) && class_exists($def['join_handler'])) {
+ $join = new $def['join_handler'];
+ }
+ else {
+ $join = new views_join();
+ }
+
+ $join->definition = $def;
+ $join->construct();
+ $join->adjusted = TRUE;
+
+ // Use a short alias for this.
+ $alias = $def['table'] . '_' . $this->table;
+ $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship);
+ }
+}
diff --git a/sites/all/modules/entity/views/plugins/entity_views_plugin_row_entity_view.inc b/sites/all/modules/entity/views/plugins/entity_views_plugin_row_entity_view.inc
new file mode 100644
index 000000000..5e738a8ca
--- /dev/null
+++ b/sites/all/modules/entity/views/plugins/entity_views_plugin_row_entity_view.inc
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Row style plugin for displaying the results as entities.
+ */
+
+/**
+ * Plugin class for displaying Views results with entity_view.
+ */
+class entity_views_plugin_row_entity_view extends views_plugin_row {
+
+ protected $entity_type, $entities;
+
+ public function init(&$view, &$display, $options = NULL) {
+ parent::init($view, $display, $options);
+
+ // Initialize the entity-type used.
+ $table_data = views_fetch_data($this->view->base_table);
+ $this->entity_type = $table_data['table']['entity type'];
+ // Set base table and field information as used by views_plugin_row to
+ // select the entity id if used with default query class.
+ $info = entity_get_info($this->entity_type);
+ if (!empty($info['base table']) && $info['base table'] == $this->view->base_table) {
+ $this->base_table = $info['base table'];
+ $this->base_field = $info['entity keys']['id'];
+ }
+ }
+
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options['view_mode'] = array('default' => 'full');
+ return $options;
+ }
+
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $entity_info = entity_get_info($this->entity_type);
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+
+ if (count($options) > 1) {
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $this->options['view_mode'],
+ );
+ }
+ else {
+ $form['view_mode_info'] = array(
+ '#type' => 'item',
+ '#title' => t('View mode'),
+ '#description' => t('Only one view mode is available for this entity type.'),
+ '#markup' => $options ? current($options) : t('Default'),
+ );
+ $form['view_mode'] = array(
+ '#type' => 'value',
+ '#value' => $options ? key($options) : 'default',
+ );
+ }
+ return $form;
+ }
+
+ public function pre_render($values) {
+ if (!empty($values)) {
+ list($this->entity_type, $this->entities) = $this->view->query->get_result_entities($values, !empty($this->relationship) ? $this->relationship : NULL, isset($this->field_alias) ? $this->field_alias : NULL);
+ }
+ // Render the entities.
+ if ($this->entities) {
+ $render = entity_view($this->entity_type, $this->entities, $this->options['view_mode']);
+ // Remove the first level of the render array.
+ $this->rendered_content = reset($render);
+ }
+ }
+
+ /**
+ * Overridden to return the entity object.
+ */
+ function get_value($values, $field = NULL) {
+ return isset($this->entities[$this->view->row_index]) ? $this->entities[$this->view->row_index] : FALSE;
+ }
+
+ public function render($values) {
+ if ($entity = $this->get_value($values)) {
+ // Add the view object as views_plugin_row_node_view::render() would.
+ // Otherwise the views theme suggestions won't work properly.
+ $entity->view = $this->view;
+ $render = $this->rendered_content[entity_id($this->entity_type, $entity)];
+ return drupal_render($render);
+ }
+ }
+}
diff --git a/sites/all/modules/file_entity/LICENSE.txt b/sites/all/modules/file_entity/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/file_entity/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/file_entity/admin_views_default/file.admin-content-file.inc b/sites/all/modules/file_entity/admin_views_default/file.admin-content-file.inc
new file mode 100644
index 000000000..c108497ca
--- /dev/null
+++ b/sites/all/modules/file_entity/admin_views_default/file.admin-content-file.inc
@@ -0,0 +1,339 @@
+<?php
+
+/**
+ * @file
+ * Default view for user administration.
+ */
+
+$view = new view();
+$view->name = 'admin_views_file';
+$view->description = 'Find and manage files.';
+$view->tag = 'admin';
+$view->base_table = 'file_managed';
+$view->human_name = 'Administration: Files';
+$view->core = 7;
+$view->api_version = '3.0';
+$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+/* Display: Master */
+$handler = $view->new_display('default', 'Master', 'default');
+$handler->display->display_options['title'] = 'Files';
+$handler->display->display_options['css_class'] = 'admin-views-view';
+$handler->display->display_options['use_ajax'] = TRUE;
+$handler->display->display_options['use_more_always'] = FALSE;
+$handler->display->display_options['access']['type'] = 'menu';
+$handler->display->display_options['cache']['type'] = 'none';
+$handler->display->display_options['query']['type'] = 'views_query';
+$handler->display->display_options['exposed_form']['type'] = 'basic';
+$handler->display->display_options['exposed_form']['options']['reset_button'] = TRUE;
+$handler->display->display_options['pager']['type'] = 'full';
+$handler->display->display_options['pager']['options']['items_per_page'] = '50';
+$handler->display->display_options['pager']['options']['offset'] = '0';
+$handler->display->display_options['pager']['options']['id'] = '0';
+$handler->display->display_options['pager']['options']['quantity'] = '9';
+$handler->display->display_options['style_plugin'] = 'table';
+$handler->display->display_options['style_options']['columns'] = array(
+ 'views_bulk_operations' => 'views_bulk_operations',
+ 'filename' => 'filename',
+ 'type' => 'type',
+ 'name' => 'name',
+ 'filesize' => 'filesize',
+ 'timestamp' => 'timestamp',
+ 'edit' => 'edit',
+ 'delete' => 'delete',
+);
+$handler->display->display_options['style_options']['default'] = 'timestamp';
+$handler->display->display_options['style_options']['info'] = array(
+ 'views_bulk_operations' => array(
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ 'filename' => array(
+ 'sortable' => 1,
+ 'default_sort_order' => 'asc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ 'type' => array(
+ 'sortable' => 1,
+ 'default_sort_order' => 'asc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ 'name' => array(
+ 'sortable' => 1,
+ 'default_sort_order' => 'asc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ 'filesize' => array(
+ 'sortable' => 1,
+ 'default_sort_order' => 'asc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ 'timestamp' => array(
+ 'sortable' => 1,
+ 'default_sort_order' => 'desc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ 'edit' => array(
+ 'sortable' => 0,
+ 'default_sort_order' => 'asc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ 'usage' => array(
+ 'sortable' => 1,
+ 'default_sort_order' => 'asc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ 'delete' => array(
+ 'sortable' => 0,
+ 'default_sort_order' => 'asc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+);
+$handler->display->display_options['style_options']['sticky'] = TRUE;
+$handler->display->display_options['style_options']['empty_table'] = TRUE;
+/* No results behavior: Global: Unfiltered text */
+$handler->display->display_options['empty']['area_text_custom']['id'] = 'area_text_custom';
+$handler->display->display_options['empty']['area_text_custom']['table'] = 'views';
+$handler->display->display_options['empty']['area_text_custom']['field'] = 'area_text_custom';
+$handler->display->display_options['empty']['area_text_custom']['empty'] = TRUE;
+$handler->display->display_options['empty']['area_text_custom']['content'] = 'No files available.';
+/* Relationship: File: User who uploaded */
+$handler->display->display_options['relationships']['uid']['id'] = 'uid';
+$handler->display->display_options['relationships']['uid']['table'] = 'file_managed';
+$handler->display->display_options['relationships']['uid']['field'] = 'uid';
+$handler->display->display_options['relationships']['uid']['label'] = 'user';
+/* Field: Bulk operations: File */
+$handler->display->display_options['fields']['views_bulk_operations']['id'] = 'views_bulk_operations';
+$handler->display->display_options['fields']['views_bulk_operations']['table'] = 'file_managed';
+$handler->display->display_options['fields']['views_bulk_operations']['field'] = 'views_bulk_operations';
+$handler->display->display_options['fields']['views_bulk_operations']['label'] = '';
+$handler->display->display_options['fields']['views_bulk_operations']['element_label_colon'] = FALSE;
+$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['display_type'] = '0';
+$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['enable_select_all_pages'] = 1;
+$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['force_single'] = 0;
+$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['entity_load_capacity'] = '10';
+$handler->display->display_options['fields']['views_bulk_operations']['vbo_operations'] = array(
+ 'action::views_bulk_operations_archive_action' => array(
+ 'selected' => 1,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 1,
+ 'label' => 'Archive',
+ 'settings' => array(
+ 'scheme' => 'public',
+ 'temporary' => 1,
+ ),
+ ),
+ 'action::views_bulk_operations_delete_item' => array(
+ 'selected' => 1,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 1,
+ 'label' => 'Delete',
+ ),
+ 'action::views_bulk_operations_script_action' => array(
+ 'selected' => 0,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ ),
+ 'action::views_bulk_operations_modify_action' => array(
+ 'selected' => 1,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 1,
+ 'override_label' => 1,
+ 'label' => 'Change value',
+ 'settings' => array(
+ 'show_all_tokens' => 1,
+ 'display_values' => array(
+ '_all_' => '_all_',
+ ),
+ ),
+ ),
+ 'action::views_bulk_operations_argument_selector_action' => array(
+ 'selected' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ 'settings' => array(
+ 'url' => '',
+ ),
+ ),
+ 'action::system_send_email_action' => array(
+ 'selected' => 0,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ ),
+ 'action::panelizer_set_status_action' => array(
+ 'selected' => 0,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ ),
+);
+/* Field: File: Name */
+$handler->display->display_options['fields']['filename']['id'] = 'filename';
+$handler->display->display_options['fields']['filename']['table'] = 'file_managed';
+$handler->display->display_options['fields']['filename']['field'] = 'filename';
+$handler->display->display_options['fields']['filename']['label'] = 'Title';
+/* Field: File: Type */
+$handler->display->display_options['fields']['type']['id'] = 'type';
+$handler->display->display_options['fields']['type']['table'] = 'file_managed';
+$handler->display->display_options['fields']['type']['field'] = 'type';
+$handler->display->display_options['fields']['type']['machine_name'] = 0;
+/* Field: File: Size */
+$handler->display->display_options['fields']['filesize']['id'] = 'filesize';
+$handler->display->display_options['fields']['filesize']['table'] = 'file_managed';
+$handler->display->display_options['fields']['filesize']['field'] = 'filesize';
+$handler->display->display_options['fields']['filesize']['empty'] = '0 bytes';
+/* Field: User: Name */
+$handler->display->display_options['fields']['name']['id'] = 'name';
+$handler->display->display_options['fields']['name']['table'] = 'users';
+$handler->display->display_options['fields']['name']['field'] = 'name';
+$handler->display->display_options['fields']['name']['relationship'] = 'uid';
+$handler->display->display_options['fields']['name']['label'] = 'Author';
+/* Field: File: Upload date */
+$handler->display->display_options['fields']['timestamp']['id'] = 'timestamp';
+$handler->display->display_options['fields']['timestamp']['table'] = 'file_managed';
+$handler->display->display_options['fields']['timestamp']['field'] = 'timestamp';
+$handler->display->display_options['fields']['timestamp']['label'] = 'Updated';
+$handler->display->display_options['fields']['timestamp']['date_format'] = 'short';
+$handler->display->display_options['fields']['timestamp']['second_date_format'] = 'long';
+/* Field: File: Usage link */
+$handler->display->display_options['fields']['usage']['id'] = 'usage';
+$handler->display->display_options['fields']['usage']['table'] = 'file_managed';
+$handler->display->display_options['fields']['usage']['field'] = 'usage';
+$handler->display->display_options['fields']['usage']['label'] = 'Used in';
+$handler->display->display_options['fields']['usage']['element_label_colon'] = FALSE;
+/* Field: File: Edit link */
+$handler->display->display_options['fields']['edit']['id'] = 'edit';
+$handler->display->display_options['fields']['edit']['table'] = 'file_managed';
+$handler->display->display_options['fields']['edit']['field'] = 'edit';
+$handler->display->display_options['fields']['edit']['label'] = '';
+$handler->display->display_options['fields']['edit']['exclude'] = TRUE;
+$handler->display->display_options['fields']['edit']['element_label_colon'] = FALSE;
+/* Field: File: Delete link */
+$handler->display->display_options['fields']['delete']['id'] = 'delete';
+$handler->display->display_options['fields']['delete']['table'] = 'file_managed';
+$handler->display->display_options['fields']['delete']['field'] = 'delete';
+$handler->display->display_options['fields']['delete']['label'] = '';
+$handler->display->display_options['fields']['delete']['exclude'] = TRUE;
+$handler->display->display_options['fields']['delete']['alter']['text'] = '<ul class="links inline">
+<li>[edit]</li>
+<li>[delete]</li>
+</ul>';
+$handler->display->display_options['fields']['delete']['element_label_colon'] = FALSE;
+/* Field: Global: Custom text */
+$handler->display->display_options['fields']['nothing']['id'] = 'nothing';
+$handler->display->display_options['fields']['nothing']['table'] = 'views';
+$handler->display->display_options['fields']['nothing']['field'] = 'nothing';
+$handler->display->display_options['fields']['nothing']['label'] = 'Operations';
+$handler->display->display_options['fields']['nothing']['alter']['text'] = '<ul class="links inline">
+<li>[edit]</li>
+<li>[delete]</li>
+</ul>';
+$handler->display->display_options['fields']['nothing']['element_label_colon'] = FALSE;
+/* Filter criterion: File: Name */
+$handler->display->display_options['filters']['filename']['id'] = 'filename';
+$handler->display->display_options['filters']['filename']['table'] = 'file_managed';
+$handler->display->display_options['filters']['filename']['field'] = 'filename';
+$handler->display->display_options['filters']['filename']['operator'] = 'contains';
+$handler->display->display_options['filters']['filename']['group'] = 1;
+$handler->display->display_options['filters']['filename']['exposed'] = TRUE;
+$handler->display->display_options['filters']['filename']['expose']['operator_id'] = 'filename_op';
+$handler->display->display_options['filters']['filename']['expose']['label'] = 'Name';
+$handler->display->display_options['filters']['filename']['expose']['operator'] = 'filename_op';
+$handler->display->display_options['filters']['filename']['expose']['identifier'] = 'filename';
+$handler->display->display_options['filters']['filename']['expose']['remember'] = TRUE;
+$handler->display->display_options['filters']['filename']['expose']['remember_roles'] = array(
+ 2 => '2',
+ 1 => 0,
+ 3 => 0,
+);
+/* Filter criterion: File: Type */
+$handler->display->display_options['filters']['type']['id'] = 'type';
+$handler->display->display_options['filters']['type']['table'] = 'file_managed';
+$handler->display->display_options['filters']['type']['field'] = 'type';
+$handler->display->display_options['filters']['type']['group'] = 1;
+$handler->display->display_options['filters']['type']['exposed'] = TRUE;
+$handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+$handler->display->display_options['filters']['type']['expose']['label'] = 'Type';
+$handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op';
+$handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+$handler->display->display_options['filters']['type']['expose']['remember'] = TRUE;
+$handler->display->display_options['filters']['type']['expose']['remember_roles'] = array(
+ 2 => '2',
+ 1 => 0,
+ 3 => 0,
+);
+/* Filter criterion: User: Name */
+$handler->display->display_options['filters']['uid']['id'] = 'uid';
+$handler->display->display_options['filters']['uid']['table'] = 'users';
+$handler->display->display_options['filters']['uid']['field'] = 'uid';
+$handler->display->display_options['filters']['uid']['relationship'] = 'uid';
+$handler->display->display_options['filters']['uid']['value'] = '';
+$handler->display->display_options['filters']['uid']['group'] = 1;
+$handler->display->display_options['filters']['uid']['exposed'] = TRUE;
+$handler->display->display_options['filters']['uid']['expose']['operator_id'] = 'uid_op';
+$handler->display->display_options['filters']['uid']['expose']['label'] = 'User';
+$handler->display->display_options['filters']['uid']['expose']['operator'] = 'uid_op';
+$handler->display->display_options['filters']['uid']['expose']['identifier'] = 'uid';
+$handler->display->display_options['filters']['uid']['expose']['remember'] = TRUE;
+$handler->display->display_options['filters']['uid']['expose']['remember_roles'] = array(
+ 2 => '2',
+ 1 => 0,
+ 3 => 0,
+);
+
+/* Display: System */
+$handler = $view->new_display('system', 'System', 'system_1');
+$handler->display->display_options['defaults']['hide_admin_links'] = FALSE;
+$handler->display->display_options['hide_admin_links'] = TRUE;
+$handler->display->display_options['defaults']['access'] = FALSE;
+$handler->display->display_options['path'] = 'admin/content/file';
+$translatables['admin_views_file'] = array(
+ t('Master'),
+ t('Files'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('« first'),
+ t('‹ previous'),
+ t('next ›'),
+ t('last »'),
+ t('No files available.'),
+ t('user'),
+ t('Name'),
+ t('Type'),
+ t('User'),
+ t('Size'),
+ t('Uploaded'),
+ t('System'),
+);
diff --git a/sites/all/modules/file_entity/file_entity.admin.inc b/sites/all/modules/file_entity/file_entity.admin.inc
new file mode 100644
index 000000000..41c7c93be
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.admin.inc
@@ -0,0 +1,1124 @@
+<?php
+/**
+ * @file
+ * File administration and module settings UI.
+ */
+
+require_once dirname(__FILE__) . '/file_entity.pages.inc';
+
+/**
+ * List file administration filters that can be applied.
+ */
+function file_filters() {
+ $visible_steam_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE);
+ $options = array();
+ foreach ($visible_steam_wrappers as $scheme => $information) {
+ $options[$scheme] = check_plain($information['name']);
+ }
+ $filters['uri'] = array(
+ 'title' => t('scheme'),
+ 'options' => array(
+ '[any]' => t('any'),
+ ) + $options,
+ );
+ $filters['type'] = array(
+ 'title' => t('type'),
+ 'options' => array(
+ '[any]' => t('any'),
+ ) + file_entity_type_get_names(),
+ );
+ return $filters;
+}
+
+/**
+ * Apply filters for file administration filters based on session.
+ *
+ * @param object $query
+ * A SelectQuery to which the filters should be applied.
+ */
+function file_entity_build_filter_query(SelectQueryInterface $query) {
+ // Build query.
+ $filter_data = isset($_SESSION['file_entity_overview_filter']) ? $_SESSION['file_entity_overview_filter'] : array();
+ foreach ($filter_data as $index => $filter) {
+ list($key, $value) = $filter;
+ switch ($key) {
+ case 'uri':
+ $query->condition('fm.' . $key, $value . '%', 'LIKE');
+ break;
+
+ case 'type':
+ $query->condition('fm.' . $key, $value);
+ break;
+
+ }
+ }
+}
+
+/**
+ * Return form for file administration filters.
+ */
+function file_entity_filter_form() {
+ $session = isset($_SESSION['file_entity_overview_filter']) ? $_SESSION['file_entity_overview_filter'] : array();
+ $filters = file_filters();
+
+ $i = 0;
+ $form['filters'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Show only items where'),
+ '#theme' => 'exposed_filters__file_entity',
+ );
+ foreach ($session as $filter) {
+ list($type, $value) = $filter;
+ if ($type == 'term') {
+ // Load term name from DB rather than search and parse options array.
+ $value = module_invoke('taxonomy', 'term_load', $value);
+ $value = $value->name;
+ }
+ else {
+ $value = $filters[$type]['options'][$value];
+ }
+ $t_args = array('%property' => $filters[$type]['title'], '%value' => $value);
+ if ($i++) {
+ $form['filters']['current'][] = array('#markup' => t('and where %property is %value', $t_args));
+ }
+ else {
+ $form['filters']['current'][] = array('#markup' => t('where %property is %value', $t_args));
+ }
+ if (in_array($type, array('type', 'uri'))) {
+ // Remove the option if it is already being filtered on.
+ unset($filters[$type]);
+ }
+ }
+
+ $form['filters']['status'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('clearfix')),
+ '#prefix' => ($i ? '<div class="additional-filters">' . t('and where') . '</div>' : ''),
+ );
+ $form['filters']['status']['filters'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('filters')),
+ );
+ foreach ($filters as $key => $filter) {
+ $form['filters']['status']['filters'][$key] = array(
+ '#type' => 'select',
+ '#options' => $filter['options'],
+ '#title' => $filter['title'],
+ '#default_value' => '[any]',
+ );
+ }
+
+ $form['filters']['status']['actions'] = array(
+ '#type' => 'actions',
+ '#attributes' => array('class' => array('container-inline')),
+ );
+ if (count($filters)) {
+ $form['filters']['status']['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => count($session) ? t('Refine') : t('Filter'),
+ );
+ }
+ if (count($session)) {
+ $form['filters']['status']['actions']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
+ $form['filters']['status']['actions']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
+ }
+
+ drupal_add_js('misc/form.js');
+
+ return $form;
+}
+
+/**
+ * Process result from file administration filter form.
+ */
+function file_entity_filter_form_submit($form, &$form_state) {
+ $filters = file_filters();
+ switch ($form_state['values']['op']) {
+ case t('Filter'):
+ case t('Refine'):
+ // Apply every filter that has a choice selected other than 'any'.
+ foreach ($filters as $filter => $options) {
+ if (isset($form_state['values'][$filter]) && $form_state['values'][$filter] != '[any]') {
+ // Flatten the options array to accommodate hierarchical/nested
+ // options.
+ $flat_options = form_options_flatten($filters[$filter]['options']);
+ // Only accept valid selections offered on the dropdown, block bad
+ // input.
+ if (isset($flat_options[$form_state['values'][$filter]])) {
+ $_SESSION['file_entity_overview_filter'][] = array($filter, $form_state['values'][$filter]);
+ }
+ }
+ }
+ break;
+
+ case t('Undo'):
+ array_pop($_SESSION['file_entity_overview_filter']);
+ break;
+
+ case t('Reset'):
+ $_SESSION['file_entity_overview_filter'] = array();
+ break;
+
+ }
+}
+
+/**
+ * Make mass update of files.
+ *
+ * Change all files in the $files array to update them with the field values in
+ * $updates.
+ *
+ * IMPORTANT NOTE: This function is intended to work when called
+ * from a form submit handler. Calling it outside of the form submission
+ * process may not work correctly.
+ *
+ * @param array $files
+ * Array of file fids to update.
+ * @param array $updates
+ * Array of key/value pairs with file field names and the
+ * value to update that field to.
+ */
+function file_entity_mass_update($files, $updates) {
+ // We use batch processing to prevent timeout when updating a large number
+ // of files.
+ if (count($files) > 10) {
+ $batch = array(
+ 'operations' => array(
+ array(
+ '_file_entity_mass_update_batch_process',
+ array($files, $updates),
+ ),
+ ),
+ 'finished' => '_file_entity_mass_update_batch_finished',
+ 'title' => t('Processing'),
+ // We use a single multi-pass operation, so the default
+ // 'Remaining x of y operations' message will be confusing here.
+ 'progress_message' => '',
+ 'error_message' => t('The update has encountered an error.'),
+ // The operations do not live in the .module file, so we need to
+ // tell the batch engine which file to load before calling them.
+ 'file' => drupal_get_path('module', 'file_entity') . '/file_entity.admin.inc',
+ );
+ batch_set($batch);
+ }
+ else {
+ foreach ($files as $fid) {
+ _file_entity_mass_update_helper($fid, $updates);
+ }
+ drupal_set_message(t('The update has been performed.'));
+ }
+}
+
+/**
+ * File Mass Update - helper function.
+ */
+function _file_entity_mass_update_helper($fid, $updates) {
+ $file = file_load($fid);
+ // For efficiency manually save the original file before applying any changes.
+ $file->original = clone $file;
+ foreach ($updates as $name => $value) {
+ $file->$name = $value;
+ }
+ file_save($file);
+ return $file;
+}
+
+/**
+ * File Mass Update Batch operation.
+ */
+function _file_entity_mass_update_batch_process($files, $updates, &$context) {
+ if (!isset($context['sandbox']['progress'])) {
+ $context['sandbox']['progress'] = 0;
+ $context['sandbox']['max'] = count($files);
+ $context['sandbox']['files'] = $files;
+ }
+
+ // Process files by groups of 5.
+ $count = min(5, count($context['sandbox']['files']));
+ for ($i = 1; $i <= $count; $i++) {
+ // For each fid, load the file, reset the values, and save it.
+ $fid = array_shift($context['sandbox']['files']);
+ $file = _file_entity_mass_update_helper($fid, $updates);
+
+ // Store result for post-processing in the finished callback.
+ $context['results'][] = l($file->filename, 'file/' . $file->fid);
+
+ // Update our progress information.
+ $context['sandbox']['progress']++;
+ }
+
+ // Inform the batch engine that we are not finished,
+ // and provide an estimation of the completion level we reached.
+ if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ }
+}
+
+/**
+ * File Mass Update Batch 'finished' callback.
+ */
+function _file_entity_mass_update_batch_finished($success, $results, $operations) {
+ if ($success) {
+ drupal_set_message(t('The update has been performed.'));
+ }
+ else {
+ drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
+ $message = format_plural(count($results), '1 item successfully processed:', '@count items successfully processed:');
+ $message .= theme('item_list', array('items' => $results));
+ drupal_set_message($message);
+ }
+}
+
+/**
+ * Menu callback: file administration.
+ */
+function file_entity_admin_file($form, $form_state) {
+ if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') {
+ return file_entity_multiple_delete_confirm($form, $form_state, array_filter($form_state['values']['files']));
+ }
+ $form['filter'] = file_entity_filter_form();
+ $form['#submit'][] = 'file_entity_filter_form_submit';
+ $form['admin'] = file_entity_admin_files();
+
+ return $form;
+}
+
+/**
+ * Form builder: Builds the file administration overview.
+ */
+function file_entity_admin_files() {
+ $admin_access = user_access('administer files');
+
+ // Build the 'Update options' form.
+ $form['options'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Update options'),
+ '#attributes' => array('class' => array('container-inline')),
+ '#access' => $admin_access,
+ );
+ $options = array();
+ foreach (module_invoke_all('file_operations') as $operation => $array) {
+ $options[$operation] = $array['label'];
+ }
+ $form['options']['operation'] = array(
+ '#type' => 'select',
+ '#title' => t('Operation'),
+ '#title_display' => 'invisible',
+ '#options' => $options,
+ '#default_value' => 'approve',
+ );
+ $form['options']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Update'),
+ '#validate' => array('file_entity_admin_files_validate'),
+ '#submit' => array('file_entity_admin_files_submit'),
+ );
+
+ // Build the sortable table header.
+ $header = array(
+ 'title' => array('data' => t('Title'), 'field' => 'fm.filename'),
+ 'type' => array('data' => t('Type'), 'field' => 'fm.type'),
+ 'size' => array('data' => t('Size'), 'field' => 'fm.filesize'),
+ 'author' => t('Author'),
+ 'timestamp' => array(
+ 'data' => t('Updated'),
+ 'field' => 'fm.timestamp',
+ 'sort' => 'desc'),
+ 'usage' => array('data' => t('Used in'), 'field' => 'total_count'),
+ 'operations' => array('data' => t('Operations')),
+ );
+
+ $query = db_select('file_managed', 'fm')->extend('PagerDefault')->extend('TableSort');
+ $query->leftJoin('file_usage', 'fu', 'fm.fid = fu.fid');
+ $query->groupBy('fm.fid');
+ $query->groupBy('fm.uid');
+ $query->groupBy('fm.timestamp');
+ $query->addExpression('SUM(fu.count)', 'total_count');
+ file_entity_build_filter_query($query);
+
+ $result = $query
+ ->fields('fm', array('fid', 'uid'))
+ ->limit(50)
+ ->orderByHeader($header)
+ ->addTag('file_access')
+ ->execute()
+ ->fetchAllAssoc('fid');
+ $files = file_load_multiple(array_keys($result));
+
+ $uids = array();
+ foreach ($files as $file) {
+ $uids[] = $file->uid;
+ }
+ $accounts = !empty($uids) ? user_load_multiple(array_unique($uids)) : array();
+
+ // Prepare the list of files.
+ $destination = drupal_get_destination();
+ $options = array();
+ foreach ($files as $file) {
+ $file_type = file_type_load($file->type);
+ $account = isset($accounts[$file->uid]) ? $accounts[$file->uid] : NULL;
+ $options[$file->fid] = array(
+ 'title' => array(
+ 'data' => array(
+ '#type' => 'link',
+ '#title' => $file->filename,
+ '#href' => 'file/' . $file->fid,
+ ),
+ ),
+ 'type' => $file_type ? check_plain($file_type->label) : FILE_TYPE_NONE,
+ 'size' => format_size($file->filesize),
+ 'author' => theme('username', array('account' => $account)),
+ 'timestamp' => format_date($file->timestamp, 'short'),
+ 'usage' => format_plural((int) $result[$file->fid]->total_count, '1 place', '@count places'),
+ );
+
+ // Show a warning for files that do not exist.
+ if (@!is_file($file->uri)) {
+ $options[$file->fid]['#attributes']['class'][] = 'error';
+ if (!file_stream_wrapper_get_instance_by_uri($file->uri)) {
+ $options[$file->fid]['#attributes']['title'] = t('The stream wrapper for @scheme files is missing.', array('@scheme' => file_uri_scheme($file->uri)));
+ }
+ else {
+ $options[$file->fid]['#attributes']['title'] = t('The file does not exist.');
+ }
+ }
+
+ // Build a list of all the accessible operations for the current file.
+ $operations = array();
+ if (file_entity_access('update', $file)) {
+ // Convert the usage count to a link.
+ $options[$file->fid]['usage'] = l($options[$file->fid]['usage'], 'file/' . $file->fid . '/usage');
+ $operations['edit'] = array(
+ 'title' => t('Edit'),
+ 'href' => 'file/' . $file->fid . '/edit',
+ 'query' => $destination,
+ );
+ }
+ if (file_entity_access('delete', $file)) {
+ $operations['delete'] = array(
+ 'title' => t('Delete'),
+ 'href' => 'file/' . $file->fid . '/delete',
+ 'query' => $destination,
+ );
+ }
+ $options[$file->fid]['operations'] = array();
+ if (count($operations) > 1) {
+ // Render an unordered list of operations links.
+ $options[$file->fid]['operations'] = array(
+ 'data' => array(
+ '#theme' => 'links__file_entity_operations',
+ '#links' => $operations,
+ '#attributes' => array('class' => array('links', 'inline')),
+ ),
+ );
+ }
+ elseif (!empty($operations)) {
+ // Render the first and only operation as a link.
+ $link = reset($operations);
+ $options[$file->fid]['operations'] = array(
+ 'data' => array(
+ '#type' => 'link',
+ '#title' => $link['title'],
+ '#href' => $link['href'],
+ '#options' => array('query' => $link['query']),
+ ),
+ );
+ }
+ }
+
+ // Only use a tableselect when the current user is able to perform any
+ // operations.
+ if ($admin_access) {
+ $form['files'] = array(
+ '#type' => 'tableselect',
+ '#header' => $header,
+ '#options' => $options,
+ '#empty' => t('No files available.'),
+ );
+ }
+ // Otherwise, use a simple table.
+ else {
+ $form['files'] = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $options,
+ '#empty' => t('No files available.'),
+ );
+ }
+
+ $form['pager'] = array('#markup' => theme('pager'));
+ return $form;
+}
+
+/**
+ * Validate file_entity_admin_files form submissions.
+ *
+ * Check if any files have been selected to perform the chosen
+ * 'Update option' on.
+ */
+function file_entity_admin_files_validate($form, &$form_state) {
+ // Error if there are no items to select.
+ if (!is_array($form_state['values']['files']) || !count(array_filter($form_state['values']['files']))) {
+ form_set_error('', t('No items selected.'));
+ }
+}
+
+/**
+ * Process file_entity_admin_files form submissions.
+ *
+ * Execute the chosen 'Update option' on the selected files.
+ */
+function file_entity_admin_files_submit($form, &$form_state) {
+ $operations = module_invoke_all('file_operations');
+ $operation = $operations[$form_state['values']['operation']];
+ // Filter out unchecked files.
+ $files = array_filter($form_state['values']['files']);
+ if ($function = $operation['callback']) {
+ // Add in callback arguments if present.
+ if (isset($operation['callback arguments'])) {
+ $args = array_merge(array($files), $operation['callback arguments']);
+ }
+ else {
+ $args = array($files);
+ }
+ call_user_func_array($function, $args);
+
+ cache_clear_all();
+ }
+ else {
+ // We need to rebuild the form to go to a second step. For example, to
+ // show the confirmation form for the deletion of files.
+ $form_state['rebuild'] = TRUE;
+ }
+}
+
+/**
+ * File entity delete confirmation.
+ */
+function file_entity_multiple_delete_confirm($form, &$form_state, $files) {
+ $form['files'] = array(
+ '#prefix' => '<ul>',
+ '#suffix' => '</ul>',
+ '#tree' => TRUE,
+ );
+ // array_filter returns only elements with TRUE values.
+ foreach ($files as $fid => $value) {
+ $filename = db_query('SELECT filename FROM {file_managed} WHERE fid = :fid', array(':fid' => $fid))->fetchField();
+ $form['files'][$fid] = array(
+ '#type' => 'hidden',
+ '#value' => $fid,
+ '#prefix' => '<li>',
+ '#suffix' => check_plain($filename) . "</li>\n",
+ );
+ }
+ $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
+ $form['#submit'][] = 'file_entity_multiple_delete_confirm_submit';
+ $confirm_question = format_plural(count($files),
+ 'Are you sure you want to delete this item?',
+ 'Are you sure you want to delete these items?');
+ return confirm_form($form,
+ $confirm_question,
+ 'admin/content/file', t('This action cannot be undone.'),
+ t('Delete'), t('Cancel'));
+}
+
+/**
+ * Submit handler for delete confirmation.
+ */
+function file_entity_multiple_delete_confirm_submit($form, &$form_state) {
+ if ($form_state['values']['confirm']) {
+ file_delete_multiple(array_keys($form_state['values']['files']));
+ $count = count($form_state['values']['files']);
+ watchdog('file_entity', 'Deleted @count files.', array('@count' => $count));
+ drupal_set_message(format_plural($count, 'Deleted 1 file.', 'Deleted @count files.'));
+ }
+ $form_state['redirect'] = 'admin/content/file';
+}
+
+/**
+ * Displays the file type admin overview page.
+ */
+function file_entity_list_types_page() {
+ $file_entity_info = entity_get_info('file');
+ $field_ui = module_exists('field_ui');
+ $colspan = $field_ui ? 5 : 3;
+ $header = array(
+ array('data' => t('Name')),
+ array('data' => t('Operations'), 'colspan' => $colspan),
+ array('data' => t('Status')),
+ );
+ $rows = array();
+ $weight = 0;
+ $types = file_type_load_all(TRUE);
+ $count = count($types);
+ foreach ($types as $type) {
+ $weight++;
+ $row = array(
+ array(
+ 'data' => theme('file_entity_file_type_overview',
+ array(
+ 'label' => $type->label,
+ 'description' => $type->description,
+ )
+ ),
+ ),
+ );
+ $path = isset($file_entity_info['bundles'][$type->type]['admin']['real path']) ? $file_entity_info['bundles'][$type->type]['admin']['real path'] : NULL;
+
+ if (empty($type->disabled) && isset($path)) {
+ $row[] = array('data' => l(t('edit file type'), $path . '/edit'));
+ if ($field_ui) {
+ $row[] = array('data' => l(t('manage fields'), $path . '/fields'));
+ $row[] = array('data' => l(t('manage display'), $path . '/display'));
+ }
+ $row[] = array('data' => l(t('manage file display'), $path . '/file-display'));
+ }
+ else {
+ $row += array_fill(1, $colspan - 1, '');
+ }
+
+ $admin_path = 'admin/structure/file-types/manage/' . $type->type;
+ switch ($type->ctools_type) {
+ // Configuration is in code.
+ case 'Default':
+ if (!empty($type->disabled)) {
+ $row[] = l(t('enable'), $admin_path . '/enable');
+ }
+ else {
+ $row[] = l(t('disable'), $admin_path . '/disable');
+ }
+ break;
+
+ // Configuration is in DB.
+ case 'Normal':
+ if (!empty($type->disabled)) {
+ $status = l(t('enable'), $admin_path . '/enable');
+ }
+ else {
+ $status = l(t('disable'), $admin_path . '/disable');
+ }
+ $row[] = $status . ' | ' . l(t('delete'), $admin_path . '/delete');
+ break;
+
+ // Configuration is in code, but overridden in DB.
+ case 'Overridden':
+ if (!empty($type->disabled)) {
+ $row[] = l(t('enable'), $admin_path . '/enable');
+ }
+ else {
+ $row[] = l(t('disable'), $admin_path . '/disable') . ' | ' . l(t('revert'), $admin_path . '/revert');
+ }
+ break;
+ }
+
+ if (!empty($type->disabled)) {
+ $row[] = t('Disabled');
+ $rows[$weight + $count] = array('data' => $row, 'class' => array('ctools-export-ui-disabled'));
+ }
+ else {
+ $row[] = $type->ctools_type;
+ $rows[$weight] = array('data' => $row);
+ }
+ }
+
+ // Move disabled items to the bottom.
+ ksort($rows);
+
+ $build['file_type_table'] = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $rows,
+ '#empty' => t('No file types available.'),
+ '#attached' => array(
+ 'css' => array(drupal_get_path('module', 'ctools') . '/css/export-ui-list.css'),
+ ),
+ );
+
+ return $build;
+}
+
+/**
+ * Form callback; presents file display settings for a given view mode.
+ */
+function file_entity_file_display_form($form, &$form_state, $file_type, $view_mode) {
+ $form['#file_type'] = $file_type->type;
+ $form['#view_mode'] = $view_mode;
+ $form['#tree'] = TRUE;
+ $form['#attached']['js'][] = drupal_get_path('module', 'file_entity') . '/file_entity.admin.js';
+
+ // Retrieve available formatters for this file type and load all configured
+ // filters for existing text formats.
+ $formatters = file_info_formatter_types();
+ foreach ($formatters as $name => $formatter) {
+ if (!empty($formatter['hidden'])) {
+ unset($formatters[$name]);
+ }
+ if (isset($formatter['mime types'])) {
+ if (file_entity_match_mimetypes($formatter['mime types'], $file_type->mimetypes)) {
+ continue;
+ }
+ unset($formatters[$name]);
+ }
+ }
+ $current_displays = file_displays_load($file_type->type, $view_mode, TRUE);
+ foreach ($current_displays as $name => $display) {
+ $current_displays[$name] = (array) $display;
+ }
+
+ // Formatter status.
+ $form['displays']['status'] = array(
+ '#type' => 'item',
+ '#title' => t('Enabled displays'),
+ '#prefix' => '<div id="file-displays-status-wrapper">',
+ '#suffix' => '</div>',
+ );
+ $i = 0;
+ foreach ($formatters as $name => $formatter) {
+ $form['displays']['status'][$name] = array(
+ '#type' => 'checkbox',
+ '#title' => check_plain($formatter['label']),
+ '#default_value' => !empty($current_displays[$name]['status']),
+ '#description' => isset($formatter['description']) ? filter_xss($formatter['description']) : NULL,
+ '#parents' => array('displays', $name, 'status'),
+ '#weight' => (isset($formatter['weight']) ? $formatter['weight'] : 0) + ($i / 1000),
+ );
+ $i++;
+ }
+
+ // Formatter order (tabledrag).
+ $form['displays']['order'] = array(
+ '#type' => 'item',
+ '#title' => t('Display precedence order'),
+ '#theme' => 'file_entity_file_display_order',
+ );
+ foreach ($formatters as $name => $formatter) {
+ $form['displays']['order'][$name]['label'] = array(
+ '#markup' => check_plain($formatter['label']),
+ );
+ $form['displays']['order'][$name]['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight for @title', array('@title' => $formatter['label'])),
+ '#title_display' => 'invisible',
+ '#delta' => 50,
+ '#default_value' => isset($current_displays[$name]['weight']) ? $current_displays[$name]['weight'] : 0,
+ '#parents' => array('displays', $name, 'weight'),
+ );
+ $form['displays']['order'][$name]['#weight'] = $form['displays']['order'][$name]['weight']['#default_value'];
+ }
+
+ // Formatter settings.
+ $form['display_settings_title'] = array(
+ '#type' => 'item',
+ '#title' => t('Display settings'),
+ );
+ $form['display_settings'] = array(
+ '#type' => 'vertical_tabs',
+ );
+ $i = 0;
+ foreach ($formatters as $name => $formatter) {
+ if (isset($formatter['settings callback']) && ($function = $formatter['settings callback']) && function_exists($function)) {
+ $defaults = !empty($formatter['default settings']) ? $formatter['default settings'] : array();
+ $settings = !empty($current_displays[$name]['settings']) ? $current_displays[$name]['settings'] : array();
+ $settings += $defaults;
+ $settings_form = $function($form, $form_state, $settings, $name, $file_type->type, $view_mode);
+ if (!empty($settings_form)) {
+ $form['displays']['settings'][$name] = array(
+ '#type' => 'fieldset',
+ '#title' => check_plain($formatter['label']),
+ '#parents' => array('displays', $name, 'settings'),
+ '#group' => 'display_settings',
+ '#weight' => (isset($formatter['weight']) ? $formatter['weight'] : 0) + ($i / 1000),
+ ) + $settings_form;
+ }
+ }
+ $i++;
+ }
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save configuration'),
+ );
+
+ return $form;
+}
+
+/**
+ * Process file display settings form submissions.
+ */
+function file_entity_file_display_form_submit($form, &$form_state) {
+ $file_type = $form['#file_type'];
+ $view_mode = $form['#view_mode'];
+ $displays = isset($form_state['values']['displays']) ? $form_state['values']['displays'] : array();
+ $displays_original = file_displays_load($file_type, $view_mode, TRUE);
+ foreach ($displays as $formatter_name => $display) {
+ $display_original = isset($displays_original[$formatter_name]) ? $displays_original[$formatter_name] : file_display_new($file_type, $view_mode, $formatter_name);
+ $display += (array) $display_original;
+ file_display_save((object) $display);
+ }
+ drupal_set_message(t('Your settings have been saved.'));
+}
+
+/**
+ * Returns HTML for the file type overview page.
+ *
+ * Specifically, this returns HTML for a file type label and description.
+ */
+function theme_file_entity_file_type_overview($variables) {
+ return check_plain($variables['label']) . '<div class="description">' . $variables['description'] . '</div>';
+}
+
+/**
+ * Returns HTML for a file display's display order table.
+ */
+function theme_file_entity_file_display_order($variables) {
+ $element = $variables['element'];
+
+ $rows = array();
+ foreach (element_children($element, TRUE) as $name) {
+ $element[$name]['weight']['#attributes']['class'][] = 'file-display-order-weight';
+ $rows[] = array(
+ 'data' => array(
+ drupal_render($element[$name]['label']),
+ drupal_render($element[$name]['weight']),
+ ),
+ 'class' => array('draggable'),
+ );
+ }
+ $output = drupal_render_children($element);
+ $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'file-displays-order')));
+ drupal_add_tabledrag('file-displays-order', 'order', 'sibling', 'file-display-order-weight', NULL, NULL, TRUE);
+
+ return $output;
+}
+
+/**
+ * Form constructor for the file type settings form.
+ *
+ * @param object $type
+ * The file type.
+ *
+ * @see file_entity_file_type_form_validate()
+ * @see file_entity_file_type_form_submit()
+ */
+function file_entity_file_type_form($form, &$form_state, $type = NULL) {
+ if (!isset($type->type)) {
+ // This is a new type.
+ $type = (object) array(
+ 'type' => '',
+ 'label' => '',
+ 'description' => '',
+ 'mimetypes' => array(),
+ );
+ }
+ $form['#file_type'] = $type;
+
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name'),
+ '#description' => t('This is the human readable name of the file type.'),
+ '#required' => TRUE,
+ '#default_value' => $type->label,
+ );
+
+ $form['type'] = array(
+ '#type' => 'machine_name',
+ '#default_value' => $type->type,
+ '#maxlength' => 255,
+ '#disabled' => (bool) $type->type,
+ '#machine_name' => array(
+ 'exists' => 'file_type_load',
+ 'source' => array('label'),
+ ),
+ '#description' => t('A unique machine-readable name for this file type. It must only contain lowercase letters, numbers, and underscores.'),
+ );
+
+ $form['description'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Description'),
+ '#description' => t('This is the description of the file type.'),
+ '#default_value' => $type->description,
+ );
+
+ $form['mimetypes'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Mimetypes'),
+ '#description' => t('Enter one mimetype per line.'),
+ '#default_value' => implode("\n", $type->mimetypes),
+ );
+
+ include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
+ $mimetypes = file_mimetype_mapping();
+
+ $form['mimetype_mapping'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Mimetype List'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['mimetype_mapping']['mapping'] = array(
+ '#theme' => 'item_list',
+ '#items' => $mimetypes['mimetypes'],
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+ if (!empty($type->type)) {
+ $form['actions']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete'),
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Form validation handler for file_entity_file_type_form().
+ *
+ * @see file_entity_file_type_form_submit()
+ */
+function file_entity_file_type_form_validate($form, &$form_state) {
+ include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
+ $mimetype_mapping = file_mimetype_mapping();
+
+ $valid_mimetypes = $mimetype_mapping['mimetypes'];
+ $submitted_mimetypes = array_filter(array_map('trim', explode("\n", $form_state['values']['mimetypes'])));
+
+ $invalid_mimetypes = array();
+ foreach ($submitted_mimetypes as $mimetype) {
+ if (!file_entity_match_mimetypes($mimetype, $valid_mimetypes)) {
+ $invalid_mimetypes[] = $mimetype;
+ }
+ }
+
+ foreach ($invalid_mimetypes as $mimetype) {
+ form_set_error('mimetypes', t('The mimetype %mimetype is not a valid mimetype.', array('%mimetype' => $mimetype)));
+ }
+}
+
+/**
+ * Form submission handler for file_entity_file_type_form().
+ *
+ * @see file_entity_file_type_form_validate()
+ */
+function file_entity_file_type_form_submit($form, &$form_state) {
+ if (!empty($form['#file_type']->type)) {
+ $type = file_type_load($form['#file_type']->type);
+ }
+ else {
+ $type = (object) array(
+ 'type' => $form_state['values']['type'],
+ );
+ }
+ if ($form_state['values']['op'] == t('Delete')) {
+ $form_state['redirect'] = 'admin/structure/file-types/manage/' . $type->type . '/delete';
+ return;
+ }
+ $type->label = $form_state['values']['label'];
+ $type->description = $form_state['values']['description'];
+ $type->mimetypes = array_filter(array_map('trim', explode("\n", $form_state['values']['mimetypes'])));
+
+ file_type_save($type);
+
+ drupal_set_message(t('The file type %type has been updated.', array('%type' => $type->label)));
+ $form_state['redirect'] = 'admin/structure/file-types';
+}
+
+
+/**
+ * Menu callback; disable a single file type.
+ */
+function file_entity_type_enable_confirm($form, &$form_state, $type) {
+ $form['type'] = array('#type' => 'value', '#value' => $type->type);
+ $form['label'] = array('#type' => 'value', '#value' => $type->label);
+ $message = t('Are you sure you want to enable the file type %type?', array('%type' => $type->label));
+ return confirm_form($form, $message, 'admin/structure/file-types', '', t('Enable'));
+}
+
+
+/**
+ * Process file type disable confirm submissions.
+ */
+function file_entity_type_enable_confirm_submit($form, &$form_state) {
+ file_type_enable($form_state['values']['type']);
+ $t_args = array('%label' => $form_state['values']['label']);
+ drupal_set_message(t('The file type %label has been enabled.', $t_args));
+ watchdog('file_entity', 'Enabled file type %label.', $t_args, WATCHDOG_NOTICE);
+ $form_state['redirect'] = 'admin/structure/file-types';
+ return;
+}
+
+
+/**
+ * Menu callback; disable a single file type.
+ */
+function file_entity_type_disable_confirm($form, &$form_state, $type) {
+ $form['type'] = array('#type' => 'value', '#value' => $type->type);
+ $form['label'] = array('#type' => 'value', '#value' => $type->label);
+
+ $message = t('Are you sure you want to disable the file type %type?', array('%type' => $type->label));
+ $caption = '';
+
+ $num_files = db_query("SELECT COUNT(*) FROM {file_managed} WHERE type = :type", array(':type' => $type->type))->fetchField();
+ if ($num_files) {
+ $caption .= '<p>' . format_plural($num_files, '%type is used by 1 file on
+ your site. If you disable this file type, you will not be able to edit
+ the %type file and it may not display correctly.', '%type is used by
+ @count files on your site. If you remove %type, you will not be able to
+ edit the %type file and it may not display correctly.',
+ array('%type' => $type->label)) . '</p>';
+ }
+
+ return confirm_form($form, $message, 'admin/structure/file-types', $caption, t('Disable'));
+}
+
+
+/**
+ * Process file type disable confirm submissions.
+ */
+function file_entity_type_disable_confirm_submit($form, &$form_state) {
+ file_type_disable($form_state['values']['type']);
+ $t_args = array('%label' => $form_state['values']['label']);
+ drupal_set_message(t('The file type %label has been disabled.', $t_args));
+ watchdog('file_entity', 'Disabled file type %label.', $t_args, WATCHDOG_NOTICE);
+ $form_state['redirect'] = 'admin/structure/file-types';
+ return;
+}
+
+
+/**
+ * Menu callback; revert a single file type.
+ */
+function file_entity_type_revert_confirm($form, &$form_state, $type) {
+ $form['type'] = array('#type' => 'value', '#value' => $type->type);
+ $form['label'] = array('#type' => 'value', '#value' => $type->label);
+ $message = t('Are you sure you want to revert the file type %type?', array('%type' => $type->label));
+ return confirm_form($form, $message, 'admin/structure/file-types', '', t('Revert'));
+}
+
+
+/**
+ * Process file type delete confirm submissions.
+ */
+function file_entity_type_revert_confirm_submit($form, &$form_state) {
+ // @NOTE deleting the file_type from the DB actually reverts it to code.
+ file_type_delete($form_state['values']['type']);
+ $t_args = array('%label' => $form_state['values']['label']);
+ drupal_set_message(t('The file type %label has been reverted.', $t_args));
+ watchdog('file_entity', 'Reverted file type %label.', $t_args, WATCHDOG_NOTICE);
+ $form_state['redirect'] = 'admin/structure/file-types';
+ return;
+}
+
+
+/**
+ * Menu callback; delete a single file type.
+ */
+function file_entity_type_delete_confirm($form, &$form_state, $type) {
+ $form['type'] = array('#type' => 'value', '#value' => $type->type);
+ $form['label'] = array('#type' => 'value', '#value' => $type->label);
+
+ $message = t('Are you sure you want to delete the file type %type?', array('%type' => $type->label));
+ $caption = '';
+
+ $num_files = db_query("SELECT COUNT(*) FROM {file_managed} WHERE type = :type", array(':type' => $type->type))->fetchField();
+ if ($num_files) {
+ $caption .= '<p>' . format_plural($num_files, '%type is used by 1 file on your site. If you remove this file type, you will not be able to edit the %type file and it may not display correctly.', '%type is used by @count pieces of file on your site. If you remove %type, you will not be able to edit the %type file and it may not display correctly.', array('%type' => $type->label)) . '</p>';
+ }
+
+ $caption .= '<p>' . t('This action cannot be undone.') . '</p>';
+
+ return confirm_form($form, $message, 'admin/structure/file-types', $caption, t('Delete'));
+}
+
+/**
+ * Process file type delete confirm submissions.
+ */
+function file_entity_type_delete_confirm_submit($form, &$form_state) {
+ file_type_delete($form_state['values']['type']);
+
+ $t_args = array('%label' => $form_state['values']['label']);
+ drupal_set_message(t('The file type %label has been deleted.', $t_args));
+ watchdog('file_entity', 'Deleted file type %label.', $t_args, WATCHDOG_NOTICE);
+
+ $form_state['redirect'] = 'admin/structure/file-types';
+ return;
+}
+
+/**
+ * Form callback for file_entity settings.
+ */
+function file_entity_settings_form($form, &$form_state) {
+ $form['file_entity_max_filesize'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Maximum upload size'),
+ '#default_value' => variable_get('file_entity_max_filesize', ''),
+ '#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current max limit <strong>%limit</strong>).', array('%limit' => format_size(file_upload_max_size()))),
+ '#size' => 10,
+ '#element_validate' => array('_file_generic_settings_max_filesize'),
+ );
+
+ $form['file_entity_default_allowed_extensions'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Default allowed file extensions'),
+ '#default_value' => variable_get('file_entity_default_allowed_extensions', 'jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp mp3 mov mp4 m4a m4v mpeg avi ogg oga ogv weba webp webm'),
+ '#description' => t('Separate extensions with a space or comma and do not include the leading dot.'),
+ '#maxlength' => NULL,
+ );
+
+ $form['file_entity_alt'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Alt attribute'),
+ '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
+ '#default_value' => variable_get('file_entity_alt', '[file:field_file_image_alt_text]'),
+ );
+ $form['file_entity_title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Title attribute'),
+ '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
+ '#default_value' => variable_get('file_entity_title', '[file:field_file_image_title_text]'),
+ );
+
+ // Provide default token values.
+ if (module_exists('token')) {
+ $form['token_help'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => array('file'),
+ '#dialog' => TRUE,
+ );
+ $form['file_entity_alt']['#description'] .= t('This field supports tokens.');
+ $form['file_entity_title']['#description'] .= t('This field supports tokens.');
+ }
+ $form['file_upload_wizard'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('File upload wizard'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ '#description' => t('Configure the steps available when uploading a new file.'),
+ );
+ $form['file_upload_wizard']['file_entity_file_upload_wizard_skip_file_type'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Skip filetype selection.'),
+ '#default_value' => variable_get('file_entity_file_upload_wizard_skip_file_type', FALSE),
+ '#description' => t('The file type selection step is only available if the uploaded file falls into two or more file types. If this step is skipped, files with no available file type or two or more file types will not be assigned a file type.'),
+ );
+ $form['file_upload_wizard']['file_entity_file_upload_wizard_skip_scheme'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Skip scheme selection.'),
+ '#default_value' => variable_get('file_entity_file_upload_wizard_skip_scheme', FALSE),
+ '#description' => t('The scheme selection step is only available if two or more file destinations, such as public local files served by the webserver and private local files served by Drupal, are available. If this step is skipped, files will automatically be saved using the default download method.'),
+ );
+ $form['file_upload_wizard']['file_entity_file_upload_wizard_skip_fields'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Skip available fields.'),
+ '#default_value' => variable_get('file_entity_file_upload_wizard_skip_fields', FALSE),
+ '#description' => t('The field selection step is only available if the file type the file belongs to has any available fields. If this step is skipped, any fields on the file will be left blank.'),
+ );
+
+ return system_settings_form($form);
+}
diff --git a/sites/all/modules/file_entity/file_entity.admin.js b/sites/all/modules/file_entity/file_entity.admin.js
new file mode 100644
index 000000000..a8f83d9e4
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.admin.js
@@ -0,0 +1,44 @@
+(function ($) {
+
+Drupal.behaviors.fileDisplayStatus = {
+ attach: function (context, settings) {
+ $('#file-displays-status-wrapper input.form-checkbox', context).once('display-status', function () {
+ var $checkbox = $(this);
+ // Retrieve the tabledrag row belonging to this display.
+ var $row = $('#' + $checkbox.attr('id').replace(/-status$/, '-weight'), context).closest('tr');
+ // Retrieve the vertical tab belonging to this display.
+ var tab = $('#' + $checkbox.attr('id').replace(/-status$/, '-settings'), context).data('verticalTab');
+
+ // Bind click handler to this checkbox to conditionally show and hide the
+ // display's tableDrag row and vertical tab pane.
+ $checkbox.bind('click.displayStatusUpdate', function () {
+ if ($checkbox.is(':checked')) {
+ $row.show();
+ if (tab) {
+ tab.tabShow().updateSummary();
+ }
+ }
+ else {
+ $row.hide();
+ if (tab) {
+ tab.tabHide().updateSummary();
+ }
+ }
+ // Restripe table after toggling visibility of table row.
+ Drupal.tableDrag['file-displays-order'].restripeTable();
+ });
+
+ // Attach summary for configurable displays (only for screen-readers).
+ if (tab) {
+ tab.fieldset.drupalSetSummary(function (tabContext) {
+ return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
+ });
+ }
+
+ // Trigger our bound click handler to update elements to initial state.
+ $checkbox.triggerHandler('click.displayStatusUpdate');
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/file_entity/file_entity.api.php b/sites/all/modules/file_entity/file_entity.api.php
new file mode 100644
index 000000000..3a7368124
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.api.php
@@ -0,0 +1,407 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the File Entity module.
+ */
+
+/**
+ * Declare that your module provides default file types.
+ *
+ * Your module may already implement this hook for other CTools plugin types.
+ * If so, copy the body of this function into the existing hook.
+ */
+function hook_ctools_plugin_api($owner, $api) {
+ if ($owner == 'file_entity' && $api == 'file_type') {
+ return array('version' => 1);
+ }
+}
+
+/**
+ * Define default file types.
+ *
+ * File types are implemented as CTools exportables, so modules can alter the
+ * defaults via hook_file_default_types_alter(), and the administrator can
+ * save overridden and custom types to the {file_type} database table.
+ *
+ * @return array
+ * An array whose keys are file type names and whose values are objects
+ * representing the file type, with the following key/value pairs:
+ * - api_version: The version of this data.
+ * - type: The file type name.
+ * - label: The human-readable name of the file type.
+ * - description: The description of this file type.
+ * - mimetypes: An array of mimetypes that this file type will map to.
+ */
+function hook_file_default_types() {
+ return array(
+ 'image' => (object) array(
+ 'api_version' => 1,
+ 'type' => 'image',
+ 'label' => t('Image'),
+ 'description' => t("An <em>Image</em> is a two-dimensional picture that has a similar appearance to some subject, usually a physical object or a person."),
+ 'mimetypes' => array(
+ 'image/*',
+ ),
+ ),
+ );
+}
+
+/**
+ * Alter default file types.
+ *
+ * @see hook_file_default_types()
+ */
+function hook_file_default_types_alter(&$types) {
+ $types['image']->mimetypes[] = 'image/svg+xml';
+}
+
+/**
+ * Define file formatters.
+ *
+ * @return array
+ * An array whose keys are file formatter names and whose values are arrays
+ * describing the formatter.
+ *
+ * @todo Document key/value pairs that comprise a formatter.
+ *
+ * @see hook_file_formatter_info_alter()
+ */
+function hook_file_formatter_info() {
+ // @todo Add example.
+}
+
+/**
+ * Perform alterations on file formatters.
+ *
+ * @param array $info
+ * Array of information on file formatters exposed by
+ * hook_file_formatter_info() implementations.
+ */
+function hook_file_formatter_info_alter(&$info) {
+ // @todo Add example.
+}
+
+/**
+ * @todo Add documentation.
+ *
+ * Note: This is not really a hook. The function name is manually specified via
+ * 'view callback' in hook_file_formatter_info(), with this recommended callback
+ * name pattern.
+ */
+function hook_file_formatter_FORMATTER_view($file, $display, $langcode) {
+}
+
+/**
+ * @todo Add documentation.
+ *
+ * Note: This is not really a hook. The function name is manually specified via
+ * 'settings callback' in hook_file_formatter_info(), with this recommended
+ * callback name pattern.
+ */
+function hook_file_formatter_FORMATTER_settings($form, &$form_state, $settings) {
+}
+
+/**
+ * @todo Add documentation.
+ */
+function hook_file_displays_alter($displays, $file, $view_mode) {
+}
+
+/**
+ * @todo Add documentation.
+ */
+function hook_file_view($file, $view_mode, $langcode) {
+}
+
+/**
+ * @todo Add documentation.
+ */
+function hook_file_view_alter($build, $type) {
+}
+
+/**
+ * Add mass file operations.
+ *
+ * This hook enables modules to inject custom operations into the mass
+ * operations dropdown found at admin/content/file, by associating a callback
+ * function with the operation, which is called when the form is submitted. The
+ * callback function receives one initial argument, which is an array of the
+ * checked files.
+ *
+ * @return array
+ * An array of operations. Each operation is an associative array that may
+ * contain the following key-value pairs:
+ * - 'label': Required. The label for the operation, displayed in the dropdown
+ * menu.
+ * - 'callback': Required. The function to call for the operation.
+ * - 'callback arguments': Optional. An array of additional arguments to pass
+ * to the callback function.
+ */
+function hook_file_operations() {
+ $operations = array(
+ 'delete' => array(
+ 'label' => t('Delete selected files'),
+ 'callback' => NULL,
+ ),
+ );
+ return $operations;
+}
+
+/**
+ * Control access to a file.
+ *
+ * Modules may implement this hook if they want to have a say in whether or not
+ * a given user has access to perform a given operation on a file.
+ *
+ * The administrative account (user ID #1) always passes any access check,
+ * so this hook is not called in that case. Users with the "bypass file access"
+ * permission may always view and edit files through the administrative
+ * interface.
+ *
+ * Note that not all modules will want to influence access on all
+ * file types. If your module does not want to actively grant or
+ * block access, return FILE_ENTITY_ACCESS_IGNORE or simply return nothing.
+ * Blindly returning FALSE will break other file access modules.
+ *
+ * @param string $op
+ * The operation to be performed. Possible values:
+ * - "create"
+ * - "delete"
+ * - "update"
+ * - "view"
+ * - "download"
+ * @param object $file
+ * The file on which the operation is to be performed, or, if it does
+ * not yet exist, the type of file to be created.
+ * @param object $account
+ * A user object representing the user for whom the operation is to be
+ * performed.
+ *
+ * @return string|NULL
+ * FILE_ENTITY_ACCESS_ALLOW if the operation is to be allowed;
+ * FILE_ENTITY_ACCESS_DENY if the operation is to be denied;
+ * FILE_ENTITY_ACCESS_IGNORE to not affect this operation at all.
+ *
+ * @ingroup file_entity_access
+ */
+function hook_file_entity_access($op, $file, $account) {
+ $type = is_string($file) ? $file : $file->type;
+
+ if ($op !== 'create' && (REQUEST_TIME - $file->timestamp) < 3600) {
+ // If the file was uploaded in the last hour, deny access to it.
+ return FILE_ENTITY_ACCESS_DENY;
+ }
+
+ // Returning nothing from this function would have the same effect.
+ return FILE_ENTITY_ACCESS_IGNORE;
+}
+
+/**
+ * Control access to listings of files.
+ *
+ * @param object $query
+ * A query object describing the composite parts of a SQL query related to
+ * listing files.
+ *
+ * @see hook_query_TAG_alter()
+ * @ingroup file_entity_access
+ */
+function hook_query_file_entity_access_alter(QueryAlterableInterface $query) {
+ // Only show files that have been uploaded more than an hour ago.
+ $query->condition('timestamp', REQUEST_TIME - 3600, '<=');
+}
+
+/**
+ * Act on a file being displayed as a search result.
+ *
+ * This hook is invoked from file_entity_search_execute(), after file_load()
+ * and file_view() have been called.
+ *
+ * @param object $file
+ * The file being displayed in a search result.
+ *
+ * @return array
+ * Extra information to be displayed with search result. This information
+ * should be presented as an associative array. It will be concatenated
+ * with the file information (filename) in the default search result theming.
+ *
+ * @see template_preprocess_search_result()
+ * @see search-result.tpl.php
+ *
+ * @ingroup file_entity_api_hooks
+ */
+function hook_file_entity_search_result($file) {
+ $file_usage_count = db_query('SELECT count FROM {file_usage} WHERE fid = :fid', array('fid' => $file->fid))->fetchField();
+ return array(
+ 'file_usage_count' => format_plural($file_usage_count, '1 use', '@count uses'),
+ );
+}
+
+/**
+ * Act on a file being indexed for searching.
+ *
+ * This hook is invoked during search indexing, after file_load(), and after
+ * the result of file_view() is added as $file->rendered to the file object.
+ *
+ * @param object $file
+ * The file being indexed.
+ *
+ * @return string
+ * Additional file information to be indexed.
+ *
+ * @ingroup file_entity_api_hooks
+ */
+function hook_file_update_index($file) {
+ $text = '';
+ $uses = db_query('SELECT module, count FROM {file_usage} WHERE fid = :fid', array(':fid' => $file->fid));
+ foreach ($uses as $use) {
+ $text .= '<h2>' . check_plain($use->module) . '</h2>' . check_plain($use->count);
+ }
+ return $text;
+}
+
+/**
+ * Provide additional methods of scoring for core search results for files.
+ *
+ * A file's search score is used to rank it among other files matched by the
+ * search, with the highest-ranked files appearing first in the search listing.
+ *
+ * For example, a module allowing users to vote on files could expose an
+ * option to allow search results' rankings to be influenced by the average
+ * voting score of a file.
+ *
+ * All scoring mechanisms are provided as options to site administrators, and
+ * may be tweaked based on individual sites or disabled altogether if they do
+ * not make sense. Individual scoring mechanisms, if enabled, are assigned a
+ * weight from 1 to 10. The weight represents the factor of magnification of
+ * the ranking mechanism, with higher-weighted ranking mechanisms having more
+ * influence. In order for the weight system to work, each scoring mechanism
+ * must return a value between 0 and 1 for every file. That value is then
+ * multiplied by the administrator-assigned weight for the ranking mechanism,
+ * and then the weighted scores from all ranking mechanisms are added, which
+ * brings about the same result as a weighted average.
+ *
+ * @return array
+ * An associative array of ranking data. The keys should be strings,
+ * corresponding to the internal name of the ranking mechanism, such as
+ * 'recent', or 'usage'. The values should be arrays themselves, with the
+ * following keys available:
+ * - "title": the human readable name of the ranking mechanism. Required.
+ * - "join": part of a query string to join to any additional necessary
+ * table. This is not necessary if the table required is already joined to
+ * by the base query, such as for the {file_managed} table. Other tables
+ * should use the full table name as an alias to avoid naming collisions.
+ * Optional.
+ * - "score": part of a query string to calculate the score for the ranking
+ * mechanism based on values in the database. This does not need to be
+ * wrapped in parentheses, as it will be done automatically; it also does
+ * not need to take the weighted system into account, as it will be done
+ * automatically. It does, however, need to calculate a decimal between
+ * 0 and 1; be careful not to cast the entire score to an integer by
+ * inadvertently introducing a variable argument. Required.
+ * - "arguments": if any arguments are required for the score, they can be
+ * specified in an array here.
+ *
+ * @ingroup file_entity_api_hooks
+ */
+function hook_file_ranking() {
+ // If voting is disabled, we can avoid returning the array, no hard feelings.
+ if (variable_get('vote_file_enabled', TRUE)) {
+ return array(
+ 'vote_average' => array(
+ 'title' => t('Average vote'),
+ // Note that we use i.sid, the search index's search item id, rather
+ // than fm.fid.
+ 'join' => 'LEFT JOIN {vote_file_data} vote_file_data ON vote_file_data.fid = i.sid',
+ // The highest possible score should be 1,
+ // and the lowest possible score, always 0, should be 0.
+ 'score' => 'vote_file_data.average / CAST(%f AS DECIMAL)',
+ // Pass in the highest possible voting score as a decimal argument.
+ 'arguments' => array(variable_get('vote_score_max', 5)),
+ ),
+ );
+ }
+}
+
+/**
+ * Alter file download headers.
+ *
+ * @param array $headers
+ * Array of download headers.
+ * @param object $file
+ * File object.
+ */
+function hook_file_download_headers_alter(array &$headers, $file) {
+ // Instead of being powered by PHP, tell the world this resource was powered
+ // by your custom module!
+ $headers['X-Powered-By'] = 'My Module';
+}
+
+/**
+ * React to a file being downloaded.
+ */
+function hook_file_transfer($uri, array $headers) {
+ // Redirect a download for an S3 file to the actual location.
+ if (file_uri_scheme($uri) == 's3') {
+ $url = file_create_url($uri);
+ drupal_goto($url);
+ }
+}
+
+/**
+ * Decides which file type (bundle) should be assigned to a file entity.
+ *
+ * @param object $file
+ * File object.
+ *
+ * @return array
+ * Array of file type machine names that can be assigned to a given file type.
+ * If there are more proposed file types the one, that was returned the first,
+ * wil be chosen. This can be, however, changed in alter hook.
+ *
+ * @see hook_file_type_alter()
+ */
+function hook_file_type($file) {
+ // Assign all files uploaded by anonymous users to a special file type.
+ if (user_is_anonymous()) {
+ return array('untrusted_files');
+ }
+}
+
+/**
+ * Alters list of file types that can be assigned to a file.
+ *
+ * @param array $types
+ * List of proposed types.
+ * @param object $file
+ * File object.
+ */
+function hook_file_type_alter(&$types, $file) {
+ // Choose a specific, non-first, file type.
+ $types = array($types[4]);
+}
+
+/**
+ * Provides metadata information.
+ *
+ * @todo Add documentation.
+ *
+ * @return array
+ * An array of metadata information.
+ */
+function hook_file_metadata_info() {
+
+}
+
+/**
+ * Alters metadata information.
+ *
+ * @todo Add documentation.
+ *
+ * @return array
+ * an array of metadata information.
+ */
+function hook_file_metadata_info_alter() {
+
+}
diff --git a/sites/all/modules/file_entity/file_entity.field.inc b/sites/all/modules/file_entity/file_entity.field.inc
new file mode 100644
index 000000000..4b039b632
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.field.inc
@@ -0,0 +1,426 @@
+<?php
+
+/**
+ * @file
+ * Field API integration for the file_entity module.
+ */
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function file_entity_field_formatter_info() {
+ $info['file_rendered'] = array(
+ 'label' => t('Rendered file'),
+ 'description' => t('Display the file in a specific view mode'),
+ 'field types' => array('file', 'image'),
+ 'settings' => array(
+ 'file_view_mode' => 'default',
+ ),
+ 'file formatter' => array(
+ 'hidden' => TRUE,
+ ),
+ );
+ $info['file_download_link'] = array(
+ 'label' => t('Download link'),
+ 'description' => t('Displays a link that will force the browser to download the file.'),
+ 'field types' => array('file', 'image'),
+ 'settings' => array(
+ 'text' => t('Download [file:name]'),
+ ),
+ );
+ $info['file_audio'] = array(
+ 'label' => t('Audio'),
+ 'description' => t('Render the file using an HTML5 audio tag.'),
+ 'field types' => array('file'),
+ 'settings' => array(
+ 'controls' => TRUE,
+ 'autoplay' => FALSE,
+ 'loop' => FALSE,
+ 'preload' => '',
+ 'multiple_file_behavior' => 'tags',
+ ),
+ 'file formatter' => array(
+ 'mime types' => array('audio/*'),
+ ),
+ );
+ $info['file_video'] = array(
+ 'label' => t('Video'),
+ 'description' => t('Render the file using an HTML5 video tag.'),
+ 'field types' => array('file'),
+ 'settings' => array(
+ 'controls' => TRUE,
+ 'autoplay' => FALSE,
+ 'loop' => FALSE,
+ 'muted' => FALSE,
+ 'width' => NULL,
+ 'height' => NULL,
+ 'preload' => '',
+ 'multiple_file_behavior' => 'tags',
+ ),
+ 'file formatter' => array(
+ 'mime types' => array('video/*'),
+ ),
+ );
+ return $info;
+}
+
+/**
+ * Implements hook_field_formatter_info_alter().
+ */
+function file_entity_field_formatter_info_alter(&$info) {
+ // Add descriptions to core formatters.
+ $descriptions = array(
+ 'file_default' => t('Create a simple link to the file. The link is prefixed by a file type icon and the name of the file is used as the link text'),
+ 'file_table' => t('Build a two-column table where the first column contains a generic link to the file and the second column displays the size of the file.'),
+ 'file_url_plain' => t('Display a plain text URL to the file.'),
+ 'image' => t('Format the file as an image. The image can be displayed using an image style and can optionally be linked to the image file itself or its parent content.'),
+ );
+ foreach ($descriptions as $key => $description) {
+ if (isset($info[$key]) && empty($info[$key]['description'])) {
+ $info[$key]['description'] = $description;
+ }
+ }
+
+ // Formatters that can be used for images but not files, should have a
+ // default mimetype restriction added to the image/* mime type for use with
+ // file formatters.
+ foreach ($info as &$formatter) {
+ if (!isset($formatter['file formatter']) && in_array('image', $formatter['field types']) && !in_array('file', $formatter['field types'])) {
+ $formatter['file formatter']['mime types'] = array('image/*');
+ }
+ }
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ */
+function file_entity_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+ $element = array();
+
+ if ($display['type'] == 'file_rendered') {
+ $element['file_view_mode'] = array(
+ '#title' => t('View mode'),
+ '#type' => 'select',
+ '#options' => file_entity_view_mode_labels(),
+ '#default_value' => $settings['file_view_mode'],
+ // Never empty, so no #empty_option
+ );
+ }
+ elseif ($display['type'] == 'file_download_link') {
+ $element['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Link text'),
+ '#description' => t('This field support tokens.'),
+ '#default_value' => $settings['text'],
+ '#required' => TRUE,
+ );
+ }
+ elseif ($display['type'] == 'file_audio') {
+ $element['controls'] = array(
+ '#title' => t('Show audio controls'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['controls'],
+ );
+ $element['autoplay'] = array(
+ '#title' => t('Autoplay'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['autoplay'],
+ );
+ $element['loop'] = array(
+ '#title' => t('Loop'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['loop'],
+ );
+ $element['preload'] = array(
+ '#title' => t('Preload'),
+ '#type' => 'select',
+ '#default_value' => $settings['preload'],
+ '#options' => drupal_map_assoc(array('none', 'auto', 'metadata')),
+ '#empty_option' => 'unspecified',
+ );
+ $element['multiple_file_behavior'] = array(
+ '#title' => t('Display of multiple files'),
+ '#type' => 'radios',
+ '#options' => array(
+ 'tags' => t('Use multiple @tag tags, each with a single source', array('@tag' => '<audio>')),
+ 'sources' => t('Use multiple sources within a single @tag tag', array('@tag' => '<audio>')),
+ ),
+ '#default_value' => $settings['multiple_file_behavior'],
+ // Hide this setting in the manage file display configuration.
+ '#access' => !empty($field),
+ );
+
+ }
+ elseif ($display['type'] == 'file_video') {
+ $element['controls'] = array(
+ '#title' => t('Show video controls'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['controls'],
+ );
+ $element['autoplay'] = array(
+ '#title' => t('Autoplay'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['autoplay'],
+ );
+ $element['loop'] = array(
+ '#title' => t('Loop'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['loop'],
+ );
+ $element['muted'] = array(
+ '#title' => t('Muted'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['muted'],
+ );
+ $element['width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Width'),
+ '#default_value' => $settings['width'],
+ '#size' => 5,
+ '#maxlength' => 5,
+ '#field_suffix' => t('pixels'),
+ );
+ $element['height'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Height'),
+ '#default_value' => $settings['height'],
+ '#size' => 5,
+ '#maxlength' => 5,
+ '#field_suffix' => t('pixels'),
+ );
+ $element['preload'] = array(
+ '#title' => t('Preload'),
+ '#type' => 'select',
+ '#default_value' => $settings['preload'],
+ '#options' => drupal_map_assoc(array('none', 'auto', 'metadata')),
+ '#empty_option' => 'unspecified',
+ );
+ $element['multiple_file_behavior'] = array(
+ '#title' => t('Display of multiple files'),
+ '#type' => 'radios',
+ '#options' => array(
+ 'tags' => t('Use multiple @tag tags, each with a single source', array('@tag' => '<video>')),
+ 'sources' => t('Use multiple sources within a single @tag tag', array('@tag' => '<video>')),
+ ),
+ '#default_value' => $settings['multiple_file_behavior'],
+ // Hide this setting in the manage file display configuration.
+ '#access' => !empty($field),
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function file_entity_field_formatter_settings_summary($field, $instance, $view_mode) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+ $summary = array();
+
+ if ($display['type'] === 'file_rendered') {
+ $view_mode_label = file_entity_view_mode_label($settings['file_view_mode'], t('Unknown'));
+ $summary[] = t('View mode: %mode', array('%mode' => $view_mode_label));
+ }
+ elseif ($display['type'] == 'file_download_link') {
+ $summary[] = t('Link text: %text', array('%text' => $settings['text']));
+ }
+ elseif ($display['type'] === 'file_audio') {
+ if (isset($settings['controls'])) {
+ $summary[] = t('Controls: %controls', array('%controls' => $settings['controls'] ? 'visible' : 'hidden'));
+ }
+ if (isset($settings['autoplay'])) {
+ $summary[] = t('Autoplay: %autoplay', array('%autoplay' => $settings['autoplay'] ? t('yes') : t('no')));
+ }
+ if (isset($settings['loop'])) {
+ $summary[] = t('Loop: %loop', array('%loop' => $settings['loop'] ? t('yes') : t('no')));
+ }
+ if (!empty($settings['preload'])) {
+ $summary[] = t('Preload: %preload', array('%preload' => $settings['preload']));
+ }
+ if (isset($settings['multiple_file_behavior'])) {
+ $summary[] = t('Multiple files: %multiple', array('%multiple' => $settings['multiple_file_behavior']));
+ }
+ }
+ elseif ($display['type'] === 'file_video') {
+ if (isset($settings['controls'])) {
+ $summary[] = t('Controls: %controls', array('%controls' => $settings['controls'] ? 'visible' : 'hidden'));
+ }
+ if (isset($settings['autoplay'])) {
+ $summary[] = t('Autoplay: %autoplay', array('%autoplay' => $settings['autoplay'] ? t('yes') : t('no')));
+ }
+ if (isset($settings['loop'])) {
+ $summary[] = t('Loop: %loop', array('%loop' => $settings['loop'] ? t('yes') : t('no')));
+ }
+ if (isset($settings['muted'])) {
+ $summary[] = t('Muted: %muted', array('%muted' => $settings['muted'] ? t('yes') : t('no')));
+ }
+ if ($settings['width'] && $settings['height']) {
+ $summary[] = t('Size: %width x %height', array('%width' => $settings['width'], '%height' => $settings['height']));
+ }
+ if (!empty($settings['preload'])) {
+ $summary[] = t('Preload: %preload', array('%preload' => $settings['preload']));
+ }
+ if (isset($settings['multiple_file_behavior'])) {
+ $summary[] = t('Multiple files: %multiple', array('%multiple' => $settings['multiple_file_behavior']));
+ }
+ }
+
+ return implode('<br />', $summary);
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ */
+function file_entity_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+ $settings = $display['settings'];
+ $element = array();
+
+ if ($display['type'] == 'file_rendered') {
+ $view_mode = $settings['file_view_mode'];
+
+ // To prevent infinite recursion caused by reference cycles, we store
+ // diplayed nodes in a recursion queue.
+ $recursion_queue = &drupal_static(__FUNCTION__, array());
+
+ // If no 'referencing entity' is set, we are starting a new 'reference
+ // thread' and need to reset the queue.
+ // @todo Bug: $entity->referencing_entity on files referenced in a different
+ // thread on the page.
+ // E.g: 1 references 1+2 / 2 references 1+2 / visit homepage.
+ // We'd need a more accurate way...
+ if (!isset($entity->referencing_entity)) {
+ $recursion_queue = array();
+ }
+
+ // The recursion queue only needs to track files.
+ if ($entity_type == 'file') {
+ list($id) = entity_extract_ids($entity_type, $entity);
+ $recursion_queue[$id] = $id;
+ }
+
+ // Prevent 'empty' fields from causing a WSOD.
+ $items = array_filter($items);
+
+ // Check the recursion queue to determine which nodes should be fully
+ // displayed, and which nodes will only be displayed as a title.
+ $files_display = array();
+ foreach ($items as $delta => $item) {
+ if (!isset($recursion_queue[$item['fid']])) {
+ $files_display[$item['fid']] = file_load($item['fid']);
+ if (!empty($item['description'])) {
+ $files_display[$item['fid']]->description = $item['description'];
+ }
+ }
+ }
+
+ // Load and build the fully displayed nodes.
+ if ($files_display) {
+ foreach ($files_display as $fid => $file) {
+ $files_display[$fid]->referencing_entity = $entity;
+ $files_display[$fid]->referencing_entity_type = $entity_type;
+ $files_display[$fid]->referencing_field = $field['field_name'];
+ }
+ $output = file_view_multiple($files_display, $view_mode);
+ // Remove the first level from the output array.
+ $files_built = reset($output);
+ }
+
+ // Assemble the render array.
+ foreach ($items as $delta => $item) {
+ if (isset($files_built[$item['fid']])) {
+ $element[$delta] = $files_built[$item['fid']];
+ }
+ }
+ }
+ elseif ($display['type'] == 'file_download_link') {
+ foreach ($items as $delta => $item) {
+ $file = (object) $item;
+ if (file_entity_access('download', $file)) {
+ $element[$delta] = array(
+ '#theme' => 'file_entity_download_link',
+ '#file' => $file,
+ '#text' => $settings['text'],
+ );
+ }
+ }
+ }
+ elseif ($display['type'] == 'file_audio') {
+ $multiple_file_behavior = $settings['multiple_file_behavior'];
+
+ // Prevent 'empty' fields from causing a WSOD.
+ $items = array_filter($items);
+
+ // Build an array of sources for each <audio> element.
+ $source_lists = array();
+ if ($multiple_file_behavior == 'tags') {
+ foreach ($items as $delta => $item) {
+ if ($item['type'] == 'audio') {
+ $source_lists[$delta] = array($item);
+ }
+ }
+ }
+ else {
+ foreach ($items as $delta => $item) {
+ if ($item['type'] == 'audio') {
+ $source_lists[0][$delta] = $item;
+ }
+ }
+ }
+
+ // Render each source list as an <audio> element.
+ foreach ($source_lists as $delta => $sources) {
+ $element[$delta] = array(
+ '#theme' => 'file_entity_file_audio',
+ '#files' => $sources,
+ '#controls' => $settings['controls'],
+ '#autoplay' => $settings['autoplay'],
+ '#loop' => $settings['loop'],
+ '#preload' => $settings['preload'],
+ );
+ }
+ }
+ elseif ($display['type'] == 'file_video') {
+ $multiple_file_behavior = $settings['multiple_file_behavior'];
+
+ // Prevent 'empty' fields from causing a WSOD.
+ $items = array_filter($items);
+
+ // Build an array of sources for each <video> element.
+ $source_lists = array();
+ if ($multiple_file_behavior == 'tags') {
+ foreach ($items as $delta => $item) {
+ if ($item['type'] == 'video') {
+ $source_lists[$delta] = array($item);
+ }
+ }
+ }
+ else {
+ foreach ($items as $delta => $item) {
+ if ($item['type'] == 'video') {
+ $source_lists[0][$delta] = $item;
+ }
+ }
+ }
+
+ // Render each source list as an <video> element.
+ foreach ($source_lists as $delta => $sources) {
+ $element[$delta] = array(
+ '#theme' => 'file_entity_file_video',
+ '#files' => $sources,
+ '#controls' => $settings['controls'],
+ '#autoplay' => $settings['autoplay'],
+ '#loop' => $settings['loop'],
+ '#muted' => $settings['muted'],
+ '#width' => $settings['width'],
+ '#height' => $settings['height'],
+ '#preload' => $settings['preload'],
+ );
+ }
+ }
+
+ return $element;
+}
diff --git a/sites/all/modules/file_entity/file_entity.file.inc b/sites/all/modules/file_entity/file_entity.file.inc
new file mode 100644
index 000000000..c9aab5c04
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.file.inc
@@ -0,0 +1,322 @@
+<?php
+
+/**
+ * @file
+ * File hooks implemented by the File entity module.
+ */
+
+/**
+ * Implements hook_file_presave().
+ */
+function file_entity_file_presave($file) {
+ // Always ensure the filemime property is current.
+ if (!empty($file->original) || empty($file->filemime)) {
+ $file->filemime = file_get_mimetype($file->uri);
+ }
+
+ // The file type is used as a bundle key, and therefore, must not be NULL.
+ // It defaults to FILE_TYPE_NONE when loaded via file_load(), but in case
+ // file_save() is called on a new file object, default it here too.
+ if (!isset($file->type)) {
+ $file->type = FILE_TYPE_NONE;
+ }
+
+ // If the file isn't already assigned a real type, determine what type should
+ // be assigned to it.
+ if ($file->type === FILE_TYPE_NONE) {
+ $type = file_get_type($file);
+ if (isset($type)) {
+ $file->type = $type;
+ }
+ }
+
+ field_attach_presave('file', $file);
+
+ // Fetch image dimensions.
+ file_entity_metadata_fetch_image_dimensions($file);
+}
+
+/**
+ * Implements hook_file_type().
+ */
+function file_entity_file_type($file) {
+ $types = array();
+ foreach (file_type_get_enabled_types() as $type) {
+ if (file_entity_match_mimetypes($type->mimetypes, $file->filemime)) {
+ $types[] = $type->type;
+ }
+ }
+
+ return $types;
+}
+
+/**
+ * Implements hook_file_insert().
+ */
+function file_entity_file_insert($file) {
+ // Ensure field data is saved since file_save() does not in Drupal 7.
+ field_attach_insert('file', $file);
+
+ // Save file metadata.
+ if (!empty($file->metadata)) {
+ $query = db_insert('file_metadata')->fields(array('fid', 'name', 'value'));
+ foreach ($file->metadata as $name => $value) {
+ $query->values(array(
+ 'fid' => $file->fid,
+ 'name' => $name,
+ 'value' => serialize($value),
+ ));
+ }
+ $query->execute();
+ }
+
+ // Clear any related field caches.
+ file_entity_invalidate_field_caches($file);
+}
+
+/**
+ * Implements hook_file_update().
+ */
+function file_entity_file_update($file) {
+ // Ensure field data is saved since file_save() does not in Drupal 7.
+ field_attach_update('file', $file);
+
+ // Save file metadata.
+ db_delete('file_metadata')->condition('fid', $file->fid)->execute();
+ if (!empty($file->metadata)) {
+ $query = db_insert('file_metadata')->fields(array('fid', 'name', 'value'));
+ foreach ($file->metadata as $name => $value) {
+ $query->values(array(
+ 'fid' => $file->fid,
+ 'name' => $name,
+ 'value' => serialize($value),
+ ));
+ }
+ $query->execute();
+ }
+
+ if (module_exists('image') && file_entity_file_get_mimetype_type($file) == 'image' && $file->filesize) {
+ // If the file has changed dimensions or a new file has been uploaded,
+ // update any image field reference to this file and flush image style
+ // derivatives.
+ $file->metadata += array('width' => NULL, 'height' => NULL);
+ $file->original->metadata += array('width' => NULL, 'height' => NULL);
+ if ($file->filesize != $file->original->filesize || $file->metadata['width'] != $file->original->metadata['width'] || $file->metadata['height'] != $file->original->metadata['height']) {
+ _file_entity_update_image_field_dimensions($file);
+ }
+
+ // Flush image style derivatives whenever an image is updated.
+ image_path_flush($file->uri);
+ }
+
+ // Clear any related field caches.
+ file_entity_invalidate_field_caches($file);
+}
+
+/**
+ * Implements hook_file_delete().
+ */
+function file_entity_file_delete($file) {
+ field_attach_delete('file', $file);
+
+ // This is safe to call since the file's records from the usage table have
+ // not yet been deleted.
+ file_entity_invalidate_field_caches($file);
+
+ // Remove file metadata.
+ db_delete('file_metadata')->condition('fid', $file->fid)->execute();
+
+ // Remove this file from the search index if needed.
+ // This code is implemented in file entity module rather than in search
+ // module because file entity is implementing search module's API, not the
+ // other way around.
+ if (module_exists('search')) {
+ search_reindex($file->fid, 'file');
+ }
+}
+
+/**
+ * Implements hook_file_mimetype_mapping_alter().
+ */
+function file_entity_file_mimetype_mapping_alter(&$mapping) {
+ // Add support for mka and mkv.
+ // @todo Remove when http://drupal.org/node/1443070 is fixed in core.
+ $new_mappings['mka'] = 'audio/x-matroska';
+ $new_mappings['mkv'] = 'video/x-matroska';
+
+ // Add support for weba, webm, and webp.
+ // @todo Remove when http://drupal.org/node/1443070 is fixed in core.
+ $new_mappings['weba'] = 'audio/webm';
+ $new_mappings['webm'] = 'video/webm';
+ $new_mappings['webp'] = 'image/webp';
+
+ foreach ($new_mappings as $extension => $mime_type) {
+ if (!in_array($mime_type, $mapping['mimetypes'])) {
+ // If the mime type does not already exist, add it.
+ $mapping['mimetypes'][] = $mime_type;
+ }
+
+ // Get the index of the mime type and assign the extension to that key.
+ $index = array_search($mime_type, $mapping['mimetypes']);
+ $mapping['extensions'][$extension] = $index;
+ }
+}
+
+/**
+ * Implements hook_file_load().
+ */
+function file_entity_file_load($files) {
+ // Add alt and title text to images.
+ $alt = variable_get('file_entity_alt', '[file:field_file_image_alt_text]');
+ $title = variable_get('file_entity_title', '[file:field_file_image_title_text]');
+
+ $replace_options = array(
+ 'clear' => TRUE,
+ 'sanitize' => FALSE,
+ );
+
+ foreach ($files as $file) {
+ $file->metadata = array();
+
+ // Load alt and title text from fields.
+ if (!empty($alt)) {
+ $output = token_replace($alt, array('file' => $file), $replace_options);
+
+ // @todo Remove once https://www.drupal.org/node/1713164 is fixed.
+ // There is currently no way to get the raw alt text returned from the
+ // token so we revert the encoding done during tokenization.
+ $file->alt = decode_entities($output);
+ }
+ if (!empty($title)) {
+ $output = token_replace($title, array('file' => $file), $replace_options);
+
+ // @todo Remove once https://www.drupal.org/node/1713164 is fixed.
+ // There is currently no way to get the raw title text returned from the
+ // token so we revert the encoding done during tokenization.
+ $file->title = decode_entities($output);
+ }
+ }
+
+ // Load and unserialize metadata.
+ $results = db_query("SELECT * FROM {file_metadata} WHERE fid IN (:fids)", array(':fids' => array_keys($files)));
+
+ foreach ($results as $result) {
+ $name = $result->name;
+
+ // image.module required height and width to be properties of the file.
+ if ($name == 'height' || $name == 'width') {
+ $files[$result->fid]->$name = unserialize($result->value);
+ }
+
+ $files[$result->fid]->metadata[$name] = unserialize($result->value);
+ }
+}
+
+/**
+ * Fetch the dimensions of an image and store them in the file metadata array.
+ */
+function file_entity_metadata_fetch_image_dimensions($file) {
+ // Prevent PHP notices when trying to read empty files.
+ // @see http://drupal.org/node/681042
+ if (!$file->filesize) {
+ return;
+ }
+
+ // Do not bother proceeding if this file does not have an image mime type.
+ if (file_entity_file_get_mimetype_type($file) != 'image') {
+ return;
+ }
+
+ // We have a non-empty image file.
+ $image_info = image_get_info($file->uri);
+ if ($image_info) {
+ $file->metadata['width'] = $image_info['width'];
+ $file->metadata['height'] = $image_info['height'];
+ }
+ else {
+ // Fallback to NULL values.
+ $file->metadata['width'] = NULL;
+ $file->metadata['height'] = NULL;
+ }
+}
+
+/**
+ * Update the image dimensions stored in any image fields for a file.
+ *
+ * @param object $file
+ * A file object that is an image.
+ *
+ * @see http://drupal.org/node/1448124
+ */
+function _file_entity_update_image_field_dimensions($file) {
+ // Prevent PHP notices when trying to read empty files.
+ // @see http://drupal.org/node/681042
+ if (!$file->filesize) {
+ return;
+ }
+
+ // Do not bother proceeding if this file does not have an image mime type.
+ if (file_entity_file_get_mimetype_type($file) != 'image') {
+ return;
+ }
+
+ // Find all image field enabled on the site.
+ $image_fields = _file_entity_get_fields_by_type('image');
+
+ foreach ($image_fields as $image_field) {
+ $query = new EntityFieldQuery();
+ $query->fieldCondition($image_field, 'fid', $file->fid);
+ $results = $query->execute();
+
+ foreach ($results as $entity_type => $entities) {
+ $entities = entity_load($entity_type, array_keys($entities));
+ foreach ($entities as $entity) {
+ foreach ($entity->{$image_field} as $langcode => $items) {
+ foreach ($items as $delta => $item) {
+ if ($item['fid'] == $file->fid) {
+ $entity->{$image_field}[$langcode][$delta]['width'] = $file->metadata['width'];
+ $entity->{$image_field}[$langcode][$delta]['height'] = $file->metadata['height'];
+ }
+ }
+ }
+
+ // Save the updated field column values.
+ _file_entity_entity_fields_update($entity_type, $entity);
+ }
+ }
+ }
+}
+
+/**
+ * Update an entity's field values without changing anything on the entity.
+ */
+function _file_entity_entity_fields_update($entity_type, $entity) {
+ list($id) = entity_extract_ids($entity_type, $entity);
+ if (empty($id)) {
+ throw new Exception(t('Cannot call _file_entity_update_entity_fields() on a new entity.'));
+ }
+
+ // Some modules use the original property.
+ if (!isset($entity->original)) {
+ $entity->original = $entity;
+ }
+
+ // Ensure that file_field_update() will not trigger additional usage.
+ unset($entity->revision);
+
+ // Invoke the field presave and update hooks.
+ field_attach_presave($entity_type, $entity);
+ field_attach_update($entity_type, $entity);
+
+ // Clear the cache for this entity now.
+ entity_get_controller($entity_type)->resetCache(array($id));
+}
+
+/**
+ * Implements hook_file_metadata_info().
+ */
+function file_entity_file_metadata_info() {
+ $info['width'] = array('label' => t('Width'), 'type' => 'integer');
+ $info['height'] = array('label' => t('Height'), 'type' => 'integer');
+ return $info;
+}
diff --git a/sites/all/modules/file_entity/file_entity.file_api.inc b/sites/all/modules/file_entity/file_entity.file_api.inc
new file mode 100644
index 000000000..cfb75f9b3
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.file_api.inc
@@ -0,0 +1,791 @@
+<?php
+
+/**
+ * @file
+ * API extensions of Drupal core's file.inc.
+ */
+
+/**
+ * The {file_managed}.type value when the file type has not yet been determined.
+ */
+define('FILE_TYPE_NONE', 'undefined');
+
+/**
+ * Returns information about file formatters from hook_file_formatter_info().
+ *
+ * @param string $formatter_type
+ * (optional) A file formatter type name. If ommitted, all file formatter
+ * will be returned.
+ *
+ * @return string|array
+ * Either a file formatter description, as provided by
+ * hook_file_formatter_info(), or an array of all existing file formatters,
+ * keyed by formatter type name.
+ */
+function file_info_formatter_types($formatter_type = NULL) {
+ $info = &drupal_static(__FUNCTION__);
+ if (!isset($info)) {
+ $info = module_invoke_all('file_formatter_info');
+ drupal_alter('file_formatter_info', $info);
+ uasort($info, '_file_entity_sort_weight_label');
+ }
+ if ($formatter_type) {
+ if (isset($info[$formatter_type])) {
+ return $info[$formatter_type];
+ }
+ }
+ else {
+ return $info;
+ }
+}
+
+/**
+ * Clears caches that are related to file entity.
+ *
+ * Clears all cached configuration related to file types, formatters, and
+ * display settings.
+ */
+function file_info_cache_clear() {
+ // Clear the CTools managed caches.
+ ctools_include('export');
+ ctools_export_load_object_reset('file_type');
+ ctools_export_load_object_reset('file_display');
+
+ // Clear the formatter type cache, managed by file_info_formatter_types().
+ drupal_static_reset('file_info_formatter_types');
+
+ // Clear file type caches.
+ drupal_static_reset('file_type_get_names');
+}
+
+/**
+ * Construct a drupal_render() style array from an array of loaded files.
+ *
+ * @param array $files
+ * An array of files as returned by file_load_multiple().
+ * @param string $view_mode
+ * View mode.
+ * @param int $weight
+ * An integer representing the weight of the first file in the list.
+ * @param string $langcode
+ * A string indicating the language field values are to be shown in. If no
+ * language is provided the current content language is used.
+ *
+ * @return array
+ * An array in the format expected by drupal_render().
+ */
+function file_view_multiple($files, $view_mode = 'full', $weight = 0, $langcode = NULL) {
+ if (empty($files)) {
+ return array();
+ }
+
+ field_attach_prepare_view('file', $files, $view_mode, $langcode);
+ entity_prepare_view('file', $files, $langcode);
+
+ $build = array();
+ foreach ($files as $file) {
+ $build['files'][$file->fid] = file_view($file, $view_mode, $langcode);
+ $build['files'][$file->fid]['#weight'] = $weight;
+ $weight++;
+ }
+ $build['files']['#sorted'] = TRUE;
+
+ return $build;
+}
+
+/**
+ * Generate an array for rendering the given file.
+ *
+ * @param object $file
+ * A file object.
+ * @param string $view_mode
+ * View mode.
+ * @param string $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ *
+ * @return array
+ * An array as expected by drupal_render().
+ */
+function file_view($file, $view_mode = 'full', $langcode = NULL) {
+ if (!isset($langcode)) {
+ $langcode = $GLOBALS['language_content']->language;
+ }
+
+ // Populate $file->content with a render() array.
+ file_build_content($file, $view_mode, $langcode);
+
+ $build = $file->content;
+ // We don't need duplicate rendering info in $file->content.
+ unset($file->content);
+
+ $build += array(
+ '#theme' => 'file_entity',
+ '#file' => $file,
+ '#view_mode' => $view_mode,
+ '#language' => $langcode,
+ );
+
+ // Add contextual links for this file, except when the file is already being
+ // displayed on its own page. Modules may alter this behavior (for example,
+ // to restrict contextual links to certain view modes) by implementing
+ // hook_file_view_alter().
+ if (!empty($file->fid) && !($view_mode == 'full' && file_entity_is_page($file))) {
+ $build['#contextual_links']['file'] = array('file', array($file->fid));
+ }
+
+ // Allow modules to modify the structured file.
+ $type = 'file';
+ drupal_alter(array('file_view', 'entity_view'), $build, $type);
+
+ return $build;
+}
+
+/**
+ * Builds a structured array representing the file's content.
+ *
+ * @param object $file
+ * A file object.
+ * @param string $view_mode
+ * View mode, e.g. 'default', 'full', etc.
+ * @param string $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ */
+function file_build_content($file, $view_mode = 'full', $langcode = NULL) {
+ if (!isset($langcode)) {
+ $langcode = $GLOBALS['language_content']->language;
+ }
+
+ // Remove previously built content, if exists.
+ $file->content = array();
+
+ // Build the actual file display.
+ // @todo Figure out how to clean this crap up.
+ $file->content['file'] = file_view_file($file, $view_mode, $langcode);
+ if (isset($file->content['file'])) {
+ if (isset($file->content['file']['#theme']) && $file->content['file']['#theme'] != 'file_link') {
+ unset($file->content['file']['#file']);
+ }
+ unset($file->content['file']['#view_mode']);
+ unset($file->content['file']['#language']);
+ }
+ else {
+ unset($file->content['file']);
+ }
+
+ // Build fields content.
+ // In case of a multiple view, file_view_multiple() already ran the
+ // 'prepare_view' step. An internal flag prevents the operation from running
+ // twice.
+ field_attach_prepare_view('file', array($file->fid => $file), $view_mode, $langcode);
+ entity_prepare_view('file', array($file->fid => $file), $langcode);
+ $file->content += field_attach_view('file', $file, $view_mode, $langcode);
+
+ $links = array();
+ $file->content['links'] = array(
+ '#theme' => 'links__file',
+ '#pre_render' => array('drupal_pre_render_links'),
+ '#attributes' => array('class' => array('links', 'inline')),
+ );
+ $file->content['links']['file'] = array(
+ '#theme' => 'links__file__file',
+ '#links' => $links,
+ '#attributes' => array('class' => array('links', 'inline')),
+ );
+
+ // Allow modules to make their own additions to the file.
+ module_invoke_all('file_view', $file, $view_mode, $langcode);
+ module_invoke_all('entity_view', $file, 'file', $view_mode, $langcode);
+}
+
+/**
+ * Generate an array for rendering just the file portion of a file entity.
+ *
+ * @param object $file
+ * A file object.
+ * @param string|array $displays
+ * Can be either:
+ * - the name of a view mode;
+ * - or an array of custom display settings, as returned by file_displays().
+ * @param string $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ *
+ * @return array
+ * An array as expected by drupal_render().
+ */
+function file_view_file($file, $displays = 'full', $langcode = NULL) {
+ if (!isset($langcode)) {
+ $langcode = $GLOBALS['language_content']->language;
+ }
+
+ // Prepare incoming display specifications.
+ if (is_string($displays)) {
+ $view_mode = $displays;
+ $displays = file_displays($file->type, $view_mode);
+ }
+ else {
+ $view_mode = '_custom_display';
+ }
+ drupal_alter('file_displays', $displays, $file, $view_mode);
+ _file_sort_array_by_weight($displays);
+
+ // Attempt to display the file with each of the possible displays. Stop after
+ // the first successful one. See file_displays() for details.
+ $element = NULL;
+ foreach ($displays as $formatter_type => $display) {
+ if (!empty($display['status'])) {
+ $formatter_info = file_info_formatter_types($formatter_type);
+ // Under normal circumstances, the UI prevents enabling formatters for
+ // incompatible MIME types. In case this was somehow circumvented (for
+ // example, a module updated its formatter definition without updating
+ // existing display settings), perform an extra check here.
+ if (isset($formatter_info['mime types'])) {
+ if (!file_entity_match_mimetypes($formatter_info['mime types'], $file->filemime)) {
+ continue;
+ }
+ }
+ if (isset($formatter_info['view callback']) && ($function = $formatter_info['view callback']) && function_exists($function)) {
+ $display['type'] = $formatter_type;
+ if (!empty($formatter_info['default settings'])) {
+ if (empty($display['settings'])) {
+ $display['settings'] = array();
+ }
+ $display['settings'] += $formatter_info['default settings'];
+ }
+ $element = $function($file, $display, $langcode);
+ if (isset($element)) {
+ break;
+ }
+ }
+ }
+ }
+
+ // As a last resort, fall back to showing a link to the file.
+ if (!isset($element)) {
+ $element = array(
+ '#theme' => 'file_link',
+ '#file' => $file,
+ );
+ }
+
+ // Add defaults and return the element.
+ $element += array(
+ '#file' => $file,
+ '#view_mode' => $view_mode,
+ '#language' => $langcode,
+ );
+
+ return $element;
+}
+
+/**
+ * @defgroup file_displays File displays API
+ * @{
+ * Functions to load and save information about file displays.
+ */
+
+/**
+ * Returns an array of displays to use for a file type in a given view mode.
+ *
+ * It is common for a site to be configured with broadly defined file types
+ * (e.g., 'video'), and to have different files of this type require different
+ * displays (for example, the code required to display a YouTube video is
+ * different than the code required to display a local QuickTime video).
+ * Therefore, the site administrator can configure multiple displays for a given
+ * file type. This function returns all of the displays that the administrator
+ * enabled for the given file type in the given view mode. file_view_file() then
+ * invokes each of these, and passes the specific file to display. Each display
+ * implementation can inspect the file, and either return a render array (if it
+ * is capable of displaying the file), or return nothing (if it is incapable of
+ * displaying the file). The first render array returned is the one used.
+ *
+ * @param string $file_type
+ * The type of file.
+ * @param string $view_mode
+ * The view mode.
+ *
+ * @return array
+ * An array keyed by the formatter type name. Each item in the array contains
+ * the following key/value pairs:
+ * - status: Whether this display is enabled. If not TRUE, file_view_file()
+ * skips over it.
+ * - weight: An integer that determines the order of precedence within the
+ * returned array. The lowest weight display capable of displaying the file
+ * is used.
+ * - settings: An array of key/value pairs specific to the formatter type. See
+ * hook_file_formatter_info() for details.
+ *
+ * @see hook_file_formatter_info()
+ * @see file_view_file()
+ */
+function file_displays($file_type, $view_mode) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ // If the requested view mode isn't configured to use a custom display for its
+ // fields, then don't use a custom display for its file either.
+ if ($view_mode != 'default') {
+ $view_mode_settings = field_view_mode_settings('file', $file_type);
+ $view_mode = !empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default';
+ }
+
+ if (!isset($cache[$file_type][$view_mode])) {
+ // Load the display configurations for the file type and view mode. If none
+ // exist for the view mode, use the default view mode.
+ $displays = file_displays_load($file_type, $view_mode, TRUE);
+ if (empty($displays) && $view_mode != 'default') {
+ $cache[$file_type][$view_mode] = file_displays($file_type, 'default');
+ }
+ else {
+ // Convert the display objects to arrays and remove unnecessary keys.
+ foreach ($displays as $formatter_name => $display) {
+ $displays[$formatter_name] = array_intersect_key((array) $display, drupal_map_assoc(array('status', 'weight', 'settings')));
+ }
+ $cache[$file_type][$view_mode] = $displays;
+ }
+ }
+
+ return $cache[$file_type][$view_mode];
+}
+
+/**
+ * Returns an array of {file_display} objects for the file type and view mode.
+ */
+function file_displays_load($file_type, $view_mode, $key_by_formatter_name = FALSE) {
+ ctools_include('export');
+
+ $display_names = array();
+ $prefix = $file_type . '__' . $view_mode . '__';
+ foreach (array_keys(file_info_formatter_types()) as $formatter_name) {
+ $display_names[] = $prefix . $formatter_name;
+ }
+ $displays = ctools_export_load_object('file_display', 'names', $display_names);
+
+ if ($key_by_formatter_name) {
+ $prefix_length = strlen($prefix);
+ $rekeyed_displays = array();
+ foreach ($displays as $name => $display) {
+ $rekeyed_displays[substr($name, $prefix_length)] = $display;
+ }
+ $displays = $rekeyed_displays;
+ }
+
+ return $displays;
+}
+
+/**
+ * Saves a {file_display} object to the database.
+ */
+function file_display_save($display) {
+ ctools_include('export');
+ ctools_export_crud_save('file_display', $display);
+ file_info_cache_clear();
+}
+
+/**
+ * Creates a new {file_display} object.
+ */
+function file_display_new($file_type, $view_mode, $formatter_name) {
+ ctools_include('export');
+ $display = ctools_export_crud_new('file_display');
+ file_info_cache_clear();
+ $display->name = implode('__', array($file_type, $view_mode, $formatter_name));
+ return $display;
+}
+
+/**
+ * @} End of "defgroup file_displays".
+ */
+
+/**
+ * @defgroup file_types File types API
+ * @{
+ * Functions to load and save information about file types.
+ */
+
+/**
+ * Load a file type configuration object.
+ *
+ * @param string $name
+ * The file type machine name to load.
+ *
+ * @return object
+ * The file type object, or FALSE if it does not exist.
+ */
+function file_type_load($name) {
+ ctools_include('export');
+ $type = ctools_export_crud_load('file_type', $name);
+ return isset($type) ? $type : FALSE;
+}
+
+/**
+ * Load multiple file type configuration objects.
+ *
+ * @param array $names
+ * An array of file type machine names to load.
+ *
+ * @return array
+ * An array of file type objects, keyed by machine name.
+ */
+function file_type_load_multiple(array $names) {
+ ctools_include('export');
+ return ctools_export_crud_load_multiple('file_type', $names);
+}
+
+/**
+ * Load all file type configuration objects.
+ *
+ * This includes all enabled and disabled file types.
+ *
+ * @param bool $reset
+ * If TRUE, the static cache of all file types will be flushed prior to
+ * loading. This can be important on listing pages where file types might
+ * have changed on the page load.
+ *
+ * @return array
+ * An array of file type objects, keyed by machine name.
+ *
+ * @see file_type_get_enabled_types()
+ * @see file_type_get_disabled_types()
+ */
+function file_type_load_all($reset = FALSE) {
+ ctools_include('export');
+ return ctools_export_crud_load_all('file_type', $reset);
+}
+
+/**
+ * Returns an array of enabled file types.
+ */
+function file_type_get_enabled_types() {
+ $types = file_type_load_all();
+ return array_filter($types, 'file_type_is_enabled');
+}
+
+/**
+ * Returns an array of disabled file types.
+ */
+function file_type_get_disabled_types() {
+ $types = file_type_load_all();
+ return array_filter($types, 'file_type_is_disabled');
+}
+
+/**
+ * Returns TRUE if a file type is enabled, FALSE otherwise.
+ */
+function file_type_is_enabled($type) {
+ return empty($type->disabled);
+}
+
+/**
+ * Returns TRUE if a file type is disabled, FALSE otherwise.
+ */
+function file_type_is_disabled($type) {
+ return !empty($type->disabled);
+}
+
+/**
+ * Returns an array of valid file extensions.
+ */
+function file_type_get_valid_extensions($type) {
+ include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
+ $mapping = file_mimetype_mapping();
+
+ $type_extensions = array();
+ $type_ext_keys = array();
+ if (!empty($type->mimetypes)) {
+ foreach ($mapping['mimetypes'] as $ext_key => $mimetype) {
+ if (file_entity_match_mimetypes($mimetype, $type->mimetypes)) {
+ $type_ext_keys[] = $ext_key;
+ }
+ }
+
+ if ($type_ext_keys) {
+ $type_extensions = array_intersect($mapping['extensions'], $type_ext_keys);
+ $type_extensions = array_keys($type_extensions);
+ }
+ }
+
+ return $type_extensions;
+}
+
+/**
+ * Updates an existing file type or creates a new one.
+ *
+ * This function can be called on its own, or via the CTools exportables
+ * 'save callback' for {file_type} objects.
+ */
+function file_type_save($type) {
+ // Get the old type object, so we now can issue the correct insert/update
+ // queries.
+ if (!empty($type->old_type) && $type->old_type != $type->type) {
+ $rename_bundle = TRUE;
+ $old_type = file_type_load($type->old_type);
+ }
+ else {
+ $rename_bundle = FALSE;
+ $old_type = file_type_load($type->type);
+ }
+
+ // The type and label fields are required, but description is optional.
+ if (!isset($type->description)) {
+ $type->description = '';
+ }
+ $fields = array(
+ 'type' => $type->type,
+ 'label' => $type->label,
+ 'description' => $type->description,
+ 'mimetypes' => serialize($type->mimetypes),
+ );
+
+ // Update an existing type object, whether with a modified 'type' property or
+ // not.
+ if ($old_type) {
+ if ($old_type->export_type & EXPORT_IN_DATABASE) {
+ db_update('file_type')
+ ->fields($fields)
+ ->condition('type', $old_type->type)
+ ->execute();
+ }
+ else {
+ db_insert('file_type')
+ ->fields($fields)
+ ->execute();
+ }
+ if ($rename_bundle) {
+ field_attach_rename_bundle('file', $old_type->type, $type->type);
+ }
+ module_invoke_all('file_type_update', $type);
+ $status = SAVED_UPDATED;
+ }
+ // Insert a new type object.
+ else {
+ db_insert('file_type')
+ ->fields($fields)
+ ->execute();
+ field_attach_create_bundle('file', $type->type);
+ module_invoke_all('file_type_insert', $type);
+ $status = SAVED_NEW;
+ }
+
+ // Clear the necessary caches.
+ file_info_cache_clear();
+
+ // Ensure the type has the correct export_type in case the $type parameter
+ // continues to be used by the calling function after this function completes.
+ if (empty($type->export_type)) {
+ $type->export_type = 0;
+ }
+ $type->export_type |= EXPORT_IN_DATABASE;
+
+ return $status;
+}
+
+/**
+ * Deletes a file type from the database.
+ *
+ * This function can be called on its own, or via the CTools exportables
+ * 'delete callback' for {file_type} objects.
+ *
+ * @param object|string $type
+ * Either a loaded file type object or the machine-name of the type.
+ */
+function file_type_delete($type) {
+ $type = is_string($type) ? file_type_load($type) : $type;
+
+ db_delete('file_type')
+ ->condition('type', $type->type)
+ ->execute();
+
+ // Remove this type from CToolS status variable.
+ $status = variable_get('default_file_type', array());
+ unset($status[$type->type]);
+ variable_set('default_file_type', $status);
+
+ file_info_cache_clear();
+
+ // After deleting from the database, check if the type still exists as a
+ // code-provided default type. If not, consider the type fully deleted and
+ // invoke the needed hooks.
+ if (!file_type_load($type->type)) {
+ field_attach_delete_bundle('file', $type->type);
+ module_invoke_all('file_type_delete', $type);
+ }
+}
+
+
+/**
+ * Enable a file type.
+ *
+ * @param string $type
+ * Type of the file_type to disable
+ */
+function file_type_enable($type) {
+ ctools_include('export');
+ ctools_export_crud_enable('file_type', $type);
+}
+
+
+/**
+ * Disable a file type.
+ *
+ * @param string $type
+ * Type of the file_type to disable
+ */
+function file_type_disable($type) {
+ ctools_include('export');
+ ctools_export_crud_disable('file_type', $type);
+}
+
+
+/**
+ * Determines file type for a given file.
+ *
+ * @param object $file
+ * File object.
+ *
+ * @return string
+ * Machine name of file type that should be used for given file.
+ */
+function file_get_type($file) {
+ $types = module_invoke_all('file_type', $file);
+ drupal_alter('file_type', $types, $file);
+
+ return empty($types) ? NULL : reset($types);
+}
+
+/**
+ * @} End of "defgroup file_types".
+ */
+
+/**
+ * Sorts an array by weight.
+ *
+ * Helper function to sort an array by the value of each item's 'weight' key,
+ * while preserving relative order of items that have equal weight.
+ */
+function _file_sort_array_by_weight(&$a) {
+ $i = 0;
+ foreach ($a as $key => $item) {
+ if (!isset($a[$key]['weight'])) {
+ $a[$key]['weight'] = 0;
+ }
+ $original_weight[$key] = $a[$key]['weight'];
+ $a[$key]['weight'] += $i / 1000;
+ $i++;
+ }
+ uasort($a, 'drupal_sort_weight');
+ foreach ($a as $key => $item) {
+ $a[$key]['weight'] = $original_weight[$key];
+ }
+}
+
+/**
+ * User sort function to sort by weight, then label/name.
+ */
+function _file_entity_sort_weight_label($a, $b) {
+ $a_weight = isset($a['weight']) ? $a['weight'] : 0;
+ $b_weight = isset($b['weight']) ? $b['weight'] : 0;
+ if ($a_weight == $b_weight) {
+ $a_label = isset($a['label']) ? $a['label'] : '';
+ $b_label = isset($b['label']) ? $b['label'] : '';
+ return strcasecmp($a_label, $b_label);
+ }
+ else {
+ return $a_weight < $b_weight ? -1 : 1;
+ }
+}
+
+/**
+ * Returns a file object which can be passed to file_save().
+ *
+ * @param string $uri
+ * A string containing the URI, path, or filename.
+ * @param bool $use_existing
+ * (Optional) If TRUE and there's an existing file in the {file_managed}
+ * table with the passed in URI, then that file object is returned.
+ * Otherwise, a new file object is returned. Default is TRUE.
+ *
+ * @return object|bool
+ * A file object, or FALSE on error.
+ *
+ * @todo This should probably be named
+ * file_load_by_uri($uri, $create_if_not_exists).
+ * @todo Remove this function when http://drupal.org/node/685818 is fixed.
+ */
+function file_uri_to_object($uri, $use_existing = TRUE) {
+ $file = FALSE;
+ $uri = file_stream_wrapper_uri_normalize($uri);
+
+ if ($use_existing) {
+ // We should always attempt to re-use a file if possible.
+ $files = entity_load('file', FALSE, array('uri' => $uri));
+ $file = !empty($files) ? reset($files) : FALSE;
+ }
+
+ if (empty($file)) {
+ $file = new stdClass();
+ $file->uid = $GLOBALS['user']->uid;
+ $file->filename = drupal_basename($uri);
+ $file->uri = $uri;
+ $file->filemime = file_get_mimetype($uri);
+ // This is gagged because some uris will not support it.
+ $file->filesize = @filesize($uri);
+ $file->timestamp = REQUEST_TIME;
+ $file->status = FILE_STATUS_PERMANENT;
+ }
+
+ return $file;
+}
+
+/**
+ * Delete multiple files.
+ *
+ * Unlike core's file_delete(), this function does not care about file usage
+ * or skip on invalid URIs. Just deletes the damn file like it should.
+ *
+ * @param array $fids
+ * An array of file IDs.
+ */
+function file_delete_multiple(array $fids) {
+ $transaction = db_transaction();
+ if (!empty($fids) && $files = file_load_multiple($fids)) {
+ try {
+ foreach ($files as $fid => $file) {
+ module_invoke_all('file_delete', $file);
+ module_invoke_all('entity_delete', $file, 'file');
+ // Skip calling field_attach_delete as file_entity_file_delete()
+ // does this.
+ // field_attach_delete('file', $file);
+ // Remove this file from the search index if needed.
+ // This code is implemented in file_entity module rather than in search
+ // module, because node module is implementing search module's API,
+ // not the other way around.
+ if (module_exists('search')) {
+ search_reindex($fid, 'file');
+ }
+
+ // Make sure the file is deleted before removing its row from the
+ // database, so UIs can still find the file in the database.
+ if (file_valid_uri($file->uri)) {
+ file_unmanaged_delete($file->uri);
+ }
+ }
+
+ db_delete('file_managed')
+ ->condition('fid', $fids, 'IN')
+ ->execute();
+ db_delete('file_usage')
+ ->condition('fid', $fids, 'IN')
+ ->execute();
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception('file', $e);
+ throw $e;
+ }
+
+ // Clear the page and block and file_load_multiple caches.
+ entity_get_controller('file')->resetCache();
+ }
+}
diff --git a/sites/all/modules/file_entity/file_entity.file_default_displays.inc b/sites/all/modules/file_entity/file_entity.file_default_displays.inc
new file mode 100644
index 000000000..ccf2376a8
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.file_default_displays.inc
@@ -0,0 +1,174 @@
+<?php
+
+/**
+ * @file
+ * Default display configuration for the default file types.
+ */
+
+/**
+ * Implements hook_file_default_displays().
+ */
+function file_entity_file_default_displays() {
+ $file_displays = array();
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'audio__default__file_field_file_audio';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'controls' => 1,
+ 'autoplay' => 0,
+ 'loop' => 0,
+ 'multiple_file_behavior' => 'tags',
+ );
+ $file_displays['audio__default__file_field_file_audio'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'audio__preview__file_field_file_default';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = '';
+ $file_displays['audio__preview__file_field_file_default'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'audio__teaser__file_field_file_audio';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'controls' => 1,
+ 'autoplay' => 0,
+ 'loop' => 0,
+ 'multiple_file_behavior' => 'tags',
+ );
+ $file_displays['audio__teaser__file_field_file_audio'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'document__default__file_field_file_default';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = '';
+ $file_displays['document__default__file_field_file_default'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'document__preview__file_field_file_default';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = '';
+ $file_displays['document__preview__file_field_file_default'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'document__teaser__file_field_file_default';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = '';
+ $file_displays['document__teaser__file_field_file_default'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'image__default__file_field_file_default';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = '';
+ $file_displays['image__default__file_field_file_default'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'image__preview__file_field_file_default';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = '';
+ $file_displays['image__preview__file_field_file_default'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'image__teaser__file_field_file_default';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = '';
+ $file_displays['image__teaser__file_field_file_default'] = $file_display;
+
+ // Enhance the default image displays if the Image module is enabled.
+ if (module_exists('image')) {
+ // Images should be displayed as unstyled images by default.
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'image__default__file_field_image';
+ $file_display->weight = 48;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'image_style' => '',
+ 'image_link' => '',
+ );
+ $file_displays['image__default__file_field_image'] = $file_display;
+
+ // Image previews should be displayed as image thumbnails by default.
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'image__preview__file_field_image';
+ $file_display->weight = 48;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'image_style' => 'thumbnail',
+ 'image_link' => '',
+ );
+ $file_displays['image__preview__file_field_image'] = $file_display;
+
+ // Image teasers should be displayed as medium images by default.
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'image__teaser__file_field_image';
+ $file_display->weight = 48;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'image_style' => 'medium',
+ 'image_link' => 'content',
+ );
+ $file_displays['image__teaser__file_field_image'] = $file_display;
+ }
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'video__default__file_field_file_video';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'controls' => 1,
+ 'autoplay' => 0,
+ 'loop' => 0,
+ 'width' => '',
+ 'height' => '',
+ 'multiple_file_behavior' => 'tags',
+ );
+ $file_displays['video__default__file_field_file_video'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'video__preview__file_field_file_default';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = '';
+ $file_displays['video__preview__file_field_file_default'] = $file_display;
+
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'video__teaser__file_field_file_video';
+ $file_display->weight = 50;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'controls' => 1,
+ 'autoplay' => 0,
+ 'loop' => 0,
+ 'width' => '',
+ 'height' => '',
+ 'multiple_file_behavior' => 'tags',
+ );
+ $file_displays['video__teaser__file_field_file_video'] = $file_display;
+
+ return $file_displays;
+}
diff --git a/sites/all/modules/file_entity/file_entity.info b/sites/all/modules/file_entity/file_entity.info
new file mode 100644
index 000000000..a6c3a15de
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.info
@@ -0,0 +1,38 @@
+name = File Entity
+description = "Extends Drupal file entities to be fieldable and viewable."
+package = Media
+core = 7.x
+
+dependencies[] = field
+dependencies[] = file
+dependencies[] = ctools
+dependencies[] = system (>=7.9)
+
+test_dependencies[] = token
+
+files[] = views/views_handler_argument_file_type.inc
+files[] = views/views_handler_field_file_rendered.inc
+files[] = views/views_handler_field_file_type.inc
+files[] = views/views_handler_filter_file_type.inc
+files[] = views/views_handler_filter_schema_type.inc
+files[] = views/views_handler_field_file_filename.inc
+files[] = views/views_handler_field_file_link.inc
+files[] = views/views_handler_field_file_link_edit.inc
+files[] = views/views_handler_field_file_link_delete.inc
+files[] = views/views_handler_field_file_link_download.inc
+files[] = views/views_handler_field_file_link_usage.inc
+files[] = views/views_plugin_row_file_rss.inc
+files[] = views/views_plugin_row_file_view.inc
+files[] = file_entity.test
+
+configure = admin/config/media/file-settings
+
+; We have to add a fake version so Git checkouts do not fail Media dependencies
+version = 7.x-2.x-dev
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta2"
+core = "7.x"
+project = "file_entity"
+datestamp = "1436896443"
+
diff --git a/sites/all/modules/file_entity/file_entity.install b/sites/all/modules/file_entity/file_entity.install
new file mode 100644
index 000000000..feeb2e21e
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.install
@@ -0,0 +1,1081 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the file_entity module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function file_entity_schema() {
+ $schema['file_type'] = array(
+ 'description' => 'Stores the settings for file types.',
+ 'fields' => array(
+ 'type' => array(
+ 'description' => 'The machine name of the file type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'label' => array(
+ 'description' => 'The human readable name of the file type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'translatable' => TRUE,
+ ),
+ 'description' => array(
+ 'description' => 'A brief description of this file type.',
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'medium',
+ 'translatable' => TRUE,
+ ),
+ 'mimetypes' => array(
+ 'description' => 'Mimetypes mapped to this file type.',
+ 'type' => 'blob',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('type'),
+ 'export' => array(
+ 'key' => 'type',
+ 'key name' => 'Type',
+ 'primary key' => 'type',
+ 'default hook' => 'file_default_types',
+ 'identifier' => 'file_type',
+ 'export type string' => 'ctools_type',
+ 'save callback' => 'file_type_save',
+ 'delete callback' => 'file_type_delete',
+ 'api' => array(
+ 'owner' => 'file_entity',
+ 'api' => 'file_type',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+ );
+ $schema['file_display'] = array(
+ 'description' => 'Stores configuration options for file displays.',
+ 'fields' => array(
+ // @todo Can be refactored as a compond primary key after
+ // http://drupal.org/node/924236 is implemented.
+ 'name' => array(
+ 'description' => 'A combined string (FILE_TYPE__VIEW_MODE__FILE_FORMATTER) identifying a file display configuration. For integration with CTools Exportables, stored as a single string rather than as a compound primary key.',
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'not null' => TRUE,
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Weight of formatter within the display chain for the associated file type and view mode. A file is rendered using the lowest weighted enabled display configuration that matches the file type and view mode and that is capable of displaying the file.',
+ ),
+ 'status' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => 'The status of the display. (1 = enabled, 0 = disabled)',
+ ),
+ 'settings' => array(
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ 'description' => 'A serialized array of name value pairs that store the formatter settings for the display.',
+ ),
+ ),
+ 'primary key' => array('name'),
+ // Exportable support via CTools.
+ 'export' => array(
+ 'key' => 'name',
+ 'key name' => 'Name',
+ 'primary key' => 'name',
+ // The {file_display}.status field is used to control whether the display
+ // is active in the display chain. CTools-level disabling is something
+ // different, and it's not yet clear how to interpret it for file
+ // displays. Until that's figured out, prevent CTools-level disabling.
+ 'can disable' => FALSE,
+ 'default hook' => 'file_default_displays',
+ 'identifier' => 'file_display',
+ 'api' => array(
+ 'owner' => 'file_entity',
+ 'api' => 'file_default_displays',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+ );
+ $schema['file_metadata'] = array(
+ 'description' => 'Cache images dimensions.',
+ 'fields' => array(
+ 'fid' => array(
+ 'description' => 'The {file_managed}.fid of the metadata.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'name' => array(
+ 'description' => "The name of the metadata (e.g. 'width').",
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'not null' => TRUE,
+ ),
+ 'value' => array(
+ 'description' => "The value of the metadata (e.g. '200px').",
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('fid', 'name'),
+ 'foreign keys' => array(
+ 'file_managed' => array(
+ 'table' => 'file_managed',
+ 'columns' => array('fid' => 'fid'),
+ ),
+ ),
+ );
+ return $schema;
+}
+
+/**
+ * Implements hook_schema_alter().
+ */
+function file_entity_schema_alter(&$schema) {
+ $schema['file_managed']['fields']['type'] = array(
+ 'description' => 'The type of this file.',
+ 'type' => 'varchar',
+ 'length' => 50,
+ 'not null' => TRUE,
+ // If the FILE_TYPE_NONE constant ever changes, then change the value here
+ // too, and add an update function to deal with existing records. The
+ // constant isn't used here, because there may be cases where this function
+ // runs without the module file loaded.
+ 'default' => 'undefined',
+ );
+ $schema['file_managed']['indexes']['file_type'] = array('type');
+}
+
+
+/**
+ * Implements hook_install().
+ */
+function file_entity_install() {
+ $schema = array();
+ file_entity_schema_alter($schema);
+ $spec = $schema['file_managed']['fields']['type'];
+ $indexes_new = array('indexes' => $schema['file_managed']['indexes']);
+
+ // If another module (e.g., Media) had added a {file_managed}.type field,
+ // then change it to the expected specification. Otherwise, add the field.
+ if (db_field_exists('file_managed', 'type')) {
+ // db_change_field() will fail if any records have type=NULL, so update
+ // them to the new default value.
+ db_update('file_managed')->fields(array('type' => $spec['default']))->isNull('type')->execute();
+
+ // Indexes using a field being changed must be dropped prior to calling
+ // db_change_field(). However, the database API doesn't provide a way to do
+ // this without knowing what the old indexes are. Therefore, it is the
+ // responsibility of the module that added them to drop them prior to
+ // allowing this module to be installed.
+ db_change_field('file_managed', 'type', 'type', $spec, $indexes_new);
+ }
+ else {
+ db_add_field('file_managed', 'type', $spec, $indexes_new);
+ }
+
+ // Set permissions.
+ $roles = user_roles();
+ foreach ($roles as $rid => $role) {
+ user_role_grant_permissions($rid, array('view files'));
+ }
+
+ // Create the title and alt text fields.
+ _file_entity_create_alt_title_fields();
+
+ // Configure default pathauto variables if it is currently installed.
+ if (module_exists('pathauto')) {
+ variable_set('pathauto_file_pattern', 'file/[file:name]');
+ }
+
+ // Classify existing files according to the currently defined file types.
+ // Queue all files to be classified during cron runs using the Queue API.
+ $queue = DrupalQueue::get('file_entity_type_determine');
+ $result = db_query('SELECT fid FROM {file_managed}');
+ foreach ($result as $record) {
+ $queue->createItem($record->fid);
+ }
+
+ // Warn users that existing files will not have a file type until the queue
+ // has been processed.
+ if ($queue->numberOfItems()) {
+ drupal_set_message(t('Existing files must be classified according to the currently defined file types. These files have been queued for processing and will have their file type determined during cron runs.'));
+ }
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function file_entity_uninstall() {
+ drupal_load('module', 'file_entity');
+ foreach (file_type_load_all(TRUE) as $type) {
+ file_type_delete($type);
+ }
+
+ // Remove the added column to the core {file_managed} table.
+ db_drop_field('file_managed', 'type');
+
+ // Remove variables.
+ variable_del('file_entity_max_filesize');
+ variable_del('file_entity_default_allowed_extensions');
+ variable_del('file_entity_alt');
+ variable_del('file_entity_title');
+ variable_del('file_entity_allow_insecure_download');
+ variable_del('file_entity_file_upload_wizard_skip_file_type');
+ variable_del('file_entity_file_upload_wizard_skip_scheme');
+ variable_del('file_entity_file_upload_wizard_skip_fields');
+}
+
+/**
+ * Create the {file_display} database table.
+ */
+function file_entity_update_7000() {
+ if (db_table_exists('file_display')) {
+ return t('The table {file_display} already exists.');
+ }
+
+ $schema['file_display'] = array(
+ 'description' => 'Stores configuration options for file displays.',
+ 'fields' => array(
+ 'name' => array(
+ 'description' => 'A combined string (FILE_TYPE__VIEW_MODE__FILE_FORMATTER) identifying a file display configuration. For integration with CTools Exportables, stored as a single string rather than as a compound primary key.',
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'not null' => TRUE,
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Weight of formatter within the display chain for the associated file type and view mode. A file is rendered using the lowest weighted enabled display configuration that matches the file type and view mode and that is capable of displaying the file.',
+ ),
+ 'status' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => 'The status of the display. (1 = enabled, 0 = disabled)',
+ ),
+ 'settings' => array(
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ 'description' => 'A serialized array of name value pairs that store the formatter settings for the display.',
+ ),
+ ),
+ 'primary key' => array('name'),
+ );
+ db_create_table('file_display', $schema['file_display']);
+}
+
+/**
+ * Move file display configurations.
+ *
+ * Move file display configurations from the 'file_displays' variable to the
+ * {file_display} database table.
+ */
+function file_entity_update_7001() {
+ $file_displays = variable_get('file_displays');
+ if (!empty($file_displays)) {
+ foreach ($file_displays as $file_type => $file_type_displays) {
+ if (!empty($file_type_displays)) {
+ foreach ($file_type_displays as $view_mode => $view_mode_displays) {
+ if (!empty($view_mode_displays)) {
+ foreach ($view_mode_displays as $formatter_name => $display) {
+ if (!empty($display)) {
+ db_merge('file_display')
+ ->key(array(
+ 'name' => implode('__', array($file_type, $view_mode, $formatter_name)),
+ ))
+ ->fields(array(
+ 'status' => isset($display['status']) ? $display['status'] : 0,
+ 'weight' => isset($display['weight']) ? $display['weight'] : 0,
+ 'settings' => isset($display['settings']) ? serialize($display['settings']) : NULL,
+ ))
+ ->execute();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ variable_del('file_displays');
+}
+
+/**
+ * Empty update function to trigger a theme registry rebuild.
+ */
+function file_entity_update_7100() { }
+
+/**
+ * Update all files with empty types to use the first part of filemime.
+ *
+ * For example, an png image with filemime 'image/png' will be assigned a file
+ * type of 'image'.
+ */
+function file_entity_update_7101() {
+ db_update('file_managed')
+ ->expression('type', "SUBSTRING_INDEX(filemime, '/', 1)")
+ ->condition('type', '')
+ ->execute();
+}
+
+/**
+ * Empty update function to trigger an entity cache rebuild.
+ */
+function file_entity_update_7102() {
+}
+
+/**
+ * Empty update function.
+ */
+function file_entity_update_7103() {
+}
+
+/**
+ * Assign view file permission when updating without the Media module.
+ */
+function file_entity_update_7104() {
+ if (!module_exists('media')) {
+ $roles = user_roles(FALSE, 'view file');
+ if (empty($roles)) {
+ // Set permissions.
+ $roles = user_roles();
+ foreach ($roles as $rid => $role) {
+ // Do not use user_role_grant_permission() since it relies on
+ // hook_permission(), which will not run for file entity module if it
+ // is disabled or the permission is renamed or removed.
+ db_merge('role_permission')
+ ->fields(array(
+ 'rid' => $rid,
+ 'permission' => 'view file',
+ 'module' => 'file_entity',
+ ))
+ ->condition('rid', $rid)
+ ->condition('permission', 'view file')
+ ->execute();
+ }
+ }
+ }
+}
+
+/**
+ * Create the {image_dimensions} database table.
+ */
+function file_entity_update_7200() {
+ if (db_table_exists('image_dimensions')) {
+ return t('The table {image_dimensions} already exists.');
+ }
+
+ $schema['image_dimensions'] = array(
+ 'description' => 'Cache images dimensions.',
+ 'fields' => array(
+ 'fid' => array(
+ 'description' => 'File ID.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'height' => array(
+ 'description' => 'The height of the image in pixels.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'width' => array(
+ 'description' => 'The width of the image in pixels..',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('fid'),
+ 'foreign keys' => array(
+ 'file_managed' => array(
+ 'table' => 'file_managed',
+ 'columns' => array('fid' => 'fid'),
+ ),
+ ),
+ );
+ db_create_table('image_dimensions', $schema['image_dimensions']);
+}
+
+/**
+ * Add the {file_type}, {file_type_mimetypes} tables.
+ */
+function file_entity_update_7201() {
+ $schema = array(
+ 'description' => 'Stores the settings for file types.',
+ 'fields' => array(
+ 'type' => array(
+ 'description' => 'The machine name of the file type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'label' => array(
+ 'description' => 'The human readable name of the file type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'translatable' => TRUE,
+ ),
+ 'description' => array(
+ 'description' => 'A brief description of this file type.',
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'medium',
+ 'translatable' => TRUE,
+ ),
+ ),
+ 'primary key' => array('type'),
+ 'export' => array(
+ 'key' => 'type',
+ 'key name' => 'Type',
+ 'primary key' => 'type',
+ 'default hook' => 'file_default_types',
+ 'identifier' => 'file_type',
+ 'export type string' => 'ctools_type',
+ 'subrecords callback' => 'file_type_load_subrecords',
+ 'save callback' => 'file_type_save',
+ 'delete callback' => 'file_type_delete',
+ 'api' => array(
+ 'owner' => 'file_entity',
+ 'api' => 'file_type',
+ 'minimum_version' => 1,
+ 'current_version' => 1,
+ ),
+ ),
+ );
+ if (!db_table_exists('file_type')) {
+ db_create_table('file_type', $schema);
+ }
+
+ $schema = array(
+ 'description' => 'Maps mimetypes to file types.',
+ 'fields' => array(
+ 'type' => array(
+ 'description' => 'The machine name of the file type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'mimetype' => array(
+ 'description' => 'Mimetypes mapped to this file type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ ),
+ 'indexes' => array(
+ 'file_type' => array('type'),
+ 'file_type_mimetype' => array('mimetype'),
+ ),
+ );
+ if (!db_table_exists('file_type_mimetypes')) {
+ db_create_table('file_type_mimetypes', $schema);
+ }
+}
+
+/**
+ * Update empty {file_managed}.type records to 'undefined'.
+ *
+ * Drupal 7.8 disallows empty string as the value for a bundle key, so update
+ * empty {file_managed}.type records to 'undefined' instead.
+ */
+function file_entity_update_7202() {
+ db_update('file_managed')
+ // Using 'undefined' instead of FILE_TYPE_NONE, because update functions can
+ // run for disabled modules.
+ ->fields(array('type' => 'undefined'))
+ ->condition('type', '')
+ ->execute();
+}
+
+/**
+ * Update permission names.
+ */
+function file_entity_update_7203() {
+ $permissions = array(
+ 'view file' => 'view files',
+ 'edit file' => 'edit any files',
+ );
+ foreach ($permissions as $old => $new) {
+ db_update('role_permission')
+ ->fields(array('permission' => $new))
+ ->condition('permission', $old)
+ ->execute();
+ }
+}
+
+
+/**
+ * Add title and alt text to image file types.
+ */
+function file_entity_update_7204() {
+ _file_entity_create_alt_title_fields();
+}
+
+/**
+ * Function to create the title and alt text fields and instances.
+ */
+function _file_entity_create_alt_title_fields() {
+ $t = get_t();
+ // Create the alt text field and instance.
+ // Define the alt text field.
+ $alt_text_field = array(
+ 'active' => '1',
+ 'cardinality' => '1',
+ 'deleted' => '0',
+ 'entity_types' => array(),
+ 'field_name' => 'field_file_image_alt_text',
+ 'foreign keys' => array(
+ 'format' => array(
+ 'columns' => array(
+ 'format' => 'format',
+ ),
+ 'table' => 'filter_format',
+ ),
+ ),
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'module' => 'text',
+ 'settings' => array(
+ 'max_length' => '255',
+ ),
+ 'translatable' => '0',
+ 'type' => 'text',
+ );
+
+ // As long as the alt text field doesn't already exist create it.
+ if (!field_info_field($alt_text_field['field_name'])) {
+ field_create_field($alt_text_field);
+ }
+
+ // Define the alt text instance.
+ $alt_text_instance = array(
+ 'bundle' => 'image',
+ 'default_value' => NULL,
+ 'deleted' => '0',
+ 'description' => $t('Alternative text is used by screen readers, search engines, and when the image cannot be loaded. By adding alt text you improve accessibility and search engine optimization.'),
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'full' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'preview' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'file',
+ 'field_name' => 'field_file_image_alt_text',
+ 'label' => 'Alt Text',
+ 'required' => 0,
+ 'settings' => array(
+ 'text_processing' => '0',
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'size' => '60',
+ ),
+ 'type' => 'text_textfield',
+ 'weight' => '-4',
+ ),
+ );
+
+ // For sites that updated from Media 1.x, continue to provide these deprecated
+ // view modes.
+ // @see http://drupal.org/node/1051090
+ if (variable_get('media__show_deprecated_view_modes')) {
+ $alt_text_instance['display'] += array(
+ 'media_link' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'media_original' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ );
+ }
+
+ // As long as the alt text instance doesn't already exist create it.
+ if (!field_info_instance($alt_text_instance['entity_type'], $alt_text_instance['field_name'], $alt_text_instance['bundle'])) {
+ field_create_instance($alt_text_instance);
+ }
+
+ // Create the title text field and instance.
+ // Define the title text field.
+ $title_text_field = array(
+ 'active' => '1',
+ 'cardinality' => '1',
+ 'deleted' => '0',
+ 'entity_types' => array(),
+ 'field_name' => 'field_file_image_title_text',
+ 'foreign keys' => array(
+ 'format' => array(
+ 'columns' => array(
+ 'format' => 'format',
+ ),
+ 'table' => 'filter_format',
+ ),
+ ),
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'module' => 'text',
+ 'settings' => array(
+ 'max_length' => '255',
+ ),
+ 'translatable' => '0',
+ 'type' => 'text',
+ );
+
+ // As long as the title text field doesn't exist create it.
+ if (!field_info_field($title_text_field['field_name'])) {
+ field_create_field($title_text_field);
+ }
+
+ // Define the title text instance.
+ $title_text_instance = array(
+ 'bundle' => 'image',
+ 'default_value' => NULL,
+ 'deleted' => '0',
+ 'description' => $t('Title text is used in the tool tip when a user hovers their mouse over the image. Adding title text makes it easier to understand the context of an image and improves usability.'),
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 1,
+ ),
+ 'full' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'preview' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'file',
+ 'field_name' => 'field_file_image_title_text',
+ 'label' => 'Title Text',
+ 'required' => 0,
+ 'settings' => array(
+ 'text_processing' => '0',
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'size' => '60',
+ ),
+ 'type' => 'text_textfield',
+ 'weight' => '-3',
+ ),
+ );
+
+ // For sites that updated from Media 1.x, continue to provide these deprecated
+ // view modes.
+ // @see http://drupal.org/node/1051090
+ if (variable_get('media__show_deprecated_view_modes')) {
+ $title_text_instance['display'] += array(
+ 'media_link' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'media_original' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ );
+ }
+
+ // As long as the title text instance doesn't already exist create it.
+ if (!field_info_instance($title_text_instance['entity_type'], $title_text_instance['field_name'], $title_text_instance['bundle'])) {
+ field_create_instance($title_text_instance);
+ }
+}
+
+/**
+ * Fix broken indexes caused by incorrect index definitions in update 7201.
+ */
+function file_entity_update_7205() {
+ // Drop broken file type indexes. These may not exist if the broken version
+ // of update 7201 was never run.
+ if (db_index_exists('file_type_mimetypes', 0)) {
+ db_drop_index('file_type_mimetypes', 0);
+ }
+ if (db_index_exists('file_type_mimetypes', 1)) {
+ db_drop_index('file_type_mimetypes', 1);
+ }
+
+ // Add file type indexes. These may already exist if the fixed version of
+ // update 7201 was run.
+ if (!db_index_exists('file_type_mimetypes', 'file_type')) {
+ db_add_index('file_type_mimetypes', 'file_type', array('type'));
+ }
+ if (!db_index_exists('file_type_mimetypes', 'file_type_mimetype')) {
+ db_add_index('file_type_mimetypes', 'file_type_mimetype', array('mimetype'));
+ }
+}
+
+/**
+ * Configure default pathauto variables if it is currently installed.
+ */
+function file_entity_update_7206() {
+ if (module_exists('pathauto')) {
+ variable_set('pathauto_file_pattern', 'file/[file:name]');
+ }
+}
+
+/**
+ * Remove the administration files limit variable.
+ */
+function file_entity_update_7207() {
+ variable_del('file_entity_admin_files_limit');
+}
+
+/**
+ * Add expanded file type permissions to roles with existing file permissions.
+ */
+function file_entity_update_7208() {
+ foreach (array('edit own files', 'edit any files', 'delete own files', 'delete any files', 'download own files', 'download any files') as $old_permission) {
+ $roles = user_roles(FALSE, $old_permission);
+
+ foreach ($roles as $rid => $name) {
+ $new_permissions = array();
+
+ foreach (file_type_get_enabled_types() as $type => $info) {
+ switch ($old_permission) {
+ case 'edit own files':
+ $new_permissions[] = 'edit own ' . $type . ' files';
+ break;
+
+ case 'edit any files':
+ $new_permissions[] = 'edit any ' . $type . ' files';
+ break;
+
+ case 'delete own files':
+ $new_permissions[] = 'delete own ' . $type . ' files';
+ break;
+
+ case 'delete any files':
+ $new_permissions[] = 'delete any ' . $type . ' files';
+ break;
+
+ case 'download own files':
+ $new_permissions[] = 'download own ' . $type . ' files';
+ break;
+
+ case 'download any files':
+ $new_permissions[] = 'download any ' . $type . ' files';
+ break;
+ }
+ }
+
+ if (!empty($new_permissions)) {
+ // Grant new permissions for the role.
+ foreach ($new_permissions as $name) {
+ db_merge('role_permission')
+ ->key(array(
+ 'rid' => $rid,
+ 'permission' => $name,
+ ))
+ ->fields(array(
+ 'module' => 'file_entity',
+ ))
+ ->execute();
+ }
+ }
+
+ // Remove old permission from the role.
+ db_delete('role_permission')
+ ->condition('rid', $rid)
+ ->condition('permission', $old_permission)
+ ->condition('module', 'file_entity')
+ ->execute();
+ }
+ }
+}
+
+/**
+ * Remove the {file_type_streams} table if it exists.
+ */
+function file_entity_update_7209() {
+ if (db_table_exists('file_type_streams')) {
+ db_drop_table('file_type_streams');
+ }
+}
+
+/**
+ * Merge MIME types into the {file_type} table.
+ */
+function file_entity_update_7210() {
+ // Add the new mimetypes field if it doesn't already exist.
+ if (!db_field_exists('file_type', 'mimetypes')) {
+ $field = array(
+ 'description' => 'Mimetypes mapped to this file type.',
+ 'type' => 'blob',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ 'serialize' => TRUE,
+ );
+
+ db_add_field('file_type', 'mimetypes', $field);
+ }
+
+ // Migrate any existing MIME type information into {file_type}.
+ if (db_table_exists('file_type_mimetypes')) {
+ module_load_include('inc', 'file_entity', 'file_entity.file_api');
+ $types = file_type_load_all(TRUE);
+ foreach ($types as $type) {
+ $mimetypes = db_select('file_type_mimetypes', 'ftm')
+ ->fields('ftm', array('mimetype'))
+ ->condition('type', $type->type)
+ ->execute()->fetchCol();
+
+ if (!empty($mimetypes)) {
+ $type->mimetypes = $mimetypes;
+ file_type_save($type);
+ }
+ }
+
+ // Remove {file_type_mimetypes} after the information is migrated.
+ db_drop_table('file_type_mimetypes');
+ }
+}
+
+/**
+ * Create the {file_metadata} table.
+ */
+function file_entity_update_7211() {
+ $schema = array(
+ 'description' => 'Stores file metadata in a key/value store.',
+ 'fields' => array(
+ 'fid' => array(
+ 'description' => 'The {file_managed}.fid of the metadata.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'name' => array(
+ 'description' => "The name of the metadata (e.g. 'width').",
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'not null' => TRUE,
+ ),
+ 'value' => array(
+ 'description' => "The value of the metadata (e.g. '200px').",
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('fid', 'name'),
+ 'foreign keys' => array(
+ 'file_managed' => array(
+ 'table' => 'file_managed',
+ 'columns' => array('fid' => 'fid'),
+ ),
+ ),
+ );
+ db_create_table('file_metadata', $schema);
+}
+
+/**
+ * Migrate the image_dimensions table to the new file_metadata table.
+ */
+function file_entity_update_7212(&$sandbox) {
+ if (!db_table_exists('image_dimensions')) {
+ return;
+ }
+
+ if (!isset($sandbox['progress'])) {
+ $sandbox['progress'] = 0;
+ $sandbox['current_fid'] = 0;
+ $sandbox['max'] = db_query('SELECT COUNT(DISTINCT fid) FROM {image_dimensions}')->fetchField();
+ }
+
+ $results = db_query_range("SELECT fid, width, height FROM {image_dimensions} WHERE fid > :fid ORDER BY fid ASC", 0, 20, array(':fid' => $sandbox['current_fid']))->fetchAllAssoc('fid');
+
+ // Clear any existing records in the metadata table in case they exist
+ // because we only want to do one insert.
+ if (!empty($results)) {
+ db_delete('file_metadata')
+ ->condition('fid', array_keys($results), 'IN')
+ ->condition(db_or()
+ ->condition('name', 'width')
+ ->condition('name', 'height')
+ )
+ ->execute();
+ }
+
+ $values = array();
+ foreach ($results as $result) {
+ foreach (array('width', 'height') as $key) {
+ $values[] = array(
+ 'fid' => $result->fid,
+ 'name' => $key,
+ 'value' => serialize((int) $result->{$key}),
+ );
+ }
+ $sandbox['progress'] += count($results);
+ $sandbox['current_fid'] = $result->fid;
+ }
+
+ if (!empty($values)) {
+ $query = db_insert('file_metadata');
+ $query->fields(array('fid', 'name', 'value'));
+ foreach ($values as $value) {
+ $query->values($value);
+ }
+ $query->execute();
+ }
+
+ $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
+
+ if ($sandbox['#finished'] >= 1) {
+ db_drop_table('image_dimensions');
+ }
+}
+
+/**
+ * Update default alt text and title image field descriptions.
+ */
+function file_entity_update_7213() {
+ if ($title_text_instance = field_info_instance('file', 'field_file_image_title_text', 'image')) {
+ if ($title_text_instance['description'] == 'Title text attribute') {
+ $title_text_instance['description'] = t('Title text is used in the tool tip when a user hovers their mouse over the image. Adding title text makes it easier to understand the context of an image and improves usability.');
+ field_update_instance($title_text_instance);
+ }
+ }
+
+ if ($alt_text_instance = field_info_instance('file', 'field_file_image_alt_text', 'image')) {
+ if ($alt_text_instance['description'] == '') {
+ $alt_text_instance['description'] = t('Alternative text is used by screen readers, search engines, and when the image cannot be loaded. By adding alt text you improve accessibility and search engine optimization.');
+ field_update_instance($alt_text_instance);
+ }
+ }
+}
+
+/**
+ * Fix the default value in {file_managed}.type to match the field schema.
+ */
+function file_entity_update_7214() {
+ db_drop_index('file_managed', 'file_type');
+ db_change_field('file_managed', 'type', 'type', array(
+ 'description' => 'The type of this file.',
+ 'type' => 'varchar',
+ 'length' => 50,
+ 'not null' => TRUE,
+ 'default' => 'undefined',
+ ));
+ db_add_index('file_managed', 'file_type', array('type'));
+}
+
+/**
+ * Fix the {file_metadata}.fid schema.
+ */
+function file_entity_update_7215() {
+ // When changing a primary key serial field to an int, we need to add a
+ // temporary index to make this update work.
+ // @see https://drupal.org/node/190027
+ db_add_index('file_metadata', 'temp', array('fid'));
+ db_drop_primary_key('file_metadata');
+ db_change_field('file_metadata', 'fid', 'fid', array(
+ 'description' => 'The {file_managed}.fid of the metadata.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ));
+ db_add_primary_key('file_metadata', array('fid', 'name'));
+ db_drop_index('file_metadata', 'temp');
+}
+
+/**
+ * This update has been removed and will not run.
+ */
+function file_entity_update_7216() {
+ // This update function previously saved default file displays into the
+ // database. It has been removed due to reported problems and is better
+ // addressed by adding support for ctools default content to features.
+}
diff --git a/sites/all/modules/file_entity/file_entity.js b/sites/all/modules/file_entity/file_entity.js
new file mode 100644
index 000000000..e70eac51c
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.js
@@ -0,0 +1,16 @@
+(function ($) {
+
+Drupal.behaviors.fileFieldsetSummaries = {
+ attach: function (context) {
+ $('fieldset.file-form-destination', context).drupalSetSummary(function (context) {
+ var scheme = $('.form-item-scheme input:checked', context).parent().text();
+ return Drupal.t('Destination: @scheme', { '@scheme': scheme });
+ });
+ $('fieldset.file-form-user', context).drupalSetSummary(function (context) {
+ var name = $('.form-item-name input', context).val() || Drupal.settings.anonymous;
+ return Drupal.t('Associated with @name', { '@name': name });
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/file_entity/file_entity.module b/sites/all/modules/file_entity/file_entity.module
new file mode 100644
index 000000000..f00dacc95
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.module
@@ -0,0 +1,2574 @@
+<?php
+
+/**
+ * @file
+ * Extends Drupal file entities to be fieldable and viewable.
+ */
+
+/**
+ * Modules should return this value from hook_file_entity_access() to allow
+ * access to a file.
+ */
+define('FILE_ENTITY_ACCESS_ALLOW', 'allow');
+
+/**
+ * Modules should return this value from hook_file_entity_access() to deny
+ * access to a file.
+ */
+define('FILE_ENTITY_ACCESS_DENY', 'deny');
+
+/**
+ * Modules should return this value from hook_file_entity_access() to not affect
+ * file access.
+ */
+define('FILE_ENTITY_ACCESS_IGNORE', NULL);
+
+/**
+ * As part of extending Drupal core's file entity API, this module adds some
+ * functions to the 'file' namespace. For organization, those are kept in the
+ * 'file_entity.file_api.inc' file.
+ */
+require_once dirname(__FILE__) . '/file_entity.file_api.inc';
+
+// @todo Remove when http://drupal.org/node/977052 is fixed.
+require_once dirname(__FILE__) . '/file_entity.field.inc';
+
+/**
+ * Implements hook_hook_info().
+ */
+function file_entity_hook_info() {
+ $hooks = array(
+ 'file_operations',
+ 'file_type_info',
+ 'file_type_info_alter',
+ 'file_formatter_info',
+ 'file_formatter_info_alter',
+ 'file_view',
+ 'file_view_alter',
+ 'file_displays_alter',
+ 'file_type',
+ 'file_type_alter',
+ 'file_download_headers_alter',
+ 'file_entity_access',
+ );
+
+ return array_fill_keys($hooks, array('group' => 'file'));
+}
+
+/**
+ * Implements hook_hook_info_alter().
+ *
+ * Add support for existing core hooks to be located in modulename.file.inc.
+ */
+function file_entity_hook_info_alter(&$info) {
+ $hooks = array(
+ // File API hooks
+ 'file_copy',
+ 'file_move',
+ 'file_validate',
+ // File access
+ 'file_download',
+ 'file_download_access',
+ 'file_download_access_alter',
+ // File entity hooks
+ 'file_load',
+ 'file_presave',
+ 'file_insert',
+ 'file_update',
+ 'file_delete',
+ // Miscellaneous hooks
+ 'file_mimetype_mapping_alter',
+ 'file_url_alter',
+ // Stream wrappers
+ 'stream_wrappers',
+ 'stream_wrappers_alter',
+ );
+ $info += array_fill_keys($hooks, array('group' => 'file'));
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function file_entity_module_implements_alter(&$implementations, $hook) {
+ // nginx_accel_redirect_file_transfer() is an accidental hook implementation.
+ // @see https://www.drupal.org/node/2278625
+ if ($hook == 'file_transfer') {
+ unset($implementations['nginx_accel_redirect']);
+ }
+}
+
+/**
+ * Implements hook_help().
+ */
+function file_entity_help($path, $arg) {
+ switch ($path) {
+ case 'admin/structure/file-types':
+ $output = '<p>' . t('When a file is uploaded to this website, it is assigned one of the following types, based on what kind of file it is.') . '</p>';
+ return $output;
+ case 'admin/structure/file-types/manage/%/display/preview':
+ case 'admin/structure/file-types/manage/%/file-display/preview':
+ drupal_set_message(t('Some modules rely on the Preview view mode to function correctly. Changing these settings may break parts of your site.'), 'warning');
+ break;
+ }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function file_entity_menu() {
+ // File Configuration
+ // @todo Move this back to admin/config/media/file-types in Drupal 8 if
+ // MENU_MAX_DEPTH is increased to a value higher than 9.
+ $items['admin/structure/file-types'] = array(
+ 'title' => 'File types',
+ 'description' => 'Manage settings for the type of files used on your site.',
+ 'page callback' => 'file_entity_list_types_page',
+ 'access arguments' => array('administer file types'),
+ 'file' => 'file_entity.admin.inc',
+ );
+ $items['admin/structure/file-types/add'] = array(
+ 'title' => 'Add file type',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_file_type_form'),
+ 'access arguments' => array('administer file types'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'file_entity.admin.inc',
+ );
+ $items['admin/structure/file-types/manage/%file_type'] = array(
+ 'title' => 'Manage file types',
+ 'description' => 'Manage settings for the type of files used on your site.',
+ );
+ $items['admin/structure/file-types/manage/%file_type/enable'] = array(
+ 'title' => 'Enable',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_type_enable_confirm', 4),
+ 'access arguments' => array('administer file types'),
+ 'file' => 'file_entity.admin.inc',
+ 'type' => MENU_CALLBACK,
+ );
+ $items['admin/structure/file-types/manage/%file_type/disable'] = array(
+ 'title' => 'Disable',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_type_disable_confirm', 4),
+ 'access arguments' => array('administer file types'),
+ 'file' => 'file_entity.admin.inc',
+ 'type' => MENU_CALLBACK,
+ );
+ $items['admin/structure/file-types/manage/%file_type/revert'] = array(
+ 'title' => 'Revert',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_type_revert_confirm', 4),
+ 'access arguments' => array('administer file types'),
+ 'file' => 'file_entity.admin.inc',
+ 'type' => MENU_CALLBACK,
+ );
+ $items['admin/structure/file-types/manage/%file_type/delete'] = array(
+ 'title' => 'Delete',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_type_delete_confirm', 4),
+ 'access arguments' => array('administer file types'),
+ 'file' => 'file_entity.admin.inc',
+ 'type' => MENU_CALLBACK,
+ );
+
+ $items['admin/content/file'] = array(
+ 'title' => 'Files',
+ 'description' => 'Manage files used on your site.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_admin_file'),
+ 'access arguments' => array('administer files'),
+ 'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
+ 'file' => 'file_entity.admin.inc',
+ );
+ $items['admin/content/file/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+
+ // general view, edit, delete for files
+ $items['file/add'] = array(
+ 'title' => 'Add file',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_add_upload', array()),
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('create'),
+ 'file' => 'file_entity.pages.inc',
+ );
+ if (module_exists('plupload') && module_exists('multiform')) {
+ $items['file/add']['page arguments'] = array('file_entity_add_upload_multiple');
+ }
+ $items['file/add/upload'] = array(
+ 'title' => 'Upload',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['file/add/upload/file'] = array(
+ 'title' => 'File',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['file/add/upload/archive'] = array(
+ 'title' => 'Archive',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_upload_archive_form'),
+ 'access arguments' => array('administer files'),
+ 'file' => 'file_entity.pages.inc',
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => -5,
+ );
+ $items['file/%file'] = array(
+ 'title callback' => 'entity_label',
+ 'title arguments' => array('file', 1),
+ // The page callback also invokes drupal_set_title() in case
+ // the menu router's title is overridden by a menu link.
+ 'page callback' => 'file_entity_view_page',
+ 'page arguments' => array(1),
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('view', 1),
+ 'file' => 'file_entity.pages.inc',
+ );
+ $items['file/%file/view'] = array(
+ 'title' => 'View',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['file/%file/usage'] = array(
+ 'title' => 'Usage',
+ 'page callback' => 'file_entity_usage_page',
+ 'page arguments' => array(1),
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('update', 1),
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE,
+ 'file' => 'file_entity.pages.inc',
+ );
+ $items['file/%file/download'] = array(
+ 'title' => 'Download',
+ 'page callback' => 'file_entity_download_page',
+ 'page arguments' => array(1),
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('download', 1),
+ 'file' => 'file_entity.pages.inc',
+ 'type' => MENU_CALLBACK,
+ );
+ $items['file/%file/edit'] = array(
+ 'title' => 'Edit',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_edit', 1),
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('update', 1),
+ 'weight' => 0,
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ 'file' => 'file_entity.pages.inc',
+ );
+ $items['file/%file/delete'] = array(
+ 'title' => 'Delete',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_delete_form', 1),
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('delete', 1),
+ 'weight' => 1,
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ 'file' => 'file_entity.pages.inc',
+ );
+
+ // Attach a "Manage file display" tab to each file type in the same way that
+ // Field UI attaches "Manage fields" and "Manage display" tabs. Note that
+ // Field UI does not have to be enabled; we're just using the same IA pattern
+ // here for attaching the "Manage file display" page.
+ $entity_info = entity_get_info('file');
+ foreach ($entity_info['bundles'] as $file_type => $bundle_info) {
+ if (isset($bundle_info['admin'])) {
+ // Get the base path and access.
+ $path = $bundle_info['admin']['path'];
+ $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
+ $access += array(
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer file types'),
+ );
+
+ // The file type must be passed to the page callbacks. It might be
+ // configured as a wildcard (multiple file types sharing the same menu
+ // router path).
+ $file_type_argument = isset($bundle_info['admin']['bundle argument']) ? $bundle_info['admin']['bundle argument'] : $file_type;
+
+ $items[$path] = array(
+ 'title' => 'Edit file type',
+ 'title callback' => 'file_entity_type_get_name',
+ 'title arguments' => array(4),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_file_type_form', $file_type_argument),
+ 'file' => 'file_entity.admin.inc',
+ ) + $access;
+
+ // Add the 'File type settings' tab.
+ $items["$path/edit"] = array(
+ 'title' => 'Edit',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+
+ // Add the 'Manage file display' tab.
+ $items["$path/file-display"] = array(
+ 'title' => 'Manage file display',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_file_display_form', $file_type_argument, 'default'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 3,
+ 'file' => 'file_entity.admin.inc',
+ ) + $access;
+
+ // Add a secondary tab for each view mode.
+ $weight = 0;
+ $view_modes = array('default' => array('label' => t('Default'))) + $entity_info['view modes'];
+ foreach ($view_modes as $view_mode => $view_mode_info) {
+ $items["$path/file-display/$view_mode"] = array(
+ 'title' => $view_mode_info['label'],
+ 'page arguments' => array('file_entity_file_display_form', $file_type_argument, $view_mode),
+ 'type' => ($view_mode == 'default' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK),
+ 'weight' => ($view_mode == 'default' ? -10 : $weight++),
+ 'file' => 'file_entity.admin.inc',
+ // View modes for which the 'custom settings' flag isn't TRUE are
+ // disabled via this access callback. This needs to extend, rather
+ // than override normal $access rules.
+ 'access callback' => '_file_entity_view_mode_menu_access',
+ 'access arguments' => array_merge(array($file_type_argument, $view_mode, $access['access callback']), $access['access arguments']),
+ );
+ }
+ }
+ }
+
+ $items['admin/config/media/file-settings'] = array(
+ 'title' => 'File settings',
+ 'description' => 'Configure allowed file extensions, default alt and title sources, and the file upload wizard.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_settings_form'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'file_entity.admin.inc',
+ );
+
+ // Optional devel module integration
+ if (module_exists('devel')) {
+ $items['file/%file/devel'] = array(
+ 'title' => 'Devel',
+ 'page callback' => 'devel_load_object',
+ 'page arguments' => array('file', 1),
+ 'access arguments' => array('access devel information'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'devel.pages.inc',
+ 'file path' => drupal_get_path('module', 'devel'),
+ 'weight' => 100,
+ );
+ $items['file/%file/devel/load'] = array(
+ 'title' => 'Load',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+ $items['file/%file/devel/render'] = array(
+ 'title' => 'Render',
+ 'page callback' => 'devel_render_object',
+ 'page arguments' => array('file', 1),
+ 'access arguments' => array('access devel information'),
+ 'file' => 'devel.pages.inc',
+ 'file path' => drupal_get_path('module', 'devel'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 100,
+ );
+ if (module_exists('token')) {
+ $items['file/%file/devel/token'] = array(
+ 'title' => 'Tokens',
+ 'page callback' => 'token_devel_token_object',
+ 'page arguments' => array('file', 1),
+ 'access arguments' => array('access devel information'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'token.pages.inc',
+ 'file path' => drupal_get_path('module', 'token'),
+ 'weight' => 5,
+ );
+ }
+ }
+
+ return $items;
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ */
+function file_entity_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ // Add action link to 'file/add' on 'admin/content/file' page.
+ if ($root_path == 'admin/content/file') {
+ $item = menu_get_item('file/add');
+ if (!empty($item['access'])) {
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $item,
+ '#weight' => $item['weight'],
+ );
+ }
+ }
+}
+
+/**
+ * Implement hook_permission().
+ */
+function file_entity_permission() {
+ $permissions = array(
+ 'bypass file access' => array(
+ 'title' => t('Bypass file access control'),
+ 'description' => t('View, edit and delete all files regardless of permission restrictions.'),
+ 'restrict access' => TRUE,
+ ),
+ 'administer file types' => array(
+ 'title' => t('Administer file types'),
+ 'restrict access' => TRUE,
+ ),
+ 'administer files' => array(
+ 'title' => t('Administer files'),
+ 'restrict access' => TRUE,
+ ),
+ 'create files' => array(
+ 'title' => t('Add and upload new files'),
+ ),
+ 'view own private files' => array(
+ 'title' => t('View own private files'),
+ ),
+ 'view own files' => array(
+ 'title' => t('View own files'),
+ ),
+ 'view private files' => array(
+ 'title' => t('View private files'),
+ 'restrict access' => TRUE,
+ ),
+ 'view files' => array(
+ 'title' => t('View files'),
+ ),
+ );
+
+ // Add description for the 'View file details' and 'View own private file
+ // details' permissions to show which stream wrappers they apply to.
+ $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
+ $wrappers += array('public' => array(t('None')), 'private' => array(t('None')));
+
+ $permissions['view files']['description'] = t('Includes the following stream wrappers: %wrappers.', array('%wrappers' => implode(', ', $wrappers['public'])));
+ $permissions['view own private files']['description'] = t('Includes the following stream wrappers: %wrappers.', array('%wrappers' => implode(', ', $wrappers['private'])));
+
+ // Generate standard file permissions for all applicable file types.
+ foreach (file_entity_permissions_get_configured_types() as $type) {
+ $permissions += file_entity_list_permissions($type);
+ }
+
+ return $permissions;
+}
+
+/*
+ * Implements hook_cron_queue_info().
+ */
+function file_entity_cron_queue_info() {
+ $queues['file_entity_type_determine'] = array(
+ 'worker callback' => 'file_entity_type_determine',
+ );
+ return $queues;
+}
+
+/*
+ * Determines file type for a given file ID and saves the file.
+ *
+ * @param $fid
+ * A file ID.
+ */
+function file_entity_type_determine($fid) {
+ if ($file = file_load($fid)) {
+ // The file type will be automatically determined when saving the file.
+ file_save($file);
+ }
+}
+
+/**
+ * Gather the rankings from the the hook_ranking implementations.
+ *
+ * @param $query
+ * A query object that has been extended with the Search DB Extender.
+ */
+function _file_entity_rankings(SelectQueryExtender $query) {
+ if ($ranking = module_invoke_all('file_ranking')) {
+ $tables = &$query->getTables();
+ foreach ($ranking as $rank => $values) {
+ if ($file_rank = variable_get('file_entity_rank_' . $rank, 0)) {
+ // If the table defined in the ranking isn't already joined, then add it.
+ if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
+ $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
+ }
+ $arguments = isset($values['arguments']) ? $values['arguments'] : array();
+ $query->addScore($values['score'], $arguments, $file_rank);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_search_info().
+ */
+function file_entity_search_info() {
+ return array(
+ 'title' => 'Files',
+ 'path' => 'file',
+ );
+}
+
+/**
+ * Implements hook_search_access().
+ */
+function file_entity_search_access() {
+ return user_access('view own private files') || user_access('view own files') || user_access('view private files') || user_access('view files');
+}
+
+/**
+ * Implements hook_search_reset().
+ */
+function file_entity_search_reset() {
+ db_update('search_dataset')
+ ->fields(array('reindex' => REQUEST_TIME))
+ ->condition('type', 'file')
+ ->execute();
+}
+
+/**
+ * Implements hook_search_status().
+ */
+function file_entity_search_status() {
+ $total = db_query('SELECT COUNT(*) FROM {file_managed}')->fetchField();
+ $remaining = db_query("SELECT COUNT(*) FROM {file_managed} fm LEFT JOIN {search_dataset} d ON d.type = 'file' AND d.sid = fm.fid WHERE d.sid IS NULL OR d.reindex <> 0")->fetchField();
+ return array('remaining' => $remaining, 'total' => $total);
+}
+
+/**
+ * Implements hook_search_admin().
+ */
+function file_entity_search_admin() {
+ // Output form for defining rank factor weights.
+ $form['file_ranking'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('File ranking'),
+ );
+ $form['file_ranking']['#theme'] = 'file_entity_search_admin';
+ $form['file_ranking']['info'] = array(
+ '#value' => '<em>' . t('The following numbers control which properties the file search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
+ );
+
+ // Note: reversed to reflect that higher number = higher ranking.
+ $options = drupal_map_assoc(range(0, 10));
+ foreach (module_invoke_all('file_ranking') as $var => $values) {
+ $form['file_ranking']['factors']['file_entity_rank_' . $var] = array(
+ '#title' => $values['title'],
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => variable_get('file_entity_rank_' . $var, 0),
+ );
+ }
+ return $form;
+}
+
+/**
+ * Implements hook_search_execute().
+ */
+function file_entity_search_execute($keys = NULL, $conditions = NULL) {
+ global $user;
+
+ // Build matching conditions
+ $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
+ $query->join('file_managed', 'fm', 'fm.fid = i.sid');
+ $query->searchExpression($keys, 'file');
+
+ // Insert special keywords.
+ $query->setOption('type', 'fm.type');
+ if ($query->setOption('term', 'ti.tid')) {
+ $query->join('taxonomy_index', 'ti', 'fm.fid = ti.fid');
+ }
+ // Only continue if the first pass query matches.
+ if (!$query->executeFirstPass()) {
+ return array();
+ }
+
+ // Add the ranking expressions.
+ _file_entity_rankings($query);
+
+ // Load results.
+ $find = $query
+ ->limit(10)
+ ->addTag('file_access')
+ ->execute();
+ $results = array();
+ foreach ($find as $item) {
+ // Render the file.
+ $file = file_load($item->sid);
+ $build = file_view($file, 'search_result');
+ unset($build['#theme']);
+ $file->rendered = drupal_render($build);
+
+ $extra = module_invoke_all('file_entity_search_result', $file);
+
+ $types = file_entity_type_get_names();
+
+ $uri = entity_uri('file', $file);
+ $results[] = array(
+ 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
+ 'type' => check_plain($types[$file->type]),
+ 'title' => $file->filename,
+ 'user' => theme('username', array('account' => user_load($file->uid))),
+ 'date' => $file->timestamp,
+ 'file' => $file,
+ 'extra' => $extra,
+ 'score' => $item->calculated_score,
+ 'snippet' => search_excerpt($keys, $file->rendered),
+ 'language' => function_exists('entity_language') ? entity_language('file', $file) : NULL,
+ );
+ }
+ return $results;
+}
+
+/**
+ * Implements hook_file_ranking().
+ */
+function file_entity_file_ranking() {
+ // Create the ranking array and add the basic ranking options.
+ $ranking = array(
+ 'relevance' => array(
+ 'title' => t('Keyword relevance'),
+ // Average relevance values hover around 0.15
+ 'score' => 'i.relevance',
+ ),
+ );
+
+ // Add relevance based on creation date.
+ if ($file_cron_last = variable_get('file_entity_cron_last', 0)) {
+ $ranking['timestamp'] = array(
+ 'title' => t('Recently posted'),
+ // Exponential decay with half-life of 6 months, starting at last indexed file
+ 'score' => 'POW(2.0, (fm.timestamp - :file_cron_last) * 6.43e-8)',
+ 'arguments' => array(':file_cron_last' => $file_cron_last),
+ );
+ }
+ return $ranking;
+}
+
+/**
+ * Returns HTML for the file ranking part of the search settings admin page.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_file_entity_search_admin($variables) {
+ $form = $variables['form'];
+
+ $output = drupal_render($form['info']);
+
+ $header = array(t('Factor'), t('Weight'));
+ foreach (element_children($form['factors']) as $key) {
+ $row = array();
+ $row[] = $form['factors'][$key]['#title'];
+ $form['factors'][$key]['#title_display'] = 'invisible';
+ $row[] = drupal_render($form['factors'][$key]);
+ $rows[] = $row;
+ }
+ $output .= theme('table', array('header' => $header, 'rows' => $rows));
+
+ $output .= drupal_render_children($form);
+ return $output;
+}
+
+/**
+ * Implements hook_update_index().
+ */
+function file_entity_update_index() {
+ $limit = (int)variable_get('search_cron_limit', 100);
+
+ $result = db_query_range("SELECT fm.fid FROM {file_managed} fm LEFT JOIN {search_dataset} d ON d.type = 'file' AND d.sid = fm.fid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, fm.fid ASC", 0, $limit, array(), array('target' => 'slave'));
+
+ foreach ($result as $file) {
+ _file_entity_index_file($file);
+ }
+}
+
+/**
+ * Index a single file.
+ *
+ * @param $file
+ * The file to index.
+ */
+function _file_entity_index_file($file) {
+ $file = file_load($file->fid);
+
+ // Save the creation time of the most recent indexed file, for the search
+ // results half-life calculation.
+ variable_set('file_entity_cron_last', $file->timestamp);
+
+ // Render the file.
+ $build = file_view($file, 'search_index');
+ unset($build['#theme']);
+ $file->rendered = drupal_render($build);
+
+ $text = '<h1>' . check_plain($file->filename) . '</h1>' . $file->rendered;
+
+ // Fetch extra data normally not visible
+ $extra = module_invoke_all('file_entity_update_index', $file);
+ foreach ($extra as $t) {
+ $text .= $t;
+ }
+
+ // Update index
+ search_index($file->fid, 'file', $text);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function file_entity_form_search_form_alter(&$form, $form_state) {
+ if (isset($form['module']) && $form['module']['#value'] == 'file_entity' && user_access('use advanced search')) {
+ // Keyword boxes:
+ $form['advanced'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Advanced search'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#attributes' => array('class' => array('search-advanced')),
+ );
+ $form['advanced']['keywords'] = array(
+ '#prefix' => '<div class="criterion">',
+ '#suffix' => '</div>',
+ );
+ $form['advanced']['keywords']['or'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing any of the words'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+ $form['advanced']['keywords']['phrase'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing the phrase'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+ $form['advanced']['keywords']['negative'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing none of the words'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+
+ // File types:
+ $types = array_map('check_plain', file_entity_type_get_names());
+ $form['advanced']['type'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Only of the type(s)'),
+ '#prefix' => '<div class="criterion">',
+ '#suffix' => '</div>',
+ '#options' => $types,
+ );
+ $form['advanced']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Advanced search'),
+ '#prefix' => '<div class="action">',
+ '#suffix' => '</div>',
+ '#weight' => 100,
+ );
+
+ $form['#validate'][] = 'file_entity_search_validate';
+ }
+}
+
+/**
+ * Form API callback for the search form. Registered in file_entity_form_alter().
+ */
+function file_entity_search_validate($form, &$form_state) {
+ // Initialize using any existing basic search keywords.
+ $keys = $form_state['values']['processed_keys'];
+
+ // Insert extra restrictions into the search keywords string.
+ if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
+ // Retrieve selected types - Form API sets the value of unselected
+ // checkboxes to 0.
+ $form_state['values']['type'] = array_filter($form_state['values']['type']);
+ if (count($form_state['values']['type'])) {
+ $keys = search_expression_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
+ }
+ }
+
+ if (isset($form_state['values']['term']) && is_array($form_state['values']['term']) && count($form_state['values']['term'])) {
+ $keys = search_expression_insert($keys, 'term', implode(',', $form_state['values']['term']));
+ }
+ if ($form_state['values']['or'] != '') {
+ if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
+ $keys .= ' ' . implode(' OR ', $matches[1]);
+ }
+ }
+ if ($form_state['values']['negative'] != '') {
+ if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
+ $keys .= ' -' . implode(' -', $matches[1]);
+ }
+ }
+ if ($form_state['values']['phrase'] != '') {
+ $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
+ }
+ if (!empty($keys)) {
+ form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
+ }
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function file_entity_admin_paths() {
+ $paths = array(
+ 'file/add' => TRUE,
+ 'file/add/*' => TRUE,
+ 'file/*/edit' => TRUE,
+ 'file/*/usage' => TRUE,
+ 'file/*/delete' => TRUE,
+ );
+ return $paths;
+}
+
+/**
+ * Implements hook_action_info_alter().
+ */
+function file_entity_action_info_alter(&$actions) {
+ if (module_exists('pathauto')) {
+ $actions['pathauto_file_update_action'] = array(
+ 'type' => 'file',
+ 'label' => t('Update file alias'),
+ 'configurable' => FALSE,
+ );
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function file_entity_theme() {
+ return array(
+ 'file_entity' => array(
+ 'render element' => 'elements',
+ 'template' => 'file_entity',
+ ),
+ 'file_entity_search_admin' => array(
+ 'render element' => 'form',
+ ),
+ 'file_entity_file_type_overview' => array(
+ 'variables' => array('label' => NULL, 'description' => NULL),
+ 'file' => 'file_entity.admin.inc',
+ ),
+ 'file_entity_file_display_order' => array(
+ 'render element' => 'element',
+ 'file' => 'file_entity.admin.inc',
+ ),
+ 'file_entity_file_link' => array(
+ 'variables' => array('file' => NULL, 'icon_directory' => NULL),
+ 'file' => 'file_entity.theme.inc',
+ ),
+ 'file_entity_download_link' => array(
+ 'variables' => array('file' => NULL, 'icon_directory' => NULL, 'text' => NULL),
+ 'file' => 'file_entity.theme.inc',
+ ),
+ 'file_entity_file_audio' => array(
+ 'variables' => array(
+ 'files' => array(),
+ 'controls' => TRUE,
+ 'autoplay' => FALSE,
+ 'loop' => FALSE,
+ 'preload' => NULL,
+ ),
+ 'file' => 'file_entity.theme.inc',
+ ),
+ 'file_entity_file_video' => array(
+ 'variables' => array(
+ 'files' => array(),
+ 'controls' => TRUE,
+ 'autoplay' => FALSE,
+ 'loop' => FALSE,
+ 'muted' => FALSE,
+ 'width' => NULL,
+ 'height' => NULL,
+ 'preload' => NULL,
+ ),
+ 'file' => 'file_entity.theme.inc',
+ ),
+ );
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * Extends the core file entity to be fieldable. The file type is used as the
+ * bundle key. File types are implemented as CTools exportables, so modules can
+ * define default file types via hook_file_default_types(), and the
+ * administrator can override the default types or add custom ones via
+ * admin/structure/file-types.
+ */
+function file_entity_entity_info_alter(&$entity_info) {
+ $entity_info['file']['fieldable'] = TRUE;
+ $entity_info['file']['entity keys']['bundle'] = 'type';
+ $entity_info['file']['bundle keys']['bundle'] = 'type';
+ $entity_info['file']['bundles'] = array();
+ $entity_info['file']['uri callback'] = 'file_entity_uri';
+ $entity_info['file']['view modes']['teaser'] = array(
+ 'label' => t('Teaser'),
+ 'custom settings' => TRUE,
+ );
+ $entity_info['file']['view modes']['full'] = array(
+ 'label' => t('Full content'),
+ 'custom settings' => FALSE,
+ );
+ $entity_info['file']['view modes']['preview'] = array(
+ 'label' => t('Preview'),
+ 'custom settings' => TRUE,
+ );
+ $entity_info['file']['view modes']['rss'] = array(
+ 'label' => t('RSS'),
+ 'custom settings' => FALSE,
+ );
+
+ // Search integration is provided by file_entity.module, so search-related
+ // view modes for files are defined here and not in search.module.
+ if (module_exists('search')) {
+ $entity_info['file']['view modes']['search_index'] = array(
+ 'label' => t('Search index'),
+ 'custom settings' => FALSE,
+ );
+ $entity_info['file']['view modes']['search_result'] = array(
+ 'label' => t('Search result'),
+ 'custom settings' => FALSE,
+ );
+ }
+
+ foreach (file_type_get_enabled_types() as $type) {
+ $entity_info['file']['bundles'][$type->type] = array(
+ 'label' => $type->label,
+ 'admin' => array(
+ 'path' => 'admin/structure/file-types/manage/%file_type',
+ 'real path' => 'admin/structure/file-types/manage/' . $type->type,
+ 'bundle argument' => 4,
+ ),
+ );
+ }
+
+ // Enable Metatag support.
+ $entity_info['file']['metatags'] = TRUE;
+
+ // Ensure some of the Entity API callbacks are supported.
+ $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
+ $entity_info['file']['view callback'] = 'file_entity_metadata_view_file';
+ $entity_info['file']['form callback'] = 'file_entity_metadata_form_file';
+ $entity_info['file']['access callback'] = 'file_entity_access';
+
+ // Add integration with the Title module for file name replacement support.
+ $entity_info['file']['field replacement'] = array(
+ 'filename' => array(
+ 'field' => array(
+ 'type' => 'text',
+ 'cardinality' => 1,
+ 'translatable' => TRUE,
+ ),
+ 'instance' => array(
+ 'label' => t('File name'),
+ 'description' => t('A field replacing file name.'),
+ 'required' => TRUE,
+ 'settings' => array(
+ 'text_processing' => 0,
+ ),
+ 'widget' => array(
+ 'weight' => -5,
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ ),
+ ),
+ ),
+ 'preprocess_key' => 'filename',
+ ),
+ );
+}
+
+/**
+ * Implements hook_entity_property_info().
+ */
+function file_entity_entity_property_info() {
+ $info['file']['properties']['type'] = array(
+ 'label' => t('File type'),
+ 'type' => 'token',
+ 'description' => t('The type of the file.'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'setter permission' => 'administer files',
+ 'options list' => 'file_entity_type_get_names',
+ 'required' => TRUE,
+ 'schema field' => 'type',
+ );
+
+ return $info;
+}
+
+/**
+ * Implements hook_field_display_ENTITY_TYPE_alter().
+ */
+function file_entity_field_display_file_alter(&$display, $context) {
+ // Hide field labels in search index.
+ if ($context['view_mode'] == 'search_index') {
+ $display['label'] = 'hidden';
+ }
+}
+
+/**
+ * URI callback for file entities.
+ */
+function file_entity_uri($file) {
+ $uri['path'] = 'file/' . $file->fid;
+ return $uri;
+}
+
+/**
+ * Entity API callback to view files.
+ */
+function file_entity_metadata_view_file($entities, $view_mode = 'full', $langcode = NULL) {
+ $result = file_view_multiple($entities, $view_mode, 0, $langcode);
+ // Make sure to key the result with 'file' instead of 'files'.
+ return array('file' => reset($result));
+}
+
+/**
+ * Entity API callback to get the form of a file entity.
+ */
+function file_entity_metadata_form_file($file) {
+ // Pre-populate the form-state with the right form include.
+ $form_state['build_info']['args'] = array($file);
+ form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
+ return drupal_build_form('file_entity_edit', $form_state);
+}
+
+/**
+ * Implements hook_ctools_plugin_directory().
+ */
+function file_entity_ctools_plugin_directory($module, $plugin) {
+ if (in_array($module, array('panelizer', 'ctools', 'page_manager'))) {
+ return 'plugins/' . $plugin;
+ }
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ *
+ * Adds 'file' as an extra field, so that its display and form component can be
+ * weighted relative to the fields that are added to file entity bundles.
+ */
+function file_entity_field_extra_fields() {
+ $info = array();
+
+ if ($file_type_names = file_entity_type_get_names()) {
+ foreach ($file_type_names as $type => $name) {
+ $info['file'][$type]['form']['filename'] = array(
+ 'label' => t('File name'),
+ 'description' => t('File name'),
+ 'weight' => -10,
+ );
+ $info['file'][$type]['form']['preview'] = array(
+ 'label' => t('File'),
+ 'description' => t('File preview'),
+ 'weight' => -5,
+ );
+ $info['file'][$type]['display']['file'] = array(
+ 'label' => t('File'),
+ 'description' => t('File display'),
+ 'weight' => 0,
+ );
+ }
+ }
+
+ return $info;
+}
+
+/**
+ * Implements hook_file_formatter_info().
+ */
+function file_entity_file_formatter_info() {
+ $formatters = array();
+
+ // Allow file field formatters to be reused for displaying the file entity's
+ // file pseudo-field.
+ foreach (field_info_formatter_types() as $key => $formatter) {
+ if (array_intersect($formatter['field types'], array('file', 'image'))) {
+ $key = 'file_field_' . $key;
+ $formatters[$key] = array(
+ 'label' => $formatter['label'],
+ 'description' => !empty($formatter['description']) ? $formatter['description'] : '',
+ 'view callback' => 'file_entity_file_formatter_file_field_view',
+ );
+ if (!empty($formatter['settings'])) {
+ $formatters[$key] += array(
+ 'default settings' => $formatter['settings'],
+ 'settings callback' => 'file_entity_file_formatter_file_field_settings',
+ );
+ }
+ if (!empty($formatter['file formatter'])) {
+ $formatters[$key] += $formatter['file formatter'];
+ }
+ }
+ }
+
+ // Add a simple file formatter for displaying an image in a chosen style.
+ if (module_exists('image')) {
+ $formatters['file_image'] = array(
+ 'label' => t('Image'),
+ 'default settings' => array(
+ 'image_style' => '',
+ 'alt' => '[file:field_file_image_alt_text]',
+ 'title' => '[file:field_file_image_title_text]'
+ ),
+ 'view callback' => 'file_entity_file_formatter_file_image_view',
+ 'settings callback' => 'file_entity_file_formatter_file_image_settings',
+ 'hidden' => TRUE,
+ 'mime types' => array('image/*'),
+ );
+ }
+
+ return $formatters;
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_view().
+ *
+ * This function provides a bridge to the field formatter API, so that file
+ * field formatters can be reused for displaying the file entity's file
+ * pseudo-field.
+ */
+function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
+ if (strpos($display['type'], 'file_field_') === 0) {
+ $field_formatter_type = substr($display['type'], strlen('file_field_'));
+ $field_formatter_info = field_info_formatter_types($field_formatter_type);
+ if (isset($field_formatter_info['module'])) {
+ // Set $display['type'] to what hook_field_formatter_*() expects.
+ $display['type'] = $field_formatter_type;
+
+ // Allow any attribute overrides (e.g. from the Media module) to be
+ // respected.
+ $item = (array) $file;
+ if (!empty($file->override['attributes'])) {
+ $item = array_merge($item, $file->override['attributes']);
+ }
+
+ // Set $items to what file field formatters expect. See file_field_load(),
+ // and note that, here, $file is already a fully loaded entity.
+ $items = array($item);
+
+ // Invoke hook_field_formatter_prepare_view() and
+ // hook_field_formatter_view(). Note that we are reusing field formatter
+ // functions, but we are not displaying a Field API field, so we set
+ // $field and $instance accordingly, and do not invoke
+ // hook_field_prepare_view(). This assumes that the formatter functions do
+ // not rely on $field or $instance. A module that implements formatter
+ // functions that rely on $field or $instance (and therefore, can only be
+ // used for real fields) can prevent this formatter from being used on the
+ // pseudo-field by removing it within hook_file_formatter_info_alter().
+ $field = $instance = NULL;
+ if (($function = ($field_formatter_info['module'] . '_field_formatter_prepare_view')) && function_exists($function)) {
+ $fid = $file->fid;
+ // hook_field_formatter_prepare_view() alters $items by reference.
+ $grouped_items = array($fid => &$items);
+ $function('file', array($fid => $file), $field, array($fid => $instance), $langcode, $grouped_items, array($fid => $display));
+ }
+ if (($function = ($field_formatter_info['module'] . '_field_formatter_view')) && function_exists($function)) {
+ $element = $function('file', $file, $field, $instance, $langcode, $items, $display);
+ // We passed the file as $items[0], so return the corresponding element.
+ if (isset($element[0])) {
+ return $element[0];
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_settings().
+ *
+ * This function provides a bridge to the field formatter API, so that file
+ * field formatters can be reused for displaying the file entity's file
+ * pseudo-field.
+ */
+function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
+ if (strpos($formatter_type, 'file_field_') === 0) {
+ $field_formatter_type = substr($formatter_type, strlen('file_field_'));
+ $field_formatter_info = field_info_formatter_types($field_formatter_type);
+
+ // Invoke hook_field_formatter_settings_form(). We are reusing field
+ // formatter functions, but we are not working with a Field API field, so
+ // set $field accordingly. Unfortunately, the API is for $settings to be
+ // transfered via the $instance parameter, so we must mock it.
+ if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
+ $field = NULL;
+ $mock_instance = array(
+ 'display' => array(
+ $view_mode => array(
+ 'type' => $field_formatter_type,
+ 'settings' => $settings,
+ ),
+ ),
+ 'entity_type' => 'file',
+ 'bundle' => $file_type,
+ );
+ return $function($field, $mock_instance, $view_mode, $form, $form_state);
+ }
+ }
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_view().
+ *
+ * Returns a drupal_render() array to display an image of the chosen style.
+ *
+ * This formatter is only capable of displaying local images. If the passed in
+ * file is either not local or not an image, nothing is returned, so that
+ * file_view_file() can try another formatter.
+ */
+function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
+ // Prevent PHP notices when trying to read empty files.
+ // @see http://drupal.org/node/681042
+ if (!$file->filesize) {
+ return;
+ }
+
+ // Do not bother proceeding if this file does not have an image mime type.
+ if (file_entity_file_get_mimetype_type($file) != 'image') {
+ return;
+ }
+
+ if (file_entity_file_is_readable($file)) {
+ // We don't sanitize here.
+ // @see http://drupal.org/node/1553094#comment-6257382
+ // Theme function will take care of escaping.
+ if (!isset($file->metadata)) {
+ $file->metadata = array();
+ }
+ $file->metadata += array('width' => NULL, 'height' => NULL);
+ $replace_options = array(
+ 'clear' => TRUE,
+ 'sanitize' => FALSE,
+ );
+ if (!empty($display['settings']['image_style'])) {
+ $element = array(
+ '#theme' => 'image_style',
+ '#style_name' => $display['settings']['image_style'],
+ '#path' => $file->uri,
+ '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
+ '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
+ '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
+ '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
+ );
+ }
+ else {
+ $element = array(
+ '#theme' => 'image',
+ '#path' => $file->uri,
+ '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
+ '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
+ '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
+ '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
+ );
+ }
+ return $element;
+ }
+}
+
+/**
+ * Check if a file entity is readable or not.
+ *
+ * @param object $file
+ * A file entity object from file_load().
+ *
+ * @return boolean
+ * TRUE if the file is using a readable stream wrapper, or FALSE otherwise.
+ */
+function file_entity_file_is_readable($file) {
+ $scheme = file_uri_scheme($file->uri);
+ $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_READ);
+ return !empty($wrappers[$scheme]);
+}
+
+/**
+ * Implements hook_file_formatter_FORMATTER_settings().
+ *
+ * Returns form elements for configuring the 'file_image' formatter.
+ */
+function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
+ $element = array();
+ $element['image_style'] = array(
+ '#title' => t('Image style'),
+ '#type' => 'select',
+ '#options' => image_style_options(FALSE),
+ '#default_value' => $settings['image_style'],
+ '#empty_option' => t('None (original image)'),
+ );
+
+ // For image files we allow the alt attribute (required in HTML).
+ $element['alt'] = array(
+ '#title' => t('Alt attribute'),
+ '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
+ '#type' => 'textfield',
+ '#default_value' => $settings['alt'],
+ );
+
+ // Allow the setting of the title attribute.
+ $element['title'] = array(
+ '#title' => t('Title attribute'),
+ '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
+ '#type' => 'textfield',
+ '#default_value' => $settings['title'],
+ );
+
+ if (module_exists('token')) {
+ $element['alt']['#description'] .= t('This field supports tokens.');
+ $element['title']['#description'] .= t('This field supports tokens.');
+ $element['tokens'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => array('file'),
+ '#dialog' => TRUE,
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Menu access callback for the 'view mode file display settings' pages.
+ *
+ * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
+ * be enabled.
+ */
+function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
+ // Deny access if the view mode isn't configured to use custom display
+ // settings.
+ $view_mode_settings = field_view_mode_settings('file', $file_type->type);
+ $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
+ if (!$visibility) {
+ return FALSE;
+ }
+
+ // Otherwise, continue to an $access_callback check.
+ $args = array_slice(func_get_args(), 3);
+ $callback = empty($access_callback) ? 0 : trim($access_callback);
+ if (is_numeric($callback)) {
+ return (bool) $callback;
+ }
+ elseif (function_exists($access_callback)) {
+ return call_user_func_array($access_callback, $args);
+ }
+}
+
+/**
+ * Implements hook_modules_enabled().
+ */
+function file_entity_modules_enabled($modules) {
+ file_info_cache_clear();
+}
+
+/**
+ * Implements hook_modules_disabled().
+ */
+function file_entity_modules_disabled($modules) {
+ file_info_cache_clear();
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function file_entity_views_api() {
+ return array(
+ 'api' => 3,
+ );
+}
+
+/**
+ * Returns whether the current page is the full page view of the passed-in file.
+ *
+ * @param $file
+ * A file object.
+ */
+function file_entity_is_page($file) {
+ $page_file = menu_get_object('file', 1);
+ return (!empty($page_file) ? $page_file->fid == $file->fid : FALSE);
+}
+
+/**
+ * Process variables for file_entity.tpl.php
+ *
+ * The $variables array contains the following arguments:
+ * - $file
+ * - $view_mode
+ *
+ * @see file_entity.tpl.php
+ */
+function template_preprocess_file_entity(&$variables) {
+ $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
+ $variables['file'] = $variables['elements']['#file'];
+ $file = $variables['file'];
+
+ $variables['id'] = drupal_html_id('file-'. $file->fid);
+ $variables['date'] = format_date($file->timestamp);
+ $account = user_load($file->uid);
+ $variables['name'] = theme('username', array('account' => $account));
+
+ $uri = entity_uri('file', $file);
+ $variables['file_url'] = url($uri['path'], $uri['options']);
+ $label = entity_label('file', $file);
+ $variables['label'] = check_plain($label);
+ $variables['page'] = $view_mode == 'full' && file_entity_is_page($file);
+
+ // Hide the file name from being displayed until we can figure out a better
+ // way to control this. We cannot simply not output the title since
+ // contextual links require $title_suffix to be output in the template.
+ // @see http://drupal.org/node/1245266
+ if (!$variables['page']) {
+ $variables['title_attributes_array']['class'][] = 'element-invisible';
+ }
+
+ // Flatten the file object's member fields.
+ $variables = array_merge((array) $file, $variables);
+
+ // Helpful $content variable for templates.
+ $variables += array('content' => array());
+ foreach (element_children($variables['elements']) as $key) {
+ $variables['content'][$key] = $variables['elements'][$key];
+ }
+
+ // Make the field variables available with the appropriate language.
+ field_attach_preprocess('file', $file, $variables['content'], $variables);
+
+ // Attach the file object to the content element.
+ $variables['content']['file']['#file'] = $file;
+
+ // Display post information only on certain file types.
+ if (variable_get('file_submitted_' . $file->type, FALSE)) {
+ $variables['display_submitted'] = TRUE;
+ $variables['submitted'] = t('Uploaded by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
+ $variables['user_picture'] = theme_get_setting('toggle_file_user_picture') ? theme('user_picture', array('account' => $account)) : '';
+ }
+ else {
+ $variables['display_submitted'] = FALSE;
+ $variables['submitted'] = '';
+ $variables['user_picture'] = '';
+ }
+
+ // Gather file classes.
+ $variables['classes_array'][] = drupal_html_class('file-' . $file->type);
+ $variables['classes_array'][] = drupal_html_class('file-' . $file->filemime);
+ if ($file->status != FILE_STATUS_PERMANENT) {
+ $variables['classes_array'][] = 'file-temporary';
+ }
+
+ // Change the 'file-entity' class into 'file'
+ if ($variables['classes_array'][0] == 'file-entity') {
+ $variables['classes_array'][0] = 'file';
+ }
+
+ // Clean up name so there are no underscores.
+ $variables['theme_hook_suggestions'][] = 'file__' . $file->type;
+ $variables['theme_hook_suggestions'][] = 'file__' . $file->type . '__' . $view_mode;
+ $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime);
+ $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime) . '__' . $view_mode;
+ $variables['theme_hook_suggestions'][] = 'file__' . $file->fid;
+ $variables['theme_hook_suggestions'][] = 'file__' . $file->fid . '__' . $view_mode;
+}
+
+/**
+ * Returns the file type name of the passed file or file type string.
+ *
+ * @param $file
+ * A file object or string that indicates the file type to return.
+ *
+ * @return
+ * The file type name or FALSE if the file type is not found.
+ */
+function file_entity_type_get_name($file) {
+ $type = is_object($file) ? $file->type : $file;
+ $info = entity_get_info('file');
+ return isset($info['bundles'][$type]['label']) ? $info['bundles'][$type]['label'] : FALSE;
+}
+
+/**
+ * Returns a list of available file type names.
+ *
+ * @return
+ * An array of file type names, keyed by the type.
+ */
+function file_entity_type_get_names() {
+ $names = &drupal_static(__FUNCTION__);
+
+ if (!isset($names)) {
+ $names = array();
+ $info = entity_get_info('file');
+ foreach ($info['bundles'] as $bundle => $bundle_info) {
+ $names[$bundle] = $bundle_info['label'];
+ }
+ }
+
+ return $names;
+}
+
+/**
+ * Return an array of available view modes for file entities.
+ */
+function file_entity_view_mode_labels() {
+ $labels = &drupal_static(__FUNCTION__);
+
+ if (!isset($options)) {
+ $entity_info = entity_get_info('file');
+ $labels = array('default' => t('Default'));
+ foreach ($entity_info['view modes'] as $machine_name => $mode) {
+ $labels[$machine_name] = $mode['label'];
+ }
+ }
+
+ return $labels;
+}
+
+/**
+ * Return the label for a specific file entity view mode.
+ */
+function file_entity_view_mode_label($view_mode, $default = FALSE) {
+ $labels = file_entity_view_mode_labels();
+ return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
+}
+
+/**
+ * Helper function to get a list of hidden stream wrappers.
+ *
+ * This is used in several places to filter queries for media so that files in
+ * temporary:// don't show up.
+ */
+function file_entity_get_hidden_stream_wrappers() {
+ return array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE));
+}
+
+/**
+ * Return a specific stream wrapper's registry information.
+ *
+ * @param $scheme
+ * A URI scheme, a stream is referenced as "scheme://target".
+ *
+ * @see file_get_stream_wrappers()
+ */
+function file_entity_get_stream_wrapper($scheme) {
+ $wrappers = file_get_stream_wrappers();
+ return isset($wrappers[$scheme]) ? $wrappers[$scheme] : FALSE;
+}
+
+/**
+ * Implements hook_stream_wrappers_alter().
+ */
+function file_entity_stream_wrappers_alter(&$wrappers) {
+ if (isset($wrappers['private'])) {
+ $wrappers['private']['private'] = TRUE;
+ }
+ if (isset($wrappers['temporary'])) {
+ $wrappers['temporary']['private'] = TRUE;
+ }
+}
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function file_entity_ctools_plugin_api($module, $api) {
+ if (($module == 'file_entity' && $api == 'file_type') || ($module == 'page_manager' && $api == 'pages_default') || $module == 'panelizer') {
+ return array('version' => 1);
+ }
+ if ($module == 'file_entity' && $api == 'file_default_displays') {
+ return array('version' => 1);
+ }
+}
+
+/**
+ * @defgroup file_entity_access File access rights
+ * @{
+ * The file access system determines who can do what to which files.
+ *
+ * In determining access rights for a file, file_entity_access() first checks
+ * whether the user has the "bypass file access" permission. Such users have
+ * unrestricted access to all files. user 1 will always pass this check.
+ *
+ * Next, all implementations of hook_file_entity_access() will be called. Each
+ * implementation may explicitly allow, explicitly deny, or ignore the access
+ * request. If at least one module says to deny the request, it will be rejected.
+ * If no modules deny the request and at least one says to allow it, the request
+ * will be permitted.
+ *
+ * There is no access grant system for files.
+ *
+ * In file listings, the process above is followed except that
+ * hook_file_entity_access() is not called on each file for performance reasons
+ * and for proper functioning of the pager system. When adding a filelisting to
+ * your module, be sure to use a dynamic query created by db_select()
+ * and add a tag of "file_entity_access". This will allow modules dealing
+ * with file access to ensure only files to which the user has access
+ * are retrieved, through the use of hook_query_TAG_alter().
+ *
+ * Note: Even a single module returning FILE_ENTITY_ACCESS_DENY from
+ * hook_file_entity_access() will block access to the file. Therefore,
+ * implementers should take care to not deny access unless they really intend to.
+ * Unless a module wishes to actively deny access it should return
+ * FILE_ENTITY_ACCESS_IGNORE (or simply return nothing)
+ * to allow other modules to control access.
+ *
+ * Stream wrappers that are considered private should implement a 'private'
+ * flag equal to TRUE in hook_stream_wrappers().
+ */
+
+/**
+ * Determine if a user may perform the given operation on the specified file.
+ *
+ * @param $op
+ * The operation to be performed on the file. Possible values are:
+ * - "view"
+ * - "download"
+ * - "update"
+ * - "delete"
+ * - "create"
+ * @param $file
+ * The file object on which the operation is to be performed, or file type
+ * (e.g. 'image') for "create" operation.
+ * @param $account
+ * Optional, a user object representing the user for whom the operation is to
+ * be performed. Determines access for a user other than the current user.
+ *
+ * @return
+ * TRUE if the operation may be performed, FALSE otherwise.
+ */
+function file_entity_access($op, $file = NULL, $account = NULL) {
+ $rights = &drupal_static(__FUNCTION__, array());
+
+ if (!$file && !in_array($op, array('view', 'download', 'update', 'delete', 'create'), TRUE)) {
+ // If there was no file to check against, and the $op was not one of the
+ // supported ones, we return access denied.
+ return FALSE;
+ }
+
+ // If no user object is supplied, the access check is for the current user.
+ if (empty($account)) {
+ $account = $GLOBALS['user'];
+ }
+
+ // $file may be either an object or a file type. Since file types cannot be
+ // an integer, use either fid or type as the static cache id.
+ $cache_id = is_object($file) ? $file->fid : $file;
+
+ // If we've already checked access for this file, user and op, return from
+ // cache.
+ if (isset($rights[$account->uid][$cache_id][$op])) {
+ return $rights[$account->uid][$cache_id][$op];
+ }
+
+ if (user_access('bypass file access', $account)) {
+ return $rights[$account->uid][$cache_id][$op] = TRUE;
+ }
+
+ // We grant access to the file if both of the following conditions are met:
+ // - No modules say to deny access.
+ // - At least one module says to grant access.
+ $access = module_invoke_all('file_entity_access', $op, $file, $account);
+ if (in_array(FILE_ENTITY_ACCESS_DENY, $access, TRUE)) {
+ return $rights[$account->uid][$cache_id][$op] = FALSE;
+ }
+ elseif (in_array(FILE_ENTITY_ACCESS_ALLOW, $access, TRUE)) {
+ return $rights[$account->uid][$cache_id][$op] = TRUE;
+ }
+
+
+ // Fall back to default behaviors on view.
+ if ($op == 'view' && is_object($file)) {
+ $scheme = file_uri_scheme($file->uri);
+ $wrapper = file_entity_get_stream_wrapper($scheme);
+
+ if (!empty($wrapper['private'])) {
+ // For private files, users can view private files if the
+ // user has the 'view private files' permission.
+ if (user_access('view private files', $account)) {
+ return $rights[$account->uid][$cache_id][$op] = TRUE;
+ }
+
+ // For private files, users can view their own private files if the
+ // user is not anonymous, and has the 'view own private files' permission.
+ if (!empty($account->uid) && $file->uid == $account->uid && user_access('view own private files', $account)) {
+ return $rights[$account->uid][$cache_id][$op] = TRUE;
+ }
+ }
+ elseif ($file->status == FILE_STATUS_PERMANENT && $file->uid == $account->uid && user_access('view own files', $account)) {
+ // For non-private files, allow to see if user owns the file.
+ return $rights[$account->uid][$cache_id][$op] = TRUE;
+ }
+ elseif ($file->status == FILE_STATUS_PERMANENT && user_access('view files', $account)) {
+ // For non-private files, users can view if they have the 'view files'
+ // permission.
+ return $rights[$account->uid][$cache_id][$op] = TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Implements hook_file_entity_access().
+ */
+function file_entity_file_entity_access($op, $file, $account) {
+ // If the file URI is invalid, deny access.
+ if (is_object($file) && !file_valid_uri($file->uri)) {
+ return FILE_ENTITY_ACCESS_DENY;
+ }
+
+ if ($op == 'create') {
+ if (user_access('create files')) {
+ return FILE_ENTITY_ACCESS_ALLOW;
+ }
+ }
+
+ if (!empty($file)) {
+ $type = is_string($file) ? $file : $file->type;
+
+ if (in_array($type, file_entity_permissions_get_configured_types())) {
+ if ($op == 'download') {
+ if (user_access('download any ' . $type . ' files', $account) || is_object($file) && user_access('download own ' . $type . ' files', $account) && ($account->uid == $file->uid)) {
+ return FILE_ENTITY_ACCESS_ALLOW;
+ }
+ }
+
+ if ($op == 'update') {
+ if (user_access('edit any ' . $type . ' files', $account) || (is_object($file) && user_access('edit own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
+ return FILE_ENTITY_ACCESS_ALLOW;
+ }
+ }
+
+ if ($op == 'delete') {
+ if (user_access('delete any ' . $type . ' files', $account) || (is_object($file) && user_access('delete own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
+ return FILE_ENTITY_ACCESS_ALLOW;
+ }
+ }
+ }
+ }
+
+ return FILE_ENTITY_ACCESS_IGNORE;
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * This is the hook_query_alter() for queries tagged with 'file_access'. It adds
+ * file access checks for the user account given by the 'account' meta-data (or
+ * global $user if not provided).
+ */
+function file_entity_query_file_access_alter(QueryAlterableInterface $query) {
+ _file_entity_query_file_entity_access_alter($query, 'file');
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * This function implements the same functionality as
+ * file_entity_query_file_access_alter() for the SQL field storage engine. File
+ * access conditions are added for field values belonging to files only.
+ */
+function file_entity_query_entity_field_access_alter(QueryAlterableInterface $query) {
+ //_file_entity_query_file_entity_access_alter($query, 'entity');
+}
+
+/**
+ * Helper for file entity access functions.
+ *
+ * @param $query
+ * The query to add conditions to.
+ * @param $type
+ * Either 'file' or 'entity' depending on what sort of query it is. See
+ * file_entity_query_file_entity_access_alter() and
+ * file_entity_query_entity_field_access_alter() for more.
+ */
+function _file_entity_query_file_entity_access_alter($query, $type) {
+ global $user;
+
+ // Read meta-data from query, if provided.
+ if (!$account = $query->getMetaData('account')) {
+ $account = $user;
+ }
+
+ // If $account can bypass file access, we don't need to alter the query.
+ if (user_access('bypass file access', $account)) {
+ return;
+ }
+
+ $tables = $query->getTables();
+ $base_table = $query->getMetaData('base_table');
+ // If no base table is specified explicitly, search for one.
+ if (!$base_table) {
+ $fallback = '';
+ foreach ($tables as $alias => $table_info) {
+ if (!($table_info instanceof SelectQueryInterface)) {
+ $table = $table_info['table'];
+ // If the file_managed table is in the query, it wins immediately.
+ if ($table == 'file_managed') {
+ $base_table = $table;
+ break;
+ }
+ // Check whether the table has a foreign key to file_managed.fid. If it
+ // does, do not run this check again as we found a base table and only
+ // file_managed can triumph that.
+ if (!$base_table) {
+ // The schema is cached.
+ $schema = drupal_get_schema($table);
+ if (isset($schema['fields']['fid'])) {
+ if (isset($schema['foreign keys'])) {
+ foreach ($schema['foreign keys'] as $relation) {
+ if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
+ $base_table = $table;
+ }
+ }
+ }
+ else {
+ // At least it's a fid. A table with a field called fid is very
+ // very likely to be a file_managed.fid in a file access query.
+ $fallback = $table;
+ }
+ }
+ }
+ }
+ }
+ // If there is nothing else, use the fallback.
+ if (!$base_table) {
+ if ($fallback) {
+ watchdog('security', 'Your file listing query is using @fallback as a base table in a query tagged for file access. This might not be secure and might not even work. Specify foreign keys in your schema to file_managed.fid ', array('@fallback' => $fallback), WATCHDOG_WARNING);
+ $base_table = $fallback;
+ }
+ else {
+ throw new Exception(t('Query tagged for file access but there is no fid. Add foreign keys to file_managed.fid in schema to fix.'));
+ }
+ }
+ }
+
+ if ($type == 'entity') {
+ // The original query looked something like:
+ // @code
+ // SELECT fid FROM sometable s
+ // WHERE ($file_access_conditions)
+ // @endcode
+ //
+ // Our query will look like:
+ // @code
+ // SELECT entity_type, entity_id
+ // FROM field_data_something s
+ // WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
+ // @endcode
+ //
+ // So instead of directly adding to the query object, we need to collect
+ // all of the file access conditions in a separate db_and() object and
+ // then add it to the query at the end.
+ $file_conditions = db_and();
+ }
+ foreach ($tables as $falias => $tableinfo) {
+ $table = $tableinfo['table'];
+ if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
+ $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
+ $subquery_conditions = db_or();
+
+ $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
+ if (!empty($wrappers['public'])) {
+ if (user_access('view files', $account)) {
+ foreach (array_keys($wrappers['public']) as $wrapper) {
+ $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
+ }
+ }
+ elseif (user_access('view own files', $account)) {
+ foreach (array_keys($wrappers['public']) as $wrapper) {
+ $subquery_conditions->condition(db_and()
+ ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
+ ->condition('fm_access.uid', $account->uid)
+ );
+ }
+ }
+ }
+ if (!empty($wrappers['private'])) {
+ if (user_access('view private files', $account)) {
+ foreach (array_keys($wrappers['private']) as $wrapper) {
+ $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
+ }
+ }
+ elseif (user_access('view own private files', $account)) {
+ foreach (array_keys($wrappers['private']) as $wrapper) {
+ $subquery_conditions->condition(db_and()
+ ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
+ ->condition('fm_access.uid', $account->uid)
+ );
+ }
+ }
+ }
+
+ if ($subquery_conditions->count()) {
+ $subquery->condition($subquery_conditions);
+
+ $field = 'fid';
+ // Now handle entities.
+ if ($type == 'entity') {
+ // Set a common alias for entities.
+ $base_alias = $falias;
+ $field = 'entity_id';
+ }
+ $subquery->where("$falias.$field = fm_access.fid");
+
+ // For an entity query, attach the subquery to entity conditions.
+ if ($type == 'entity') {
+ $file_conditions->exists($subquery);
+ }
+ // Otherwise attach it to the node query itself.
+ elseif ($table == 'file_managed') {
+ // Fix for https://drupal.org/node/2073085
+ $db_or = db_or();
+ $db_or->exists($subquery);
+ $db_or->isNull($falias . '.' . $field);
+ $query->condition($db_or);
+ }
+ else {
+ $query->exists($subquery);
+ }
+ }
+ }
+ }
+
+ if ($type == 'entity' && $file_conditions->count()) {
+ // All the file access conditions are only for field values belonging to
+ // files.
+ $file_conditions->condition("$base_alias.entity_type", 'file');
+ $or = db_or();
+ $or->condition($file_conditions);
+ // If the field value belongs to a non-file entity type then this function
+ // does not do anything with it.
+ $or->condition("$base_alias.entity_type", 'file', '<>');
+ // Add the compiled set of rules to the query.
+ $query->condition($or);
+ }
+}
+
+/**
+ * Implements hook_file_download().
+ */
+function file_entity_file_download($uri) {
+ // Load the file from the URI.
+ $file = file_uri_to_object($uri);
+
+ // An existing file wasn't found, so we don't control access.
+ // E.g. image derivatives will fall here.
+ if (empty($file->fid)) {
+ return NULL;
+ }
+
+ // Allow the user to download the file if they have appropriate permissions.
+ if (file_entity_access('view', $file)) {
+ return file_get_content_headers($file);
+ }
+
+ return NULL;
+}
+
+/**
+ * Helper function to generate standard file permission list for a given type.
+ *
+ * @param $type
+ * The machine-readable name of the file type.
+ * @return array
+ * An array of permission names and descriptions.
+ */
+function file_entity_list_permissions($type) {
+ $info = file_type_load($type);
+
+ // Build standard list of file permissions for this type.
+ $permissions = array(
+ "edit own $type files" => array(
+ 'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
+ ),
+ "edit any $type files" => array(
+ 'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
+ ),
+ "delete own $type files" => array(
+ 'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
+ ),
+ "delete any $type files" => array(
+ 'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
+ ),
+ "download own $type files" => array(
+ 'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
+ ),
+ "download any $type files" => array(
+ 'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
+ ),
+ );
+
+ return $permissions;
+}
+
+/**
+ * Returns an array of file types that should be managed by permissions.
+ *
+ * By default, this will include all file types in the system. To exclude a
+ * specific file from getting permissions defined for it, set the
+ * file_entity_permissions_$type variable to 0. File entity does not provide an
+ * interface for doing so, however, contrib modules may exclude their own files
+ * in hook_install(). Alternatively, contrib modules may configure all file
+ * types at once, or decide to apply some other hook_file_entity_access()
+ * implementation to some or all file types.
+ *
+ * @return
+ * An array of file types managed by this module.
+ */
+function file_entity_permissions_get_configured_types() {
+
+ $configured_types = array();
+
+ foreach (file_type_get_enabled_types() as $type => $info) {
+ if (variable_get('file_entity_permissions_' . $type, 1)) {
+ $configured_types[] = $type;
+ }
+ }
+
+ return $configured_types;
+}
+
+/**
+ * @} End of "defgroup file_entity_access".
+ *
+ * Implements hook_file_default_types().
+ */
+function file_entity_file_default_types() {
+ $types = array();
+
+ // Image.
+ $types['image'] = (object) array(
+ 'api_version' => 1,
+ 'type' => 'image',
+ 'label' => t('Image'),
+ 'description' => t('An <em>Image</em> file is a still visual.'),
+ 'mimetypes' => array(
+ 'image/*',
+ ),
+ );
+
+ // Video.
+ $types['video'] = (object) array(
+ 'api_version' => 1,
+ 'type' => 'video',
+ 'label' => t('Video'),
+ 'description' => t('A <em>Video</em> file is a moving visual recording.'),
+ 'mimetypes' => array(
+ 'video/*',
+ ),
+ );
+
+ // Audio.
+ $types['audio'] = (object) array(
+ 'api_version' => 1,
+ 'type' => 'audio',
+ 'label' => t('Audio'),
+ 'description' => t('An <em>Audio</em> file is a sound recording.'),
+ 'mimetypes' => array(
+ 'audio/*',
+ ),
+ );
+
+ // Document.
+ $types['document'] = (object) array(
+ 'api_version' => 1,
+ 'type' => 'document',
+ 'label' => t('Document'),
+ 'description' => t('A <em>Document</em> file is written information.'),
+ 'mimetypes' => array(
+ 'text/plain',
+ 'application/msword',
+ 'application/vnd.ms-excel',
+ 'application/pdf',
+ 'application/vnd.ms-powerpoint',
+ 'application/vnd.oasis.opendocument.text',
+ 'application/vnd.oasis.opendocument.spreadsheet',
+ 'application/vnd.oasis.opendocument.presentation',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ ),
+ );
+
+ return $types;
+}
+
+/**
+ * Implements hook_file_operations().
+ */
+function file_entity_file_operations() {
+ $operations = array(
+ 'permanent' => array(
+ 'label' => t('Indicate that the selected files are permanent and should not be deleted'),
+ 'callback' => 'file_entity_mass_update',
+ 'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
+ ),
+ 'temporary' => array(
+ 'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
+ 'callback' => 'file_entity_mass_update',
+ 'callback arguments' => array('updates' => array('status' => 0)),
+ ),
+ 'delete' => array(
+ 'label' => t('Delete selected files'),
+ 'callback' => NULL,
+ ),
+ );
+ return $operations;
+}
+
+/**
+ * Clear the field cache for any entities referencing a specific file.
+ *
+ * @param object $file
+ * A file object.
+ */
+function file_entity_invalidate_field_caches($file) {
+ $entity_types = &drupal_static(__FUNCTION__);
+
+ // Gather the list of entity types which support field caching.
+ if (!isset($entity_types)) {
+ $entity_types = array();
+ foreach (entity_get_info() as $entity_type => $entity_info) {
+ if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
+ $entity_types[] = $entity_type;
+ }
+ }
+ }
+
+ // If no entity types support field caching, then there is no work to be done.
+ if (empty($entity_types)) {
+ return;
+ }
+
+ $records = db_query("SELECT DISTINCT type, id FROM {file_usage} WHERE fid = :fid AND type IN (:types) AND id > 0", array(':fid' => $file->fid, ':types' => $entity_types))->fetchAll();
+ if (!empty($records)) {
+ $cids = array();
+ foreach ($records as $record) {
+ $cids[] = 'field:' . $record->type . ':' . $record->id;
+ }
+ cache_clear_all($cids, 'cache_field');
+ }
+}
+
+/**
+ * Check if a file entity is considered local or not.
+ *
+ * @param object $file
+ * A file entity object from file_load().
+ *
+ * @return
+ * TRUE if the file is using a local stream wrapper, or FALSE otherwise.
+ */
+function file_entity_file_is_local($file) {
+ $scheme = file_uri_scheme($file->uri);
+ $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
+ return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
+}
+
+/**
+ * Check if a file entity is considered writeable or not.
+ *
+ * @param object $file
+ * A file entity object from file_load().
+ *
+ * @return
+ * TRUE if the file is using a visible, readable and writeable stream wrapper,
+ * or FALSE otherwise.
+ */
+function file_entity_file_is_writeable($file) {
+ $scheme = file_uri_scheme($file->uri);
+ $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
+ return !empty($wrappers[$scheme]);
+}
+
+/**
+ * Pre-render callback for adding validation descriptions to file upload fields.
+ */
+function file_entity_upload_validators_pre_render($element) {
+ if (!empty($element['#upload_validators'])) {
+ if (!isset($element['#description'])) {
+ $element['#description'] = '';
+ }
+ if ($element['#description'] !== FALSE) {
+ $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
+ }
+ }
+ return $element;
+}
+
+/**
+ * @name pathauto_file Pathauto integration for the core file module.
+ * @{
+ */
+
+/**
+ * Implements hook_file_insert() on behalf of pathauto.module.
+ */
+function pathauto_file_insert($file) {
+ pathauto_file_update_alias($file, 'insert');
+}
+
+/**
+ * Implements hook_file_update() on behalf of pathauto.module.
+ */
+function pathauto_file_update($file) {
+ pathauto_file_update_alias($file, 'update');
+}
+
+/**
+ * Implements hook_file_delete() on behalf of pathauto.module.
+ */
+function pathauto_file_delete($file) {
+ pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
+ *
+ * Add the Pathauto settings to the file form.
+ */
+function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
+ $file = $form_state['file'];
+ $langcode = pathauto_entity_language('file', $file);
+ pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
+}
+
+/**
+ * Implements hook_file_operations() on behalf of pathauto.module.
+ */
+function pathauto_file_operations() {
+ $operations['pathauto_update_alias'] = array(
+ 'label' => t('Update URL alias'),
+ 'callback' => 'pathauto_file_update_alias_multiple',
+ 'callback arguments' => array('bulkupdate', array('message' => TRUE)),
+ );
+ return $operations;
+}
+
+/**
+ * Update the URL aliases for an individual file.
+ *
+ * @param $file
+ * A file object.
+ * @param $op
+ * Operation being performed on the file ('insert', 'update' or 'bulkupdate').
+ * @param $options
+ * An optional array of additional options.
+ */
+function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
+ // Skip processing if the user has disabled pathauto for the file.
+ if (isset($file->path['pathauto']) && empty($file->path['pathauto']) && empty($options['force'])) {
+ return;
+ }
+
+ $options += array('language' => pathauto_entity_language('file', $file));
+
+ // Skip processing if the file has no pattern.
+ if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
+ return;
+ }
+
+ module_load_include('inc', 'pathauto');
+ $uri = entity_uri('file', $file);
+ pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
+}
+
+/**
+ * Update the URL aliases for multiple files.
+ *
+ * @param $fids
+ * An array of file IDs.
+ * @param $op
+ * Operation being performed on the files ('insert', 'update' or
+ * 'bulkupdate').
+ * @param $options
+ * An optional array of additional options.
+ */
+function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
+ $options += array('message' => FALSE);
+
+ $files = file_load_multiple($fids);
+ foreach ($files as $file) {
+ pathauto_file_update_alias($file, $op, $options);
+ }
+
+ if (!empty($options['message'])) {
+ drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
+ }
+}
+
+/**
+ * Update action wrapper for pathauto_file_update_alias().
+ */
+function pathauto_file_update_action($file, $context = array()) {
+ pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
+}
+
+/**
+ * @} End of "name pathauto_file".
+ */
+
+/**
+ * Implements hook_form_FORM_ID_alter() for file_entity_edit() on behalf of path.module.
+ */
+function path_form_file_entity_edit_alter(&$form, $form_state) {
+ // Make sure this does not show up on the delete confirmation form.
+ if (empty($form_state['confirm_delete'])) {
+ $file = $form_state['file'];
+ $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
+ $langcode = !empty($langcode) ? $langcode : LANGUAGE_NONE;
+ $conditions = array('source' => 'file/' . $file->fid, 'language' => $langcode);
+ $path = (isset($file->fid) ? path_load($conditions) : array());
+ if ($path === FALSE) {
+ $path = array();
+ }
+ $path += array(
+ 'pid' => NULL,
+ 'source' => isset($file->fid) ? 'file/' . $file->fid : NULL,
+ 'alias' => '',
+ 'language' => $langcode,
+ );
+ $form['path'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('URL path settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => empty($path['alias']),
+ '#group' => 'additional_settings',
+ '#attributes' => array(
+ 'class' => array('path-form'),
+ ),
+ '#attached' => array(
+ 'js' => array(drupal_get_path('module', 'path') . '/path.js'),
+ ),
+ '#access' => user_access('create url aliases') || user_access('administer url aliases'),
+ '#weight' => 30,
+ '#tree' => TRUE,
+ '#element_validate' => array('path_form_element_validate'),
+ );
+ $form['path']['alias'] = array(
+ '#type' => 'textfield',
+ '#title' => t('URL alias'),
+ '#default_value' => $path['alias'],
+ '#maxlength' => 255,
+ '#description' => t('Optionally specify an alternative URL by which this file can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'),
+ );
+ $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
+ $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
+ $form['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
+ }
+}
+
+/**
+ * Implements hook_file_insert() on behalf of path.module.
+ */
+function path_file_insert($file) {
+ if (isset($file->path)) {
+ $path = $file->path;
+ $path['alias'] = trim($path['alias']);
+ // Only save a non-empty alias.
+ if (!empty($path['alias'])) {
+ // Ensure fields for programmatic executions.
+ $path['source'] = 'file/' . $file->fid;
+ // Core does not provide a way to store the file language but contrib
+ // modules can do it so we need to take this into account.
+ $langcode = entity_language('file', $file);
+ $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
+ path_save($path);
+ }
+ }
+}
+
+/**
+ * Implements hook_file_update() on behalf of path.module.
+ */
+function path_file_update($file) {
+ if (isset($file->path)) {
+ $path = $file->path;
+ $path['alias'] = trim($path['alias']);
+ // Delete old alias if user erased it.
+ if (!empty($path['fid']) && empty($path['alias'])) {
+ path_delete($path['fid']);
+ }
+ // Only save a non-empty alias.
+ if (!empty($path['alias'])) {
+ // Ensure fields for programmatic executions.
+ $path['source'] = 'file/' . $file->fid;
+ // Core does not provide a way to store the file language but contrib
+ // modules can do it so we need to take this into account.
+ $langcode = entity_language('file', $file);
+ $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
+ path_save($path);
+ }
+ }
+}
+
+/**
+ * Implements hook_file_delete() on behalf of path.module.
+ */
+function path_file_delete($file) {
+ // Delete all aliases associated with this file.
+ path_delete(array('source' => 'file/' . $file->fid));
+}
+
+/**
+ * Checks if pattern(s) match mimetype(s).
+ */
+function file_entity_match_mimetypes($needle, $haystack) {
+ $needle = is_array($needle) ? $needle : array($needle);
+ $haystack = is_array($haystack) ? $haystack : array($haystack);
+
+ foreach ($haystack as $mimetype) {
+ foreach ($needle as $search) {
+ if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * A wrapper function for the PHP function fnmatch().
+ *
+ * We include this, because Windows servers do not implement fnmatch() until
+ * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
+ */
+function file_entity_fnmatch($pattern, $string) {
+ if (!function_exists('fnmatch')) {
+ return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
+ }
+ return fnmatch($pattern, $string);
+}
+
+/**
+ * Return an URI for a file download.
+ */
+function file_entity_download_uri($file) {
+ $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
+ if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
+ $uri['options']['query']['token'] = file_entity_get_download_token($file);
+ }
+ return $uri;
+}
+
+function file_entity_file_get_mimetype_type($file) {
+ list($type, $subtype) = explode('/', $file->filemime, 2);
+ return $type;
+}
+
+/**
+ * Implements hook_admin_menu_map().
+ */
+function file_entity_admin_menu_map() {
+ if (!user_access('administer file types')) {
+ return;
+ }
+ $map['admin/structure/file-types/manage/%file_type'] = array(
+ 'parent' => 'admin/structure/file-types',
+ 'arguments' => array(
+ array('%file_type' => array_keys(file_entity_type_get_names())),
+ ),
+ );
+ return $map;
+}
+
+/*
+ * Generates a token to protect a file download URL.
+ *
+ * This prevents unauthorized crawling of all file download URLs since the
+ * {file_managed}.fid column is an auto-incrementing serial field and is easy
+ * to guess or attempt many at once. This can be costly both in CPU time
+ * and bandwidth.
+ *
+ * @see image_style_path_token()
+ *
+ * @param object $file
+ * A file entity object.
+ *
+ * @return string
+ * An eight-character token which can be used to protect file downloads
+ * against denial-of-service attacks.
+ */
+function file_entity_get_download_token($file) {
+ // Return the first eight characters.
+ return substr(drupal_hmac_base64("file/$file->fid/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
+}
+
+/**
+ * Find all fields that are of a certain field type.
+ *
+ * @param string $field_type
+ * A field type.
+ *
+ * @return array
+ * An array of field names that match the type $field_type.
+ */
+function _file_entity_get_fields_by_type($field_type) {
+ $return = array();
+ if (function_exists('field_info_field_map')) {
+ foreach (field_info_field_map() as $field_name => $field) {
+ if ($field['type'] == $field_type) {
+ $return[$field_name] = $field_name;
+ }
+ }
+ }
+ else {
+ foreach (field_info_fields() as $field_name => $field) {
+ if ($field['type'] == $field_type) {
+ $return[$field_name] = $field_name;
+ }
+ }
+ }
+ return $return;
+}
+
+/**
+ * Implements hook_field_attach_load().
+ */
+function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
+ // Loop over all the entities looking for entities with attached images.
+ foreach ($entities as $entity) {
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+ // Examine every image field instance attached to this entity's bundle.
+ $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
+ foreach ($instances as $field_name => $instance) {
+ if (!empty($entity->{$field_name})) {
+ foreach ($entity->{$field_name} as $langcode => $items) {
+ foreach ($items as $delta => $item) {
+ // If alt and title text is not specified, fall back to alt and
+ // title text on the file.
+ if (!empty($item['fid']) && (empty($item['alt']) || empty($item['title']))) {
+ $file = file_load($item['fid']);
+ foreach (array('alt', 'title') as $key) {
+ if (empty($item[$key]) && !empty($file->{$key})) {
+ $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
+ $wrappers = array();
+ foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
+ if (empty($wrapper['private'])) {
+ $wrappers['public'][$key] = $wrapper['name'];
+ }
+ else {
+ $wrappers['private'][$key] = $wrapper['name'];
+ }
+ }
+ return $wrappers;
+}
diff --git a/sites/all/modules/file_entity/file_entity.pages.inc b/sites/all/modules/file_entity/file_entity.pages.inc
new file mode 100644
index 000000000..17688fee7
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.pages.inc
@@ -0,0 +1,1167 @@
+<?php
+
+/**
+ * @file
+ * Supports file operations including View, Edit, and Delete.
+ */
+
+/**
+ * Menu callback; view a single file entity.
+ */
+function file_entity_view_page($file) {
+ drupal_set_title($file->filename);
+
+ $uri = entity_uri('file', $file);
+ // Set the file path as the canonical URL to prevent duplicate content.
+ drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
+ // Set the non-aliased path as a default shortlink.
+ drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
+
+ return file_view($file, 'full');
+}
+
+/**
+ * Menu callback; download a single file entity.
+ */
+function file_entity_download_page($file) {
+ // Ensure there is a valid token to download this file.
+ if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
+ if (!isset($_GET['token']) || $_GET['token'] !== file_entity_get_download_token($file)) {
+ return MENU_ACCESS_DENIED;
+ }
+ }
+
+ // If the file does not exist it can cause problems with file_transfer().
+ if (!is_file($file->uri)) {
+ return MENU_NOT_FOUND;
+ }
+
+ $headers = array(
+ 'Content-Type' => mime_header_encode($file->filemime),
+ 'Content-Disposition' => 'attachment; filename="' . mime_header_encode(drupal_basename($file->uri)) . '"',
+ 'Content-Length' => $file->filesize,
+ 'Content-Transfer-Encoding' => 'binary',
+ 'Pragma' => 'no-cache',
+ 'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
+ 'Expires' => '0',
+ );
+
+ // Let other modules alter the download headers.
+ drupal_alter('file_download_headers', $headers, $file);
+
+ // Let other modules know the file is being downloaded.
+ module_invoke_all('file_transfer', $file->uri, $headers);
+
+ if (file_entity_file_is_local($file)) {
+ // For local files, transfer the file and do not reveal the actual URL.
+ file_transfer($file->uri, $headers);
+ }
+ else {
+ // For remote files, just redirect the user to that file's actual URL.
+ $headers['Location'] = file_create_url($file->uri);
+ foreach ($headers as $name => $value) {
+ drupal_add_http_header($name, $value);
+ }
+ drupal_send_headers();
+ drupal_exit();
+ }
+}
+
+/**
+ * Form callback for adding a file via an upload form.
+ *
+ * This is a multi step form which has 1-3 pages:
+ * - Upload file
+ * - Choose filetype
+ * If there is only one candidate (based on mimetype) we will skip this step.
+ * - Edit fields
+ * Skip this step if there are no fields on this entity type.
+ */
+function file_entity_add_upload($form, &$form_state, array $options = array()) {
+ $step = (isset($form_state['step']) && in_array($form_state['step'], array(1, 2, 3, 4))) ? $form_state['step'] : 1;
+ $form['#step'] = $step;
+ $form['#options'] = $options;
+ switch ($step) {
+ case 1:
+ return file_entity_add_upload_step_upload($form, $form_state, $options);
+
+ case 2:
+ return file_entity_add_upload_step_filetype($form, $form_state, $options);
+
+ case 3:
+ return file_entity_add_upload_step_scheme($form, $form_state, $options);
+
+ case 4:
+ return file_entity_add_upload_step_fields($form, $form_state, $options);
+
+ }
+}
+
+/**
+ * Generate form fields for the first step in the add file wizard.
+ */
+function file_entity_add_upload_step_upload($form, &$form_state, array $options = array()) {
+ $form['upload'] = array(
+ '#type' => 'managed_file',
+ '#title' => t('Upload a new file'),
+ '#upload_location' => file_entity_upload_destination_uri($options),
+ '#upload_validators' => file_entity_get_upload_validators($options),
+ '#progress_indicator' => 'bar',
+ '#required' => TRUE,
+ '#pre_render' => array('file_managed_file_pre_render', 'file_entity_upload_validators_pre_render'),
+ '#default_value' => isset($form_state['storage']['upload']) ? $form_state['storage']['upload'] : NULL,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['next'] = array(
+ '#type' => 'submit',
+ '#value' => t('Next'),
+ );
+
+ form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
+
+ return $form;
+}
+
+/**
+ * Generate form fields for the second step in the add file wizard.
+ */
+function file_entity_add_upload_step_filetype($form, &$form_state, array $options = array()) {
+ $file = file_load($form_state['storage']['upload']);
+
+ $form['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('File type'),
+ '#options' => file_entity_get_filetype_candidates($file),
+ '#default_value' => isset($form_state['storage']['type']) ? $form_state['storage']['type'] : NULL,
+ '#required' => TRUE,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['previous'] = array(
+ '#type' => 'submit',
+ '#value' => t('Previous'),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('file_entity_add_upload_submit'),
+ );
+ $form['actions']['next'] = array(
+ '#type' => 'submit',
+ '#value' => t('Next'),
+ );
+
+ return $form;
+}
+
+/**
+ * Generate form fields for the third step in the add file wizard.
+ */
+function file_entity_add_upload_step_scheme($form, &$form_state, array $options = array()) {
+ $file = file_load($form_state['storage']['upload']);
+
+ $schemes = array();
+ foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) {
+ $schemes[$scheme] = check_plain($info['description']);
+ }
+
+ // Remove any schemes not found in the instance settings.
+ if (!empty($options['schemes'])) {
+ $schemes = array_intersect_key($schemes, array_flip($options['schemes']));
+ }
+
+ // Determine which scheme to use as the default value.
+ if (isset($form_state['storage']['scheme'])) {
+ $fallback_scheme = $form_state['storage']['scheme'];
+ }
+ elseif (!empty($options['uri_scheme'])) {
+ $fallback_scheme = $options['uri_scheme'];
+ }
+ else {
+ $fallback_scheme = file_default_scheme();
+ }
+
+ $form['scheme'] = array(
+ '#type' => 'radios',
+ '#title' => t('Destination'),
+ '#options' => $schemes,
+ '#default_value' => $fallback_scheme,
+ '#required' => TRUE,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['previous'] = array(
+ '#type' => 'submit',
+ '#value' => t('Previous'),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('file_entity_add_upload_submit'),
+ );
+ $form['actions']['next'] = array(
+ '#type' => 'submit',
+ '#value' => t('Next'),
+ );
+
+ return $form;
+}
+
+/**
+ * Generate form fields for the fourth step in the add file wizard.
+ */
+function file_entity_add_upload_step_fields($form, &$form_state, array $options = array()) {
+ // Load the file and overwrite the filetype set on the previous screen.
+ $file = file_load($form_state['storage']['upload']);
+ $file->type = $form_state['storage']['type'];
+
+ // Let users modify the filename here.
+ $form['filename'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name'),
+ '#default_value' => $file->filename,
+ '#required' => TRUE,
+ '#maxlength' => 255,
+ '#weight' => -10,
+ );
+
+ // Add fields.
+ field_attach_form('file', $file, $form, $form_state);
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['previous'] = array(
+ '#type' => 'submit',
+ '#value' => t('Previous'),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('file_entity_add_upload_submit'),
+ );
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+
+ return $form;
+}
+
+/**
+ * Page callback to show file usage information.
+ */
+function file_entity_usage_page($file) {
+ $rows = array();
+ $occured_entities = array();
+
+ // Determine all of the locations where a file is used, then loop through the
+ // occurrences and filter out any duplicates.
+ foreach (file_usage_list($file) as $module => $type) {
+ foreach ($type as $entity_type => $entity_ids) {
+ // There are cases where the actual entity doesn't exist.
+ // We have to handle this.
+ $entity_info = entity_get_info($entity_type);
+ $entities = empty($entity_info) ? NULL : entity_load($entity_type, array_keys($entity_ids));
+
+ foreach ($entity_ids as $entity_id => $count) {
+ // If this entity has already been listed in the table, just add any
+ // additional usage to the total count column in the table row and
+ // continue on to the next iteration of the loop.
+ if (isset($occured_entities[$entity_type][$entity_id])) {
+ $rows[$occured_entities[$entity_type][$entity_id]][2] += $count;
+ continue;
+ }
+
+ // Retrieve the label and the URI of the entity.
+ $label = empty($entities[$entity_id]) ? $module : entity_label($entity_type, $entities[$entity_id]);
+ $entity_uri = empty($entities[$entity_id]) ? NULL : entity_uri($entity_type, $entities[$entity_id]);
+
+ // Link the label to the URI when possible.
+ if (empty($entity_uri)) {
+ $entity = check_plain($label);
+ }
+ else {
+ $entity = l($label, $entity_uri['path']);
+ }
+
+ $rows[] = array($entity, $entity_type, $count);
+
+ // Record the occurrence of the entity to ensure that it isn't listed in
+ // the table again.
+ $occured_entities[$entity_type][$entity_id] = count($rows) - 1;
+ }
+ }
+ }
+
+ $header = array(t('Entity'), t('Entity type'), t('Use count'));
+ $build['usage_table'] = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $rows,
+ '#caption' => t('This table lists all of the places where @filename is used.', array('@filename' => $file->filename)),
+ '#empty' => t('This file is not currently used.'),
+ );
+
+ return $build;
+}
+
+/**
+ * Get the candidate filetypes for a given file.
+ *
+ * Only filetypes for which the user has access to create entities are returned.
+ *
+ * @param array $file
+ * An upload file array from form_state.
+ *
+ * @return array
+ * An array of file type bundles that support the file's mime type.
+ */
+function file_entity_get_filetype_candidates($file) {
+ $types = module_invoke_all('file_type', $file);
+ drupal_alter('file_type', $types, $file);
+ $candidates = array();
+ foreach ($types as $type) {
+ $file->type = $type;
+ if (file_entity_access('create', $file)) {
+ $candidates[$type] = file_entity_type_get_name($file);
+ }
+ }
+ return $candidates;
+}
+
+/**
+ * Submit handler for the add file form.
+ */
+function file_entity_add_upload_submit($form, &$form_state) {
+ $form_state['storage'] = isset($form_state['storage']) ? $form_state['storage'] : array();
+ $form_state['storage'] = array_merge($form_state['storage'], $form_state['values']);
+
+ // This var is set to TRUE when we are ready to save the file.
+ $save = FALSE;
+ $trigger = $form_state['triggering_element']['#id'];
+
+ $steps_to_check = array(2, 3);
+ if ($trigger == 'edit-previous') {
+ // If the previous button was hit,
+ // the step checking order should be reversed 3, 2.
+ $steps_to_check = array_reverse($steps_to_check);
+ }
+
+ foreach ($steps_to_check as $step) {
+ // Check if we can skip step 2 and 3.
+ if (($form['#step'] == $step - 1 && $trigger == 'edit-next') || ($form['#step'] == $step + 1 && $trigger == 'edit-previous')) {
+ $file = file_load($form_state['storage']['upload']);
+ if ($step == 2) {
+ // Check if we can skip step 2.
+ $candidates = file_entity_get_filetype_candidates($file);
+ if (count($candidates) == 1) {
+ $candidates_keys = array_keys($candidates);
+ // There is only one possible filetype for this file.
+ // Skip the second page.
+ $form['#step'] += ($trigger == 'edit-previous') ? -1 : 1;
+ $form_state['storage']['type'] = reset($candidates_keys);
+ }
+ elseif (!$candidates || variable_get('file_entity_file_upload_wizard_skip_file_type', FALSE)) {
+ // Do not assign the file a file type.
+ $form['#step'] += ($trigger == 'edit-previous') ? -1 : 1;
+ $form_state['storage']['type'] = FILE_TYPE_NONE;
+ }
+ }
+ else {
+ // Check if we can skip step 3.
+ $options = $form['#options'];
+ $schemes = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
+
+ // Remove any schemes not found in the instance settings.
+ if (!empty($options['schemes'])) {
+ $schemes = array_intersect_key($schemes, $options['schemes']);
+ }
+
+ if (!file_entity_file_is_writeable($file)) {
+ // The file is read-only (remote) and must use its provided scheme.
+ $form['#step'] += ($trigger == 'edit-previous') ? -1 : 1;
+ $form_state['storage']['scheme'] = file_uri_scheme($file->uri);
+ }
+ elseif (count($schemes) == 1) {
+ // There is only one possible stream wrapper for this file.
+ // Skip the third page.
+ $form['#step'] += ($trigger == 'edit-previous') ? -1 : 1;
+ $form_state['storage']['scheme'] = key($schemes);
+ }
+ elseif (variable_get('file_entity_file_upload_wizard_skip_scheme', FALSE)) {
+ $form['#step'] += ($trigger == 'edit-previous') ? -1 : 1;
+
+ // Fallback to the URI scheme specified in the field settings
+ // otherwise use the default file scheme.
+ if (!empty($options['uri_scheme'])) {
+ $form_state['storage']['scheme'] = $options['uri_scheme'];
+ }
+ else {
+ $form_state['storage']['scheme'] = file_default_scheme();
+ }
+ }
+ }
+ }
+ }
+
+ // We have the filetype, check if we can skip step 4.
+ if (($form['#step'] == 3 && $trigger == 'edit-next')) {
+ $file = file_load($form_state['storage']['upload']);
+ if (!field_info_instances('file', $form_state['storage']['type'])) {
+ // This filetype doesn't have fields, save the file.
+ $save = TRUE;
+ }
+ elseif (variable_get('file_entity_file_upload_wizard_skip_fields', FALSE)) {
+ // Save the file with blanks fields.
+ $save = TRUE;
+ }
+ }
+
+ // Form id's can vary depending on how many other forms are displayed, so we
+ // need to do string comparissons. e.g edit-submit--2.
+ if (strpos($trigger, 'edit-next') !== FALSE) {
+ $form_state['step'] = $form['#step'] + 1;
+ }
+ elseif (strpos($trigger, 'edit-previous') !== FALSE) {
+ $form_state['step'] = $form['#step'] - 1;
+ }
+ elseif (strpos($trigger, 'edit-submit') !== FALSE) {
+ $save = TRUE;
+ }
+
+ if ($save) {
+ $file = file_load($form_state['storage']['upload']);
+ if ($file) {
+ if (file_uri_scheme($file->uri) != $form_state['storage']['scheme']) {
+ $file_destination = $form_state['storage']['scheme'] . '://' . file_uri_target($file->uri);
+ $file_destination = file_stream_wrapper_uri_normalize($file_destination);
+ if ($moved_file = file_move($file, $file_destination, FILE_EXISTS_RENAME)) {
+ // Only re-assign the file object if file_move() did not fail.
+ $file = $moved_file;
+ }
+ }
+ $file->type = $form_state['storage']['type'];
+ $file->display = TRUE;
+
+ // Change the file from temporary to permanent.
+ $file->status = FILE_STATUS_PERMANENT;
+
+ // Save the form fields.
+ // Keep in mind that the values for the Field API fields must be in
+ // $form_state['values'] and not in ['storage']. This is true as long as
+ // the fields are on the last page of the multi step form.
+ entity_form_submit_build_entity('file', $file, $form, $form_state);
+
+ file_save($file);
+ $form_state['file'] = $file;
+ drupal_set_message(t('@type %name was uploaded.', array('@type' => file_entity_type_get_name($file), '%name' => $file->filename)));
+ }
+ else {
+ drupal_set_message(t('An error occurred and no file was uploaded.'), 'error');
+ return;
+ }
+
+ // Figure out destination.
+ if (user_access('administer files')) {
+ $path = 'admin/content/file';
+ }
+ else {
+ $path = 'file/' . $file->fid;
+ }
+ $form_state['redirect'] = $path;
+ }
+ else {
+ $form_state['rebuild'] = TRUE;
+ }
+
+ // Clear the page and block caches.
+ cache_clear_all();
+}
+
+/**
+ * Determines the upload location for the file add upload form.
+ *
+ * @param array $params
+ * An array of parameters from the media browser.
+ * @param array $data
+ * (optional) An array of token objects to pass to token_replace().
+ *
+ * @return string
+ * A file directory URI with tokens replaced.
+ *
+ * @see token_replace()
+ */
+function file_entity_upload_destination_uri(array $params, array $data = array()) {
+ $params += array(
+ 'uri_scheme' => file_default_scheme(),
+ 'file_directory' => '',
+ );
+
+ $destination = trim($params['file_directory'], '/');
+
+ // Replace tokens.
+ $destination = token_replace($destination, $data);
+
+ return $params['uri_scheme'] . '://' . $destination;
+}
+
+/**
+ * Form for uploading multiple files.
+ */
+function file_entity_add_upload_multiple($form, &$form_state, $params = array()) {
+ $form = file_entity_add_upload($form, $form_state, $params);
+ unset($form['upload']['#title']);
+ // The validators will be set from plupload anyway. This isn't pretty,
+ // but don't allow it to show up twice.
+ unset($form['upload']['#description']);
+
+ $form['upload']['#type'] = 'plupload';
+
+ // Ensure that we call the plupload_element_pre_render function.
+ // If it isn't called, it doesn't set the JS settings that transfers the
+ // list of allowed file extentions to the PLUpload widget.
+ // We override the 'file_entity_upload_validators_pre_render' setting if it
+ // exists, because both pre-render hooks adds the upload-help with list of
+ // allowed file extensions.
+ $index = array_search('file_entity_upload_validators_pre_render', $form['upload']['#pre_render']);
+ if ($index !== FALSE) {
+ $form['upload']['#pre_render'][$index] = 'plupload_element_pre_render';
+ }
+ else {
+ $form['upload']['#pre_render'][] = 'plupload_element_pre_render';
+ }
+
+ $form['submit']['#value'] = t('Start upload');
+ return $form;
+}
+
+/**
+ * Submit handler for the multiple upload form.
+ */
+function file_entity_add_upload_multiple_submit($form, &$form_state) {
+ $upload_location = !empty($form['upload']['#upload_location']) ?
+ $form['upload']['#upload_location'] . '/' :
+ variable_get('file_default_scheme', 'public') . '://';
+
+ // Ensure writable destination directory for the files.
+ file_prepare_directory($upload_location, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+
+ // We can't use file_save_upload() because of
+ // http://www.jacobsingh.name/content/tight-coupling-no-not.
+ foreach ($form_state['values']['upload'] as $uploaded_file) {
+ if ($uploaded_file['status'] == 'done') {
+ $source = $uploaded_file['tmppath'];
+ $destination = file_stream_wrapper_uri_normalize($upload_location . $uploaded_file['name']);
+ // Rename it to its original name, and put it in its final home.
+ // Note - not using file_move here because if we call file_get_mime
+ // (in file_uri_to_object) while it has a .tmp extension, it horks.
+ $destination = file_unmanaged_move($source, $destination, FILE_EXISTS_RENAME);
+
+ $file = file_uri_to_object($destination);
+ $file->status = FILE_STATUS_PERMANENT;
+ file_save($file);
+
+ $form_state['files'][$file->fid] = $file;
+ }
+ else {
+ // @todo: move this to element validate or something.
+ form_set_error('pud', t('The specified file %name could not be uploaded.', array('%name' => $uploaded_file['name'])));
+ }
+ }
+
+ // Redirect to the file edit page.
+ if (file_entity_access('update', $file) && module_exists('media_bulk_upload')) {
+ $destination = array();
+ if (isset($_GET['destination'])) {
+ $destination = drupal_get_destination();
+ unset($_GET['destination']);
+ }
+ elseif (user_access('administer files')) {
+ $destination = array('destination' => 'admin/content/file');
+ }
+ $form_state['redirect'] = array('admin/content/file/edit-multiple/' . implode(' ', array_keys($form_state['files'])), array('query' => $destination));
+ }
+ else {
+ $form_state['redirect'] = user_access('administer files') ? 'admin/content/file' : '<front>';
+ }
+
+ // Clear the page and block caches.
+ cache_clear_all();
+}
+
+/**
+ * Page callback: Form constructor for the file edit form.
+ *
+ * Path: file/%file/edit
+ *
+ * @param object $file
+ * A file object from file_load().
+ *
+ * @see file_entity_menu()
+ *
+ * @todo Rename this form to file_edit_form to ease into core.
+ */
+function file_entity_edit($form, &$form_state, $file) {
+ drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $file->type, '@title' => $file->filename)), PASS_THROUGH);
+
+ $form_state['file'] = $file;
+
+ $form['#attributes']['class'][] = 'file-form';
+ if (!empty($file->type)) {
+ $form['#attributes']['class'][] = 'file-' . $file->type . '-form';
+ }
+
+ // Basic file information.
+ // These elements are just values so they are not even sent to the client.
+ foreach (array('fid', 'type', 'uid', 'timestamp') as $key) {
+ $form[$key] = array(
+ '#type' => 'value',
+ '#value' => isset($file->$key) ? $file->$key : NULL,
+ );
+ }
+
+ $form['filename'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name'),
+ '#default_value' => $file->filename,
+ '#required' => TRUE,
+ '#maxlength' => 255,
+ '#weight' => -10,
+ );
+
+ // Add a 'replace this file' upload field if the file is writeable.
+ if (file_entity_file_is_writeable($file)) {
+ // Set up replacement file validation.
+ $replacement_options = array();
+
+ // The replacement file must have an extension valid for the original type.
+ $file_extensions = array();
+ $file_type_name = isset($file->type) ? $file->type : file_get_type($file);
+ if ($file_type_name && ($file_type = file_type_load($file_type_name))) {
+ $file_extensions = file_type_get_valid_extensions($file_type);
+ }
+
+ // Set allowed file extensions.
+ if (!empty($file_extensions)) {
+ // Set to type based file extensions.
+ $replacement_options['file_extensions'] = implode(' ', $file_extensions);
+ }
+ else {
+ // Fallback to the extension of the current file.
+ $replacement_options['file_extensions'] = pathinfo($file->uri, PATHINFO_EXTENSION);
+ }
+
+ $form['replace_upload'] = array(
+ '#type' => 'file',
+ '#title' => t('Replace file'),
+ '#description' => t('This file will replace the existing file. This action cannot be undone.'),
+ '#upload_validators' => file_entity_get_upload_validators($replacement_options),
+ '#pre_render' => array('file_entity_upload_validators_pre_render'),
+ );
+ }
+
+ $form['preview'] = file_view_file($file, 'preview');
+
+ $form['additional_settings'] = array(
+ '#type' => 'vertical_tabs',
+ '#weight' => 99,
+ );
+
+ // File destination information for administrators.
+ $form['destination'] = array(
+ '#type' => 'fieldset',
+ '#access' => user_access('administer files') && file_entity_file_is_writeable($file),
+ '#title' => t('Destination'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'additional_settings',
+ '#attributes' => array(
+ 'class' => array('file-form-destination'),
+ ),
+ '#attached' => array(
+ 'js' => array(
+ drupal_get_path('module', 'file_entity') . '/file_entity.js',
+ ),
+ ),
+ );
+
+ $options = array();
+ foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) {
+ $options[$scheme] = check_plain($info['name']);
+ }
+
+ $form['destination']['scheme'] = array(
+ '#type' => 'radios',
+ '#title' => t('Destination'),
+ '#options' => $options,
+ '#default_value' => file_uri_scheme($file->uri),
+ );
+
+ // File user information for administrators.
+ $form['user'] = array(
+ '#type' => 'fieldset',
+ '#access' => user_access('administer files'),
+ '#title' => t('User information'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'additional_settings',
+ '#attributes' => array(
+ 'class' => array('file-form-user'),
+ ),
+ '#attached' => array(
+ 'js' => array(
+ drupal_get_path('module', 'file_entity') . '/file_entity.js',
+ array(
+ 'type' => 'setting',
+ 'data' => array('anonymous' => variable_get('anonymous', t('Anonymous'))),
+ ),
+ ),
+ ),
+ '#weight' => 90,
+ );
+ $form['user']['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Associated with'),
+ '#maxlength' => 60,
+ '#autocomplete_path' => 'user/autocomplete',
+ '#default_value' => !empty($file->uid) ? user_load($file->uid)->name : '',
+ '#weight' => -1,
+ '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
+ );
+
+ // Add the buttons.
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#weight' => 5,
+ '#submit' => array('file_entity_edit_submit'),
+ '#validate' => array('file_entity_edit_validate'),
+ );
+ $form['actions']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete'),
+ '#weight' => 10,
+ '#submit' => array('file_entity_edit_delete_submit'),
+ '#access' => file_entity_access('delete', $file),
+ );
+
+ // Build the URL for the cancel button taking into account that there might be
+ // a "destination" that includes query string variables.
+ $parameters = drupal_get_query_parameters();
+ $destination = isset($parameters['destination']) ? $parameters['destination'] : 'file/' . $file->fid;
+ $url = drupal_parse_url($destination);
+
+ $form['actions']['cancel'] = array(
+ '#type' => 'link',
+ '#title' => t('Cancel'),
+ '#href' => $url['path'],
+ '#options' => array('query' => $url['query']),
+ '#weight' => 15,
+ );
+
+ $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
+ field_attach_form('file', $file, $form, $form_state, $langcode);
+
+ return $form;
+}
+
+/**
+ * Form validation handler for file_entity_edit().
+ */
+function file_entity_edit_validate($form, &$form_state) {
+ $file = (object) $form_state['values'];
+
+ // Validate the "associated user" field.
+ if (!empty($file->name) && !($account = user_load_by_name($file->name))) {
+ // The use of empty() is mandatory in the context of usernames
+ // as the empty string denotes the anonymous user. In case we
+ // are dealing with an anonymous user we set the user ID to 0.
+ form_set_error('name', t('The username %name does not exist.', array('%name' => $file->name)));
+ }
+
+ // Handle the replacement file if uploaded.
+ if (isset($form_state['values']['replace_upload'])) {
+ // Save the file as a temporary file.
+ $file = file_save_upload('replace_upload', $form['replace_upload']['#upload_validators']);
+ if (!empty($file)) {
+ // Put the temporary file in form_values so we can save it on submit.
+ $form_state['values']['replace_upload'] = $file;
+ }
+ elseif ($file === FALSE) {
+ // File uploaded failed.
+ form_set_error('replace_upload', t('The replacement file could not be uploaded.'));
+ }
+ }
+
+ // Run entity form validation.
+ entity_form_field_validate('file', $form, $form_state);
+}
+
+/**
+ * Form submission handler for the 'Save' button for file_entity_edit().
+ */
+function file_entity_edit_submit($form, &$form_state) {
+ $file = $form_state['file'];
+ $orphaned_uri = '';
+
+ // Check if a replacement file has been uploaded.
+ if (!empty($form_state['values']['replace_upload'])) {
+ $replacement = $form_state['values']['replace_upload'];
+ // Move file from temp to permanent home.
+ $destination_uri = rtrim($file->uri, drupal_basename($file->uri)) . drupal_basename($replacement->uri);
+ $replace_mode = $destination_uri == $file->uri ? FILE_EXISTS_REPLACE : FILE_EXISTS_RENAME;
+ if ($new_file_uri = file_unmanaged_copy($replacement->uri, $destination_uri, $replace_mode)) {
+ // @todo Add watchdog() about replaced file here?
+
+ // Remove temporary file.
+ file_delete($replacement);
+
+ // Update if the uri target has changed.
+ if ($new_file_uri != $file->uri) {
+ // Store the original file uri to delete if save is sucessful.
+ $orphaned_uri = $file->uri;
+
+ // Update file entity uri.
+ $file->uri = $new_file_uri;
+ }
+ }
+ }
+
+ // Run entity form submit handling and save the file.
+ entity_form_submit_build_entity('file', $file, $form, $form_state);
+
+ // A user might assign the associated user by entering a user name in the file
+ // edit form, which we then need to translate to a user ID.
+ if (isset($file->name)) {
+ // The use of isset() is mandatory in the context of user IDs, because
+ // user ID 0 denotes the anonymous user.
+ if ($user = user_load_by_name($file->name)) {
+ $file->uid = $user->uid;
+ }
+ else {
+ // Anonymous user.
+ $file->uid = 0;
+ }
+ }
+ elseif ($file->uid) {
+ $user = user_load($file->uid);
+ $file->name = $user->name;
+ }
+
+ if (file_uri_scheme($file->uri) != $form_state['values']['scheme']) {
+ $file_destination = $form_state['storage']['scheme'] . '://' . file_uri_target($file->uri);
+ $file_destination = file_stream_wrapper_uri_normalize($file_destination);
+ if ($moved_file = file_move($file, $file_destination, FILE_EXISTS_RENAME)) {
+ // Only re-assign the file object if file_move() did not fail.
+ $file = $moved_file;
+ }
+ }
+
+ file_save($file);
+
+ $args = array(
+ '@type' => file_entity_type_get_name($file),
+ '%title' => entity_label('file', $file),
+ );
+ watchdog('file', '@type: updated %title.', $args);
+ drupal_set_message(t('@type %title has been updated.', $args));
+
+ // Clean up orphaned file.
+ if (!empty($orphaned_uri)) {
+ file_unmanaged_delete($orphaned_uri);
+
+ $args['@orphaned'] = file_uri_target($orphaned_uri);
+ watchdog('file', '@type: deleted orphaned file @orphaned for %title.', $args);
+ drupal_set_message(t('The replaced @type @orphaned has been deleted.', $args));
+ }
+
+ $form_state['redirect'] = 'file/' . $file->fid;
+
+ // Clear the page and block caches.
+ cache_clear_all();
+}
+
+/**
+ * Form submission handler for the 'Delete' button for file_entity_edit().
+ */
+function file_entity_edit_delete_submit($form, &$form_state) {
+ $fid = $form_state['values']['fid'];
+ $destination = array();
+ if (isset($_GET['destination'])) {
+ $destination = drupal_get_destination();
+ unset($_GET['destination']);
+ }
+ $form_state['redirect'] = array('file/' . $fid . '/delete', array('query' => $destination));
+
+ // Clear the page and block caches.
+ cache_clear_all();
+}
+
+/**
+ * Page callback: Form constructor for the file deletion confirmation form.
+ *
+ * Path: file/%file/delete
+ *
+ * @param object $file
+ * A file object from file_load().
+ *
+ * @see file_entity_menu()
+ */
+function file_entity_delete_form($form, &$form_state, $file) {
+ $form_state['file'] = $file;
+
+ $form['fid'] = array(
+ '#type' => 'value',
+ '#value' => $file->fid,
+ );
+
+ $description = t('This action cannot be undone.');
+ if ($references = file_usage_list($file)) {
+ $description .= ' ' . t('This file is currently in use and may cause problems if deleted.');
+ }
+
+ return confirm_form($form,
+ t('Are you sure you want to delete the file %title?', array(
+ '%title' => entity_label('file', $file),
+ )),
+ 'file/' . $file->fid,
+ $description,
+ t('Delete')
+ );
+}
+
+/**
+ * Form submission handler for file_entity_delete_form().
+ */
+function file_entity_delete_form_submit($form, &$form_state) {
+ if ($form_state['values']['confirm'] && $file = file_load($form_state['values']['fid'])) {
+ // Use file_delete_multiple() rather than file_delete() since we want to
+ // avoid unwanted validation and usage checking.
+ file_delete_multiple(array($file->fid));
+
+ $args = array(
+ '@type' => file_entity_type_get_name($file),
+ '%title' => entity_label('file', $file),
+ );
+ watchdog('file', '@type: deleted %title.', $args);
+ drupal_set_message(t('@type %title has been deleted.', $args));
+ }
+
+ $form_state['redirect'] = '<front>';
+
+ // Clear the page and block caches.
+ cache_clear_all();
+}
+
+/**
+ * Form constructor for file deletion confirmation form.
+ *
+ * @param array $files
+ * An array of file objects.
+ */
+function file_entity_multiple_delete_form($form, &$form_state, array $files) {
+ $form['files'] = array(
+ '#prefix' => '<ul>',
+ '#suffix' => '</ul>',
+ '#tree' => TRUE,
+ );
+
+ $files_have_usage = FALSE;
+ foreach ($files as $fid => $file) {
+ $title = entity_label('file', $file);
+ $usage = file_usage_list($file);
+ if (!empty($usage)) {
+ $files_have_usage = TRUE;
+ $title = t('@title (in use)', array('@title' => $title));
+ }
+ else {
+ $title = check_plain($title);
+ }
+ $form['files'][$fid] = array(
+ '#type' => 'hidden',
+ '#value' => $fid,
+ '#prefix' => '<li>',
+ '#suffix' => $title . "</li>\n",
+ );
+ }
+
+ $form['operation'] = array(
+ '#type' => 'hidden',
+ '#value' => 'delete',
+ );
+
+ $description = t('This action cannot be undone.');
+ if ($files_have_usage) {
+ $description .= ' ' . t('Some of the files are currently in use and may cause problems if deleted.');
+ }
+
+ return confirm_form(
+ $form,
+ format_plural(count($files), 'Are you sure you want to delete this file?', 'Are you sure you want to delete these files?'),
+ 'admin/content/file',
+ $description,
+ t('Delete')
+ );
+}
+
+/**
+ * Form submission handler for file_entity_multiple_delete_form().
+ */
+function file_entity_multiple_delete_form_submit($form, &$form_state) {
+ if ($form_state['values']['confirm'] && $fids = array_keys($form_state['values']['files'])) {
+ file_delete_multiple($fids);
+ $count = count($fids);
+ watchdog('file', 'Deleted @count files.', array('@count' => $count));
+ drupal_set_message(format_plural($count, 'Deleted one file.', 'Deleted @count files.'));
+ }
+ $form_state['redirect'] = 'admin/content/file';
+
+ // Clear the page and block caches.
+ cache_clear_all();
+}
+
+/**
+ * Page callback for the file edit form.
+ *
+ * @deprecated
+ * Use drupal_get_form('file_entity_edit')
+ */
+function file_entity_page_edit($file) {
+ return drupal_get_form('file_entity_edit', $file);
+}
+
+/**
+ * Page callback for the file deletion confirmation form.
+ *
+ * @deprecated
+ * Use drupal_get_form('file_entity_delete_form')
+ */
+function file_entity_page_delete($file) {
+ return drupal_get_form('file_entity_delete_form');
+}
+
+/**
+ * Retrieves the upload validators for a file.
+ *
+ * @param array $options
+ * (optional) An array of options for file validation.
+ *
+ * @return array
+ * An array suitable for passing to file_save_upload() or for a managed_file
+ * or upload element's '#upload_validators' property.
+ */
+function file_entity_get_upload_validators(array $options = array()) {
+ // Set up file upload validators.
+ $validators = array();
+
+ // Validate file extensions. If there are no file extensions in $params and
+ // there are no Media defaults, there is no file extension validation.
+ if (!empty($options['file_extensions'])) {
+ $validators['file_validate_extensions'] = array($options['file_extensions']);
+ }
+ else {
+ $validators['file_validate_extensions'] = array(variable_get('file_entity_default_allowed_extensions', 'jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp mp3 mov mp4 m4a m4v mpeg avi ogg oga ogv weba webp webm'));
+ }
+
+ // Cap the upload size according to the system or user defined limit.
+ $max_filesize = parse_size(file_upload_max_size());
+ $file_entity_max_filesize = parse_size(variable_get('file_entity_max_filesize', ''));
+
+ // If the user defined a size limit, use the smaller of the two.
+ if (!empty($file_entity_max_filesize)) {
+ $max_filesize = min($max_filesize, $file_entity_max_filesize);
+ }
+
+ if (!empty($options['max_filesize']) && $options['max_filesize'] < $max_filesize) {
+ $max_filesize = parse_size($options['max_filesize']);
+ }
+
+ // There is always a file size limit due to the PHP server limit.
+ $validators['file_validate_size'] = array($max_filesize);
+
+ // Add image validators.
+ $options += array('min_resolution' => 0, 'max_resolution' => 0);
+ if ($options['min_resolution'] || $options['max_resolution']) {
+ $validators['file_validate_image_resolution'] = array($options['max_resolution'], $options['min_resolution']);
+ }
+
+ // Add other custom upload validators from options.
+ if (!empty($options['upload_validators'])) {
+ $validators += $options['upload_validators'];
+ }
+
+ return $validators;
+}
+
+function file_entity_upload_archive_form($form, &$form_state) {
+ $options = array(
+ 'file_extensions' => archiver_get_extensions(),
+ );
+
+ $form['upload'] = array(
+ '#type' => 'managed_file',
+ '#title' => t('Upload an archive file'),
+ '#upload_location' => NULL, // Upload to the temporary directory.
+ '#upload_validators' => file_entity_get_upload_validators($options),
+ '#progress_indicator' => 'bar',
+ '#required' => TRUE,
+ '#pre_render' => array('file_managed_file_pre_render', 'file_entity_upload_validators_pre_render'),
+ );
+
+ $form['pattern'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Pattern'),
+ '#description' => t('Only files matching this pattern will be imported. For example, to import all jpg and gif files, the pattern would be <em>*.jpg|*.gif</em>. Use <em>.*</em> to extract all files in the archive.'),
+ '#default_value' => '.*',
+ '#required' => TRUE,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Submit'),
+ );
+
+ form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
+
+ return $form;
+}
+
+/**
+ * Upload a file.
+ */
+function file_entity_upload_archive_form_submit($form, &$form_state) {
+ $form_state['files'] = array();
+
+ if ($archive = file_load($form_state['values']['upload'])) {
+ if ($archiver = archiver_get_archiver($archive->uri)) {
+ $files = $archiver->listContents();
+
+ $extract_dir = file_default_scheme() . '://' . pathinfo($archive->filename, PATHINFO_FILENAME);
+ $extract_dir = file_destination($extract_dir, FILE_EXISTS_RENAME);
+ if (!file_prepare_directory($extract_dir, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY)) {
+ throw new Exception(t('Unable to prepar, e directory %dir for extraction.', array('%dir' => $extract_dir)));
+ }
+
+ $archiver->extract($extract_dir);
+ $pattern = '/' . $form_state['values']['pattern'] . '/';
+ if ($files = file_scan_directory($extract_dir, $pattern)) {
+ foreach ($files as $file) {
+ $file->status = FILE_STATUS_PERMANENT;
+ $file->uid = $archive->uid;
+ file_save($file);
+ $form_state['files'][$file->fid] = $file;
+ }
+ }
+ drupal_set_message(t('Extracted %file and added @count new files.', array('%file' => $archive->filename, '@count' => count($files))));
+ }
+ else {
+ throw new Exception(t('Cannot extract %file, not a valid archive.', array('%file' => $archive->uri)));
+ }
+ }
+
+ // Redirect to the file edit page.
+ if (file_entity_access('edit') && module_exists('multiform')) {
+ $destination = array('destination' => 'admin/content/file');
+ if (isset($_GET['destination'])) {
+ $destination = drupal_get_destination();
+ unset($_GET['destination']);
+ }
+ $form_state['redirect'] = array('admin/content/file/edit-multiple/' . implode(' ', array_keys($form_state['files'])), array('query' => $destination));
+ }
+ else {
+ $form_state['redirect'] = 'admin/content/file';
+ }
+}
diff --git a/sites/all/modules/file_entity/file_entity.pathauto.inc b/sites/all/modules/file_entity/file_entity.pathauto.inc
new file mode 100644
index 000000000..3b3d6957e
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.pathauto.inc
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Pathauto integration for files.
+ *
+ * @ingroup pathauto
+ */
+
+/**
+ * Implements hook_path_alias_types().
+ */
+function file_entity_path_alias_types() {
+ $objects['file/'] = t('Files');
+
+ return $objects;
+}
+
+/**
+ * Implements hook_pathauto().
+ */
+function file_entity_pathauto($op) {
+ switch ($op) {
+ case 'settings':
+ $settings = array();
+ $settings['module'] = 'file';
+ $settings['token_type'] = 'file';
+ $settings['groupheader'] = t('File paths');
+ $settings['patterndescr'] = t('Default path pattern (applies to all file types with blank patterns below)');
+ $settings['patterndefault'] = 'files/[file:name]';
+ $settings['batch_update_callback'] = 'file_entity_pathauto_bulk_update_batch_process';
+ $settings['batch_file'] = drupal_get_path('module', 'file_entity') . '/file_entity.pathauto.inc';
+
+ foreach (file_type_get_enabled_types() as $file_type => $type) {
+ $settings['patternitems'][$file_type] = t('Pattern for all @file_type paths.', array('@file_type' => $type->label));
+ }
+ return (object) $settings;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Batch processing callback; Generate aliases for files.
+ */
+function file_entity_pathauto_bulk_update_batch_process(&$context) {
+ if (!isset($context['sandbox']['current'])) {
+ $context['sandbox']['count'] = 0;
+ $context['sandbox']['current'] = 0;
+ }
+
+ $query = db_select('file_managed', 'fm');
+ $query->leftJoin('url_alias', 'ua', "CONCAT('file/', fm.fid) = ua.source");
+ $query->addField('fm', 'fid');
+ $query->isNull('ua.source');
+ $query->condition('fm.fid', $context['sandbox']['current'], '>');
+ $query->orderBy('fm.fid');
+ $query->addTag('pathauto_bulk_update');
+ $query->addMetaData('entity', 'file');
+
+ // Get the total amount of items to process.
+ if (!isset($context['sandbox']['total'])) {
+ $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField();
+
+ // If there are no files to update, the stop immediately.
+ if (!$context['sandbox']['total']) {
+ $context['finished'] = 1;
+ return;
+ }
+ }
+
+ $query->range(0, 25);
+ $fids = $query->execute()->fetchCol();
+
+ pathauto_file_update_alias_multiple($fids, 'bulkupdate');
+ $context['sandbox']['count'] += count($fids);
+ $context['sandbox']['current'] = max($fids);
+ $context['message'] = t('Updated alias for file @fid.', array('@fid' => end($fids)));
+
+ if ($context['sandbox']['count'] != $context['sandbox']['total']) {
+ $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+ }
+}
diff --git a/sites/all/modules/file_entity/file_entity.test b/sites/all/modules/file_entity/file_entity.test
new file mode 100644
index 000000000..2c72cbd86
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.test
@@ -0,0 +1,1635 @@
+<?php
+
+/**
+ * @file
+ * Test integration for the file_entity module.
+ */
+
+class FileEntityTestHelper extends DrupalWebTestCase {
+ function setUp() {
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
+ }
+ $modules[] = 'file_entity';
+ parent::setUp($modules);
+ }
+
+ /**
+ * Retrieves a sample file of the specified type.
+ */
+ function getTestFile($type_name, $size = NULL) {
+ // Get a file to upload.
+ $file = current($this->drupalGetTestFiles($type_name, $size));
+
+ // Add a filesize property to files as would be read by file_load().
+ $file->filesize = filesize($file->uri);
+
+ return $file;
+ }
+
+ /**
+ * Retrieves the fid of the last inserted file.
+ */
+ function getLastFileId() {
+ return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
+ }
+
+ /**
+ * Get a file from the database based on its filename.
+ *
+ * @param $filename
+ * A file filename, usually generated by $this->randomName().
+ * @param $reset
+ * (optional) Whether to reset the internal file_load() cache.
+ *
+ * @return
+ * A file object matching $filename.
+ */
+ function getFileByFilename($filename, $reset = FALSE) {
+ $files = file_load_multiple(array(), array('filename' => $filename), $reset);
+ // Load the first file returned from the database.
+ $returned_file = reset($files);
+ return $returned_file;
+ }
+
+ protected function createFileEntity($settings = array()) {
+ // Populate defaults array.
+ $settings += array(
+ 'filepath' => 'Файл для тестирования ' . $this->randomName(), // Prefix with non-latin characters to ensure that all file-related tests work with international filenames.
+ 'filemime' => 'text/plain',
+ 'uid' => 1,
+ 'timestamp' => REQUEST_TIME,
+ 'status' => FILE_STATUS_PERMANENT,
+ 'contents' => "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.",
+ 'scheme' => file_default_scheme(),
+ 'type' => NULL,
+ );
+
+ $filepath = $settings['scheme'] . '://' . $settings['filepath'];
+
+ file_put_contents($filepath, $settings['contents']);
+ $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
+
+ $file = new stdClass();
+ $file->uri = $filepath;
+ $file->filename = drupal_basename($file->uri);
+ $file->filemime = $settings['filemime'];
+ $file->uid = $settings['uid'];
+ $file->timestamp = $settings['timestamp'];
+ $file->filesize = filesize($file->uri);
+ $file->status = $settings['status'];
+ $file->type = $settings['type'];
+
+ // The file type is used as a bundle key, and therefore, must not be NULL.
+ if (!isset($file->type)) {
+ $file->type = FILE_TYPE_NONE;
+ }
+
+ // If the file isn't already assigned a real type, determine what type should
+ // be assigned to it.
+ if ($file->type === FILE_TYPE_NONE) {
+ $type = file_get_type($file);
+ if (isset($type)) {
+ $file->type = $type;
+ }
+ }
+
+ // Write the record directly rather than calling file_save() so we don't
+ // invoke the hooks.
+ $this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, t('The file was added to the database.'), 'Create test file');
+
+ return $file;
+ }
+
+ protected function createFileType($overrides = array()) {
+ $type = new stdClass();
+ $type->type = 'test';
+ $type->label = "Test";
+ $type->description = '';
+ $type->mimetypes = array('image/jpeg', 'image/gif', 'image/png', 'image/tiff');
+
+ foreach ($overrides as $k => $v) {
+ $type->$k = $v;
+ }
+
+ file_type_save($type);
+ return $type;
+ }
+
+ /**
+ * Overrides DrupalWebTestCase::drupalGetToken() to support the hash salt.
+ *
+ * @todo Remove when http://drupal.org/node/1555862 is fixed in core.
+ */
+ protected function drupalGetToken($value = '') {
+ $private_key = drupal_get_private_key();
+ return drupal_hmac_base64($value, $this->session_id . $private_key . drupal_get_hash_salt());
+ }
+}
+
+/**
+ * Tests file type classification functionality.
+ */
+class FileEntityFileTypeClassificationTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity classification',
+ 'description' => 'Test existing file entity classification functionality.',
+ 'group' => 'File entity',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ * Test that existing files are properly classified by file type.
+ */
+ function testFileTypeClassification() {
+ // Get test text and image files.
+ $file = current($this->drupalGetTestFiles('text'));
+ $text_file = file_save($file);
+ $file = current($this->drupalGetTestFiles('image'));
+ $image_file = file_save($file);
+
+ // Enable file entity which adds adds a file type property to files and
+ // queues up existing files for classification.
+ module_enable(array('file_entity'));
+
+ // Existing files have yet to be classified and should have an undefined
+ // file type.
+ $file_type = $this->getFileType($text_file);
+ $this->assertEqual($file_type['type'], 'undefined', t('The text file has an undefined file type.'));
+ $file_type = $this->getFileType($image_file);
+ $this->assertEqual($file_type['type'], 'undefined', t('The image file has an undefined file type.'));
+
+ // The classification queue is processed during cron runs. Run cron to
+ // trigger the classification process.
+ $this->cronRun();
+
+ // The classification process should assign a file type to any file whose
+ // MIME type is assigned to a file type. Check to see if each file was
+ // assigned a proper file type.
+ $file_type = $this->getFileType($text_file);
+ $this->assertEqual($file_type['type'], 'document', t('The text file was properly assigned the Document file type.'));
+ $file_type = $this->getFileType($image_file);
+ $this->assertEqual($file_type['type'], 'image', t('The image file was properly assigned the Image file type.'));
+ }
+
+ /**
+ * Get the file type of a given file.
+ *
+ * @param $file
+ * A file object.
+ *
+ * @return
+ * The file's file type as a string.
+ */
+ function getFileType($file) {
+ $type = db_select('file_managed', 'fm')
+ ->fields('fm', array('type'))
+ ->condition('fid', $file->fid, '=')
+ ->execute()
+ ->fetchAssoc();
+
+ return $type;
+ }
+}
+
+/**
+ * Tests basic file entity functionality.
+ */
+class FileEntityUnitTestCase extends FileEntityTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity unit tests',
+ 'description' => 'Test basic file entity functionality.',
+ 'group' => 'File entity',
+ );
+ }
+
+ /**
+ * Regression tests for core issue http://drupal.org/node/1239376.
+ */
+ function testMimeTypeMappings() {
+ $tests = array(
+ 'public://test.ogg' => 'audio/ogg',
+ 'public://test.mkv' => 'video/x-m4v',
+ 'public://test.mka' => 'audio/x-matroska',
+ 'public://test.mkv' => 'video/x-matroska',
+ 'public://test.webp' => 'image/webp',
+ );
+ foreach ($tests as $input => $expected) {
+ $this->assertEqual(file_get_mimetype($input), $expected);
+ }
+ }
+
+ /**
+ * Tests basic file entity properties.
+ */
+ function testFileEntity() {
+ // Save a raw file, turning it into a file entity.
+ $file = $this->getTestFile('text');
+ $file->uid = 1;
+ $file->status = FILE_STATUS_PERMANENT;
+ file_save($file);
+
+ // Test entity ID, revision ID, and bundle.
+ $ids = entity_extract_ids('file', $file);
+ $this->assertIdentical($ids, array($file->fid, NULL, 'document'));
+
+ // Test the entity URI callback.
+ $uri = entity_uri('file', $file);
+ $this->assertEqual($uri['path'], "file/{$file->fid}");
+ }
+
+ /**
+ * Tests storing image height and width as file metadata.
+ */
+ function testImageDimensions() {
+ // Test hook_file_insert().
+ $file = current($this->drupalGetTestFiles('image'));
+ $image_file = file_save($file);
+ $this->assertTrue(isset($image_file->metadata['height']), 'Image height retrieved on file_save() for an image file.');
+ $this->assertTrue(isset($image_file->metadata['width']), 'Image width retrieved on file_save() for an image file.');
+
+ $file = current($this->drupalGetTestFiles('text'));
+ $text_file = file_save($file);
+ $this->assertFalse(isset($text_file->metadata['height']), 'No image height retrieved on file_save() for an text file.');
+ $this->assertFalse(isset($text_file->metadata['width']), 'No image width retrieved on file_save() for an text file.');
+
+ // Test hook_file_load().
+ // Clear the cache and load fresh files objects to test file_load behavior.
+ entity_get_controller('file')->resetCache();
+
+ $file = file_load($image_file->fid);
+ $this->assertTrue(isset($file->metadata['height']), 'Image dimensions retrieved on file_load() for an image file.');
+ $this->assertTrue(isset($file->metadata['width']), 'Image dimensions retrieved on file_load() for an image file.');
+
+ $this->assertEqual($file->metadata['height'], $image_file->metadata['height'], 'Loaded image height is equal to saved image height.');
+ $this->assertEqual($file->metadata['width'], $image_file->metadata['width'], 'Loaded image width is equal to saved image width.');
+
+ $file = file_load($text_file->fid);
+ $this->assertFalse(isset($file->metadata['height']), 'No image height retrieved on file_load() for an text file.');
+ $this->assertFalse(isset($file->metadata['width']), 'No image width retrieved on file_load() for an text file.');
+
+ // Test hook_file_update().
+ // Load the first image file and resize it.
+ $height = $image_file->metadata['width'] / 2;
+ $width = $image_file->metadata['height'] / 2;
+ $image = image_load($image_file->uri);
+ image_resize($image, $width, $height);
+ image_save($image);
+ file_save($image_file);
+
+ $this->assertEqual($image_file->metadata['height'], $height, 'Image file height updated by file_save().');
+ $this->assertEqual($image_file->metadata['width'], $width, 'Image file width updated by file_save().');
+
+ // Clear the cache and reload the file.
+ entity_get_controller('file')->resetCache();
+
+ $file = file_load($image_file->fid);
+ $this->assertEqual($file->metadata['height'], $height, 'Updated image height retrieved by file_load().');
+ $this->assertEqual($file->metadata['width'], $width, 'Updated image width retrieved by file_load().');
+
+ // Verify that the image dimension metadata is removed on file deletion.
+ file_delete($file, TRUE);
+ $this->assertFalse(db_query('SELECT COUNT(*) FROM {file_metadata} WHERE fid = :fid', array(':fid' => 'fid'))->fetchField(), 'Row deleted in {file_metadata} on file_delete().');
+ }
+}
+
+/**
+ * Tests editing existing file entities.
+ */
+class FileEntityEditTestCase extends FileEntityTestHelper {
+ protected $web_user;
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity edit',
+ 'description' => 'Create a file and test file edit functionality.',
+ 'group' => 'File entity',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->web_user = $this->drupalCreateUser(array('edit own document files', 'create files'));
+ $this->admin_user = $this->drupalCreateUser(array('bypass file access', 'administer files'));
+ }
+
+ /**
+ * Check file edit functionality.
+ */
+ function testFileEntityEdit() {
+ $this->drupalLogin($this->web_user);
+
+ $test_file = $this->getTestFile('text');
+ $name_key = "filename";
+
+ // Create file to edit.
+ $edit = array();
+ $edit['files[upload]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost('file/add', $edit, t('Next'));
+ if ($this->xpath('//input[@name="scheme"]')) {
+ $this->drupalPost(NULL, array(), t('Next'));
+ }
+
+ // Check that the file exists in the database.
+ $file = $this->getFileByFilename($test_file->filename);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that "edit" link points to correct page.
+ $this->clickLink(t('Edit'));
+ $edit_url = url("file/$file->fid/edit", array('absolute' => TRUE));
+ $actual_url = $this->getURL();
+ $this->assertEqual($edit_url, $actual_url, t('On edit page.'));
+
+ // Check that the name field is displayed with the correct value.
+ $active = '<span class="element-invisible">' . t('(active tab)') . '</span>';
+ $link_text = t('!local-task-title!active', array('!local-task-title' => t('Edit'), '!active' => $active));
+ $this->assertText(strip_tags($link_text), 0, t('Edit tab found and marked active.'));
+ $this->assertFieldByName($name_key, $file->filename, t('Name field displayed.'));
+
+ // The user does not have "delete" permissions so no delete button should be found.
+ $this->assertNoFieldByName('op', t('Delete'), 'Delete button not found.');
+
+ // Edit the content of the file.
+ $edit = array();
+ $edit[$name_key] = $this->randomName(8);
+ // Stay on the current page, without reloading.
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Check that the name field is displayed with the updated values.
+ $this->assertText($edit[$name_key], t('Name displayed.'));
+ }
+
+ /**
+ * Check changing file associated user fields.
+ */
+ function testFileEntityAssociatedUser() {
+ $this->drupalLogin($this->admin_user);
+
+ // Create file to edit.
+ $test_file = $this->getTestFile('text');
+ $name_key = "filename";
+ $edit = array();
+ $edit['files[upload]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost('file/add', $edit, t('Next'));
+
+ // Check that the file was associated with the currently logged in user.
+ $file = $this->getFileByFilename($test_file->filename);
+ $this->assertIdentical($file->uid, $this->admin_user->uid, 'File associated with admin user.');
+
+ // Try to change the 'associated user' field to an invalid user name.
+ $edit = array(
+ 'name' => 'invalid-name',
+ );
+ $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+ $this->assertText('The username invalid-name does not exist.');
+
+ // Change the associated user field to an empty string, which should assign
+ // association to the anonymous user (uid 0).
+ $edit['name'] = '';
+ $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+ $file = file_load($file->fid);
+ $this->assertIdentical($file->uid, '0', 'File associated with anonymous user.');
+
+ // Change the associated user field to another user's name (that is not
+ // logged in).
+ $edit['name'] = $this->web_user->name;
+ $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+ $file = file_load($file->fid);
+ $this->assertIdentical($file->uid, $this->web_user->uid, 'File associated with normal user.');
+
+ // Check that normal users cannot change the associated user information.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('file/' . $file->fid . '/edit');
+ $this->assertNoFieldByName('name');
+ }
+}
+
+/**
+ * Tests creating new file entities through the file upload wizard.
+ */
+class FileEntityUploadWizardTestCase extends FileEntityTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity upload wizard',
+ 'description' => 'Upload a file using the multi-step wizard.',
+ 'group' => 'File entity',
+ 'dependencies' => array('token'),
+ );
+ }
+
+ function setUp() {
+ parent::setUp('token');
+
+ // Disable the private file system which is automatically enabled by
+ // DrupalTestCase so we can test the upload wizard correctly.
+ variable_del('file_private_path');
+
+ $web_user = $this->drupalCreateUser(array('create files', 'view own private files'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Test the basic file upload wizard functionality.
+ */
+ function testFileEntityUploadWizardBasic() {
+ $test_file = $this->getTestFile('text');
+
+ // Step 1: Upload a basic document file.
+ $edit = array();
+ $edit['files[upload]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost('file/add', $edit, t('Next'));
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the document file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Document', '%name' => $file->filename)), t('Document file uploaded.'));
+ }
+
+ /**
+ * Test the file upload wizard type step.
+ */
+ function testFileEntityUploadWizardTypes() {
+ $test_file = $this->getTestFile('text');
+
+ // Create multiple file types with the same mime types.
+ $this->createFileType(array('type' => 'document1', 'label' => 'Document 1', 'mimetypes' => array('text/plain')));
+ $this->createFileType(array('type' => 'document2', 'label' => 'Document 2', 'mimetypes' => array('text/plain')));
+
+ // Step 1: Upload a basic document file.
+ $edit = array();
+ $edit['files[upload]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost('file/add', $edit, t('Next'));
+
+ // Step 2: File type selection.
+ $edit = array();
+ $edit['type'] = 'document2';
+ $this->drupalPost(NULL, $edit, t('Next'));
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the document file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Document 2', '%name' => $file->filename)), t('Document 2 file uploaded.'));
+ }
+
+ /**
+ * Test the file upload wizard scheme step.
+ */
+ function testFileEntityUploadWizardSchemes() {
+ $test_file = $this->getTestFile('text');
+
+ // Enable the private file system.
+ variable_set('file_private_path', $this->private_files_directory);
+
+ // Step 1: Upload a basic document file.
+ $edit = array();
+ $edit['files[upload]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost('file/add', $edit, t('Next'));
+
+ // Step 3: Scheme selection.
+ $edit = array();
+ $edit['scheme'] = 'private';
+ $this->drupalPost(NULL, $edit, t('Next'));
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the document file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Document', '%name' => $file->filename)), t('Document file uploaded.'));
+ }
+
+ /**
+ * Test the file upload wizard field step.
+ */
+ function testFileEntityUploadWizardFields() {
+ $test_file = $this->getTestFile('image');
+ $filename = $this->randomName();
+ $alt = $this->randomName();
+ $title = $this->randomName();
+
+ // Step 1: Upload a basic image file.
+ $edit = array();
+ $edit['files[upload]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost('file/add', $edit, t('Next'));
+
+ // Step 4: Attached fields.
+ $edit = array();
+ $edit['filename'] = $filename;
+ $edit['field_file_image_alt_text[' . LANGUAGE_NONE . '][0][value]'] = $alt;
+ $edit['field_file_image_title_text[' . LANGUAGE_NONE . '][0][value]'] = $title;
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the image file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Image', '%name' => $filename)), t('Image file uploaded.'));
+
+ // Check that the alt and title text was loaded from the fields.
+ $this->assertEqual($file->alt, $alt, t('Alt text was stored as file metadata.'));
+ $this->assertEqual($file->title, $title, t('Title text was stored as file metadata.'));
+ }
+
+ /**
+ * Test skipping each of the file upload wizard steps.
+ */
+ function testFileEntityUploadWizardStepSkipping() {
+ $test_file = $this->getTestFile('image');
+ $filename = $this->randomName();
+
+ // Ensure that the file is affected by every step.
+ variable_set('file_private_path', $this->private_files_directory);
+
+ $this->createFileType(array('type' => 'image1', 'label' => 'Image 1', 'mimetypes' => array('image/jpeg', 'image/gif', 'image/png', 'image/tiff')));
+ $this->createFileType(array('type' => 'image2', 'label' => 'Image 2', 'mimetypes' => array('image/jpeg', 'image/gif', 'image/png', 'image/tiff')));
+
+ $field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $field = array('field_name' => $field_name, 'type' => 'text');
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => $field_name,
+ 'entity_type' => 'file',
+ 'bundle' => 'image2',
+ 'label' => $this->randomName() . '_label',
+ );
+ field_create_instance($instance);
+
+ // Test skipping each upload wizard step.
+ foreach (array('types', 'schemes', 'fields') as $step) {
+ // Step to skip.
+ switch ($step) {
+ case 'types':
+ variable_set('file_entity_file_upload_wizard_skip_file_type', TRUE);
+ break;
+ case 'schemes':
+ variable_set('file_entity_file_upload_wizard_skip_scheme', TRUE);
+ break;
+ case 'fields':
+ variable_set('file_entity_file_upload_wizard_skip_fields', TRUE);
+ break;
+ }
+
+ // Step 1: Upload a basic image file.
+ $edit = array();
+ $edit['files[upload]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost('file/add', $edit, t('Next'));
+
+ // Step 2: File type selection.
+ if ($step != 'types') {
+ $edit = array();
+ $edit['type'] = 'image2';
+ $this->drupalPost(NULL, $edit, t('Next'));
+ }
+
+ // Step 3: Scheme selection.
+ if ($step != 'schemes') {
+ $edit = array();
+ $edit['scheme'] = 'private';
+ $this->drupalPost(NULL, $edit, t('Next'));
+ }
+
+ // Step 4: Attached fields.
+ if ($step != 'fields') {
+ // Skipping file type selection essentially skips this step as well
+ // because the file will not be assigned a type so no fields will be
+ // available.
+ if ($step != 'types') {
+ $edit = array();
+ $edit['filename'] = $filename;
+ $edit[$field_name . '[' . LANGUAGE_NONE . '][0][value]'] = $this->randomName();
+ $this->drupalPost(NULL, $edit, t('Save'));
+ }
+ }
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Determine the file's file type.
+ $type = file_type_load($file->type);
+
+ // Check that the image file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => $type->label, '%name' => $file->filename)), t('Image file uploaded.'));
+
+ // Reset 'skip' variables.
+ variable_del('file_entity_file_upload_wizard_skip_file_type');
+ variable_del('file_entity_file_upload_wizard_skip_scheme');
+ variable_del('file_entity_file_upload_wizard_skip_fields');
+ }
+ }
+}
+
+/**
+ * Test file administration page functionality.
+ */
+class FileEntityAdminTestCase extends FileEntityTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File administration',
+ 'description' => 'Test file administration page functionality.',
+ 'group' => 'File entity',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Remove the "view files" permission which is set
+ // by default for all users so we can test this permission
+ // correctly.
+ $roles = user_roles();
+ foreach ($roles as $rid => $role) {
+ user_role_revoke_permissions($rid, array('view files'));
+ }
+
+ $this->admin_user = $this->drupalCreateUser(array('administer files', 'bypass file access'));
+ $this->base_user_1 = $this->drupalCreateUser(array('administer files'));
+ $this->base_user_2 = $this->drupalCreateUser(array('administer files', 'view own private files'));
+ $this->base_user_3 = $this->drupalCreateUser(array('administer files', 'view private files'));
+ $this->base_user_4 = $this->drupalCreateUser(array('administer files', 'edit any document files', 'delete any document files', 'edit any image files', 'delete any image files'));
+ }
+
+ /**
+ * Tests that the table sorting works on the files admin pages.
+ */
+ function testFilesAdminSort() {
+ $this->drupalLogin($this->admin_user);
+ $i = 0;
+ foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) {
+ $this->createFileEntity(array('filepath' => $prefix . $this->randomName(6), 'timestamp' => $i));
+ $i++;
+ }
+
+ // Test that the default sort by file_managed.timestamp DESC actually fires properly.
+ $files_query = db_select('file_managed', 'fm')
+ ->fields('fm', array('fid'))
+ ->orderBy('timestamp', 'DESC')
+ ->execute()
+ ->fetchCol();
+
+ $files_form = array();
+ $this->drupalGet('admin/content/file');
+ foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) {
+ $files_form[] = $input;
+ }
+ $this->assertEqual($files_query, $files_form, 'Files are sorted in the form according to the default query.');
+
+ // Compare the rendered HTML node list to a query for the files ordered by
+ // filename to account for possible database-dependent sort order.
+ $files_query = db_select('file_managed', 'fm')
+ ->fields('fm', array('fid'))
+ ->orderBy('filename')
+ ->execute()
+ ->fetchCol();
+
+ $files_form = array();
+ $this->drupalGet('admin/content/file', array('query' => array('sort' => 'asc', 'order' => 'Title')));
+ foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) {
+ $files_form[] = $input;
+ }
+ $this->assertEqual($files_query, $files_form, 'Files are sorted in the form the same as they are in the query.');
+ }
+
+ /**
+ * Tests files overview with different user permissions.
+ */
+ function testFilesAdminPages() {
+ $this->drupalLogin($this->admin_user);
+
+ $files['public_image'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_1->uid, 'type' => 'image'));
+ $files['public_document'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_2->uid, 'type' => 'document'));
+ $files['private_image'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_1->uid, 'type' => 'image'));
+ $files['private_document'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_2->uid, 'type' => 'document'));
+
+ // Verify view, usage, edit, and delete links for any file.
+ $this->drupalGet('admin/content/file');
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $this->assertLinkByHref('file/' . $file->fid);
+ $this->assertLinkByHref('file/' . $file->fid . '/usage');
+ $this->assertLinkByHref('file/' . $file->fid . '/edit');
+ $this->assertLinkByHref('file/' . $file->fid . '/delete');
+ // Verify tableselect.
+ $this->assertFieldByName('files[' . $file->fid . ']', '', t('Tableselect found.'));
+ }
+
+ // Verify no operation links are displayed for regular users.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_1);
+ $this->drupalGet('admin/content/file');
+ $this->assertResponse(200);
+ $this->assertLinkByHref('file/' . $files['public_image']->fid);
+ $this->assertLinkByHref('file/' . $files['public_document']->fid);
+ $this->assertNoLinkByHref('file/' . $files['public_image']->fid . '/edit');
+ $this->assertNoLinkByHref('file/' . $files['public_image']->fid . '/delete');
+ $this->assertNoLinkByHref('file/' . $files['public_document']->fid . '/edit');
+ $this->assertNoLinkByHref('file/' . $files['public_document']->fid . '/delete');
+
+ // Verify no tableselect.
+ $this->assertNoFieldByName('files[' . $files['public_image']->fid . ']', '', t('No tableselect found.'));
+
+ // Verify private file is displayed with permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_2);
+ $this->drupalGet('admin/content/file');
+ $this->assertResponse(200);
+ $this->assertLinkByHref('file/' . $files['private_document']->fid);
+ // Verify no operation links are displayed.
+ $this->assertNoLinkByHref('file/' . $files['private_document']->fid . '/edit');
+ $this->assertNoLinkByHref('file/' . $files['private_document']->fid . '/delete');
+
+ // Verify user cannot see private file of other users.
+ $this->assertNoLinkByHref('file/' . $files['private_image']->fid);
+ $this->assertNoLinkByHref('file/' . $files['private_image']->fid . '/edit');
+ $this->assertNoLinkByHref('file/' . $files['private_image']->fid . '/delete');
+
+ // Verify no tableselect.
+ $this->assertNoFieldByName('files[' . $files['private_document']->fid . ']', '', t('No tableselect found.'));
+
+ // Verify private file is displayed with permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_3);
+ $this->drupalGet('admin/content/file');
+ $this->assertResponse(200);
+
+ // Verify user can see private file of other users.
+ $this->assertLinkByHref('file/' . $files['private_document']->fid);
+ $this->assertLinkByHref('file/' . $files['private_image']->fid);
+
+ // Verify operation links are displayed for users with appropriate permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_4);
+ $this->drupalGet('admin/content/file');
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $this->assertLinkByHref('file/' . $file->fid);
+ $this->assertLinkByHref('file/' . $file->fid . '/usage');
+ $this->assertLinkByHref('file/' . $file->fid . '/edit');
+ $this->assertLinkByHref('file/' . $file->fid . '/delete');
+ }
+
+ // Verify file access can be bypassed.
+ $this->drupalLogout();
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet('admin/content/file');
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $this->assertLinkByHref('file/' . $file->fid);
+ $this->assertLinkByHref('file/' . $file->fid . '/usage');
+ $this->assertLinkByHref('file/' . $file->fid . '/edit');
+ $this->assertLinkByHref('file/' . $file->fid . '/delete');
+ }
+ }
+}
+
+/**
+ * Tests the file usage page.
+ */
+class FileEntityUsageTestCase extends FileEntityTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity usage',
+ 'description' => 'Create a file and verify its usage.',
+ 'group' => 'File entity',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $web_user = $this->drupalCreateUser(array('create files', 'bypass file access', 'edit own article content'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Create a file and verify its usage information.
+ */
+ function testFileEntityUsagePage() {
+ $image_field = 'field_image';
+ $image = $this->getTestFile('image');
+
+ // Create a node, save it, then edit it to upload a file.
+ $edit = array(
+ "files[" . $image_field . "_" . LANGUAGE_NONE . "_0]" => drupal_realpath($image->uri),
+ );
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Load the uploaded file.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+
+ // View the file's usage page.
+ $this->drupalGet('file/' . $file->fid . '/usage');
+
+ // Verify that a link to the entity is available.
+ $this->assertLink($node->title);
+ $this->assertLinkByHref('node/' . $node->nid);
+
+ // Verify that the entity type and use count information is also present.
+ $expected_values = array(
+ 'type' => 'node',
+ 'count' => 1,
+ );
+ foreach ($expected_values as $name => $value) {
+ $this->assertTrue($this->xpath('//table/tbody/tr/td[normalize-space(text())=:text]', array(':text' => $value)), t('File usage @name was found in the table.', array('@name' => $name)));
+ }
+
+ // Add a reference to the file from the same entity but registered by a
+ // different module to ensure that the usage count is incremented and no
+ // additional table rows are created.
+ file_usage_add($file, 'example_module', 'node', $node->nid, 2);
+
+ // Reload the page and verify that the expected values are present.
+ $this->drupalGet('file/' . $file->fid . '/usage');
+ $expected_values['count'] = 3;
+ foreach ($expected_values as $name => $value) {
+ $this->assertTrue($this->xpath('//table/tbody/tr/td[normalize-space(text())=:text]', array(':text' => $value)), t('File usage @name was found in the table.', array('@name' => $name)));
+ }
+
+ // Add a reference to the file from an entity that doesn't exist to ensure
+ // that this case is handled.
+ file_usage_add($file, 'test_module', 'imaginary', 1);
+
+ // Reload the page.
+ $this->drupalGet('file/' . $file->fid . '/usage');
+
+ // Verify that the module name is used in place of a link to the entity.
+ $this->assertNoLink('test_module');
+ $this->assertRaw('test_module', 'Module name used in place of link to the entity.');
+
+ // Verify that the entity type and use count information is also present.
+ $expected_values = array(
+ 'type' => 'imaginary',
+ 'count' => 1,
+ );
+ foreach ($expected_values as $name => $value) {
+ $this->assertTrue($this->xpath('//table/tbody/tr/td[normalize-space(text())=:text]', array(':text' => $value)), t('File usage @name was found in the table.', array('@name' => $name)));
+ }
+ }
+}
+
+/**
+ * Tests image alt and title text.
+ */
+class FileEntityAltTitleTestCase extends FileEntityTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity alt and title text',
+ 'description' => 'Create an image file with alt and title text.',
+ 'group' => 'File entity',
+ 'dependencies' => array('token'),
+ );
+ }
+
+ function setUp() {
+ parent::setUp('token');
+
+ $web_user = $this->drupalCreateUser(array('create files', 'edit own image files'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Create an "image" file and verify its associated alt and title text.
+ */
+ function testFileEntityAltTitle() {
+ $test_file = $this->getTestFile('image');
+ $alt_field_name = 'field_file_image_alt_text'; // Name of the default alt text field added to the image file type.
+ $title_field_name = 'field_file_image_title_text'; // Name of the default title text field added to the image file type.
+
+ // Create a file.
+ $edit = array();
+ $edit['files[upload]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost('file/add', $edit, t('Next'));
+
+ // Step 2: Scheme selection.
+ if ($this->xpath('//input[@name="scheme"]')) {
+ $this->drupalPost(NULL, array(), t('Next'));
+ }
+
+ // Step 3: Attached fields.
+ $alt = 'Quote&quot; Amp&amp; ' . 'Файл для тестирования ' . $this->randomName(); // Generate alt text containing HTML entities, spaces and non-latin characters.
+ $title = 'Quote&quot; Amp&amp; ' . 'Файл для тестирования ' . $this->randomName(); // Generate title text containing HTML entities, spaces and non-latin characters.
+
+ $edit = array();
+ $edit[$alt_field_name . '[' . LANGUAGE_NONE . '][0][value]'] = $alt;
+ $edit[$title_field_name . '[' . LANGUAGE_NONE . '][0][value]'] = $title;
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Check that the image file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Image', '%name' => $test_file->filename)), t('Image file uploaded.'));
+
+ // Check that the file exists in the database.
+ $file = $this->getFileByFilename($test_file->filename);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the alt and title text was loaded from the fields.
+ $this->assertEqual($file->alt, $alt, t('Alt text was stored as file metadata.'));
+ $this->assertEqual($file->title, $title, t('Title text was stored as file metadata.'));
+
+ // Verify that the alt and title text is present on the page.
+ $image_info = array(
+ 'path' => $file->uri,
+ 'alt' => $alt,
+ 'title' => $title,
+ 'width' => $file->width,
+ 'height' => $file->height,
+ );
+ $default_output = theme('image', $image_info);
+ $this->assertRaw($default_output, 'Image displayed using user supplied alt and title attributes.');
+
+ // Verify that the alt and title text can be edited.
+ $new_alt = $this->randomName();
+ $new_title = $this->randomName();
+
+ $edit = array();
+ $edit[$alt_field_name . '[' . LANGUAGE_NONE . '][0][value]'] = $new_alt;
+ $edit[$title_field_name . '[' . LANGUAGE_NONE . '][0][value]'] = $new_title;
+ $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+
+ $image_info = array(
+ 'path' => $file->uri,
+ 'alt' => $new_alt,
+ 'title' => $new_title,
+ 'width' => $file->width,
+ 'height' => $file->height,
+ );
+ $default_output = theme('image', $image_info);
+ $this->assertRaw($default_output, 'Image displayed using updated alt and title attributes.');
+ }
+}
+
+/**
+ * Tests replacing the file associated with a file entity.
+ */
+class FileEntityReplaceTestCase extends FileEntityTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File replacement',
+ 'description' => 'Test file replace functionality.',
+ 'group' => 'File entity',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ * @todo Test image dimensions for an image field are reset when a file is replaced.
+ * @todo Test image styles are cleared when an image is updated.
+ */
+ function testReplaceFile() {
+ // Select the first text test file to use.
+ $file = $this->createFileEntity(array('type' => 'document'));
+
+ // Create a user with file edit permissions.
+ $user = $this->drupalCreateUser(array('edit any document files'));
+ $this->drupalLogin($user);
+
+ // Test that the Upload widget appears for a local file.
+ $this->drupalGet('file/' . $file->fid . '/edit');
+ $this->assertFieldByName('files[replace_upload]');
+
+ // Test that file saves without uploading a file.
+ $this->drupalPost(NULL, array(), t('Save'));
+ $this->assertText(t('Document @file has been updated.', array('@file' => $file->filename)), 'File was updated without file upload.');
+
+ // Get a text file to use as a replacement.
+ $original = clone $file;
+ $replacement = $this->getTestFile('text');
+
+ // Test that the file saves when uploading a replacement file.
+ $edit = array();
+ $edit['files[replace_upload]'] = drupal_realpath($replacement->uri);
+ $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+ $this->assertText(t('Document @file has been updated.', array('@file' => $file->filename)), 'File was updated with file upload.');
+
+ // Re-load the file from the database.
+ $file = file_load($file->fid);
+
+ // Test how file properties changed after the file has been replaced.
+ $this->assertEqual($file->filename, $original->filename, 'Updated file name did not change.');
+ $this->assertNotEqual($file->filesize, $original->filesize, 'Updated file size changed from previous file.');
+ $this->assertEqual($file->filesize, $replacement->filesize, 'Updated file size matches uploaded file.');
+ $this->assertEqual(file_get_contents($file->uri), file_get_contents($replacement->uri), 'Updated file contents matches uploaded file.');
+ $this->assertFalse(entity_load('file', FALSE, array('status' => 0)), 'Temporary file used for replacement was deleted.');
+
+ // Get an image file.
+ $image = $this->getTestFile('image');
+ $edit['files[replace_upload]'] = drupal_realpath($image->uri);
+
+ // Test that validation works by uploading a non-text file as a replacement.
+ $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+ $this->assertRaw(t('The specified file %file could not be uploaded. Only files with the following extensions are allowed:', array('%file' => $image->filename)), 'File validation works, upload failed correctly.');
+
+ // Create a non-local file record.
+ $file2 = new stdClass();
+ $file2->uri = 'oembed://' . $this->randomName();
+ $file2->filename = drupal_basename($file2->uri);
+ $file2->filemime = 'image/oembed';
+ $file2->type = 'image';
+ $file2->uid = 1;
+ $file2->timestamp = REQUEST_TIME;
+ $file2->filesize = 0;
+ $file2->status = 0;
+ // Write the record directly rather than calling file_save() so we don't
+ // invoke the hooks.
+ $this->assertTrue(drupal_write_record('file_managed', $file2), 'Non-local file was added to the database.');
+
+ // Test that Upload widget does not appear for non-local file.
+ $this->drupalGet('file/' . $file2->fid . '/edit');
+ $this->assertNoFieldByName('files[replace_upload]');
+ }
+}
+
+/**
+ * Tests file entity tokens.
+ */
+class FileEntityTokenTestCase extends FileEntityTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity tokens',
+ 'description' => 'Test the file entity tokens.',
+ 'group' => 'File entity',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ }
+
+ function testFileEntityTokens() {
+ $file = $this->createFileEntity(array('type' => 'document'));
+ $tokens = array(
+ 'type' => 'Document',
+ 'type:name' => 'Document',
+ 'type:machine-name' => 'document',
+ 'type:count' => 1,
+ );
+ $this->assertTokens('file', array('file' => $file), $tokens);
+
+ $file = $this->createFileEntity(array('type' => 'image'));
+ $tokens = array(
+ 'type' => 'Image',
+ 'type:name' => 'Image',
+ 'type:machine-name' => 'image',
+ 'type:count' => 1,
+ );
+ $this->assertTokens('file', array('file' => $file), $tokens);
+ }
+
+ function assertTokens($type, array $data, array $tokens, array $options = array()) {
+ $token_input = drupal_map_assoc(array_keys($tokens));
+ $values = token_generate($type, $token_input, $data, $options);
+ foreach ($tokens as $token => $expected) {
+ if (!isset($expected)) {
+ $this->assertTrue(!isset($values[$token]), t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token)));
+ }
+ elseif (!isset($values[$token])) {
+ $this->fail(t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token)));
+ }
+ elseif (!empty($options['regex'])) {
+ $this->assertTrue(preg_match('/^' . $expected . '$/', $values[$token]), t("Token value for [@type:@token] was '@actual', matching regular expression pattern '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $values[$token], '@expected' => $expected)));
+ }
+ else {
+ $this->assertIdentical($values[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $values[$token], '@expected' => $expected)));
+ }
+ }
+
+ return $values;
+ }
+}
+
+/**
+ * Tests adding support for bundles to the core 'file' entity.
+ */
+class FileEntityTypeTestCase extends FileEntityTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity types',
+ 'description' => 'Test the file entity types.',
+ 'group' => 'File entity',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ * Test admin pages access and functionality.
+ */
+ function testAdminPages() {
+ // Create a user with file type administration access.
+ $user = $this->drupalCreateUser(array('administer file types'));
+ $this->drupalLogin($user);
+
+ $this->drupalGet('admin/structure/file-types');
+ $this->assertResponse(200, 'File types admin page is accessible');
+ }
+
+ /**
+ * Test creating a new type. Basic CRUD.
+ */
+ function testCreate() {
+ $type_machine_type = 'foo';
+ $type_machine_label = 'foobar';
+ $type = $this->createFileType(array('type' => $type_machine_type, 'label' => $type_machine_label));
+ $loaded_type = file_type_load($type_machine_type);
+ $this->assertEqual($loaded_type->label, $type_machine_label, "Was able to create a type and retreive it.");
+ }
+
+ /**
+ * Test file types CRUD UI.
+ */
+ function testTypesCrudUi() {
+ $this->drupalGet('admin/structure/file-types');
+ $this->assertResponse(403, 'File types UI page is not accessible to unauthorized users.');
+
+ $user = $this->drupalCreateUser(array('administer file types'));
+ $this->drupalLogin($user);
+
+ $this->drupalGet('admin/structure/file-types');
+ $this->assertResponse(200, 'File types UI page is accessible to users with adequate permission.');
+
+ // Create new file type.
+ $edit = array(
+ 'label' => t('Test type'),
+ 'type' => 'test_type',
+ 'description' => t('This is dummy file type used just for testing.'),
+ 'mimetypes' => 'image/png',
+ );
+ $this->drupalGet('admin/structure/file-types/add');
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertText(t('The file type @type has been updated.', array('@type' => $edit['label'])), 'New file type successfully created.');
+ $this->assertText($edit['label'], 'New file type created: label found.');
+ $this->assertText($edit['description'], 'New file type created: description found.');
+ $this->assertFieldByXPath("//table//tr[1]//td[7]", t('Normal'), 'Newly created file type is stored in DB.');
+ $this->assertLink(t('disable'), 0, 'Able to disable newly created file type.');
+ $this->assertLink(t('delete'), 0, 'Able to delete newly created file type.');
+ $this->assertLinkByHref('admin/structure/file-types/manage/' . $edit['type'] . '/disable', 0, 'Disable link points to disable confirmation page.');
+ $this->assertLinkByHref('admin/structure/file-types/manage/' . $edit['type'] . '/delete', 0, 'Delete link points to delete confirmation page.');
+
+ // Edit file type.
+ $this->drupalGet('admin/structure/file-types/manage/' . $edit['type'] . '/edit');
+ $this->assertRaw(t('Save'), 'Save button found on edit page.');
+ $this->assertRaw(t('Delete'), 'Delete button found on edit page.');
+ $this->assertRaw($edit['label'], 'Label found on file type edit page');
+ $this->assertText($edit['description'], 'Description found on file type edit page');
+ $this->assertText($edit['mimetypes'], 'Mime-type configuration found on file type edit page');
+ $this->assertText(t('Mimetype List'), 'Mimetype list present on edit form.');
+
+ // Modify file type.
+ $edit['label'] = t('New type label');
+ $this->drupalPost(NULL, array('label' => $edit['label']), t('Save'));
+ $this->assertText(t('The file type @type has been updated.', array('@type' => $edit['label'])), 'File type was modified.');
+ $this->assertText($edit['label'], 'Modified label found on file types list.');
+
+ // Disable and re-enable file type.
+ $this->drupalGet('admin/structure/file-types/manage/' . $edit['type'] . '/disable');
+ $this->assertText(t('Are you sure you want to disable the file type @type?', array('@type' => $edit['label'])), 'Disable confirmation page found.');
+ $this->drupalPost(NULL, array(), t('Disable'));
+ $this->assertText(t('The file type @type has been disabled.', array('@type' => $edit['label'])), 'Disable confirmation message found.');
+ $this->assertFieldByXPath("//table//tr[5]//td[1]", $edit['label'], 'Disabled type moved to the tail of the list.');
+ $this->assertLink(t('enable'), 0, 'Able to re-enable newly created file type.');
+ $this->assertLinkByHref('admin/structure/file-types/manage/' . $edit['type'] . '/enable', 0, 'Enable link points to enable confirmation page.');
+ $this->drupalGet('admin/structure/file-types/manage/' . $edit['type'] . '/enable');
+ $this->assertText(t('Are you sure you want to enable the file type @type?', array('@type' => $edit['label'])), 'Enable confirmation page found.');
+ $this->drupalPost(NULL, array(), t('Enable'));
+ $this->assertText(t('The file type @type has been enabled.', array('@type' => $edit['label'])), 'Enable confirmation message found.');
+ $this->assertFieldByXPath("//table//tr[1]//td[1]", $edit['label'], 'Enabled type moved to the top of the list.');
+
+ // Delete newly created type.
+ $this->drupalGet('admin/structure/file-types/manage/' . $edit['type'] . '/delete');
+ $this->assertText(t('Are you sure you want to delete the file type @type?', array('@type' => $edit['label'])), 'Delete confirmation page found.');
+ $this->drupalPost(NULL, array(), t('Delete'));
+ $this->assertText(t('The file type @type has been deleted.', array('@type' => $edit['label'])), 'Delete confirmation message found.');
+ $this->drupalGet('admin/structure/file-types');
+ $this->assertNoText($edit['label'], 'File type successfully deleted.');
+
+ // Edit exported file type.
+ $this->drupalGet('admin/structure/file-types/manage/image/edit');
+ $this->assertRaw(t('Image'), 'Label found on file type edit page');
+ $this->assertText("image/*", 'Mime-type configuration found on file type edit page');
+ $this->drupalPost(NULL, array('label' => t('Funky images')), t('Save'));
+ $this->assertText(t('The file type @type has been updated.', array('@type' => t('Funky images'))), 'File type was modified.');
+ $this->assertText(t('Funky image'), 'Modified label found on file types list.');
+ $this->assertFieldByXPath("//table//tr[1]//td[7]", t('Overridden'), 'Modified type overrides configuration from code.');
+ $this->assertLink(t('revert'), 0, 'Able to revert overridden file type.');
+ $this->assertLinkByHref('admin/structure/file-types/manage/image/revert', 0, 'Revert link points to revert confirmation page.');
+
+ // Revert file type.
+ $this->drupalGet('admin/structure/file-types/manage/image/revert');
+ $this->assertText(t('Are you sure you want to revert the file type @type?', array('@type' => t('Funky images'))), 'Revert confirmation page found.');
+ $this->drupalPost(NULL, array(), t('Revert'));
+ $this->assertText(t('The file type @type has been reverted.', array('@type' => t('Funky images'))), 'Revert confirmation message found.');
+ $this->assertText(t('Image'), 'Reverted file type found in list.');
+ $this->assertFieldByXPath("//table//tr[1]//td[7]", t('Default'), 'Reverted file type shows correct state.');
+ }
+}
+
+/**
+ * Tests the file entity access API.
+ */
+class FileEntityAccessTestCase extends FileEntityTestHelper {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity access',
+ 'description' => 'Test the access aspects of file entity.',
+ 'group' => 'File entity',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Remove the "view files" permission which is set by default for all users
+ // so we can test this permission correctly.
+ $roles = user_roles();
+ foreach ($roles as $rid => $role) {
+ user_role_revoke_permissions($rid, array('view files'));
+ }
+ }
+
+ /**
+ * Runs basic tests for file_entity_access function.
+ */
+ function testFileEntityAccess() {
+ $file = $this->createFileEntity(array('type' => 'image'));
+
+ // Ensures user with 'bypass file access' permission can do everything.
+ $web_user = $this->drupalCreateUser(array('bypass file access'));
+ $this->assertFileEntityAccess(array('create' => TRUE), NULL, $web_user);
+ $this->assertFileEntityAccess(array('view' => TRUE, 'download' => TRUE, 'update' => TRUE, 'delete' => TRUE), $file, $web_user);
+
+ // A user with 'administer files' should not access CRUD operations.
+ $web_user = $this->drupalCreateUser(array('administer files'));
+ $this->assertFileEntityAccess(array('view' => FALSE, 'download' => FALSE, 'update' => FALSE, 'delete' => FALSE), $file, $web_user);
+
+ // User cannot 'view files'.
+ $web_user = $this->drupalCreateUser(array('create files'));
+ $this->assertFileEntityAccess(array('view' => FALSE), $file, $web_user);
+ // But can upload new ones.
+ $this->assertFileEntityAccess(array('create' => TRUE), NULL, $web_user);
+
+ // User can view own files but no other files.
+ $web_user = $this->drupalCreateUser(array('create files', 'view own files'));
+ $this->assertFileEntityAccess(array('view' => FALSE), $file, $web_user);
+ $file->uid = $web_user->uid;
+ $this->assertFileEntityAccess(array('view' => TRUE), $file, $web_user);
+
+ // User can download own files but no other files.
+ $web_user = $this->drupalCreateUser(array('create files', 'download own image files'));
+ $this->assertFileEntityAccess(array('download' => FALSE), $file, $web_user);
+ $file->uid = $web_user->uid;
+ $this->assertFileEntityAccess(array('download' => TRUE), $file, $web_user);
+
+ // User can update own files but no other files.
+ $web_user = $this->drupalCreateUser(array('create files', 'view own files', 'edit own image files'));
+ $this->assertFileEntityAccess(array('update' => FALSE), $file, $web_user);
+ $file->uid = $web_user->uid;
+ $this->assertFileEntityAccess(array('update' => TRUE), $file, $web_user);
+
+ // User can delete own files but no other files.
+ $web_user = $this->drupalCreateUser(array('create files', 'view own files', 'edit own image files', 'delete own image files'));
+ $this->assertFileEntityAccess(array('delete' => FALSE), $file, $web_user);
+ $file->uid = $web_user->uid;
+ $this->assertFileEntityAccess(array('delete' => TRUE), $file, $web_user);
+
+ // User can view any file.
+ $web_user = $this->drupalCreateUser(array('create files', 'view files'));
+ $this->assertFileEntityAccess(array('view' => TRUE), $file, $web_user);
+
+ // User can download any file.
+ $web_user = $this->drupalCreateUser(array('create files', 'download any image files'));
+ $this->assertFileEntityAccess(array('download' => TRUE), $file, $web_user);
+
+ // User can edit any file.
+ $web_user = $this->drupalCreateUser(array('create files', 'view files', 'edit any image files'));
+ $this->assertFileEntityAccess(array('update' => TRUE), $file, $web_user);
+
+ // User can delete any file.
+ $web_user = $this->drupalCreateUser(array('create files', 'view files', 'edit any image files', 'delete any image files'));
+ $this->assertFileEntityAccess(array('delete' => TRUE), $file, $web_user);
+ }
+
+ /**
+ * Tests page access.
+ *
+ * Verifies the privileges required to access the following pages:
+ * file/add
+ * file/%/view
+ * file/%/download
+ * file/%/edit
+ * file/%/usage
+ * file/%/delete
+ */
+ function testFileEntityPageAccess() {
+ // Test creating files without permission.
+ $web_user = $this->drupalCreateUser();
+ $this->drupalLogin($web_user);
+ $this->drupalGet('file/add');
+ $this->assertResponse(403, 'Users without access can not access the file add page');
+
+ // Test creating files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'create files' => TRUE,
+ ));
+ $this->drupalGet('file/add');
+ $this->assertResponse(200, 'Users with access can access the file add page');
+
+ $file = $this->createFileEntity(array('type' => 'document','uid' => $web_user->uid));
+
+ // Test viewing own files without permission.
+ $this->drupalGet("file/{$file->fid}/view");
+ $this->assertResponse(403, 'Users without access can not view their own files');
+
+ // Test viewing own files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'view own files' => TRUE,
+ ));
+ $this->drupalGet("file/{$file->fid}/view");
+ $this->assertResponse(200, 'Users with access can view their own files');
+
+ // Test viewing any files without permission.
+ $file->uid = 1;
+ file_save($file);
+ $this->drupalGet("file/{$file->fid}/view");
+ $this->assertResponse(403, 'Users with access can not view any file');
+
+ // Test viewing any files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'view files' => TRUE,
+ ));
+ $this->drupalGet("file/{$file->fid}/view");
+ $this->assertResponse(200, 'Users with access can view any file');
+
+ // Test downloading own files without permission.
+ $file->uid = $web_user->uid;
+ file_save($file);
+ $url = "file/{$file->fid}/download";
+ $this->drupalGet($url, array('query' => array('token' => file_entity_get_download_token($file))));
+ $this->assertResponse(403, 'Users without access can not download their own files');
+
+ // Test downloading own files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'download own document files' => TRUE,
+ ));
+ $this->drupalGet($url, array('query' => array('token' => file_entity_get_download_token($file))));
+ $this->assertResponse(200, 'Users with access can download their own files');
+
+ // Test downloading any files without permission.
+ $file->uid = 1;
+ file_save($file);
+ $url = "file/{$file->fid}/download";
+ $this->drupalGet($url, array('query' => array('token' => file_entity_get_download_token($file))));
+ $this->assertResponse(403, 'Users without access can not download any file');
+
+ // Test downloading any files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'download any document files' => TRUE,
+ ));
+ $this->drupalGet($url, array('query' => array('token' => file_entity_get_download_token($file))));
+ $this->assertResponse(200, 'Users with access can download any file');
+
+ // Test downloading files with an invalid token.
+ $this->drupalGet($url, array('query' => array('token' => 'invalid-token')));
+ $this->assertResponse(403, 'Cannot download file with an invalid token.');
+
+ // Test downloading files without a token.
+ $this->drupalGet($url);
+ $this->assertResponse(403, 'Cannot download file without a token.');
+ variable_set('file_entity_allow_insecure_download', TRUE);
+
+ // Test downloading files with permission but without a token when insecure
+ // downloads are enabled.
+ $this->drupalGet($url);
+ $this->assertResponse(200, 'Users with access can download the file without a token when file_entity_allow_insecure_download is set.');
+
+ // Tests editing own files without permission.
+ $file->uid = $web_user->uid;
+ file_save($file);
+ $this->drupalGet("file/{$file->fid}/edit");
+ $this->assertResponse(403, 'Users without access can not edit own files');
+
+ // Tests checking the usage of their own files without permission.
+ $this->drupalGet("file/{$file->fid}/usage");
+ $this->assertResponse(403, 'Users without access can not check the usage of their own files');
+
+ // Tests editing own files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'edit own document files' => TRUE,
+ ));
+ $this->drupalGet("file/{$file->fid}/edit");
+ $this->assertResponse(200, 'Users with access can edit own files');
+
+ // Tests checking the usage of their own files without permission.
+ $this->drupalGet("file/{$file->fid}/usage");
+ $this->assertResponse(200, 'Users with access can check the usage of their own files');
+
+ // Tests editing any files without permission.
+ $file->uid = 1;
+ file_save($file);
+ $this->drupalGet("file/{$file->fid}/edit");
+ $this->assertResponse(403, 'Users without access can not edit any file');
+
+ // Tests checking the usage of any files without permission.
+ $this->drupalGet("file/{$file->fid}/usage");
+ $this->assertResponse(403, 'Users without access can not check the usage of any file');
+
+ // Tests editing any files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'edit any document files' => TRUE,
+ ));
+ $this->drupalGet("file/{$file->fid}/edit");
+ $this->assertResponse(200, 'Users with access can edit any file');
+
+ // Tests checking the usage of any files with permission.
+ $this->drupalGet("file/{$file->fid}/usage");
+ $this->assertResponse(200, 'Users with access can check the usage of any file');
+
+ // Tests deleting own files without permission.
+ $file->uid = $web_user->uid;
+ file_save($file);
+ $this->drupalGet("file/{$file->fid}/delete");
+ $this->assertResponse(403, 'Users without access can not delete their own files');
+
+ // Tests deleting own files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'delete own document files' => TRUE,
+ ));
+ $this->drupalGet("file/{$file->fid}/delete");
+ $this->assertResponse(200, 'Users with access can delete their own files');
+
+ // Tests deleting any files without permission.
+ $file->uid = 1;
+ file_save($file);
+ $this->drupalGet("file/{$file->fid}/delete");
+ $this->assertResponse(403, 'Users without access can not delete any file');
+
+ // Tests deleting any files with permission.
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array(
+ 'delete any document files' => TRUE,
+ ));
+ $this->drupalGet("file/{$file->fid}/delete");
+ $this->assertResponse(200, 'Users with access can delete any file');
+ }
+
+ /**
+ * Test to see if we have access to download private files when granted the permissions.
+ */
+ function testFileEntityPrivateDownloadAccess() {
+ foreach ($this->getPrivateDownloadAccessCases() as $case) {
+ // Create users and login only if non-anonymous.
+ $authenticated_user = !is_null($case['permissions']);
+ if ($authenticated_user) {
+ $account = $this->drupalCreateUser($case['permissions']);
+ $this->drupalLogin($account);
+ }
+
+ // Create private, permanent files owned by this user only he's an owner.
+ if (!empty($case['owner'])) {
+ $file = $this->createFileEntity(array('type' => 'document', 'uid' => $account->uid, 'scheme' => 'private'));
+
+ // Check if the physical file is there.
+ $arguments = array('%name' => $file->filename, '%username' => $account->name, '%uri' => $file->uri);
+ $this->assertTrue(is_file($file->uri), format_string('File %name owned by %username successfully created at %uri.', $arguments));
+ $url = file_create_url($file->uri);
+ $message_file_info = ' ' . format_string('File %uri was checked.', array('%uri' => $file->uri));
+ }
+
+ // Try to download the file.
+ $this->drupalGet($url);
+ $this->assertResponse($case['expect'], $case['message'] . $message_file_info);
+
+ // Logout authenticated users.
+ if ($authenticated_user) {
+ $this->drupalLogout();
+ }
+ }
+ }
+
+ /**
+ * Asserts file_entity_access correctly grants or denies access.
+ */
+ function assertFileEntityAccess($ops, $file, $account) {
+ drupal_static_reset('file_entity_access');
+ foreach ($ops as $op => $result) {
+ $msg = t("file_entity_access returns @result with operation '@op'.", array('@result' => $result ? 'true' : 'false', '@op' => $op));
+ $this->assertEqual($result, file_entity_access($op, $file, $account), $msg);
+ }
+ }
+
+ /**
+ * Helper for testFileEntityPrivateDownloadAccess() test.
+ *
+ * Defines several cases for accesing private files.
+ *
+ * @return array
+ * Array of associative arrays, each one having the next keys:
+ * - "message" string with the assertion message.
+ * - "permissions" array of permissions or NULL for anonymous user.
+ * - "expect" expected HTTP response code.
+ * - "owner" Optional boolean indicating if the user is a file owner.
+ */
+ function getPrivateDownloadAccessCases() {
+ return array(
+ array(
+ 'message' => "File owners cannot download their own files unless they are granted the 'view own private files' permission.",
+ 'permissions' => array(),
+ 'expect' => 403,
+ 'owner' => TRUE,
+ ),
+ array(
+ 'message' => "File owners can download their own files as they have been granted the 'view own private files' permission.",
+ 'permissions' => array('view own private files'),
+ 'expect' => 200,
+ 'owner' => TRUE,
+ ),
+ array(
+ 'message' => "Anonymous users cannot download private files.",
+ 'permissions' => NULL,
+ 'expect' => 403,
+ ),
+ array(
+ 'message' => "Authenticated users cannot download each other's private files.",
+ 'permissions' => array(),
+ 'expect' => 403,
+ ),
+ array(
+ 'message' => "Users who can view public files are not able to download private files.",
+ 'permissions' => array('view files'),
+ 'expect' => 403,
+ ),
+ array(
+ 'message' => "Users who bypass file access can download any file.",
+ 'permissions' => array('bypass file access'),
+ 'expect' => 200,
+ ),
+ );
+ }
+}
+
+/**
+ * Tests overriding file attributes.
+ */
+class FileEntityAttributeOverrideTestCase extends FileEntityTestHelper {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'File entity attribute override',
+ 'description' => 'Test overriding file entity attributes.',
+ 'group' => 'File entity',
+ );
+ }
+
+ /**
+ * Test to see if file attributes can be overridden.
+ */
+ function testFileEntityFileAttributeOverrides() {
+ $overrides = array(
+ 'width' => 40,
+ 'height' => 20,
+ 'alt' => $this->randomName(),
+ 'title' => $this->randomName(),
+
+ );
+
+ // Create an image file entity for testing.
+ $file = $this->createFileEntity(array('type' => 'image'));
+
+ // Override a variety of attributes.
+ foreach ($overrides as $override => $value) {
+ $file->override['attributes'][$override] = $value;
+ }
+
+ // Build just the file portion of a file entity.
+ $build = file_view_file($file, 'full');
+
+ // Verify that all of the overrides replaced the attributes.
+ foreach ($overrides as $attribute => $expected_value) {
+ $this->assertEqual($build['#file']->$attribute, $expected_value, format_string('The %attribute was overridden correctly.', array('%attribute' => $attribute)));
+ }
+ }
+}
diff --git a/sites/all/modules/file_entity/file_entity.theme.inc b/sites/all/modules/file_entity/file_entity.theme.inc
new file mode 100644
index 000000000..6801b2d84
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.theme.inc
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * @file
+ * Theme callbacks for the file entity module.
+ */
+
+/**
+ * Copy of theme_file_file_link() for linking to the view file page.
+ *
+ * @see theme_file_file_link()
+ */
+function theme_file_entity_file_link($variables) {
+ $file = $variables['file'];
+ $icon_directory = $variables['icon_directory'];
+
+ $url = 'file/' . $file->fid;
+ $icon = theme('file_icon', array('file' => $file, 'icon_directory' => $icon_directory));
+
+ // Set options as per anchor format described at
+ // http://microformats.org/wiki/file-format-examples
+ $options = array(
+ 'attributes' => array(
+ 'type' => $file->filemime . '; length=' . $file->filesize,
+ ),
+ );
+
+ // Use the description as the link text if available.
+ if (empty($file->description)) {
+ $link_text = $file->filename;
+ }
+ else {
+ $link_text = $file->description;
+ $options['attributes']['title'] = check_plain($file->filename);
+ }
+
+ return '<span class="file">' . $icon . ' ' . l($link_text, $url, $options) . '</span>';
+}
+
+/**
+ * Copy of theme_file_file_link() for linking to the file download URL.
+ *
+ * @see theme_file_file_link()
+ */
+function theme_file_entity_download_link($variables) {
+ $file = $variables['file'];
+ $icon_directory = $variables['icon_directory'];
+
+ $uri = file_entity_download_uri($file);
+ $icon = theme('file_icon', array('file' => $file, 'icon_directory' => $icon_directory));
+
+ // Set options as per anchor format described at
+ // http://microformats.org/wiki/file-format-examples
+ $uri['options']['attributes']['type'] = $file->filemime . '; length=' . $file->filesize;
+
+ // Provide the default link text.
+ if (!isset($variables['text'])) {
+ $variables['text'] = t('Download [file:name]');
+ }
+
+ // Perform unsanitized token replacement if $uri['options']['html'] is empty
+ // since then l() will escape the link text.
+ $variables['text'] = token_replace($variables['text'], array('file' => $file), array('clear' => TRUE, 'sanitize' => !empty($uri['options']['html'])));
+
+ $output = '<span class="file">' . $icon . ' ' . l($variables['text'], $uri['path'], $uri['options']);
+ $output .= ' ' . '<span class="file-size">(' . format_size($file->filesize) . ')</span>';
+ $output .= '</span>';
+
+ return $output;
+}
+
+/**
+ * Returns HTML for displaying an HTML5 <audio> tag.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - file: Associative array of file data, which must include "uri".
+ * - controls: Boolean indicating whether or not controls should be displayed.
+ * - autoplay: Boolean indicating whether or not the audio should start
+ * playing automatically.
+ * - loop: Boolean indicating whether or not the audio should loop.
+ *
+ * @ingroup themeable
+ */
+function theme_file_entity_file_audio($variables) {
+ $files = $variables['files'];
+ $output = '';
+
+ $audio_attributes = array();
+ if ($variables['controls']) {
+ $audio_attributes['controls'] = 'controls';
+ }
+ if ($variables['autoplay']) {
+ $audio_attributes['autoplay'] = 'autoplay';
+ }
+ if ($variables['loop']) {
+ $audio_attributes['loop'] = 'loop';
+ }
+ if (!empty($variables['preload'])) {
+ $audio_attributes['preload'] = $variables['preload'];
+ }
+
+ $output .= '<audio' . drupal_attributes($audio_attributes) . '>';
+ foreach ($files as $delta => $file) {
+ $source_attributes = array(
+ 'src' => file_create_url($file['uri']),
+ 'type' => $file['filemime'],
+ );
+ $output .= '<source' . drupal_attributes($source_attributes) . ' />';
+ }
+ $output .= '</audio>';
+ return $output;
+}
+
+/**
+ * Returns HTML for displaying an HTML5 <video> tag.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - file: Associative array of file data, which must include "uri".
+ * - controls: Boolean indicating whether or not controls should be displayed.
+ * - autoplay: Boolean indicating whether or not the video should start
+ * playing automatically.
+ * - loop: Boolean indicating whether or not the video should loop.
+ * - muted: Boolean indicating whether or not the sound should be muted.
+ * - width: Width, in pixels, of the video player.
+ * - height: Height, in pixels, of the video player.
+ *
+ * @ingroup themeable
+ */
+function theme_file_entity_file_video($variables) {
+ $files = $variables['files'];
+ $output = '';
+
+ $video_attributes = array();
+ if ($variables['controls']) {
+ $video_attributes['controls'] = 'controls';
+ }
+ if ($variables['autoplay']) {
+ $video_attributes['autoplay'] = 'autoplay';
+ }
+ if ($variables['loop']) {
+ $video_attributes['loop'] = 'loop';
+ }
+ if ($variables['muted']) {
+ $video_attributes['muted'] = 'muted';
+ }
+ if ($variables['width']) {
+ $video_attributes['width'] = $variables['width'];
+ }
+ if ($variables['height']) {
+ $video_attributes['height'] = $variables['height'];
+ }
+ if (!empty($variables['preload'])) {
+ $video_attributes['preload'] = $variables['preload'];
+ }
+
+ $output .= '<video' . drupal_attributes($video_attributes) . '>';
+ foreach ($files as $delta => $file) {
+ $source_attributes = array(
+ 'src' => file_create_url($file['uri']),
+ 'type' => $file['filemime'],
+ );
+ $output .= '<source' . drupal_attributes($source_attributes) . ' />';
+ }
+ $output .= '</video>';
+ return $output;
+}
diff --git a/sites/all/modules/file_entity/file_entity.tokens.inc b/sites/all/modules/file_entity/file_entity.tokens.inc
new file mode 100644
index 000000000..d3d1a30e3
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.tokens.inc
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Token integration for the file_entity module.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function file_entity_token_info() {
+ // File type tokens.
+ $info['types']['file-type'] = array(
+ 'name' => t('File type'),
+ 'description' => t('Tokens associated with file types.'),
+ 'needs-data' => 'file_type',
+ );
+ $info['tokens']['file-type']['name'] = array(
+ 'name' => t('Name'),
+ 'description' => t('The name of the file type.'),
+ );
+ $info['tokens']['file-type']['machine-name'] = array(
+ 'name' => t('Machine-readable name'),
+ 'description' => t('The unique machine-readable name of the file type.'),
+ );
+ $info['tokens']['file-type']['count'] = array(
+ 'name' => t('File count'),
+ 'description' => t('The number of files belonging to the file type.'),
+ );
+ $info['tokens']['file-type']['edit-url'] = array(
+ 'name' => t('Edit URL'),
+ 'description' => t("The URL of the file type's edit page."),
+ );
+
+ // File tokens.
+ $info['tokens']['file']['type'] = array(
+ 'name' => t('File type'),
+ 'description' => t('The file type of the file.'),
+ 'type' => 'file-type',
+ );
+ $info['tokens']['file']['download-url'] = array(
+ 'name' => t('Download URL'),
+ 'description' => t('The URL to download the file directly.'),
+ 'type' => 'url',
+ );
+
+ return $info;
+}
+
+/**
+ * Implements hook_token_info_alter().
+ */
+function file_entity_token_info_alter(&$info) {
+ $info['tokens']['file']['name']['description'] = t('The name of the file.');
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function file_entity_tokens($type, $tokens, array $data = array(), array $options = array()) {
+ $replacements = array();
+
+ $url_options = array('absolute' => TRUE);
+ if (isset($options['language'])) {
+ $url_options['language'] = $options['language'];
+ $language_code = $options['language']->language;
+ }
+ else {
+ $language_code = NULL;
+ }
+
+ $sanitize = !empty($options['sanitize']);
+
+ // File tokens.
+ if ($type == 'file' && !empty($data['file'])) {
+ $file = $data['file'];
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ case 'type':
+ if ($file_type = file_type_load($file->type)) {
+ $replacements[$original] = $sanitize ? check_plain($file_type->label) : $file_type->label;
+ }
+ break;
+
+ case 'download-url':
+ $uri = file_entity_download_uri($file);
+ $replacements[$original] = url($uri['path'], $uri['options'] + $url_options);
+ break;
+ }
+ }
+
+ // Chained token relationships.
+ if (($file_type_tokens = token_find_with_prefix($tokens, 'type')) && $file_type = file_type_load($file->type)) {
+ $replacements += token_generate('file-type', $file_type_tokens, array('file_type' => $file_type), $options);
+ }
+ if ($download_url_tokens = token_find_with_prefix($tokens, 'download-url')) {
+ $replacements += token_generate('url', $download_url_tokens, file_entity_download_uri($file), $options);
+ }
+ }
+
+ // File type tokens.
+ if ($type == 'file-type' && !empty($data['file_type'])) {
+ $file_type = $data['file_type'];
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ case 'name':
+ $replacements[$original] = $sanitize ? check_plain($file_type->label) : $file_type->label;
+ break;
+
+ case 'machine-name':
+ // This is a machine name so does not ever need to be sanitized.
+ $replacements[$original] = $file_type->type;
+ break;
+
+ case 'count':
+ $query = db_select('file_managed');
+ $query->condition('type', $file_type->type);
+ $query->addTag('file_type_file_count');
+ $count = $query->countQuery()->execute()->fetchField();
+ $replacements[$original] = (int) $count;
+ break;
+
+ case 'edit-url':
+ $replacements[$original] = url('admin/structure/file-types/manage/' . $file_type->type . '/fields', $url_options);
+ break;
+ }
+ }
+ }
+
+ return $replacements;
+}
diff --git a/sites/all/modules/file_entity/file_entity.tpl.php b/sites/all/modules/file_entity/file_entity.tpl.php
new file mode 100644
index 000000000..cc12db7c0
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.tpl.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to display a file.
+ *
+ * Available variables:
+ * - $label: the (sanitized) file name of the file.
+ * - $content: An array of file items. Use render($content) to print them all,
+ * or print a subset such as render($content['field_example']). Use
+ * hide($content['field_example']) to temporarily suppress the printing of a
+ * given element.
+ * - $user_picture: The file owner's picture from user-picture.tpl.php.
+ * - $date: Formatted added date. Preprocess functions can reformat it by
+ * calling format_date() with the desired parameters on the $timestamp
+ * variable.
+ * - $name: Themed username of file owner output from theme_username().
+ * - $file_url: Direct URL of the current file.
+ * - $display_submitted: Whether submission information should be displayed.
+ * - $submitted: Submission information created from $name and $date during
+ * template_preprocess_file().
+ * - $classes: String of classes that can be used to style contextually through
+ * CSS. It can be manipulated through the variable $classes_array from
+ * preprocess functions. The default values can be one or more of the
+ * following:
+ * - file-entity: The current template type, i.e., "theming hook".
+ * - file-[type]: The current file type. For example, if the file is a
+ * "Image" file it would result in "file-image". Note that the machine
+ * name will often be in a short form of the human readable label.
+ * - file-[mimetype]: The current file's MIME type. For exampe, if the file
+ * is a PNG image, it would result in "file-image-png"
+ * - $title_prefix (array): An array containing additional output populated by
+ * modules, intended to be displayed in front of the main title tag that
+ * appears in the template.
+ * - $title_suffix (array): An array containing additional output populated by
+ * modules, intended to be displayed after the main title tag that appears in
+ * the template.
+ *
+ * Other variables:
+ * - $file: Full file object. Contains data that may not be safe.
+ * - $type: File type, i.e. image, audio, video, etc.
+ * - $uid: User ID of the file owner.
+ * - $timestamp: Time the file was added formatted in Unix timestamp.
+ * - $classes_array: Array of html class attribute values. It is flattened
+ * into a string within the variable $classes.
+ * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in
+ * listings.
+ * - $id: Position of the file. Increments each time it's output.
+ *
+ * File status variables:
+ * - $view_mode: View mode, e.g. 'default', 'full', etc.
+ * - $page: Flag for the full page state.
+ * - $is_front: Flags true when presented in the front page.
+ * - $logged_in: Flags true when the current user is a logged-in member.
+ * - $is_admin: Flags true when the current user is an administrator.
+ *
+ * Field variables: for each field instance attached to the file a corresponding
+ * variable is defined, e.g. $file->caption becomes $caption. When needing to
+ * access a field's raw values, developers/themers are strongly encouraged to
+ * use these variables. Otherwise they will have to explicitly specify the
+ * desired field language, e.g. $file->caption['en'], thus overriding any
+ * language negotiation rule that was previously applied.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_file_entity()
+ * @see template_process()
+ *
+ * @ingroup themeable
+ */
+?>
+<div id="<?php print $id; ?>" class="<?php print $classes ?>"<?php print $attributes; ?>>
+
+ <?php print render($title_prefix); ?>
+ <?php if (!$page): ?>
+ <h2<?php print $title_attributes; ?>><a href="<?php print $file_url; ?>"><?php print $label; ?></a></h2>
+ <?php endif; ?>
+ <?php print render($title_suffix); ?>
+
+ <?php if ($display_submitted): ?>
+ <div class="submitted">
+ <?php print $submitted; ?>
+ </div>
+ <?php endif; ?>
+
+ <div class="content"<?php print $content_attributes; ?>>
+ <?php
+ // We hide the links now so that we can render them later.
+ hide($content['links']);
+ print render($content);
+ ?>
+ </div>
+
+ <?php print render($content['links']); ?>
+
+</div>
diff --git a/sites/all/modules/file_entity/file_entity.views.inc b/sites/all/modules/file_entity/file_entity.views.inc
new file mode 100644
index 000000000..101123a04
--- /dev/null
+++ b/sites/all/modules/file_entity/file_entity.views.inc
@@ -0,0 +1,174 @@
+<?php
+
+/**
+ * @file
+ * Views integration for the file_entity module.
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function file_entity_views_data() {
+ // File type.
+ $data['file_managed']['type'] = array(
+ 'title' => t('Type'),
+ 'help' => t('The type of the file (for example, "audio", "image", "video", etc).'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_type',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_file_type',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_file_type',
+ ),
+ );
+
+ // File schema type.
+ $data['file_managed']['schema_type'] = array(
+ 'title' => t('Schema type'),
+ 'help' => t('Filter files by schema, such as public or private.'),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_schema_type',
+ ),
+ );
+
+ // Rendered file.
+ $data['file_managed']['rendered'] = array(
+ 'title' => t('Rendered'),
+ 'help' => t('Display the file in a specific view mode.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_rendered',
+ 'click sortable' => TRUE,
+ 'real field' => 'fid',
+ 'additional fields' => array(
+ 'fid',
+ ),
+ ),
+ );
+
+ // View link.
+ $data['file_managed']['link'] = array(
+ 'title' => t('Link'),
+ 'help' => t('Provide a simple link to the file entity.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_link',
+ 'real field' => 'fid',
+ 'additional fields' => array(
+ 'fid',
+ ),
+ ),
+ );
+
+ // Edit link.
+ $data['file_managed']['edit'] = array(
+ 'title' => t('Edit link'),
+ 'help' => t('Provide a simple link to edit the file entity.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_link_edit',
+ 'real field' => 'fid',
+ 'additional fields' => array(
+ 'fid',
+ ),
+ ),
+ );
+
+ // Delete link.
+ $data['file_managed']['delete'] = array(
+ 'title' => t('Delete link'),
+ 'help' => t('Provide a simple link to delete the file entity.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_link_delete',
+ 'real field' => 'fid',
+ 'additional fields' => array(
+ 'fid',
+ ),
+ ),
+ );
+
+ // Download link.
+ $data['file_managed']['download'] = array(
+ 'title' => t('Download link'),
+ 'help' => t('Provide a simple link to download the file entity.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_link_download',
+ 'real field' => 'fid',
+ 'additional fields' => array(
+ 'fid',
+ ),
+ ),
+ );
+
+ // Usage link.
+ $data['file_managed']['usage'] = array(
+ 'title' => t('Usage link'),
+ 'help' => t('Provide a simple link to view the usage of the file entity.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_link_usage',
+ 'click sortable' => TRUE,
+ 'real field' => 'fid',
+ 'additional fields' => array(
+ 'fid',
+ ),
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function file_entity_views_data_alter(&$data) {
+ // Add access tag for all queries against file_managed.
+ $data['file_managed']['table']['base']['access query tag'] = 'file_access';
+ // Override the filename field handler.
+ $data['file_managed']['filename']['field']['handler'] = 'views_handler_field_file_filename';
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function file_entity_views_plugins() {
+ return array(
+ 'module' => 'views', // This just tells our themes are elsewhere.
+ 'row' => array(
+ 'file' => array(
+ 'title' => t('File'),
+ 'help' => t('Display the file with standard file view.'),
+ 'handler' => 'views_plugin_row_file_view',
+ 'base' => array('file_managed'), // only works with 'file' as base.
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-file',
+ ),
+ 'file_rss' => array(
+ 'title' => t('File'),
+ 'help' => t('Display the file with standard file view.'),
+ 'handler' => 'views_plugin_row_file_rss',
+ 'theme' => 'views_view_row_rss',
+ 'base' => array('file_managed'), // only works with 'file' as base.
+ 'uses options' => TRUE,
+ 'type' => 'feed',
+ 'help topic' => 'style-file-rss',
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_views_query_substitutions().
+ */
+function file_entity_views_query_substitutions() {
+ return array(
+ '***ADMINISTER_FILES***' => intval(user_access('administer files')),
+ '***BYPASS_FILE_ACCESS***' => intval(user_access('bypass file access')),
+ );
+}
diff --git a/sites/all/modules/file_entity/plugins/content_types/file_content.inc b/sites/all/modules/file_entity/plugins/content_types/file_content.inc
new file mode 100644
index 000000000..7895307a4
--- /dev/null
+++ b/sites/all/modules/file_entity/plugins/content_types/file_content.inc
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('File content'),
+ 'description' => t('Display a full file with a view mode.'),
+ 'required context' => new ctools_context_required(t('File'), 'entity:file'),
+ 'category' => t('File'),
+ 'defaults' => array(
+ 'link' => FALSE,
+ 'view_mode' => 'teaser',
+ ),
+);
+
+/**
+ * Render the node content.
+ */
+function file_entity_file_content_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (!empty($context) && empty($context->data)) {
+ return;
+ }
+
+ $file = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'file_entity';
+ $block->delta = $file->fid;
+
+ if (empty($file)) {
+ $block->delta = 'placeholder';
+ $block->title = t('File title.');
+ $block->content = t('File content goes here.');
+ }
+ else {
+ if (!empty($conf['identifier'])) {
+ $node->ctools_template_identifier = $conf['identifier'];
+ }
+
+ $block->title = $file->filename;
+ $block->content = file_build_content($file, $conf['view_mode']);
+ }
+
+ if (!empty($conf['link']) && $file) {
+ $block->title_link = entity_uri('file', $file);
+ }
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for this plugin.
+ */
+function file_entity_file_content_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $form['link'] = array(
+ '#title' => t('Link title to file'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link'],
+ '#description' => t('Check this to make the title link to the file.'),
+ );
+
+ $entity_info = entity_get_info('file');
+ $build_mode_options = array();
+ foreach ($entity_info['view modes'] as $mode => $option) {
+ $build_mode_options[$mode] = $option['label'];
+ }
+
+ $form['view_mode'] = array(
+ '#title' => t('View mode'),
+ '#type' => 'select',
+ '#description' => t('Select a view mode for this node.'),
+ '#options' => $build_mode_options,
+ '#default_value' => $conf['view_mode'],
+ );
+
+ return $form;
+}
+
+function file_entity_file_content_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+function file_entity_file_content_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" content', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/file_entity/plugins/content_types/file_display.inc b/sites/all/modules/file_entity/plugins/content_types/file_display.inc
new file mode 100644
index 000000000..8133a3b90
--- /dev/null
+++ b/sites/all/modules/file_entity/plugins/content_types/file_display.inc
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+ 'single' => TRUE,
+ 'title' => t('File display'),
+ 'description' => t('Displays the file with a configurable style.'),
+ 'required context' => new ctools_context_required(t('File'), 'entity:file'),
+ 'category' => t('File'),
+ 'defaults' => array(
+ 'displays' => array(),
+ ),
+);
+
+/**
+ * Render the node content.
+ */
+function file_entity_file_display_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (!empty($context) && empty($context->data)) {
+ return;
+ }
+ $file = isset($context->data) ? clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'file_entity';
+ $block->delta = $file->fid;
+
+ if (empty($file)) {
+ $block->delta = 'placeholder';
+ $block->title = t('File display');
+ $block->content = t('File display goes here.');
+ }
+ else {
+ if (!empty($conf['identifier'])) {
+ $file->ctools_template_identifier = $conf['identifier'];
+ }
+
+ $block->title = $file->filename;
+ $block->content = file_view_file($file, $conf['displays']);
+ }
+
+ if (!empty($conf['link']) && $file) {
+ $block->title_link = entity_uri('file', $file);
+ }
+
+ return $block;
+}
+
+/**
+ * Edit form for this plugin.
+ */
+function file_entity_file_display_content_type_edit_form($form, &$form_state) {
+ $conf = $form_state['conf'];
+ $form['#tree'] = TRUE;
+ $form['#attached']['js'][] = drupal_get_path('module', 'file_entity') . '/file_entity.admin.js';
+
+ // Retrieve available formatters for this file. We can load all file types
+ // since we don't know which type the file is at this point.
+ $formatters = file_info_formatter_types();
+
+ // Formatter status.
+ $form['displays']['status'] = array(
+ '#type' => 'item',
+ '#title' => t('Enabled displays'),
+ '#prefix' => '<div id="file-displays-status-wrapper">',
+ '#suffix' => '</div>',
+ );
+ $i=0;
+ foreach ($formatters as $name => $formatter) {
+ $form['displays']['status'][$name] = array(
+ '#type' => 'checkbox',
+ '#title' => check_plain($formatter['label']),
+ '#default_value' => !empty($conf['displays'][$name]['status']),
+ '#description' => isset($formatter['description']) ? filter_xss($formatter['description']) : NULL,
+ '#parents' => array('displays', $name, 'status'),
+ '#weight' => (isset($formatter['weight']) ? $formatter['weight'] : 0) + ($i / 1000),
+ );
+ $i++;
+ }
+ // Formatter order (tabledrag).
+ $form['displays']['order'] = array(
+ '#type' => 'item',
+ '#title' => t('Display precedence order'),
+ '#theme' => 'file_entity_file_display_order',
+ );
+ foreach ($formatters as $name => $formatter) {
+ $form['displays']['order'][$name]['label'] = array(
+ '#markup' => check_plain($formatter['label']),
+ );
+ $form['displays']['order'][$name]['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight for @title', array('@title' => $formatter['label'])),
+ '#title_display' => 'invisible',
+ '#delta' => 50,
+ '#default_value' => isset($conf['displays'][$name]['weight']) ? $conf['displays'][$name]['weight'] : 0,
+ '#parents' => array('displays', $name, 'weight'),
+ );
+ $form['displays']['order'][$name]['#weight'] = $form['displays']['order'][$name]['weight']['#default_value'];
+ }
+
+ // Formatter settings.
+ $form['display_settings_title'] = array(
+ '#type' => 'item',
+ '#title' => t('Display settings'),
+ );
+ $form['display_settings'] = array(
+ '#type' => 'vertical_tabs',
+ );
+ $i=0;
+ foreach ($formatters as $name => $formatter) {
+ if (isset($formatter['settings callback']) && ($function = $formatter['settings callback']) && function_exists($function)) {
+ $defaults = !empty($formatter['default settings']) ? $formatter['default settings'] : array();
+ $settings = !empty($conf['displays'][$name]['settings']) ? $conf['displays'][$name]['settings'] : array();
+ $settings += $defaults;
+ $settings_form = $function($form, $form_state, $settings, $name, $file_type, $view_mode);
+ if (!empty($settings_form)) {
+ $form['displays']['settings'][$name] = array(
+ '#type' => 'fieldset',
+ '#title' => check_plain($formatter['label']),
+ '#parents' => array('displays', $name, 'settings'),
+ '#group' => 'display_settings',
+ '#weight' => (isset($formatter['weight']) ? $formatter['weight'] : 0) + ($i / 1000),
+ ) + $settings_form;
+ }
+ }
+ $i++;
+ }
+ return $form;
+}
+
+function file_entity_file_display_content_type_edit_form_submit($form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+function file_entity_file_display_content_type_admin_title($subtype, $conf, $context) {
+ return t('"@s" content', array('@s' => $context->identifier));
+}
diff --git a/sites/all/modules/file_entity/plugins/entity/PanelizerEntityFile.class.php b/sites/all/modules/file_entity/plugins/entity/PanelizerEntityFile.class.php
new file mode 100644
index 000000000..7f15dfbce
--- /dev/null
+++ b/sites/all/modules/file_entity/plugins/entity/PanelizerEntityFile.class.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * @file
+ * Class for the Panelizer file entity plugin.
+ */
+
+/**
+ * Panelizer Entity file plugin class.
+ *
+ * Handles file specific functionality for Panelizer.
+ */
+class PanelizerEntityFile extends PanelizerEntityDefault {
+ public $entity_admin_root = 'admin/structure/file-types/%';
+ public $entity_admin_bundle = 3;
+ public $views_table = 'file_managed';
+
+ public function entity_access($op, $entity) {
+ return file_entity_access($op, $entity);
+ }
+
+ /**
+ * Implement the save function for the entity.
+ */
+ public function entity_save($entity) {
+ file_save($entity);
+ }
+
+ /**
+ * Implement the save function for the entity.
+ */
+ public function entity_allows_revisions($entity) {
+ return array(FALSE, FALSE);
+
+ }
+
+ public function settings_form(&$form, &$form_state) {
+ parent::settings_form($form, $form_state);
+
+ $warn = FALSE;
+ foreach ($this->plugin['bundles'] as $info) {
+ if (!empty($info['status'])) {
+ $warn = TRUE;
+ break;
+ }
+ }
+
+ if ($warn) {
+ $task = page_manager_get_task('file_view');
+ if (!empty($task['disabled'])) {
+ drupal_set_message('The file template page is currently not enabled in page manager. You must enable this for Panelizer to be able to panelize files.', 'warning');
+ }
+
+ $handler = page_manager_load_task_handler($task, '', 'file_view_panelizer');
+ if (!empty($handler->disabled)) {
+ drupal_set_message('The panelizer variant on the file template page is currently not enabled in page manager. You must enable this for Panelizer to be able to panelize files.', 'warning');
+ }
+ }
+ }
+
+ public function entity_identifier($entity) {
+ return t('This file');
+ }
+
+ public function entity_bundle_label() {
+ return t('File type');
+ }
+
+ function get_default_display($bundle, $view_mode) {
+ // For now we just go with the empty display.
+ // @todo come up with a better default display.
+ return parent::get_default_display($bundle, $view_mode);
+ }
+
+ /**
+ * Implements a delegated hook_page_manager_handlers().
+ *
+ * This makes sure that all panelized entities have the proper entry
+ * in page manager for rendering.
+ */
+ public function hook_default_page_manager_handlers(&$handlers) {
+ page_manager_get_task('file_view');
+
+ $handler = new stdClass;
+ $handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */
+ $handler->api_version = 1;
+ $handler->name = 'file_view_panelizer';
+ $handler->task = 'file_view';
+ $handler->subtask = '';
+ $handler->handler = 'panelizer_node';
+ $handler->weight = -100;
+ $handler->conf = array(
+ 'title' => t('File panelizer'),
+ 'context' => 'argument_entity_id:file_1',
+ 'access' => array(),
+ );
+ $handlers['file_view_panelizer'] = $handler;
+
+ return $handlers;
+ }
+
+ /**
+ * Implements a delegated hook_form_alter.
+ *
+ * We want to add Panelizer settings for the bundle to the file type form.
+ */
+ public function hook_form_alter(&$form, &$form_state, $form_id) {
+ if ($form_id == 'file_entity_file_type_form') {
+ if (isset($form['#file_type'])) {
+ $bundle = $form['#file_type']->type;
+ $this->add_bundle_setting_form($form, $form_state, $bundle, array('machine_name'));
+ }
+ }
+ }
+
+ public function hook_page_alter(&$page) {
+
+ }
+
+ public function hook_views_plugins_alter(&$plugins) {
+
+ }
+
+}
diff --git a/sites/all/modules/file_entity/plugins/entity/file.inc b/sites/all/modules/file_entity/plugins/entity/file.inc
new file mode 100644
index 000000000..fd5823116
--- /dev/null
+++ b/sites/all/modules/file_entity/plugins/entity/file.inc
@@ -0,0 +1,22 @@
+<?php
+/**
+ * @file
+ * Definition of the Panelizer file plugin.
+ */
+
+$plugin = array(
+ 'handler' => 'PanelizerEntityFile',
+ 'entity path' => 'file/%file',
+ 'uses page manager' => TRUE,
+ 'hooks' => array(
+ 'menu' => TRUE,
+ 'admin_paths' => TRUE,
+ 'permission' => TRUE,
+ 'panelizer_defaults' => TRUE,
+ 'default_page_manager_handlers' => TRUE,
+ 'form_alter' => TRUE,
+ 'page_alter' => TRUE,
+ 'views_data_alter' => TRUE,
+ 'views_plugins_alter' => TRUE,
+ ),
+);
diff --git a/sites/all/modules/file_entity/plugins/tasks/file_view.inc b/sites/all/modules/file_entity/plugins/tasks/file_view.inc
new file mode 100644
index 000000000..5b8237581
--- /dev/null
+++ b/sites/all/modules/file_entity/plugins/tasks/file_view.inc
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @file
+ * Handle the 'file view' override task.
+ *
+ * This plugin overrides file/%file and reroutes it to the page manager, where
+ * a list of tasks can be used to service this request based upon criteria
+ * supplied by access plugins.
+ */
+
+/**
+ * Specialized implementation of hook_page_manager_task_tasks().
+ */
+function file_entity_file_view_page_manager_tasks() {
+ return array(
+ // This is a 'page' task and will fall under the page admin UI
+ 'task type' => 'page',
+ 'title' => t('File template'),
+ 'admin title' => t('File template'),
+ 'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying files at <em>file/%file</em>. If you add variants, you may use selection criteria such as file types or user access to provide different views of files. If no variant is selected, the default Drupal file view will be used. This page only affects files viewed as pages, it will not affect files viewed in lists or at other locations. Also please note that if you are using Pathauto, aliases may make a file to be available somewhere else, but as far as Drupal is concerned, they are still at file/%file.'),
+ 'admin path' => 'file/%file',
+
+ // Menu hooks so that we can alter the file/%file menu entry to point to us.
+ 'hook menu' => 'file_entity_file_view_menu',
+ 'hook menu alter' => 'file_entity_file_view_menu_alter',
+
+ // This is task uses 'context' handlers and must implement these to give the
+ // handler data it needs.
+ 'handler type' => 'context',
+ 'get arguments' => 'file_entity_file_view_get_arguments',
+ 'get context placeholders' => 'file_entity_file_view_get_contexts',
+
+ // Allow this to be enabled or disabled:
+ 'disabled' => variable_get('file_entity_file_view_disabled', TRUE),
+ 'enable callback' => 'file_entity_file_view_enable',
+ 'access callback' => 'file_entity_file_view_access_check',
+ );
+}
+
+/**
+ * Callback defined by page_manager_file_view_page_manager_tasks().
+ *
+ * Alter the file view input so that file view comes to us rather than the
+ * normal file view process.
+ */
+function file_entity_file_view_menu_alter(&$items, $task) {
+ if (variable_get('file_entity_file_view_disabled', TRUE)) {
+ return;
+ }
+
+ // Override the node view handler for our purpose.
+ $callback = $items['file/%file']['page callback'];
+ if ($callback == 'file_entity_view_page' || variable_get('page_manager_override_anyway', FALSE)) {
+ $items['file/%file']['page callback'] = 'file_entity_file_view_page';
+ $items['file/%file']['file path'] = $task['path'];
+ $items['file/%file']['file'] = $task['file'];
+ }
+ else {
+ // automatically disable this task if it cannot be enabled.
+ variable_set('file_entity_file_view_disabled', TRUE);
+ if (!empty($GLOBALS['page_manager_enabling_file_view'])) {
+ drupal_set_message(t('Page manager module is unable to enable file/%file because some other module already has overridden with %callback.', array('%callback' => $callback)), 'error');
+ }
+ }
+}
+
+/**
+ * Entry point for our overridden file view.
+ *
+ * This function asks its assigned handlers who, if anyone, would like
+ * to run with it. If no one does, it passes through to File entity's
+ * file view, which is file_entity_view_page().
+ */
+function file_entity_file_view_page($file) {
+ // Load my task plugin:
+ $task = page_manager_get_task('file_view');
+
+ // Load the account into a context.
+ ctools_include('context');
+ ctools_include('context-task-handler');
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($file));
+
+ // We need to mimic Drupal's behavior of setting the file title here.
+ drupal_set_title($file->filename);
+ $uri = entity_uri('file', $file);
+ // Set the file path as the canonical URL to prevent duplicate content.
+ drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
+ // Set the non-aliased path as a default shortlink.
+ drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
+ $contexts = ctools_context_handler_get_task_contexts($task, '', array($file));
+
+ $output = ctools_context_handler_render($task, '', $contexts, array($file->fid));
+ if ($output != FALSE) {
+ return $output;
+ }
+
+ $function = 'file_entity_view_page';
+ foreach (module_implements('page_manager_override') as $module) {
+ $call = $module . '_page_manager_override';
+ if (($rc = $call('file_view')) && function_exists($rc)) {
+ $function = $rc;
+ break;
+ }
+ }
+
+ // Otherwise, fall back.
+ return $function($file);
+}
+
+/**
+ * Callback to get arguments provided by this task handler.
+ *
+ * Since this is the file view and there is no UI on the arguments, we
+ * create dummy arguments that contain the needed data.
+ */
+function file_entity_file_view_get_arguments($task, $subtask_id) {
+ return array(
+ array(
+ 'keyword' => 'file',
+ 'identifier' => t('File being viewed'),
+ 'id' => 1,
+ 'name' => 'entity_id:file',
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Callback to get context placeholders provided by this handler.
+ */
+function file_entity_file_view_get_contexts($task, $subtask_id) {
+ return ctools_context_get_placeholders_from_argument(page_manager_file_view_get_arguments($task, $subtask_id));
+}
+
+/**
+ * Callback to enable/disable the page from the UI.
+ */
+function file_entity_file_view_enable($cache, $status) {
+ variable_set('file_entity_file_view_disabled', $status);
+
+ // Set a global flag so that the menu routine knows it needs
+ // to set a message if enabling cannot be done.
+ if (!$status) {
+ $GLOBALS['page_manager_enabling_file_view'] = TRUE;
+ }
+}
+
+/**
+ * Callback to determine if a page is accessible.
+ *
+ * @param $task
+ * The task plugin.
+ * @param $subtask_id
+ * The subtask id
+ * @param $contexts
+ * The contexts loaded for the task.
+ * @return
+ * TRUE if the current user can access the page.
+ */
+function page_manager_file_view_access_check($task, $subtask_id, $contexts) {
+ $context = reset($contexts);
+ return file_entity_access('view');
+}
diff --git a/sites/all/modules/file_entity/tests/file_entity_test.info b/sites/all/modules/file_entity/tests/file_entity_test.info
new file mode 100644
index 000000000..fd1bbc931
--- /dev/null
+++ b/sites/all/modules/file_entity/tests/file_entity_test.info
@@ -0,0 +1,13 @@
+name = "File Entity Test"
+description = "Support module for File Entity tests."
+package = Testing
+core = 7.x
+dependencies[] = file_entity
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta2"
+core = "7.x"
+project = "file_entity"
+datestamp = "1436896443"
+
diff --git a/sites/all/modules/file_entity/tests/file_entity_test.module b/sites/all/modules/file_entity/tests/file_entity_test.module
new file mode 100644
index 000000000..c6b1dfd4b
--- /dev/null
+++ b/sites/all/modules/file_entity/tests/file_entity_test.module
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * File Entity Test
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function file_entity_test_menu() {
+ $items = array();
+
+ return $items;
+}
diff --git a/sites/all/modules/file_entity/tests/file_entity_test.pages.inc b/sites/all/modules/file_entity/tests/file_entity_test.pages.inc
new file mode 100644
index 000000000..4b608f7ca
--- /dev/null
+++ b/sites/all/modules/file_entity/tests/file_entity_test.pages.inc
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Test pages for the File Entity Test module.
+ */
diff --git a/sites/all/modules/file_entity/views/views_handler_argument_file_type.inc b/sites/all/modules/file_entity/views/views_handler_argument_file_type.inc
new file mode 100644
index 000000000..3eb2fa520
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_argument_file_type.inc
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_file_type.
+ */
+
+/**
+ * Argument handler to accept a file type.
+ */
+class views_handler_argument_file_type extends views_handler_argument_string {
+
+ /**
+ * Override the behavior of summary_name(). Get the user friendly version
+ * of the file type.
+ */
+ function summary_name($data) {
+ return $this->file_type($data->{$this->name_alias});
+ }
+
+ /**
+ * Override the behavior of title(). Get the user friendly version of the
+ * file type.
+ */
+ function title() {
+ return $this->file_type($this->argument);
+ }
+
+ /**
+ * Helper function to return the human-readable type of the file.
+ */
+ function file_type($type) {
+ $output = file_entity_type_get_name($type);
+ if (empty($output)) {
+ $output = t('Unknown file type');
+ }
+ return check_plain($output);
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_field_file_filename.inc b/sites/all/modules/file_entity/views/views_handler_field_file_filename.inc
new file mode 100644
index 000000000..7f3a9923c
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_field_file_filename.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_filename.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to a file.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_filename extends views_handler_field_file {
+ /**
+ * Constructor to provide additional field to add.
+ */
+ function init(&$view, &$options) {
+ if (!empty($this->options['link_to_file'])) {
+ $this->additional_fields['fid'] = 'fid';
+ }
+ parent::init($view, $options);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_file'] = array('default' => TRUE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Provide link to file option
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['link_to_file']['#title'] = t('Link this field to the original file');
+ }
+
+ /**
+ * Render whatever the data is as a link to the file.
+ *
+ * Data should be made XSS safe prior to calling this function.
+ */
+ function render_link($data, $values) {
+ if (!empty($this->options['link_to_file']) && !empty($this->additional_fields['fid'])) {
+ if ($data !== NULL && $data !== '') {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = 'file/' . $this->get_value($values, 'fid');
+ }
+ }
+ return $data;
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_field_file_link.inc b/sites/all/modules/file_entity/views/views_handler_field_file_link.inc
new file mode 100644
index 000000000..2e5df5f34
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_field_file_link.inc
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_link.
+ */
+
+/**
+ * Field handler to present a link to the file.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_link extends views_handler_field_entity {
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['text'] = array('default' => '', 'translatable' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text to display'),
+ '#default_value' => $this->options['text'],
+ );
+ parent::options_form($form, $form_state);
+
+ // The path is set by render_link function so don't allow to set it.
+ $form['alter']['path'] = array('#access' => FALSE);
+ $form['alter']['external'] = array('#access' => FALSE);
+ }
+
+ function render($values) {
+ if ($entity = $this->get_value($values)) {
+ return $this->render_link($entity, $values);
+ }
+ }
+
+ function render_link($file, $values) {
+ if (file_entity_access('view', $file)) {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "file/$file->fid";
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('View');
+ return $text;
+ }
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_field_file_link_delete.inc b/sites/all/modules/file_entity/views/views_handler_field_file_link_delete.inc
new file mode 100644
index 000000000..49e195ed1
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_field_file_link_delete.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_link_delete.
+ */
+
+/**
+ * Field handler to present a link to delete a file.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_link_delete extends views_handler_field_file_link {
+
+ /**
+ * Renders the link.
+ */
+ function render_link($file, $values) {
+ // Ensure user has access to delete this file.
+ if (!file_entity_access('delete', $file)) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "file/$file->fid/delete";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('Delete');
+ return $text;
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_field_file_link_download.inc b/sites/all/modules/file_entity/views/views_handler_field_file_link_download.inc
new file mode 100644
index 000000000..21c272950
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_field_file_link_download.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_link_download.
+ */
+
+/**
+ * Field handler to present a link to download a file.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_link_download extends views_handler_field_file_link {
+
+ /**
+ * Renders the link.
+ */
+ function render_link($file, $values) {
+ // Ensure user has access to download this file.
+ if (!file_entity_access('download', $file)) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $uri = file_entity_download_uri($file);
+ $this->options['alter']['path'] = $uri['path'];
+ $this->options['alter'] = drupal_array_merge_deep($this->options['alter'], $uri['options']);
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('Download');
+ return $text;
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_field_file_link_edit.inc b/sites/all/modules/file_entity/views/views_handler_field_file_link_edit.inc
new file mode 100644
index 000000000..993b9dde1
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_field_file_link_edit.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_link_edit.
+ */
+
+/**
+ * Field handler to present a link file edit.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_link_edit extends views_handler_field_file_link {
+
+ /**
+ * Renders the link.
+ */
+ function render_link($file, $values) {
+ // Ensure user has access to edit this file.
+ if (!file_entity_access('update', $file)) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "file/$file->fid/edit";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('Edit');
+ return $text;
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_field_file_link_usage.inc b/sites/all/modules/file_entity/views/views_handler_field_file_link_usage.inc
new file mode 100644
index 000000000..49e1df8d7
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_field_file_link_usage.inc
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_link_usage.
+ */
+
+/**
+ * Field handler to present a link to view the usage of a file.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_link_usage extends views_handler_field_file_link {
+
+ /**
+ * Renders the link.
+ */
+ function render_link($file, $values) {
+ // Ensure user has access to update this file.
+ if (!file_entity_access('update', $file)) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "file/$file->fid/usage";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ // Get total count for each file.
+ $total_count = 0;
+ foreach (file_usage_list($file) as $module => $usage) {
+ foreach ($usage as $entity_type => $entity_ids) {
+ foreach ($entity_ids as $id => $count) {
+ $total_count += $count;
+ }
+ }
+ }
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : format_plural((int) $total_count, '1 place', '@count places');
+ return $text;
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_field_file_rendered.inc b/sites/all/modules/file_entity/views/views_handler_field_file_rendered.inc
new file mode 100644
index 000000000..d67737427
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_field_file_rendered.inc
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_rendered.
+ */
+
+/**
+ * Field handler to render a file with a view mode using file_view_file().
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_rendered extends views_handler_field_entity {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['file_view_mode'] = array('default' => 'default');
+ return $options;
+ }
+
+ /**
+ * Provide file_view_mode option for to file display.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $entity_info = entity_get_info('file');
+ $options = array('default' => t('Default'));
+ foreach ($entity_info['view modes'] as $file_view_mode => $file_view_mode_info) {
+ $options[$file_view_mode] = $file_view_mode_info['label'];
+ }
+
+ $form['file_view_mode'] = array(
+ '#title' => t('File view mode'),
+ '#description' => t('Select a view mode. Note that only the file will be rendered and not any of its fields.'),
+ '#type' => 'select',
+ '#default_value' => $this->options['file_view_mode'],
+ '#options' => $options,
+ );
+ }
+
+ function render($values) {
+ $file = $this->get_value($values);
+ return file_view_file($file, $this->options['file_view_mode']);
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_field_file_type.inc b/sites/all/modules/file_entity/views/views_handler_field_file_type.inc
new file mode 100644
index 000000000..92e12fe02
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_field_file_type.inc
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_type.
+ */
+
+/**
+ * Field handler to translate a file type into its readable form.
+ */
+class views_handler_field_file_type extends views_handler_field_file {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['machine_name'] = array('default' => FALSE);
+
+ return $options;
+ }
+
+ /**
+ * Provide machine_name option for to file type display.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['machine_name'] = array(
+ '#title' => t('Output machine name'),
+ '#description' => t('Display field as the file type machine name.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['machine_name']),
+ '#fieldset' => 'more',
+ );
+ }
+
+ /**
+ * Render file type as human readable name, unless using machine_name option.
+ */
+ function render_name($data, $values) {
+ if ($this->options['machine_name'] != 1 && $data !== NULL && $data !== '') {
+ return t($this->sanitize_value(file_entity_type_get_name($data)));
+ }
+ return $this->sanitize_value($data);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->render_name($value, $values), $values);
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_filter_file_type.inc b/sites/all/modules/file_entity/views/views_handler_filter_file_type.inc
new file mode 100644
index 000000000..6bca816cf
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_filter_file_type.inc
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_file_type.
+ */
+
+/**
+ * Filter by file type
+ */
+class views_handler_filter_file_type extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_title = t('File types');
+ $options = array();
+ foreach (file_entity_type_get_names() as $type => $name) {
+ $options[$type] = t($name);
+ }
+ asort($options);
+ $this->value_options = $options;
+ }
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_handler_filter_schema_type.inc b/sites/all/modules/file_entity/views/views_handler_filter_schema_type.inc
new file mode 100644
index 000000000..d3d8fbb84
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_handler_filter_schema_type.inc
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @file
+ * Filter by the file schema type.
+ */
+
+class views_handler_filter_schema_type extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_title = t('File Schema types');
+ $types = file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE);
+ $options = array();
+ foreach ($types as $type => $info) {
+ $options[$type] = t($info['name']);
+ }
+ asort($options);
+ $this->value_options = $options;
+ }
+ }
+
+ function op_simple() {
+ if (empty($this->value)) {
+ return;
+ }
+ $this->ensure_my_table();
+
+ // We use array_values() because the checkboxes keep keys and that can cause
+ // array addition problems.
+ $statements = array();
+
+ $not_in = $this->operator == 'not in' ? TRUE : FALSE;
+ $schema_operator = $not_in ? 'NOT LIKE' : 'LIKE';
+ $composite = $not_in ? ' AND ' : ' OR ';
+
+ foreach ($this->value as $schema) {
+ $statements[] = 'uri ' . $schema_operator . ' \'' . db_like($schema) . '://%\'';
+ }
+
+ $this->query->add_where_expression($this->options['group'], implode($composite, $statements));
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_plugin_row_file_rss.inc b/sites/all/modules/file_entity/views/views_plugin_row_file_rss.inc
new file mode 100644
index 000000000..4a39024f7
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_plugin_row_file_rss.inc
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * @file
+ * Contains the file RSS row style plugin.
+ */
+
+/**
+ * Plugin which performs a file_view on the resulting object
+ * and formats it as an RSS item.
+ */
+class views_plugin_row_file_rss extends views_plugin_row {
+ // Basic properties that let the row style follow relationships.
+ var $base_table = 'file_managed';
+ var $base_field = 'fid';
+
+ // Stores the files loaded with pre_render.
+ var $files = array();
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['item_length'] = array('default' => 'default');
+ $options['links'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Override init function to convert fulltext view-mode to full.
+ */
+ function init(&$view, &$display, $options = NULL) {
+ parent::init($view, $display, $options);
+
+ if ($this->options['item_length'] == 'fulltext') {
+ $this->options['item_length'] = 'full';
+ }
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['item_length'] = array(
+ '#type' => 'select',
+ '#title' => t('Display type'),
+ '#options' => $this->options_form_summary_options(),
+ '#default_value' => $this->options['item_length'],
+ );
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display links'),
+ '#default_value' => $this->options['links'],
+ );
+ }
+
+ /**
+ * Return the main options, which are shown in the summary title.
+ */
+ function options_form_summary_options() {
+ $entity_info = entity_get_info('file');
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+ $options['title'] = t('Title only');
+ $options['default'] = t('Use site default RSS settings');
+ return $options;
+ }
+
+ function summary_title() {
+ $options = $this->options_form_summary_options();
+ return check_plain($options[$this->options['item_length']]);
+ }
+
+
+ function pre_render($values) {
+ $fids = array();
+ foreach ($values as $row) {
+ $fids[] = $row->{$this->field_alias};
+ }
+ if (!empty($fids)) {
+ $this->files = file_load_multiple($fids);
+ }
+ }
+
+ function render($row) {
+ // For the most part, this code is taken from node_feed() in node.module
+ global $base_url;
+
+ $fid = $row->{$this->field_alias};
+ if (!is_numeric($fid)) {
+ return;
+ }
+
+ $display_mode = $this->options['item_length'];
+ if ($display_mode == 'default') {
+ $display_mode = variable_get('feed_item_length', 'teaser');
+ }
+
+ // Load the specified file:
+ $file = $this->files[$fid];
+ if (empty($file)) {
+ return;
+ }
+
+ $item_text = '';
+
+ $uri = entity_uri('file', $file);
+ $user = user_load($file->uid);
+ $file->link = url($uri['path'], $uri['options'] + array('absolute' => TRUE));
+ $file->rss_namespaces = array();
+ $file->rss_elements = array(
+ array(
+ 'key' => 'pubDate',
+ 'value' => gmdate('r', $file->timestamp),
+ ),
+ array(
+ 'key' => 'dc:creator',
+ 'value' => $user->name,
+ ),
+ array(
+ 'key' => 'guid',
+ 'value' => $file->fid . ' at ' . $base_url,
+ 'attributes' => array('isPermaLink' => 'false'),
+ ),
+ );
+
+ // The file gets built and modules add to or modify $file->rss_elements
+ // and $file->rss_namespaces.
+
+ $build_mode = $display_mode;
+
+ $build = file_view($file, $build_mode);
+ unset($build['#theme']);
+
+ if (!empty($file->rss_namespaces)) {
+ $this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $file->rss_namespaces);
+ }
+ elseif (function_exists('rdf_get_namespaces')) {
+ // Merge RDF namespaces in the XML namespaces in case they are used
+ // further in the RSS content.
+ $xml_rdf_namespaces = array();
+ foreach (rdf_get_namespaces() as $prefix => $uri) {
+ $xml_rdf_namespaces['xmlns:' . $prefix] = $uri;
+ }
+ $this->view->style_plugin->namespaces += $xml_rdf_namespaces;
+ }
+
+ // Hide the links if desired.
+ if (!$this->options['links']) {
+ hide($build['links']);
+ }
+
+ if ($display_mode != 'title') {
+ // We render file contents and force links to be last.
+ $build['links']['#weight'] = 1000;
+ $item_text .= drupal_render($build);
+ }
+
+ $item = new stdClass();
+ $item->description = $item_text;
+ $item->title = $file->filename;
+ $item->link = $file->link;
+ $item->elements = $file->rss_elements;
+ $item->fid = $file->fid;
+
+ return theme($this->theme_functions(), array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'row' => $item
+ ));
+ }
+}
diff --git a/sites/all/modules/file_entity/views/views_plugin_row_file_view.inc b/sites/all/modules/file_entity/views/views_plugin_row_file_view.inc
new file mode 100644
index 000000000..7b5fe8263
--- /dev/null
+++ b/sites/all/modules/file_entity/views/views_plugin_row_file_view.inc
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains the file view row style plugin.
+ */
+
+/**
+ * Plugin which performs a file_view on the resulting object.
+ *
+ * Most of the code on this object is in the theme function.
+ */
+class views_plugin_row_file_view extends views_plugin_row {
+ // Basic properties that let the row style follow relationships.
+ var $base_table = 'file_managed';
+ var $base_field = 'fid';
+
+ // Stores the files loaded with pre_render.
+ var $files = array();
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['view_mode'] = array('default' => 'default');
+ $options['links'] = array('default' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => file_entity_view_mode_labels(),
+ '#title' => t('View mode'),
+ '#default_value' => $this->options['view_mode'],
+ );
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display links'),
+ '#default_value' => $this->options['links'],
+ );
+ }
+
+ function summary_title() {
+ $view_mode_label = file_entity_view_mode_label($this->options['view_mode'], t('Unknown'));
+ return check_plain($view_mode_label);
+ }
+
+ function pre_render($values) {
+ $fids = array();
+ foreach ($values as $row) {
+ $fids[] = $row->{$this->field_alias};
+ }
+ $this->files = file_load_multiple($fids);
+ }
+
+ function render($row) {
+ $file = $this->files[$row->{$this->field_alias}];
+ $file->view = $this->view;
+ $build = file_view($file, $this->options['view_mode']);
+
+ return drupal_render($build);
+ }
+}
diff --git a/sites/all/modules/l10n_update/LICENSE.txt b/sites/all/modules/l10n_update/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/l10n_update/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/l10n_update/README.txt b/sites/all/modules/l10n_update/README.txt
new file mode 100644
index 000000000..feb75cb2a
--- /dev/null
+++ b/sites/all/modules/l10n_update/README.txt
@@ -0,0 +1,158 @@
+
+Localization Update
+-------------------
+ Automatically download and update your translations by fetching them from
+ http://localize.drupal.org or any other Localization server.
+
+ The l10n update module helps to keep the translation of your drupal core and
+ contributed modules up to date with the central Drupal translation repository
+ at http://localize.drupal.org. Alternatively locally stored translation files
+ can be used as translation source too.
+
+ By choice updates are performed automatically or manually. Locally altered
+ translations can either be respected or ignored.
+
+ The l10n update module is developed for:
+ * Distributions which include their own translations in .po files.
+ * Site admins who want to update the translation with each new module revision.
+ * Site builders who want an easy tool to download translations for a site.
+ * Multi-sites that share one translation source.
+
+ Project page: http://drupal.org/project/l10n_update
+ Support queue: http://drupal.org/project/issues/l10n_update
+
+Installation
+------------
+ Download, unpack the module the usual way.
+ Enable this module and the Locale module (core).
+
+ You need at least one language (besides the default English).
+ On Administration > Configuration > Regional and language > Languages:
+ Click "Add language".
+ Select a language from the select list "Language name".
+ Then click the "Add language" button.
+
+ Drupal is now importing interface translations. This can take a few minutes.
+ When it's finished, you'll get a confirmation with a summary of the
+ translations that have been imported.
+
+ If required, enable the new language as default language.
+ Administration > Configuration > Regional and language > Languages:
+ Select your new default language.
+
+Update interface translations
+-----------------------------
+ You want to import translations regularly using cron. You can enable this
+ on Administration > Configuration > Regional and language > Languages:
+ Choose the "Translation updates" tab.
+ Change "Check for updates" to "Daily" or "Weekly" instead of the default "Never".
+ From now on cron will check for updated translations, and import them is required.
+
+ The status of the translations is reported on the "Status report" page at
+ Administration > Reports.
+
+ To check the translation status and execute updates manually, go to
+ Administration > Configuration > Regional and language > Translate inteface
+ Choose the "Update" tab.
+ You see a list of all modules and their translation status.
+ On the bottom of the page, you can manually update using "Update translations".
+
+Use Drush
+---------
+ You can also use drush to update your translations:
+ drush l10n-update # Update translations.
+ drush l10n-update-refresh # Refresh available information.
+ drush l10n-update-status # Show translation status of available project
+
+
+Summary of administrative pages
+-------------------------------
+ Translations status overview can be found at
+ Administration > Configuration > Regional and language > Languages > Translation updates
+
+ Update configuration settings can be found at
+ Administration > Configuration > Regional and language > Translate interface > Update
+
+Translating Drupal core, modules and themes
+-------------------------------------------
+ When Drupal core or contributed modules or themes get installed, Drupal core
+ checks if .po translation files are present and updates the translations with
+ the strings found in these files. After this, the localization update module
+ checks the localization server for more recent translations, and updates
+ the site translations if a more recent version was found.
+ Note that the translations contained in the project packages may become
+ obsolete in future releases.
+
+ Changes to translations made locally using the site's build in translation
+ interface (Administer > Site building > Translate interface > Search) and
+ changes made using the localization client module are marked. Using the
+ 'Update mode' setting 'Edited translations are kept...', locally edited
+ strings will not be overwritten by translation updates.
+ NOTE: Only manual changes made AFTER installing Localization Update module
+ are preserved. To preserve manual changes made prior to installation of
+ Localization Update module, use the option 'All existing translations are kept...'.
+
+po files, multi site and distributions
+--------------------------------------
+ Multi sites and other installations that share the file system can share
+ downloaded translation files. The Localization Update module can save these
+ translations to disk. Other installations can use these saved translations
+ as their translation source.
+
+ All installations that share the same translation files must be configured
+ with the same 'Store downloaded files' file path e.g. 'sites/all/translations'.
+ Set the 'Update source' of one installation to 'Local files and remote server'
+ or 'Remote server only', all other installations are set to
+ 'Local files only' or 'Local files and remote server'.
+
+ Translation files are saved with the following file name syntax:
+
+ <module name>-<release>.<language code>.po
+
+ For example:
+ masquerade-6.x-1.5.de.po
+ tac_lite-7.x-1.0-beta1.nl.po
+
+ Po files included in distributions should match this syntax too.
+
+Alternative source of translation
+---------------------------------
+
+ The download path pattern is normally defined in the above defined xml file.
+ You may override the download path of the po file on a project by project
+ basis by adding this definition in the .info file:
+
+ l10n path = http://example.com/files/translations/%core/%project/%project-%release.%language.po
+
+ Modules can force Locale to load the translation of an other project by
+ defining 'interface translation project' in their .info file. This can be
+ usefull for custom modules to use for example a common translation file
+
+ interface translation project = my_project
+
+ This can be used in combination with an alternative path to the translation
+ file. For example:
+
+ l10n path = sites/all/modules/custom/%project/%project.%language.po
+
+Exclude a project from translation checks and updates
+-----------------------------------------------------
+
+ Individual modules can be excluded from translation checks and updates. For
+ example custom modules or features. Add the following line to the .info file
+ to exclude a module from translation checks and updates:
+
+ interface translation project = FALSE
+
+API
+---
+ Using hook_l10n_update_projects_alter modules can alter or specify the
+ translation repositories on a per module basis.
+
+ See l10n_update.api.php for more information.
+
+Maintainers
+-----------
+ Erik Stielstra
+ Gábor Hojtsy
+ Jose Reyero
diff --git a/sites/all/modules/l10n_update/css/l10n_update.admin-rtl.css b/sites/all/modules/l10n_update/css/l10n_update.admin-rtl.css
new file mode 100644
index 000000000..f17a0ff26
--- /dev/null
+++ b/sites/all/modules/l10n_update/css/l10n_update.admin-rtl.css
@@ -0,0 +1,11 @@
+/**
+ * Available translation updates page.
+ */
+#l10n-update-status-form .expand .inner {
+ background: transparent url(../images/menu-collapsed-rtl.png) right .6em no-repeat;
+ margin-right: -12px;
+ padding-right: 12px;
+}
+#l10n-update-status-form .expanded .expand .inner {
+ background: transparent url(../images/menu-expanded.png) right .6em no-repeat;
+}
diff --git a/sites/all/modules/l10n_update/css/l10n_update.admin.css b/sites/all/modules/l10n_update/css/l10n_update.admin.css
new file mode 100644
index 000000000..bc08bc9c9
--- /dev/null
+++ b/sites/all/modules/l10n_update/css/l10n_update.admin.css
@@ -0,0 +1,82 @@
+/**
+ * Available translation updates page.
+ */
+#l10n-update-status-form table {
+ table-layout: fixed;
+}
+#l10n-update-status-form th.select-all {
+ width: 4%;
+}
+#l10n-update-status-form th.title {
+ width: 25%;
+}
+#l10n-update-status-form th.description {
+}
+#l10n-update-status-form td {
+ vertical-align: top;
+}
+#l10n-update-status-form .expand .inner {
+ background: transparent url(../images/menu-collapsed.png) left .6em no-repeat;
+ margin-left: -12px;
+ padding-left: 12px;
+}
+#l10n-update-status-form .expanded .expand .inner {
+ background: transparent url(../images/menu-expanded.png) left .6em no-repeat;
+}
+
+#l10n-update-status-form .label {
+ color: #1d1d1d;
+ font-size: 1.15em;
+ font-weight: bold;
+}
+#l10n-update-status-form .description {
+ cursor: pointer;
+}
+#l10n-update-status-form .description .inner {
+ color: #5c5c5b;
+ line-height: 20px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+#l10n-update-status-form .expanded .description .inner {
+ height: auto;
+ overflow: visible;
+ white-space: normal;
+}
+#l10n-update-status-form .expanded .description .text {
+ -webkit-hyphens: auto;
+ -moz-hyphens: auto;
+ hyphens: auto;
+}
+.js #l10n-update-status-form .description .inner {
+ height: 20px;
+}
+#l10n-update-status-form .expanded .description .inner {
+ height: auto;
+ overflow: visible;
+ white-space: normal;
+}
+#l10n-update-status-form .details {
+ padding: 5px 0;
+ max-width: 490px;
+ white-space: normal;
+ font-size: 0.9em;
+ color: #666;
+}
+#l10n-update-status-form .visually-hidden {
+ position: absolute !important;
+ clip: rect(1px, 1px, 1px, 1px);
+ overflow: hidden;
+ height: 1px;
+ width: 1px;
+ word-wrap: normal;
+}
+@media screen and (max-width: 40em) {
+ #l10n-update-status-form th.title {
+ width: 20%;
+ }
+ #l10n-update-status-form th.status {
+ width: 40%;
+ }
+}
diff --git a/sites/all/modules/l10n_update/images/menu-collapsed-rtl.png b/sites/all/modules/l10n_update/images/menu-collapsed-rtl.png
new file mode 100644
index 000000000..dc8d0b882
--- /dev/null
+++ b/sites/all/modules/l10n_update/images/menu-collapsed-rtl.png
Binary files differ
diff --git a/sites/all/modules/l10n_update/images/menu-collapsed.png b/sites/all/modules/l10n_update/images/menu-collapsed.png
new file mode 100644
index 000000000..91f3fd40e
--- /dev/null
+++ b/sites/all/modules/l10n_update/images/menu-collapsed.png
Binary files differ
diff --git a/sites/all/modules/l10n_update/images/menu-expanded.png b/sites/all/modules/l10n_update/images/menu-expanded.png
new file mode 100644
index 000000000..46f39ecb3
--- /dev/null
+++ b/sites/all/modules/l10n_update/images/menu-expanded.png
Binary files differ
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoHeader.php b/sites/all/modules/l10n_update/includes/gettext/PoHeader.php
new file mode 100644
index 000000000..2367f0597
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoHeader.php
@@ -0,0 +1,418 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Gettext\PoHeader
+ */
+
+/**
+ * Gettext PO header handler.
+ *
+ * Possible Gettext PO header elements are explained in
+ * http://www.gnu.org/software/gettext/manual/gettext.html#Header-Entry,
+ * but we only support a subset of these directly.
+ *
+ * Example header:
+ *
+ * "Project-Id-Version: Drupal core (7.11)\n"
+ * "POT-Creation-Date: 2012-02-12 22:59+0000\n"
+ * "PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
+ * "Language-Team: Catalan\n"
+ * "MIME-Version: 1.0\n"
+ * "Content-Type: text/plain; charset=utf-8\n"
+ * "Content-Transfer-Encoding: 8bit\n"
+ * "Plural-Forms: nplurals=2; plural=(n>1);\n"
+ */
+class PoHeader {
+
+ /**
+ * Language code.
+ *
+ * @var string
+ */
+ private $_langcode;
+
+ /**
+ * Formula for the plural form.
+ *
+ * @var string
+ */
+ private $_pluralForms;
+
+ /**
+ * Author(s) of the file.
+ *
+ * @var string
+ */
+ private $_authors;
+
+ /**
+ * Date the po file got created.
+ *
+ * @var string
+ */
+ private $_po_date;
+
+ /**
+ * Human readable language name.
+ *
+ * @var string
+ */
+ private $_languageName;
+
+ /**
+ * Name of the project the translation belongs to.
+ *
+ * @var string
+ */
+ private $_projectName;
+
+ /**
+ * Constructor, creates a PoHeader with default values.
+ *
+ * @param string $langcode
+ * Language code.
+ */
+ public function __construct($langcode = NULL) {
+ $this->_langcode = $langcode;
+ // Ignore errors when run during site installation before
+ // date_default_timezone_set() is called.
+ $this->_po_date = @date("Y-m-d H:iO");
+ $this->_pluralForms = 'nplurals=2; plural=(n > 1);';
+ }
+
+ /**
+ * Get the plural form.
+ *
+ * @return string
+ * Plural form component from the header, for example:
+ * 'nplurals=2; plural=(n > 1);'.
+ */
+ function getPluralForms() {
+ return $this->_pluralForms;
+ }
+
+ /**
+ * Set the human readable language name.
+ *
+ * @param string $languageName
+ * Human readable language name.
+ */
+ function setLanguageName($languageName) {
+ $this->_languageName = $languageName;
+ }
+
+ /**
+ * Get the human readable language name.
+ *
+ * @return string
+ * The human readable language name.
+ */
+ function getLanguageName() {
+ return $this->_languageName;
+ }
+
+ /**
+ * Set the project name.
+ *
+ * @param string $projectName
+ * Human readable project name.
+ */
+ function setProjectName($projectName) {
+ $this->_projectName = $projectName;
+ }
+
+ /**
+ * Get the project name.
+ *
+ * @return string
+ * The human readable project name.
+ */
+ function getProjectName() {
+ return $this->_projectName;
+ }
+
+ /**
+ * Populate internal values from a string.
+ *
+ * @param string $header
+ * Full header string with key-value pairs.
+ */
+ public function setFromString($header) {
+ // Get an array of all header values for processing.
+ $values = $this->parseHeader($header);
+
+ // There is only one value relevant for our header implementation when
+ // reading, and that is the plural formula.
+ if (!empty($values['Plural-Forms'])) {
+ $this->_pluralForms = $values['Plural-Forms'];
+ }
+ }
+
+ /**
+ * Generate a Gettext PO formatted header string based on data set earlier.
+ */
+ public function __toString() {
+ $output = '';
+
+ $isTemplate = empty($this->_languageName);
+
+ $output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->_languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->_projectName) . "\n";
+ if (!empty($this->_authors)) {
+ $output .= '# Generated by ' . implode("\n# ", $this->_authors) . "\n";
+ }
+ $output .= "#\n";
+
+ // Add the actual header information.
+ $output .= "msgid \"\"\n";
+ $output .= "msgstr \"\"\n";
+ $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
+ $output .= "\"POT-Creation-Date: " . $this->_po_date . "\\n\"\n";
+ $output .= "\"PO-Revision-Date: " . $this->_po_date . "\\n\"\n";
+ $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
+ $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
+ $output .= "\"MIME-Version: 1.0\\n\"\n";
+ $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
+ $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
+ $output .= "\"Plural-Forms: " . $this->_pluralForms . "\\n\"\n";
+ $output .= "\n";
+
+ return $output;
+ }
+
+ /**
+ * Parses a Plural-Forms entry from a Gettext Portable Object file header.
+ *
+ * @param string $pluralforms
+ * The Plural-Forms entry value.
+ *
+ * @return
+ * An array containing the number of plural forms and the converted version
+ * of the formula that can be evaluated with PHP later.
+ */
+ function parsePluralForms($pluralforms) {
+ // First, delete all whitespace.
+ $pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
+
+ // Select the parts that define nplurals and plural.
+ $nplurals = strstr($pluralforms, "nplurals=");
+ if (strpos($nplurals, ";")) {
+ // We want the string from the 10th char, because "nplurals=" length is 9.
+ $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
+ }
+ else {
+ return FALSE;
+ }
+ $plural = strstr($pluralforms, "plural=");
+ if (strpos($plural, ";")) {
+ // We want the string from the 8th char, because "plural=" length is 7.
+ $plural = substr($plural, 7, strpos($plural, ";") - 7);
+ }
+ else {
+ return FALSE;
+ }
+
+ // Get PHP version of the plural formula.
+ $plural = $this->parseArithmetic($plural);
+
+ if ($plural !== FALSE) {
+ return array($nplurals, $plural);
+ }
+ else {
+ throw new Exception('The plural formula could not be parsed.');
+ }
+ }
+
+ /**
+ * Parses a Gettext Portable Object file header.
+ *
+ * @param string $header
+ * A string containing the complete header.
+ *
+ * @return array
+ * An associative array of key-value pairs.
+ */
+ private function parseHeader($header) {
+ $header_parsed = array();
+ $lines = array_map('trim', explode("\n", $header));
+ foreach ($lines as $line) {
+ if ($line) {
+ list($tag, $contents) = explode(":", $line, 2);
+ $header_parsed[trim($tag)] = trim($contents);
+ }
+ }
+ return $header_parsed;
+ }
+
+ /**
+ * Parses and sanitizes an arithmetic formula into a PHP expression.
+ *
+ * While parsing, we ensure, that the operators have the right
+ * precedence and associativity.
+ *
+ * @param string $string
+ * A string containing the arithmetic formula.
+ *
+ * @return
+ * A version of the formula to evaluate with PHP later.
+ */
+ private function parseArithmetic($string) {
+ // Operator precedence table.
+ $precedence = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8);
+ // Right associativity.
+ $right_associativity = array("?" => 1, ":" => 1);
+
+ $tokens = $this->tokenizeFormula($string);
+
+ // Parse by converting into infix notation then back into postfix
+ // Operator stack - holds math operators and symbols.
+ $operator_stack = array();
+ // Element Stack - holds data to be operated on.
+ $element_stack = array();
+
+ foreach ($tokens as $token) {
+ $current_token = $token;
+
+ // Numbers and the $n variable are simply pushed into $element_stack.
+ if (is_numeric($token)) {
+ $element_stack[] = $current_token;
+ }
+ elseif ($current_token == "n") {
+ $element_stack[] = '$n';
+ }
+ elseif ($current_token == "(") {
+ $operator_stack[] = $current_token;
+ }
+ elseif ($current_token == ")") {
+ $topop = array_pop($operator_stack);
+ while (isset($topop) && ($topop != "(")) {
+ $element_stack[] = $topop;
+ $topop = array_pop($operator_stack);
+ }
+ }
+ elseif (!empty($precedence[$current_token])) {
+ // If it's an operator, then pop from $operator_stack into
+ // $element_stack until the precedence in $operator_stack is less
+ // than current, then push into $operator_stack.
+ $topop = array_pop($operator_stack);
+ while (isset($topop) && ($precedence[$topop] >= $precedence[$current_token]) && !(($precedence[$topop] == $precedence[$current_token]) && !empty($right_associativity[$topop]) && !empty($right_associativity[$current_token]))) {
+ $element_stack[] = $topop;
+ $topop = array_pop($operator_stack);
+ }
+ if ($topop) {
+ // Return element to top.
+ $operator_stack[] = $topop;
+ }
+ // Parentheses are not needed.
+ $operator_stack[] = $current_token;
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ // Flush operator stack.
+ $topop = array_pop($operator_stack);
+ while ($topop != NULL) {
+ $element_stack[] = $topop;
+ $topop = array_pop($operator_stack);
+ }
+
+ // Now extract formula from stack.
+ $previous_size = count($element_stack) + 1;
+ while (count($element_stack) < $previous_size) {
+ $previous_size = count($element_stack);
+ for ($i = 2; $i < count($element_stack); $i++) {
+ $op = $element_stack[$i];
+ if (!empty($precedence[$op])) {
+ $f = "";
+ if ($op == ":") {
+ $f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")";
+ }
+ elseif ($op == "?") {
+ $f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1];
+ }
+ else {
+ $f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")";
+ }
+ array_splice($element_stack, $i - 2, 3, $f);
+ break;
+ }
+ }
+ }
+
+ // If only one element is left, the number of operators is appropriate.
+ if (count($element_stack) == 1) {
+ return $element_stack[0];
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Tokenize the formula.
+ *
+ * @param string $formula
+ * A string containing the arithmetic formula.
+ *
+ * @return array
+ * List of arithmetic tokens identified in the formula.
+ */
+ private function tokenizeFormula($formula) {
+ $formula = str_replace(" ", "", $formula);
+ $tokens = array();
+ for ($i = 0; $i < strlen($formula); $i++) {
+ if (is_numeric($formula[$i])) {
+ $num = $formula[$i];
+ $j = $i + 1;
+ while ($j < strlen($formula) && is_numeric($formula[$j])) {
+ $num .= $formula[$j];
+ $j++;
+ }
+ $i = $j - 1;
+ $tokens[] = $num;
+ }
+ elseif ($pos = strpos(" =<>!&|", $formula[$i])) {
+ $next = $formula[$i + 1];
+ switch ($pos) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ if ($next == '=') {
+ $tokens[] = $formula[$i] . '=';
+ $i++;
+ }
+ else {
+ $tokens[] = $formula[$i];
+ }
+ break;
+ case 5:
+ if ($next == '&') {
+ $tokens[] = '&&';
+ $i++;
+ }
+ else {
+ $tokens[] = $formula[$i];
+ }
+ break;
+ case 6:
+ if ($next == '|') {
+ $tokens[] = '||';
+ $i++;
+ }
+ else {
+ $tokens[] = $formula[$i];
+ }
+ break;
+ }
+ }
+ else {
+ $tokens[] = $formula[$i];
+ }
+ }
+ return $tokens;
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoItem.php b/sites/all/modules/l10n_update/includes/gettext/PoItem.php
new file mode 100644
index 000000000..2d56c2b82
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoItem.php
@@ -0,0 +1,282 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Gettext\PoItem.
+ */
+
+/**
+ * PoItem handles one translation.
+ */
+class PoItem {
+
+ /**
+ * The language code this translation is in.
+ *
+ * @car string
+ */
+ private $_langcode;
+
+ /**
+ * The context this translation belongs to.
+ *
+ * @var string
+ */
+ private $_context = '';
+
+ /**
+ * The source string or array of strings if it has plurals.
+ *
+ * @var string or array
+ * @see $_plural
+ */
+ private $_source;
+
+ /**
+ * Flag indicating if this translation has plurals.
+ *
+ * @var boolean
+ */
+ private $_plural;
+
+ /**
+ * The comment of this translation.
+ *
+ * @var string
+ */
+ private $_comment;
+
+ /**
+ * The translation string or array of strings if it has plurals.
+ *
+ * @var string or array
+ * @see $_plural
+ */
+ private $_translation;
+
+ /**
+ * Get the language code of the currently used language.
+ *
+ * @return string with langcode
+ */
+ function getLangcode() {
+ return $this->_langcode;
+ }
+
+ /**
+ * Set the language code of the current language.
+ *
+ * @param string $langcode
+ */
+ function setLangcode($langcode) {
+ $this->_langcode = $langcode;
+ }
+
+ /**
+ * Get the context this translation belongs to.
+ *
+ * @return string $context
+ */
+ function getContext() {
+ return $this->_context;
+ }
+
+ /**
+ * Set the context this translation belongs to.
+ *
+ * @param string $context
+ */
+ function setContext($context) {
+ $this->_context = $context;
+ }
+
+ /**
+ * Get the source string or the array of strings if the translation has
+ * plurals.
+ *
+ * @return string or array $translation
+ */
+ function getSource() {
+ return $this->_source;
+ }
+
+ /**
+ * Set the source string or the array of strings if the translation has
+ * plurals.
+ *
+ * @param string or array $source
+ */
+ function setSource($source) {
+ $this->_source = $source;
+ }
+
+ /**
+ * Get the translation string or the array of strings if the translation has
+ * plurals.
+ *
+ * @return string or array $translation
+ */
+ function getTranslation() {
+ return $this->_translation;
+ }
+
+ /**
+ * Set the translation string or the array of strings if the translation has
+ * plurals.
+ *
+ * @param string or array $translation
+ */
+ function setTranslation($translation) {
+ $this->_translation = $translation;
+ }
+
+ /**
+ * Set if the translation has plural values.
+ *
+ * @param boolean $plural
+ */
+ function setPlural($plural) {
+ $this->_plural = $plural;
+ }
+
+ /**
+ * Get if the translation has plural values.
+ *
+ * @return boolean $plural
+ */
+ function isPlural() {
+ return $this->_plural;
+ }
+
+ /**
+ * Get the comment of this translation.
+ *
+ * @return String $comment
+ */
+ function getComment() {
+ return $this->_comment;
+ }
+
+ /**
+ * Set the comment of this translation.
+ *
+ * @param String $comment
+ */
+ function setComment($comment) {
+ $this->_comment = $comment;
+ }
+
+ /**
+ * Create the PoItem from a structured array.
+ *
+ * @param array values
+ */
+ public function setFromArray(array $values = array()) {
+ if (isset($values['context'])) {
+ $this->setContext($values['context']);
+ }
+ if (isset($values['source'])) {
+ $this->setSource($values['source']);
+ }
+ if (isset($values['translation'])) {
+ $this->setTranslation($values['translation']);
+ }
+ if (isset($values['comment'])){
+ $this->setComment($values['comment']);
+ }
+ if (isset($this->_source) &&
+ strpos($this->_source, L10N_UPDATE_PLURAL_DELIMITER) !== FALSE) {
+ $this->setSource(explode(L10N_UPDATE_PLURAL_DELIMITER, $this->_source));
+ $this->setTranslation(explode(L10N_UPDATE_PLURAL_DELIMITER, $this->_translation));
+ $this->setPlural(count($this->_translation) > 1);
+ }
+ }
+
+ /**
+ * Output the PoItem as a string.
+ */
+ public function __toString() {
+ return $this->formatItem();
+ }
+
+ /**
+ * Format the POItem as a string.
+ */
+ private function formatItem() {
+ $output = '';
+
+ // Format string context.
+ if (!empty($this->_context)) {
+ $output .= 'msgctxt ' . $this->formatString($this->_context);
+ }
+
+ // Format translation.
+ if ($this->_plural) {
+ $output .= $this->formatPlural();
+ }
+ else {
+ $output .= $this->formatSingular();
+ }
+
+ // Add one empty line to separate the translations.
+ $output .= "\n";
+
+ return $output;
+ }
+
+ /**
+ * Formats a plural translation.
+ */
+ private function formatPlural() {
+ $output = '';
+
+ // Format source strings.
+ $output .= 'msgid ' . $this->formatString($this->_source[0]);
+ $output .= 'msgid_plural ' . $this->formatString($this->_source[1]);
+
+ foreach ($this->_translation as $i => $trans) {
+ if (isset($this->_translation[$i])) {
+ $output .= 'msgstr[' . $i . '] ' . $this->formatString($trans);
+ }
+ else {
+ $output .= 'msgstr[' . $i . '] ""' . "\n";
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Formats a singular translation.
+ */
+ private function formatSingular() {
+ $output = '';
+ $output .= 'msgid ' . $this->formatString($this->_source);
+ $output .= 'msgstr ' . (isset($this->_translation) ? $this->formatString($this->_translation) : '""');
+ return $output;
+ }
+
+ /**
+ * Formats a string for output on multiple lines.
+ */
+ private function formatString($string) {
+ // Escape characters for processing.
+ $string = addcslashes($string, "\0..\37\\\"");
+
+ // Always include a line break after the explicit \n line breaks from
+ // the source string. Otherwise wrap at 70 chars to accommodate the extra
+ // format overhead too.
+ $parts = explode("\n", wordwrap(str_replace('\n', "\\n\n", $string), 70, " \n"));
+
+ // Multiline string should be exported starting with a "" and newline to
+ // have all lines aligned on the same column.
+ if (count($parts) > 1) {
+ return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n";
+ }
+ // Single line strings are output on the same line.
+ else {
+ return "\"$parts[0]\"\n";
+ }
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoMemoryWriter.php b/sites/all/modules/l10n_update/includes/gettext/PoMemoryWriter.php
new file mode 100644
index 000000000..18e07915f
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoMemoryWriter.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Gettext\PoMemoryWriter.
+ */
+
+/**
+ * Defines a Gettext PO memory writer, to be used by the installer.
+ */
+class PoMemoryWriter implements PoWriterInterface {
+
+ /**
+ * Array to hold all PoItem elements.
+ *
+ * @var array
+ */
+ private $_items;
+
+ /**
+ * Constructor, initialize empty items.
+ */
+ function __construct() {
+ $this->_items = array();
+ }
+
+ /**
+ * Implements PoWriterInterface::writeItem().
+ */
+ public function writeItem(PoItem $item) {
+ if (is_array($item->getSource())) {
+ $item->setSource(implode(L10N_UPDATE_PLURAL_DELIMITER, $item->getSource()));
+ $item->setTranslation(implode(L10N_UPDATE_PLURAL_DELIMITER, $item->getTranslation()));
+ }
+ $context = $item->getContext();
+ $this->_items[$context != NULL ? $context : ''][$item->getSource()] = $item->getTranslation();
+ }
+
+ /**
+ * Implements PoWriterInterface::writeItems().
+ */
+ public function writeItems(PoReaderInterface $reader, $count = -1) {
+ $forever = $count == -1;
+ while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
+ $this->writeItem($item);
+ }
+ }
+
+ /**
+ * Get all stored PoItem's.
+ *
+ * @return array PoItem
+ */
+ public function getData() {
+ return $this->_items;
+ }
+
+ /**
+ * Implements Drupal\Component\Gettext\PoMetadataInterface:setLangcode().
+ *
+ * Not implemented. Not relevant for the MemoryWriter.
+ */
+ function setLangcode($langcode) {
+ }
+
+ /**
+ * Implements Drupal\Component\Gettext\PoMetadataInterface:getLangcode().
+ *
+ * Not implemented. Not relevant for the MemoryWriter.
+ */
+ function getLangcode() {
+ }
+
+ /**
+ * Implements Drupal\Component\Gettext\PoMetadataInterface:getHeader().
+ *
+ * Not implemented. Not relevant for the MemoryWriter.
+ */
+ function getHeader() {
+ }
+
+ /**
+ * Implements Drupal\Component\Gettext\PoMetadataInterface:setHeader().
+ *
+ * Not implemented. Not relevant for the MemoryWriter.
+ */
+ function setHeader(PoHeader $header) {
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoMetadataInterface.php b/sites/all/modules/l10n_update/includes/gettext/PoMetadataInterface.php
new file mode 100644
index 000000000..982aa392c
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoMetadataInterface.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Gettext\PoMetadataInterface.
+ */
+
+/**
+ * Methods required for both reader and writer implementations.
+ *
+ * @see Drupal\Component\Gettext\PoReaderInterface
+ * @see Drupal\Component\Gettext\PoWriterInterface
+ */
+interface PoMetadataInterface {
+
+ /**
+ * Set language code.
+ *
+ * @param string $langcode
+ * Language code string.
+ */
+ public function setLangcode($langcode);
+
+ /**
+ * Get language code.
+ *
+ * @return string
+ * Language code string.
+ */
+ public function getLangcode();
+
+ /**
+ * Set header metadata.
+ *
+ * @param PoHeader $header
+ * Header object representing metadata in a PO header.
+ */
+ public function setHeader(PoHeader $header);
+
+ /**
+ * Get header metadata.
+ *
+ * @return PoHeader $header
+ * Header instance representing metadata in a PO header.
+ */
+ public function getHeader();
+
+}
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoReaderInterface.php b/sites/all/modules/l10n_update/includes/gettext/PoReaderInterface.php
new file mode 100644
index 000000000..45ddf7795
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoReaderInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Gettext\PoReaderInterface.
+ */
+
+/**
+ * Shared interface definition for all Gettext PO Readers.
+ */
+interface PoReaderInterface extends PoMetadataInterface {
+
+ /**
+ * Reads and returns a PoItem (source/translation pair).
+ *
+ * @return PoItem
+ * Wrapper for item data instance.
+ */
+ public function readItem();
+
+}
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoStreamInterface.php b/sites/all/modules/l10n_update/includes/gettext/PoStreamInterface.php
new file mode 100644
index 000000000..f8ca36eb7
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoStreamInterface.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Gettext\PoStreamInterface.
+ */
+
+/**
+ * Common functions for file/stream based PO readers/writers.
+ *
+ * @see PoReaderInterface
+ * @see PoWriterInterface
+ */
+interface PoStreamInterface {
+
+ /**
+ * Open the stream. Set the URI for the stream earlier with setURI().
+ */
+ public function open();
+
+ /**
+ * Close the stream.
+ */
+ public function close();
+
+ /**
+ * Get the URI of the PO stream that is being read or written.
+ *
+ * @return
+ * URI string for this stream.
+ */
+ public function getURI();
+
+ /**
+ * Set the URI of the PO stream that is going to be read or written.
+ *
+ * @param $uri
+ * URI string to set for this stream.
+ */
+ public function setURI($uri);
+
+}
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoStreamReader.php b/sites/all/modules/l10n_update/includes/gettext/PoStreamReader.php
new file mode 100644
index 000000000..5455a3106
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoStreamReader.php
@@ -0,0 +1,603 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Gettext\PoStreamReader.
+ */
+
+/**
+ * Implements Gettext PO stream reader.
+ *
+ * The PO file format parsing is implemented according to the documentation at
+ * http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
+ */
+class PoStreamReader implements PoStreamInterface, PoReaderInterface {
+
+ /**
+ * Source line number of the stream being parsed.
+ *
+ * @var int
+ */
+ private $_line_number = 0;
+
+ /**
+ * Parser context for the stream reader state machine.
+ *
+ * Possible contexts are:
+ * - 'COMMENT' (#)
+ * - 'MSGID' (msgid)
+ * - 'MSGID_PLURAL' (msgid_plural)
+ * - 'MSGCTXT' (msgctxt)
+ * - 'MSGSTR' (msgstr or msgstr[])
+ * - 'MSGSTR_ARR' (msgstr_arg)
+ *
+ * @var string
+ */
+ private $_context = 'COMMENT';
+
+ /**
+ * Current entry being read. Incomplete.
+ *
+ * @var array
+ */
+ private $_current_item = array();
+
+ /**
+ * Current plural index for plural translations.
+ *
+ * @var int
+ */
+ private $_current_plural_index = 0;
+
+ /**
+ * URI of the PO stream that is being read.
+ *
+ * @var string
+ */
+ private $_uri = '';
+
+ /**
+ * Language code for the PO stream being read.
+ *
+ * @var string
+ */
+ private $_langcode = NULL;
+
+ /**
+ * Size of the current PO stream.
+ *
+ * @var int
+ */
+ private $_size;
+
+ /**
+ * File handle of the current PO stream.
+ *
+ * @var resource
+ */
+ private $_fd;
+
+ /**
+ * The PO stream header.
+ *
+ * @var PoHeader
+ */
+ private $_header;
+
+ /**
+ * Object wrapper for the last read source/translation pair.
+ *
+ * @var PoItem
+ */
+ private $_last_item;
+
+ /**
+ * Indicator of whether the stream reading is finished.
+ *
+ * @var boolean
+ */
+ private $_finished;
+
+ /**
+ * Array of translated error strings recorded on reading this stream so far.
+ *
+ * @var array
+ */
+ private $_errors;
+
+ /**
+ * Implements PoMetadataInterface::getLangcode().
+ */
+ public function getLangcode() {
+ return $this->_langcode;
+ }
+
+ /**
+ * Implements PoMetadataInterface::setLangcode().
+ */
+ public function setLangcode($langcode) {
+ $this->_langcode = $langcode;
+ }
+
+ /**
+ * Implements PoMetadataInterface::getHeader().
+ */
+ public function getHeader() {
+ return $this->_header;
+ }
+
+ /**
+ * Implements PoMetadataInterface::setHeader().
+ *
+ * Not applicable to stream reading and therefore not implemented.
+ */
+ public function setHeader(PoHeader $header) {
+ }
+
+ /**
+ * Implements PoStreamInterface::getURI().
+ */
+ public function getURI() {
+ return $this->_uri;
+ }
+
+ /**
+ * Implements PoStreamInterface::setURI().
+ */
+ public function setURI($uri) {
+ $this->_uri = $uri;
+ }
+
+ /**
+ * Implements PoStreamInterface::open().
+ *
+ * Opens the stream and reads the header. The stream is ready for reading
+ * items after.
+ *
+ * @throws Exception
+ * If the URI is not yet set.
+ */
+ public function open() {
+ if (!empty($this->_uri)) {
+ $this->_fd = fopen($this->_uri, 'rb');
+ $this->_size = ftell($this->_fd);
+ $this->readHeader();
+ }
+ else {
+ throw new \Exception('Cannot open stream without URI set.');
+ }
+ }
+
+ /**
+ * Implements PoStreamInterface::close().
+ *
+ * @throws Exception
+ * If the stream is not open.
+ */
+ public function close() {
+ if ($this->_fd) {
+ fclose($this->_fd);
+ }
+ else {
+ throw new \Exception('Cannot close stream that is not open.');
+ }
+ }
+
+ /**
+ * Implements PoReaderInterface::readItem().
+ */
+ public function readItem() {
+ // Clear out the last item.
+ $this->_last_item = NULL;
+
+ // Read until finished with the stream or a complete item was identified.
+ while (!$this->_finished && is_null($this->_last_item)) {
+ $this->readLine();
+ }
+
+ return $this->_last_item;
+ }
+
+ /**
+ * Sets the seek position for the current PO stream.
+ *
+ * @param int $seek
+ * The new seek position to set.
+ */
+ public function setSeek($seek) {
+ fseek($this->_fd, $seek);
+ }
+
+ /**
+ * Returns the pointer position of the current PO stream.
+ */
+ public function getSeek() {
+ return ftell($this->_fd);
+ }
+
+ /**
+ * Read the header from the PO stream.
+ *
+ * The header is a special case PoItem, using the empty string as source and
+ * key-value pairs as translation. We just reuse the item reader logic to
+ * read the header.
+ */
+ private function readHeader() {
+ $item = $this->readItem();
+ // Handle the case properly when the .po file is empty (0 bytes).
+ if (!$item) {
+ return;
+ }
+ $header = new PoHeader;
+ $header->setFromString(trim($item->getTranslation()));
+ $this->_header = $header;
+ }
+
+ /**
+ * Reads a line from the PO stream and stores data internally.
+ *
+ * Expands $this->_current_item based on new data for the current item. If
+ * this line ends the current item, it is saved with setItemFromArray() with
+ * data from $this->_current_item.
+ *
+ * An internal state machine is maintained in this reader using $this->_context
+ * as the reading state. PO items are inbetween COMMENT states (when items have
+ * at least one line or comment inbetween them or indicated by MSGSTR or
+ * MSGSTR_ARR followed immediately by an MSGID or MSGCTXT (when items closely
+ * follow each other).
+ *
+ * @return
+ * FALSE if an error was logged, NULL otherwise. The errors are considered
+ * non-blocking, so reading can continue, while the errors are collected
+ * for later presentation.
+ */
+ private function readLine() {
+ // Read a line and set the stream finished indicator if it was not
+ // possible anymore.
+ $line = fgets($this->_fd);
+ $this->_finished = ($line === FALSE);
+
+ if (!$this->_finished) {
+
+ if ($this->_line_number == 0) {
+ // The first line might come with a UTF-8 BOM, which should be removed.
+ $line = str_replace("\xEF\xBB\xBF", '', $line);
+ // Current plurality for 'msgstr[]'.
+ $this->_current_plural_index = 0;
+ }
+
+ // Track the line number for error reporting.
+ $this->_line_number++;
+
+ // Initialize common values for error logging.
+ $log_vars = array(
+ '%uri' => $this->getURI(),
+ '%line' => $this->_line_number,
+ );
+
+ // Trim away the linefeed. \\n might appear at the end of the string if
+ // another line continuing the same string follows. We can remove that.
+ $line = trim(strtr($line, array("\\\n" => "")));
+
+ if (!strncmp('#', $line, 1)) {
+ // Lines starting with '#' are comments.
+
+ if ($this->_context == 'COMMENT') {
+ // Already in comment context, add to current comment.
+ $this->_current_item['#'][] = substr($line, 1);
+ }
+ elseif (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
+ // We are currently in string context, save current item.
+ $this->setItemFromArray($this->_current_item);
+
+ // Start a new entry for the comment.
+ $this->_current_item = array();
+ $this->_current_item['#'][] = substr($line, 1);
+
+ $this->_context = 'COMMENT';
+ return;
+ }
+ else {
+ // A comment following any other context is a syntax error.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
+ return FALSE;
+ }
+ return;
+ }
+ elseif (!strncmp('msgid_plural', $line, 12)) {
+ // A plural form for the current source string.
+
+ if ($this->_context != 'MSGID') {
+ // A plural form can only be added to an msgid directly.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ // Remove 'msgid_plural' and trim away whitespace.
+ $line = trim(substr($line, 12));
+
+ // Only the plural source string is left, parse it.
+ $quoted = $this->parseQuoted($line);
+ if ($quoted === FALSE) {
+ // The plural form must be wrapped in quotes.
+ $this->_errors[] = format_string('The translation stream %uri contains a syntax error on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ // Append the plural source to the current entry.
+ if (is_string($this->_current_item['msgid'])) {
+ // The first value was stored as string. Now we know the context is
+ // plural, it is converted to array.
+ $this->_current_item['msgid'] = array($this->_current_item['msgid']);
+ }
+ $this->_current_item['msgid'][] = $quoted;
+
+ $this->_context = 'MSGID_PLURAL';
+ return;
+ }
+ elseif (!strncmp('msgid', $line, 5)) {
+ // Starting a new message.
+
+ if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
+ // We are currently in string context, save current item.
+ $this->setItemFromArray($this->_current_item);
+
+ // Start a new context for the msgid.
+ $this->_current_item = array();
+ }
+ elseif ($this->_context == 'MSGID') {
+ // We are currently already in the context, meaning we passed an id with no data.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ // Remove 'msgid' and trim away whitespace.
+ $line = trim(substr($line, 5));
+
+ // Only the message id string is left, parse it.
+ $quoted = $this->parseQuoted($line);
+ if ($quoted === FALSE) {
+ // The message id must be wrapped in quotes.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
+ return FALSE;
+ }
+
+ $this->_current_item['msgid'] = $quoted;
+ $this->_context = 'MSGID';
+ return;
+ }
+ elseif (!strncmp('msgctxt', $line, 7)) {
+ // Starting a new context.
+
+ if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
+ // We are currently in string context, save current item.
+ $this->setItemFromArray($this->_current_item);
+ $this->_current_item = array();
+ }
+ elseif (!empty($this->_current_item['msgctxt'])) {
+ // A context cannot apply to another context.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ // Remove 'msgctxt' and trim away whitespaces.
+ $line = trim(substr($line, 7));
+
+ // Only the msgctxt string is left, parse it.
+ $quoted = $this->parseQuoted($line);
+ if ($quoted === FALSE) {
+ // The context string must be quoted.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ $this->_current_item['msgctxt'] = $quoted;
+
+ $this->_context = 'MSGCTXT';
+ return;
+ }
+ elseif (!strncmp('msgstr[', $line, 7)) {
+ // A message string for a specific plurality.
+
+ if (($this->_context != 'MSGID') &&
+ ($this->_context != 'MSGCTXT') &&
+ ($this->_context != 'MSGID_PLURAL') &&
+ ($this->_context != 'MSGSTR_ARR')) {
+ // Plural message strings must come after msgid, msgxtxt,
+ // msgid_plural, or other msgstr[] entries.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ // Ensure the plurality is terminated.
+ if (strpos($line, ']') === FALSE) {
+ $this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ // Extract the plurality.
+ $frombracket = strstr($line, '[');
+ $this->_current_plural_index = substr($frombracket, 1, strpos($frombracket, ']') - 1);
+
+ // Skip to the next whitespace and trim away any further whitespace,
+ // bringing $line to the message text only.
+ $line = trim(strstr($line, " "));
+
+ $quoted = $this->parseQuoted($line);
+ if ($quoted === FALSE) {
+ // The string must be quoted.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
+ return FALSE;
+ }
+ if (!isset($this->_current_item['msgstr']) || !is_array($this->_current_item['msgstr'])) {
+ $this->_current_item['msgstr'] = array();
+ }
+
+ $this->_current_item['msgstr'][$this->_current_plural_index] = $quoted;
+
+ $this->_context = 'MSGSTR_ARR';
+ return;
+ }
+ elseif (!strncmp("msgstr", $line, 6)) {
+ // A string pair for an msgidid (with optional context).
+
+ if (($this->_context != 'MSGID') && ($this->_context != 'MSGCTXT')) {
+ // Strings are only valid within an id or context scope.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ // Remove 'msgstr' and trim away away whitespaces.
+ $line = trim(substr($line, 6));
+
+ // Only the msgstr string is left, parse it.
+ $quoted = $this->parseQuoted($line);
+ if ($quoted === FALSE) {
+ // The string must be quoted.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ $this->_current_item['msgstr'] = $quoted;
+
+ $this->_context = 'MSGSTR';
+ return;
+ }
+ elseif ($line != '') {
+ // Anything that is not a token may be a continuation of a previous token.
+
+ $quoted = $this->parseQuoted($line);
+ if ($quoted === FALSE) {
+ // This string must be quoted.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
+ return FALSE;
+ }
+
+ // Append the string to the current item.
+ if (($this->_context == 'MSGID') || ($this->_context == 'MSGID_PLURAL')) {
+ if (is_array($this->_current_item['msgid'])) {
+ // Add string to last array element for plural sources.
+ $last_index = count($this->_current_item['msgid']) - 1;
+ $this->_current_item['msgid'][$last_index] .= $quoted;
+ }
+ else {
+ // Singular source, just append the string.
+ $this->_current_item['msgid'] .= $quoted;
+ }
+ }
+ elseif ($this->_context == 'MSGCTXT') {
+ // Multiline context name.
+ $this->_current_item['msgctxt'] .= $quoted;
+ }
+ elseif ($this->_context == 'MSGSTR') {
+ // Multiline translation string.
+ $this->_current_item['msgstr'] .= $quoted;
+ }
+ elseif ($this->_context == 'MSGSTR_ARR') {
+ // Multiline plural translation string.
+ $this->_current_item['msgstr'][$this->_current_plural_index] .= $quoted;
+ }
+ else {
+ // No valid context to append to.
+ $this->_errors[] = format_string('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
+ return FALSE;
+ }
+ return;
+ }
+ }
+
+ // Empty line read or EOF of PO stream, close out the last entry.
+ if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
+ $this->setItemFromArray($this->_current_item);
+ $this->_current_item = array();
+ }
+ elseif ($this->_context != 'COMMENT') {
+ $this->_errors[] = format_string('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
+ return FALSE;
+ }
+ }
+
+ /**
+ * Store the parsed values as a PoItem object.
+ */
+ public function setItemFromArray($value) {
+ $plural = FALSE;
+
+ $comments = '';
+ if (isset($value['#'])) {
+ $comments = $this->shortenComments($value['#']);
+ }
+
+ if (is_array($value['msgstr'])) {
+ // Sort plural variants by their form index.
+ ksort($value['msgstr']);
+ $plural = TRUE;
+ }
+
+ $item = new PoItem();
+ $item->setContext(isset($value['msgctxt']) ? $value['msgctxt'] : '');
+ $item->setSource($value['msgid']);
+ $item->setTranslation($value['msgstr']);
+ $item->setPlural($plural);
+ $item->setComment($comments);
+ $item->setLangcode($this->_langcode);
+
+ $this->_last_item = $item;
+
+ $this->_context = 'COMMENT';
+ }
+
+ /**
+ * Parses a string in quotes.
+ *
+ * @param $string
+ * A string specified with enclosing quotes.
+ *
+ * @return
+ * The string parsed from inside the quotes.
+ */
+ function parseQuoted($string) {
+ if (substr($string, 0, 1) != substr($string, -1, 1)) {
+ // Start and end quotes must be the same.
+ return FALSE;
+ }
+ $quote = substr($string, 0, 1);
+ $string = substr($string, 1, -1);
+ if ($quote == '"') {
+ // Double quotes: strip slashes.
+ return stripcslashes($string);
+ }
+ elseif ($quote == "'") {
+ // Simple quote: return as-is.
+ return $string;
+ }
+ else {
+ // Unrecognized quote.
+ return FALSE;
+ }
+ }
+
+ /**
+ * Generates a short, one-string version of the passed comment array.
+ *
+ * @param $comment
+ * An array of strings containing a comment.
+ *
+ * @return
+ * Short one-string version of the comment.
+ */
+ private function shortenComments($comment) {
+ $comm = '';
+ while (count($comment)) {
+ $test = $comm . substr(array_shift($comment), 1) . ', ';
+ if (strlen($comm) < 130) {
+ $comm = $test;
+ }
+ else {
+ break;
+ }
+ }
+ return trim(substr($comm, 0, -2));
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoStreamWriter.php b/sites/all/modules/l10n_update/includes/gettext/PoStreamWriter.php
new file mode 100644
index 000000000..cd316b90e
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoStreamWriter.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Gettext\PoStreamWriter.
+ */
+
+/**
+ * Defines a Gettext PO stream writer.
+ */
+class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
+
+ /**
+ * URI of the PO stream that is being written.
+ *
+ * @var string
+ */
+ private $_uri;
+
+ /**
+ * The Gettext PO header.
+ *
+ * @var PoHeader
+ */
+ private $_header;
+
+ /**
+ * File handle of the current PO stream.
+ *
+ * @var resource
+ */
+ private $_fd;
+
+ /**
+ * Get the PO header of the current stream.
+ *
+ * @return PoHeader
+ * The Gettext PO header.
+ */
+ public function getHeader() {
+ return $this->_header;
+ }
+
+ /**
+ * Set the PO header for the current stream.
+ *
+ * @param PoHeader $header
+ * The Gettext PO header to set.
+ */
+ public function setHeader(PoHeader $header) {
+ $this->_header = $header;
+ }
+
+ /**
+ * Get the current language code used.
+ *
+ * @return string
+ * The language code.
+ */
+ public function getLangcode() {
+ return $this->_langcode;
+ }
+
+ /**
+ * Set the language code.
+ *
+ * @param string $langcode
+ * The language code.
+ */
+ public function setLangcode($langcode) {
+ $this->_langcode = $langcode;
+ }
+
+ /**
+ * Implements PoStreamInterface::open().
+ */
+ public function open() {
+ // Open in write mode. Will overwrite the stream if it already exists.
+ $this->_fd = fopen($this->getURI(), 'w');
+ // Write the header at the start.
+ $this->writeHeader();
+ }
+
+ /**
+ * Implements PoStreamInterface::close().
+ *
+ * @throws Exception
+ * If the stream is not open.
+ */
+ public function close() {
+ if ($this->_fd) {
+ fclose($this->_fd);
+ }
+ else {
+ throw new Exception('Cannot close stream that is not open.');
+ }
+ }
+
+ /**
+ * Write data to the stream.
+ *
+ * @param string $data
+ * Piece of string to write to the stream. If the value is not directly a
+ * string, casting will happen in writing.
+ *
+ * @throws Exception
+ * If writing the data is not possible.
+ */
+ private function write($data) {
+ $result = fputs($this->_fd, $data);
+ if ($result === FALSE) {
+ throw new Exception('Unable to write data: ' . substr($data, 0, 20));
+ }
+ }
+
+ /**
+ * Write the PO header to the stream.
+ */
+ private function writeHeader() {
+ $this->write($this->_header);
+ }
+
+ /**
+ * Implements PoWriterInterface::writeItem().
+ */
+ public function writeItem(PoItem $item) {
+ $this->write($item);
+ }
+
+ /**
+ * Implements PoWriterInterface::writeItems().
+ */
+ public function writeItems(PoReaderInterface $reader, $count = -1) {
+ $forever = $count == -1;
+ while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
+ $this->writeItem($item);
+ }
+ }
+
+ /**
+ * Implements PoStreamInterface::getURI().
+ *
+ * @throws Exception
+ * If the URI is not set.
+ */
+ public function getURI() {
+ if (empty($this->_uri)) {
+ throw new Exception('No URI set.');
+ }
+ return $this->_uri;
+ }
+
+ /**
+ * Implements PoStreamInterface::setURI().
+ */
+ public function setURI($uri) {
+ $this->_uri = $uri;
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/gettext/PoWriterInterface.php b/sites/all/modules/l10n_update/includes/gettext/PoWriterInterface.php
new file mode 100644
index 000000000..cb486feeb
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/gettext/PoWriterInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Gettext\PoWriterInterface.
+ */
+
+/**
+ * Shared interface definition for all Gettext PO Writers.
+ */
+interface PoWriterInterface extends PoMetadataInterface {
+
+ /**
+ * Writes the given item.
+ *
+ * @param PoItem $item
+ * One specific item to write.
+ */
+ public function writeItem(PoItem $item);
+
+ /**
+ * Writes all or the given amount of items.
+ *
+ * @param PoReaderInterface $reader
+ * Reader to read PoItems from.
+ * @param $count
+ * Amount of items to read from $reader to write. If -1, all items are
+ * read from $reader.
+ */
+ public function writeItems(PoReaderInterface $reader, $count = -1);
+
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/Gettext.php b/sites/all/modules/l10n_update/includes/locale/Gettext.php
new file mode 100644
index 000000000..a90b96d18
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/Gettext.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Definition of Gettext class.
+ */
+
+/**
+ * Static class providing Drupal specific Gettext functionality.
+ *
+ * The operations are related to pumping data from a source to a destination,
+ * for example:
+ * - Remote files http://*.po to memory
+ * - File public://*.po to database
+ */
+class Gettext {
+
+ /**
+ * Reads the given PO files into the database.
+ *
+ * @param stdClass $file
+ * File object with an URI property pointing at the file's path.
+ * - "langcode": The language the strings will be added to.
+ * - "uri": File URI.
+ * @param array $options
+ * An array with options that can have the following elements:
+ * - 'overwrite_options': Overwrite options array as defined in
+ * PoDatabaseWriter. Optional, defaults to an empty array.
+ * - 'customized': Flag indicating whether the strings imported from $file
+ * are customized translations or come from a community source. Use
+ * L10N_UPDATE_CUSTOMIZED or L10N_UPDATE_NOT_CUSTOMIZED. Optional, defaults to
+ * L10N_UPDATE_NOT_CUSTOMIZED.
+ * - 'seek': Specifies from which position in the file should the reader
+ * start reading the next items. Optional, defaults to 0.
+ * - 'items': Specifies the number of items to read. Optional, defaults to
+ * -1, which means that all the items from the stream will be read.
+ *
+ * @return array
+ * Report array as defined in PoDatabaseWriter.
+ *
+ * @see PoDatabaseWriter
+ */
+ static function fileToDatabase($file, $options) {
+ // Add the default values to the options array.
+ $options += array(
+ 'overwrite_options' => array(),
+ 'customized' => L10N_UPDATE_NOT_CUSTOMIZED,
+ 'items' => -1,
+ 'seek' => 0,
+ );
+ // Instantiate and initialize the stream reader for this file.
+ $reader = new PoStreamReader();
+ $reader->setLangcode($file->langcode);
+ $reader->setURI($file->uri);
+
+ try {
+ $reader->open();
+ }
+ catch (\Exception $exception) {
+ throw $exception;
+ }
+
+ $header = $reader->getHeader();
+ if (!$header) {
+ throw new \Exception('Missing or malformed header.');
+ }
+
+ // Initialize the database writer.
+ $writer = new PoDatabaseWriter();
+ $writer->setLangcode($file->langcode);
+ $writer_options = array(
+ 'overwrite_options' => $options['overwrite_options'],
+ 'customized' => $options['customized'],
+ );
+ $writer->setOptions($writer_options);
+ $writer->setHeader($header);
+
+ // Attempt to pipe all items from the file to the database.
+ try {
+ if ($options['seek']) {
+ $reader->setSeek($options['seek']);
+ }
+ $writer->writeItems($reader, $options['items']);
+ }
+ catch (\Exception $exception) {
+ throw $exception;
+ }
+
+ // Report back with an array of status information.
+ $report = $writer->getReport();
+
+ // Add the seek position to the report. This is useful for the batch
+ // operation.
+ $report['seek'] = $reader->getSeek();
+ return $report;
+ }
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/PoDatabaseReader.php b/sites/all/modules/l10n_update/includes/locale/PoDatabaseReader.php
new file mode 100644
index 000000000..faddd3726
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/PoDatabaseReader.php
@@ -0,0 +1,178 @@
+<?php
+
+/**
+ * @file
+ * Definition of PoDatabaseReader.
+ */
+
+/**
+ * Gettext PO reader working with the locale module database.
+ */
+class PoDatabaseReader implements PoReaderInterface {
+
+ /**
+ * An associative array indicating which type of strings should be read.
+ *
+ * Elements of the array:
+ * - not_customized: boolean indicating if not customized strings should be
+ * read.
+ * - customized: boolean indicating if customized strings should be read.
+ * - no_translated: boolean indicating if non-translated should be read.
+ *
+ * The three options define three distinct sets of strings, which combined
+ * cover all strings.
+ *
+ * @var array
+ */
+ private $_options;
+
+ /**
+ * Language code of the language being read from the database.
+ *
+ * @var string
+ */
+ private $_langcode;
+
+ /**
+ * Store the result of the query so it can be iterated later.
+ *
+ * @var resource
+ */
+ private $_result;
+
+ /**
+ * Database storage to retrieve the strings from.
+ *
+ * @var StringDatabaseStorage
+ */
+ protected $storage;
+
+ /**
+ * Constructor, initializes with default options.
+ */
+ function __construct() {
+ $this->setOptions(array());
+ $this->storage = new StringDatabaseStorage();
+ }
+
+ /**
+ * Implements PoMetadataInterface::getLangcode().
+ */
+ public function getLangcode() {
+ return $this->_langcode;
+ }
+
+ /**
+ * Implements PoMetadataInterface::setLangcode().
+ */
+ public function setLangcode($langcode) {
+ $this->_langcode = $langcode;
+ }
+
+ /**
+ * Get the options used by the reader.
+ */
+ function getOptions() {
+ return $this->_options;
+ }
+
+ /**
+ * Set the options for the current reader.
+ */
+ function setOptions(array $options) {
+ $options += array(
+ 'customized' => FALSE,
+ 'not_customized' => FALSE,
+ 'not_translated' => FALSE,
+ );
+ $this->_options = $options;
+ }
+
+ /**
+ * Implements PoMetadataInterface::getHeader().
+ */
+ function getHeader() {
+ return new PoHeader($this->getLangcode());
+ }
+
+ /**
+ * Implements PoMetadataInterface::setHeader().
+ *
+ * @throws Exception
+ * Always, because you cannot set the PO header of a reader.
+ */
+ function setHeader(PoHeader $header) {
+ throw new \Exception('You cannot set the PO header in a reader.');
+ }
+
+ /**
+ * Builds and executes a database query based on options set earlier.
+ */
+ private function loadStrings() {
+ $langcode = $this->_langcode;
+ $options = $this->_options;
+ $conditions = array();
+
+ if (array_sum($options) == 0) {
+ // If user asked to not include anything in the translation files,
+ // that would not make sense, so just fall back on providing a template.
+ $langcode = NULL;
+ // Force option to get both translated and untranslated strings.
+ $options['not_translated'] = TRUE;
+ }
+ // Build and execute query to collect source strings and translations.
+ if (!empty($langcode)) {
+ $conditions['language'] = $langcode;
+ // Translate some options into field conditions.
+ if ($options['customized']) {
+ if (!$options['not_customized']) {
+ // Filter for customized strings only.
+ $conditions['customized'] = L10N_UPDATE_CUSTOMIZED;
+ }
+ // Else no filtering needed in this case.
+ }
+ else {
+ if ($options['not_customized']) {
+ // Filter for non-customized strings only.
+ $conditions['customized'] = L10N_UPDATE_NOT_CUSTOMIZED;
+ }
+ else {
+ // Filter for strings without translation.
+ $conditions['translated'] = FALSE;
+ }
+ }
+ if (!$options['not_translated']) {
+ // Filter for string with translation.
+ $conditions['translated'] = TRUE;
+ }
+ return $this->storage->getTranslations($conditions);
+ }
+ else {
+ // If no language, we don't need any of the target fields.
+ return $this->storage->getStrings($conditions);
+ }
+ }
+
+ /**
+ * Get the database result resource for the given language and options.
+ */
+ private function readString() {
+ if (!isset($this->_result)) {
+ $this->_result = $this->loadStrings();
+ }
+ return array_shift($this->_result);
+ }
+
+ /**
+ * Implements PoReaderInterface::readItem().
+ */
+ function readItem() {
+ if ($string = $this->readString()) {
+ $values = (array)$string;
+ $poItem = new PoItem();
+ $poItem->setFromArray($values);
+ return $poItem;
+ }
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/PoDatabaseWriter.php b/sites/all/modules/l10n_update/includes/locale/PoDatabaseWriter.php
new file mode 100644
index 000000000..50aaa330f
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/PoDatabaseWriter.php
@@ -0,0 +1,296 @@
+<?php
+
+/**
+ * @file
+ * Definition of PoDatabaseWriter.
+ */
+
+/**
+ * Gettext PO writer working with the locale module database.
+ */
+class PoDatabaseWriter implements PoWriterInterface {
+
+ /**
+ * An associative array indicating what data should be overwritten, if any.
+ *
+ * Elements of the array:
+ * - override_options
+ * - not_customized: boolean indicating that not customized strings should
+ * be overwritten.
+ * - customized: boolean indicating that customized strings should be
+ * overwritten.
+ * - customized: the strings being imported should be saved as customized.
+ * One of L10N_UPDATE_CUSTOMIZED or L10N_UPDATE_NOT_CUSTOMIZED.
+ *
+ * @var array
+ */
+ private $_options;
+
+ /**
+ * Language code of the language being written to the database.
+ *
+ * @var string
+ */
+ private $_langcode;
+
+ /**
+ * Header of the po file written to the database.
+ *
+ * @var PoHeader
+ */
+ private $_header;
+
+ /**
+ * Associative array summarizing the number of changes done.
+ *
+ * Keys for the array:
+ * - additions: number of source strings newly added
+ * - updates: number of translations updated
+ * - deletes: number of translations deleted
+ * - skips: number of strings skipped due to disallowed HTML
+ *
+ * @var array
+ */
+ private $_report;
+
+ /**
+ * Database storage to store the strings in.
+ *
+ * @var StringDatabaseStorage
+ */
+ protected $storage;
+
+ /**
+ * Constructor, initialize reporting array.
+ */
+ function __construct() {
+ $this->setReport();
+ $this->storage = new StringDatabaseStorage();
+ }
+
+ /**
+ * Implements PoMetadataInterface::getLangcode().
+ */
+ public function getLangcode() {
+ return $this->_langcode;
+ }
+
+ /**
+ * Implements PoMetadataInterface::setLangcode().
+ */
+ public function setLangcode($langcode) {
+ $this->_langcode = $langcode;
+ }
+
+ /**
+ * Get the report of the write operations.
+ */
+ public function getReport() {
+ return $this->_report;
+ }
+
+ /**
+ * Set the report array of write operations.
+ *
+ * @param array $report
+ * Associative array with result information.
+ */
+ function setReport($report = array()) {
+ $report += array(
+ 'additions' => 0,
+ 'updates' => 0,
+ 'deletes' => 0,
+ 'skips' => 0,
+ 'strings' => array(),
+ );
+ $this->_report = $report;
+ }
+
+ /**
+ * Get the options used by the writer.
+ */
+ function getOptions() {
+ return $this->_options;
+ }
+
+ /**
+ * Set the options for the current writer.
+ */
+ function setOptions(array $options) {
+ if (!isset($options['overwrite_options'])) {
+ $options['overwrite_options'] = array();
+ }
+ $options['overwrite_options'] += array(
+ 'not_customized' => FALSE,
+ 'customized' => FALSE,
+ );
+ $options += array(
+ 'customized' => L10N_UPDATE_NOT_CUSTOMIZED,
+ );
+ $this->_options = $options;
+ }
+
+ /**
+ * Implements PoMetadataInterface::getHeader().
+ */
+ function getHeader() {
+ return $this->_header;
+ }
+
+ /**
+ * Implements PoMetadataInterface::setHeader().
+ *
+ * Sets the header and configure Drupal accordingly.
+ *
+ * Before being able to process the given header we need to know in what
+ * context this database write is done. For this the options must be set.
+ *
+ * A langcode is required to set the current header's PluralForm.
+ *
+ * @param PoHeader $header
+ * Header metadata.
+ *
+ * @throws Exception
+ */
+ function setHeader(PoHeader $header) {
+ $this->_header = $header;
+ $languages = language_list();
+
+ // Check for options.
+ $options = $this->getOptions();
+ if (empty($options)) {
+ throw new \Exception('Options should be set before assigning a PoHeader.');
+ }
+ $overwrite_options = $options['overwrite_options'];
+
+ // Check for langcode.
+ $langcode = $this->_langcode;
+ if (empty($langcode)) {
+ throw new \Exception('Langcode should be set before assigning a PoHeader.');
+ }
+
+ // Check is language is already created.
+ if (!isset($languages[$langcode])) {
+ throw new \Exception('Language should be known before using it.');
+ }
+
+ if (array_sum($overwrite_options) || empty($languages[$langcode]->plurals)) {
+ // Get and store the plural formula if available.
+ $plural = $header->getPluralForms();
+ if (isset($plural) && $p = $header->parsePluralForms($plural)) {
+ list($nplurals, $formula) = $p;
+ db_update('languages')
+ ->fields(array(
+ 'plurals' => $nplurals,
+ 'formula' => $formula,
+ ))
+ ->condition('language', $langcode)
+ ->execute();
+ }
+ }
+ }
+
+ /**
+ * Implements PoWriterInterface::writeItem().
+ */
+ function writeItem(PoItem $item) {
+ if ($item->isPlural()) {
+ $item->setSource(join(L10N_UPDATE_PLURAL_DELIMITER, $item->getSource()));
+ $item->setTranslation(join(L10N_UPDATE_PLURAL_DELIMITER, $item->getTranslation()));
+ }
+ $this->importString($item);
+ }
+
+ /**
+ * Implements PoWriterInterface::writeItems().
+ */
+ public function writeItems(PoReaderInterface $reader, $count = -1) {
+ $forever = $count == -1;
+ while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
+ $this->writeItem($item);
+ }
+ }
+
+ /**
+ * Imports one string into the database.
+ *
+ * @param PoItem $item
+ * The item being imported.
+ *
+ * @return int
+ * The string ID of the existing string modified or the new string added.
+ */
+ private function importString(PoItem $item) {
+ // Initialize overwrite options if not set.
+ $this->_options['overwrite_options'] += array(
+ 'not_customized' => FALSE,
+ 'customized' => FALSE,
+ );
+ $overwrite_options = $this->_options['overwrite_options'];
+ $customized = $this->_options['customized'];
+
+ $context = $item->getContext();
+ $source = $item->getSource();
+ $translation = $item->getTranslation();
+
+ // Look up the source string and any existing translation.
+ $strings = $this->storage->getTranslations(array(
+ 'language' => $this->_langcode,
+ 'source' => $source,
+ 'context' => $context
+ ));
+ $string = reset($strings);
+
+ if (!empty($translation)) {
+ // Skip this string unless it passes a check for dangerous code.
+ if (!locale_string_is_safe($translation)) {
+ watchdog('l10n_update', 'Import of string "%string" was skipped because of disallowed or malformed HTML.', array('%string' => $translation), WATCHDOG_ERROR);
+ $this->_report['skips']++;
+ return 0;
+ }
+ elseif ($string) {
+ $string->setString($translation);
+ if ($string->isNew()) {
+ // No translation in this language.
+ $string->setValues(array(
+ 'language' => $this->_langcode,
+ 'customized' => $customized
+ ));
+ $string->save();
+ $this->_report['additions']++;
+ }
+ elseif ($overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
+ // Translation exists, only overwrite if instructed.
+ $string->customized = $customized;
+ $string->save();
+ $this->_report['updates']++;
+ }
+ $this->_report['strings'][] = $string->getId();
+ return $string->lid;
+ }
+ else {
+ // No such source string in the database yet.
+ $string = $this->storage->createString(array('source' => $source, 'context' => $context))
+ ->save();
+ $target = $this->storage->createTranslation(array(
+ 'lid' => $string->getId(),
+ 'language' => $this->_langcode,
+ 'translation' => $translation,
+ 'customized' => $customized,
+ ))->save();
+
+ $this->_report['additions']++;
+ $this->_report['strings'][] = $string->getId();
+ return $string->lid;
+ }
+ }
+ elseif ($string && !$string->isNew() && $overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
+ // Empty translation, remove existing if instructed.
+ $string->delete();
+ $this->_report['deletes']++;
+ $this->_report['strings'][] = $string->lid;
+ return $string->lid;
+ }
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/SourceString.php b/sites/all/modules/l10n_update/includes/locale/SourceString.php
new file mode 100644
index 000000000..9f1815edc
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/SourceString.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Definition of SourceString.
+ */
+
+/**
+ * Defines the locale source string object.
+ *
+ * This class represents a module-defined string value that is to be translated.
+ * This string must at least contain a 'source' field, which is the raw source
+ * value, and is assumed to be in English language.
+ */
+class SourceString extends StringBase {
+ /**
+ * Implements StringInterface::isSource().
+ */
+ public function isSource() {
+ return isset($this->source);
+ }
+
+ /**
+ * Implements StringInterface::isTranslation().
+ */
+ public function isTranslation() {
+ return FALSE;
+ }
+
+ /**
+ * Implements LocaleString::getString().
+ */
+ public function getString() {
+ return isset($this->source) ? $this->source : '';
+ }
+
+ /**
+ * Implements LocaleString::setString().
+ */
+ public function setString($string) {
+ $this->source = $string;
+ return $this;
+ }
+
+ /**
+ * Implements LocaleString::isNew().
+ */
+ public function isNew() {
+ return empty($this->lid);
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/StringBase.php b/sites/all/modules/l10n_update/includes/locale/StringBase.php
new file mode 100644
index 000000000..a623a4da5
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/StringBase.php
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * @file
+ * Definition of StringBase.
+ */
+
+/**
+ * Defines the locale string base class.
+ *
+ * This is the base class to be used for locale string objects and contains
+ * the common properties and methods for source and translation strings.
+ */
+abstract class StringBase implements StringInterface {
+ /**
+ * The string identifier.
+ *
+ * @var integer
+ */
+ public $lid;
+
+ /**
+ * The string locations indexed by type.
+ *
+ * @var string
+ */
+ public $locations;
+
+ /**
+ * The source string.
+ *
+ * @var string
+ */
+ public $source;
+
+ /**
+ * The string context.
+ *
+ * @var string
+ */
+ public $context;
+
+ /**
+ * The string version.
+ *
+ * @var string
+ */
+ public $version;
+
+ /**
+ * The locale storage this string comes from or is to be saved to.
+ *
+ * @var StringStorageInterface
+ */
+ protected $storage;
+
+ /**
+ * Constructs a new locale string object.
+ *
+ * @param object|array $values
+ * Object or array with initial values.
+ */
+ public function __construct($values = array()) {
+ $this->setValues((array)$values);
+ }
+
+ /**
+ * Implements StringInterface::getId().
+ */
+ public function getId() {
+ return isset($this->lid) ? $this->lid : NULL;
+ }
+
+ /**
+ * Implements StringInterface::setId().
+ */
+ public function setId($lid) {
+ $this->lid = $lid;
+ return $this;
+ }
+
+ /**
+ * Implements StringInterface::getVersion().
+ */
+ public function getVersion() {
+ return isset($this->version) ? $this->version : NULL;
+ }
+
+ /**
+ * Implements StringInterface::setVersion().
+ */
+ public function setVersion($version) {
+ $this->version = $version;
+ return $this;
+ }
+
+ /**
+ * Implements StringInterface::getPlurals().
+ */
+ public function getPlurals() {
+ return explode(L10N_UPDATE_PLURAL_DELIMITER, $this->getString());
+ }
+
+ /**
+ * Implements StringInterface::setPlurals().
+ */
+ public function setPlurals($plurals) {
+ $this->setString(implode(L10N_UPDATE_PLURAL_DELIMITER, $plurals));
+ return $this;
+ }
+
+ /**
+ * Implements StringInterface::getStorage().
+ */
+ public function getStorage() {
+ return isset($this->storage) ? $this->storage : NULL;
+ }
+
+ /**
+ * Implements StringInterface::setStorage().
+ */
+ public function setStorage($storage) {
+ $this->storage = $storage;
+ return $this;
+ }
+
+ /**
+ * Implements StringInterface::setValues().
+ */
+ public function setValues(array $values, $override = TRUE) {
+ foreach ($values as $key => $value) {
+ if (property_exists($this, $key) && ($override || !isset($this->$key))) {
+ $this->$key = $value;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Implements StringInterface::getValues().
+ */
+ public function getValues(array $fields) {
+ $values = array();
+ foreach ($fields as $field) {
+ if (isset($this->$field)) {
+ $values[$field] = $this->$field;
+ }
+ }
+ return $values;
+ }
+
+ /**
+ * Implements LocaleString::save().
+ */
+ public function save() {
+ if ($storage = $this->getStorage()) {
+ $storage->save($this);
+ }
+ else {
+ throw new StringStorageException(format_string('The string cannot be saved because its not bound to a storage: @string', array(
+ '@string' => $this->getString()
+ )));
+ }
+ return $this;
+ }
+
+ /**
+ * Implements LocaleString::delete().
+ */
+ public function delete() {
+ if (!$this->isNew()) {
+ if ($storage = $this->getStorage()) {
+ $storage->delete($this);
+ }
+ else {
+ throw new StringStorageException(format_string('The string cannot be deleted because its not bound to a storage: @string', array(
+ '@string' => $this->getString()
+ )));
+ }
+ }
+ return $this;
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php b/sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php
new file mode 100644
index 000000000..d478b07b0
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php
@@ -0,0 +1,518 @@
+<?php
+
+/**
+ * @file
+ * Definition of StringDatabaseStorage.
+ */
+
+/**
+ * Defines the locale string class.
+ *
+ * This is the base class for SourceString and TranslationString.
+ */
+class StringDatabaseStorage implements StringStorageInterface {
+
+ /**
+ * Additional database connection options to use in queries.
+ *
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * Constructs a new StringStorage controller.
+ *
+ * @param array $options
+ * (optional) Any additional database connection options to use in queries.
+ */
+ public function __construct(array $options = array()) {
+ $this->options = $options;
+ }
+
+ /**
+ * Implements StringStorageInterface::getStrings().
+ */
+ public function getStrings(array $conditions = array(), array $options = array()) {
+ return $this->dbStringLoad($conditions, $options, 'SourceString');
+ }
+
+ /**
+ * Implements StringStorageInterface::getTranslations().
+ */
+ public function getTranslations(array $conditions = array(), array $options = array()) {
+ return $this->dbStringLoad($conditions, array('translation' => TRUE) + $options, 'TranslationString');
+ }
+
+ /**
+ * Implements StringStorageInterface::findString().
+ */
+ public function findString(array $conditions) {
+ $values = $this->dbStringSelect($conditions)
+ ->execute()
+ ->fetchAssoc();
+
+ if (!empty($values)) {
+ $string = new SourceString($values);
+ $string->setStorage($this);
+ return $string;
+ }
+ }
+
+ /**
+ * Implements StringStorageInterface::findTranslation().
+ */
+ public function findTranslation(array $conditions) {
+ $values = $this->dbStringSelect($conditions, array('translation' => TRUE))
+ ->execute()
+ ->fetchAssoc();
+
+ if (!empty($values)) {
+ $string = new TranslationString($values);
+ $this->checkVersion($string, VERSION);
+ $string->setStorage($this);
+ return $string;
+ }
+ }
+
+ /**
+ * Implements StringStorageInterface::countStrings().
+ */
+ public function countStrings() {
+ return $this->dbExecute("SELECT COUNT(*) FROM {locales_source}")->fetchField();
+ }
+
+ /**
+ * Implements StringStorageInterface::countTranslations().
+ */
+ public function countTranslations() {
+ return $this->dbExecute("SELECT t.language, COUNT(*) AS translated FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid GROUP BY t.language")->fetchAllKeyed();
+ }
+
+ /**
+ * Implements StringStorageInterface::save().
+ */
+ public function save($string) {
+ if ($string->isNew()) {
+ $result = $this->dbStringInsert($string);
+ if ($string->isSource() && $result) {
+ // Only for source strings, we set the locale identifier.
+ $string->setId($result);
+ }
+ $string->setStorage($this);
+ }
+ else {
+ $this->dbStringUpdate($string);
+ }
+ return $this;
+ }
+
+ /**
+ * Checks whether the string version matches a given version, fix it if not.
+ *
+ * @param StringInterface $string
+ * The string object.
+ * @param string $version
+ * Drupal version to check against.
+ */
+ protected function checkVersion($string, $version) {
+ if ($string->getId() && $string->getVersion() != $version) {
+ $string->setVersion($version);
+ db_update('locales_source', $this->options)
+ ->condition('lid', $string->getId())
+ ->fields(array('version' => $version))
+ ->execute();
+ }
+ }
+
+ /**
+ * Implements StringStorageInterface::delete().
+ */
+ public function delete($string) {
+ if ($keys = $this->dbStringKeys($string)) {
+ $this->dbDelete('locales_target', $keys)->execute();
+ if ($string->isSource()) {
+ $this->dbDelete('locales_source', $keys)->execute();
+ $this->dbDelete('locales_location', $keys)->execute();
+ $string->setId(NULL);
+ }
+ }
+ else {
+ throw new StringStorageException(format_string('The string cannot be deleted because it lacks some key fields: @string', array(
+ '@string' => $string->getString()
+ )));
+ }
+ return $this;
+ }
+
+ /**
+ * Implements StringStorageInterface::deleteLanguage().
+ */
+ public function deleteStrings($conditions) {
+ $lids = $this->dbStringSelect($conditions, array('fields' => array('lid')))->execute()->fetchCol();
+ if ($lids) {
+ $this->dbDelete('locales_target', array('lid' => $lids))->execute();
+ $this->dbDelete('locales_source', array('lid' => $lids))->execute();
+ $this->dbDelete('locales_location', array('sid' => $lids))->execute();
+ }
+ }
+
+ /**
+ * Implements StringStorageInterface::deleteLanguage().
+ */
+ public function deleteTranslations($conditions) {
+ $this->dbDelete('locales_target', $conditions)->execute();
+ }
+
+ /**
+ * Implements StringStorageInterface::createString().
+ */
+ public function createString($values = array()) {
+ return new SourceString($values + array('storage' => $this));
+ }
+
+ /**
+ * Implements StringStorageInterface::createTranslation().
+ */
+ public function createTranslation($values = array()) {
+ return new TranslationString($values + array(
+ 'storage' => $this,
+ 'is_new' => TRUE
+ ));
+ }
+
+ /**
+ * Gets table alias for field.
+ *
+ * @param string $field
+ * Field name to find the table alias for.
+ *
+ * @return string
+ * Either 's', 't' or 'l' depending on whether the field belongs to source,
+ * target or location table table.
+ */
+ protected function dbFieldTable($field) {
+ if (in_array($field, array('language', 'translation', 'customized'))) {
+ return 't';
+ }
+ elseif (in_array($field, array('type', 'name'))) {
+ return 'l';
+ }
+ else {
+ return 's';
+ }
+ }
+
+ /**
+ * Gets table name for storing string object.
+ *
+ * @param StringInterface $string
+ * The string object.
+ *
+ * @return string
+ * The table name.
+ */
+ protected function dbStringTable($string) {
+ if ($string->isSource()) {
+ return 'locales_source';
+ }
+ elseif ($string->isTranslation()) {
+ return 'locales_target';
+ }
+ }
+
+ /**
+ * Gets keys values that are in a database table.
+ *
+ * @param StringInterface $string
+ * The string object.
+ *
+ * @return array
+ * Array with key fields if the string has all keys, or empty array if not.
+ */
+ protected function dbStringKeys($string) {
+ if ($string->isSource()) {
+ $keys = array('lid');
+ }
+ elseif ($string->isTranslation()) {
+ $keys = array('lid', 'language');
+ }
+ if (!empty($keys) && ($values = $string->getValues($keys)) && count($keys) == count($values)) {
+ return $values;
+ }
+ else {
+ return array();
+ }
+ }
+
+ /**
+ * Loads multiple string objects.
+ *
+ * @param array $conditions
+ * Any of the conditions used by dbStringSelect().
+ * @param array $options
+ * Any of the options used by dbStringSelect().
+ * @param string $class
+ * Class name to use for fetching returned objects.
+ *
+ * @return array
+ * Array of objects of the class requested.
+ */
+ protected function dbStringLoad(array $conditions, array $options, $class) {
+ $strings = array();
+ $result = $this->dbStringSelect($conditions, $options)->execute();
+ foreach ($result as $item) {
+ $string = new $class($item);
+ $string->setStorage($this);
+ $strings[] = $string;
+ }
+ return $strings;
+ }
+
+ /**
+ * Builds a SELECT query with multiple conditions and fields.
+ *
+ * The query uses both 'locales_source' and 'locales_target' tables.
+ * Note that by default, as we are selecting both translated and untranslated
+ * strings target field's conditions will be modified to match NULL rows too.
+ *
+ * @param array $conditions
+ * An associative array with field => value conditions that may include
+ * NULL values. If a language condition is included it will be used for
+ * joining the 'locales_target' table.
+ * @param array $options
+ * An associative array of additional options. It may contain any of the
+ * options used by StringStorageInterface::getStrings() and these additional
+ * ones:
+ * - 'translation', Whether to include translation fields too. Defaults to
+ * FALSE.
+ * @return SelectQuery
+ * Query object with all the tables, fields and conditions.
+ */
+ protected function dbStringSelect(array $conditions, array $options = array()) {
+ // Change field 'customized' into 'l10n_status'. This enables the Drupal 8
+ // backported code to work with the Drupal 7 style database tables.
+ if (isset($conditions['customized'])) {
+ $conditions['l10n_status'] = $conditions['customized'];
+ unset($conditions['customized']);
+ }
+ if (isset($options['customized'])) {
+ $options['l10n_status'] = $options['customized'];
+ unset($options['customized']);
+ }
+ // Start building the query with source table and check whether we need to
+ // join the target table too.
+ $query = db_select('locales_source', 's', $this->options)
+ ->fields('s');
+
+ // Figure out how to join and translate some options into conditions.
+ if (isset($conditions['translated'])) {
+ // This is a meta-condition we need to translate into simple ones.
+ if ($conditions['translated']) {
+ // Select only translated strings.
+ $join = 'innerJoin';
+ }
+ else {
+ // Select only untranslated strings.
+ $join = 'leftJoin';
+ $conditions['translation'] = NULL;
+ }
+ unset($conditions['translated']);
+ }
+ else {
+ $join = !empty($options['translation']) ? 'leftJoin' : FALSE;
+ }
+
+ if ($join) {
+ if (isset($conditions['language'])) {
+ // If we've got a language condition, we use it for the join.
+ $query->$join('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", array(
+ ':langcode' => $conditions['language']
+ ));
+ unset($conditions['language']);
+ }
+ else {
+ // Since we don't have a language, join with locale id only.
+ $query->$join('locales_target', 't', "t.lid = s.lid");
+ }
+ if (!empty($options['translation'])) {
+ // We cannot just add all fields because 'lid' may get null values.
+ $query->addField('t', 'language');
+ $query->addField('t', 'translation');
+ $query->addField('t', 'l10n_status', 'customized');
+ }
+ }
+
+ // If we have conditions for location's type or name, then we need the
+ // location table, for which we add a subquery.
+ if (isset($conditions['type']) || isset($conditions['name'])) {
+ $subquery = db_select('locales_location', 'l', $this->options)
+ ->fields('l', array('sid'));
+ foreach (array('type', 'name') as $field) {
+ if (isset($conditions[$field])) {
+ $subquery->condition('l.' . $field, $conditions[$field]);
+ unset($conditions[$field]);
+ }
+ }
+ $query->condition('s.lid', $subquery, 'IN');
+ }
+
+ // Add conditions for both tables.
+ foreach ($conditions as $field => $value) {
+ $table_alias = $this->dbFieldTable($field);
+ $field_alias = $table_alias . '.' . $field;
+ if (is_null($value)) {
+ $query->isNull($field_alias);
+ }
+ elseif ($table_alias == 't' && $join === 'leftJoin') {
+ // Conditions for target fields when doing an outer join only make
+ // sense if we add also OR field IS NULL.
+ $query->condition(db_or()
+ ->condition($field_alias, $value)
+ ->isNull($field_alias)
+ );
+ }
+ else {
+ $query->condition($field_alias, $value);
+ }
+ }
+
+ // Process other options, string filter, query limit, etc...
+ if (!empty($options['filters'])) {
+ if (count($options['filters']) > 1) {
+ $filter = db_or();
+ $query->condition($filter);
+ }
+ else {
+ // If we have a single filter, just add it to the query.
+ $filter = $query;
+ }
+ foreach ($options['filters'] as $field => $string) {
+ $filter->condition($this->dbFieldTable($field) . '.' . $field, '%' . db_like($string) . '%', 'LIKE');
+ }
+ }
+
+ if (!empty($options['pager limit'])) {
+ $query = $query->extend('PagerDefault')->limit($options['pager limit']);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Createds a database record for a string object.
+ *
+ * @param StringInterface $string
+ * The string object.
+ *
+ * @return bool|int
+ * If the operation failed, returns FALSE.
+ * If it succeeded returns the last insert ID of the query, if one exists.
+ *
+ * @throws StringStorageException
+ * If the string is not suitable for this storage, an exception ithrown.
+ */
+ protected function dbStringInsert($string) {
+ if ($string->isSource()) {
+ $string->setValues(array('context' => '', 'version' => 'none'), FALSE);
+ $fields = $string->getValues(array('source', 'context', 'version'));
+ }
+ elseif ($string->isTranslation()) {
+ $string->setValues(array('customized' => 0), FALSE);
+ $fields = $string->getValues(array('lid', 'language', 'translation', 'customized'));
+ }
+ if (!empty($fields)) {
+ // Change field 'customized' into 'l10n_status'. This enables the Drupal 8
+ // backported code to work with the Drupal 7 style database tables.
+ if (isset($fields['customized'])) {
+ $fields['l10n_status'] = $fields['customized'];
+ unset($fields['customized']);
+ }
+
+ return db_insert($this->dbStringTable($string), $this->options)
+ ->fields($fields)
+ ->execute();
+ }
+ else {
+ throw new StringStorageException(format_string('The string cannot be saved: @string', array(
+ '@string' => $string->getString()
+ )));
+ }
+ }
+
+ /**
+ * Updates string object in the database.
+ *
+ * @param StringInterface $string
+ * The string object.
+ *
+ * @return bool|int
+ * If the record update failed, returns FALSE. If it succeeded, returns
+ * SAVED_NEW or SAVED_UPDATED.
+ *
+ * @throws StringStorageException
+ * If the string is not suitable for this storage, an exception is thrown.
+ */
+ protected function dbStringUpdate($string) {
+ if ($string->isSource()) {
+ $values = $string->getValues(array('source', 'context', 'version'));
+ }
+ elseif ($string->isTranslation()) {
+ $values = $string->getValues(array('translation', 'customized'));
+ }
+ if (!empty($values) && $keys = $this->dbStringKeys($string)) {
+ // Change field 'customized' into 'l10n_status'. This enables the Drupal 8
+ // backported code to work with the Drupal 7 style database tables.
+ if (isset($keys['customized'])) {
+ $keys['l10n_status'] = $keys['customized'];
+ unset($keys['customized']);
+ }
+ if (isset($values['customized'])) {
+ $values['l10n_status'] = $values['customized'];
+ unset($values['customized']);
+ }
+
+ return db_merge($this->dbStringTable($string), $this->options)
+ ->key($keys)
+ ->fields($values)
+ ->execute();
+ }
+ else {
+ throw new StringStorageException(format_string('The string cannot be updated: @string', array(
+ '@string' => $string->getString()
+ )));
+ }
+ }
+
+ /**
+ * Creates delete query.
+ *
+ * @param string $table
+ * The table name.
+ * @param array $keys
+ * Array with object keys indexed by field name.
+ *
+ * @return DeleteQuery
+ * Returns a new DeleteQuery object for the active database.
+ */
+ protected function dbDelete($table, $keys) {
+ $query = db_delete($table, $this->options);
+ // Change field 'customized' into 'l10n_status'. This enables the Drupal 8
+ // backported code to work with the Drupal 7 style database tables.
+ if (isset($keys['customized'])) {
+ $keys['l10n_status'] = $keys['customized'];
+ unset($keys['customized']);
+ }
+
+ foreach ($keys as $field => $value) {
+ $query->condition($field, $value);
+ }
+ return $query;
+ }
+
+ /**
+ * Executes an arbitrary SELECT query string.
+ */
+ protected function dbExecute($query, array $args = array()) {
+ return db_query($query, $args, $this->options);
+ }
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/StringInterface.php b/sites/all/modules/l10n_update/includes/locale/StringInterface.php
new file mode 100644
index 000000000..9709c8213
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/StringInterface.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * @file
+ * Definition of StringInterface.
+ */
+
+/**
+ * Defines the locale string interface.
+ */
+interface StringInterface {
+
+ /**
+ * Gets the string unique identifier.
+ *
+ * @return int
+ * The string identifier.
+ */
+ public function getId();
+
+ /**
+ * Sets the string unique identifier.
+ *
+ * @param int $id
+ * The string identifier.
+ *
+ * @return LocaleString
+ * The called object.
+ */
+ public function setId($id);
+
+ /**
+ * Gets the string version.
+ *
+ * @return string
+ * Version identifier.
+ */
+ public function getVersion();
+
+ /**
+ * Sets the string version.
+ *
+ * @param string $version
+ * Version identifier.
+ *
+ * @return LocaleString
+ * The called object.
+ */
+ public function setVersion($version);
+
+ /**
+ * Gets plain string contained in this object.
+ *
+ * @return string
+ * The string contained in this object.
+ */
+ public function getString();
+
+ /**
+ * Sets the string contained in this object.
+ *
+ * @param string $string
+ * String to set as value.
+ *
+ * @return LocaleString
+ * The called object.
+ */
+ public function setString($string);
+
+ /**
+ * Splits string to work with plural values.
+ *
+ * @return array
+ * Array of strings that are plural variants.
+ */
+ public function getPlurals();
+
+ /**
+ * Sets this string using array of plural values.
+ *
+ * Serializes plural variants in one string glued by L10N_UPDATE_PLURAL_DELIMITER.
+ *
+ * @param array $plurals
+ * Array of strings with plural variants.
+ *
+ * @return LocaleString
+ * The called object.
+ */
+ public function setPlurals($plurals);
+
+ /**
+ * Gets the string storage.
+ *
+ * @return StringStorageInterface
+ * The storage used for this string.
+ */
+ public function getStorage();
+
+ /**
+ * Sets the string storage.
+ *
+ * @param StringStorageInterface $storage
+ * The storage to use for this string.
+ *
+ * @return LocaleString
+ * The called object.
+ */
+ public function setStorage($storage);
+
+ /**
+ * Checks whether the object is not saved to storage yet.
+ *
+ * @return bool
+ * TRUE if the object exists in the storage, FALSE otherwise.
+ */
+ public function isNew();
+
+ /**
+ * Checks whether the object is a source string.
+ *
+ * @return bool
+ * TRUE if the object is a source string, FALSE otherwise.
+ */
+ public function isSource();
+
+ /**
+ * Checks whether the object is a translation string.
+ *
+ * @return bool
+ * TRUE if the object is a translation string, FALSE otherwise.
+ */
+ public function isTranslation();
+
+ /**
+ * Sets an array of values as object properties.
+ *
+ * @param array $values
+ * Array with values indexed by property name,
+ * @param bool $override
+ * (optional) Whether to override already set fields, defaults to TRUE.
+ *
+ * @return LocaleString
+ * The called object.
+ */
+ public function setValues(array $values, $override = TRUE);
+
+ /**
+ * Gets field values that are set for given field names.
+ *
+ * @param array $fields
+ * Array of field names.
+ *
+ * @return array
+ * Array of field values indexed by field name.
+ */
+ public function getValues(array $fields);
+
+ /**
+ * Saves string object to storage.
+ *
+ * @return LocaleString
+ * The called object.
+ *
+ * @throws StringStorageException
+ * In case of failures, an exception is thrown.
+ */
+ public function save();
+
+ /**
+ * Deletes string object from storage.
+ *
+ * @return LocaleString
+ * The called object.
+ *
+ * @throws StringStorageException
+ * In case of failures, an exception is thrown.
+ */
+ public function delete();
+
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/StringStorageException.php b/sites/all/modules/l10n_update/includes/locale/StringStorageException.php
new file mode 100644
index 000000000..946fae500
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/StringStorageException.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Entity\StringStorageException.
+ */
+
+/**
+ * Defines an exception thrown when storage operations fail.
+ */
+class StringStorageException extends \Exception { }
diff --git a/sites/all/modules/l10n_update/includes/locale/StringStorageInterface.php b/sites/all/modules/l10n_update/includes/locale/StringStorageInterface.php
new file mode 100644
index 000000000..e2f5d4342
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/StringStorageInterface.php
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * @file
+ * Contains \StringStorageInterface.
+ */
+
+/**
+ * Defines the locale string storage interface.
+ */
+interface StringStorageInterface {
+
+ /**
+ * Loads multiple source string objects.
+ *
+ * @param array $conditions
+ * (optional) Array with conditions that will be used to filter the strings
+ * returned and may include any of the following elements:
+ * - Any simple field value indexed by field name.
+ * - 'translated', TRUE to get only translated strings or FALSE to get only
+ * untranslated strings. If not set it returns both translated and
+ * untranslated strings that fit the other conditions.
+ * Defaults to no conditions which means that it will load all strings.
+ * @param array $options
+ * (optional) An associative array of additional options. It may contain
+ * any of the following optional keys:
+ * - 'filters': Array of string filters indexed by field name.
+ * - 'pager limit': Use pager and set this limit value.
+ *
+ * @return array
+ * Array of \StringInterface objects matching the conditions.
+ */
+ public function getStrings(array $conditions = array(), array $options = array());
+
+ /**
+ * Loads multiple string translation objects.
+ *
+ * @param array $conditions
+ * (optional) Array with conditions that will be used to filter the strings
+ * returned and may include all of the conditions defined by getStrings().
+ * @param array $options
+ * (optional) An associative array of additional options. It may contain
+ * any of the options defined by getStrings().
+ *
+ * @return array
+ * Array of \StringInterface objects matching the conditions.
+ *
+ * @see StringStorageInterface::getStrings()
+ */
+ public function getTranslations(array $conditions = array(), array $options = array());
+
+ /**
+ * Loads a string source object, fast query.
+ *
+ * These 'fast query' methods are the ones in the critical path and their
+ * implementation must be optimized for speed, as they may run many times
+ * in a single page request.
+ *
+ * @param array $conditions
+ * (optional) Array with conditions that will be used to filter the strings
+ * returned and may include all of the conditions defined by getStrings().
+ *
+ * @return \SourceString|null
+ * Minimal TranslationString object if found, NULL otherwise.
+ */
+ public function findString(array $conditions);
+
+ /**
+ * Loads a string translation object, fast query.
+ *
+ * This function must only be used when actually translating strings as it
+ * will have the effect of updating the string version. For other purposes
+ * the getTranslations() method should be used instead.
+ *
+ * @param array $conditions
+ * (optional) Array with conditions that will be used to filter the strings
+ * returned and may include all of the conditions defined by getStrings().
+ *
+ * @return \TranslationString|null
+ * Minimal TranslationString object if found, NULL otherwise.
+ */
+ public function findTranslation(array $conditions);
+
+ /**
+ * Save string object to storage.
+ *
+ * @param \StringInterface $string
+ * The string object.
+ *
+ * @return \StringStorageInterface
+ * The called object.
+ *
+ * @throws \StringStorageException
+ * In case of failures, an exception is thrown.
+ */
+ public function save($string);
+
+ /**
+ * Delete string from storage.
+ *
+ * @param \StringInterface $string
+ * The string object.
+ *
+ * @return \StringStorageInterface
+ * The called object.
+ *
+ * @throws \StringStorageException
+ * In case of failures, an exception is thrown.
+ */
+ public function delete($string);
+
+ /**
+ * Deletes source strings and translations using conditions.
+ *
+ * @param array $conditions
+ * Array with simple field conditions for source strings.
+ */
+ public function deleteStrings($conditions);
+
+ /**
+ * Deletes translations using conditions.
+ *
+ * @param array $conditions
+ * Array with simple field conditions for string translations.
+ */
+ public function deleteTranslations($conditions);
+
+ /**
+ * Counts source strings.
+ *
+ * @return int
+ * The number of source strings contained in the storage.
+ */
+ public function countStrings();
+
+ /**
+ * Counts translations.
+ *
+ * @return array
+ * The number of translations for each language indexed by language code.
+ */
+ public function countTranslations();
+
+ /**
+ * Creates a source string object bound to this storage but not saved.
+ *
+ * @param array $values
+ * (optional) Array with initial values. Defaults to empty array.
+ *
+ * @return \SourceString
+ * New source string object.
+ */
+ public function createString($values = array());
+
+ /**
+ * Creates a string translation object bound to this storage but not saved.
+ *
+ * @param array $values
+ * (optional) Array with initial values. Defaults to empty array.
+ *
+ * @return \TranslationString
+ * New string translation object.
+ */
+ public function createTranslation($values = array());
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/TranslationString.php b/sites/all/modules/l10n_update/includes/locale/TranslationString.php
new file mode 100644
index 000000000..39f7196e5
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/TranslationString.php
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * @file
+ * Definition of TranslationString.
+ */
+
+/**
+ * Defines the locale translation string object.
+ *
+ * This class represents a translation of a source string to a given language,
+ * thus it must have at least a 'language' which is the language code and a
+ * 'translation' property which is the translated text of the the source string
+ * in the specified language.
+ */
+class TranslationString extends StringBase {
+ /**
+ * The language code.
+ *
+ * @var string
+ */
+ public $language;
+
+ /**
+ * The string translation.
+ *
+ * @var string
+ */
+ public $translation;
+
+ /**
+ * Integer indicating whether this string is customized.
+ *
+ * @var int
+ */
+ public $customized;
+
+ /**
+ * Boolean indicating whether the string object is new.
+ *
+ * @var bool
+ */
+ protected $is_new;
+
+ /**
+ * Overrides StringBase::__construct().
+ */
+ public function __construct($values = array()) {
+ parent::__construct($values);
+ if (!isset($this->is_new)) {
+ // We mark the string as not new if it is a complete translation.
+ // This will work when loading from database, otherwise the storage
+ // controller that creates the string object must handle it.
+ $this->is_new = !$this->isTranslation();
+ }
+ }
+
+ /**
+ * Sets the string as customized / not customized.
+ *
+ * @param bool $customized
+ * (optional) Whether the string is customized or not. Defaults to TRUE.
+ *
+ * @return TranslationString
+ * The called object.
+ */
+ public function setCustomized($customized = TRUE) {
+ $this->customized = $customized ? L10N_UPDATE_CUSTOMIZED : L10N_UPDATE_NOT_CUSTOMIZED;
+ return $this;
+ }
+
+ /**
+ * Implements StringInterface::isSource().
+ */
+ public function isSource() {
+ return FALSE;
+ }
+
+ /**
+ * Implements StringInterface::isTranslation().
+ */
+ public function isTranslation() {
+ return !empty($this->lid) && !empty($this->language) && isset($this->translation);
+ }
+
+ /**
+ * Implements StringInterface::getString().
+ */
+ public function getString() {
+ return isset($this->translation) ? $this->translation : '';
+ }
+
+ /**
+ * Implements StringInterface::setString().
+ */
+ public function setString($string) {
+ $this->translation = $string;
+ return $this;
+ }
+
+ /**
+ * Implements StringInterface::isNew().
+ */
+ public function isNew() {
+ return $this->is_new;
+ }
+
+ /**
+ * Implements StringInterface::save().
+ */
+ public function save() {
+ parent::save();
+ $this->is_new = FALSE;
+ return $this;
+ }
+
+ /**
+ * Implements StringInterface::delete().
+ */
+ public function delete() {
+ parent::delete();
+ $this->is_new = TRUE;
+ return $this;
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/includes/locale/TranslationsStreamWrapper.php b/sites/all/modules/l10n_update/includes/locale/TranslationsStreamWrapper.php
new file mode 100644
index 000000000..1b1948a15
--- /dev/null
+++ b/sites/all/modules/l10n_update/includes/locale/TranslationsStreamWrapper.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Definition of TranslationStreamWrapper.
+ */
+
+/**
+ * A Drupal interface translations (translations://) stream wrapper class.
+ *
+ * Supports storing translation files.
+ */
+class TranslationsStreamWrapper extends DrupalLocalStreamWrapper {
+ /**
+ * Implements abstract public function getDirectoryPath()
+ */
+ public function getDirectoryPath() {
+ return variable_get('l10n_update_download_store', L10N_UPDATE_DEFAULT_TRANSLATION_PATH);
+ }
+
+ /**
+ * Overrides getExternalUrl().
+ */
+ function getExternalUrl() {
+ throw new Exception('PO files URL should not be public.');
+ }
+}
diff --git a/sites/all/modules/l10n_update/js/l10n_update.admin.js b/sites/all/modules/l10n_update/js/l10n_update.admin.js
new file mode 100644
index 000000000..a785f0780
--- /dev/null
+++ b/sites/all/modules/l10n_update/js/l10n_update.admin.js
@@ -0,0 +1,37 @@
+(function ($) {
+
+/**
+ * Show/hide the description details on Available translation updates page.
+ */
+Drupal.behaviors.hideUpdateInformation = {
+ attach: function (context, settings) {
+ var $table = $('#l10n-update-status-form').once('expand-updates');
+ if ($table.length) {
+ var $tbodies = $table.find('tbody');
+
+ // Open/close the description details by toggling a tr class.
+ $tbodies.find('.description').bind('click keydown', function (e) {
+ if (e.keyCode && (e.keyCode !== 13 && e.keyCode !== 32)) {
+ return;
+ }
+ e.preventDefault();
+ var $tr = $(this).closest('tr');
+
+ $tr.toggleClass('expanded');
+
+ // Change screen reader text.
+ $tr.find('.update-description-prefix').text(function () {
+ if ($tr.hasClass('expanded')) {
+ return Drupal.t('Hide description');
+ }
+ else {
+ return Drupal.t('Show description');
+ }
+ });
+ });
+ $table.find('.requirements, .links').hide();
+ }
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/l10n_update/l10n_update-translation-last-check.tpl.php b/sites/all/modules/l10n_update/l10n_update-translation-last-check.tpl.php
new file mode 100644
index 000000000..a6798188d
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update-translation-last-check.tpl.php
@@ -0,0 +1,18 @@
+<?php
+/**
+* @file
+* Default theme implementation for the last time we checked for update data.
+*
+* Available variables:
+* - $last_checked: User interface string with the formatted time ago when the
+ * site last checked for available updates.
+* - $link: A link to manually check available updates.
+*
+* @see template_preprocess_l10n_update_last_check()
+*
+* @ingroup themeable
+*/
+?>
+<div class="l10n_update-checked">
+ <p><?php print $last_checked; ?> <span class="check-manually">(<?php print $link; ?>)</span></p>
+</div>
diff --git a/sites/all/modules/l10n_update/l10n_update-translation-update-info.tpl.php b/sites/all/modules/l10n_update/l10n_update-translation-update-info.tpl.php
new file mode 100644
index 000000000..3a5606252
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update-translation-update-info.tpl.php
@@ -0,0 +1,31 @@
+<?php
+/**
+* @file
+* Default theme implementation for displaying translation status information.
+*
+* Displays translation status information per language.
+*
+* Available variables:
+* - module_list: A list of names of modules that have available translation
+* updates.
+* - details: Rendered list of the translation details.
+* - missing_updates_status: If there are any modules that are missing
+* translation updates, this variable will contain text indicating how many
+* modules are missing translations.
+*
+* @see template_preprocess_l10n_update_update_info()
+*
+* @ingroup themeable
+*/
+?>
+<div class="inner" tabindex="0" role="button">
+ <span class="update-description-prefix visually-hidden">Show description</span>
+ <?php if($module_list): ?>
+ <span class="text"><?php print $module_list; ?></span>
+ <?php elseif($missing_updates_status): ?>
+ <span class="text"><?php print $missing_updates_status; ?></span>
+ <?php endif; ?>
+ <?php if($details): ?>
+ <div class="details"><?php print drupal_render($details); ?></div>
+ <?php endif; ?>
+</div>
diff --git a/sites/all/modules/l10n_update/l10n_update.admin.inc b/sites/all/modules/l10n_update/l10n_update.admin.inc
new file mode 100644
index 000000000..96dae0f74
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.admin.inc
@@ -0,0 +1,490 @@
+<?php
+
+/**
+ * @file
+ * Admin settings and update page.
+ */
+
+/**
+ * Page callback: Checks for translation updates and displays the status.
+ *
+ * Manually checks the translation status without the use of cron.
+ */
+function l10n_update_manual_status() {
+ module_load_include('compare.inc', 'l10n_update');
+
+ // Check the translation status of all translatable projects in all languages.
+ // First we clear the cached list of projects. Although not strictly
+ // necessary, this is helpful in case the project list is out of sync.
+ l10n_update_flush_projects();
+ l10n_update_check_projects();
+
+ // Execute a batch if required. A batch is only used when remote files
+ // are checked.
+ if (batch_get()) {
+ batch_process('admin/config/regional/translate/update');
+ }
+ drupal_goto('admin/config/regional/translate/update');
+}
+
+/**
+ * Page callback: Translation status page.
+ */
+function l10n_update_status_form() {
+ module_load_include('compare.inc', 'l10n_update');
+ $updates = $options = array();
+ $languages_update = $languages_not_found = array();
+ $projects_update = array();
+
+ // @todo Calling l10n_update_build_projects() is an expensive way to
+ // get a module name. In follow-up issue http://drupal.org/node/1842362
+ // the project name will be stored to display use, like here.
+ $project_data = l10n_update_build_projects();
+ $languages = l10n_update_translatable_language_list();
+ $status = l10n_update_get_status();
+
+ // Prepare information about projects which have available translation
+ // updates.
+ if ($languages && $status) {
+ foreach ($status as $project) {
+ foreach ($project as $langcode => $project_info) {
+ if (isset($project_data[$project_info->name])) {
+ // No translation file found for this project-language combination.
+ if (empty($project_info->type)) {
+ $updates[$langcode]['not_found'][] = array(
+ 'name' => $project_info->name == 'drupal' ? t('Drupal core') : $project_data[$project_info->name]->info['name'],
+ 'version' => $project_info->version,
+ 'info' => _l10n_update_status_debug_info($project_info),
+ );
+ $languages_not_found[$langcode] = $langcode;
+ }
+ // Translation update found for this project-language combination.
+ elseif ($project_info->type == L10N_UPDATE_LOCAL || $project_info->type == L10N_UPDATE_REMOTE ) {
+ $local = isset($project_info->files[L10N_UPDATE_LOCAL]) ? $project_info->files[L10N_UPDATE_LOCAL] : NULL;
+ $remote = isset($project_info->files[L10N_UPDATE_REMOTE]) ? $project_info->files[L10N_UPDATE_REMOTE] : NULL;
+ $recent = _l10n_update_source_compare($local, $remote) == L10N_UPDATE_SOURCE_COMPARE_LT ? $remote : $local;
+ $updates[$langcode]['updates'][] = array(
+ 'name' => $project_info->name == 'drupal' ? t('Drupal core') : $project_data[$project_info->name]->info['name'],
+ 'version' => $project_info->version,
+ 'timestamp' => $recent->timestamp,
+ );
+ $languages_update[$langcode] = $langcode;
+ $projects_update[$project_info->name] = $project_info->name;
+ }
+ }
+ }
+ }
+ $languages_not_found = array_diff($languages_not_found, $languages_update);
+
+ // Build data options for the select table.
+ foreach($updates as $langcode => $update) {
+ $title = check_plain($languages[$langcode]);
+ $l10n_update_update_info = array('#theme' => 'l10n_update_update_info');
+ foreach (array('updates', 'not_found') as $update_status) {
+ if (isset($update[$update_status])) {
+ $l10n_update_update_info['#' . $update_status] = $update[$update_status];
+ }
+ }
+ $options[$langcode] = array(
+ 'title' => array(
+ 'class' => array('label'),
+ 'data' => array(
+ '#title' => $title,
+ '#markup' => $title
+ ),
+ ),
+ 'status' => array('class' => array('description', 'expand', 'priority-low'), 'data' => drupal_render($l10n_update_update_info)),
+ );
+ }
+ // Sort the table data on language name.
+ uasort($options, function ($a, $b) {
+ return strcasecmp($a['title']['data']['#title'], $b['title']['data']['#title']);
+ });
+ }
+
+ $last_checked = variable_get('l10n_update_last_check');
+ $form['last_checked'] = array(
+ '#theme' => 'l10n_update_last_check',
+ '#last' => $last_checked,
+ );
+
+ $header = array(
+ 'title' => array(
+ 'data' => t('Language'),
+ 'class' => array('title'),
+ ),
+ 'status' => array(
+ 'data' => t('Status'),
+ 'class' => array('status', 'priority-low'),
+ ),
+ );
+
+ if (!$languages) {
+ $empty = t('No translatable languages available. <a href="@add_language">Add a language</a> first.', array('@add_language' => url('admin/config/regional/language')));
+ }
+ elseif (empty($options)) {
+ $empty = t('All translations up to date.');
+ }
+ else {
+ $empty = t('No translation status available. <a href="@check">Check manually</a>.', array('@check' => url('admin/config/regional/translate/check')));
+ }
+
+ // The projects which require an update. Used by the _submit callback.
+ $form['projects_update'] = array(
+ '#type' => 'value',
+ '#value' => $projects_update,
+ );
+ $form['langcodes'] = array(
+ '#type' => 'tableselect',
+ '#header' => $header,
+ '#options' => $options,
+ '#default_value' => $languages_update,
+ '#empty' => $empty,
+ '#js_select' => TRUE,
+ '#multiple' => TRUE,
+ '#required' => TRUE,
+ '#not_found' => $languages_not_found,
+ '#after_build' => array('l10n_update_language_table'),
+ '#attributes' => array(),
+ );
+
+ $form['#attached'] = array(
+ 'js' => array(
+ drupal_get_path('module', 'l10n_update') . '/js/l10n_update.admin.js',
+ ),
+ 'css' => array(
+ drupal_get_path('module', 'l10n_update') . '/css/l10n_update.admin.css',
+ ),
+ );
+
+ if ($languages_update) {
+ $form['actions'] = array(
+ '#type' => 'actions',
+ 'submit' => array(
+ '#type' => 'submit',
+ '#value' => t('Update translations'),
+ ),
+ '#attributes' => array(),
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Form validation handler for locale_translation_status_form().
+ */
+function l10n_update_status_form_validate($form, &$form_state) {
+ // Check if a language has been selected. 'tableselect' doesn't.
+ if (!array_filter($form_state['values']['langcodes'])) {
+ form_set_error('', t('Select a language to update.'));
+ }
+}
+
+/**
+ * Form submission handler for locale_translation_status_form().
+ */
+function l10n_update_status_form_submit($form, $form_state) {
+ module_load_include('fetch.inc', 'l10n_update');
+ $langcodes = array_filter($form_state['values']['langcodes']);
+ $projects = array_filter($form_state['values']['projects_update']);
+
+ // Set the translation import options. This determines if existing
+ // translations will be overwritten by imported strings.
+ $options = _l10n_update_default_update_options();
+
+ // If the status was updated recently we can immediately start fetching the
+ // translation updates. If the status is expired we clear it an run a batch to
+ // update the status and then fetch the translation updates.
+ $last_checked = variable_get('l10n_update_last_check');
+ if ($last_checked < REQUEST_TIME - L10N_UPDATE_STATUS_TTL) {
+ l10n_update_clear_status();
+ $batch = l10n_update_batch_update_build(array(), $langcodes, $options);
+ batch_set($batch);
+ }
+ else {
+ $batch = l10n_update_batch_fetch_build($projects, $langcodes, $options);
+ batch_set($batch);
+ }
+}
+
+/**
+ * Page callback: Settings form.
+ */
+function l10n_update_admin_settings_form($form, &$form_state) {
+ $form['l10n_update_check_frequency'] = array(
+ '#type' => 'radios',
+ '#title' => t('Check for updates'),
+ '#default_value' => variable_get('l10n_update_check_frequency', '0'),
+ '#options' => array(
+ '0' => t('Never (manually)'),
+ '7' => t('Weekly'),
+ '30' => t('Monthly'),
+ ),
+ '#description' => t('Select how frequently you want to check for new interface translations for your currently installed modules and themes. <a href="@url">Check updates now</a>.', array('@url' => url('admin/config/regional/translate/check'))),
+ );
+
+ $form['l10n_update_check_disabled'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Check for updates of disabled modules and themes'),
+ '#default_value' => variable_get('l10n_update_check_disabled', FALSE),
+ );
+
+ $form['l10n_update_check_mode'] = array(
+ '#type' => 'radios',
+ '#title' => t('Translation source'),
+ '#default_value' => variable_get('l10n_update_check_mode', L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL),
+ '#options' => array(
+ L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL => t('Drupal translation server and local files'),
+ L10N_UPDATE_USE_SOURCE_LOCAL => t('Local files only'),
+ ),
+ '#description' => t('The source of translation files for automatic interface translation.'),
+ );
+
+ $form['l10n_update_download_store'] = array(
+ '#title' => t('Translations directory'),
+ '#type' => 'textfield',
+ '#default_value' => variable_get('l10n_update_download_store', L10N_UPDATE_DEFAULT_TRANSLATION_PATH),
+ '#required' => TRUE,
+ '#description' => t('A path relative to the Drupal installation directory where translation files will be stored, e.g. sites/all/translations. Saved translation files can be reused by other installations.'),
+ );
+
+ $form['l10n_update_import_mode'] = array(
+ '#type' => 'radios',
+ '#title' => t('Import behaviour'),
+ '#default_value' => variable_get('l10n_update_import_mode', LOCALE_IMPORT_KEEP),
+ '#options' => array(
+ LOCALE_IMPORT_KEEP => t("Don't overwrite existing translations."),
+ L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED => t('Only overwrite imported translations, customized translations are kept.'),
+ LOCALE_IMPORT_OVERWRITE => t('Overwrite existing translations.'),
+ ),
+ '#description' => t('How to treat existing translations when automatically updating the interface translations.'),
+ );
+
+ $form['#submit'][] = 'l10n_update_admin_settings_form_submit';
+ return system_settings_form($form);
+}
+
+/**
+ * Validation handler for translation update settings.
+ */
+function l10n_update_admin_settings_form_validate($form, &$form_state) {
+ // Check for existing translations directory and create one if required.
+ $directory = $form_state['values']['l10n_update_download_store'];
+ if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ form_set_error('l10n_update_download_store', t('The directory %directory does not exist or is not writable.', array('%directory' => $directory)));
+ watchdog('file system', 'The directory %directory does not exist or is not writable.', array('%directory' => $directory), WATCHDOG_ERROR);
+ }
+}
+
+/**
+ * Submit handler for translation update settings.
+ */
+function l10n_update_admin_settings_form_submit($form, $form_state) {
+ // Invalidate the cached translation status when the configuration setting of
+ // 'l10n_update_check_mode' or 'check_disabled' change.
+ if ($form['l10n_update_check_mode']['#default_value'] != $form_state['values']['l10n_update_check_mode'] ||
+ $form['l10n_update_check_disabled']['#default_value'] != $form_state['values']['l10n_update_check_disabled']) {
+ l10n_update_clear_status();
+ }
+}
+
+/**
+ * Get array of import options.
+ *
+ * The import options of the Locale module are used but the UI text is altered
+ * to suit the Localization update cases.
+ *
+ * @return
+ * Keyed array of import options.
+ */
+function _l10n_update_admin_import_options() {
+ return array(
+ LOCALE_IMPORT_OVERWRITE => t('Translation updates replace existing ones, new ones are added'),
+ L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED => t('Edited translations are kept, only previously imported ones are overwritten and new translations are added'),
+ LOCALE_IMPORT_KEEP => t('All existing translations are kept, only new translations are added.'),
+ );
+}
+
+/**
+ * Provides debug info for projects in case translation files are not found.
+ *
+ * Translations files are being fetched either from Drupal translation server
+ * and local files or only from the local filesystem depending on the
+ * "Translation source" setting at admin/config/regional/language/update.
+ * This method will produce debug information including the respective path(s)
+ * based on this setting.
+ *
+ * Translations for development versions are never fetched, so the debug info
+ * for that is a fixed message.
+ *
+ * @param array $source
+ * An array which is the project information of the source.
+ *
+ * @return string
+ * The string which contains debug information.
+ */
+function _l10n_update_status_debug_info($source) {
+ $remote_path = isset($source->files['remote']->uri) ? $source->files['remote']->uri : '';
+ $local_path = isset($source->files['local']->uri) ? $source->files['local']->uri : '';
+
+ if (strpos($source->version, 'dev') !== FALSE) {
+ return t('No translation files are provided for development releases.');
+ }
+ if (l10n_update_use_remote_source() && $remote_path && $local_path) {
+ return t('File not found at %remote_path nor at %local_path', array(
+ '%remote_path' => $remote_path,
+ '%local_path' => $local_path,
+ ));
+ }
+ elseif ($local_path) {
+ return t('File not found at %local_path', array('%local_path' => $local_path));
+ }
+ return t('Translation file location could not be determined.');
+}
+
+/**
+ * Form element callback: After build changes to the language update table.
+ *
+ * Adds labels to the languages and removes checkboxes from languages from which
+ * translation files could not be found.
+ */
+function l10n_update_language_table($form_element) {
+ // Remove checkboxes of languages without updates.
+ if ($form_element['#not_found']) {
+ foreach ($form_element['#not_found'] as $langcode) {
+ $form_element[$langcode] = array();
+ }
+ }
+ return $form_element;
+}
+
+/**
+ * Returns HTML for translation edit form.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - form: The form that contains the language information.
+ *
+ * @see l10n_update_edit_form()
+ * @ingroup themeable
+ */
+function theme_l10n_update_edit_form_strings($variables) {
+ $output = '';
+ $form = $variables['form'];
+ $header = array(
+ t('Source string'),
+ t('Translation for @language', array('@language' => $form['#language'])),
+ );
+ $rows = array();
+ foreach (element_children($form) as $lid) {
+ $string = $form[$lid];
+ if ($string['plural']['#value']) {
+ $source = drupal_render($string['original_singular']) . '<br />' . drupal_render($string['original_plural']);
+ }
+ else {
+ $source = drupal_render($string['original']);
+ }
+ $source .= empty($string['context']) ? '' : '<br /><small>' . t('In Context') . ':&nbsp;' . $string['context']['#value'] . '</small>';
+ $rows[] = array(
+ array('data' => $source),
+ array('data' => $string['translations']),
+ );
+ }
+ $table = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $rows,
+ '#empty' => t('No strings available.'),
+ '#attributes' => array('class' => array('locale-translate-edit-table')),
+ );
+ $output .= drupal_render($table);
+ $pager = array('#theme' => 'pager');
+ $output .= drupal_render($pager);
+ return $output;
+}
+
+/**
+ * Prepares variables for translation status information templates.
+ *
+ * Translation status information is displayed per language.
+ *
+ * Default template: l10n_update-translation-update-info.tpl.php.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - updates: The projects which have updates.
+ * - not_found: The projects which updates are not found.
+ *
+ * @see l10n_update_status_form()
+ */
+function template_preprocess_l10n_update_update_info(&$variables) {
+ $details = array();
+ $modules = array();
+
+ // Default values
+ $variables['modules'] = array();
+ $variables['module_list'] = '';
+ $details['available_updates_list'] = array();
+
+ // Build output for available updates.
+ if (isset($variables['updates'])) {
+ $releases = array();
+ if ($variables['updates']) {
+ foreach ($variables['updates'] as $update) {
+ $modules[] = $update['name'];
+ $releases[] = t('@module (@date)', array('@module' => $update['name'], '@date' => format_date($update['timestamp'], 'html_date')));
+ }
+ $variables['modules'] = $modules;
+ $variables['module_list'] = t('Updates for: @modules', array('@modules' => implode(', ', $modules)));
+ }
+ $details['available_updates_list'] = array(
+ '#theme' => 'item_list',
+ '#items' => $releases,
+ );
+ }
+
+ // Build output for updates not found.
+ if (isset($variables['not_found'])) {
+ $releases = array();
+ $variables['missing_updates_status'] = format_plural(count($variables['not_found']), 'Missing translations for one project', 'Missing translations for @count projects');
+ if ($variables['not_found']) {
+ foreach ($variables['not_found'] as $update) {
+ $version = $update['version'] ? $update['version'] : t('no version');
+ $releases[] = t('@module (@version).', array('@module' => $update['name'], '@version' => $version)) . ' ' . $update['info'];
+ }
+ }
+ $details['missing_updates_list'] = array(
+ '#theme' => 'item_list',
+ '#items' => $releases,
+ );
+ // Prefix the missing updates list if there is an available updates lists
+ // before it.
+ if (!empty($details['missing_updates_list']['#items'])) {
+ $details['missing_updates_list']['#prefix'] = t('Missing translations for:');
+ }
+ }
+ $variables['details'] = $details;
+}
+
+/**
+ * Prepares variables for most recent translation update templates.
+ *
+ * Displays the last time we checked for locale update data. In addition to
+ * properly formatting the given timestamp, this function also provides a "Check
+ * manually" link that refreshes the available update and redirects back to the
+ * same page.
+ *
+ * Default template: l10n_update-translation-last-check.tpl.php.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - last: The timestamp when the site last checked for available updates.
+ *
+ * @see l10n_update_status_form()
+ */
+function template_preprocess_l10n_update_last_check(&$variables) {
+ $last = $variables['last'];
+ $variables['last_checked'] = $last ? t('Last checked: !time ago', array('!time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never');
+ $variables['link'] = l(t('Check manually'), 'admin/config/regional/translate/check');
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.api.php b/sites/all/modules/l10n_update/l10n_update.api.php
new file mode 100644
index 000000000..2d98621bc
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.api.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * API documentation for Localize updater module.
+ */
+
+/**
+ * Alter the list of project to be updated by l10n update.
+ *
+ * l10n_update uses the same list of projects as update module. Using this hook
+ * the list can be altered.
+ *
+ * @param array $projects
+ * Array of projects.
+ */
+function hook_l10n_update_projects_alter(&$projects) {
+ // The $projects array contains the project data produced by
+ // update_get_projects(). A number of the array elements are described in
+ // the documentation of hook_update_projects_alter().
+
+ // In the .info file of a project a localization server can be specified.
+ // Using this hook the localization server specification can be altered or
+ // added. The 'l10n path' element is optional but can be specified to override
+ // the translation download path specified in the 10n_server.xml file.
+ $projects['existing_example_project'] = array(
+ 'info' => array(
+ 'l10n path' => 'http://example.com/files/translations/%core/%project/%project-%release.%language.po',
+ ),
+ );
+
+ // With this hook it is also possible to add a new project wich does not
+ // exist as a real module or theme project but is treated by the localization
+ // update module as one. The below data is the minumum to be specified.
+ // As in the previous example the 'l10n path' element is optional.
+ $projects['new_example_project'] = array(
+ 'project_type' => 'module',
+ 'name' => 'new_example_project',
+ 'info' => array(
+ 'name' => 'New example project',
+ 'version' => '7.x-1.5',
+ 'core' => '7.x',
+ 'l10n path' => 'http://example.com/files/translations/%core/%project/%project-%release.%language.po',
+ ),
+ );
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.batch.inc b/sites/all/modules/l10n_update/l10n_update.batch.inc
new file mode 100644
index 000000000..ba53454c6
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.batch.inc
@@ -0,0 +1,246 @@
+<?php
+
+/**
+ * @file
+ * Batch process to check the availability of remote or local po files.
+ */
+
+/**
+ * Load the common translation API.
+ */
+// @todo Combine functions differently in files to avoid unnecessary includes.
+// Follow-up issue http://drupal.org/node/1834298
+require_once __DIR__ . '/l10n_update.translation.inc';
+
+/**
+ * Batch operation callback: Check status of a remote and local po file.
+ *
+ * Checks the presence and creation time po translation files in located at
+ * remote server location and local file system.
+ *
+ * @param string $project
+ * Machine name of the project for which to check the translation status.
+ * @param string $langcode
+ * Language code of the language for which to check the translation.
+ * @param array $options
+ * Optional, an array with options that can have the following elements:
+ * - 'finish_feedback': Whether or not to give feedback to the user when the
+ * batch is finished. Optional, defaults to TRUE.
+ * - 'use_remote': Whether or not to check the remote translation file.
+ * Optional, defaults to TRUE.
+ * @param array $context
+ * The batch context.
+*/
+function l10n_update_batch_status_check($project, $langcode, $options = array(), &$context) {
+ $failure = $checked = FALSE;
+ $options += array(
+ 'finish_feedback' => TRUE,
+ 'use_remote' => TRUE,
+ );
+ $source = l10n_update_get_status(array($project), array($langcode));
+ $source = $source[$project][$langcode];
+
+ // Check the status of local translation files.
+ if (isset($source->files[L10N_UPDATE_LOCAL])) {
+ if ($file = l10n_update_source_check_file($source)) {
+ l10n_update_status_save($source->name, $source->langcode, L10N_UPDATE_LOCAL, $file);
+ }
+ $checked = TRUE;
+ }
+
+ // Check the status of remote translation files.
+ if ($options['use_remote'] && isset($source->files[L10N_UPDATE_REMOTE])) {
+ $remote_file = $source->files[L10N_UPDATE_REMOTE];
+ module_load_include('http.inc', 'l10n_update');
+ if ($result = l10n_update_http_check($remote_file->uri)) {
+ // Update the file object with the result data. In case of a redirect we
+ // store the resulting uri.
+ if (!empty($result->updated)) {
+ $remote_file->uri = isset($result->redirect_url) ? $result->redirect_url : $remote_file->uri;
+ $remote_file->timestamp = $result->updated;
+ l10n_update_status_save($source->name, $source->langcode, L10N_UPDATE_REMOTE, $remote_file);
+ }
+ // @todo What to do with when the file is not found (404)? To prevent
+ // re-checking within the TTL (1day, 1week) we can set a last_checked
+ // timestamp or cache the result.
+ $checked = TRUE;
+ }
+ else {
+ $failure = TRUE;
+ }
+ }
+
+ // Provide user feedback and record success or failure for reporting at the
+ // end of the batch.
+ if ($options['finish_feedback'] && $checked) {
+ $context['results']['files'][] = $source->name;
+ }
+ if ($failure && !$checked) {
+ $context['results']['failed_files'][] = $source->name;
+ }
+ $context['message'] = t('Checked translation for %project.', array('%project' => $source->project));
+}
+
+/**
+ * Batch finished callback: Set result message.
+ *
+ * @param boolean $success
+ * TRUE if batch successfully completed.
+ * @param array $results
+ * Batch results.
+ */
+function l10n_update_batch_status_finished($success, $results) {
+ if ($success) {
+ if (isset($results['failed_files'])) {
+ if (module_exists('dblog')) {
+ $message = format_plural(count($results['failed_files']), 'One translation file could not be checked. <a href="@url">See the log</a> for details.', '@count translation files could not be checked. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog')));
+ }
+ else {
+ $message = format_plural(count($results['failed_files']), 'One translation files could not be checked. See the log for details.', '@count translation files could not be checked. See the log for details.');
+ }
+ drupal_set_message($message, 'error');
+ }
+ if (isset($results['files'])) {
+ drupal_set_message(format_plural(
+ count($results['files']),
+ 'Checked available interface translation updates for one project.',
+ 'Checked available interface translation updates for @count projects.'
+ ));
+ }
+ if (!isset($results['failed_files']) && !isset($results['files'])) {
+ drupal_set_message(t('Nothing to check.'));
+ }
+ variable_set('l10n_update_last_check', REQUEST_TIME);
+ }
+ else {
+ drupal_set_message(t('An error occurred trying to check available interface translation updates.'), 'error');
+ }
+}
+
+/**
+ * Batch operation: Download a remote translation file.
+ *
+ * Downloads a remote gettext file into the translations directory. When
+ * successfully the translation status is updated.
+ *
+ * @param string $project
+ * Name of the translatable project.
+ * @param string $langcode
+ * Language code.
+ * @param array $context
+ * The batch context.
+ *
+ * @see l10n_update_batch_fetch_import()
+ */
+function l10n_update_batch_fetch_download($project, $langcode, &$context) {
+ $sources = l10n_update_get_status(array($project), array($langcode));
+ if (isset($sources[$project][$langcode])) {
+ $source = $sources[$project][$langcode];
+ if (isset($source->type) && $source->type == L10N_UPDATE_REMOTE) {
+ if ($file = l10n_update_download_source($source->files[L10N_UPDATE_REMOTE], 'translations://')) {
+ $context['message'] = t('Downloaded translation for %project.', array('%project' => $source->project));
+ l10n_update_status_save($source->name, $source->langcode, L10N_UPDATE_LOCAL, $file);
+ }
+ else {
+ $context['results']['failed_files'][] = $source->files[L10N_UPDATE_REMOTE];
+ }
+ }
+ }
+}
+
+/**
+ * Batch process: Import translation file.
+ *
+ * Imports a gettext file from the translation directory. When successfully the
+ * translation status is updated.
+ *
+ * @param string $project
+ * Name of the translatable project.
+ * @param string $langcode
+ * Language code.
+ * @param array $options
+ * Array of import options.
+ * @param array $context
+ * The batch context.
+ *
+ * @see l10n_update_batch_import_files()
+ * @see l10n_update_batch_fetch_download()
+ */
+function l10n_update_batch_fetch_import($project, $langcode, $options, &$context) {
+ $sources = l10n_update_get_status(array($project), array($langcode));
+ if (isset($sources[$project][$langcode])) {
+ $source = $sources[$project][$langcode];
+ if (isset($source->type)) {
+ if ($source->type == L10N_UPDATE_REMOTE || $source->type == L10N_UPDATE_LOCAL) {
+ $file = $source->files[L10N_UPDATE_LOCAL];
+ module_load_include('bulk.inc', 'l10n_update');
+ $options += array(
+ 'message' => t('Importing translation for %project.', array('%project' => $source->project)),
+ );
+ // Import the translation file. For large files the batch operations is
+ // progressive and will be called repeatedly until finished.
+ l10n_update_batch_import($file, $options, $context);
+
+ // The import is finished.
+ if (isset($context['finished']) && $context['finished'] == 1) {
+ // The import is successful.
+ if (isset($context['results']['files'][$file->uri])) {
+ $context['message'] = t('Imported translation for %project.', array('%project' => $source->project));
+
+ // Save the data of imported source into the {l10n_update_file} table and
+ // update the current translation status.
+ l10n_update_status_save($project, $langcode, L10N_UPDATE_CURRENT, $source->files[L10N_UPDATE_LOCAL]);
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Batch finished callback: Set result message.
+ *
+ * @param boolean $success
+ * TRUE if batch successfully completed.
+ * @param array
+ * Batch results.
+ */
+function l10n_update_batch_fetch_finished($success, $results) {
+ module_load_include('bulk.inc', 'l10n_update');
+ if ($success) {
+ variable_set('l10n_update_last_check', REQUEST_TIME);
+ }
+ l10n_update_batch_finished($success, $results);
+}
+
+
+
+/**
+ * Downloads a translation file from a remote server.
+ *
+ * @param object $source_file
+ * Source file object with at least:
+ * - "uri": uri to download the file from.
+ * - "project": Project name.
+ * - "langcode": Translation language.
+ * - "version": Project version.
+ * - "filename": File name.
+ * @param string $directory
+ * Directory where the downloaded file will be saved. Defaults to the
+ * temporary file path.
+ *
+ * @return object
+ * File object if download was successful. FALSE on failure.
+ */
+function l10n_update_download_source($source_file, $directory = 'temporary://') {
+ if ($uri = system_retrieve_file($source_file->uri, $directory)) {
+ $file = clone($source_file);
+ $file->type = L10N_UPDATE_LOCAL;
+ $file->uri = $uri;
+ $file->directory = $directory;
+ $file->timestamp = filemtime($uri);
+ return $file;
+ }
+ watchdog('l10n_update', 'Unable to download translation file @uri.', array('@uri' => $source_file->uri), WATCHDOG_ERROR);
+ return FALSE;
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.bulk.inc b/sites/all/modules/l10n_update/l10n_update.bulk.inc
new file mode 100644
index 000000000..eee6f19ae
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.bulk.inc
@@ -0,0 +1,726 @@
+<?php
+
+/**
+ * @file
+ * Mass import-export and batch import functionality for Gettext .po files.
+ */
+
+/**
+ * Form constructor for the translation import screen.
+ *
+ * @see l10n_update_import_form_submit()
+ * @ingroup forms
+ */
+function l10n_update_import_form($form, &$form_state) {
+ drupal_static_reset('language_list');
+ $languages = language_list();
+
+ // Initialize a language list to the ones available, including English if we
+ // are to translate Drupal to English as well.
+ $existing_languages = array();
+ foreach ($languages as $langcode => $language) {
+ if ($langcode != 'en' || l10n_update_english()) {
+ $existing_languages[$langcode] = $language->name;
+ }
+ }
+
+ // If we have no languages available, present the list of predefined languages
+ // only. If we do have already added languages, set up two option groups with
+ // the list of existing and then predefined languages.
+ form_load_include($form_state, 'inc', 'language', 'language.admin');
+ if (empty($existing_languages)) {
+ $language_options = language_admin_predefined_list();
+ $default = key($language_options);
+ }
+ else {
+ $default = key($existing_languages);
+ $language_options = array(
+ t('Existing languages') => $existing_languages,
+ t('Languages not yet added') => language_admin_predefined_list()
+ );
+ }
+
+ $validators = array(
+ 'file_validate_extensions' => array('po'),
+ 'file_validate_size' => array(file_upload_max_size()),
+ );
+ $form['file'] = array(
+ '#type' => 'file',
+ '#title' => t('Translation file'),
+ '#description' => theme('file_upload_help', array('description' => t('A Gettext Portable Object file.'), 'upload_validators' => $validators)),
+ '#size' => 50,
+ '#upload_validators' => $validators,
+ '#attributes' => array('class' => array('file-import-input')),
+ '#attached' => array(
+ 'js' => array(
+ drupal_get_path('module', 'locale') . '/locale.bulk.js' => array(),
+ ),
+ ),
+ );
+ $form['langcode'] = array(
+ '#type' => 'select',
+ '#title' => t('Language'),
+ '#options' => $language_options,
+ '#default_value' => $default,
+ '#attributes' => array('class' => array('langcode-input')),
+ );
+
+ $form['customized'] = array(
+ '#title' => t('Treat imported strings as custom translations'),
+ '#type' => 'checkbox',
+ );
+ $form['overwrite_options'] = array(
+ '#type' => 'container',
+ '#tree' => TRUE,
+ );
+ $form['overwrite_options']['not_customized'] = array(
+ '#title' => t('Overwrite non-customized translations'),
+ '#type' => 'checkbox',
+ '#states' => array(
+ 'checked' => array(
+ ':input[name="customized"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ $form['overwrite_options']['customized'] = array(
+ '#title' => t('Overwrite existing customized translations'),
+ '#type' => 'checkbox',
+ );
+
+ $form['actions'] = array(
+ '#type' => 'actions'
+ );
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import')
+ );
+ return $form;
+}
+
+/**
+ * Form submission handler for l10n_update_import_form().
+ */
+function l10n_update_import_form_submit($form, &$form_state) {
+ // Ensure we have the file uploaded.
+ if ($file = file_save_upload('file', $form_state, $form['file']['#upload_validators'], 'translations://', 0)) {
+
+ // Add language, if not yet supported.
+ $language = language_load($form_state['values']['langcode']);
+ if (empty($language)) {
+ $language = new Language(array(
+ 'id' => $form_state['values']['langcode']
+ ));
+ $language = language_save($language);
+ drupal_set_message(t('The language %language has been created.', array('%language' => t($language->name))));
+ }
+ $options = array(
+ 'langcode' => $form_state['values']['langcode'],
+ 'overwrite_options' => $form_state['values']['overwrite_options'],
+ 'customized' => $form_state['values']['customized'] ? L10N_UPDATE_CUSTOMIZED : L10N_UPDATE_NOT_CUSTOMIZED,
+ );
+ $file = l10n_update_file_attach_properties($file, $options);
+ $batch = l10n_update_batch_build(array($file->uri => $file), $options);
+ batch_set($batch);
+ }
+ else {
+ form_set_error('file', $form_state, t('File to import not found.'));
+ $form_state['rebuild'] = TRUE;
+ return;
+ }
+
+ $form_state['redirect_route']['route_name'] = 'locale.translate_page';
+ return;
+}
+
+/**
+ * Form constructor for the Gettext translation files export form.
+ *
+ * @see l10n_update_export_form_submit()
+ * @ingroup forms
+ */
+function l10n_update_export_form($form, &$form_state) {
+ global $language;
+ $languages = language_list();
+ $language_options = array();
+ foreach ($languages as $langcode => $language) {
+ if ($langcode != 'en' || l10n_update_english()) {
+ $language_options[$langcode] = $language->name;
+ }
+ }
+ $language_default = language_default();
+
+ if (empty($language_options)) {
+ $form['langcode'] = array(
+ '#type' => 'value',
+ '#value' => $language->language,
+ );
+ $form['langcode_text'] = array(
+ '#type' => 'item',
+ '#title' => t('Language'),
+ '#markup' => t('No language available. The export will only contain source strings.'),
+ );
+ }
+ else {
+ $form['langcode'] = array(
+ '#type' => 'select',
+ '#title' => t('Language'),
+ '#options' => $language_options,
+ '#default_value' => $language_default->id,
+ '#empty_option' => t('Source text only, no translations'),
+ '#empty_value' => $language->language,
+ );
+ $form['content_options'] = array(
+ '#type' => 'details',
+ '#title' => t('Export options'),
+ '#collapsed' => TRUE,
+ '#tree' => TRUE,
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name="langcode"]' => array('value' => $language->language),
+ ),
+ ),
+ );
+ $form['content_options']['not_customized'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include non-customized translations'),
+ '#default_value' => TRUE,
+ );
+ $form['content_options']['customized'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include customized translations'),
+ '#default_value' => TRUE,
+ );
+ $form['content_options']['not_translated'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include untranslated text'),
+ '#default_value' => TRUE,
+ );
+ }
+
+ $form['actions'] = array(
+ '#type' => 'actions'
+ );
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Export')
+ );
+ return $form;
+}
+
+/**
+ * Form submission handler for l10n_update_export_form().
+ */
+function l10n_update_export_form_submit($form, &$form_state) {
+ global $language;
+
+ // If template is required, language code is not given.
+ if ($form_state['values']['langcode'] != $language->language) {
+ $languages = language_list();
+ $language = isset($languages[$form_state['values']['langcode']]) ? $languages[$form_state['values']['langcode']] : NULL;
+ }
+ else {
+ $language = NULL;
+ }
+ $content_options = isset($form_state['values']['content_options']) ? $form_state['values']['content_options'] : array();
+ $reader = new PoDatabaseReader();
+ $languageName = '';
+ if ($language != NULL) {
+ $reader->setLangcode($language->id);
+ $reader->setOptions($content_options);
+ $languages = language_list();
+ $languageName = isset($languages[$language->id]) ? $languages[$language->id]->name : '';
+ $filename = $language->id .'.po';
+ }
+ else {
+ // Template required.
+ $filename = 'drupal.pot';
+ }
+
+ $item = $reader->readItem();
+ if (!empty($item)) {
+ $uri = tempnam('temporary://', 'po_');
+ $header = $reader->getHeader();
+ $header->setProjectName(variable_get('site_name'));
+ $header->setLanguageName($languageName);
+
+ $writer = new PoStreamWriter;
+ $writer->setUri($uri);
+ $writer->setHeader($header);
+
+ $writer->open();
+ $writer->writeItem($item);
+ $writer->writeItems($reader);
+ $writer->close();
+ }
+ else {
+ drupal_set_message('Nothing to export.');
+ }
+}
+
+/**
+ * Prepare a batch to import all translations.
+ *
+ * @param array $options
+ * An array with options that can have the following elements:
+ * - 'langcode': The language code. Optional, defaults to NULL, which means
+ * that the language will be detected from the name of the files.
+ * - 'overwrite_options': Overwrite options array as defined in
+ * PoDatabaseWriter. Optional, defaults to an empty array.
+ * - 'customized': Flag indicating whether the strings imported from $file
+ * are customized translations or come from a community source. Use
+ * L10N_UPDATE_CUSTOMIZED or L10N_UPDATE_NOT_CUSTOMIZED. Optional, defaults to
+ * L10N_UPDATE_NOT_CUSTOMIZED.
+ * - 'finish_feedback': Whether or not to give feedback to the user when the
+ * batch is finished. Optional, defaults to TRUE.
+ *
+ * @param $force
+ * (optional) Import all available files, even if they were imported before.
+ *
+ * @todo
+ * Integrate with update status to identify projects needed and integrate
+ * l10n_update functionality to feed in translation files alike.
+ * See http://drupal.org/node/1191488.
+ */
+function l10n_update_batch_import_files($options, $force = FALSE) {
+ $options += array(
+ 'overwrite_options' => array(),
+ 'customized' => L10N_UPDATE_NOT_CUSTOMIZED,
+ 'finish_feedback' => TRUE,
+ );
+
+ if (!empty($options['langcode'])) {
+ $langcodes = array($options['langcode']);
+ }
+ else {
+ // If langcode was not provided, make sure to only import files for the
+ // languages we have enabled.
+ $langcodes = array_keys(language_list());
+ }
+
+ $files = l10n_update_get_interface_translation_files(array(), $langcodes);
+
+ if (!$force) {
+ $result = db_select('l10n_update_file', 'lf')
+ ->fields('lf', array('langcode', 'uri', 'timestamp'))
+ ->condition('language', $langcodes)
+ ->execute()
+ ->fetchAllAssoc('uri');
+ foreach ($result as $uri => $info) {
+ if (isset($files[$uri]) && filemtime($uri) <= $info->timestamp) {
+ // The file is already imported and not changed since the last import.
+ // Remove it from file list and don't import it again.
+ unset($files[$uri]);
+ }
+ }
+ }
+ return l10n_update_batch_build($files, $options);
+}
+
+/**
+ * Get interface translation files present in the translations directory.
+ *
+ * @param array $projects
+ * Project names from which to get the translation files and history.
+ * Defaults to all projects.
+ * @param array $langcodes
+ * Language codes from which to get the translation files and history.
+ * Defaults to all languagues
+ *
+ * @return array
+ * An array of interface translation files keyed by their URI.
+ */
+function l10n_update_get_interface_translation_files($projects = array(), $langcodes = array()) {
+ module_load_include('compare.inc', 'l10n_update');
+ $files = array();
+ $projects = $projects ? $projects : array_keys(l10n_update_get_projects());
+ $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
+
+ // Scan the translations directory for files matching a name pattern
+ // containing a project name and language code: {project}.{langcode}.po or
+ // {project}-{version}.{langcode}.po.
+ // Only files of known projects and languages will be returned.
+ $directory = variable_get('l10n_update_download_store', L10N_UPDATE_DEFAULT_TRANSLATION_PATH);
+ $result = file_scan_directory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', array('recurse' => FALSE));
+
+ foreach ($result as $file) {
+ // Update the file object with project name and version from the file name.
+ $file = l10n_update_file_attach_properties($file);
+ if (in_array($file->project, $projects)) {
+ if (in_array($file->langcode, $langcodes)) {
+ $files[$file->uri] = $file;
+ }
+ }
+ }
+
+ return $files;
+}
+
+/**
+ * Build a locale batch from an array of files.
+ *
+ * @param $files
+ * Array of file objects to import.
+ *
+ * @param array $options
+ * An array with options that can have the following elements:
+ * - 'langcode': The language code. Optional, defaults to NULL, which means
+ * that the language will be detected from the name of the files.
+ * - 'overwrite_options': Overwrite options array as defined in
+ * PoDatabaseWriter. Optional, defaults to an empty array.
+ * - 'customized': Flag indicating whether the strings imported from $file
+ * are customized translations or come from a community source. Use
+ * L10N_UPDATE_CUSTOMIZED or L10N_UPDATE_NOT_CUSTOMIZED. Optional, defaults to
+ * L10N_UPDATE_NOT_CUSTOMIZED.
+ * - 'finish_feedback': Whether or not to give feedback to the user when the
+ * batch is finished. Optional, defaults to TRUE.
+ *
+ * @return
+ * A batch structure or FALSE if $files was empty.
+ */
+function l10n_update_batch_build($files, $options) {
+ $options += array(
+ 'overwrite_options' => array(),
+ 'customized' => L10N_UPDATE_NOT_CUSTOMIZED,
+ 'finish_feedback' => TRUE,
+ );
+ if (count($files)) {
+ $operations = array();
+ foreach ($files as $file) {
+ // We call l10n_update_batch_import for every batch operation.
+ $operations[] = array('l10n_update_batch_import', array($file, $options));
+ }
+ // Save the translation status of all files.
+ $operations[] = array('l10n_update_batch_import_save', array());
+
+ // Add a final step to refresh JavaScript and configuration strings.
+ $operations[] = array('l10n_update_batch_refresh', array());
+
+ $batch = array(
+ 'operations' => $operations,
+ 'title' => t('Importing interface translations'),
+ 'progress_message' => '',
+ 'error_message' => t('Error importing interface translations'),
+ 'file' => drupal_get_path('module', 'locale') . '/locale.bulk.inc',
+ );
+ if ($options['finish_feedback']) {
+ $batch['finished'] = 'l10n_update_batch_finished';
+ }
+ return $batch;
+ }
+ return FALSE;
+}
+
+/**
+ * Perform interface translation import as a batch step.
+ *
+ * @param object $file
+ * A file object of the gettext file to be imported. The file object must
+ * contain a language parameter. This is used as the language of the import.
+ *
+ * @param array $options
+ * An array with options that can have the following elements:
+ * - 'langcode': The language code.
+ * - 'overwrite_options': Overwrite options array as defined in
+ * PoDatabaseWriter. Optional, defaults to an empty array.
+ * - 'customized': Flag indicating whether the strings imported from $file
+ * are customized translations or come from a community source. Use
+ * L10N_UPDATE_CUSTOMIZED or L10N_UPDATE_NOT_CUSTOMIZED. Optional, defaults to
+ * L10N_UPDATE_NOT_CUSTOMIZED.
+ * - 'message': Alternative message to display during import. Note, this must
+ * be sanitized text.
+ *
+ * @param $context
+ * Contains a list of files imported.
+ */
+function l10n_update_batch_import($file, $options, &$context) {
+ // Merge the default values in the $options array.
+ $options += array(
+ 'overwrite_options' => array(),
+ 'customized' => L10N_UPDATE_NOT_CUSTOMIZED,
+ );
+
+ if (isset($file->langcode)) {
+
+ try {
+ if (empty($context['sandbox'])) {
+ $context['sandbox']['parse_state'] = array(
+ 'filesize' => filesize(drupal_realpath($file->uri)),
+ 'chunk_size' => 200,
+ 'seek' => 0,
+ );
+ }
+ // Update the seek and the number of items in the $options array().
+ $options['seek'] = $context['sandbox']['parse_state']['seek'];
+ $options['items'] = $context['sandbox']['parse_state']['chunk_size'];
+ $report = Gettext::fileToDatabase($file, $options);
+ // If not yet finished with reading, mark progress based on size and
+ // position.
+ if ($report['seek'] < filesize($file->uri)) {
+
+ $context['sandbox']['parse_state']['seek'] = $report['seek'];
+ // Maximize the progress bar at 95% before completion, the batch API
+ // could trigger the end of the operation before file reading is done,
+ // because of floating point inaccuracies. See
+ // http://drupal.org/node/1089472
+ $context['finished'] = min(0.95, $report['seek'] / filesize($file->uri));
+ if (isset($options['message'])) {
+ $context['message'] = t('!message (@percent%).', array('!message' => $options['message'], '@percent' => (int) ($context['finished'] * 100)));
+ }
+ else {
+ $context['message'] = t('Importing translation file: %filename (@percent%).', array('%filename' => $file->filename, '@percent' => (int) ($context['finished'] * 100)));
+ }
+ }
+ else {
+ // We are finished here.
+ $context['finished'] = 1;
+
+ // Store the file data for processing by the next batch operation.
+ $file->timestamp = filemtime($file->uri);
+ $context['results']['files'][$file->uri] = $file;
+ $context['results']['languages'][$file->uri] = $file->langcode;
+ }
+
+ // Add the reported values to the statistics for this file.
+ // Each import iteration reports statistics in an array. The results of
+ // each iteration are added and merged here and stored per file.
+ if (!isset($context['results']['stats']) || !isset($context['results']['stats'][$file->uri])) {
+ $context['results']['stats'][$file->uri] = array();
+ }
+ foreach ($report as $key => $value) {
+ if (is_numeric($report[$key])) {
+ if (!isset($context['results']['stats'][$file->uri][$key])) {
+ $context['results']['stats'][$file->uri][$key] = 0;
+ }
+ $context['results']['stats'][$file->uri][$key] += $report[$key];
+ }
+ elseif (is_array($value)) {
+ $context['results']['stats'][$file->uri] += array($key => array());
+ $context['results']['stats'][$file->uri][$key] = array_merge($context['results']['stats'][$file->uri][$key], $value);
+ }
+ }
+ }
+ catch (Exception $exception) {
+ // Import failed. Store the data of the failing file.
+ $context['results']['failed_files'][] = $file;
+ watchdog('l10n_update', 'Unable to import translations file: @file (@message)', array('@file' => $file->uri, '@message' => $exception->getMessage()));
+ }
+ }
+}
+
+/**
+ * Batch callback: Save data of imported files.
+ *
+ * @param $context
+ * Contains a list of imported files.
+ */
+function l10n_update_batch_import_save($context) {
+ if (isset($context['results']['files'])) {
+ foreach ($context['results']['files'] as $file) {
+ // Update the file history if both project and version are known. This
+ // table is used by the automated translation update function which tracks
+ // translation status of module and themes in the system. Other
+ // translation files are not tracked and are therefore not stored in this
+ // table.
+ if ($file->project && $file->version) {
+ $file->last_checked = REQUEST_TIME;
+ l10n_update_update_file_history($file);
+ }
+ }
+ $context['message'] = t('Translations imported.');
+ }
+}
+
+/**
+ * Refreshs translations after importing strings.
+ *
+ * @param array $context
+ * Contains a list of strings updated and information about the progress.
+ */
+function l10n_update_batch_refresh(array &$context) {
+ if (!isset($context['sandbox']['refresh'])) {
+ $strings = $langcodes = array();
+ if (isset($context['results']['stats'])) {
+ // Get list of unique string identifiers and language codes updated.
+ $langcodes = array_unique(array_values($context['results']['languages']));
+ foreach ($context['results']['stats'] as $report) {
+ $strings = array_merge($strings, $report['strings']);
+ }
+ }
+ if ($strings) {
+ // Initialize multi-step string refresh.
+ $context['message'] = t('Updating translations for JavaScript and configuration strings.');
+ $context['sandbox']['refresh']['strings'] = array_unique($strings);
+ $context['sandbox']['refresh']['languages'] = $langcodes;
+ $context['sandbox']['refresh']['names'] = array();
+ $context['results']['stats']['config'] = 0;
+ $context['sandbox']['refresh']['count'] = count($strings);
+
+ // We will update strings on later steps.
+ $context['finished'] = 1 - 1 / $context['sandbox']['refresh']['count'];
+ }
+ else {
+ $context['finished'] = 1;
+ }
+ }
+ elseif (!empty($context['sandbox']['refresh']['strings'])) {
+ // Not perfect but will give some indication of progress.
+ $context['finished'] = 1 - count($context['sandbox']['refresh']['strings']) / $context['sandbox']['refresh']['count'];
+ // Pending strings, refresh 100 at a time, get next pack.
+ $next = array_slice($context['sandbox']['refresh']['strings'], 0, 100);
+ array_splice($context['sandbox']['refresh']['strings'], 0, count($next));
+ // Clear cache and force refresh of JavaScript translations.
+ _l10n_update_refresh_translations($context['sandbox']['refresh']['languages'], $next);
+ }
+ else {
+ $context['finished'] = 1;
+ }
+}
+
+/**
+ * Finished callback of system page locale import batch.
+ */
+function l10n_update_batch_finished($success, $results) {
+ if ($success) {
+ $additions = $updates = $deletes = $skips = $config = 0;
+ if (isset($results['failed_files'])) {
+ if (module_exists('dblog')) {
+ $message = format_plural(count($results['failed_files']), 'One translation file could not be imported. <a href="@url">See the log</a> for details.', '@count translation files could not be imported. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog')));
+ }
+ else {
+ $message = format_plural(count($results['failed_files']), 'One translation files could not be imported. See the log for details.', '@count translation files could not be imported. See the log for details.');
+ }
+ drupal_set_message($message, 'error');
+ }
+ if (isset($results['files'])) {
+ $skipped_files = array();
+ // If there are no results and/or no stats (eg. coping with an empty .po
+ // file), simply do nothing.
+ if ($results && isset($results['stats'])) {
+ foreach ($results['stats'] as $filepath => $report) {
+ $additions += $report['additions'];
+ $updates += $report['updates'];
+ $deletes += $report['deletes'];
+ $skips += $report['skips'];
+ if ($report['skips'] > 0) {
+ $skipped_files[] = $filepath;
+ }
+ }
+ }
+ drupal_set_message(format_plural(count($results['files']),
+ 'One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
+ '@count translation files imported. %number translations were added, %update translations were updated and %delete translations were removed.',
+ array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)
+ ));
+ watchdog('l10n_update', 'Translations imported: %number added, %update updated, %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+
+ if ($skips) {
+ if (module_exists('dblog')) {
+ $message = format_plural($skips, 'One translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog')));
+ }
+ else {
+ $message = format_plural($skips, 'One translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.');
+ }
+ drupal_set_message($message, 'warning');
+ watchdog('l10n_update', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING);
+ }
+ }
+ }
+}
+
+/**
+ * Creates a file object and populates the timestamp property.
+ *
+ * @param $filepath
+ * The filepath of a file to import.
+ *
+ * @return
+ * An object representing the file.
+ */
+function l10n_update_file_create($filepath) {
+ $file = new stdClass();
+ $file->filename = drupal_basename($filepath);
+ $file->uri = $filepath;
+ $file->timestamp = filemtime($file->uri);
+ return $file;
+}
+
+/**
+ * Generates file properties from filename and options.
+ *
+ * An attempt is made to determine the translation language, project name and
+ * project version from the file name. Supported file name patterns are:
+ * {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po.
+ * Alternatively the translation language can be set using the $options.
+ *
+ * @param object $file
+ * A file object of the gettext file to be imported.
+ * @param array $options
+ * An array with options:
+ * - 'langcode': The language code. Overrides the file language.
+ *
+ * @return object
+ * Modified file object.
+ */
+function l10n_update_file_attach_properties($file, $options = array()) {
+ // If $file is a file entity, convert it to a stdClass.
+ if ($file instanceof FileInterface) {
+ $file = (object) array(
+ 'filename' => $file->getFilename(),
+ 'uri' => $file->getFileUri(),
+ );
+ }
+
+ // Extract project, version and language code from the file name. Supported:
+ // {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po
+ preg_match('!
+ ( # project OR project and version OR emtpy (group 1)
+ ([a-z_]+) # project name (group 2)
+ \. # .
+ | # OR
+ ([a-z_]+) # project name (group 3)
+ \- # -
+ ([0-9a-z\.\-\+]+) # version (group 4)
+ \. # .
+ | # OR
+ ) # (empty)
+ ([^\./]+) # language code (group 5)
+ \. # .
+ po # po extension
+ $!x', $file->filename, $matches);
+ if (isset($matches[5])) {
+ $file->project = $matches[2] . $matches[3];
+ $file->version = $matches[4];
+ $file->langcode = isset($options['langcode']) ? $options['langcode'] : $matches[5];
+ }
+ return $file;
+}
+
+/**
+ * Deletes interface translation files and translation history records.
+ *
+ * @param array $projects
+ * Project names from which to delete the translation files and history.
+ * Defaults to all projects.
+ * @param array $langcodes
+ * Language codes from which to delete the translation files and history.
+ * Defaults to all languagues
+ *
+ * @return boolean
+ * TRUE if files are removed successfully. FALSE if one or more files could
+ * not be deleted.
+ */
+function l10n_update_delete_translation_files($projects = array(), $langcodes = array()) {
+ $fail = FALSE;
+ l10n_update_file_history_delete($projects, $langcodes);
+
+ // Delete all translation files from the translations directory.
+ if ($files = l10n_update_get_interface_translation_files($projects, $langcodes)) {
+ foreach ($files as $file) {
+ $success = file_unmanaged_delete($file->uri);
+ if (!$success) {
+ $fail = TRUE;
+ }
+ }
+ }
+ return !$fail;
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.compare.inc b/sites/all/modules/l10n_update/l10n_update.compare.inc
new file mode 100644
index 000000000..b5bb94b62
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.compare.inc
@@ -0,0 +1,386 @@
+<?php
+
+/**
+ * @file
+ * The API for comparing project translation status with available translation.
+ */
+
+/**
+ * Load common APIs.
+ */
+// @todo Combine functions differently in files to avoid unnecessary includes.
+// Follow-up issue http://drupal.org/node/1834298
+require_once __DIR__ . '/l10n_update.translation.inc';
+
+/**
+ * Clear the project data table.
+ */
+function l10n_update_flush_projects() {
+ db_truncate('l10n_update_project')->execute();
+ drupal_static_reset('l10n_update_build_projects');
+}
+
+/**
+ * Rebuild project list
+ *
+ * @param $refresh
+ * TRUE: Refresh project list.
+ *
+ * @return array
+ * Array of project objects to be considered for translation update.
+ */
+function l10n_update_build_projects($refresh = FALSE) {
+ $projects = &drupal_static(__FUNCTION__, array(), $refresh);
+ if (empty($projects)) {
+ module_load_include('inc', 'l10n_update');
+
+ // Get the project list based on .info files.
+ $projects = l10n_update_project_list();
+
+ // Mark all previous projects as disabled and store new project data.
+ db_update('l10n_update_project')
+ ->fields(array(
+ 'status' => 0,
+ ))
+ ->execute();
+
+ $default_server = l10n_update_default_translation_server();
+
+ if (module_exists('update')) {
+ $projects_info = update_get_available(TRUE);
+ }
+ foreach ($projects as $name => $data) {
+
+ // Force update fetch of project data in cases where Drupal's performance
+ // optimized approach is missing out on some projects.
+ // @see http://drupal.org/node/1671570#comment-6216090
+ if (module_exists('update') && !isset($projects_info[$name])) {
+ module_load_include('fetch.inc', 'update');
+ _update_process_fetch_task($data);
+ $available = _update_get_cached_available_releases();
+ if (!empty($available[$name])) {
+ $projects_info[$name] = $available[$name];
+ }
+ }
+
+ if (isset($projects_info[$name]['releases']) && $projects_info[$name]['project_status'] != 'not-fetched') {
+ // Find out if a dev version is installed.
+ if (preg_match("/^[0-9]+\.x-([0-9]+)\..*-dev$/", $data['info']['version'], $matches)) {
+ // Find a suitable release to use as alternative translation.
+ foreach ($projects_info[$name]['releases'] as $project_release) {
+ // The first release with the same major release number which is not
+ // a dev release is the one. Releases are sorted the most recent first.
+ if ($project_release['version_major'] == $matches[1] &&
+ (!isset($project_release['version_extra']) || $project_release['version_extra'] != 'dev')) {
+ $release = $project_release;
+ break;
+ }
+ }
+ }
+ if (!empty($release['version'])) {
+ $data['info']['version'] = $release['version'];
+ }
+
+ unset($release);
+ }
+ // Without Update module we do a best effort fallback. A development
+ // release will fall back to the corresponding release version.
+ elseif (!isset($projects_info) && isset($data['info']['version'])) {
+ if (preg_match('/[^x](\+\d+)?-dev$/', $data['info']['version'])) {
+ $data['info']['version'] = preg_replace('/(\+\d+)?-dev$/', '', $data['info']['version']);
+ }
+ }
+
+ $data += array(
+ 'version' => isset($data['info']['version']) ? $data['info']['version'] : '',
+ 'core' => isset($data['info']['core']) ? $data['info']['core'] : DRUPAL_CORE_COMPATIBILITY,
+ 'l10n_path' => isset($data['info']['l10n path']) && $data['info']['l10n path'] ? $data['info']['l10n path'] : $default_server['pattern'],
+ 'status' => 1,
+ );
+ $project = (object) $data;
+ $projects[$name] = $project;
+
+ // Create or update the project record.
+ db_merge('l10n_update_project')
+ ->key(array('name' => $project->name))
+ ->fields(array(
+ 'name' => $project->name,
+ 'project_type' => $project->project_type,
+ 'core' => $project->core,
+ 'version' => $project->version,
+ 'l10n_path' => $project->l10n_path,
+ 'status' => $project->status,
+ ))
+ ->execute();
+
+ // Invalidate the cache of translatable projects.
+ l10n_update_clear_cache_projects();
+ }
+ }
+ return $projects;
+}
+
+/**
+ * Get update module's project list
+ *
+ * @return array
+ */
+function l10n_update_project_list() {
+ $projects = array();
+ $disabled = variable_get('l10n_update_check_disabled', 0);
+ // Unlike update module, this one has no cache
+ _l10n_update_project_info_list($projects, system_rebuild_module_data(), 'module', $disabled);
+ _l10n_update_project_info_list($projects, system_rebuild_theme_data(), 'theme', $disabled);
+ // Allow other modules to alter projects before fetching and comparing.
+ drupal_alter('l10n_update_projects', $projects);
+ return $projects;
+}
+
+/**
+ * Populate an array of project data.
+ *
+ * Based on _update_process_info_list()
+ *
+ * @param $projects
+ * @param $list
+ * @param $project_type
+ * @param $disabled
+ * TRUE to include disabled projects too
+ */
+function _l10n_update_project_info_list(&$projects, $list, $project_type, $disabled = FALSE) {
+ foreach ($list as $file) {
+ if (!$disabled && empty($file->status)) {
+ // Skip disabled modules or themes.
+ continue;
+ }
+
+ // Skip if the .info file is broken.
+ if (empty($file->info)) {
+ continue;
+ }
+
+ // If the .info doesn't define the 'project', try to figure it out.
+ if (!isset($file->info['project'])) {
+ $file->info['project'] = l10n_update_get_project_name($file);
+ }
+
+ // If the .info defines the 'interface translation project', this value will
+ // override the 'project' value.
+ if (isset($file->info['interface translation project'])) {
+ $file->info['project'] = $file->info['interface translation project'];
+ }
+
+ // If we still don't know the 'project', give up.
+ if (empty($file->info['project'])) {
+ continue;
+ }
+
+ // If we don't already know it, grab the change time on the .info file
+ // itself. Note: we need to use the ctime, not the mtime (modification
+ // time) since many (all?) tar implementations will go out of their way to
+ // set the mtime on the files it creates to the timestamps recorded in the
+ // tarball. We want to see the last time the file was changed on disk,
+ // which is left alone by tar and correctly set to the time the .info file
+ // was unpacked.
+ if (!isset($file->info['_info_file_ctime'])) {
+ $info_filename = dirname($file->uri) . '/' . $file->name . '.info';
+ $file->info['_info_file_ctime'] = filectime($info_filename);
+ }
+
+ $project_name = $file->info['project'];
+ if (!isset($projects[$project_name])) {
+ // Only process this if we haven't done this project, since a single
+ // project can have multiple modules or themes.
+ $projects[$project_name] = array(
+ 'name' => $project_name,
+ 'info' => $file->info,
+ 'datestamp' => isset($file->info['datestamp']) ? $file->info['datestamp'] : 0,
+ 'includes' => array($file->name => isset($file->info['name']) ? $file->info['name'] : $file->name),
+ 'project_type' => $project_name == 'drupal' ? 'core' : $project_type,
+ );
+ }
+ else {
+ $projects[$project_name]['includes'][$file->name] = $file->info['name'];
+ $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
+ }
+ }
+}
+
+/**
+ * Given a $file object (as returned by system_rebuild_module_data()), figure
+ * out what project it belongs to.
+ *
+ * Based on update_get_project_name().
+ *
+ * @param $file
+ * @return string
+ * @see system_get_files_database()
+ */
+function l10n_update_get_project_name($file) {
+ $project_name = '';
+ if (isset($file->info['project'])) {
+ $project_name = $file->info['project'];
+ }
+ elseif (isset($file->info['package']) && (strpos($file->info['package'], 'Core') === 0)) {
+ $project_name = 'drupal';
+ }
+ return $project_name;
+}
+
+/**
+ * Retrieve data for default server.
+ *
+ * @return array
+ * Array of server parameters:
+ * - "server_pattern": URI containing po file pattern.
+ */
+function l10n_update_default_translation_server() {
+ $pattern = variable_get('l10n_update_default_update_url', L10N_UPDATE_DEFAULT_SERVER_PATTERN);
+
+ return array(
+ 'pattern' => $pattern,
+ );
+}
+
+/**
+ * Check for the latest release of project translations.
+ *
+ * @param array $projects
+ * Array of project names to check. Defaults to all translatable projects.
+ * @param string $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ *
+ * @return array
+ * Available sources indexed by project and language.
+ */
+// @todo Return batch or NULL
+function l10n_update_check_projects($projects = array(), $langcodes = array()) {
+ if (l10n_update_use_remote_source()) {
+ // Retrieve the status of both remote and local translation sources by
+ // using a batch process.
+ l10n_update_check_projects_batch($projects, $langcodes);
+ }
+ else {
+ // Retrieve and save the status of local translations only.
+ l10n_update_check_projects_local($projects, $langcodes);
+ variable_set('l10n_update_last_check', REQUEST_TIME);
+ }
+}
+
+/**
+ * Gets and stores the status and timestamp of remote po files.
+ *
+ * A batch process is used to check for po files at remote locations and (when
+ * configured) to check for po files in the local file system. The most recent
+ * translation source states are stored in the state variable
+ * 'l10n_update_translation_status'.
+ *
+ * @param array $projects
+ * Array of project names to check. Defaults to all translatable projects.
+ * @param string $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ */
+function l10n_update_check_projects_batch($projects = array(), $langcodes = array()) {
+ // Build and set the batch process.
+ $batch = l10n_update_batch_status_build($projects, $langcodes);
+ batch_set($batch);
+}
+
+/**
+ * Builds a batch to get the status of remote and local translation files.
+ *
+ * The batch process fetches the state of both local and (if configured) remote
+ * translation files. The data of the most recent translation is stored per
+ * per project and per language. This data is stored in a state variable
+ * 'l10n_update_translation_status'. The timestamp it was last updated is stored
+ * in the state variable 'l10n_upate_last_checked'.
+ *
+ * @param array $projects
+ * Array of project names for which to check the state of translation files.
+ * Defaults to all translatable projects.
+ * @param array $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ *
+ * @return array
+ * Batch definition array.
+ */
+function l10n_update_batch_status_build($projects = array(), $langcodes = array()) {
+ $projects = $projects ? $projects : array_keys(l10n_update_get_projects());
+ $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
+ $options = _l10n_update_default_update_options();
+
+ $operations = _l10n_update_batch_status_operations($projects, $langcodes, $options);
+
+ $batch = array(
+ 'operations' => $operations,
+ 'title' => t('Checking translations'),
+ 'progress_message' => '',
+ 'finished' => 'l10n_update_batch_status_finished',
+ 'error_message' => t('Error checking translation updates.'),
+ 'file' => drupal_get_path('module', 'l10n_update') . '/l10n_update.batch.inc',
+ );
+ return $batch;
+}
+
+/**
+ * Helper function to construct batch operations checking remote translation
+ * status.
+ *
+ * @param array $projects
+ * Array of project names to be processed.
+ * @param array $langcodes
+ * Array of language codes.
+ * @param array $options
+ * Batch processing options.
+ *
+ * @return array
+ * Array of batch operations.
+ */
+function _l10n_update_batch_status_operations($projects, $langcodes, $options = array()) {
+ $operations = array();
+
+ foreach ($projects as $project) {
+ foreach ($langcodes as $langcode) {
+ // Check status of local and remote translation sources.
+ $operations[] = array('l10n_update_batch_status_check', array($project, $langcode, $options));
+ }
+ }
+
+ return $operations;
+}
+
+/**
+ * Check and store the status and timestamp of local po files.
+ *
+ * Only po files in the local file system are checked. Any remote translation
+ * files will be ignored.
+ *
+ * Projects may contain a server_pattern option containing a pattern of the
+ * path to the po source files. If no server_pattern is defined the default
+ * translation directory is checked for the po file. When a server_pattern is
+ * defined the specified location is checked. The server_pattern can be set in
+ * the module's .info.yml file or by using
+ * hook_l10n_update_projects_alter().
+ *
+ * @param array $projects
+ * Array of project names for which to check the state of translation files.
+ * Defaults to all translatable projects.
+ * @param array $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ */
+function l10n_update_check_projects_local($projects = array(), $langcodes = array()) {
+ $projects = l10n_update_get_projects($projects);
+ $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
+
+ // For each project and each language we check if a local po file is
+ // available. When found the source object is updated with the appropriate
+ // type and timestamp of the po file.
+ foreach ($projects as $name => $project) {
+ foreach ($langcodes as $langcode) {
+ $source = l10n_update_source_build($project, $langcode);
+ if ($file = l10n_update_source_check_file($source)) {
+ l10n_update_status_save($name, $langcode, L10N_UPDATE_LOCAL, $file);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.drush.inc b/sites/all/modules/l10n_update/l10n_update.drush.inc
new file mode 100644
index 000000000..3f06cfce9
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.drush.inc
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * @file
+ * Drush interface to l10n-update functionalities.
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function l10n_update_drush_command() {
+ $commands = array();
+ $commands['l10n-update-refresh'] = array(
+ 'description' => 'Refresh available information.',
+ );
+ $commands['l10n-update-status'] = array(
+ 'description' => 'Show translation status of available projects.',
+ 'options' => array(
+ 'languages' => 'Comma separated list of languages. Defaults to all available languages. Example: --languages="nl, fr, de"',
+ )
+ );
+ $commands['l10n-update'] = array(
+ 'description' => 'Update translations.',
+ 'options' => array(
+ 'languages' => 'Comma separated list of languages. Defaults to all available languages. Example: --languages="nl, fr, de"',
+ 'mode' => 'Determine if existing translations are overwitten during import. Use "overwrite" to overwrite any existing translation, "replace" to replace previously imported translations but not overwrite edited strings, "keep" to keep any existing translation and only add new translations. Default value: "keep"'
+ ),
+ );
+ return $commands;
+}
+
+/**
+ * Callback for command l10n-update-refresh.
+ */
+function drush_l10n_update_refresh() {
+ module_load_include('compare.inc', 'l10n_update');
+
+ // Check the translation status of all translatable projects in all languages.
+ // First we clear the cached list of projects. Although not strictly
+ // necessary, this is helpful in case the project list is out of sync.
+ l10n_update_flush_projects();
+ l10n_update_check_projects();
+
+ // Execute a batch if required. A batch is only used when remote files
+ // are checked.
+ if (batch_get()) {
+ drush_backend_batch_process();
+ }
+}
+
+/**
+ * Validate command l10n-update-status.
+ */
+function drush_l10n_update_status_validate() {
+ return _drush_l10n_update_validate_languages();
+}
+
+/**
+ * Callback for command l10n-update-status.
+ */
+function drush_l10n_update_status() {
+ $status = l10n_update_get_status();
+
+ if (!empty($status)) {
+ $languages = drush_get_option('languages');
+
+ // Build table header.
+ $table = array();
+ $header = array(dt('Project'));
+ foreach ($languages as $langcode => $language) {
+ $header[] = $language->name;
+ }
+ $table[] = $header;
+
+ // Iterate projects to obtain per language status.
+ foreach ($status as $name => $project) {
+ $row = array();
+ // First column: Project name & version number.
+ $project_details = reset($project);
+ $title = isset($project_details->title) ? $project_details->title : $project_details->name;
+ $row[] = dt('@project (@version)', array('@project' => $title, '@version' => $project_details->version));
+
+ // Other columns: Status per language.
+ foreach ($languages as $langcode => $language) {
+ $current = $project[$langcode]->type == L10N_UPDATE_CURRENT;
+ $local_update = $project[$langcode]->type == L10N_UPDATE_LOCAL;
+ $remote_update = $project[$langcode]->type == L10N_UPDATE_REMOTE;
+ if ($local_update || $remote_update) {
+ $row[] = $remote_update ? dt('Remote update available') : dt('Local update available');
+ }
+ elseif ($current) {
+ $row[] = dt('Up to date');
+ }
+ else {
+ $row[] = dt('No info');
+ }
+ }
+ $table[] = $row;
+ }
+ drush_print_table($table, TRUE);
+ }
+ else {
+ drush_log(dt('No languages to update.'), 'warning');
+ }
+}
+
+/**
+ * Validate command l10n-update.
+ */
+function drush_l10n_update_validate() {
+ $lang_validation = _drush_l10n_update_validate_languages();
+ if ($lang_validation == FALSE) {
+ return FALSE;
+ }
+ // Check provided update mode is valid.
+ $mode = drush_get_option('mode', 'keep');
+ if (!in_array($mode, array('keep', 'replace', 'overwrite'))) {
+ return drush_set_error('L10N_UPDATE_INVALID_MODE', dt('Invalid update mode. Valid options are keep, replace, overwrite.'));
+ }
+}
+
+/**
+ * Callback for command l10n-update.
+ */
+function drush_l10n_update() {
+ module_load_include('fetch.inc', 'l10n_update');
+ $updates = _drush_l10n_update_get_updates();
+
+ if ($updates['projects']) {
+ drush_log(dt('Found @count projects to update.', array('@count' => count($updates['projects']))), 'status');
+
+ // Batch update all projects for selected languages.
+ $mode = drush_get_option('mode', 'keep');
+ $options = _l10n_update_default_update_options();
+
+ switch ($mode) {
+ case 'keep':
+ $options['overwrite_options'] = array(
+ 'not_customized' => FALSE,
+ 'customized' => FALSE,
+ );
+ break;
+ case 'replace':
+ $options['overwrite_options'] = array(
+ 'not_customized' => TRUE,
+ 'customized' => FALSE,
+ );
+ break;
+ case 'overwrite':
+ $options['overwrite_options'] = array(
+ 'not_customized' => TRUE,
+ 'customized' => TRUE,
+ );
+ break;
+
+ default:
+ return drush_set_error('L10N_UPDATE_INVALID_MODE', dt('Invalid update mode. Valid options are keep, overwrite.'));
+ break;
+ }
+
+ $languages = array_keys(drush_get_option('languages'));
+
+ // Get translation status of the projects, download and update translations.
+ $batch = l10n_update_batch_update_build(array(), $languages, $options);
+ drush_log($batch['title'], 'status');
+ drush_log($batch['init_message'], 'status');
+ batch_set($batch);
+ drush_backend_batch_process();
+ }
+ else {
+ drush_log(dt('All project translations up to date'), 'status');
+ }
+}
+
+/**
+ * Helper function to validate languages.
+ *
+ * Used by _validate hooks.
+ * 1. Check other languages than english are available.
+ * 2. Check user provided languages are valid.
+ */
+function _drush_l10n_update_validate_languages() {
+ // Check if there are installed languages other than english.
+ $installed_languages = l10n_update_translatable_language_list();
+
+ // Indicate that there's nothing to do, only show a warning.
+ if (empty($installed_languages)) {
+ drush_log(dt('No languages to update.'), 'warning');
+ return FALSE;
+ }
+
+ // Check provided languages are valid.
+ $languages = drush_get_option('languages', '');
+ $languages = array_map('trim', _convert_csv_to_array($languages));
+ if (count($languages)) {
+ foreach ($languages as $key => $langcode) {
+ if (!isset($installed_languages[$langcode])) {
+ if (is_numeric($langcode)) {
+ drush_set_error('L10N_UPDATE_INVALID_LANGUAGE', dt('Invalid language "@langcode". Use for example: --languages="nl, fr, de"', array('@langcode' => $langcode)));
+ }
+ else {
+ drush_set_error('L10N_UPDATE_INVALID_LANGUAGE', dt('Language "@langcode" is not installed.', array('@langcode' => $langcode)));
+ }
+ }
+ else {
+ unset($languages[$key]);
+ $languages[$langcode] = $installed_languages[$langcode];
+ }
+ }
+ if (drush_get_error() != DRUSH_SUCCESS) {
+ drush_print(dt('Available languages: @languages', array('@languages' => implode(', ', array_keys($installed_languages)))));
+ return FALSE;
+ }
+ }
+ else {
+ $languages = $installed_languages;
+ }
+ drush_set_option('languages', $languages);
+ return TRUE;
+}
+
+/**
+ * Helper function to obtain $updates.
+ *
+ * @return $updates array or NULL.
+ */
+function _drush_l10n_update_get_updates() {
+ $updates = array();
+ $languages = l10n_update_translatable_language_list();
+ $status = l10n_update_get_status();
+
+ drush_log(dt('Fetching update information for all projects / all languages.'), 'status');
+
+ // Prepare information about projects which have available translation
+ // updates.
+ if ($languages && $status) {
+ foreach ($status as $project) {
+ foreach ($project as $langcode => $project_info) {
+ // Translation update found for this project-language combination.
+ if ($project_info->type && ($project_info->type == L10N_UPDATE_LOCAL || $project_info->type == L10N_UPDATE_REMOTE)) {
+ $updates['projects'][$project_info->name] = $project_info;
+ $updates['languages'][$langcode] = $project_info;
+ }
+ }
+ }
+ }
+ if ($updates) {
+ return $updates;
+ }
+ else {
+ drush_log(dt('No languages to update.'), 'warning');
+ }
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.fetch.inc b/sites/all/modules/l10n_update/l10n_update.fetch.inc
new file mode 100644
index 000000000..85178b2aa
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.fetch.inc
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * The API for download and import of translations from remote and local sources.
+ */
+
+/**
+ * Load the common translation API.
+ */
+// @todo Combine functions differently in files to avoid unnecessary includes.
+// Follow-up issue http://drupal.org/node/1834298
+require_once __DIR__ . '/l10n_update.translation.inc';
+
+/**
+ * Builds a batch to check, download and import project translations.
+ *
+ * @param array $projects
+ * Array of project names for which to update the translations. Defaults to
+ * all translatable projects.
+ * @param array $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ * @param array $options
+ * Array of import options. See locale_translate_batch_import_files().
+ *
+ * @return array
+ * Batch definition array.
+ */
+function l10n_update_batch_update_build($projects = array(), $langcodes = array(), $options = array()) {
+ module_load_include('compare.inc', 'l10n_update');
+ $projects = $projects ? $projects : array_keys(l10n_update_get_projects());
+ $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
+ $status_options = $options;
+ $status_options['finish_feedback'] = FALSE;
+
+ // Check status of local and remote translation files.
+ $operations = _l10n_update_batch_status_operations($projects, $langcodes, $status_options);
+ // Download and import translations.
+ $operations = array_merge($operations, _l10n_update_fetch_operations($projects, $langcodes, $options));
+
+ $batch = array(
+ 'operations' => $operations,
+ 'title' => t('Updating translations'),
+ 'progress_message' => '',
+ 'error_message' => t('Error importing translation files'),
+ 'finished' => 'l10n_update_batch_fetch_finished',
+ 'file' => drupal_get_path('module', 'l10n_update') . '/l10n_update.batch.inc',
+ );
+ return $batch;
+}
+
+/**
+ * Builds a batch to download and import project translations.
+ *
+ * @param array $projects
+ * Array of project names for which to check the state of translation files.
+ * Defaults to all translatable projects.
+ * @param array $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ * @param array $options
+ * Array of import options. See l10n_update_batch_import_files().
+ *
+ * @return array
+ * Batch definition array.
+ */
+function l10n_update_batch_fetch_build($projects = array(), $langcodes = array(), $options = array()) {
+ $projects = $projects ? $projects : array_keys(l10n_update_get_projects());
+ $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
+
+ $batch = array(
+ 'operations' => _l10n_update_fetch_operations($projects, $langcodes, $options),
+ 'title' => t('Updating translations.'),
+ 'progress_message' => '',
+ 'error_message' => t('Error importing translation files'),
+ 'finished' => 'l10n_update_batch_fetch_finished',
+ 'file' => drupal_get_path('module', 'l10n_update') . '/l10n_update.batch.inc',
+ );
+ return $batch;
+}
+
+/**
+ * Helper function to construct the batch operations to fetch translations.
+ *
+ * @param array $projects
+ * Array of project names for which to check the state of translation files.
+ * Defaults to all translatable projects.
+ * @param array $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ * @param array $options
+ * Array of import options.
+ *
+ * @return array
+ * Array of batch operations.
+ */
+function _l10n_update_fetch_operations($projects, $langcodes, $options) {
+ $operations = array();
+
+ foreach ($projects as $project) {
+ foreach ($langcodes as $langcode) {
+ if (l10n_update_use_remote_source()) {
+ $operations[] = array('l10n_update_batch_fetch_download', array($project, $langcode));
+ }
+ $operations[] = array('l10n_update_batch_fetch_import', array($project, $langcode, $options));
+ }
+ }
+
+ return $operations;
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.http.inc b/sites/all/modules/l10n_update/l10n_update.http.inc
new file mode 100644
index 000000000..0f05ae5f4
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.http.inc
@@ -0,0 +1,386 @@
+<?php
+
+/**
+ * @file
+ * Http API for l10n updates.
+ */
+
+/**
+ * Check if remote file exists and when it was last updated.
+ *
+ * @param $url
+ * URL of remote file.
+ * @param $headers
+ * HTTP request headers.
+ * @return object
+ * Result object containing the HTTP request headers, response code, headers,
+ * data, redirect status and updated timestamp.
+ * @see l10n_update_http_request()
+ */
+function l10n_update_http_check($url, $headers = array()) {
+ $result = l10n_update_http_request($url, array('headers' => $headers, 'method' => 'HEAD'));
+ if (!isset($result->error)) {
+ if ($result && $result->code == 200) {
+ $result->updated = isset($result->headers['last-modified']) ? strtotime($result->headers['last-modified']) : 0;
+ }
+ return $result;
+ }
+ else {
+ switch ($result->code) {
+ case 404:
+ // File not found occurs when a translation file is not yet available
+ // at the translation server. But also if a custom module or custom
+ // theme does not define the location of a translation file. By default
+ // the file is checked at the translation server, but it will not be
+ // found there.
+ watchdog('l10n_update', 'File not found: @uri.', array('@uri' => $url));
+ return TRUE;
+ case 0:
+ watchdog('l10n_update', 'Error occurred when trying to check @remote: @errormessage.', array('@errormessage' => $result->error, '@remote' => $url), WATCHDOG_ERROR);
+ break;
+ default:
+ watchdog('l10n_update', 'HTTP error @errorcode occurred when trying to check @remote.', array('@errorcode' => $result->code, '@remote' => $url), WATCHDOG_ERROR);
+ break;
+ }
+ }
+ return $result;
+}
+
+/**
+ * Perform an HTTP request.
+ *
+ * We cannot use drupal_http_request() at install, see http://drupal.org/node/527484
+ *
+ * This is a flexible and powerful HTTP client implementation. Correctly
+ * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
+ *
+ * @param $url
+ * A string containing a fully qualified URI.
+ * @param array $options
+ * (optional) An array that can have one or more of the following elements:
+ * - headers: An array containing request headers to send as name/value pairs.
+ * - method: A string containing the request method. Defaults to 'GET'.
+ * - data: A string containing the request body, formatted as
+ * 'param=value&param=value&...'. Defaults to NULL.
+ * - max_redirects: An integer representing how many times a redirect
+ * may be followed. Defaults to 3.
+ * - timeout: A float representing the maximum number of seconds the function
+ * call may take. The default is 30 seconds. If a timeout occurs, the error
+ * code is set to the HTTP_REQUEST_TIMEOUT constant.
+ * - context: A context resource created with stream_context_create().
+ *
+ * @return object
+ * An object that can have one or more of the following components:
+ * - request: A string containing the request body that was sent.
+ * - code: An integer containing the response status code, or the error code
+ * if an error occurred.
+ * - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
+ * - status_message: The status message from the response, if a response was
+ * received.
+ * - redirect_code: If redirected, an integer containing the initial response
+ * status code.
+ * - redirect_url: If redirected, a string containing the URL of the redirect
+ * target.
+ * - error: If an error occurred, the error message. Otherwise not set.
+ * - headers: An array containing the response headers as name/value pairs.
+ * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
+ * easy access the array keys are returned in lower case.
+ * - data: A string containing the response body that was received.
+ */
+function l10n_update_http_request($url, array $options = array()) {
+ $result = new stdClass();
+
+ // Parse the URL and make sure we can handle the schema.
+ $uri = @parse_url($url);
+
+ if ($uri == FALSE) {
+ $result->error = 'unable to parse URL';
+ $result->code = -1001;
+ return $result;
+ }
+
+ if (!isset($uri['scheme'])) {
+ $result->error = 'missing schema';
+ $result->code = -1002;
+ return $result;
+ }
+
+ timer_start(__FUNCTION__);
+
+ // Merge the default options.
+ $options += array(
+ 'headers' => array(),
+ 'method' => 'GET',
+ 'data' => NULL,
+ 'max_redirects' => 3,
+ 'timeout' => 30.0,
+ 'context' => NULL,
+ );
+
+ // Merge the default headers.
+ $options['headers'] += array(
+ 'User-Agent' => 'Drupal (+http://drupal.org/)',
+ );
+
+ // stream_socket_client() requires timeout to be a float.
+ $options['timeout'] = (float) $options['timeout'];
+
+ // Use a proxy if one is defined and the host is not on the excluded list.
+ $proxy_server = variable_get('proxy_server', '');
+ if ($proxy_server && _drupal_http_use_proxy($uri['host'])) {
+ // Set the scheme so we open a socket to the proxy server.
+ $uri['scheme'] = 'proxy';
+ // Set the path to be the full URL.
+ $uri['path'] = $url;
+ // Since the URL is passed as the path, we won't use the parsed query.
+ unset($uri['query']);
+
+ // Add in username and password to Proxy-Authorization header if needed.
+ if ($proxy_username = variable_get('proxy_username', '')) {
+ $proxy_password = variable_get('proxy_password', '');
+ $options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . (!empty($proxy_password) ? ":" . $proxy_password : ''));
+ }
+ // Some proxies reject requests with any User-Agent headers, while others
+ // require a specific one.
+ $proxy_user_agent = variable_get('proxy_user_agent', '');
+ // The default value matches neither condition.
+ if ($proxy_user_agent === NULL) {
+ unset($options['headers']['User-Agent']);
+ }
+ elseif ($proxy_user_agent) {
+ $options['headers']['User-Agent'] = $proxy_user_agent;
+ }
+ }
+
+ switch ($uri['scheme']) {
+ case 'proxy':
+ // Make the socket connection to a proxy server.
+ $socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080);
+ // The Host header still needs to match the real request.
+ $options['headers']['Host'] = $uri['host'];
+ $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
+ break;
+
+ case 'http':
+ case 'feed':
+ $port = isset($uri['port']) ? $uri['port'] : 80;
+ $socket = 'tcp://' . $uri['host'] . ':' . $port;
+ // RFC 2616: "non-standard ports MUST, default ports MAY be included".
+ // We don't add the standard port to prevent from breaking rewrite rules
+ // checking the host that do not take into account the port number.
+ $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
+ break;
+
+ case 'https':
+ // Note: Only works when PHP is compiled with OpenSSL support.
+ $port = isset($uri['port']) ? $uri['port'] : 443;
+ $socket = 'ssl://' . $uri['host'] . ':' . $port;
+ $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
+ break;
+
+ default:
+ $result->error = 'invalid schema ' . $uri['scheme'];
+ $result->code = -1003;
+ return $result;
+ }
+
+ if (empty($options['context'])) {
+ $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
+ }
+ else {
+ // Create a stream with context. Allows verification of a SSL certificate.
+ $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']);
+ }
+
+ // Make sure the socket opened properly.
+ if (!$fp) {
+ // When a network error occurs, we use a negative number so it does not
+ // clash with the HTTP status codes.
+ $result->code = -$errno;
+ $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
+
+ // Mark that this request failed. This will trigger a check of the web
+ // server's ability to make outgoing HTTP requests the next time that
+ // requirements checking is performed.
+ // See system_requirements().
+ // variable_set('drupal_http_request_fails', TRUE);
+
+ return $result;
+ }
+
+ // Construct the path to act on.
+ $path = isset($uri['path']) ? $uri['path'] : '/';
+ if (isset($uri['query'])) {
+ $path .= '?' . $uri['query'];
+ }
+
+ // Only add Content-Length if we actually have any content or if it is a POST
+ // or PUT request. Some non-standard servers get confused by Content-Length in
+ // at least HEAD/GET requests, and Squid always requires Content-Length in
+ // POST/PUT requests.
+ $content_length = strlen($options['data']);
+ if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
+ $options['headers']['Content-Length'] = $content_length;
+ }
+
+ // If the server URL has a user then attempt to use basic authentication.
+ if (isset($uri['user'])) {
+ $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($uri['pass']) ? ':' . $uri['pass'] : ''));
+ }
+
+ // If the database prefix is being used by SimpleTest to run the tests in a copied
+ // database then set the user-agent header to the database prefix so that any
+ // calls to other Drupal pages will run the SimpleTest prefixed database. The
+ // user-agent is used to ensure that multiple testing sessions running at the
+ // same time won't interfere with each other as they would if the database
+ // prefix were stored statically in a file or database variable.
+ $test_info = &$GLOBALS['drupal_test_info'];
+ if (!empty($test_info['test_run_id'])) {
+ $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
+ }
+
+ $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
+ foreach ($options['headers'] as $name => $value) {
+ $request .= $name . ': ' . trim($value) . "\r\n";
+ }
+ $request .= "\r\n" . $options['data'];
+ $result->request = $request;
+ // Calculate how much time is left of the original timeout value.
+ $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
+ if ($timeout > 0) {
+ stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
+ fwrite($fp, $request);
+ }
+
+ // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
+ // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
+ // instead must invoke stream_get_meta_data() each iteration.
+ $info = stream_get_meta_data($fp);
+ $alive = !$info['eof'] && !$info['timed_out'];
+ $response = '';
+
+ while ($alive) {
+ // Calculate how much time is left of the original timeout value.
+ $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
+ if ($timeout <= 0) {
+ $info['timed_out'] = TRUE;
+ break;
+ }
+ stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
+ $chunk = fread($fp, 1024);
+ $response .= $chunk;
+ $info = stream_get_meta_data($fp);
+ $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
+ }
+ fclose($fp);
+
+ if ($info['timed_out']) {
+ $result->code = HTTP_REQUEST_TIMEOUT;
+ $result->error = 'request timed out';
+ return $result;
+ }
+ // Parse response headers from the response body.
+ // Be tolerant of malformed HTTP responses that separate header and body with
+ // \n\n or \r\r instead of \r\n\r\n.
+ list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
+ $response = preg_split("/\r\n|\n|\r/", $response);
+
+ // Parse the response status line.
+ list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
+ $result->protocol = $protocol;
+ $result->status_message = $status_message;
+
+ $result->headers = array();
+
+ // Parse the response headers.
+ while ($line = trim(array_shift($response))) {
+ list($name, $value) = explode(':', $line, 2);
+ $name = strtolower($name);
+ if (isset($result->headers[$name]) && $name == 'set-cookie') {
+ // RFC 2109: the Set-Cookie response header comprises the token Set-
+ // Cookie:, followed by a comma-separated list of one or more cookies.
+ $result->headers[$name] .= ',' . trim($value);
+ }
+ else {
+ $result->headers[$name] = trim($value);
+ }
+ }
+
+ $responses = array(
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ );
+ // RFC 2616 states that all unknown HTTP codes must be treated the same as the
+ // base code in their class.
+ if (!isset($responses[$code])) {
+ $code = floor($code / 100) * 100;
+ }
+ $result->code = $code;
+
+ switch ($code) {
+ case 200: // OK
+ case 304: // Not modified
+ break;
+ case 301: // Moved permanently
+ case 302: // Moved temporarily
+ case 307: // Moved temporarily
+ $location = $result->headers['location'];
+ $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
+ if ($options['timeout'] <= 0) {
+ $result->code = HTTP_REQUEST_TIMEOUT;
+ $result->error = 'request timed out';
+ }
+ elseif ($options['max_redirects']) {
+ // Redirect to the new location.
+ $options['max_redirects']--;
+ $result = l10n_update_http_request($location, $options);
+ $result->redirect_code = $code;
+ }
+ if (!isset($result->redirect_url)) {
+ $result->redirect_url = $location;
+ }
+ break;
+ default:
+ $result->error = $status_message;
+ }
+
+ return $result;
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.info b/sites/all/modules/l10n_update/l10n_update.info
new file mode 100644
index 000000000..6b6cb297b
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.info
@@ -0,0 +1,38 @@
+name = Localization update
+description = Provides automatic downloads and updates for translations.
+dependencies[] = locale
+core = 7.x
+package = Multilingual
+
+files[] = includes/gettext/PoHeader.php
+files[] = includes/gettext/PoItem.php
+files[] = includes/gettext/PoMemoryWriter.php
+files[] = includes/gettext/PoMetadataInterface.php
+files[] = includes/gettext/PoReaderInterface.php
+files[] = includes/gettext/PoStreamInterface.php
+files[] = includes/gettext/PoStreamReader.php
+files[] = includes/gettext/PoStreamWriter.php
+files[] = includes/gettext/PoWriterInterface.php
+
+files[] = includes/locale/Gettext.php
+files[] = includes/locale/PoDatabaseReader.php
+files[] = includes/locale/PoDatabaseWriter.php
+files[] = includes/locale/SourceString.php
+files[] = includes/locale/StringBase.php
+files[] = includes/locale/StringDatabaseStorage.php
+files[] = includes/locale/StringInterface.php
+files[] = includes/locale/StringStorageException.php
+files[] = includes/locale/StringStorageInterface.php
+files[] = includes/locale/TranslationString.php
+files[] = includes/locale/TranslationsStreamWrapper.php
+
+files[] = tests/L10nUpdateCronTest.test
+files[] = tests/L10nUpdateInterfaceTest.test
+files[] = tests/L10nUpdateTest.test
+files[] = tests/L10nUpdateTestBase.test
+; Information added by Drupal.org packaging script on 2014-11-10
+version = "7.x-2.0"
+core = "7.x"
+project = "l10n_update"
+datestamp = "1415625781"
+
diff --git a/sites/all/modules/l10n_update/l10n_update.install b/sites/all/modules/l10n_update/l10n_update.install
new file mode 100644
index 000000000..0b47d3eb0
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.install
@@ -0,0 +1,365 @@
+<?php
+
+/**
+ * @file
+ * Install file for l10n remote updates.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function l10n_update_schema() {
+ $schema['l10n_update_project'] = array(
+ 'description' => 'Update information for project translations.',
+ 'fields' => array(
+ 'name' => array(
+ 'description' => 'A unique short name to identify the project.',
+ 'type' => 'varchar',
+ 'length' => '50',
+ 'not null' => TRUE,
+ ),
+ 'project_type' => array(
+ 'description' => 'Project type, may be core, module, theme',
+ 'type' => 'varchar',
+ 'length' => '50',
+ 'not null' => TRUE,
+ ),
+ 'core' => array(
+ 'description' => 'Core compatibility string for this project.',
+ 'type' => 'varchar',
+ 'length' => '128',
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'version' => array(
+ 'description' => 'Human readable name for project used on the interface.',
+ 'type' => 'varchar',
+ 'length' => '128',
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'l10n_server' => array(
+ 'description' => 'Localization server for this project.',
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'l10n_path' => array(
+ 'description' => 'Server path this project updates.',
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'status' => array(
+ 'description' => 'Status flag. If TRUE, translations of this module will be updated.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 1,
+ ),
+ ),
+ 'primary key' => array('name'),
+ );
+
+ $schema['l10n_update_file'] = array(
+ 'description' => 'File and download information for project translations.',
+ 'fields' => array(
+ 'project' => array(
+ 'description' => 'A unique short name to identify the project.',
+ 'type' => 'varchar',
+ 'length' => '50',
+ 'not null' => TRUE,
+ ),
+ 'language' => array(
+ 'description' => 'Reference to the {languages}.language for this translation.',
+ 'type' => 'varchar',
+ 'length' => '12',
+ 'not null' => TRUE,
+ ),
+ 'type' => array(
+ 'description' => 'File origin: download or localfile',
+ 'type' => 'varchar',
+ 'length' => '50',
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'filename' => array(
+ 'description' => 'Link to translation file for download.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'fileurl' => array(
+ 'description' => 'Link to translation file for download.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'uri' => array(
+ 'description' => 'File system path for importing the file.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'timestamp' => array(
+ 'description' => 'Unix timestamp of the time the file was downloaded or saved to disk. Zero if not yet downloaded',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'disp-width' => '11',
+ 'default' => 0,
+ ),
+ 'version' => array(
+ 'description' => 'Version tag of the downloaded file.',
+ 'type' => 'varchar',
+ 'length' => '128',
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'status' => array(
+ 'description' => 'Status flag. TBD',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 1,
+ ),
+ 'last_checked' => array(
+ 'description' => 'Unix timestamp of the last time this translation was downloaded from or checked at remote server and confirmed to be the most recent release available.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'disp-width' => '11',
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('project', 'language'),
+ );
+
+ $schema['cache_l10n_update'] = drupal_get_schema_unprocessed('system', 'cache');
+ $schema['cache_l10n_update']['description'] = 'Cache table for the Localization Update module to store information about available releases, fetched from central server.';
+
+ return $schema;
+}
+
+/**
+ * Implements hook_schema_alter().
+ */
+function l10n_update_schema_alter(&$schema) {
+ $schema['locales_target']['fields']['l10n_status'] = array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Boolean indicating whether the translation is custom to this site.',
+ );
+}
+
+/**
+ * Implements hook_install().
+ */
+function l10n_update_install() {
+ db_add_field('locales_target', 'l10n_status', array('type' => 'int', 'not null' => TRUE, 'default' => 0));
+ variable_set('l10n_update_rebuild_projects', 1);
+
+ // Create the translation directory. We try different alternative paths as the
+ // default may not always be writable.
+ $directories = array(
+ variable_get('l10n_update_download_store', L10N_UPDATE_DEFAULT_TRANSLATION_PATH),
+ variable_get('file_public_path', conf_path() . '/files') . '/translations',
+ 'sites/default/files/translations',
+ );
+ foreach ($directories as $directory) {
+ if (file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ variable_set('l10n_update_download_store', $directory);
+ return;
+ }
+ }
+ watchdog('l10n_update', 'The directory %directory does not exist or is not writable.', array('%directory' => $directories[0]), WATCHDOG_ERROR);
+ drupal_set_message(t('The directory %directory does not exist or is not writable.', array('%directory' => $directories[0])), 'error');
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function l10n_update_uninstall() {
+ db_drop_field('locales_target', 'l10n_status');
+
+ variable_del('l10n_update_check_disabled');
+ variable_del('l10n_update_default_filename');
+ variable_del('l10n_update_default_update_url');
+ variable_del('l10n_update_import_enabled');
+ variable_del('l10n_update_import_mode');
+ variable_del('l10n_update_check_frequency');
+ variable_del('l10n_update_last_check');
+ variable_del('l10n_update_download_store');
+ variable_del('l10n_update_translation_status');
+ variable_del('l10n_update_check_mode');}
+
+/**
+ * Implements hook_requirements().
+ */
+function l10n_update_requirements($phase) {
+ $requirements = array();
+ if ($phase == 'runtime') {
+ $available_updates = array();
+ $untranslated = array();
+ $languages = l10n_update_translatable_language_list();
+
+ if ($languages) {
+ // Determine the status of the translation updates per language.
+ $status = l10n_update_get_status();
+ if ($status) {
+ foreach ($status as $project) {
+ foreach ($project as $langcode => $project_info) {
+ if (empty($project_info->type)) {
+ $untranslated[$langcode] = $languages[$langcode];
+ }
+ elseif ($project_info->type == L10N_UPDATE_LOCAL || $project_info->type == L10N_UPDATE_REMOTE) {
+ $available_updates[$langcode] = $languages[$langcode];
+ }
+ }
+ }
+
+ if ($available_updates || $untranslated) {
+ if ($available_updates) {
+ $requirements['l10n_update'] = array(
+ 'title' => 'Translation update status',
+ 'value' => l(t('Updates available'), 'admin/config/regional/translate/update'),
+ 'severity' => REQUIREMENT_WARNING,
+ 'description' => t('Updates available for: @languages. See the <a href="!updates">Available translation updates</a> page for more information.', array('@languages' => implode(', ', $available_updates), '!updates' => url('admin/config/regional/translate/update'))),
+ );
+ }
+ else {
+ $requirements['l10n_update'] = array(
+ 'title' => 'Translation update status',
+ 'value' => t('Missing translations'),
+ 'severity' => REQUIREMENT_INFO,
+ 'description' => t('Missing translations for: @languages. See the <a href="!updates">Available translation updates</a> page for more information.', array('@languages' => implode(', ', $untranslated), '!updates' => url('admin/config/regional/translate/update'))),
+ );
+ }
+ }
+ else {
+ $requirements['l10n_update'] = array(
+ 'title' => 'Translation update status',
+ 'value' => t('Up to date'),
+ 'severity' => REQUIREMENT_OK,
+ );
+ }
+ }
+ else {
+ $requirements['locale_translation'] = array(
+ 'title' => 'Translation update status',
+ 'value' => l(t('Can not determine status'), 'admin/config/regional/translate/update'),
+ 'severity' => REQUIREMENT_WARNING,
+ 'description' => t('No translation status is available. See the <a href="!updates">Available translation updates</a> page for more information.', array('!updates' => url('admin/config/regional/translate/update'))),
+ );
+ }
+ }
+ }
+ if ($phase == 'update') {
+ // Make sure the 'translations' stream wrapper class gets registered.
+ // This is needed when upgrading to 7.x-2.x.
+ if (!class_exists('TranslationsStreamWrapper')) {
+ registry_rebuild();
+ }
+ }
+ return $requirements;
+}
+
+/**
+ * Rename filepath to uri in {l10n_update_file} table.
+ */
+function l10n_update_update_7001() {
+ // Only do this update if the field exists from D6.
+ // If it doesn't, we've got a pure D7 site that doesn't need it.
+ if (db_field_exists('l10n_update_file', 'filepath')) {
+ db_change_field('l10n_update_file', 'filepath', 'uri', array(
+ 'description' => 'File system path for importing the file.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ));
+ }
+}
+
+/**
+ * Delete 'last_updated' field from {l10n_update_file} table.
+ */
+function l10n_update_update_7002() {
+ db_drop_field('l10n_update_file', 'last_updated');
+}
+
+/**
+ * Delete 'import_date' field from {l10n_update_file} table.
+ */
+function l10n_update_update_7003() {
+ db_drop_field('l10n_update_file', 'import_date');
+}
+
+/**
+ * Create {cache_l10n_update} table.
+ */
+function l10n_update_update_7004() {
+ if (!db_table_exists('cache_l10n_update')) {
+ $schema = drupal_get_schema_unprocessed('system', 'cache');
+ $schema['description'] = 'Cache table for the Localization Update module to store information about available releases, fetched from central server.';
+ db_create_table('cache_l10n_update', $schema);
+ }
+}
+
+/**
+ * Migration to 7.x-2.x branch.
+ */
+function l10n_update_update_7200() {
+ // Make sure the 'translations' stream wrapper class gets registered.
+ if (!class_exists('TranslationsStreamWrapper')) {
+ registry_rebuild();
+ }
+
+ if (!variable_get('l10n_update_download_store', '')) {
+ variable_set('l10n_update_download_store', 'sites/all/translations');
+ }
+ // Create the translation directory. We try different alternative paths as the
+ // default may not always be writable.
+ $directories = array(
+ variable_get('l10n_update_download_store', L10N_UPDATE_DEFAULT_TRANSLATION_PATH),
+ variable_get('file_public_path', conf_path() . '/files') . '/translations',
+ 'sites/default/files/translations',
+ );
+ foreach ($directories as $directory) {
+ if (file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ variable_set('l10n_update_download_store', $directory);
+ return;
+ }
+ }
+ watchdog('l10n_update', 'The directory %directory does not exist or is not writable.', array('%directory' => $directories[0]), WATCHDOG_ERROR);
+ drupal_set_message(t('The directory %directory does not exist or is not writable.', array('%directory' => $directories[0])), 'error');
+
+ // Translation source 'Remote server only' is no longer supported. Use 'Remote
+ // and local' instead.
+ $mode = variable_get('l10n_update_check_mode', 3); // L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL
+ if ($mode == 1) {
+ variable_set('l10n_update_check_mode', 3); // L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL
+ }
+
+ // Daily cron updates are no longer supported. Use weekly instead.
+ $frequency = variable_get('l10n_update_check_frequency', '0');
+ if ($frequency == '1') {
+ variable_set('l10n_update_check_frequency', '7');
+ }
+
+ // Clean up deprecated variables.
+ variable_del('l10n_update_default_server');
+ variable_del('l10n_update_default_server_url');
+ variable_del('l10n_update_rebuild_projects');
+}
+
+/**
+ * Sets the default translation files directory.
+ */
+function l10n_update_update_7201() {
+ if (!variable_get('l10n_update_download_store', '')) {
+ variable_set('l10n_update_download_store', 'sites/all/translations');
+ }
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.module b/sites/all/modules/l10n_update/l10n_update.module
new file mode 100644
index 000000000..09e93bb84
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.module
@@ -0,0 +1,761 @@
+<?php
+
+/**
+ * @file
+ * Download translations from remote localization server.
+ */
+
+/**
+ * Translation update mode: Use local files only.
+ *
+ * When checking for available translation updates, only local files will be
+ * used. Any remote translation file will be ignored. Also custom modules and
+ * themes which have set a "server pattern" to use a remote translation server
+ * will be ignored.
+ */
+define('L10N_UPDATE_USE_SOURCE_LOCAL', 2);
+
+/**
+ * Translation update mode: Use both remote and local files.
+ *
+ * When checking for available translation updates, both local and remote files
+ * will be checked.
+ */
+define('L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL', 3);
+
+/**
+ * Default location of gettext file on the translation server.
+ *
+ * @see l10n_update_default_translation_server().
+ */
+define('L10N_UPDATE_DEFAULT_SERVER_PATTERN', 'http://ftp.drupal.org/files/translations/%core/%project/%project-%release.%language.po');
+
+/**
+ * Default gettext file name on the translation server.
+ */
+define('L10N_UPDATE_DEFAULT_FILE_NAME', '%project-%release.%language.po');
+
+/**
+ * Default gettext file name on the translation server.
+ */
+define('L10N_UPDATE_DEFAULT_TRANSLATION_PATH', 'sites/all/translations');
+
+/**
+ * The number of seconds that the translations status entry should be considered.
+ */
+define('L10N_UPDATE_STATUS_TTL', 600);
+
+/**
+ * UI option for override of existing translations. Only override non-customized
+ * translations.
+ */
+define('L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED', 2);
+
+/**
+ * Translation source is a remote file.
+ */
+define('L10N_UPDATE_REMOTE', 'remote');
+
+/**
+ * Translation source is a local file.
+ */
+define('L10N_UPDATE_LOCAL', 'local');
+
+/**
+ * Translation source is the current translation.
+ */
+define('L10N_UPDATE_CURRENT', 'current');
+
+/**
+ * The delimiter used to split plural strings.
+ *
+ * This is the ETX (End of text) character and is used as a minimal means to
+ * separate singular and plural variants in source and translation text. It
+ * was found to be the most compatible delimiter for the supported databases.
+ */
+define('L10N_UPDATE_PLURAL_DELIMITER', "\03");
+
+/**
+ * Flag for locally not customized interface translation.
+ *
+ * Such translations are imported from .po files downloaded from
+ * localize.drupal.org for example.
+ */
+define('L10N_UPDATE_NOT_CUSTOMIZED', 0);
+
+/**
+ * Flag for locally customized interface translation.
+ *
+ * Strings are customized when translated or edited using the build in
+ * string translation form. Strings can also be marked as customized when a po
+ * file is imported.
+ */
+define('L10N_UPDATE_STRING_CUSTOM', 1);
+
+/**
+ * Flag for locally customized interface translation.
+ *
+ * Such translations are edited from their imported originals on the user
+ * interface or are imported as customized.
+ */
+define('L10N_UPDATE_CUSTOMIZED', 1);
+
+/**
+ * Implements hook_help().
+ */
+function l10n_update_help($path, $arg) {
+ switch ($path) {
+ case 'admin/config/regional/translate/update':
+ $output = '<p>' . t('Status of interface translations for each of the enabled languages.') . '</p>';
+ $output .= '<p>' . t('If there are available updates you can click on "Update translation" for them to be downloaded and imported now or you can edit the configuration for them to be updated automatically on the <a href="@update-settings">Update settings page</a>', array('@update-settings' => url('admin/config/regional/language/update'))) . '</p>';
+ return $output;
+ break;
+ case 'admin/config/regional/language/update':
+ $output = '<p>' . t('These are the settings for the translation update system. To update your translations now, check out the <a href="@update-admin">Translation update administration page</a>.', array('@update-admin' => url('admin/config/regional/translate/update'))) . '</p>';
+ return $output;
+ break;
+ }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function l10n_update_menu() {
+ $items['admin/config/regional/translate/update'] = array(
+ 'title' => 'Update',
+ 'description' => 'Available updates',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('l10n_update_status_form'),
+ 'access arguments' => array('translate interface'),
+ 'file' => 'l10n_update.admin.inc',
+ 'weight' => 20,
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items['admin/config/regional/translate/check'] = array(
+ 'title' => 'Update',
+ 'description' => 'Available updates',
+ 'page callback' => 'l10n_update_manual_status',
+ 'access arguments' => array('translate interface'),
+ 'file' => 'l10n_update.admin.inc',
+ 'weight' => 20,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['admin/config/regional/language/update'] = array(
+ 'title' => 'Translation updates',
+ 'description' => 'Automatic update configuration',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('l10n_update_admin_settings_form'),
+ 'access arguments' => array('translate interface'),
+ 'file' => 'l10n_update.admin.inc',
+ 'weight' => 20,
+ 'type' => MENU_LOCAL_TASK,
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function l10n_update_theme() {
+ return array(
+ 'l10n_update_last_check' => array(
+ 'variables' => array('last' => NULL),
+ 'file' => 'l10n_update.admin.inc',
+ 'template' => 'l10n_update-translation-last-check',
+ ),
+ 'l10n_update_update_info' => array(
+ 'variables' => array('updates' => array(), 'not_found' => array()),
+ 'file' => 'l10n_update.admin.inc',
+ 'template' => 'l10n_update-translation-update-info',
+ ),
+ );
+}
+/**
+ * Implements hook_menu_alter().
+ */
+function l10n_update_menu_alter(&$menu) {
+ // Redirect l10n_client AJAX callback path for strings.
+ if (module_exists('l10n_client')) {
+ $menu['l10n_client/save']['page callback'] = 'l10n_update_client_save_string';
+ }
+}
+
+/**
+ * Implements hook_cron().
+ *
+ * Check one project/language at a time, download and import if update available
+ */
+function l10n_update_cron() {
+ // Update translations only when an update frequency was set by the admin
+ // and a translatable language was set.
+ // Update tasks are added to the queue here but processed by Drupal's cron
+ // using the cron worker defined in l10n_update_queue_info().
+ if ($frequency = variable_get('l10n_update_check_frequency', '0') && l10n_update_translatable_language_list()) {
+ module_load_include('translation.inc', 'l10n_update');
+ l10n_update_cron_fill_queue();
+ }
+}
+
+/**
+ * Implements hook_cron_queue_info().
+ */
+function l10n_update_cron_queue_info() {
+ $queues['l10n_update'] = array(
+ 'worker callback' => 'l10n_update_worker',
+ 'time' => 30,
+ );
+ return $queues;
+}
+
+/**
+ * Callback: Executes interface translation queue tasks.
+ *
+ * The translation update functions executed here are batch operations which
+ * are also used in translation update batches. The batch functions may need to
+ * be executed multiple times to complete their task, typically this is the
+ * translation import function. When a batch function is not finished, a new
+ * queue task is created and added to the end of the queue. The batch context
+ * data is needed to continue the batch task is stored in the queue with the
+ * queue data.
+ *
+ * @param array $data
+ * Queue data array containing:
+ * - Function name.
+ * - Array of function arguments. Optionally contains the batch context data.
+ *
+ * @see l10n_update_queue_info()
+ */
+function l10n_update_worker($data) {
+ module_load_include('batch.inc', 'l10n_update');
+ list($function, $args) = $data;
+
+ // We execute batch operation functions here to check, download and import the
+ // translation files. Batch functions use a context variable as last argument
+ // which is passed by reference. When a batch operation is called for the
+ // first time a default batch context is created. When called iterative
+ // (usually the batch import function) the batch context is passed through via
+ // the queue and is part of the $data.
+ $last = count($args) - 1;
+ if (!is_array($args[$last]) || !isset($args[$last]['finished'])) {
+ $batch_context = array(
+ 'sandbox' => array(),
+ 'results' => array(),
+ 'finished' => 1,
+ 'message' => '',
+ );
+ }
+ else {
+ $batch_context = $args[$last];
+ unset ($args[$last]);
+ }
+ $args = array_merge($args, array(&$batch_context));
+
+ // Call the batch operation function.
+ call_user_func_array($function, $args);
+
+ // If the batch operation is not finished we create a new queue task to
+ // continue the task. This is typically the translation import task.
+ if ($batch_context['finished'] < 1) {
+ unset($batch_context['strings']);
+ $queue = DrupalQueue::get('l10n_update', TRUE);
+ $queue->createItem(array($function, $args));
+ }
+}
+
+/**
+ * Implements hook_stream_wrappers().
+ */
+function l10n_update_stream_wrappers() {
+ // Load the stream wrapper class if not automatically loaded. This happens
+ // before update.php is executed.
+ if (!class_exists('TranslationsStreamWrapper')) {
+ require_once('includes/locale/TranslationsStreamWrapper.php');
+ }
+
+ $wrappers['translations'] = array(
+ 'name' => t('Translation files'),
+ 'class' => 'TranslationsStreamWrapper',
+ 'description' => t('Translation files.'),
+ 'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
+ );
+
+ return $wrappers;
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function l10n_update_form_alter(&$form, $form_state, $form_id) {
+ switch ($form_id) {
+ case 'locale_translate_edit_form':
+ case 'i18n_string_locale_translate_edit_form':
+ $form['#submit'][] = 'l10n_update_locale_translate_edit_form_submit';
+ break;
+ case 'locale_languages_predefined_form':
+ case 'locale_languages_custom_form':
+ $form['#submit'][] = 'l10n_update_languages_changed_submit';
+ break;
+ case 'locale_languages_delete_form':
+ // A language is being deleted.
+ $form['#submit'][] = 'l10n_update_languages_delete_submit';
+ break;
+ }
+}
+
+/**
+ * Implements hook_modules_enabled().
+ *
+ * Refresh project translation status and get translations if required.
+ */
+function l10n_update_modules_enabled($modules) {
+ $components['module'] = $modules;
+ l10n_update_system_update($components);
+}
+
+/**
+ * Implements hook_modules_disabled().
+ *
+ * Set disabled modules to be ignored when updating translations.
+ */
+function l10n_update_modules_disabled($modules) {
+ if (!variable_get('l10n_update_check_disabled', FALSE)) {
+ db_update('l10n_update_project')
+ ->fields(array(
+ 'status' => 0,
+ ))
+ ->condition('name', $modules)
+ ->execute();
+ }
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ *
+ * Remove data of uninstalled modules from {l10n_update_file} table and
+ * rebuild the projects cache.
+ */
+function l10n_update_modules_uninstalled($modules) {
+ $components['module'] = $modules;
+ l10n_update_system_remove($components);
+}
+
+/**
+ * Implements hook_themes_enabled().
+ *
+ * Refresh project translation status and get translations if required.
+ */
+function l10n_update_themes_enabled($themes) {
+ $components['theme'] = $themes;
+ l10n_update_system_update($components);
+}
+
+/**
+ * Additional submit handler for language forms
+ *
+ * We need to refresh status when a new language is enabled / disabled
+ */
+function l10n_update_languages_changed_submit($form, $form_state) {
+ if (variable_get('l10n_update_import_enabled', TRUE)) {
+ if (empty($form_state['values']['predefined_langcode']) || $form_state['values']['predefined_langcode'] == 'custom') {
+ $langcode = $form_state['values']['langcode'];
+ }
+ else {
+ $langcode = $form_state['values']['predefined_langcode'];
+ }
+
+ // Download and import translations for the newly added language.
+ module_load_include('fetch.inc', 'l10n_update');
+ $options = _l10n_update_default_update_options();
+ $batch = l10n_update_batch_update_build(array(), array($langcode), $options);
+ batch_set($batch);
+ }
+}
+
+/**
+ * Additional submit handler for language deletion form.
+ *
+ * When a language is deleted, the file history of this language is cleared.
+ */
+function l10n_update_languages_delete_submit($form, $form_state) {
+ $langcode = $form_state['values']['langcode'];
+ l10n_update_file_history_delete(array(), $langcode);
+}
+
+/**
+ * Additional submit handler for locale and i18n_string translation edit form.
+ *
+ * Mark locally edited translations as customized.
+ *
+ * @see l10n_update_form_alter()
+ */
+function l10n_update_locale_translate_edit_form_submit($form, &$form_state) {
+ $lid = $form_state['values']['lid'];
+ foreach ($form_state['values']['translations'] as $langcode => $value) {
+ if (!empty($value) && $value != $form_state['complete form']['translations'][$langcode]['#default_value']) {
+ // An update has been made, mark the string as customized.
+ db_update('locales_target')
+ ->fields(array('l10n_status' => L10N_UPDATE_STRING_CUSTOM))
+ ->condition('lid', $lid)
+ ->condition('language', $langcode)
+ ->execute();
+ }
+ }
+}
+
+/**
+ * Menu callback. Saves a string translation coming as POST data.
+ */
+function l10n_update_client_save_string() {
+ global $user, $language;
+
+ if (l10n_client_access()) {
+ if (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['textgroup']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {
+ // Ensure we have this source string before we attempt to save it.
+ // @todo: add actual context support.
+ $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(':source' => $_POST['source'], ':context' => '', ':textgroup' => $_POST['textgroup']))->fetchField();
+
+ if (!empty($lid)) {
+ module_load_include('translation.inc', 'l10n_update');
+ $report = array('skips' => 0, 'additions' => 0, 'updates' => 0, 'deletes' => 0);
+ // @todo: add actual context support.
+ _l10n_update_locale_import_one_string_db($report, $language->language, '', $_POST['source'], $_POST['target'], $_POST['textgroup'], NULL, LOCALE_IMPORT_OVERWRITE, L10N_UPDATE_STRING_CUSTOM);
+ cache_clear_all('locale:', 'cache', TRUE);
+ _locale_invalidate_js($language->language);
+ if (!empty($report['skips'])) {
+ $message = theme('l10n_client_message', array('message' => t('Not saved locally due to invalid HTML content.')));
+ }
+ elseif (!empty($report['additions']) || !empty($report['updates'])) {
+ $message = theme('l10n_client_message', array('message' => t('Translation saved locally.'), 'level' => WATCHDOG_INFO));
+ }
+ elseif (!empty($report['deletes'])) {
+ $message = theme('l10n_client_message', array('message' => t('Translation successfuly removed locally.'), 'level' => WATCHDOG_INFO));
+ }
+ else {
+ $message = theme('l10n_client_message', array('message' => t('Unknown error while saving translation locally.')));
+ }
+
+ // Submit to remote server if enabled.
+ if (variable_get('l10n_client_use_server', FALSE) && user_access('submit translations to localization server') && ($_POST['textgroup'] == 'default')) {
+ if (!empty($user->data['l10n_client_key'])) {
+ $remote_result = l10n_client_submit_translation($language->language, $_POST['source'], $_POST['target'], $user->data['l10n_client_key'], l10n_client_user_token($user));
+ $message .= theme('l10n_client_message', array('message' => $remote_result[1], 'level' => $remote_result[0] ? WATCHDOG_INFO : WATCHDOG_ERROR));
+ }
+ else {
+ $server_url = variable_get('l10n_client_server', 'http://localize.drupal.org');
+ $user_edit_url = url('user/' . $user->uid . '/edit', array('absolute' => TRUE));
+ $message .= theme('l10n_client_message', array('message' => t('You could share your work with !l10n_server if you set your API key at !user_link.', array('!l10n_server' => l($server_url, $server_url), '!user_link' => l($user_edit_url, 'user/' . $user->uid . '/edit'))), 'level' => WATCHDOG_WARNING));
+ }
+ }
+ }
+ else {
+ $message = theme('l10n_client_message', array('message' => t('Not saved due to source string missing.')));
+ }
+ }
+ else {
+ $message = theme('l10n_client_message', array('message' => t('Not saved due to missing form values.')));
+ }
+ }
+ else {
+ $message = theme('l10n_client_message', array('message' => t('Not saved due to insufficient permissions.')));
+ }
+ drupal_json_output($message);
+ exit;
+}
+
+/**
+ * Imports translations when new modules or themes are installed.
+ *
+ * This function will start a batch to import translations for the added
+ * components.
+ *
+ * @param array $components
+ * An array of arrays of component (theme and/or module) names to import
+ * translations for, indexed by type.
+ */
+function l10n_update_system_update(array $components) {
+ $components += array('module' => array(), 'theme' => array());
+ $list = array_merge($components['module'], $components['theme']);
+
+ // Skip running the translation imports if in the installer,
+ // because it would break out of the installer flow. We have
+ // built-in support for translation imports in the installer.
+ if (!drupal_installation_attempted() && l10n_update_translatable_language_list() && variable_get('l10n_update_import_enabled', TRUE)) {
+ module_load_include('compare.inc', 'l10n_update');
+
+ // Update the list of translatable projects and start the import batch.
+ // Only when new projects are added the update batch will be triggered. Not
+ // each enabled module will introduce a new project. E.g. sub modules.
+ $projects = array_keys(l10n_update_build_projects());
+ if ($list = array_intersect($list, $projects)) {
+ module_load_include('fetch.inc', 'l10n_update');
+ // Get translation status of the projects, download and update translations.
+ $options = _l10n_update_default_update_options();
+ $batch = l10n_update_batch_update_build($list, array(), $options);
+ batch_set($batch);
+ }
+ }
+}
+
+/**
+ * Delete translation history of modules and themes.
+ *
+ * Only the translation history is removed, not the source strings or
+ * translations. This is not possible because strings are shared between
+ * modules and we have no record of which string is used by which module.
+ *
+ * @param array $components
+ * An array of arrays of component (theme and/or module) names to import
+ * translations for, indexed by type.
+ */
+function l10n_update_system_remove($components) {
+ $components += array('module' => array(), 'theme' => array());
+ $list = array_merge($components['module'], $components['theme']);
+ if ($language_list = l10n_update_translatable_language_list()) {
+ module_load_include('compare.inc', 'l10n_update');
+ module_load_include('bulk.inc', 'l10n_update');
+
+ // Only when projects are removed, the translation files and records will be
+ // deleted. Not each disabled module will remove a project. E.g. sub modules.
+ $projects = array_keys(l10n_update_get_projects());
+ if ($list = array_intersect($list, $projects)) {
+ l10n_update_file_history_delete($list);
+
+ // Remove translation files.
+ l10n_update_delete_translation_files($list, array());
+
+ // Remove translatable projects.
+ // Followup issue http://drupal.org/node/1842362 to replace the
+ // {l10n_update_project} table. Then change this to a function call.
+ db_delete('l10n_update_project')
+ ->condition('name', $list)
+ ->execute();
+
+ // Clear the translation status.
+ l10n_update_status_delete_projects($list);
+ }
+
+ }
+}
+
+/**
+ * Gets current translation status from the {l10n_update_file} table.
+ *
+ * @return array
+ * Array of translation file objects.
+ */
+function l10n_update_get_file_history() {
+ $history = &drupal_static(__FUNCTION__, array());
+
+ if (empty($history)) {
+ // Get file history from the database.
+ $result = db_query('SELECT project, language, filename, version, uri, timestamp, last_checked FROM {l10n_update_file}');
+ foreach ($result as $file) {
+ $file->langcode = $file->language;
+ $file->type = $file->timestamp ? L10N_UPDATE_CURRENT : '';
+ $history[$file->project][$file->langcode] = $file;
+ }
+ }
+ return $history;
+}
+
+/**
+ * Updates the {locale_file} table.
+ *
+ * @param object $file
+ * Object representing the file just imported.
+ *
+ * @return integer
+ * FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED.
+ *
+ * @see drupal_write_record()
+ */
+function l10n_update_update_file_history($file) {
+ // Update or write new record.
+ if (db_query("SELECT project FROM {l10n_update_file} WHERE project = :project AND language = :langcode", array(':project' => $file->project, ':langcode' => $file->langcode))->fetchField()) {
+ $update = array('project', 'language');
+ }
+ else {
+ $update = array();
+ }
+ $file->language = $file->langcode;
+ $result = drupal_write_record('l10n_update_file', $file, $update);
+ // The file history has changed, flush the static cache now.
+ // @todo Can we make this more fine grained?
+ drupal_static_reset('l10n_update_get_file_history');
+ return $result;
+}
+
+/**
+ * Deletes the history of downloaded translations.
+ *
+ * @param array $projects
+ * Project name(s) to be deleted from the file history. If both project(s) and
+ * language code(s) are specified the conditions will be ANDed.
+ * @param array $langcode
+ * Language code(s) to be deleted from the file history.
+ */
+function l10n_update_file_history_delete($projects = array(), $langcodes = array()) {
+ $query = db_delete('l10n_update_file');
+ if (!empty($projects)) {
+ $query->condition('project', $projects);
+ }
+ if (!empty($langcodes)) {
+ $query->condition('language', $langcodes);
+ }
+ $query->execute();
+}
+
+/**
+ * Gets the current translation status.
+ *
+ * @todo What is 'translation status'?
+ */
+function l10n_update_get_status($projects = NULL, $langcodes = NULL) {
+ $result = array();
+ $status = variable_get('l10n_update_translation_status', array());
+ module_load_include('translation.inc', 'l10n_update');
+ $projects = $projects ? $projects : array_keys(l10n_update_get_projects());
+ $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
+
+ // Get the translation status of each project-language combination. If no
+ // status was stored, a new translation source is created.
+ foreach ($projects as $project) {
+ foreach ($langcodes as $langcode) {
+ if (isset($status[$project][$langcode])) {
+ $result[$project][$langcode] = $status[$project][$langcode];
+ }
+ else {
+ $sources = l10n_update_build_sources(array($project), array($langcode));
+ if (isset($sources[$project][$langcode])) {
+ $result[$project][$langcode] = $sources[$project][$langcode];
+ }
+ }
+ }
+ }
+ return $result;
+}
+
+/**
+ * Saves the status of translation sources in static cache.
+ *
+ * @param string $project
+ * Machine readable project name.
+ * @param string $langcode
+ * Language code.
+ * @param string $type
+ * Type of data to be stored.
+ * @param array $data
+ * File object also containing timestamp when the translation is last updated.
+ */
+function l10n_update_status_save($project, $langcode, $type, $data) {
+ // Followup issue: http://drupal.org/node/1842362
+ // Split status storage per module/language and expire individually. This will
+ // improve performance for large sites.
+
+ // Load the translation status or build it if not already available.
+ module_load_include('translation.inc', 'l10n_update');
+ $status = l10n_update_get_status();
+ if (empty($status)) {
+ $projects = l10n_update_get_projects(array($project));
+ if (isset($projects[$project])) {
+ $status[$project][$langcode] = l10n_update_source_build($projects[$project], $langcode);
+ }
+ }
+
+ // Merge the new status data with the existing status.
+ if (isset($status[$project][$langcode])) {
+ switch ($type) {
+ case L10N_UPDATE_REMOTE:
+ case L10N_UPDATE_LOCAL:
+ // Add the source data to the status array.
+ $status[$project][$langcode]->files[$type] = $data;
+ // Check if this translation is the most recent one. Set timestamp and
+ // data type of the most recent translation source.
+ if (isset($data->timestamp) && $data->timestamp) {
+ if ($data->timestamp > $status[$project][$langcode]->timestamp) {
+ $status[$project][$langcode]->timestamp = $data->timestamp;
+ $status[$project][$langcode]->last_checked = REQUEST_TIME;
+ $status[$project][$langcode]->type = $type;
+ }
+ }
+ break;
+ case L10N_UPDATE_CURRENT:
+ $data->last_checked = REQUEST_TIME;
+ $status[$project][$langcode]->timestamp = $data->timestamp;
+ $status[$project][$langcode]->last_checked = $data->last_checked;
+ $status[$project][$langcode]->type = $type;
+ l10n_update_update_file_history($data);
+ break;
+ }
+
+ variable_set('l10n_update_translation_status', $status);
+ variable_set('l10n_update_last_check', REQUEST_TIME);
+ }
+}
+
+/**
+ * Delete language entries from the status cache.
+ *
+ * @param array $langcodes
+ * Language code(s) to be deleted from the cache.
+ */
+function l10n_update_status_delete_languages($langcodes) {
+ if ($status = l10n_update_get_status()) {
+ foreach ($status as $project => $languages) {
+ foreach ($languages as $langcode => $source) {
+ if (in_array($langcode, $langcodes)) {
+ unset($status[$project][$langcode]);
+ }
+ }
+ }
+ variable_set('l10n_update_translation_status', $status);
+ }
+}
+
+/**
+ * Delete project entries from the status cache.
+ *
+ * @param array $projects
+ * Project name(s) to be deleted from the cache.
+ */
+function l10n_update_status_delete_projects($projects) {
+ $status = l10n_update_get_status();
+
+ foreach ($status as $project => $languages) {
+ if (in_array($project, $projects)) {
+ unset($status[$project]);
+ }
+ }
+ variable_set('l10n_update_translation_status', $status);
+}
+
+/**
+ * Returns list of translatable languages.
+ *
+ * @return array
+ * Array of enabled languages keyed by language name. English is omitted.
+ */
+function l10n_update_translatable_language_list() {
+ $languages = locale_language_list('name');
+ unset($languages['en']);
+ return $languages;
+}
+
+/**
+ * Clear the translation status cache.
+ */
+function l10n_update_clear_status() {
+ variable_del('l10n_update_translation_status');
+ variable_del('l10n_update_last_check');
+}
+
+/**
+ * Checks whether remote translation sources are used.
+ *
+ * @return bool
+ * Returns TRUE if remote translations sources should be taken into account
+ * when checking or importing translation files, FALSE otherwise.
+ */
+function l10n_update_use_remote_source() {
+ return variable_get('l10n_update_check_mode', L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL) == L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL;
+}
diff --git a/sites/all/modules/l10n_update/l10n_update.translation.inc b/sites/all/modules/l10n_update/l10n_update.translation.inc
new file mode 100644
index 000000000..05f82cca3
--- /dev/null
+++ b/sites/all/modules/l10n_update/l10n_update.translation.inc
@@ -0,0 +1,570 @@
+<?php
+
+/**
+ * @file
+ * Common API for interface translation.
+ */
+
+/**
+ * Comparison result of source files timestamps.
+ *
+ * Timestamp of source 1 is less than the timestamp of source 2.
+ * @see _l10n_update_source_compare()
+ */
+define('L10N_UPDATE_SOURCE_COMPARE_LT', -1);
+
+/**
+ * Comparison result of source files timestamps.
+ *
+ * Timestamp of source 1 is equal to the timestamp of source 2.
+ * @see _l10n_update_source_compare()
+ */
+define('L10N_UPDATE_SOURCE_COMPARE_EQ', 0);
+
+/**
+ * Comparison result of source files timestamps.
+ *
+ * Timestamp of source 1 is greater than the timestamp of source 2.
+ * @see _l10n_update_source_compare()
+ */
+define('L10N_UPDATE_SOURCE_COMPARE_GT', 1);
+
+/**
+ * Get array of projects which are available for interface translation.
+ *
+ * This project data contains all projects which will be checked for available
+ * interface translations.
+ *
+ * For full functionality this function depends on Update module.
+ * When Update module is enabled the project data will contain the most recent
+ * module status; both in enabled status as in version. When Update module is
+ * disabled this function will return the last known module state. The status
+ * will only be updated once Update module is enabled.
+ *
+ * @params array $project_names
+ * Array of names of the projects to get.
+ *
+ * @return array
+ * Array of project data for translation update.
+ *
+ * @see l10n_update_build_projects()
+ */
+function l10n_update_get_projects($project_names = array()) {
+ $projects = &drupal_static(__FUNCTION__, array());
+
+ if (empty($projects)) {
+ // Get project data from the database.
+ $result = db_query('SELECT name, project_type, core, version, l10n_path as server_pattern, status FROM {l10n_update_project}');
+ // http://drupal.org/node/1777106 is a follow-up issue to make the check for
+ // possible out-of-date project information more robust.
+ if ($result->rowCount() == 0) {
+ module_load_include('compare.inc', 'l10n_update');
+ // At least the core project should be in the database, so we build the
+ // data if none are found.
+ l10n_update_build_projects();
+ $result = db_query('SELECT name, project_type, core, version, l10n_path as server_pattern, status FROM {l10n_update_project}');
+ }
+
+ foreach ($result as $project) {
+ $projects[$project->name] = $project;
+ }
+ }
+
+ // Return the requested project names or all projects.
+ if ($project_names) {
+ return array_intersect_key($projects, drupal_map_assoc($project_names));
+ }
+ return $projects;
+}
+
+/**
+ * Clears the projects cache.
+ */
+function l10n_update_clear_cache_projects() {
+ drupal_static('l10n_update_get_projects', array());
+}
+
+/**
+ * Loads cached translation sources containing current translation status.
+ *
+ * @param array $projects
+ * Array of project names. Defaults to all translatable projects.
+ * @param array $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ *
+ * @return array
+ * Array of source objects. Keyed with <project name>:<language code>.
+ *
+ * @see l10n_update_source_build()
+ */
+function l10n_update_load_sources($projects = NULL, $langcodes = NULL) {
+ $sources = array();
+ $projects = $projects ? $projects : array_keys(l10n_update_get_projects());
+ $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
+
+ // Load source data from l10n_update_status cache.
+ $status = l10n_update_get_status();
+
+ // Use only the selected projects and languages for update.
+ foreach($projects as $project) {
+ foreach ($langcodes as $langcode) {
+ $sources[$project][$langcode] = isset($status[$project][$langcode]) ? $status[$project][$langcode] : NULL;
+ }
+ }
+ return $sources;
+}
+
+/**
+ * Build translation sources.
+ *
+ * @param array $projects
+ * Array of project names. Defaults to all translatable projects.
+ * @param array $langcodes
+ * Array of language codes. Defaults to all translatable languages.
+ *
+ * @return array
+ * Array of source objects. Keyed by project name and language code.
+ *
+ * @see l10n_update_source_build()
+ */
+function l10n_update_build_sources($projects = array(), $langcodes = array()) {
+ $sources = array();
+ $projects = l10n_update_get_projects($projects);
+ $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
+
+ foreach ($projects as $project) {
+ foreach ($langcodes as $langcode) {
+ $source = l10n_update_source_build($project, $langcode);
+ $sources[$source->name][$source->langcode] = $source;
+ }
+ }
+ return $sources;
+}
+
+/**
+ * Checks whether a po file exists in the local filesystem.
+ *
+ * It will search in the directory set in the translation source. Which defaults
+ * to the "translations://" stream wrapper path. The directory may contain any
+ * valid stream wrapper.
+ *
+ * The "local" files property of the source object contains the definition of a
+ * po file we are looking for. The file name defaults to
+ * %project-%release.%language.po. Per project this value can be overridden
+ * using the server_pattern directive in the module's .info.yml file or by using
+ * hook_l10n_update_projects_alter().
+ *
+ * @param object $source
+ * Translation source object.
+ *
+ * @return stdClass
+ * Source file object of the po file, updated with:
+ * - "uri": File name and path.
+ * - "timestamp": Last updated time of the po file.
+ * FALSE if the file is not found.
+ *
+ * @see l10n_update_source_build()
+ */
+function l10n_update_source_check_file($source) {
+ if (isset($source->files[L10N_UPDATE_LOCAL])) {
+ $source_file = $source->files[L10N_UPDATE_LOCAL];
+ $directory = $source_file->directory;
+ $filename = '/' . preg_quote($source_file->filename) . '$/';
+
+ if ($files = file_scan_directory($directory, $filename, array('key' => 'name', 'recurse' => FALSE))) {
+ $file = current($files);
+ $source_file->uri = $file->uri;
+ $source_file->timestamp = filemtime($file->uri);
+ return $source_file;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Builds abstract translation source.
+ *
+ * @param object $project
+ * Project object.
+ * @param string $langcode
+ * Language code.
+ * @param string $filename
+ * File name of translation file. May contain placeholders.
+ *
+ * @return object
+ * Source object:
+ * - "project": Project name.
+ * - "name": Project name (inherited from project).
+ * - "language": Language code.
+ * - "core": Core version (inherited from project).
+ * - "version": Project version (inherited from project).
+ * - "project_type": Project type (inherited from project).
+ * - "files": Array of file objects containing properties of local and remote
+ * translation files.
+ * Other processes can add the following properties:
+ * - "type": Most recent translation source found. L10N_UPDATE_REMOTE and
+ * L10N_UPDATE_LOCAL indicate available new translations,
+ * L10N_UPDATE_CURRENT indicate that the current translation is them
+ * most recent. "type" sorresponds with a key of the "files" array.
+ * - "timestamp": The creation time of the "type" translation (file).
+ * - "last_checked": The time when the "type" translation was last checked.
+ * The "files" array can hold file objects of type:
+ * L10N_UPDATE_LOCAL, L10N_UPDATE_REMOTE and
+ * L10N_UPDATE_CURRENT. Each contains following properties:
+ * - "type": The object type (L10N_UPDATE_LOCAL,
+ * L10N_UPDATE_REMOTE, etc. see above).
+ * - "project": Project name.
+ * - "langcode": Language code.
+ * - "version": Project version.
+ * - "uri": Local or remote file path.
+ * - "directory": Directory of the local po file.
+ * - "filename": File name.
+ * - "timestamp": Timestamp of the file.
+ * - "keep": TRUE to keep the downloaded file.
+ */
+function l10n_update_source_build($project, $langcode, $filename = NULL) {
+ // Create a source object with data of the project object.
+ $source = clone $project;
+ $source->project = $project->name;
+ $source->langcode = $langcode;
+ $source->type = '';
+ $source->timestamp = 0;
+ $source->last_checked = 0;
+
+ $filename = $filename ? $filename : variable_get('l10n_update_default_filename', L10N_UPDATE_DEFAULT_FILE_NAME);
+
+ // If the server_pattern contains a remote file path we will check for a
+ // remote file. The local version of this file will only be checked if a
+ // translations directory has been defined. If the server_pattern is a local
+ // file path we will only check for a file in the local file system.
+ $files = array();
+ if (_l10n_update_file_is_remote($source->server_pattern)) {
+ $files[L10N_UPDATE_REMOTE] = (object) array(
+ 'project' => $project->name,
+ 'langcode' => $langcode,
+ 'version' => $project->version,
+ 'type' => L10N_UPDATE_REMOTE,
+ 'filename' => l10n_update_build_server_pattern($source, basename($source->server_pattern)),
+ 'uri' => l10n_update_build_server_pattern($source, $source->server_pattern),
+ );
+ $files[L10N_UPDATE_LOCAL] = (object) array(
+ 'project' => $project->name,
+ 'langcode' => $langcode,
+ 'version' => $project->version,
+ 'type' => L10N_UPDATE_LOCAL,
+ 'filename' => l10n_update_build_server_pattern($source, $filename),
+ 'directory' => 'translations://',
+ );
+ $files[L10N_UPDATE_LOCAL]->uri = $files[L10N_UPDATE_LOCAL]->directory . $files[L10N_UPDATE_LOCAL]->filename;
+ }
+ else {
+ $files[L10N_UPDATE_LOCAL] = (object) array(
+ 'project' => $project->name,
+ 'langcode' => $langcode,
+ 'version' => $project->version,
+ 'type' => L10N_UPDATE_LOCAL,
+ 'filename' => l10n_update_build_server_pattern($source, basename($source->server_pattern)),
+ 'directory' => l10n_update_build_server_pattern($source, drupal_dirname($source->server_pattern)),
+ );
+ $files[L10N_UPDATE_LOCAL]->uri = $files[L10N_UPDATE_LOCAL]->directory . '/' . $files[L10N_UPDATE_LOCAL]->filename;
+ }
+ $source->files = $files;
+
+ // If this project+language is already translated, we add its status and
+ // update the current translation timestamp and last_updated time. If the
+ // project+language is not translated before, create a new record.
+ $history = l10n_update_get_file_history();
+ if (isset($history[$project->name][$langcode]) && $history[$project->name][$langcode]->timestamp) {
+ $source->files[L10N_UPDATE_CURRENT] = $history[$project->name][$langcode];
+ $source->type = L10N_UPDATE_CURRENT;
+ $source->timestamp = $history[$project->name][$langcode]->timestamp;
+ $source->last_checked = $history[$project->name][$langcode]->last_checked;
+ }
+ else {
+ l10n_update_update_file_history($source);
+ }
+
+ return $source;
+}
+
+/**
+ * Build path to translation source, out of a server path replacement pattern.
+ *
+ * @param object $project
+ * Project object containing data to be inserted in the template.
+ * @param string $template
+ * String containing placeholders. Available placeholders:
+ * - "%project": Project name.
+ * - "%release": Project version.
+ * - "%core": Project core version.
+ * - "%language": Language code.
+ *
+ * @return string
+ * String with replaced placeholders.
+ */
+function l10n_update_build_server_pattern($project, $template) {
+ $variables = array(
+ '%project' => $project->name,
+ '%release' => $project->version,
+ '%core' => $project->core,
+ '%language' => isset($project->langcode) ? $project->langcode : '%language',
+ );
+ return strtr($template, $variables);
+}
+
+/**
+ * Populate a queue with project to check for translation updates.
+ */
+function l10n_update_cron_fill_queue() {
+ $updates = array();
+
+ // Determine which project+language should be updated.
+ $last = REQUEST_TIME - variable_get('l10n_update_check_frequency', '0') * 3600 * 24;
+ $query = db_select('l10n_update_file', 'f');
+ $query->join('l10n_update_project', 'p', 'p.name = f.project');
+ $query->condition('f.last_checked', $last, '<');
+ $query->fields('f', array('project', 'language'));
+ // Only currently installed / enabled components should be checked for.
+ $query->condition('p.status', 1);
+ $files = $query->execute()->fetchAll();
+ foreach ($files as $file) {
+ $updates[$file->project][] = $file->language;
+
+ // Update the last_checked timestamp of the project+language that will
+ // be checked for updates.
+ db_update('l10n_update_file')
+ ->fields(array('last_checked' => REQUEST_TIME))
+ ->condition('project', $file->project)
+ ->condition('language', $file->language)
+ ->execute();
+ }
+
+ // For each project+language combination a number of tasks are added to
+ // the queue.
+ if ($updates) {
+ module_load_include('fetch.inc', 'l10n_update');
+ $options = _l10n_update_default_update_options();
+ $queue = DrupalQueue::get('l10n_update', TRUE);
+
+ foreach ($updates as $project => $languages) {
+ $batch = l10n_update_batch_update_build(array($project), $languages, $options);
+ foreach ($batch['operations'] as $item) {
+ $queue->createItem($item);
+ }
+ }
+ }
+}
+
+/**
+ * Determine if a file is a remote file.
+ *
+ * @param string $uri
+ * The URI or URI pattern of the file.
+ *
+ * @return boolean
+ * TRUE if the $uri is a remote file.
+ */
+function _l10n_update_file_is_remote($uri) {
+ $scheme = file_uri_scheme($uri);
+ if ($scheme) {
+ return !drupal_realpath($scheme . '://');
+ }
+ return FALSE;
+}
+
+/**
+ * Compare two update sources, looking for the newer one.
+ *
+ * The timestamp property of the source objects are used to determine which is
+ * the newer one.
+ *
+ * @param object $source1
+ * Source object of the first translation source.
+ * @param object $source2
+ * Source object of available update.
+ *
+ * @return integer
+ * - "L10N_UPDATE_SOURCE_COMPARE_LT": $source1 < $source2 OR $source1
+ * is missing.
+ * - "L10N_UPDATE_SOURCE_COMPARE_EQ": $source1 == $source2 OR both
+ * $source1 and $source2 are missing.
+ * - "L10N_UPDATE_SOURCE_COMPARE_EQ": $source1 > $source2 OR $source2
+ * is missing.
+ */
+function _l10n_update_source_compare($source1, $source2) {
+ if (isset($source1->timestamp) && isset($source2->timestamp)) {
+ if ($source1->timestamp == $source2->timestamp) {
+ return L10N_UPDATE_SOURCE_COMPARE_EQ;
+ }
+ else {
+ return $source1->timestamp > $source2->timestamp ? L10N_UPDATE_SOURCE_COMPARE_GT : L10N_UPDATE_SOURCE_COMPARE_LT;
+ }
+ }
+ elseif (isset($source1->timestamp) && !isset($source2->timestamp)) {
+ return L10N_UPDATE_SOURCE_COMPARE_GT;
+ }
+ elseif (!isset($source1->timestamp) && isset($source2->timestamp)) {
+ return L10N_UPDATE_SOURCE_COMPARE_LT;
+ }
+ else {
+ return L10N_UPDATE_SOURCE_COMPARE_EQ;
+ }
+}
+
+/**
+ * Returns default import options for translation update.
+ *
+ * @return array
+ * Array of translation import options.
+ */
+function _l10n_update_default_update_options() {
+ $options = array(
+ 'customized' => L10N_UPDATE_NOT_CUSTOMIZED,
+ 'finish_feedback' => TRUE,
+ 'use_remote' => l10n_update_use_remote_source(),
+ );
+
+ switch (variable_get('l10n_update_import_mode', LOCALE_IMPORT_KEEP)) {
+ case LOCALE_IMPORT_OVERWRITE:
+ $options['overwrite_options'] = array(
+ 'customized' => TRUE,
+ 'not_customized' => TRUE,
+ );
+ break;
+ case L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED:
+ $options['overwrite_options'] = array(
+ 'customized' => FALSE,
+ 'not_customized' => TRUE,
+ );
+ break;
+ case LOCALE_IMPORT_KEEP:
+ $options['overwrite_options'] = array(
+ 'customized' => FALSE,
+ 'not_customized' => FALSE,
+ );
+ break;
+ }
+
+ return $options;
+}
+
+/**
+ * Import one string into the database.
+ *
+ * @param $report
+ * Report array summarizing the number of changes done in the form:
+ * array(inserts, updates, deletes).
+ * @param $langcode
+ * Language code to import string into.
+ * @param $context
+ * The context of this string.
+ * @param $source
+ * Source string.
+ * @param $translation
+ * Translation to language specified in $langcode.
+ * @param $textgroup
+ * Name of textgroup to store translation in.
+ * @param $location
+ * Location value to save with source string.
+ * @param $mode
+ * Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
+ * @param $status
+ * Status of translation if created: L10N_UPDATE_STRING_DEFAULT or L10N_UPDATE_STRING_CUSTOM
+ * @param $plid
+ * Optional plural ID to use.
+ * @param $plural
+ * Optional plural value to use.
+ * @return
+ * The string ID of the existing string modified or the new string added.
+ */
+function _l10n_update_locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $textgroup, $location, $mode, $status = L10N_UPDATE_NOT_CUSTOMIZED, $plid = 0, $plural = 0) {
+ $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(':source' => $source, ':context' => $context, ':textgroup' => $textgroup))->fetchField();
+
+ if (!empty($translation)) {
+ // Skip this string unless it passes a check for dangerous code.
+ // Text groups other than default still can contain HTML tags
+ // (i.e. translatable blocks).
+ if ($textgroup == "default" && !locale_string_is_safe($translation)) {
+ $report['skips']++;
+ $lid = 0;
+ watchdog('locale', 'Disallowed HTML detected. String not imported: %string', array('%string' => $translation), WATCHDOG_WARNING);
+ }
+ elseif ($lid) {
+ // We have this source string saved already.
+ db_update('locales_source')
+ ->fields(array(
+ 'location' => $location,
+ ))
+ ->condition('lid', $lid)
+ ->execute();
+
+ $exists = db_query("SELECT lid, l10n_status FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchObject();
+
+ if (!$exists) {
+ // No translation in this language.
+ db_insert('locales_target')
+ ->fields(array(
+ 'lid' => $lid,
+ 'language' => $langcode,
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural,
+ ))
+ ->execute();
+
+ $report['additions']++;
+ }
+ elseif (($exists->l10n_status == L10N_UPDATE_NOT_CUSTOMIZED && $mode == L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED) || $mode == LOCALE_IMPORT_OVERWRITE) {
+ // Translation exists, only overwrite if instructed.
+ db_update('locales_target')
+ ->fields(array(
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural,
+ ))
+ ->condition('language', $langcode)
+ ->condition('lid', $lid)
+ ->execute();
+
+ $report['updates']++;
+ }
+ }
+ else {
+ // No such source string in the database yet.
+ $lid = db_insert('locales_source')
+ ->fields(array(
+ 'location' => $location,
+ 'source' => $source,
+ 'context' => (string) $context,
+ 'textgroup' => $textgroup,
+ ))
+ ->execute();
+
+ db_insert('locales_target')
+ ->fields(array(
+ 'lid' => $lid,
+ 'language' => $langcode,
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural,
+ 'l10n_status' => $status,
+ ))
+ ->execute();
+
+ $report['additions']++;
+ }
+ }
+ elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+ // Empty translation, remove existing if instructed.
+ db_delete('locales_target')
+ ->condition('language', $langcode)
+ ->condition('lid', $lid)
+ ->condition('plid', $plid)
+ ->condition('plural', $plural)
+ ->execute();
+
+ $report['deletes']++;
+ }
+
+ return $lid;
+}
diff --git a/sites/all/modules/l10n_update/tests/L10nUpdateCronTest.test b/sites/all/modules/l10n_update/tests/L10nUpdateCronTest.test
new file mode 100644
index 000000000..2759286d8
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/L10nUpdateCronTest.test
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @file
+ * Contains L10nUpdateCronTest.
+ */
+
+/**
+ * Tests for translation update using cron.
+ */
+class L10nUpdateCronTest extends L10nUpdateTestBase {
+
+ protected $batch_output = array();
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Update translations using cron',
+ 'description' => 'Tests for using cron to update project interface translations.',
+ 'group' => 'Localization Update',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
+ $this->drupalLogin($admin_user);
+ $this->addLanguage('de');
+ }
+
+ /**
+ * Tests interface translation update using cron.
+ */
+ function testUpdateCron() {
+ // Set a flag to let the l10n_update_test module replace the project data
+ // with a set of test projects.
+ variable_set('l10n_update_test_projects_alter', TRUE);
+
+ // Setup local and remote translations files.
+ $this->setTranslationFiles();
+ variable_set('l10n_update_default_filename', '%project-%release.%language._po');
+
+ // Update translations using batch to ensure a clean test starting point.
+ $this->drupalGet('admin/config/regional/translate/check');
+ $this->drupalPost('admin/config/regional/translate/update', array(), t('Update translations'));
+
+ // Store translation status for comparison.
+ $initial_history = l10n_update_get_file_history();
+
+ // Prepare for test: Simulate new translations being available.
+ // Change the last updated timestamp of a translation file.
+ $contrib_module_two_uri = 'public://local/contrib_module_two-7.x-2.0-beta4.de._po';
+ touch(drupal_realpath($contrib_module_two_uri), REQUEST_TIME);
+
+ // Prepare for test: Simulate that the file has not been checked for a long
+ // time. Set the last_check timestamp to zero.
+ $query = db_update('l10n_update_file');
+ $query->fields(array('last_checked' => 0));
+ $query->condition('project', 'contrib_module_two');
+ $query->condition('language', 'de');
+ $query->execute();
+
+ // Test: Disable cron update and verify that no tasks are added to the
+ // queue.
+ $edit = array(
+ 'l10n_update_check_frequency' => '0',
+ );
+ $this->drupalPost('admin/config/regional/language/update', $edit, t('Save configuration'));
+
+ // Execute l10n_update cron taks to add tasks to the queue.
+ l10n_update_cron();
+
+ // Check whether no tasks are added to the queue.
+ $queue = DrupalQueue::get('l10n_update', TRUE);
+ $this->assertEqual($queue->numberOfItems(), 0, 'Queue is empty');
+
+ // Test: Enable cron update and check if update tasks are added to the
+ // queue.
+ // Set cron update to Weekly.
+ $edit = array(
+ 'l10n_update_check_frequency' => '7',
+ );
+ $this->drupalPost('admin/config/regional/language/update', $edit, t('Save configuration'));
+
+ // Execute l10n_update cron task to add tasks to the queue.
+ l10n_update_cron();
+
+ // Check whether tasks are added to the queue.
+ $queue = DrupalQueue::get('l10n_update', TRUE);
+ $this->assertEqual($queue->numberOfItems(), 3, 'Queue holds tasks for one project.');
+ $item = $queue->claimItem();
+ $queue->releaseItem($item);
+ $this->assertEqual($item->data[1][0], 'contrib_module_two', 'Queue holds tasks for contrib module one.');
+
+ // Test: Run cron for a second time and check if tasks are not added to
+ // the queue twice.
+ l10n_update_cron();
+
+ // Check whether no more tasks are added to the queue.
+ $queue = DrupalQueue::get('l10n_update', TRUE);
+ $this->assertEqual($queue->numberOfItems(), 3, 'Queue holds tasks for one project.');
+
+ // Ensure last checked is updated to a greater time than the initial value.
+ sleep(1);
+ // Test: Execute cron and check if tasks are executed correctly.
+ // Run cron to process the tasks in the queue.
+ $this->drupalGet('admin/reports/status/run-cron');
+
+ drupal_static_reset('l10n_update_get_file_history');
+ $history = l10n_update_get_file_history();
+ $initial = $initial_history['contrib_module_two']['de'];
+ $current = $history['contrib_module_two']['de'];
+ $this->assertTrue($current->timestamp > $initial->timestamp, 'Timestamp is updated');
+ $this->assertTrue($current->last_checked > $initial->last_checked, 'Last checked is updated');
+ }
+}
diff --git a/sites/all/modules/l10n_update/tests/L10nUpdateInterfaceTest.test b/sites/all/modules/l10n_update/tests/L10nUpdateInterfaceTest.test
new file mode 100644
index 000000000..4ce23ab57
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/L10nUpdateInterfaceTest.test
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Contains L10nUpdateInterfaceTest.
+ */
+
+/**
+ * Tests for the l10n_update status user interfaces.
+ */
+class L10nUpdateInterfaceTest extends L10nUpdateTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Update translations user interface',
+ 'description' => 'Tests for the user interface of project interface translations.',
+ 'group' => 'Localization Update',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
+ $this->drupalLogin($admin_user);
+ }
+
+ /**
+ * Tests the user interfaces of the interface translation update system.
+ *
+ * Testing the Available updates summary on the side wide status page and the
+ * Avaiable translation updates page.
+ */
+ function testInterface() {
+ // Enable the module this test uses for its translations.
+ module_enable(array('l10n_update_test_translate'));
+
+ // No language added.
+ // Check status page and Available translation updates page.
+ $this->drupalGet('admin/reports/status');
+ $this->assertNoText(t('Translation update status'), 'No status message');
+
+ $this->drupalGet('admin/config/regional/translate/update');
+ $this->assertRaw(t('No translatable languages available. <a href="@add_language">Add a language</a> first.', array('@add_language' => url('admin/config/regional/language'))), 'Language message');
+
+ // Add German language.
+ $this->addLanguage('de');
+
+ // Drupal core is probably in 7.x, but tests may also be executed with
+ // stable releases. As this is an uncontrolled factor in the test, we will
+ // mark Drupal core as translated and continue with the prepared modules.
+ $status = l10n_update_get_status();
+ $status['drupal']['de']->type = 'current';
+ variable_set('l10n_update_translation_status', $status);
+
+ // One language added, all translations up to date.
+ $this->drupalGet('admin/reports/status');
+ $this->assertText(t('Translation update status'), 'Status message');
+ $this->assertText(t('Up to date'), 'Translations up to date');
+ $this->drupalGet('admin/config/regional/translate/update');
+ $this->assertText(t('All translations up to date.'), 'Translations up to date');
+
+ // Set l10n_update_test_translate module to have a local translation available.
+ $status = l10n_update_get_status();
+ $status['l10n_update_test_translate']['de']->type = 'local';
+ variable_set('l10n_update_translation_status', $status);
+
+ // Check if updates are available for German.
+ $this->drupalGet('admin/reports/status');
+ $this->assertText(t('Translation update status'), 'Status message');
+ $this->assertRaw(t('Updates available for: @languages. See the <a href="@updates">Available translation updates</a> page for more information.', array('@languages' => t('German'), '@updates' => url('admin/config/regional/translate/update'))), 'Updates available message');
+ $this->drupalGet('admin/config/regional/translate/update');
+ $this->assertText(t('Updates for: @modules', array('@modules' => 'Localization Update test translate')), 'Translations avaiable');
+
+ // Set l10n_update_test_translate module to have a dev release and no
+ // translation found.
+ $status = l10n_update_get_status();
+ $status['l10n_update_test_translate']['de']->version = '1.3-dev';
+ $status['l10n_update_test_translate']['de']->type = '';
+ variable_set('l10n_update_translation_status', $status);
+
+ // Check if no updates were found.
+ $this->drupalGet('admin/reports/status');
+ $this->assertText(t('Translation update status'), 'Status message');
+ $this->assertRaw(t('Missing translations for: @languages. See the <a href="@updates">Available translation updates</a> page for more information.', array('@languages' => t('German'), '@updates' => url('admin/config/regional/translate/update'))), 'Missing translations message');
+ $this->drupalGet('admin/config/regional/translate/update');
+ $this->assertText(t('Missing translations for one project'), 'No translations found');
+ $this->assertText(t('@module (@version).', array('@module' => 'Localization Update test translate', '@version' => '1.3-dev')), 'Release details');
+ $this->assertText(t('No translation files are provided for development releases.'), 'Release info');
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/tests/L10nUpdateTest.test b/sites/all/modules/l10n_update/tests/L10nUpdateTest.test
new file mode 100644
index 000000000..c19409ba7
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/L10nUpdateTest.test
@@ -0,0 +1,440 @@
+<?php
+
+/**
+ * @file
+ * Contains L10nUpdateTest.
+ */
+
+/**
+ * Tests for update translations.
+ */
+class L10nUpdateTest extends L10nUpdateTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Update translations',
+ 'description' => 'Tests for updating the interface translations of projects.',
+ 'group' => 'Localization Update',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
+ $this->drupalLogin($admin_user);
+
+ // We use German as test language. This language must match the translation
+ // file that come with the l10n_update_test module (test.de.po) and can therefore
+ // not be chosen randomly.
+ $this->addLanguage('de');
+
+ module_load_include('compare.inc', 'l10n_update');
+ module_load_include('fetch.inc', 'l10n_update');
+ }
+
+ /**
+ * Checks if a list of translatable projects gets build.
+ */
+ function testUpdateProjects() {
+ module_load_include('compare.inc', 'l10n_update');
+ variable_set('l10n_update_test_projects_alter', TRUE);
+
+ // Make the test modules look like a normal custom module. i.e. make the
+ // modules not hidden. l10n_update_test_system_info_alter() modifies the project
+ // info of the l10n_update_test and l10n_update_test_translate modules.
+ variable_set('l10n_update_test_system_info_alter', TRUE);
+ $this->resetAll();
+
+ // Check if interface translation data is collected from hook_info.
+ $projects = l10n_update_project_list();
+ $this->assertFalse(isset($projects['l10n_update_test_translate']), 'Hidden module not found');
+ $this->assertEqual($projects['l10n_update_test']['info']['interface translation server pattern'], 'sites/all/modules/l10n_update/tests/test.%language.po', 'Interface translation parameter found in project info.');
+ $this->assertEqual($projects['l10n_update_test']['name'] , 'l10n_update_test', format_string('%key found in project info.', array('%key' => 'interface translation project')));
+ }
+
+ /**
+ * Checks if local or remote translation sources are detected.
+ *
+ * The translation status process by default checks the status of the
+ * installed projects. For testing purpose a predefined set of modules with
+ * fixed file names and release versions is used. This custom project
+ * definition is applied using a hook_l10n_update_projects_alter
+ * implementation in the l10n_update_test module.
+ *
+ * This test generates a set of local and remote translation files in their
+ * respective local and remote translation directory. The test checks whether
+ * the most recent files are selected in the different check scenarios: check
+ * for local files only, check for both local and remote files.
+ */
+ function testUpdateCheckStatus() {
+ // Set a flag to let the l10n_update_test module replace the project data with a
+ // set of test projects.
+ variable_set('l10n_update_test_projects_alter', TRUE);
+
+ // Create local and remote translations files.
+ $this->setTranslationFiles();
+ variable_set('l10n_update_default_filename', '%project-%release.%language._po');
+
+ // Set the test conditions.
+ $edit = array(
+ 'l10n_update_check_mode' => L10N_UPDATE_USE_SOURCE_LOCAL,
+ );
+ $this->drupalPost('admin/config/regional/language/update', $edit, t('Save configuration'));
+
+ // Get status of translation sources at local file system.
+ $this->drupalGet('admin/config/regional/translate/check');
+ $result = l10n_update_get_status();
+ $this->assertEqual($result['contrib_module_one']['de']->type, L10N_UPDATE_LOCAL, 'Translation of contrib_module_one found');
+ $this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestamp_old, 'Translation timestamp found');
+ $this->assertEqual($result['contrib_module_two']['de']->type, L10N_UPDATE_LOCAL, 'Translation of contrib_module_two found');
+ $this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestamp_new, 'Translation timestamp found');
+ $this->assertEqual($result['l10n_update_test']['de']->type, L10N_UPDATE_LOCAL, 'Translation of l10n_update_test found');
+ $this->assertEqual($result['custom_module_one']['de']->type, L10N_UPDATE_LOCAL, 'Translation of custom_module_one found');
+
+ // Set the test conditions.
+ $edit = array(
+ 'l10n_update_check_mode' => L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL,
+ );
+ $this->drupalPost('admin/config/regional/language/update', $edit, t('Save configuration'));
+
+ // Get status of translation sources at both local and remote locations.
+ $this->drupalGet('admin/config/regional/translate/check');
+ $result = l10n_update_get_status();
+ $this->assertEqual($result['contrib_module_one']['de']->type, L10N_UPDATE_REMOTE, 'Translation of contrib_module_one found');
+ $this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestamp_new, 'Translation timestamp found');
+ $this->assertEqual($result['contrib_module_two']['de']->type, L10N_UPDATE_LOCAL, 'Translation of contrib_module_two found');
+ $this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestamp_new, 'Translation timestamp found');
+ $this->assertEqual($result['contrib_module_three']['de']->type, L10N_UPDATE_LOCAL, 'Translation of contrib_module_three found');
+ $this->assertEqual($result['contrib_module_three']['de']->timestamp, $this->timestamp_old, 'Translation timestamp found');
+ $this->assertEqual($result['l10n_update_test']['de']->type, L10N_UPDATE_LOCAL, 'Translation of l10n_update_test found');
+ $this->assertEqual($result['custom_module_one']['de']->type, L10N_UPDATE_LOCAL, 'Translation of custom_module_one found');
+ }
+
+ /**
+ * Tests translation import from remote sources.
+ *
+ * Test conditions:
+ * - Source: remote and local files
+ * - Import overwrite: all existing translations
+ */
+ function testUpdateImportSourceRemote() {
+ // Build the test environment.
+ $this->setTranslationFiles();
+ $this-> setCurrentTranslations();
+ variable_set('l10n_update_default_filename', '%project-%release.%language._po');
+
+ // Set the update conditions for this test.
+ $edit = array(
+ 'l10n_update_check_mode' => L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL,
+ 'overwrite' => LOCALE_IMPORT_OVERWRITE,
+ );
+ $this->drupalPost('admin/config/regional/language/update', $edit, t('Save configuration'));
+
+ // Get the translation status.
+ $this->drupalGet('admin/config/regional/translate/check');
+
+ // Check the status on the Available translation status page.
+ $this->assertRaw('<label class="element-invisible" for="edit-langcodes-de">Update German </label>', 'German language found');
+ $this->assertText('Updates for: Contributed module one, Contributed module two, Custom module one, Locale test', 'Updates found');
+ $this->assertText('Contributed module one (' . format_date($this->timestamp_new, 'medium') . ')', 'Updates for Contrib module one');
+ $this->assertText('Contributed module two (' . format_date($this->timestamp_new, 'medium') . ')', 'Updates for Contrib module two');
+
+ // Execute the translation update.
+ $this->drupalPost('admin/config/regional/translate/update', array(), t('Update translations'));
+
+ // Check if the translation has been updated, using the status cache.
+ $status = l10n_update_get_status();
+ $this->assertEqual($status['contrib_module_one']['de']->type, L10N_UPDATE_CURRENT, 'Translation of contrib_module_one found');
+ $this->assertEqual($status['contrib_module_two']['de']->type, L10N_UPDATE_CURRENT, 'Translation of contrib_module_two found');
+ $this->assertEqual($status['contrib_module_three']['de']->type, L10N_UPDATE_CURRENT, 'Translation of contrib_module_three found');
+
+ // Check the new translation status.
+ // The static cache needs to be flushed first to get the most recent data
+ // from the database. The function was called earlier during this test.
+ drupal_static_reset('l10n_update_get_file_history');
+ $history = l10n_update_get_file_history();
+ $this->assertTrue($history['contrib_module_one']['de']->timestamp >= $this->timestamp_now, 'Translation of contrib_module_one is imported');
+ $this->assertTrue($history['contrib_module_one']['de']->last_checked >= $this->timestamp_now, 'Translation of contrib_module_one is updated');
+ $this->assertEqual($history['contrib_module_two']['de']->timestamp, $this->timestamp_new, 'Translation of contrib_module_two is imported');
+ $this->assertTrue($history['contrib_module_two']['de']->last_checked >= $this->timestamp_now, 'Translation of contrib_module_two is updated');
+ $this->assertEqual($history['contrib_module_three']['de']->timestamp, $this->timestamp_medium, 'Translation of contrib_module_three is not imported');
+ $this->assertEqual($history['contrib_module_three']['de']->last_checked, $this->timestamp_medium, 'Translation of contrib_module_three is not updated');
+
+ // Check whether existing translations have (not) been overwritten.
+ $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_1', 'Translation of January');
+ $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_2', 'Translation of February');
+ $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
+ $this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
+ $this->assertEqual(t('May', array(), array('langcode' => 'de')), 'Mai_customized', 'Translation of May');
+ $this->assertEqual(t('June', array(), array('langcode' => 'de')), 'Juni', 'Translation of June');
+ $this->assertEqual(t('Monday', array(), array('langcode' => 'de')), 'Montag', 'Translation of Monday');
+ }
+
+ /**
+ * Tests translation import from local sources.
+ *
+ * Test conditions:
+ * - Source: local files only
+ * - Import overwrite: all existing translations
+ */
+ function testUpdateImportSourceLocal() {
+ // Build the test environment.
+ $this->setTranslationFiles();
+ $this-> setCurrentTranslations();
+ variable_set('l10n_update_default_filename', '%project-%release.%language._po');
+
+ // Set the update conditions for this test.
+ $edit = array(
+ 'l10n_update_check_mode' => L10N_UPDATE_USE_SOURCE_LOCAL,
+ 'overwrite' => LOCALE_IMPORT_OVERWRITE,
+ );
+ $this->drupalPost('admin/config/regional/language/update', $edit, t('Save configuration'));
+
+ // Execute the translation update.
+ $this->drupalGet('admin/config/regional/translate/check');
+ $this->drupalPost('admin/config/regional/translate/update', array(), t('Update translations'));
+
+ // Check if the translation has been updated, using the status cache.
+ $status = l10n_update_get_status();
+ $this->assertEqual($status['contrib_module_one']['de']->type, L10N_UPDATE_CURRENT, 'Translation of contrib_module_one found');
+ $this->assertEqual($status['contrib_module_two']['de']->type, L10N_UPDATE_CURRENT, 'Translation of contrib_module_two found');
+ $this->assertEqual($status['contrib_module_three']['de']->type, L10N_UPDATE_CURRENT, 'Translation of contrib_module_three found');
+
+ // Check the new translation status.
+ // The static cache needs to be flushed first to get the most recent data
+ // from the database. The function was called earlier during this test.
+ drupal_static_reset('l10n_update_get_file_history');
+ $history = l10n_update_get_file_history();
+ $this->assertTrue($history['contrib_module_one']['de']->timestamp >= $this->timestamp_medium, 'Translation of contrib_module_one is imported');
+ $this->assertEqual($history['contrib_module_one']['de']->last_checked, $this->timestamp_medium, 'Translation of contrib_module_one is updated');
+ $this->assertEqual($history['contrib_module_two']['de']->timestamp, $this->timestamp_new, 'Translation of contrib_module_two is imported');
+ $this->assertTrue($history['contrib_module_two']['de']->last_checked >= $this->timestamp_now, 'Translation of contrib_module_two is updated');
+ $this->assertEqual($history['contrib_module_three']['de']->timestamp, $this->timestamp_medium, 'Translation of contrib_module_three is not imported');
+ $this->assertEqual($history['contrib_module_three']['de']->last_checked, $this->timestamp_medium, 'Translation of contrib_module_three is not updated');
+
+ // Check whether existing translations have (not) been overwritten.
+ $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January');
+ $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_2', 'Translation of February');
+ $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
+ $this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
+ $this->assertEqual(t('May', array(), array('langcode' => 'de')), 'Mai_customized', 'Translation of May');
+ $this->assertEqual(t('June', array(), array('langcode' => 'de')), 'Juni', 'Translation of June');
+ $this->assertEqual(t('Monday', array(), array('langcode' => 'de')), 'Montag', 'Translation of Monday');
+ }
+
+ /**
+ * Tests translation import and only overwrite non-customized translations.
+ *
+ * Test conditions:
+ * - Source: remote and local files
+ * - Import overwrite: only overwrite non-customized translations
+ */
+ function testUpdateImportModeNonCustomized() {
+ // Build the test environment.
+ $this->setTranslationFiles();
+ $this-> setCurrentTranslations();
+ variable_set('l10n_update_default_filename', '%project-%release.%language._po');
+
+ // Set the test conditions.
+ $edit = array(
+ 'l10n_update_check_mode' => L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL,
+ 'overwrite' => L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED,
+ );
+ $this->drupalPost('admin/config/regional/language/update', $edit, t('Save configuration'));
+
+ // Execute translation update.
+ $this->drupalGet('admin/config/regional/translate/check');
+ $this->drupalPost('admin/config/regional/translate/update', array(), t('Update translations'));
+
+ // Check whether existing translations have (not) been overwritten.
+ $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January');
+ $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_customized', 'Translation of February');
+ $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
+ $this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
+ $this->assertEqual(t('May', array(), array('langcode' => 'de')), 'Mai_customized', 'Translation of May');
+ $this->assertEqual(t('June', array(), array('langcode' => 'de')), 'Juni', 'Translation of June');
+ $this->assertEqual(t('Monday', array(), array('langcode' => 'de')), 'Montag', 'Translation of Monday');
+ }
+
+ /**
+ * Tests translation import and don't overwrite any translation.
+ *
+ * Test conditions:
+ * - Source: remote and local files
+ * - Import overwrite: don't overwrite any existing translation
+ */
+ function testUpdateImportModeNone() {
+ // Build the test environment.
+ $this->setTranslationFiles();
+ $this-> setCurrentTranslations();
+ variable_set('l10n_update_default_filename', '%project-%release.%language._po');
+
+ // Set the test conditions.
+ $edit = array(
+ 'l10n_update_check_mode' => L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL,
+ 'overwrite' => LOCALE_IMPORT_KEEP,
+ );
+ $this->drupalPost('admin/config/regional/language/update', $edit, t('Save configuration'));
+
+ // Execute translation update.
+ $this->drupalGet('admin/config/regional/translate/check');
+ $this->drupalPost('admin/config/regional/translate/update', array(), t('Update translations'));
+
+ // Check whether existing translations have (not) been overwritten.
+ $this->assertTranslation('January', 'Januar_customized', 'de');
+ $this->assertTranslation('February', 'Februar_customized', 'de');
+ $this->assertTranslation('March', 'Marz', 'de');
+ $this->assertTranslation('April', 'April_2', 'de');
+ $this->assertTranslation('May', 'Mai_customized', 'de');
+ $this->assertTranslation('June', 'Juni', 'de');
+ $this->assertTranslation('Monday', 'Montag', 'de');
+ }
+
+ /**
+ * Tests automatic translation import when a module is enabled.
+ */
+ function testEnableUninstallModule() {
+ // Make the hidden test modules look like a normal custom module.
+ variable_set('l10n_update_test_system_info_alter', TRUE);
+
+ // Check if there is no translation yet.
+ $this->assertTranslation('Tuesday', '', 'de');
+
+ // Enable a module.
+ $edit = array(
+ 'modules[Testing][l10n_update_test_translate][enable]' => '1',
+ );
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+
+ // Check if translations have been imported.
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
+ array('%number' => 0, '%update' => 7, '%delete' => 0)), 'One translation file imported.');
+ $this->assertTranslation('Tuesday', 'Dienstag', 'de');
+
+// // Disable and uninstall a module
+// module_disable(array('l10n_update_test_translate'));
+// $edit = array(
+// 'uninstall[l10n_update_test_translate]' => '1',
+// );
+// $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
+// $this->drupalPost(NULL, array(), t('Uninstall'));
+//
+// // Check if the file data is removed from the database.
+// $history = l10n_update_get_file_history();
+// $this->assertFalse(isset($history['l10n_update_test_translate']), 'Project removed from the file history');
+// $projects = l10n_update_get_projects();
+// $this->assertFalse(isset($projects['l10n_update_test_translate']), 'Project removed from the project list');
+ }
+
+ /**
+ * Tests automatic translation import when a langauge is enabled.
+ *
+ * When a language is added, the system will check for translations files of
+ * enabled modules and will import them. When a language is removed the system
+ * will remove all translations of that langugue from the database.
+ */
+ function testEnableLanguage() {
+ // Make the hidden test modules look like a normal custom module.
+ variable_set('l10n_update_test_system_info_alter', TRUE);
+
+ // Enable a module.
+ $edit = array(
+ 'modules[Testing][l10n_update_test_translate][enable]' => '1',
+ );
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+
+ // Check if there is no Dutch translation yet.
+ $this->assertTranslation('Extraday', '', 'nl');
+ $this->assertTranslation('Tuesday', 'Dienstag', 'de');
+
+ // Add a language.
+ $this->addLanguage('nl');
+
+ // Check if the right number of translations are added.
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
+ array('%number' => 0, '%update' => 8, '%delete' => 0)), 'One language added.');
+ $this->assertTranslation('Extraday', 'extra dag', 'nl');
+
+ // Check if the language data is added to the database.
+ $result = db_query("SELECT project FROM {l10n_update_file} WHERE language='nl'")->fetchField();
+ $this->assertTrue((boolean) $result, 'Files removed from file history');
+
+ // Remove a language.
+ $this->drupalPost('admin/config/regional/language/delete/nl', array(), t('Delete'));
+
+ // Check if the language data is removed from the database.
+ $result = db_query("SELECT project FROM {l10n_update_file} WHERE language='nl'")->fetchField();
+ $this->assertFalse($result, 'Files removed from file history');
+
+ // Check that the Dutch translation is gone.
+ $this->assertTranslation('Extraday', '', 'nl');
+ $this->assertTranslation('Tuesday', 'Dienstag', 'de');
+ }
+
+ /**
+ * Tests automatic translation import when a custom langauge is enabled.
+ */
+ function testEnableCustomLanguage() {
+ // Make the hidden test modules look like a normal custom module.
+ variable_set('l10n_update_test_system_info_alter', TRUE);
+
+ // Enable a module.
+ $edit = array(
+ 'modules[Testing][l10n_update_test_translate][enable]' => '1',
+ );
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+
+ // Create and enable a custom language with language code 'xx' and a random
+ // name.
+ $langcode = 'xx';
+ $name = $this->randomName(16);
+ $edit = array(
+ 'langcode' => $langcode,
+ 'name' => $name,
+ 'native' => $name,
+ 'prefix' => $langcode,
+ 'direction' => '0',
+ );
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
+ drupal_static_reset('language_list');
+ $languages = language_list();
+ $this->assertTrue(isset($languages[$langcode]), format_string('Language %langcode added.', array('%langcode' => $langcode)));
+
+ // Ensure the translation file is automatically imported when the language
+ // was added.
+ $this->assertText(t('One translation file imported.'), 'Language file automatically imported.');
+ $this->assertText(t('One translation string was skipped because of disallowed or malformed HTML'), 'Language file automatically imported.');
+
+ // Ensure the strings were successfully imported.
+ $search = array(
+ 'string' => 'lundi',
+ 'language' => $langcode,
+ 'translation' => 'translated',
+ );
+ $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+ $this->assertNoText(t('No strings available.'), 'String successfully imported.');
+
+ // Ensure the multiline string was imported.
+ $search = array(
+ 'string' => 'Source string for multiline translation',
+ 'language' => $langcode,
+ 'translation' => 'all',
+ );
+ $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+ $this->assertText('Source string for multiline translation', 'String successfully imported.');
+
+ // Ensure 'Allowed HTML source string' was imported but the translation for
+ // 'Another allowed HTML source string' was not because it contains invalid
+ // HTML.
+ $search = array(
+ 'string' => 'HTML source string',
+ 'language' => $langcode,
+ 'translation' => 'translated',
+ );
+ $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+// $this->assertText('Allowed HTML source string', 'String successfully imported.');
+ $this->assertNoText('Another allowed HTML source string', 'String with disallowed translation not imported.');
+ }
+
+}
diff --git a/sites/all/modules/l10n_update/tests/L10nUpdateTestBase.test b/sites/all/modules/l10n_update/tests/L10nUpdateTestBase.test
new file mode 100644
index 000000000..2738701e0
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/L10nUpdateTestBase.test
@@ -0,0 +1,284 @@
+<?php
+
+/**
+ * @file
+ * Contains L10nUpdateTest.
+ */
+
+/**
+ * Tests for update translations.
+ */
+class L10nUpdateTestBase extends DrupalWebTestCase {
+
+ /**
+ * Timestamp for an old translation.
+ *
+ * @var integer
+ */
+ protected $timestamp_old;
+
+ /**
+ * Timestamp for a medium aged translation.
+ *
+ * @var integer
+ */
+ protected $timestamp_medium;
+
+ /**
+ * Timestamp for a new translation.
+ *
+ * @var integer
+ */
+ protected $timestamp_new;
+
+ function setUp() {
+ parent::setUp('update', 'locale', 'l10n_update', 'l10n_update_test');
+
+ // Setup timestamps to identify old and new translation sources.
+ $this->timestamp_old = REQUEST_TIME - 300;
+ $this->timestamp_medium = REQUEST_TIME - 200;
+ $this->timestamp_new = REQUEST_TIME - 100;
+ $this->timestamp_now = REQUEST_TIME;
+ }
+
+ /**
+ * Sets the value of the default translations directory.
+ *
+ * @param string $path
+ * Path of the translations directory relative to the drupal installation
+ * directory.
+ */
+ protected function setTranslationsDirectory($path) {
+ file_prepare_directory($path, FILE_CREATE_DIRECTORY);
+ variable_set('l10n_update_download_store', $path);
+ }
+
+ /**
+ * Adds a language.
+ *
+ * @param $langcode
+ * The language code of the language to add.
+ */
+ protected function addLanguage($langcode) {
+ $edit = array('langcode' => $langcode);
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+ drupal_static_reset('language_list');
+ $languages = language_list();
+ $this->assertTrue(isset($languages[$langcode]), format_string('Language %langcode added.', array('%langcode' => $langcode)));
+ }
+
+ /**
+ * Creates a translation file and tests its timestamp.
+ *
+ * @param string $path
+ * Path of the file relative to the public file path.
+ * @param string $filename
+ * Name of the file to create.
+ * @param integer $timestamp
+ * Timestamp to set the file to. Defaults to current time.
+ * @param array $translations
+ * Array of source/target value translation strings. Only singular strings
+ * are supported, no plurals. No double quotes are allowed in source and
+ * translations strings.
+ */
+ protected function makePoFile($path, $filename, $timestamp = NULL, $translations = array()) {
+ $timestamp = $timestamp ? $timestamp : REQUEST_TIME;
+ $path = 'public://' . $path;
+ $text = '';
+ $po_header = <<<EOF
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 7\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
+
+EOF;
+
+ // Convert array of translations to Gettext source and translation strings.
+ if ($translations) {
+ foreach ($translations as $source => $target) {
+ $text .= 'msgid "'. $source . '"' . "\n";
+ $text .= 'msgstr "'. $target . '"' . "\n";
+ }
+ }
+
+ file_prepare_directory($path, FILE_CREATE_DIRECTORY);
+ $file = (object) array(
+ 'uid' => 1,
+ 'filename' => $filename,
+ 'uri' => $path . '/' . $filename,
+ 'filemime' => 'text/x-gettext-translation',
+ 'timestamp' => $timestamp,
+ 'status' => FILE_STATUS_PERMANENT,
+ );
+ file_put_contents($file->uri, $po_header . $text);
+ touch(drupal_realpath($file->uri), $timestamp);
+ file_save($file);
+ }
+
+ /**
+ * Setup the environment containing local and remote translation files.
+ *
+ * Update tests require a simulated environment for local and remote files.
+ * Normally remote files are located at a remote server (e.g. ftp.drupal.org).
+ * For testing we can not rely on this. A directory in the file system of the
+ * test site is designated for remote files and is addressed using an absolute
+ * URL. Because Drupal does not allow files with a po extension to be accessed
+ * (denied in .htaccess) the translation files get a _po extension. Another
+ * directory is designated for local translation files.
+ *
+ * The environment is set up with the following files. File creation times are
+ * set to create different variations in test conditions.
+ * contrib_module_one
+ * - remote file: timestamp new
+ * - local file: timestamp old
+ * - current: timestamp medium
+ * contrib_module_two
+ * - remote file: timestamp old
+ * - local file: timestamp new
+ * - current: timestamp medium
+ * contrib_module_three
+ * - remote file: timestamp old
+ * - local file: timestamp old
+ * - current: timestamp medium
+ * custom_module_one
+ * - local file: timestamp new
+ * - current: timestamp medium
+ * Time stamp of current translation set by setCurrentTranslations() is always
+ * timestamp medium. This makes it easy to predict which translation will be
+ * imported.
+ */
+ protected function setTranslationFiles() {
+ // A flag is set to let the l10n_update_test module replace the project data with
+ // a set of test projects which match the below project files.
+ variable_set('l10n_update_test_projects_alter', TRUE);
+
+ // Setup the environment.
+ $public_path = drupal_realpath('public://');
+ $this->setTranslationsDirectory($public_path . '/local');
+ variable_set('l10n_update_default_filename', '%project-%release.%language._po');
+
+ // Setting up sets of translations for the translation files.
+ $translations_one = array('January' => 'Januar_1', 'February' => 'Februar_1', 'March' => 'Marz_1');
+ $translations_two = array( 'February' => 'Februar_2', 'March' => 'Marz_2', 'April' => 'April_2');
+ $translations_three = array('April' => 'April_3', 'May' => 'Mai_3', 'June' => 'Juni_3');
+
+ // Add a number of files to the local file system to serve as remote
+ // translation server and match the project definitions set in
+ // l10n_update_test_l10n_update_projects_alter().
+ $this->makePoFile('remote/7.x/contrib_module_one', 'contrib_module_one-7.x-1.1.de._po', $this->timestamp_new, $translations_one);
+ $this->makePoFile('remote/7.x/contrib_module_two', 'contrib_module_two-7.x-2.0-beta4.de._po', $this->timestamp_old, $translations_two);
+ $this->makePoFile('remote/7.x/contrib_module_three', 'contrib_module_three-7.x-1.0.de._po', $this->timestamp_old, $translations_three);
+
+ // Add a number of files to the local file system to serve as local
+ // translation files and match the project definitions set in
+ // l10n_update_test_l10n_update_projects_alter().
+ $this->makePoFile('local', 'contrib_module_one-7.x-1.1.de._po', $this->timestamp_old, $translations_one);
+ $this->makePoFile('local', 'contrib_module_two-7.x-2.0-beta4.de._po', $this->timestamp_new, $translations_two);
+ $this->makePoFile('local', 'contrib_module_three-7.x-1.0.de._po', $this->timestamp_old, $translations_three);
+ $this->makePoFile('local', 'custom_module_one.de.po', $this->timestamp_new);
+ }
+
+ /**
+ * Setup existing translations in the database and set up the status of
+ * existing translations.
+ */
+ protected function setCurrentTranslations() {
+ // Setup to add German translations to the database.
+ $langcode = 'de';
+ $writer = new PoDatabaseWriter();
+ $writer->setLangcode($langcode);
+ $writer->setOptions(array(
+ 'overwrite_options' => array(
+ 'not_customized' => TRUE,
+ 'customized' => TRUE,
+ ),
+ ));
+
+ // Add non customized translations to the database.
+ $writer->setOptions(array('customized' => L10N_UPDATE_NOT_CUSTOMIZED));
+ $non_customized_translations = array(
+ 'March' => 'Marz',
+ 'June' => 'Juni',
+ );
+
+ foreach ($non_customized_translations as $source => $translation) {
+ $poItem = new PoItem();
+ $poItem->setFromArray(array(
+ 'source' => $source,
+ 'translation' => $translation,
+ ));
+ $writer->writeItem($poItem);
+ }
+
+ // Add customized translations to the database.
+ $writer->setOptions(array('customized' => L10N_UPDATE_CUSTOMIZED));
+ $customized_translations = array(
+ 'January' => 'Januar_customized',
+ 'February' => 'Februar_customized',
+ 'May' => 'Mai_customized',
+ );
+
+ foreach ($customized_translations as $source => $translation) {
+ $poItem = new PoItem();
+ $poItem->setFromArray(array(
+ 'source' => $source,
+ 'translation' => $translation,
+ ));
+ $writer->writeItem($poItem);
+ }
+
+ // Add a state of current translations in l10n_update_files.
+ $default = array(
+ 'language' => $langcode,
+ 'uri' => '',
+ 'timestamp' => $this->timestamp_medium,
+ 'last_checked' => $this->timestamp_medium,
+ );
+ $data[] = array(
+ 'project' => 'contrib_module_one',
+ 'filename' => 'contrib_module_one-7.x-1.1.de._po',
+ 'version' => '7.x-1.1',
+ );
+ $data[] = array(
+ 'project' => 'contrib_module_two',
+ 'filename' => 'contrib_module_two-7.x-2.0-beta4.de._po',
+ 'version' => '7.x-2.0-beta4',
+ );
+ $data[] = array(
+ 'project' => 'contrib_module_three',
+ 'filename' => 'contrib_module_three-7.x-1.0.de._po',
+ 'version' => '7.x-1.0',
+ );
+ $data[] = array(
+ 'project' => 'custom_module_one',
+ 'filename' => 'custom_module_one.de.po',
+ 'version' => '',
+ );
+ foreach ($data as $file) {
+ $file = (object) array_merge($default, $file);
+ drupal_write_record('l10n_update_file', $file);
+ }
+ }
+
+ /**
+ * Checks the translation of a string.
+ *
+ * @param string $source
+ * Translation source string
+ * @param string $translation
+ * Translation to check. Use empty string to check for a not existing
+ * translation.
+ * @param string $langcode
+ * Language code of the language to translate to.
+ * @param string $message
+ * (optional) A message to display with the assertion.
+ */
+ protected function assertTranslation($source, $translation, $langcode, $message = '') {
+ $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', array(':source' => $source, ':langcode' => $langcode))->fetchField();
+ $db_translation = $db_translation == FALSE ? '' : $db_translation;
+ $this->assertEqual($translation, $db_translation, $message ? $message : format_string('Correct translation of %source (%language)', array('%source' => $source, '%language' => $langcode)));
+ }
+}
diff --git a/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.info b/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.info
new file mode 100644
index 000000000..7dac6a763
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.info
@@ -0,0 +1,14 @@
+name = 'Localization Update test'
+type = module
+description = 'Support module for Localization Update module testing.'
+package = Testing
+version = '1.2'
+core = 7.x
+hidden = true
+
+; Information added by Drupal.org packaging script on 2014-11-10
+version = "7.x-2.0"
+core = "7.x"
+project = "l10n_update"
+datestamp = "1415625781"
+
diff --git a/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.install b/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.install
new file mode 100644
index 000000000..e7c2ff764
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.install
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the l10n_update_test module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function l10n_update_test_uninstall() {
+ // Clear variables.
+ variable_del('l10n_update_test_system_info_alter');
+ variable_del('l10n_update_test_projects_alter');
+}
diff --git a/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.module b/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.module
new file mode 100644
index 000000000..81dfb18fa
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/modules/l10n_update_test/l10n_update_test.module
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Simulate a custom module with a local po file.
+ */
+
+/**
+ * Implements hook_system_info_alter().
+ *
+ * Make the test scripts to be believe this is not a hidden test module, but
+ * a regular custom module.
+ */
+function l10n_update_test_system_info_alter(&$info, $file, $type) {
+ // Only modify the system info if required.
+ // By default the l10n_update_test modules are hidden and have a project specified.
+ // To test the module detection process by l10n_update_project_list() the
+ // test modules should mimic a custom module. I.e. be non-hidden.
+ if (variable_get('l10n_update_test_system_info_alter', FALSE)) {
+ if ($file->name == 'l10n_update_test' || $file->name == 'l10n_update_test_translate') {
+ // Don't hide the module.
+ $info['hidden'] = FALSE;
+ }
+ }
+}
+
+/**
+ * Implements hook_l10n_update_projects_alter().
+ *
+ * The translation status process by default checks the status of the installed
+ * projects. This function replaces the data of the installed modules by a
+ * predefined set of modules with fixed file names and release versions. Project
+ * names, versions, timestamps etc must be fixed because they must match the
+ * files created by the test script.
+ *
+ * The "l10n_update_test_projects_alter" variable must be set by the test script
+ * in order for this hook to take effect.
+ */
+function l10n_update_test_l10n_update_projects_alter(&$projects) {
+ if (variable_get('l10n_update_test_projects_alter', FALSE)) {
+
+ // Instead of the default ftp.drupal.org we use the file system of the test
+ // instance to simulate a remote file location.
+ $wrapper = file_stream_wrapper_get_instance_by_uri('public://');
+ $remote_url = $wrapper->getExternalUrl() . '/remote/';
+
+ // Completely replace the project data with a set of test projects.
+ $projects = array (
+ 'contrib_module_one' => array (
+ 'name' => 'contrib_module_one',
+ 'info' => array (
+ 'name' => 'Contributed module one',
+ 'l10n path' => $remote_url . '%core/%project/%project-%release.%language._po',
+ 'package' => 'Other',
+ 'version' => '7.x-1.1',
+ 'project' => 'contrib_module_one',
+ 'datestamp' => '1344471537',
+ '_info_file_ctime' => 1348767306,
+ ),
+ 'datestamp' => '1344471537',
+ 'includes' => array (
+ 'contrib_module_one' => 'Contributed module one',
+ ),
+ 'project_type' => 'module',
+ 'project_status' => TRUE,
+ ),
+ 'contrib_module_two' => array (
+ 'name' => 'contrib_module_two',
+ 'info' => array (
+ 'name' => 'Contributed module two',
+ 'l10n path' => $remote_url . '%core/%project/%project-%release.%language._po',
+ 'package' => 'Other',
+ 'version' => '7.x-2.0-beta4',
+ 'project' => 'contrib_module_two',
+ 'datestamp' => '1344471537',
+ '_info_file_ctime' => 1348767306,
+ ),
+ 'datestamp' => '1344471537',
+ 'includes' => array (
+ 'contrib_module_two' => 'Contributed module two',
+ ),
+ 'project_type' => 'module',
+ 'project_status' => TRUE,
+ ),
+ 'contrib_module_three' => array (
+ 'name' => 'contrib_module_three',
+ 'info' => array (
+ 'name' => 'Contributed module three',
+ 'l10n path' => $remote_url . '%core/%project/%project-%release.%language._po',
+ 'package' => 'Other',
+ 'version' => '7.x-1.0',
+ 'project' => 'contrib_module_three',
+ 'datestamp' => '1344471537',
+ '_info_file_ctime' => 1348767306,
+ ),
+ 'datestamp' => '1344471537',
+ 'includes' => array (
+ 'contrib_module_three' => 'Contributed module three',
+ ),
+ 'project_type' => 'module',
+ 'project_status' => TRUE,
+ ),
+ 'l10n_update_test' => array (
+ 'name' => 'l10n_update_test',
+ 'info' => array (
+ 'name' => 'Locale test',
+ 'interface translation project' => 'l10n_update_test',
+ 'l10n path' => 'sites/all/modules/l10n_update/tests/test.%language.po',
+ 'package' => 'Other',
+ 'version' => NULL,
+ 'project' => 'l10n_update_test',
+ '_info_file_ctime' => 1348767306,
+ 'datestamp' => 0,
+ ),
+ 'datestamp' => 0,
+ 'includes' => array (
+ 'l10n_update_test' => 'Locale test',
+ ),
+ 'project_type' => 'module',
+ 'project_status' => TRUE,
+ ),
+ 'custom_module_one' => array (
+ 'name' => 'custom_module_one',
+ 'info' => array (
+ 'name' => 'Custom module one',
+ 'interface translation project' => 'custom_module_one',
+ 'l10n path' => 'translations://custom_module_one.%language.po',
+ 'package' => 'Other',
+ 'version' => NULL,
+ 'project' => 'custom_module_one',
+ '_info_file_ctime' => 1348767306,
+ 'datestamp' => 0,
+ ),
+ 'datestamp' => 0,
+ 'includes' => array (
+ 'custom_module_one' => 'Custom module one',
+ ),
+ 'project_type' => 'module',
+ 'project_status' => TRUE,
+ ),
+ );
+ }
+}
diff --git a/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.info b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.info
new file mode 100644
index 000000000..510f31fb2
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.info
@@ -0,0 +1,16 @@
+name = 'Localization Update test translate'
+type = module
+description = 'Translation test module for Localization Update module testing.'
+package = Testing
+version = '1.3'
+core = 7.x
+hidden = true
+interface translation project = l10n_update_test_translate
+l10n path = sites/all/modules/contrib/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.%language.po
+
+; Information added by Drupal.org packaging script on 2014-11-10
+version = "7.x-2.0"
+core = "7.x"
+project = "l10n_update"
+datestamp = "1415625781"
+
diff --git a/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.module b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.module
new file mode 100644
index 000000000..25b9ed871
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.module
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Simulates a custom module with a local po file.
+ */
+
+/**
+ * Implements hook_system_info_alter().
+ *
+ * By default this modules is hidden but once enabled it behaves like a normal
+ * (not hidden) module. This hook implementation changes the .info.yml data by
+ * setting the hidden status to FALSE.
+ */
+function l10n_update_test_translate_system_info_alter(&$info, $file, $type) {
+ if ($file->name == 'l10n_update_test_translate') {
+ // Don't hide the module.
+ if (isset($info['hidden'])) {
+ $info['hidden'] = FALSE;
+ }
+
+ // Correct the path to the translation file. At a test-environment, the
+ // module may be place in a different path.
+ $basename = basename($info['l10n path']);
+ $path = drupal_get_path('module', 'l10n_update') . '/tests/modules/l10n_update_test_translate/translations/';
+ $info['l10n path'] = $path . $basename;
+ }
+}
diff --git a/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.de.po b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.de.po
new file mode 100644
index 000000000..85e2841fa
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.de.po
@@ -0,0 +1,28 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 7\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
+
+msgid "Monday"
+msgstr "Montag"
+
+msgid "Tuesday"
+msgstr "Dienstag"
+
+msgid "Wednesday"
+msgstr "Mittwoch"
+
+msgid "Thursday"
+msgstr "Donnerstag"
+
+msgid "Friday"
+msgstr "Freitag"
+
+msgid "Saturday"
+msgstr "Samstag"
+
+msgid "Sunday"
+msgstr "Sonntag"
diff --git a/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.nl.po b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.nl.po
new file mode 100644
index 000000000..2e956c191
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.nl.po
@@ -0,0 +1,31 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 7\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
+
+msgid "Monday"
+msgstr "maandag"
+
+msgid "Tuesday"
+msgstr "dinsdag"
+
+msgid "Wednesday"
+msgstr "woensdag"
+
+msgid "Thursday"
+msgstr "donderdag"
+
+msgid "Extraday"
+msgstr "extra dag"
+
+msgid "Friday"
+msgstr "vrijdag"
+
+msgid "Saturday"
+msgstr "zaterdag"
+
+msgid "Sunday"
+msgstr "zondag"
diff --git a/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.xx.po b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.xx.po
new file mode 100644
index 000000000..e2db1d305
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.xx.po
@@ -0,0 +1,40 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 7\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
+
+msgid "Monday"
+msgstr "lundi"
+
+msgid "Tuesday"
+msgstr "mardi"
+
+msgid "Wednesday"
+msgstr "mercredi"
+
+msgid "Thursday"
+msgstr "jeudi"
+
+msgid "Friday"
+msgstr "vendredi"
+
+msgid "Saturday"
+msgstr "samedi"
+
+msgid "Sunday"
+msgstr "dimanche"
+
+msgid "Allowed HTML source string"
+msgstr "<strong>Allowed HTML translation string</strong>"
+
+msgid "Another allowed HTML source string"
+msgstr "<script>Disallowed HTML translation string</script>"
+
+msgid "Source string for multiline translation"
+msgstr ""
+"Multiline translation string "
+"to make sure that "
+"import works with it."
diff --git a/sites/all/modules/l10n_update/tests/test.de.po b/sites/all/modules/l10n_update/tests/test.de.po
new file mode 100644
index 000000000..67183fb67
--- /dev/null
+++ b/sites/all/modules/l10n_update/tests/test.de.po
@@ -0,0 +1,10 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 7\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
+
+msgid "Monday"
+msgstr "Montag"
diff --git a/sites/all/modules/libraries/CHANGELOG.txt b/sites/all/modules/libraries/CHANGELOG.txt
new file mode 100644
index 000000000..e3c941198
--- /dev/null
+++ b/sites/all/modules/libraries/CHANGELOG.txt
@@ -0,0 +1,100 @@
+
+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.
+#1775668 by tstoeckler: Fix bogus assertion message in assertLibraryFiles().
+#1773640 by tstoeckler: Use drupal_get_path() to find the profile directory.
+#1565426 by tstoeckler: Invoke hook_libraries_info() in enabled themes.
+
+Libraries 7.x-2.0, 2012-07-29
+-----------------------------
+#1606018 by chemical: Tests fail if the module is downloaded from Drupal.org.
+#1386250 by tstoeckler: Clarify module and library installation in README.txt.
+#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.
+#1347214 by rfay: Improve update function 7200.
+#1323530 by tstoeckler: Document libraries_get_version() pattern matching.
+#1325524 by sun, Rob Loach, tstoeckler: Statically cache libraries_detect().
+#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.
+#962214 by tstoeckler, sun: Add support for library dependencies.
+#1224838 by sun, mjpa: Fix library path not being prepended to JS/CSS files.
+#1023258 by tstoeckler: Make 'files' consistently keyed by filename.
+#958162 by sun, tstoeckler: Add pre-detect callback group.
+#958162 by sun, tstoeckler: Make tests debuggable and provide libraries_info_defaults().
+#961476 by tstoeckler: Changed libraries_get_path() to return FALSE by default.
+#958162 by tstoeckler, sun, good_man: Allow to apply callbacks to libraries.
+#1125904 by tstoeckler, boombatower: Fix drush libraries-list.
+#1050076 by tstoeckler: Re-utilize libraries_detect() and remove libraries_detect_library().
+#466090 by tstoeckler: Add update function.
+#466090 by tstoeckler: Allow cache to be flushed.
+#466090 by tstoeckler, sun: Cache library information.
+#1064008 by tstoeckler, bfroehle: Fix outdated API examples in libraries.api.php.
+#1028744 by tstoeckler: Code clean-up.
+#1023322 by tstoeckler, sun: Fixed libraries shouldn't be loaded multiple times.
+#1024080 by hswong3i, tstoeckler: Fixed installation profile retrieval.
+#995988 by good_man: Wrong default install profile.
+#975498 by Gábor Hojtsy: Update JS/CSS-loading to new drupal_add_js/css() API.
+#958162 by tsteoeckler, sun: Consistent variable naming.
+#924130 by aaronbauman: Fixed libraries_get_path() should use drupal_static().
+#958162 by tstoeckler, sun: Code clean-up, tests revamp, more robust loading.
+#919632 by tstoeckler, sun: Allow library information to be stored in info files.
+by sun: Fixed testbot breaks upon directory name/info file name mismatch.
+#864376 by tstoeckler, sun: Code-cleanup, allow hard-coded 'version'.
+#939174 by sun, tstoeckler: Rename example.info to libraries_example.info.
+by sun: Fixed testbot breaks upon .info file without .module file.
+#542940 by tstoeckler, sun: Add libraries-list command.
+#919632 by tstoeckler: Add example library info file for testing purposes.
+#719896 by tstoeckler, sun: Documentation clean-up and tests improvement.
+#542940 by sun: Added initial Drush integration file.
+#719896 by tstoeckler, sun: Improved library detection and library loading.
+#855050 by Gábor Hojtsy: Avoid call-time pass by reference in libraries_detect().
+#719896 by tstoeckler, sun: Added starting point for hook_libraries_info().
+
+
+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
+------------------------------------
+#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/libraries/LICENSE.txt b/sites/all/modules/libraries/LICENSE.txt
new file mode 100755
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/libraries/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/libraries/README.txt b/sites/all/modules/libraries/README.txt
new file mode 100644
index 000000000..70a77256c
--- /dev/null
+++ b/sites/all/modules/libraries/README.txt
@@ -0,0 +1,37 @@
+
+-- SUMMARY --
+
+Libraries API provides external library handling for Drupal modules.
+
+For a full description visit the project page:
+ http://drupal.org/project/libraries
+Bug reports, feature suggestions and latest developments:
+ http://drupal.org/project/issues/libraries
+
+
+-- REQUIREMENTS --
+
+* None.
+
+
+-- INSTALLATION --
+
+* Install as usual, see http://drupal.org/node/70151 for further information.
+ Note that installing external libraries is separate from installing this
+ module and should happen in the sites/all/libraries directory. See
+ http://drupal.org/node/1440066 for more information.
+
+
+-- CONTACT --
+
+Current maintainers:
+* Daniel F. Kudwien (sun) - http://drupal.org/user/54136
+* Tobias Stöckler (tstoeckler) - http://drupal.org/user/107158
+
+
+This project has been sponsored by:
+* UNLEASHED MIND
+ Specialized in consulting and planning of Drupal powered sites, UNLEASHED
+ MIND offers installation, development, theming, customization, and hosting
+ to get you started. Visit http://www.unleashedmind.com for more information.
+
diff --git a/sites/all/modules/libraries/libraries.api.php b/sites/all/modules/libraries/libraries.api.php
new file mode 100644
index 000000000..8ac311153
--- /dev/null
+++ b/sites/all/modules/libraries/libraries.api.php
@@ -0,0 +1,474 @@
+<?php
+
+/**
+ * @file
+ * Documents API functions for Libraries module.
+ */
+
+/**
+ * Return information about external libraries.
+ *
+ * @return
+ * An associative array whose keys are internal names of libraries and whose
+ * values are describing each library. Each key is the directory name below
+ * the 'libraries' directory, in which the library may be found. Each value is
+ * an associative array containing:
+ * - name: The official, human-readable name of the library.
+ * - vendor url: The URL of the homepage of the library.
+ * - download url: The URL of a web page on which the library can be obtained.
+ * - path: (optional) A relative path from the directory of the library to the
+ * actual library. Only required if the extracted download package contains
+ * the actual library files in a sub-directory.
+ * - library path: (optional) The absolute path to the library directory. This
+ * should not be declared normally, as it is automatically detected, to
+ * allow for multiple possible library locations. A valid use-case is an
+ * external library, in which case the full URL to the library should be
+ * specified here.
+ * - version: (optional) The version of the library. This should not be
+ * declared normally, as it is automatically detected (see 'version
+ * callback' below) to allow for version changes of libraries without code
+ * 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 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
+ * here. There are two ways to declare the version 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 'version arguments'). Defaults to libraries_get_version().
+ * 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.
+ * '@version 1.2.3' in the above example) but the match of the first
+ * sub-pattern (i.e. '1.2.3' in the above example).
+ * - lines: (optional) The maximum number of lines to search the pattern in.
+ * Defaults to 20.
+ * - 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().
+ * - css: A list of CSS files to load, using the same syntax as Drupal
+ * core's hook_library().
+ * - php: A list of PHP files to load.
+ * - dependencies: An array of libraries this library depends on. Similar to
+ * declaring module dependencies, the dependency declaration may contain
+ * information on the supported version. Examples of supported declarations:
+ * @code
+ * $libraries['dependencies'] = array(
+ * // Load the 'example' library, regardless of the version available:
+ * 'example',
+ * // Only load the 'example' library, if version 1.2 is available:
+ * 'example (1.2)',
+ * // Only load a version later than 1.3-beta2 of the 'example' library:
+ * 'example (>1.3-beta2)'
+ * // Only load a version equal to or later than 1.3-beta3:
+ * 'example (>=1.3-beta3)',
+ * // Only load a version earlier than 1.5:
+ * 'example (<1.5)',
+ * // Only load a version equal to or earlier than 1.4:
+ * 'example (<=1.4)',
+ * // Combinations of the above are allowed as well:
+ * 'example (>=1.3-beta2, <1.5)',
+ * );
+ * @endcode
+ * - variants: (optional) An associative array of available library variants.
+ * For example, the top-level 'files' property may refer to a default
+ * variant that is compressed. If the library also ships with a minified and
+ * uncompressed/source variant, those can be defined here. Each key should
+ * describe the variant type, e.g. 'minified' or 'source'. Each value is an
+ * associative array of top-level properties that are entirely overridden by
+ * the variant, most often just 'files'. Additionally, each variant can
+ * contain following properties:
+ * - variant callback: (optional) The name of a function that detects the
+ * variant and returns TRUE or FALSE, depending on whether the variant is
+ * 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 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 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
+ * 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 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 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
+ * library changes between versions, different 'files' may need to be
+ * loaded, different 'variants' may become available, or Drupal modules need
+ * to load different integration files adapted to the new version. Each key
+ * is a version *string* (PHP does not support floats as keys). Each value
+ * is an associative array of top-level properties that are entirely
+ * overridden by the version.
+ * - integration files: (optional) An associative array whose keys are module
+ * names and whose values are sets of files to load for the module, using
+ * the same notion as the top-level 'files' property. Each specified file
+ * should contain the path to the file relative to the module it belongs to.
+ * - callbacks: An associative array whose keys are callback groups and whose
+ * values are arrays of callbacks to apply to the library in that group.
+ * Each callback receives the following arguments:
+ * - $library: An array of library information belonging to the top-level
+ * library, a specific version, a specific variant or a specific variant
+ * of a specific version. Because library information such as the 'files'
+ * property (see above) can be declared in all these different locations
+ * of the library array, but a callback may have to act on all these
+ * different parts of the library, it is called recursively for each
+ * library with a certain part of the libraries array passed as $library
+ * each time.
+ * - $version: If the $library array belongs to a certain version (see
+ * above), a string containing the version. This argument may be empty, so
+ * NULL should be specified as the default value.
+ * - $variant: If the $library array belongs to a certain variant (see
+ * above), a string containing the variant name. This argument may be
+ * empty, so NULL should be specified as the default value.
+ * 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:
+ * - $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
+ * contains the version-specific information, if specified, and following
+ * additional information is available:
+ * - $library['installed']: A boolean indicating whether the library is
+ * installed or not.
+ * - $library['version']: If it could be detected, a string containing the
+ * version of the library.
+ * - $library['variants'][$variant]['installed']: For each specified
+ * variant, a boolean indicating whether the variant is installed or
+ * not.
+ * Note that in this group the 'versions' property is no longer available.
+ * - pre-dependencies-load: Callbacks registered in this group are applied
+ * directly before the library's dependencies are loaded. At this point
+ * the library contains variant-specific information, if specified. Note
+ * that in this group the 'variants' property is no longer available.
+ * - pre-load: Callbacks registered in this group are applied directly after
+ * the library's dependencies are loaded and before the library itself is
+ * loaded.
+ * - post-load: Callbacks registered in this group are applied directly
+ * after this library is loaded. At this point, the library contains a
+ * 'loaded' key, which contains the number of files that were loaded.
+ * Additional top-level properties can be registered as needed.
+ *
+ * @see hook_library()
+ */
+function hook_libraries_info() {
+ // The following is a full explanation of all properties. See below for more
+ // concrete example implementations.
+
+ // This array key lets Libraries API search for 'sites/all/libraries/example'
+ // directory, which should contain the entire, original extracted library.
+ $libraries['example'] = array(
+ // Only used in administrative UI of Libraries API.
+ 'name' => 'Example library',
+ 'vendor url' => 'http://example.com',
+ 'download url' => 'http://example.com/download',
+ // Optional: If, after extraction, the actual library files are contained in
+ // 'sites/all/libraries/example/lib', specify the relative path here.
+ 'path' => 'lib',
+ // Optional: Define a custom version detection callback, if required.
+ 'version callback' => 'mymodule_get_version',
+ // Specify arguments for the version callback. By default,
+ // libraries_get_version() takes a named argument array:
+ 'version arguments' => array(
+ 'file' => 'docs/CHANGELOG.txt',
+ 'pattern' => '@version\s+([0-9a-zA-Z\.-]+)@',
+ 'lines' => 5,
+ 'cols' => 20,
+ ),
+ // Default list of files of the library to load. Important: Only specify
+ // third-party files belonging to the library here, not integration files of
+ // your module.
+ 'files' => array(
+ // 'js' and 'css' follow the syntax of hook_library(), but file paths are
+ // relative to the library path.
+ 'js' => array(
+ 'exlib.js',
+ 'gadgets/foo.js',
+ ),
+ 'css' => array(
+ 'lib_style.css',
+ 'skin/example.css',
+ ),
+ // For PHP libraries, specify include files here, still relative to the
+ // library path.
+ 'php' => array(
+ 'exlib.php',
+ 'exlib.inc',
+ ),
+ ),
+ // Optional: Specify alternative variants of the library, if available.
+ 'variants' => array(
+ // All properties defined for 'minified' override top-level properties.
+ 'minified' => array(
+ 'files' => array(
+ 'js' => array(
+ 'exlib.min.js',
+ 'gadgets/foo.min.js',
+ ),
+ 'css' => array(
+ 'lib_style.css',
+ 'skin/example.css',
+ ),
+ ),
+ 'variant callback' => 'mymodule_check_variant',
+ 'variant arguments' => array(
+ 'variant' => 'minified',
+ ),
+ ),
+ ),
+ // Optional, but usually required: Override top-level properties for later
+ // versions of the library. The properties of the minimum version that is
+ // matched override the top-level properties. Note:
+ // - When registering 'versions', it usually does not make sense to register
+ // 'files', 'variants', and 'integration files' on the top-level, as most
+ // of those likely need to be different per version and there are no
+ // defaults.
+ // - The array keys have to be strings, as PHP does not support floats for
+ // array keys.
+ 'versions' => array(
+ '2' => array(
+ 'files' => array(
+ 'js' => array('exlib.js'),
+ 'css' => array('exlib_style.css'),
+ ),
+ ),
+ '3.0' => array(
+ 'files' => array(
+ 'js' => array('exlib.js'),
+ 'css' => array('lib_style.css'),
+ ),
+ ),
+ '3.2' => array(
+ 'files' => array(
+ 'js' => array(
+ 'exlib.js',
+ 'gadgets/foo.js',
+ ),
+ 'css' => array(
+ 'lib_style.css',
+ 'skin/example.css',
+ ),
+ ),
+ ),
+ ),
+ // Optional: Register files to auto-load for your module. All files must be
+ // keyed by module, and follow the syntax of the 'files' property.
+ 'integration files' => array(
+ 'mymodule' => array(
+ 'js' => array('ex_lib.inc'),
+ ),
+ ),
+ // Optionally register callbacks to apply to the library during different
+ // stages of its lifetime ('callback groups').
+ 'callbacks' => array(
+ // Used to alter the info associated with the library.
+ 'info' => array(
+ 'mymodule_example_libraries_info_callback',
+ ),
+ // Called before detecting the given library.
+ 'pre-detect' => array(
+ 'mymodule_example_libraries_predetect_callback',
+ ),
+ // Called after detecting the library.
+ 'post-detect' => array(
+ 'mymodule_example_libraries_postdetect_callback',
+ ),
+ // Called before the library's dependencies are loaded.
+ 'pre-dependencies-load' => array(
+ 'mymodule_example_libraries_pre_dependencies_load_callback',
+ ),
+ // Called before the library is loaded.
+ 'pre-load' => array(
+ 'mymodule_example_libraries_preload_callback',
+ ),
+ // Called after the library is loaded.
+ 'post-load' => array(
+ 'mymodule_example_libraries_postload_callback',
+ ),
+ ),
+ );
+
+ // A very simple library. No changing APIs (hence, no versions), no variants.
+ // Expected to be extracted into 'sites/all/libraries/simple'.
+ $libraries['simple'] = array(
+ 'name' => 'Simple library',
+ 'vendor url' => 'http://example.com/simple',
+ 'download url' => 'http://example.com/simple',
+ 'version arguments' => array(
+ 'file' => 'readme.txt',
+ // Best practice: Document the actual version strings for later reference.
+ // 1.x: Version 1.0
+ 'pattern' => '/Version (\d+)/',
+ 'lines' => 5,
+ ),
+ 'files' => array(
+ 'js' => array('simple.js'),
+ ),
+ );
+
+ // A library that (naturally) evolves over time with API changes.
+ $libraries['tinymce'] = array(
+ 'name' => 'TinyMCE',
+ 'vendor url' => 'http://tinymce.moxiecode.com',
+ 'download url' => 'http://tinymce.moxiecode.com/download.php',
+ 'path' => 'jscripts/tiny_mce',
+ // The regular expression catches two parts (the major and the minor
+ // version), which libraries_get_version() doesn't allow.
+ 'version callback' => 'tinymce_get_version',
+ 'version arguments' => array(
+ // It can be easier to parse the first characters of a minified file
+ // instead of doing a multi-line pattern matching in a source file. See
+ // 'lines' and 'cols' below.
+ 'file' => 'jscripts/tiny_mce/tiny_mce.js',
+ // Best practice: Document the actual version strings for later reference.
+ // 2.x: this.majorVersion="2";this.minorVersion="1.3"
+ // 3.x: majorVersion:'3',minorVersion:'2.0.1'
+ 'pattern' => '@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@',
+ 'lines' => 1,
+ 'cols' => 100,
+ ),
+ 'versions' => array(
+ '2.1' => array(
+ 'files' => array(
+ 'js' => array('tiny_mce.js'),
+ ),
+ 'variants' => array(
+ 'source' => array(
+ 'files' => array(
+ 'js' => array('tiny_mce_src.js'),
+ ),
+ ),
+ ),
+ 'integration files' => array(
+ 'wysiwyg' => array(
+ 'js' => array('editors/js/tinymce-2.js'),
+ 'css' => array('editors/js/tinymce-2.css'),
+ ),
+ ),
+ ),
+ // Definition used if 3.1 or above is detected.
+ '3.1' => array(
+ // Does not support JS aggregation.
+ 'files' => array(
+ 'js' => array(
+ 'tiny_mce.js' => array('preprocess' => FALSE),
+ ),
+ ),
+ 'variants' => array(
+ // New variant leveraging jQuery. Not stable yet; therefore not the
+ // default variant.
+ 'jquery' => array(
+ 'files' => array(
+ 'js' => array(
+ 'tiny_mce_jquery.js' => array('preprocess' => FALSE),
+ ),
+ ),
+ ),
+ 'source' => array(
+ 'files' => array(
+ 'js' => array(
+ 'tiny_mce_src.js' => array('preprocess' => FALSE),
+ ),
+ ),
+ ),
+ ),
+ 'integration files' => array(
+ 'wysiwyg' => array(
+ 'js' => array('editors/js/tinymce-3.js'),
+ 'css' => array('editors/js/tinymce-3.css'),
+ ),
+ ),
+ ),
+ ),
+ );
+ return $libraries;
+}
+
+/**
+ * Alter the library information before detection and caching takes place.
+ *
+ * The library definitions are passed by reference. A common use-case is adding
+ * a module's integration files to the library array, so that the files are
+ * loaded whenever the library is. As noted above, it is important to declare
+ * integration files inside of an array, whose key is the module name.
+ *
+ * @see hook_libraries_info()
+ */
+function hook_libraries_info_alter(&$libraries) {
+ $files = array(
+ 'php' => array('example_module.php_spellchecker.inc'),
+ );
+ $libraries['php_spellchecker']['integration files']['example_module'] = $files;
+}
+
+/**
+ * Specify paths to look for library info files.
+ *
+ * Libraries API looks in the following directories for library info files by
+ * default:
+ * - libraries
+ * - profiles/$profile/libraries
+ * - sites/all/libraries
+ * - sites/$site/libraries
+ * This hook allows you to specify additional locations to look for library info
+ * files. This should only be used for modules that declare many libraries.
+ * Modules that only implement a few libraries should implement
+ * hook_libraries_info().
+ *
+ * @return
+ * An array of paths.
+ */
+function hook_libraries_paths() {
+ // Taken from the Libraries test module, which needs to specify the path to
+ // the test library.
+ return array(drupal_get_path('module', 'libraries_test') . '/example');
+}
diff --git a/sites/all/modules/libraries/libraries.drush.inc b/sites/all/modules/libraries/libraries.drush.inc
new file mode 100644
index 000000000..26b35eb83
--- /dev/null
+++ b/sites/all/modules/libraries/libraries.drush.inc
@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * @file
+ * Drush integration for Libraries API.
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function libraries_drush_command() {
+ $items['libraries-list'] = array(
+ 'callback' => 'libraries_drush_list',
+ 'description' => dt('Lists registered library information.'),
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
+ );
+ /**$items['libraries-download'] = array(
+ 'callback' => 'libraries_drush_download',
+ 'description' => dt('Downloads a registered library into the libraries directory for the active site.'),
+ 'arguments' => array(
+ 'name' => dt('The internal name of the registered library.'),
+ ),
+ );*/
+ return $items;
+}
+
+/**
+ * Implements hook_drush_help().
+ */
+function libraries_drush_help($section) {
+ switch ($section) {
+ case 'drush:libraries-list':
+ return dt('Lists registered library information.');
+
+ case 'drush:libraries-download':
+ return dt('Downloads a registered library into the libraries directory for the active site.
+
+See libraries-list for a list of registered libraries.');
+ }
+}
+
+/**
+ * Lists registered library information.
+ */
+function libraries_drush_list() {
+ $libraries = array();
+ foreach (libraries_info() as $name => $info) {
+ $libraries[$name] = libraries_detect($name);
+ }
+ ksort($libraries);
+
+ if (empty($libraries)) {
+ drush_print('There are no registered libraries.');
+ }
+
+ else {
+ $rows = array();
+ // drush_print_table() automatically treats the first row as the header, if
+ // $header is TRUE.
+ $rows[] = array(dt('Name'), dt('Status'), dt('Version'), dt('Variants'), dt('Dependencies'));
+ foreach ($libraries as $name => $library) {
+ $status = ($library['installed'] ? dt('OK') : drupal_ucfirst($library['error']));
+ $version = (($library['installed'] && !empty($library['version'])) ? $library['version'] : '-');
+
+ // Only list installed variants.
+ $variants = array();
+ foreach ($library['variants'] as $variant_name => $variant) {
+ if ($variant['installed']) {
+ $variants[] = $variant_name;
+ }
+ }
+ $variants = (empty($variants) ? '-' : implode(', ', $variants));
+
+ $dependencies = (!empty($library['dependencies']) ? implode(', ', $library['dependencies']) : '-');
+
+ $rows[] = array($name, $status, $version, $variants, $dependencies);
+ }
+ // Make the possible values for the 'Status' column and the 'Version' header
+ // wrap nicely.
+ $widths = array(0, 12, 7, 0, 0);
+ drush_print_table($rows, TRUE, $widths);
+ }
+}
+
+/**
+ * Downloads a library.
+ *
+ * @param $name
+ * The internal name of the library to download.
+ */
+function libraries_drush_download($name) {
+ return;
+
+ // @todo Looks wonky?
+ if (!drush_shell_exec('type unzip')) {
+ return drush_set_error(dt('Missing dependency: unzip. Install it before using this command.'));
+ }
+
+ // @todo Simply use current drush site.
+ $args = func_get_args();
+ if ($args[0]) {
+ $path = $args[0];
+ }
+ else {
+ $path = 'sites/all/libraries';
+ }
+
+ // Create the path if it does not exist.
+ if (!is_dir($path)) {
+ drush_op('mkdir', $path);
+ drush_log(dt('Directory @path was created', array('@path' => $path)), 'notice');
+ }
+
+ // Set the directory to the download location.
+ $olddir = getcwd();
+ chdir($path);
+
+ $filename = basename(COLORBOX_DOWNLOAD_URI);
+ $dirname = basename(COLORBOX_DOWNLOAD_URI, '.zip');
+
+ // Remove any existing Colorbox plugin directory
+ if (is_dir($dirname)) {
+ drush_log(dt('A existing Colorbox plugin was overwritten at @path', array('@path' => $path)), 'notice');
+ }
+ // Remove any existing Colorbox plugin zip archive
+ if (is_file($filename)) {
+ drush_op('unlink', $filename);
+ }
+
+ // Download the zip archive
+ if (!drush_shell_exec('wget '. COLORBOX_DOWNLOAD_URI)) {
+ drush_shell_exec('curl -O '. COLORBOX_DOWNLOAD_URI);
+ }
+
+ if (is_file($filename)) {
+ // Decompress the zip archive
+ drush_shell_exec('unzip -qq -o '. $filename);
+ // Remove the zip archive
+ drush_op('unlink', $filename);
+ }
+
+ // Set working directory back to the previous working directory.
+ chdir($olddir);
+
+ if (is_dir($path .'/'. $dirname)) {
+ drush_log(dt('Colorbox plugin has been downloaded to @path', array('@path' => $path)), 'success');
+ }
+ else {
+ drush_log(dt('Drush was unable to download the Colorbox plugin to @path', array('@path' => $path)), 'error');
+ }
+}
diff --git a/sites/all/modules/libraries/libraries.info b/sites/all/modules/libraries/libraries.info
new file mode 100644
index 000000000..136d323b0
--- /dev/null
+++ b/sites/all/modules/libraries/libraries.info
@@ -0,0 +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 2014-02-09
+version = "7.x-2.2"
+core = "7.x"
+project = "libraries"
+datestamp = "1391965716"
+
diff --git a/sites/all/modules/libraries/libraries.install b/sites/all/modules/libraries/libraries.install
new file mode 100644
index 000000000..b210b98c9
--- /dev/null
+++ b/sites/all/modules/libraries/libraries.install
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Install, uninstall, and update functions for libraries.module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function libraries_schema() {
+ $schema['cache_libraries'] = drupal_get_schema_unprocessed('system', 'cache');
+ $schema['cache_libraries']['description'] = 'Cache table to store library information.';
+ return $schema;
+}
+
+/**
+ * Create the 'cache_libraries' table.
+ */
+function libraries_update_7200() {
+ // Note that previous versions of this function created the table with a
+ // different table comment.
+ if (!db_table_exists('cache_libraries')) {
+ $specs = libraries_schema();
+ db_create_table('cache_libraries', $specs['cache_libraries']);
+ }
+}
diff --git a/sites/all/modules/libraries/libraries.module b/sites/all/modules/libraries/libraries.module
new file mode 100644
index 000000000..0448476d9
--- /dev/null
+++ b/sites/all/modules/libraries/libraries.module
@@ -0,0 +1,869 @@
+<?php
+
+/**
+ * @file
+ * External library handling for Drupal modules.
+ */
+
+/**
+ * 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
+ if (db_table_exists('cache_libraries')) {
+ return array('cache_libraries');
+ }
+}
+
+/**
+ * Gets the path of a library.
+ *
+ * @param $name
+ * The machine name of a library to return the path for.
+ * @param $base_path
+ * Whether to prefix the resulting path with base_path().
+ *
+ * @return
+ * The path to the specified library or FALSE if the library wasn't found.
+ *
+ * @ingroup libraries
+ */
+function libraries_get_path($name, $base_path = FALSE) {
+ $libraries = &drupal_static(__FUNCTION__);
+
+ if (!isset($libraries)) {
+ $libraries = libraries_get_libraries();
+ }
+
+ $path = ($base_path ? base_path() : '');
+ if (!isset($libraries[$name])) {
+ return FALSE;
+ }
+ else {
+ $path .= $libraries[$name];
+ }
+
+ return $path;
+}
+
+/**
+ * Returns an array of library directories.
+ *
+ * Returns an array of library directories from the all-sites directory
+ * (i.e. sites/all/libraries/), the profiles directory, and site-specific
+ * directory (i.e. sites/somesite/libraries/). The returned array will be keyed
+ * by the library name. Site-specific libraries are prioritized over libraries
+ * in the default directories. That is, if a library with the same name appears
+ * in both the site-wide directory and site-specific directory, only the
+ * site-specific version will be listed.
+ *
+ * @return
+ * A list of library directories.
+ *
+ * @ingroup libraries
+ */
+function libraries_get_libraries() {
+ $searchdir = array();
+ $profile = drupal_get_path('profile', drupal_get_profile());
+ $config = conf_path();
+
+ // Similar to 'modules' and 'themes' directories in the root directory,
+ // certain distributions may want to place libraries into a 'libraries'
+ // directory in Drupal's root directory.
+ $searchdir[] = 'libraries';
+
+ // Similar to 'modules' and 'themes' directories inside an installation
+ // profile, installation profiles may want to place libraries into a
+ // 'libraries' directory.
+ $searchdir[] = "$profile/libraries";
+
+ // Always search sites/all/libraries.
+ $searchdir[] = 'sites/all/libraries';
+
+ // Also search sites/<domain>/*.
+ $searchdir[] = "$config/libraries";
+
+ // Retrieve list of directories.
+ $directories = array();
+ $nomask = array('CVS');
+ foreach ($searchdir as $dir) {
+ if (is_dir($dir) && $handle = opendir($dir)) {
+ while (FALSE !== ($file = readdir($handle))) {
+ if (!in_array($file, $nomask) && $file[0] != '.') {
+ if (is_dir("$dir/$file")) {
+ $directories[$file] = "$dir/$file";
+ }
+ }
+ }
+ closedir($handle);
+ }
+ }
+
+ return $directories;
+}
+
+/**
+ * Looks for library info files.
+ *
+ * This function scans the following directories for info files:
+ * - libraries
+ * - profiles/$profilename/libraries
+ * - sites/all/libraries
+ * - sites/$sitename/libraries
+ * - any directories specified via hook_libraries_info_file_paths()
+ *
+ * @return
+ * An array of info files, keyed by library name. The values are the paths of
+ * the files.
+ */
+function libraries_scan_info_files() {
+ $profile = drupal_get_path('profile', drupal_get_profile());
+ $config = conf_path();
+
+ // Build a list of directories.
+ $directories = module_invoke_all('libraries_info_file_paths');
+ $directories[] = 'libraries';
+ $directories[] = "$profile/libraries";
+ $directories[] = 'sites/all/libraries';
+ $directories[] = "$config/libraries";
+
+ // Scan for info files.
+ $files = array();
+ foreach ($directories as $dir) {
+ if (file_exists($dir)) {
+ $files = array_merge($files, file_scan_directory($dir, '@^[A-Za-z0-9._-]+\.libraries\.info$@', array(
+ 'key' => 'name',
+ 'recurse' => FALSE,
+ )));
+ }
+ }
+
+ foreach ($files as $filename => $file) {
+ $files[basename($filename, '.libraries')] = $file;
+ unset($files[$filename]);
+ }
+
+ return $files;
+}
+
+/**
+ * Invokes library callbacks.
+ *
+ * @param $group
+ * A string containing the group of callbacks that is to be applied. Should be
+ * either 'info', 'pre-detect', 'post-detect', or 'load'.
+ * @param $library
+ * 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);
+ }
+}
+
+/**
+ * Helper function to apply a callback to all parts of a library.
+ *
+ * Because library declarations can include variants and versions, and those
+ * version declarations can in turn include variants, modifying e.g. the 'files'
+ * property everywhere it is declared can be quite cumbersome, in which case
+ * this helper function is useful.
+ *
+ * @param $library
+ * An array of library information, passed by reference.
+ * @param $callback
+ * A string containing the callback to apply to all parts of a library.
+ */
+function libraries_traverse_library(&$library, $callback) {
+ // Always apply the callback to the top-level library.
+ $callback($library, NULL, NULL);
+
+ // Apply the callback to versions.
+ if (isset($library['versions'])) {
+ foreach ($library['versions'] as $version_string => &$version) {
+ $callback($version, $version_string, NULL);
+ // Versions can include variants as well.
+ if (isset($version['variants'])) {
+ foreach ($version['variants'] as $version_variant_name => &$version_variant) {
+ $callback($version_variant, $version_string, $version_variant_name);
+ }
+ }
+ }
+ }
+
+ // Apply the callback to variants.
+ if (isset($library['variants'])) {
+ foreach ($library['variants'] as $variant_name => &$variant) {
+ $callback($variant, NULL, $variant_name);
+ }
+ }
+}
+
+/**
+ * Library info callback to make all 'files' properties consistent.
+ *
+ * This turns libraries' file information declared as e.g.
+ * @code
+ * $library['files']['js'] = array('example_1.js', 'example_2.js');
+ * @endcode
+ * into
+ * @code
+ * $library['files']['js'] = array(
+ * 'example_1.js' => array(),
+ * 'example_2.js' => array(),
+ * );
+ * @endcode
+ * It does the same for the 'integration files' property.
+ *
+ * @param $library
+ * An associative array of library information or a part of it, passed by
+ * reference.
+ * @param $version
+ * If the library information belongs to a specific version, the version
+ * string. NULL otherwise.
+ * @param $variant
+ * If the library information belongs to a specific variant, the variant name.
+ * NULL otherwise.
+ *
+ * @see libraries_info()
+ * @see libraries_invoke()
+ */
+function libraries_prepare_files(&$library, $version = NULL, $variant = NULL) {
+ // Both the 'files' property and the 'integration files' property contain file
+ // declarations, and we want to make both consistent.
+ $file_types = array();
+ if (isset($library['files'])) {
+ $file_types[] = &$library['files'];
+ }
+ if (isset($library['integration files'])) {
+ // Integration files are additionally keyed by module.
+ foreach ($library['integration files'] as &$integration_files) {
+ $file_types[] = &$integration_files;
+ }
+ }
+ foreach ($file_types as &$files) {
+ // Go through all supported types of files.
+ foreach (array('js', 'css', 'php') as $type) {
+ if (isset($files[$type])) {
+ foreach ($files[$type] as $key => $value) {
+ // Unset numeric keys and turn the respective values into keys.
+ if (is_numeric($key)) {
+ $files[$type][$value] = array();
+ unset($files[$type][$key]);
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Library post-detect callback to process and detect dependencies.
+ *
+ * It checks whether each of the dependencies of a library are installed and
+ * available in a compatible version.
+ *
+ * @param $library
+ * An associative array of library information or a part of it, passed by
+ * reference.
+ * @param $version
+ * If the library information belongs to a specific version, the version
+ * string. NULL otherwise.
+ * @param $variant
+ * If the library information belongs to a specific variant, the variant name.
+ * NULL otherwise.
+ *
+ * @see libraries_info()
+ * @see libraries_invoke()
+ */
+function libraries_detect_dependencies(&$library, $version = NULL, $variant = NULL) {
+ if (isset($library['dependencies'])) {
+ foreach ($library['dependencies'] as &$dependency_string) {
+ $dependency_info = drupal_parse_dependency($dependency_string);
+ $dependency = libraries_detect($dependency_info['name']);
+ if (!$dependency['installed']) {
+ $library['installed'] = FALSE;
+ $library['error'] = 'missing dependency';
+ $library['error message'] = t('The %dependency library, which the %library library depends on, is not installed.', array(
+ '%dependency' => $dependency['name'],
+ '%library' => $library['name'],
+ ));
+ }
+ elseif (drupal_check_incompatibility($dependency_info, $dependency['version'])) {
+ $library['installed'] = FALSE;
+ $library['error'] = 'incompatible dependency';
+ $library['error message'] = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
+ '%dependency_version' => $dependency['version'],
+ '%dependency' => $dependency['name'],
+ '%library' => $library['name'],
+ ));
+ }
+
+ // Remove the version string from the dependency, so libraries_load() can
+ // load the libraries directly.
+ $dependency_string = $dependency_info['name'];
+ }
+ }
+}
+
+/**
+ * Returns information about registered libraries.
+ *
+ * The returned information is unprocessed; i.e., as registered by modules.
+ *
+ * @param $name
+ * (optional) The machine name of a library to return registered information
+ * for. If omitted, information about all registered libraries is returned.
+ *
+ * @return array|false
+ * An associative array containing registered information for all libraries,
+ * the registered information for the library specified by $name, or FALSE if
+ * the library $name is not registered.
+ *
+ * @see hook_libraries_info()
+ *
+ * @todo Re-introduce support for include file plugin system - either by copying
+ * Wysiwyg's code, or directly switching to CTools.
+ */
+function &libraries_info($name = NULL) {
+ // This static cache is re-used by libraries_detect() to save memory.
+ $libraries = &drupal_static(__FUNCTION__);
+
+ if (!isset($libraries)) {
+ $libraries = array();
+
+ // 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.
+ $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['info type'] = 'theme';
+ $properties['theme'] = $theme_name;
+ $libraries[$machine_name] = $properties;
+ }
+ }
+ }
+ }
+
+ // Gather information from .info files.
+ // .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;
+ }
+
+ // Provide defaults.
+ foreach ($libraries as $machine_name => &$properties) {
+ libraries_info_defaults($properties, $machine_name);
+ }
+
+ // 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) {
+ libraries_invoke('info', $properties);
+ }
+ }
+
+ if (isset($name)) {
+ if (!empty($libraries[$name])) {
+ return $libraries[$name];
+ }
+ else {
+ $false = FALSE;
+ return $false;
+ }
+ }
+ return $libraries;
+}
+
+/**
+ * Applies default properties to a library definition.
+ *
+ * @library
+ * An array of library information, passed by reference.
+ * @name
+ * The machine name of the passed-in library.
+ */
+function libraries_info_defaults(&$library, $name) {
+ $library += array(
+ 'machine name' => $name,
+ 'name' => $name,
+ 'vendor url' => '',
+ 'download url' => '',
+ 'path' => '',
+ 'library path' => NULL,
+ 'version callback' => 'libraries_get_version',
+ 'version arguments' => array(),
+ 'files' => array(),
+ 'dependencies' => array(),
+ 'variants' => array(),
+ 'versions' => array(),
+ 'integration files' => array(),
+ 'callbacks' => array(),
+ // @todo Remove in 7.x-3.x
+ 'post-load integration files' => FALSE,
+ );
+ $library['callbacks'] += array(
+ 'info' => array(),
+ 'pre-detect' => array(),
+ 'post-detect' => array(),
+ 'pre-dependencies-load' => array(),
+ 'pre-load' => array(),
+ 'post-load' => array(),
+ );
+
+ // Add our own callbacks before any others.
+ array_unshift($library['callbacks']['info'], 'libraries_prepare_files');
+ array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies');
+
+ return $library;
+}
+
+/**
+ * Tries to detect a library and its installed version.
+ *
+ * @param $name
+ * The machine name of a library to return registered information for.
+ *
+ * @return array|false
+ * An associative array containing registered information for the library
+ * specified by $name, or FALSE if the library $name is not registered.
+ * In addition to the keys returned by libraries_info(), the following keys
+ * are contained:
+ * - installed: A boolean indicating whether the library is installed. Note
+ * that not only the top-level library, but also each variant contains this
+ * key.
+ * - version: If the version could be detected, the full version string.
+ * - error: If an error occurred during library detection, one of the
+ * following error statuses: "not found", "not detected", "not supported".
+ * - error message: If an error occurred during library detection, a detailed
+ * error message.
+ *
+ * @see libraries_info()
+ */
+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;
+ }
+
+ $library['installed'] = FALSE;
+
+ // Check whether the library exists.
+ if (!isset($library['library path'])) {
+ $library['library path'] = libraries_get_path($library['machine name']);
+ }
+ if ($library['library path'] === FALSE || !file_exists($library['library path'])) {
+ $library['error'] = 'not found';
+ $library['error message'] = t('The %library library could not be found.', array(
+ '%library' => $library['name'],
+ ));
+ return $library;
+ }
+
+ // Invoke callbacks in the 'pre-detect' group.
+ libraries_invoke('pre-detect', $library);
+
+ // Detect library version, if not hardcoded.
+ if (!isset($library['version'])) {
+ // We support both a single parameter, which is an associative array, and an
+ // indexed array of multiple parameters.
+ if (isset($library['version arguments'][0])) {
+ // Add the library as the first argument.
+ $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments']));
+ }
+ else {
+ $library['version'] = $library['version callback']($library, $library['version arguments']);
+ }
+ if (empty($library['version'])) {
+ $library['error'] = 'not detected';
+ $library['error message'] = t('The version of the %library library could not be detected.', array(
+ '%library' => $library['name'],
+ ));
+ return $library;
+ }
+ }
+
+ // Determine to which supported version the installed version maps.
+ if (!empty($library['versions'])) {
+ ksort($library['versions']);
+ $version = 0;
+ foreach ($library['versions'] as $supported_version => $version_properties) {
+ if (version_compare($library['version'], $supported_version, '>=')) {
+ $version = $supported_version;
+ }
+ }
+ if (!$version) {
+ $library['error'] = 'not supported';
+ $library['error message'] = t('The installed version %version of the %library library is not supported.', array(
+ '%version' => $library['version'],
+ '%library' => $library['name'],
+ ));
+ return $library;
+ }
+
+ // Apply version specific definitions and overrides.
+ $library = array_merge($library, $library['versions'][$version]);
+ unset($library['versions']);
+ }
+
+ // Check each variant if it is installed.
+ if (!empty($library['variants'])) {
+ foreach ($library['variants'] as $variant_name => &$variant) {
+ // If no variant callback has been set, assume the variant to be
+ // installed.
+ if (!isset($variant['variant callback'])) {
+ $variant['installed'] = TRUE;
+ }
+ else {
+ // We support both a single parameter, which is an associative array,
+ // and an indexed array of multiple parameters.
+ if (isset($variant['variant arguments'][0])) {
+ // Add the library as the first argument, and the variant name as the second.
+ $variant['installed'] = call_user_func_array($variant['variant callback'], array_merge(array($library, $variant_name), $variant['variant arguments']));
+ }
+ else {
+ $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']);
+ }
+ if (!$variant['installed']) {
+ $variant['error'] = 'not found';
+ $variant['error message'] = t('The %variant variant of the %library library could not be found.', array(
+ '%variant' => $variant_name,
+ '%library' => $library['name'],
+ ));
+ }
+ }
+ }
+ }
+
+ // If we end up here, the library should be usable.
+ $library['installed'] = TRUE;
+
+ // Invoke callbacks in the 'post-detect' group.
+ libraries_invoke('post-detect', $library);
+
+ return $library;
+}
+
+/**
+ * Loads a library.
+ *
+ * @param $name
+ * The name of the library to load.
+ * @param $variant
+ * The name of the variant to load. Note that only one variant of a library
+ * can be loaded within a single request. The variant that has been passed
+ * first is used; different variant names in subsequent calls are ignored.
+ *
+ * @return
+ * An associative array of the library information as returned from
+ * libraries_info(). The top-level properties contain the effective definition
+ * of the library (variant) that has been loaded. Additionally:
+ * - installed: Whether the library is installed, as determined by
+ * libraries_detect_library().
+ * - loaded: Either the amount of library files that have been loaded, or
+ * FALSE if the library could not be loaded.
+ * See hook_libraries_info() for more information.
+ */
+function libraries_load($name, $variant = NULL) {
+ $loaded = &drupal_static(__FUNCTION__, array());
+
+ if (!isset($loaded[$name])) {
+ $library = cache_get($name, 'cache_libraries');
+ if ($library) {
+ $library = $library->data;
+ }
+ else {
+ $library = libraries_detect($name);
+ 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)) {
+ // Ensure that the $variant key exists, and if it does not, set its
+ // 'installed' property to FALSE by default. This will prevent the loading
+ // of the library files below.
+ $library['variants'] += array($variant => array('installed' => FALSE));
+ $library = array_merge($library, $library['variants'][$variant]);
+ }
+ // Regardless of whether a specific variant was requested or not, there can
+ // only be one variant of a library within a single request.
+ unset($library['variants']);
+
+ // Invoke callbacks in the 'pre-dependencies-load' group.
+ libraries_invoke('pre-dependencies-load', $library);
+
+ // If the library (variant) is installed, load it.
+ $library['loaded'] = FALSE;
+ if ($library['installed']) {
+ // Load library dependencies.
+ if (isset($library['dependencies'])) {
+ foreach ($library['dependencies'] as $dependency) {
+ libraries_load($dependency);
+ }
+ }
+
+ // Invoke callbacks in the 'pre-load' group.
+ libraries_invoke('pre-load', $library);
+
+ // Load all the files associated with the library.
+ $library['loaded'] = libraries_load_files($library);
+
+ // Invoke callbacks in the 'post-load' group.
+ libraries_invoke('post-load', $library);
+ }
+ $loaded[$name] = $library;
+ }
+
+ return $loaded[$name];
+}
+
+/**
+ * Loads a library's files.
+ *
+ * @param $library
+ * An array of library information as returned by libraries_info().
+ *
+ * @return
+ * The number of loaded files.
+ */
+function libraries_load_files($library) {
+ // 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,
+ ));
+ }
+ }
+ }
+
+ // Construct the full path to the library for later use.
+ $path = $library['library path'];
+ $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path);
+
+ // Count the number of loaded files for the return value.
+ $count = 0;
+
+ // Load both the JavaScript and the CSS files.
+ // The parameters for drupal_add_js() and drupal_add_css() require special
+ // handling.
+ // @see drupal_process_attached()
+ foreach (array('js', 'css') as $type) {
+ if (!empty($library['files'][$type])) {
+ foreach ($library['files'][$type] as $data => $options) {
+ // If the value is not an array, it's a filename and passed as first
+ // (and only) argument.
+ if (!is_array($options)) {
+ $data = $options;
+ $options = array();
+ }
+ // In some cases, the first parameter ($data) is an array. Arrays can't
+ // be passed as keys in PHP, so we have to get $data from the value
+ // array.
+ if (is_numeric($data)) {
+ $data = $options['data'];
+ unset($options['data']);
+ }
+ // Prepend the library path to the file name.
+ $data = "$path/$data";
+ // Apply the default group if the group isn't explicitly given.
+ if (!isset($options['group'])) {
+ $options['group'] = ($type == 'js') ? JS_DEFAULT : CSS_DEFAULT;
+ }
+ call_user_func('drupal_add_' . $type, $data, $options);
+ $count++;
+ }
+ }
+ }
+
+ // Load PHP files.
+ if (!empty($library['files']['php'])) {
+ foreach ($library['files']['php'] as $file => $array) {
+ $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file;
+ if (file_exists($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.
+ *
+ * @param $library
+ * An associative array containing all information about the library.
+ * @param $options
+ * An associative array containing with the following keys:
+ * - file: The filename to parse for the version, relative to the library
+ * path. 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.
+ * '@version 1.2.3' in the above example) but the match of the first
+ * sub-pattern (i.e. '1.2.3' in the above example).
+ * - lines: (optional) The maximum number of lines to search the pattern in.
+ * Defaults to 20.
+ * - 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.
+ *
+ * @return
+ * A string containing the version of the library.
+ *
+ * @see libraries_get_path()
+ */
+function libraries_get_version($library, $options) {
+ // Provide defaults.
+ $options += array(
+ 'file' => '',
+ 'pattern' => '',
+ 'lines' => 20,
+ 'cols' => 200,
+ );
+
+ $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
+ if (empty($options['file']) || !file_exists($file)) {
+ return;
+ }
+ $file = fopen($file, 'r');
+ while ($options['lines'] && $line = fgets($file, $options['cols'])) {
+ if (preg_match($options['pattern'], $line, $version)) {
+ fclose($file);
+ return $version[1];
+ }
+ $options['lines']--;
+ }
+ fclose($file);
+}
diff --git a/sites/all/modules/libraries/tests/libraries.test b/sites/all/modules/libraries/tests/libraries.test
new file mode 100644
index 000000000..419263181
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries.test
@@ -0,0 +1,573 @@
+<?php
+
+/**
+ * @file
+ * Tests for Libraries API.
+ */
+
+/**
+ * Tests basic Libraries API functions.
+ */
+class LibrariesUnitTestCase extends DrupalUnitTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Libraries API unit tests',
+ 'description' => 'Tests basic functions provided by Libraries API.',
+ 'group' => 'Libraries API',
+ );
+ }
+
+ function setUp() {
+ drupal_load('module', 'libraries');
+ parent::setUp();
+ }
+
+ /**
+ * Tests libraries_get_path().
+ */
+ function testLibrariesGetPath() {
+ // Note that, even though libraries_get_path() doesn't find the 'example'
+ // library, we are able to make it 'installed' by specifying the 'library
+ // path' up-front. This is only used for testing purposed and is strongly
+ // discouraged as it defeats the purpose of Libraries API in the first
+ // place.
+ $this->assertEqual(libraries_get_path('example'), FALSE, 'libraries_get_path() returns FALSE for a missing library.');
+ }
+
+ /**
+ * Tests libraries_prepare_files().
+ */
+ function testLibrariesPrepareFiles() {
+ $expected = array(
+ 'files' => array(
+ 'js' => array('example.js' => array()),
+ 'css' => array('example.css' => array()),
+ 'php' => array('example.php' => array()),
+ ),
+ );
+ $library = array(
+ 'files' => array(
+ 'js' => array('example.js'),
+ 'css' => array('example.css'),
+ 'php' => array('example.php'),
+ ),
+ );
+ libraries_prepare_files($library, NULL, NULL);
+ $this->assertEqual($expected, $library, 'libraries_prepare_files() works correctly.');
+ }
+}
+
+/**
+ * Tests basic detection and loading of libraries.
+ */
+class LibrariesTestCase extends DrupalWebTestCase {
+ protected $profile = 'testing';
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Libraries detection and loading',
+ 'description' => 'Tests detection and loading of libraries.',
+ 'group' => 'Libraries API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('libraries', 'libraries_test_module');
+ theme_enable(array('libraries_test_theme'));
+ }
+
+ /**
+ * Tests libraries_detect_dependencies().
+ */
+ function testLibrariesDetectDependencies() {
+ $library = array(
+ 'name' => 'Example',
+ 'dependencies' => array('example_missing'),
+ );
+ libraries_detect_dependencies($library);
+ $this->assertEqual($library['error'], 'missing dependency', 'libraries_detect_dependencies() detects missing dependency');
+ $error_message = t('The %dependency library, which the %library library depends on, is not installed.', array(
+ '%dependency' => 'Example missing',
+ '%library' => $library['name'],
+ ));
+ $this->verbose("Expected:<br>$error_message");
+ $this->verbose('Actual:<br>' . $library['error message']);
+ $this->assertEqual($library['error message'], $error_message, 'Correct error message for a missing dependency');
+ // Test versioned dependencies.
+ $version = '1.1';
+ $compatible = array(
+ '1.1',
+ '<=1.1',
+ '>=1.1',
+ '<1.2',
+ '<2.0',
+ '>1.0',
+ '>1.0-rc1',
+ '>1.0-beta2',
+ '>1.0-alpha3',
+ '>0.1',
+ '<1.2, >1.0',
+ '>0.1, <=1.1',
+ );
+ $incompatible = array(
+ '1.2',
+ '2.0',
+ '<1.1',
+ '>1.1',
+ '<=1.0',
+ '<=1.0-rc1',
+ '<=1.0-beta2',
+ '<=1.0-alpha3',
+ '>=1.2',
+ '<1.1, >0.9',
+ '>=0.1, <1.1',
+ );
+ $library = array(
+ 'name' => 'Example',
+ );
+ foreach ($compatible as $version_string) {
+ $library['dependencies'][0] = "example_dependency ($version_string)";
+ // libraries_detect_dependencies() is a post-detect callback, so
+ // 'installed' is already set, when it is called. It sets the value to
+ // FALSE for missing or incompatible dependencies.
+ $library['installed'] = TRUE;
+ libraries_detect_dependencies($library);
+ $this->verbose('Library:<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertTrue($library['installed'], "libraries_detect_dependencies() detects compatible version string: '$version_string' is compatible with '$version'");
+ }
+ foreach ($incompatible as $version_string) {
+ $library['dependencies'][0] = "example_dependency ($version_string)";
+ $library['installed'] = TRUE;
+ unset($library['error'], $library['error message']);
+ libraries_detect_dependencies($library);
+ $this->verbose('Library:<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['error'], 'incompatible dependency', "libraries_detect_dependencies() detects incompatible version strings: '$version_string' is incompatible with '$version'");
+ }
+ // Instead of repeating this assertion for each version string, we just
+ // re-use the $library variable from the foreach loop.
+ $error_message = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
+ '%dependency_version' => $version,
+ '%dependency' => 'Example dependency',
+ '%library' => $library['name'],
+ ));
+ $this->verbose("Expected:<br>$error_message");
+ $this->verbose('Actual:<br>' . $library['error message']);
+ $this->assertEqual($library['error message'], $error_message, 'Correct error message for an incompatible dependency');
+ }
+
+ /**
+ * Tests libraries_scan_info_files().
+ */
+ function testLibrariesScanInfoFiles() {
+ $expected = array('example_info_file' => (object) array(
+ 'uri' => drupal_get_path('module', 'libraries') . '/tests/libraries/example_info_file.libraries.info',
+ 'filename' => 'example_info_file.libraries.info',
+ 'name' => 'example_info_file.libraries',
+ ));
+ $this->assertEqual(libraries_scan_info_files(), $expected, 'libraries_scan_info_files() correctly finds the example info file.');
+ $this->verbose('<pre>' . var_export(libraries_scan_info_files(), TRUE) . '</pre>');
+ }
+
+ /**
+ * Tests libraries_info().
+ */
+ function testLibrariesInfo() {
+ // Test that modules can provide and alter library information.
+ $info = libraries_info();
+ $this->assertTrue(isset($info['example_module']));
+ $this->verbose('Library:<pre>' . var_export($info['example_module'], TRUE) . '</pre>');
+ $this->assertEqual($info['example_module']['info type'], 'module');
+ $this->assertEqual($info['example_module']['module'], 'libraries_test_module');
+ $this->assertTrue($info['example_module']['module_altered']);
+
+ // Test that themes can provide and alter library information.
+ $this->assertTrue(isset($info['example_theme']));
+ $this->verbose('Library:<pre>' . var_export($info['example_theme'], TRUE) . '</pre>');
+ $this->assertEqual($info['example_theme']['info type'], 'theme');
+ $this->assertEqual($info['example_theme']['theme'], 'libraries_test_theme');
+ $this->assertTrue($info['example_theme']['theme_altered']);
+
+ // Test that library information is found correctly.
+ $expected = array(
+ 'name' => 'Example files',
+ 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example',
+ 'version' => '1',
+ 'files' => array(
+ 'js' => array('example_1.js' => array()),
+ 'css' => array('example_1.css' => array()),
+ 'php' => array('example_1.php' => array()),
+ ),
+ 'info type' => 'module',
+ 'module' => 'libraries_test_module',
+ );
+ libraries_info_defaults($expected, 'example_files');
+ $library = libraries_info('example_files');
+ $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+ $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library, $expected, 'Library information is correctly gathered.');
+
+ // Test a library specified with an .info file gets detected.
+ $expected = array(
+ 'name' => 'Example info file',
+ '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');
+ // If this module was downloaded from Drupal.org, the Drupal.org packaging
+ // system has corrupted the test info file.
+ // @see http://drupal.org/node/1606606
+ unset($library['core'], $library['datestamp'], $library['project'], $library['version']);
+ $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+ $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library, $expected, 'Library specified with an .info file found');
+ }
+
+ /**
+ * Tests libraries_detect().
+ */
+ function testLibrariesDetect() {
+ // Test missing library.
+ $library = libraries_detect('example_missing');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['error'], 'not found', 'Missing library not found.');
+ $error_message = t('The %library library could not be found.', array(
+ '%library' => $library['name'],
+ ));
+ $this->assertEqual($library['error message'], $error_message, 'Correct error message for a missing library.');
+
+ // Test unknown library version.
+ $library = libraries_detect('example_undetected_version');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['error'], 'not detected', 'Undetected version detected as such.');
+ $error_message = t('The version of the %library library could not be detected.', array(
+ '%library' => $library['name'],
+ ));
+ $this->assertEqual($library['error message'], $error_message, 'Correct error message for a library with an undetected version.');
+
+ // Test unsupported library version.
+ $library = libraries_detect('example_unsupported_version');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['error'], 'not supported', 'Unsupported version detected as such.');
+ $error_message = t('The installed version %version of the %library library is not supported.', array(
+ '%version' => $library['version'],
+ '%library' => $library['name'],
+ ));
+ $this->assertEqual($library['error message'], $error_message, 'Correct error message for a library with an unsupported version.');
+
+ // Test supported library version.
+ $library = libraries_detect('example_supported_version');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['installed'], TRUE, 'Supported library version found.');
+
+ // Test libraries_get_version().
+ $library = libraries_detect('example_default_version_callback');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['version'], '1', 'Expected version returned by default version callback.');
+
+ // Test a multiple-parameter version callback.
+ $library = libraries_detect('example_multiple_parameter_version_callback');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['version'], '1', 'Expected version returned by multiple parameter version callback.');
+
+ // Test a top-level files property.
+ $library = libraries_detect('example_files');
+ $files = array(
+ 'js' => array('example_1.js' => array()),
+ 'css' => array('example_1.css' => array()),
+ 'php' => array('example_1.php' => array()),
+ );
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['files'], $files, 'Top-level files property works.');
+
+ // Test version-specific library files.
+ $library = libraries_detect('example_versions');
+ $files = array(
+ 'js' => array('example_2.js' => array()),
+ 'css' => array('example_2.css' => array()),
+ 'php' => array('example_2.php' => array()),
+ );
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['files'], $files, 'Version-specific library files found.');
+
+ // Test missing variant.
+ $library = libraries_detect('example_variant_missing');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['variants']['example_variant']['error'], 'not found', 'Missing variant not found');
+ $error_message = t('The %variant variant of the %library library could not be found.', array(
+ '%variant' => 'example_variant',
+ '%library' => 'Example variant missing',
+ ));
+ $this->assertEqual($library['variants']['example_variant']['error message'], $error_message, 'Correct error message for a missing variant.');
+
+ // Test existing variant.
+ $library = libraries_detect('example_variant');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.');
+ }
+
+ /**
+ * Tests libraries_load().
+ */
+ function testLibrariesLoad() {
+ // Test dependencies.
+ $library = libraries_load('example_dependency_missing');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertFalse($library['loaded'], 'Library with missing dependency cannot be loaded');
+ $library = libraries_load('example_dependency_incompatible');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertFalse($library['loaded'], 'Library with incompatible dependency cannot be loaded');
+ $library = libraries_load('example_dependency_compatible');
+ $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library['loaded'], 1, 'Library with compatible dependency is loaded');
+ $loaded = &drupal_static('libraries_load');
+ $this->verbose('<pre>' . var_export($loaded, TRUE) . '</pre>');
+ $this->assertEqual($loaded['example_dependency']['loaded'], 1, 'Dependency library is also loaded');
+
+ // Test that PHP files that have a local $path variable do not break library
+ // loading.
+ // @see _libraries_require_once()
+ $library = libraries_load('example_path_variable_override');
+ $this->assertEqual($library['loaded'], 2, 'PHP files cannot break library loading.');
+ }
+
+ /**
+ * Tests the applying of callbacks.
+ */
+ function testCallbacks() {
+ $expected = array(
+ 'name' => 'Example callback',
+ 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example',
+ 'version' => '1',
+ 'versions' => array(
+ '1' => array(
+ 'variants' => array(
+ 'example_variant' => array(
+ 'info callback' => 'not applied',
+ 'pre-detect callback' => 'not applied',
+ 'post-detect callback' => 'not applied',
+ 'pre-dependencies-load callback' => 'not applied',
+ 'pre-load callback' => 'not applied',
+ 'post-load callback' => 'not applied',
+ ),
+ ),
+ 'info callback' => 'not applied',
+ 'pre-detect callback' => 'not applied',
+ 'post-detect callback' => 'not applied',
+ 'pre-dependencies-load callback' => 'not applied',
+ 'pre-load callback' => 'not applied',
+ 'post-load callback' => 'not applied',
+ ),
+ ),
+ 'variants' => array(
+ 'example_variant' => array(
+ 'info callback' => 'not applied',
+ 'pre-detect callback' => 'not applied',
+ 'post-detect callback' => 'not applied',
+ 'pre-dependencies-load callback' => 'not applied',
+ 'pre-load callback' => 'not applied',
+ 'post-load callback' => 'not applied',
+ ),
+ ),
+ 'callbacks' => array(
+ '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',
+ 'post-detect callback' => 'not applied',
+ 'pre-dependencies-load callback' => 'not applied',
+ 'pre-load callback' => 'not applied',
+ 'post-load callback' => 'not applied',
+ 'info type' => 'module',
+ 'module' => 'libraries_test_module',
+ );
+ libraries_info_defaults($expected, 'example_callback');
+
+ // Test a callback in the 'info' group.
+ $expected['info callback'] = 'applied (top-level)';
+ $expected['versions']['1']['info callback'] = 'applied (version 1)';
+ $expected['versions']['1']['variants']['example_variant']['info callback'] = 'applied (version 1, variant example_variant)';
+ $expected['variants']['example_variant']['info callback'] = 'applied (variant example_variant)';
+ $library = libraries_info('example_callback');
+ $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+ $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library, $expected, 'Prepare callback was applied correctly.');
+
+ // Test a callback in the 'pre-detect' and 'post-detect' phases.
+ // Successfully detected libraries should only contain version information
+ // for the detected version and thus, be marked as installed.
+ unset($expected['versions']);
+ $expected['installed'] = TRUE;
+ // Additionally, version-specific properties of the detected version are
+ // supposed to override the corresponding top-level properties.
+ $expected['info callback'] = 'applied (version 1)';
+ $expected['variants']['example_variant']['installed'] = TRUE;
+ $expected['variants']['example_variant']['info callback'] = 'applied (version 1, variant example_variant)';
+ // Version-overloading takes place after the 'pre-detect' callbacks have
+ // been applied.
+ $expected['pre-detect callback'] = 'applied (version 1)';
+ $expected['post-detect callback'] = 'applied (top-level)';
+ $expected['variants']['example_variant']['pre-detect callback'] = 'applied (version 1, variant example_variant)';
+ $expected['variants']['example_variant']['post-detect callback'] = 'applied (variant example_variant)';
+ $library = libraries_detect('example_callback');
+ $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+ $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library, $expected, 'Detect callback was applied correctly.');
+
+ // Test a callback in the 'pre-dependencies-load', 'pre-load' and
+ // 'post-load' phases.
+ // Successfully loaded libraries should only contain information about the
+ // already loaded variant.
+ unset($expected['variants']);
+ $expected['loaded'] = 0;
+ $expected['pre-dependencies-load callback'] = 'applied (top-level)';
+ $expected['pre-load callback'] = 'applied (top-level)';
+ $expected['post-load callback'] = 'applied (top-level)';
+ $library = libraries_load('example_callback');
+ $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+ $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library, $expected, 'Pre-load and post-load callbacks were applied correctly.');
+ // This is not recommended usually and is only used for testing purposes.
+ drupal_static_reset('libraries_load');
+ // Successfully loaded library variants are supposed to contain the specific
+ // variant information only.
+ $expected['info callback'] = 'applied (version 1, variant example_variant)';
+ $expected['pre-detect callback'] = 'applied (version 1, variant example_variant)';
+ $expected['post-detect callback'] = 'applied (variant example_variant)';
+ $library = libraries_load('example_callback', 'example_variant');
+ $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+ $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+ $this->assertEqual($library, $expected, 'Pre-detect and post-detect callbacks were applied correctly to a variant.');
+ }
+
+ /**
+ * Tests that library files are properly added to the page output.
+ *
+ * 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_module_load()
+ */
+ function testLibrariesOutput() {
+ // Test loading of a simple library with a top-level files property.
+ $this->drupalGet('libraries-test-module/files');
+ $this->assertLibraryFiles('example_1', 'File loading');
+
+ // Test loading of integration files.
+ $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-module/versions');
+ $this->assertLibraryFiles('example_2', 'Version overloading');
+
+ // Test variant loading.
+ $this->drupalGet('libraries-test-module/variant');
+ $this->assertLibraryFiles('example_3', 'Variant loading');
+
+ // Test version overloading and variant loading.
+ $this->drupalGet('libraries-test-module/versions-and-variants');
+ $this->assertLibraryFiles('example_4', 'Concurrent version and variant overloading');
+
+ // Test caching.
+ 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-module/cache');
+ $this->assertRaw('The <em>info</em> callback group was invoked.', 'Info callback invoked for uncached libraries.');
+ $this->assertRaw('The <em>pre-detect</em> callback group was invoked.', 'Pre-detect callback invoked for uncached libraries.');
+ $this->assertRaw('The <em>post-detect</em> callback group was invoked.', 'Post-detect callback invoked for uncached libraries.');
+ $this->assertRaw('The <em>pre-load</em> callback group was invoked.', 'Pre-load callback invoked for uncached libraries.');
+ $this->assertRaw('The <em>post-load</em> callback group was invoked.', 'Post-load callback invoked for uncached libraries.');
+ // When the library information is cached only the 'pre-load' and
+ // 'post-load' callback groups should be invoked.
+ $this->drupalGet('libraries-test-module/cache');
+ $this->assertNoRaw('The <em>info</em> callback group was not invoked.', 'Info callback not invoked for cached libraries.');
+ $this->assertNoRaw('The <em>pre-detect</em> callback group was not invoked.', 'Pre-detect callback not invoked for cached libraries.');
+ $this->assertNoRaw('The <em>post-detect</em> callback group was not invoked.', 'Post-detect callback not invoked for cached libraries.');
+ $this->assertRaw('The <em>pre-load</em> callback group was invoked.', 'Pre-load callback invoked for cached libraries.');
+ $this->assertRaw('The <em>post-load</em> callback group was invoked.', 'Post-load callback invoked for cached libraries.');
+ variable_set('libraries_test_module_cache', FALSE);
+ }
+
+ /**
+ * Helper function to assert that a library was correctly loaded.
+ *
+ * Asserts that all the correct files were loaded and all the incorrect ones
+ * were not.
+ *
+ * @param $name
+ * The name of the files that should be loaded. The current testing system
+ * knows of 'example_1', 'example_2', 'example_3' and 'example_4'. Each name
+ * has an associated JavaScript, CSS and PHP file that will be asserted. All
+ * other files will be asserted to not be loaded. See
+ * tests/example/README.txt for more information on how the loading of the
+ * files is tested.
+ * @param $label
+ * (optional) A label to prepend to the assertion messages, to make them
+ * less ambiguous.
+ * @param $extensions
+ * (optional) The expected file extensions of $name. Defaults to
+ * array('js', 'css', 'php').
+ */
+ function assertLibraryFiles($name, $label = '', $extensions = array('js', 'css', 'php')) {
+ $label = ($label !== '' ? "$label: " : '');
+
+ // Test that the wrong files are not loaded...
+ $names = array(
+ 'example_1' => FALSE,
+ 'example_2' => FALSE,
+ 'example_3' => FALSE,
+ 'example_4' => FALSE,
+ );
+ // ...and the correct ones are.
+ $names[$name] = TRUE;
+
+ // Test for the specific HTML that the different file types appear as in the
+ // DOM.
+ $html = array(
+ 'js' => array('<script type="text/javascript" src="', '"></script>'),
+ 'css' => array('@import url("', '");'),
+ // PHP files do not get added to the DOM directly.
+ // @see _libraries_test_load()
+ 'php' => array('<li>', '</li>'),
+ );
+
+ foreach ($names as $name => $expected) {
+ foreach ($extensions as $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'))) {
+ $filepath = url('', array('absolute' => TRUE)) . $filepath . '?' . variable_get('css_js_query_string');
+ }
+ $raw = $html[$extension][0] . $filepath . $html[$extension][1];
+ if ($expected) {
+ $this->assertRaw($raw, "$label$name.$extension found.");
+ }
+ else {
+ $this->assertNoRaw($raw, "$label$name.$extension not found.");
+ }
+ }
+ }
+ }
+
+}
+
diff --git a/sites/all/modules/libraries/tests/libraries/example/README.txt b/sites/all/modules/libraries/tests/libraries/example/README.txt
new file mode 100644
index 000000000..6c50a58ae
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/README.txt
@@ -0,0 +1,43 @@
+
+Example library
+
+Version 1
+
+This file is an example file to test version detection.
+
+The various other files in this directory are to test the loading of JavaScript,
+CSS and PHP files.
+- JavaScript: The filenames of the JavaScript 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 JavaScript,
+ though, is not yet possible with SimpleTest. To allow for easier debugging, we
+ 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."
+ [file] is either 'example_1.js', 'example_2.js', 'example_3.js',
+ '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
+ possible with SimpleTest. Hence, the CSS files, if loaded, make the following
+ text a certain color:
+ "If one of the CSS test files has been loaded, this text will be colored:
+ - example_1: red
+ - example_2: green
+ - example_3: orange
+ - example_4: blue
+ - 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.
+- PHP: The loading of PHP files is tested by defining a dummy function in the
+ 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_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/libraries/tests/libraries/example/example_1.css b/sites/all/modules/libraries/tests/libraries/example/example_1.css
new file mode 100644
index 000000000..ed9ea3c27
--- /dev/null
+++ b/sites/all/modules/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/libraries/tests/libraries/example/example_1.js b/sites/all/modules/libraries/tests/libraries/example/example_1.js
new file mode 100644
index 000000000..659ff0ff6
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_1.js
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-module-js' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+ attach: function(context, settings) {
+ $('.libraries-test-module-js').text('If this text shows up, example_1.js was loaded successfully.')
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_1.php b/sites/all/modules/libraries/tests/libraries/example/example_1.php
new file mode 100644
index 000000000..4142e4116
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_1.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+// @see _libraries_require_once()
+$path = 'abc';
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_module_example_1() {
+}
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_2.css b/sites/all/modules/libraries/tests/libraries/example/example_2.css
new file mode 100644
index 000000000..2bd923973
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_2.css
@@ -0,0 +1,12 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-module-css' div green. See README.txt for more
+ * information.
+ */
+
+.libraries-test-module-css {
+ color: green;
+}
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_2.js b/sites/all/modules/libraries/tests/libraries/example/example_2.js
new file mode 100644
index 000000000..6a17a6d85
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_2.js
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-module-js' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+ attach: function(context, settings) {
+ $('.libraries-test-module-js').text('If this text shows up, example_2.js was loaded successfully.')
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_2.php b/sites/all/modules/libraries/tests/libraries/example/example_2.php
new file mode 100644
index 000000000..8dc6d305a
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_2.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+// @see _libraries_require_once()
+$path = 'abc';
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_module_example_2() {
+}
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_3.css b/sites/all/modules/libraries/tests/libraries/example/example_3.css
new file mode 100644
index 000000000..fa29df459
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_3.css
@@ -0,0 +1,12 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-module-css' div orange. See README.txt for more
+ * information.
+ */
+
+.libraries-test-module-css {
+ color: orange;
+}
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_3.js b/sites/all/modules/libraries/tests/libraries/example/example_3.js
new file mode 100644
index 000000000..f47402da7
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_3.js
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-module-js' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+ attach: function(context, settings) {
+ $('.libraries-test-module-js').text('If this text shows up, example_3.js was loaded successfully.')
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_3.php b/sites/all/modules/libraries/tests/libraries/example/example_3.php
new file mode 100644
index 000000000..3734f38f9
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_3.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_module_example_3() {
+}
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_4.css b/sites/all/modules/libraries/tests/libraries/example/example_4.css
new file mode 100644
index 000000000..137f65606
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_4.css
@@ -0,0 +1,12 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-module-css' div blue. See README.txt for more
+ * information.
+ */
+
+.libraries-test-module-css {
+ color: blue;
+}
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_4.js b/sites/all/modules/libraries/tests/libraries/example/example_4.js
new file mode 100644
index 000000000..c097735f5
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_4.js
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-module-js' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+ attach: function(context, settings) {
+ $('.libraries-test-module-js').text('If this text shows up, example_4.js was loaded successfully.')
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/libraries/tests/libraries/example/example_4.php b/sites/all/modules/libraries/tests/libraries/example/example_4.php
new file mode 100644
index 000000000..c5f8dcb52
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example/example_4.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_module_example_4() {
+}
diff --git a/sites/all/modules/libraries/tests/libraries/example_info_file.libraries.info b/sites/all/modules/libraries/tests/libraries/example_info_file.libraries.info
new file mode 100644
index 000000000..59475b4eb
--- /dev/null
+++ b/sites/all/modules/libraries/tests/libraries/example_info_file.libraries.info
@@ -0,0 +1,10 @@
+; This is an example info file of a library used for testing purposes.
+name = Example info file
+
+
+; Information added by Drupal.org packaging script on 2014-02-09
+version = "7.x-2.2"
+core = "7.x"
+project = "libraries"
+datestamp = "1391965716"
+
diff --git a/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.css b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.css
new file mode 100644
index 000000000..37fd00d0a
--- /dev/null
+++ b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.css
@@ -0,0 +1,12 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-module-css' div purple. See README.txt for more
+ * information.
+ */
+
+.libraries-test-module-css {
+ color: purple;
+}
diff --git a/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.inc b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.inc
new file mode 100644
index 000000000..557c49b46
--- /dev/null
+++ b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.inc
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_module_integration_file() {
+}
diff --git a/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.info b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.info
new file mode 100644
index 000000000..450a0a20c
--- /dev/null
+++ b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.info
@@ -0,0 +1,13 @@
+name = Libraries test module
+description = Tests library detection and loading.
+core = 7.x
+package = Testing
+dependencies[] = libraries
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2014-02-09
+version = "7.x-2.2"
+core = "7.x"
+project = "libraries"
+datestamp = "1391965716"
+
diff --git a/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.js b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.js
new file mode 100644
index 000000000..d648e90a9
--- /dev/null
+++ b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.js
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-module-js' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+ attach: function(context, settings) {
+ $('.libraries-test-module-js').text('If this text shows up, libraries_test_module.js was loaded successfully.')
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.module b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.module
new file mode 100644
index 000000000..65f412ece
--- /dev/null
+++ b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module.module
@@ -0,0 +1,626 @@
+<?php
+
+/**
+ * @file
+ * Tests the library detection and loading.
+ */
+
+/**
+ * Implements hook_libraries_info().
+ */
+function libraries_test_module_libraries_info() {
+ // Test library information gathering.
+ $libraries['example_module'] = array(
+ 'name' => 'Example module',
+ 'module_altered' => FALSE,
+ );
+
+ // Test library detection.
+ $libraries['example_missing'] = array(
+ 'name' => 'Example 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/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/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/libraries/example',
+ 'version callback' => '_libraries_test_module_return_version',
+ 'version arguments' => array('1'),
+ 'versions' => array(
+ '1' => array(),
+ ),
+ );
+
+ // Test the default version callback.
+ $libraries['example_default_version_callback'] = array(
+ 'name' => 'Example default version callback',
+ 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example',
+ 'version arguments' => array(
+ 'file' => 'README.txt',
+ // Version 1
+ 'pattern' => '/Version (\d+)/',
+ 'lines' => 5,
+ ),
+ );
+
+ // 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/libraries/example',
+ // Version 1
+ '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/libraries/example',
+ 'version' => '1',
+ 'files' => array(
+ 'js' => array('example_1.js'),
+ 'css' => array('example_1.css'),
+ 'php' => array('example_1.php'),
+ ),
+ );
+
+ // 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_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_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/libraries/example',
+ 'version' => '2',
+ 'versions' => array(
+ '1' => array(
+ 'files' => array(
+ 'js' => array('example_1.js'),
+ 'css' => array('example_1.css'),
+ 'php' => array('example_1.php'),
+ ),
+ ),
+ '2' => array(
+ 'files' => array(
+ 'js' => array('example_2.js'),
+ 'css' => array('example_2.css'),
+ 'php' => array('example_2.php'),
+ ),
+ ),
+ ),
+ );
+
+ // Test variant detection.
+ $libraries['example_variant_missing'] = array(
+ 'name' => 'Example variant missing',
+ 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example',
+ 'version' => '1',
+ 'variants' => array(
+ 'example_variant' => array(
+ 'files' => array(
+ 'js' => array('example_3.js'),
+ 'css' => array('example_3.css'),
+ 'php' => array('example_3.php'),
+ ),
+ 'variant callback' => '_libraries_test_module_return_installed',
+ 'variant arguments' => array(FALSE),
+ ),
+ ),
+ );
+
+ $libraries['example_variant'] = array(
+ 'name' => 'Example variant',
+ 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example',
+ 'version' => '1',
+ 'variants' => array(
+ 'example_variant' => array(
+ 'files' => array(
+ 'js' => array('example_3.js'),
+ 'css' => array('example_3.css'),
+ 'php' => array('example_3.php'),
+ ),
+ 'variant callback' => '_libraries_test_module_return_installed',
+ 'variant arguments' => array(TRUE),
+ ),
+ ),
+ );
+
+ // 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/libraries/example',
+ 'version' => '2',
+ 'versions' => array(
+ '1' => array(
+ 'variants' => array(
+ 'example_variant_1' => array(
+ 'files' => array(
+ 'js' => array('example_1.js'),
+ 'css' => array('example_1.css'),
+ 'php' => array('example_1.php'),
+ ),
+ 'variant callback' => '_libraries_test_module_return_installed',
+ 'variant arguments' => array(TRUE),
+ ),
+ 'example_variant_2' => array(
+ 'files' => array(
+ 'js' => array('example_2.js'),
+ 'css' => array('example_2.css'),
+ 'php' => array('example_2.php'),
+ ),
+ 'variant callback' => '_libraries_test_module_return_installed',
+ 'variant arguments' => array(TRUE),
+ ),
+ ),
+ ),
+ '2' => array(
+ 'variants' => array(
+ 'example_variant_1' => array(
+ 'files' => array(
+ 'js' => array('example_3.js'),
+ 'css' => array('example_3.css'),
+ 'php' => array('example_3.php'),
+ ),
+ 'variant callback' => '_libraries_test_module_return_installed',
+ 'variant arguments' => array(TRUE),
+ ),
+ 'example_variant_2' => array(
+ 'files' => array(
+ 'js' => array('example_4.js'),
+ 'css' => array('example_4.css'),
+ 'php' => array('example_4.php'),
+ ),
+ 'variant callback' => '_libraries_test_module_return_installed',
+ 'variant arguments' => array(TRUE),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ // Test dependency loading.
+ // We add one file to each library to be able to verify if it was loaded with
+ // libraries_load().
+ // 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/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/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/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/libraries/example',
+ 'version' => '1',
+ 'dependencies' => array('example_dependency (>=1.1)'),
+ 'files' => array('js' => array('example_1.js')),
+ );
+
+ // Test the applying of callbacks.
+ $libraries['example_callback'] = array(
+ 'name' => 'Example callback',
+ 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example',
+ 'version' => '1',
+ 'versions' => array(
+ '1' => array(
+ 'variants' => array(
+ 'example_variant' => array(
+ // These keys are for testing purposes only.
+ 'info callback' => 'not applied',
+ 'pre-detect callback' => 'not applied',
+ 'post-detect callback' => 'not applied',
+ 'pre-dependencies-load callback' => 'not applied',
+ 'pre-load callback' => 'not applied',
+ 'post-load callback' => 'not applied',
+ ),
+ ),
+ // These keys are for testing purposes only.
+ 'info callback' => 'not applied',
+ 'pre-detect callback' => 'not applied',
+ 'post-detect callback' => 'not applied',
+ 'pre-dependencies-load callback' => 'not applied',
+ 'pre-load callback' => 'not applied',
+ 'post-load callback' => 'not applied',
+ ),
+ ),
+ 'variants' => array(
+ 'example_variant' => array(
+ // These keys are for testing purposes only.
+ 'info callback' => 'not applied',
+ 'pre-detect callback' => 'not applied',
+ 'post-detect callback' => 'not applied',
+ 'pre-dependencies-load callback' => 'not applied',
+ 'pre-load callback' => 'not applied',
+ 'post-load callback' => 'not applied',
+ ),
+ ),
+ 'callbacks' => array(
+ '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',
+ 'pre-detect callback' => 'not applied',
+ 'post-detect callback' => 'not applied',
+ 'pre-dependencies-load callback' => 'not applied',
+ 'pre-load callback' => 'not applied',
+ '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_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_module_return_installed(),
+ * because of the different arguments that are passed automatically.
+ */
+function _libraries_test_module_return_version($library, $version) {
+ return $version;
+}
+
+/**
+ * Gets the version information from an arbitrary library.
+ *
+ * Test function for a version callback with multiple arguments. This is an
+ * exact copy of libraries_get_version(), which uses a single $option argument,
+ * except for the fact that it uses multiple arguments. Since we support both
+ * type of version callbacks, detecting the version of a test library with this
+ * function ensures that the arguments are passed correctly. This function might
+ * be a useful reference for a custom version callback that uses multiple
+ * parameters.
+ *
+ * @param $library
+ * An associative array containing all information about the library.
+ * @param $file
+ * The filename to parse for the version, relative to the library path. For
+ * example: 'docs/changelog.txt'.
+ * @param pattern
+ * A string containing a regular expression (PCRE) to match the library
+ * version. For example: '/@version (\d+)\.(\d+)/'.
+ * @param lines
+ * (optional) The maximum number of lines to search the pattern in. Defaults
+ * to 20.
+ * @param 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.
+ *
+ * @return
+ * A string containing the version of the library.
+ *
+ * @see libraries_get_version()
+ */
+function _libraries_test_module_get_version($library, $file, $pattern, $lines = 20, $cols = 200) {
+
+ $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $file;
+ if (!file_exists($file)) {
+ return;
+ }
+ $file = fopen($file, 'r');
+ while ($lines && $line = fgets($file, $cols)) {
+ if (preg_match($pattern, $line, $version)) {
+ fclose($file);
+ return $version[1];
+ }
+ $lines--;
+ }
+ fclose($file);
+}
+
+/**
+ * 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_module_return_version(), because of
+ * the different arguments that are passed automatically.
+ */
+function _libraries_test_module_return_installed($library, $name, $installed) {
+ return $installed;
+}
+
+/**
+ * Sets the 'info callback' key.
+ *
+ * This function is used as a test callback for the 'info' callback group.
+ *
+ * @see _libraries_test_module_callback()
+ */
+function _libraries_test_module_info_callback(&$library, $version, $variant) {
+ _libraries_test_module_callback($library, $version, $variant, 'info');
+}
+
+/**
+ * Sets the 'pre-detect callback' key.
+ *
+ * This function is used as a test callback for the 'pre-detect' callback group.
+ *
+ * @see _libraries_test_module_callback()
+ */
+function _libraries_test_module_pre_detect_callback(&$library, $version, $variant) {
+ _libraries_test_module_callback($library, $version, $variant, 'pre-detect');
+}
+
+/**
+ * Sets the 'post-detect callback' key.
+ *
+ * This function is used as a test callback for the 'post-detect callback group.
+ *
+ * @see _libraries_test_module_callback()
+ */
+function _libraries_test_module_post_detect_callback(&$library, $version, $variant) {
+ _libraries_test_module_callback($library, $version, $variant, 'post-detect');
+}
+
+/**
+ * Sets the 'pre-dependencies-load callback' key.
+ *
+ * This function is used as a test callback for the 'pre-dependencies-load'
+ * callback group.
+ *
+ * @see _libraries_test_module_callback()
+ */
+function _libraries_test_module_pre_dependencies_load_callback(&$library, $version, $variant) {
+ _libraries_test_module_callback($library, $version, $variant, 'pre-dependencies-load');
+}
+
+/**
+ * Sets the 'pre-load callback' key.
+ *
+ * This function is used as a test callback for the 'pre-load' callback group.
+ *
+ * @see _libraries_test_module_callback()
+ */
+function _libraries_test_module_pre_load_callback(&$library, $version, $variant) {
+ _libraries_test_module_callback($library, $version, $variant, 'pre-load');
+}
+
+/**
+ * Sets the 'post-load callback' key.
+ *
+ * This function is used as a test callback for the 'post-load' callback group.
+ *
+ * @see _libraries_test_module_callback()
+ */
+function _libraries_test_module_post_load_callback(&$library, $version, $variant) {
+ _libraries_test_module_callback($library, $version, $variant, 'post-load');
+}
+
+/**
+ * Sets the '[group] callback' key, where [group] is prepare, detect, or load.
+ *
+ * This function is used as a test callback for the all callback groups.
+ *
+ * It sets the '[group] callback' (see above) key to 'applied ([part])' where
+ * [part] is either 'top-level', 'version x.y' (where x.y is the passed-in
+ * version string), 'variant example' (where example is the passed-in variant
+ * name), or 'version x.y, variant example' (see above), depending on the part
+ * of the library the passed-in library information belongs to.
+ *
+ * @param $library
+ * An array of library information, which may be version- or variant-specific.
+ * Passed by reference.
+ * @param $version
+ * The version the library information passed in $library belongs to, or NULL
+ * if the passed library information is not version-specific.
+ * @param $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_module_callback(&$library, $version, $variant, $group) {
+ $string = 'applied';
+ if (isset($version) && isset($variant)) {
+ $string .= " (version $version, variant $variant)";
+ }
+ elseif (isset($version)) {
+ $string .= " (version $version)";
+ }
+ elseif (isset($variant)) {
+ $string .= " (variant $variant)";
+ }
+ else {
+ $string .= ' (top-level)';
+ }
+ $library["$group callback"] = $string;
+
+ // 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_module_cache', FALSE)) {
+ drupal_set_message("The <em>$group</em> callback group was invoked.");
+ }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function libraries_test_module_menu() {
+ $base = array(
+ 'page callback' => '_libraries_test_module_load',
+ 'access callback' => TRUE,
+ );
+ $items['libraries-test-module/files'] = $base + array(
+ 'title' => 'Test files',
+ 'page arguments' => array('example_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-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-module/variant'] = $base + array(
+ 'title' => 'Test variant loading',
+ 'page arguments' => array('example_variant', 'example_variant'),
+ );
+ $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-module/cache'] = $base + array(
+ 'title' => 'Test caching of library information',
+ 'page arguments' => array('example_callback'),
+ );
+ return $items;
+}
+
+/**
+ * Loads a specified library (variant) for testing.
+ *
+ * JavaScript and CSS files can be checked directly by SimpleTest, so we only
+ * need to manually check for PHP files. We provide information about the loaded
+ * JavaScript and CSS files for easier debugging. See example/README.txt for
+ * more information.
+ */
+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.
+ $output = '';
+
+ // For easer debugging of JS loading, a text is shown that the JavaScript will
+ // replace.
+ $output .= '<h2>JavaScript</h2>';
+ $output .= '<div class="libraries-test-module-js">';
+ $output .= 'If this text shows up, no JavaScript test file was loaded.';
+ $output .= '</div>';
+
+ // For easier debugging of CSS loading, the loaded CSS files will color the
+ // following text.
+ $output .= '<h2>CSS</h2>';
+ $output .= '<div class="libraries-test-module-css">';
+ $output .= 'If one of the CSS test files has been loaded, this text will be colored:';
+ $output .= '<ul>';
+ // Do not reference the actual CSS files (i.e. including '.css'), because that
+ // breaks testing.
+ $output .= '<li>example_1: red</li>';
+ $output .= '<li>example_2: green</li>';
+ $output .= '<li>example_3: orange</li>';
+ $output .= '<li>example_4: blue</li>';
+ $output .= '<li>libraries_test_module: purple</li>';
+ $output .= '<li>libraries_test_theme: turquoise</li>';
+ $output .= '</ul>';
+ $output .= '</div>';
+
+ $output .= '<h2>PHP</h2>';
+ $output .= '<div class="libraries-test-module-php">';
+ $output .= 'The following is a list of all loaded test PHP files:';
+ $output .= '<ul>';
+ $files = get_included_files();
+ foreach ($files as $file) {
+ if (strpos($file, 'libraries/test') && !strpos($file, 'libraries_test_module.module') && !strpos($file, 'template.php')) {
+ $output .= '<li>' . str_replace(DRUPAL_ROOT . '/', '', $file) . '</li>';
+ }
+ }
+ $output .= '</ul>';
+ $output .= '</div>';
+
+ 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/libraries/tests/modules/libraries_test_module/libraries_test_module_post_load.inc b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module_post_load.inc
new file mode 100644
index 000000000..8e308f259
--- /dev/null
+++ b/sites/all/modules/libraries/tests/modules/libraries_test_module/libraries_test_module_post_load.inc
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_module_integration_file_post_load() {
+}
+
+// Call a function that is defined in the library file, to ensure that was
+// loaded prior to this file.
+_libraries_test_module_example_1();
diff --git a/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.css b/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.css
new file mode 100644
index 000000000..a87542716
--- /dev/null
+++ b/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.css
@@ -0,0 +1,12 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-module-css' div purple. See README.txt for more
+ * information.
+ */
+
+.libraries-test-module-css {
+ color: turquoise;
+}
diff --git a/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.inc b/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.inc
new file mode 100644
index 000000000..df1112cd7
--- /dev/null
+++ b/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.inc
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_theme_integration_file() {
+}
diff --git a/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info b/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info
new file mode 100644
index 000000000..7528416be
--- /dev/null
+++ b/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info
@@ -0,0 +1,11 @@
+name = Libraries test theme
+description = Tests that themes can provide and alter library information.
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2014-02-09
+version = "7.x-2.2"
+core = "7.x"
+project = "libraries"
+datestamp = "1391965716"
+
diff --git a/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.js b/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.js
new file mode 100644
index 000000000..0ae846e42
--- /dev/null
+++ b/sites/all/modules/libraries/tests/themes/libraries_test_theme/libraries_test_theme.js
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-module-js' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+ attach: function(context, settings) {
+ $('.libraries-test-module-js').text('If this text shows up, libraries_test_theme.js was loaded successfully.')
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/libraries/tests/themes/libraries_test_theme/template.php b/sites/all/modules/libraries/tests/themes/libraries_test_theme/template.php
new file mode 100644
index 000000000..cbb53ace9
--- /dev/null
+++ b/sites/all/modules/libraries/tests/themes/libraries_test_theme/template.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Libraries test theme.
+ */
+
+/**
+ * Implements hook_libraries_info().
+ */
+function libraries_test_theme_libraries_info() {
+ $libraries['example_theme'] = array(
+ 'name' => '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/media/LICENSE.txt b/sites/all/modules/media/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/media/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/media/README.txt b/sites/all/modules/media/README.txt
new file mode 100644
index 000000000..6875a0722
--- /dev/null
+++ b/sites/all/modules/media/README.txt
@@ -0,0 +1,7 @@
+
+/**
+ * @file
+ * README for the Media Module.
+ */
+
+See http://drupal.org/node/356802
diff --git a/sites/all/modules/media/css/media.css b/sites/all/modules/media/css/media.css
new file mode 100644
index 000000000..a93715622
--- /dev/null
+++ b/sites/all/modules/media/css/media.css
@@ -0,0 +1,147 @@
+/**
+ * @file
+ * Styles for the media library.
+ *
+ * The display and layout of the Media browser assumes Drupal's Seven theme as
+ * the theme active when this is displayed.
+ */
+
+/* jQuery UI Resets */
+.ui-tabs {
+ padding: 0;
+}
+.ui-front {
+ z-index: 10001 !important;
+}
+
+.ui-dialog.media-wrapper .ui-dialog-content {
+ padding: 0;
+}
+
+.ui-dialog.media-wrapper .ui-dialog-buttonpane {
+ display: none;
+}
+
+#media-browser-tabset .ui-widget-header {
+ background: none;
+}
+
+/* Remove the default border */
+.ui-widget-content {
+ border: none;
+}
+
+/* *********************************************************** */
+/* Browser layout themeing */
+
+/* Size the branding header appropriately */
+#media-browser-tabset #branding {
+ padding: 10px 10px 0px 10px;
+}
+
+#media-browser-tabset #branding h1 {
+ float: left;
+ height: 16px;
+ margin-top: 0px;
+}
+
+/* Float the tabs right to keep the UI consistent across themes */
+#media-tabs-wrapper {
+ float: right;
+}
+
+#media-browser-tabset ul.tabs {
+ padding: 0;
+ border: none;
+}
+
+/* Reset the height to match the browser */
+#media-browser-tabset ul.tabs.primary li a:link {
+ font-weight: bold;
+ margin-right: 0;
+}
+
+/* *********************************************************** */
+/* Media item display */
+
+.media-item {
+ background: #eee;
+ border: 1px solid #CCCCCC;
+ box-shadow: inset 0 0 15px rgba(0,0,0,.1), inset 0 0 0 1px rgba(0,0,0,.05);
+ display: inline-block;
+ padding: 5px;
+ position: relative;
+}
+
+.media-item img {
+ display: block;
+}
+
+.media-item .label-wrapper {
+ background: rgba(255,255,255,.8);
+ bottom: 0;
+ box-shadow: inset 0 0 0 1px rgba(0,0,0,.15);
+ left: 0;
+ max-height: 100%;
+ overflow: hidden;
+ position: absolute;
+ right: 0;
+ text-align: center;
+ word-wrap: break-word;
+}
+
+.media-item .label-wrapper label {
+ font-size: 10px;
+ padding: 5px 10px;
+}
+
+/* Media item lists */
+
+#media-browser-library-list {
+ margin: 0;
+ padding: 0;
+}
+
+.media-list-thumbnails li {
+ float: left;
+ list-style: none;
+ margin: 0 10px 10px 0;
+}
+
+.media-list-thumbnails li a {
+ text-decoration: none;
+}
+
+.media-list-thumbnails .media-item.selected {
+ background: #F4ECC7;
+ border-color: #058AC5;
+}
+
+.media-list-thumbnails .media-item:hover {
+ border-color: #058AC5;
+ cursor: pointer;
+}
+
+.media-list-thumbnails .media-item .label-wrapper label {
+ color: #058AC5;
+}
+
+.media-list-thumbnails .media-item .label-wrapper label:hover {
+ cursor: pointer;
+}
+
+.media-list-thumbnails .form-type-checkbox {
+ bottom: 117px;
+ left: 6px;
+ margin: 0;
+ padding: 0;
+ position: relative;
+}
+
+/* File field */
+
+.media-widget .preview {
+ display: inline-block;
+ margin-right: 10px;
+ vertical-align: middle;
+}
diff --git a/sites/all/modules/media/images/icons/default/application-octet-stream.png b/sites/all/modules/media/images/icons/default/application-octet-stream.png
new file mode 100644
index 000000000..0e6de2f54
--- /dev/null
+++ b/sites/all/modules/media/images/icons/default/application-octet-stream.png
Binary files differ
diff --git a/sites/all/modules/media/images/icons/default/audio-mpeg.png b/sites/all/modules/media/images/icons/default/audio-mpeg.png
new file mode 100644
index 000000000..b8763985d
--- /dev/null
+++ b/sites/all/modules/media/images/icons/default/audio-mpeg.png
Binary files differ
diff --git a/sites/all/modules/media/images/icons/default/audio-x-generic.png b/sites/all/modules/media/images/icons/default/audio-x-generic.png
new file mode 100644
index 000000000..b8763985d
--- /dev/null
+++ b/sites/all/modules/media/images/icons/default/audio-x-generic.png
Binary files differ
diff --git a/sites/all/modules/media/images/icons/default/file-unknown.png b/sites/all/modules/media/images/icons/default/file-unknown.png
new file mode 100644
index 000000000..46125e7f6
--- /dev/null
+++ b/sites/all/modules/media/images/icons/default/file-unknown.png
Binary files differ
diff --git a/sites/all/modules/media/images/icons/default/image-x-generic.png b/sites/all/modules/media/images/icons/default/image-x-generic.png
new file mode 100644
index 000000000..c50e3c780
--- /dev/null
+++ b/sites/all/modules/media/images/icons/default/image-x-generic.png
Binary files differ
diff --git a/sites/all/modules/media/images/icons/default/video-x-generic.png b/sites/all/modules/media/images/icons/default/video-x-generic.png
new file mode 100644
index 000000000..58add033f
--- /dev/null
+++ b/sites/all/modules/media/images/icons/default/video-x-generic.png
Binary files differ
diff --git a/sites/all/modules/media/includes/MediaBrowserPlugin.inc b/sites/all/modules/media/includes/MediaBrowserPlugin.inc
new file mode 100644
index 000000000..807a4e41d
--- /dev/null
+++ b/sites/all/modules/media/includes/MediaBrowserPlugin.inc
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaBrowserPlugin.
+ */
+
+/**
+ * Defines a Media browser plugin base class.
+ *
+ * MediaBrowserPlugin implementations need to implement at least the
+ * view() method.
+ */
+abstract class MediaBrowserPlugin implements MediaBrowserPluginInterface {
+ /**
+ * The plugin metadata array from hook_media_browser_plugin_info().
+ *
+ * @var array
+ */
+ protected $info;
+
+ /**
+ * The parameters for the current media browser from
+ * media_get_browser_params().
+ *
+ * @var array
+ */
+ protected $params;
+
+ /**
+ * Implements MediaBrowserPluginInterface::__construct().
+ */
+ public function __construct($info, $params) {
+ $this->info = $info;
+ $this->params = $params;
+ }
+
+ /**
+ * Implements MediaBrowserPluginInterface::access().
+ */
+ public function access($account = NULL) {
+ // Backwards compatible support for 'access callback' definitions.
+ if (isset($this->info['access callback'])) {
+ $access_callback = $this->info['access callback'];
+ $access_arguments = isset($this->info['access arguments']) ? $this->info['access arguments'] : array();
+ return function_exists($access_callback) && call_user_func_array($access_callback, $access_arguments);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Provide a render array to display the plugin in a media browser.
+ *
+ * This render array will be a jQuery tab in the media browser.
+ *
+ * Some elements are special:
+ * - #settings: Drupal.settings.media.browser.$key (where key is the array
+ * key).
+ * - #callback: If provided, will make the tab an "ajax" tab.
+ * - #title: If provided, will be used as the tab's title. Otherwise the
+ * 'title' value from the plugin's hook_media_browser_plugin_info() will
+ * be used.
+ * - #weight: If provided, will be used to order the tabs between each other.
+ * A lower weight will be displayed first while a higher weight will be
+ * displayed later. If not provided, and there is a 'weight' value in the
+ * plugin's hook_media_browser_plugin_info() then it will be used,
+ * otherwise a default of 0 will be used.
+ * - form: If the plugin is to display a native Drupal form, then the output
+ * of drupal_get_form should be returned into the 'form' render key. If a
+ * form's callback isn't normally loaded, module_load_include() should be
+ * used to ensure that the form can be displayed.
+ *
+ * Example usage:
+ * @code
+ * module_load_include('inc', 'mymodule', 'mymodule.pages');
+ * $build['#attached']['js'][] = drupal_get_path('module', 'mymodule') . '/js/mymodule.media.browser.js';
+ * $build['form'] = drupal_get_form('mymodule_media_form');
+ * return $build;
+ * @endcode
+ *
+ * @return array
+ * Renderable array.
+ */
+ abstract public function view();
+}
diff --git a/sites/all/modules/media/includes/MediaBrowserPluginInterface.inc b/sites/all/modules/media/includes/MediaBrowserPluginInterface.inc
new file mode 100644
index 000000000..3ff11340a
--- /dev/null
+++ b/sites/all/modules/media/includes/MediaBrowserPluginInterface.inc
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaBrowserPluginInterface.
+ */
+
+/**
+ * Defines a Media browser plugin.
+ *
+ * Extends the MediaBrowserPluginInterface with methods expected by all
+ * Media browser classes.
+ */
+interface MediaBrowserPluginInterface {
+ /**
+ * Set up the plugin class.
+ *
+ * @param array $info
+ * An array of plugin info from hook_media_browser_plugin_info()
+ * implementations.
+ * @param array $params
+ * An array of parameters which came in is $_GET['params']. The expected
+ * parameters are still being defined.
+ * - 'types': array of media types to support
+ * - 'multiselect': boolean; TRUE enables multiselect
+ */
+ public function __construct($info, $params);
+
+ /**
+ * Check if a user can access this plugin.
+ *
+ * @param object $account
+ * An optional user account object from user_load(). Defaults to the current
+ * global user.
+ *
+ * @return bool
+ * TRUE if the user can access this plugin, or FALSE otherwise.
+ */
+ public function access($account = NULL);
+
+ // The view() method is an abstract function so it is defined in MediaBrowser
+ // Plugin.
+ // @todo public function view();
+}
diff --git a/sites/all/modules/media/includes/MediaBrowserUpload.inc b/sites/all/modules/media/includes/MediaBrowserUpload.inc
new file mode 100644
index 000000000..57721cb97
--- /dev/null
+++ b/sites/all/modules/media/includes/MediaBrowserUpload.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaBrowserUpload.
+ */
+
+/**
+ * Media browser plugin for showing the upload form.
+ *
+ * @deprecated
+ */
+class MediaBrowserUpload extends MediaBrowserPlugin {
+ /**
+ * Implements MediaBrowserPluginInterface::access().
+ */
+ public function access($account = NULL) {
+ return file_entity_access('create', NULL, $account);
+ }
+
+ /**
+ * Implements MediaBrowserPlugin::view().
+ */
+ public function view() {
+ module_load_include('inc', 'file_entity', 'file_entity.pages');
+
+ $build = array();
+ $build['form'] = drupal_get_form('file_entity_add_upload', $this->params);
+
+ return $build;
+ }
+}
diff --git a/sites/all/modules/media/includes/MediaBrowserView.inc b/sites/all/modules/media/includes/MediaBrowserView.inc
new file mode 100644
index 000000000..0321a119c
--- /dev/null
+++ b/sites/all/modules/media/includes/MediaBrowserView.inc
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaBrowserView.
+ */
+
+/**
+ * Media browser plugin for displaying a specific view and display.
+ */
+class MediaBrowserView extends MediaBrowserPlugin {
+ /**
+ * The view object from views_get_view() for this plugin.
+ *
+ * @var view
+ */
+ protected $view;
+
+ /**
+ * Implements MediaBrowserPluginInterface::__construct().
+ */
+ public function __construct($info, $params) {
+ parent::__construct($info, $params);
+
+ // Set up the view object with the proper display.
+ if ($view = views_get_view($info['view_name'])) {
+ $display_id = !empty($info['view_display_id']) ? $info['view_display_id'] : NULL;
+ if ($view->set_display($display_id)) {
+ $this->view = $view;
+ }
+ }
+ }
+
+ /**
+ * Implements MediaBrowserPluginInterface::access().
+ */
+ public function access($account = NULL) {
+ return !empty($this->view) && $this->view->access($this->view->current_display, $account);
+ }
+
+ /**
+ * Implements MediaBrowserPlugin::view().
+ */
+ public function view() {
+ if (!empty($this->view)) {
+ $build['#markup'] = $this->view->preview();
+
+ // Allow the View title to override the plugin title.
+ if ($title = $this->view->get_title()) {
+ $build['#title'] = $title;
+ }
+
+ return $build;
+ }
+ }
+}
diff --git a/sites/all/modules/media/includes/MediaEntityTranslationHandler.inc b/sites/all/modules/media/includes/MediaEntityTranslationHandler.inc
new file mode 100644
index 000000000..4e78ed18d
--- /dev/null
+++ b/sites/all/modules/media/includes/MediaEntityTranslationHandler.inc
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Media translation handler for the entity translation module.
+ */
+
+
+/**
+ * Media translation handler.
+ */
+class MediaEntityTranslationHandler extends EntityTranslationDefaultHandler {
+
+ /**
+ * Constructor function.
+ */
+ public function __construct($entity_type, $entity_info, $entity) {
+ parent::__construct('file', $entity_info, $entity);
+ }
+
+ /**
+ * Entity form handler.
+ *
+ * @see EntityTranslationDefaultHandler::entityForm()
+ */
+ public function entityForm(&$form, &$form_state) {
+ parent::entityForm($form, $form_state);
+
+ if (isset($form['actions']['delete_translation'])) {
+ $form['actions']['delete_translation']['#weight'] = 10;
+ }
+
+ if ($this->getPathScheme() == 'media') {
+ $language = $GLOBALS[LANGUAGE_TYPE_CONTENT];
+ $form_langcode = $this->getFormLanguage();
+ $source_langcode = $this->getSourceLanguage();
+ $translations = $this->getTranslations();
+
+ // If a translation in the current content language is missing we display
+ // a link to create it, unless we are not already doing it.
+ if ($language->language != $form_langcode && empty($source_langcode) && !isset($translations->data[$language->language])) {
+ $link = array(
+ 'title' => t('Add @language translation', array('@language' => $language->name)),
+ 'href' => $this->getEditPath() . '/add/' . $form_langcode . '/' . $language->language,
+ 'localized_options' => array('attributes' => array('class' => array('ctools-use-modal'))),
+ );
+ $form['media_add_translation'] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $link,
+ '#weight' => -110,
+ '#prefix' => '<ul class="action-links">',
+ '#suffix' => '</ul>',
+ );
+ }
+
+ // Hide unsupported elements.
+ $form['source_language']['#access'] = FALSE;
+ if (isset($form['actions']['delete_translation'])) {
+ $form['actions']['delete_translation']['#access'] = FALSE;
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/media/includes/MediaReadOnlyStreamWrapper.inc b/sites/all/modules/media/includes/MediaReadOnlyStreamWrapper.inc
new file mode 100644
index 000000000..ee051fdbd
--- /dev/null
+++ b/sites/all/modules/media/includes/MediaReadOnlyStreamWrapper.inc
@@ -0,0 +1,476 @@
+<?php
+
+/**
+ * @file
+ * Implements a base class for Resource Stream Wrappers.
+ */
+
+/**
+ * A base class for Resource Stream Wrappers.
+ *
+ * This class provides a complete stream wrapper implementation. It passes
+ * incoming URL's through an interpolation method then recursively calls
+ * the invoking PHP filesystem function.
+ *
+ * MediaReadOnlyStreamWrapper implementations need to override at least the
+ * interpolateUrl method to rewrite the URL before is it passed back into the
+ * calling function.
+ */
+abstract class MediaReadOnlyStreamWrapper implements DrupalStreamWrapperInterface {
+ protected $parameters = array();
+ protected $base_url = NULL;
+ private $_DEBUG_MODE = NULL;
+
+ /**
+ * Utility function to return paramenters.
+ */
+ public function get_parameters() {
+ return $this->parameters;
+ }
+
+ // As part of the inode protection mode returned by stat(), identifies the
+ // file as a regular file, as opposed to a directory, symbolic link, or other
+ // type of "file".
+ // @see http://linux.die.net/man/2/stat
+ const S_IFREG = 0100000;
+
+ /**
+ * Template for stat calls.
+ *
+ * All elements must be initialized.
+ */
+ protected $_stat = array(
+ 0 => 0, // Device number
+ 'dev' => 0,
+ 1 => 0, // Inode number
+ 'ino' => 0,
+ // Inode protection mode. file_unmanaged_delete() requires is_file() to
+ // return TRUE.
+ 2 => self::S_IFREG,
+ 'mode' => self::S_IFREG,
+ 3 => 0, // Number of links.
+ 'nlink' => 0,
+ 4 => 0, // Userid of owner.
+ 'uid' => 0,
+ 5 => 0, // Groupid of owner.
+ 'gid' => 0,
+ 6 => -1, // Device type, if inode device *
+ 'rdev' => -1,
+ 7 => 0, // Size in bytes.
+ 'size' => 0,
+ 8 => 0, // Time of last access (Unix timestamp).
+ 'atime' => 0,
+ 9 => 0, // Time of last modification (Unix timestamp).
+ 'mtime' => 0,
+ 10 => 0, // Time of last inode change (Unix timestamp).
+ 'ctime' => 0,
+ 11 => -1, // Blocksize of filesystem IO.
+ 'blksize' => -1,
+ 12 => -1, // Number of blocks allocated.
+ 'blocks' => -1,
+ );
+
+ /**
+ * Handles parameters on the URL string.
+ */
+ public function interpolateUrl() {
+ if ($parameters = $this->get_parameters()) {
+ return $this->base_url . '?' . http_build_query($parameters);
+ }
+ }
+
+ /**
+ * Returns a web accessible URL for the resource.
+ *
+ * This function should return a URL that can be embedded in a web page
+ * and accessed from a browser. For example, the external URL of
+ * "youtube://xIpLd0WQKCY" might be
+ * "http://www.youtube.com/watch?v=xIpLd0WQKCY".
+ *
+ * @return string
+ * Returns a string containing a web accessible URL for the resource.
+ */
+ public function getExternalUrl() {
+ return $this->interpolateUrl();
+ }
+
+ /**
+ * Base implementation of getMimeType().
+ */
+ public static function getMimeType($uri, $mapping = NULL) {
+ return 'application/octet-stream';
+ }
+
+ /**
+ * Base implementation of realpath().
+ */
+ public function realpath() {
+ return $this->getExternalUrl();
+ }
+
+ /**
+ * Stream context resource.
+ *
+ * @var Resource
+ */
+ public $context;
+
+ /**
+ * A generic resource handle.
+ *
+ * @var Resource
+ */
+ public $handle = NULL;
+
+ /**
+ * Instance URI (stream).
+ *
+ * A stream is referenced as "scheme://target".
+ *
+ * @var String
+ */
+ protected $uri;
+
+ /**
+ * Base implementation of setUri().
+ */
+ public function setUri($uri) {
+ $this->uri = $uri;
+ $this->parameters = $this->_parse_url($uri);
+ }
+
+ /**
+ * Base implementation of getUri().
+ */
+ public function getUri() {
+ return $this->uri;
+ }
+
+ /**
+ * Report an error.
+ *
+ * @param string $message
+ * The untranslated string to report.
+ * @param array $options
+ * An optional array of options to send to t().
+ * @param bool $display
+ * If TRUE, then we display the error to the user.
+ *
+ * @return bool
+ * We return FALSE, since we sometimes pass that back from the reporting
+ * function.
+ */
+ private function _report_error($message, $options = array(), $display = FALSE) {
+ watchdog('resource', $message, $options, WATCHDOG_ERROR);
+ if ($display) {
+ drupal_set_message(t($message, $options), 'error');
+ }
+ return FALSE;
+ }
+
+ /**
+ * Sets the debug mode.
+ */
+ private function _debug($message, $type = 'status') {
+ if ($this->_DEBUG_MODE) {
+ drupal_set_message($message, $type);
+ }
+ }
+
+ /**
+ * Returns an array of any parameters stored in the URL's path.
+ *
+ * @param string $url
+ * The URL to parse, such as youtube://v/[video-code]/t/[tags+more-tags].
+ *
+ * @return array
+ * An associative array of all the parameters in the path,
+ * or FALSE if the $url is ill-formed.
+ */
+ protected function _parse_url($url) {
+ $path = explode('://', $url);
+ $parts = explode('/', $path[1]);
+ $params = array();
+ $count = 0;
+ $total = count($parts);
+ if (!$total || ($total % 2)) {
+ // If we have no parts, or an odd number of parts, it's malformed.
+ return FALSE;
+ }
+ while ($count < $total) {
+ // We iterate count for each step of the assignment to keep us honest.
+ $params[$parts[$count++]] = $parts[$count++];
+ }
+ return $params;
+ }
+
+ /**
+ * Support for fopen(), file_get_contents(), file_put_contents() etc.
+ *
+ * @param string $url
+ * A string containing the path to the file to open.
+ * @param string $mode
+ * The file mode ("r", "wb" etc.).
+ * @param bitmask $options
+ * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
+ * @param string &$opened_url
+ * A string containing the path actually opened.
+ *
+ * @return bool
+ * TRUE if file was opened successfully.
+ */
+ public function stream_open($url, $mode, $options, &$opened_url) {
+ $this->_debug(t('Stream open: %url', array('%url' => $url)));
+
+ // We only handle Read-Only mode by default.
+ if ($mode != 'r' && $mode != 'rb') {
+ return $this->_report_error('Attempted to open %url as mode: %mode.', array('%url' => $url, '%mode' => $mode), ($options & STREAM_REPORT_ERRORS));
+ }
+
+ // We parse a URL as youtube://v/dsyiufo34/t/cats+dogs to store
+ // the relevant code(s) in our private array of parameters.
+ $this->parameters = $this->_parse_url($url);
+
+ if ($this->parameters === FALSE) {
+ return $this->_report_error('Attempted to parse an ill-formed url: %url.', array('%url' => $url), ($options & STREAM_REPORT_ERRORS));
+ }
+
+ if ((bool) $this->parameters && ($options & STREAM_USE_PATH)) {
+ $opened_url = $url;
+ }
+
+ $this->_debug(t('Stream opened: %parameters', array('%parameters' => print_r($this->parameters, TRUE))));
+
+ return (bool) $this->parameters;
+ }
+
+ /**
+ * Undocumented PHP stream wrapper method.
+ */
+ function stream_lock($operation) {
+ return FALSE;
+ }
+
+ /**
+ * Support for fread(), file_get_contents() etc.
+ *
+ * @param int $count
+ * Maximum number of bytes to be read.
+ *
+ * @return bool
+ * The string that was read, or FALSE in case of an error.
+ */
+ public function stream_read($count) {
+ return FALSE;
+ }
+
+ /**
+ * Support for fwrite(), file_put_contents() etc.
+ *
+ * Since this is a read only stream wrapper this always returns false.
+ *
+ * @param string $data
+ * The string to be written.
+ *
+ * @return bool
+ * Returns FALSE.
+ */
+ public function stream_write($data) {
+ return FALSE;
+ }
+
+ /**
+ * Support for feof().
+ *
+ * @return bool
+ * TRUE if end-of-file has been reached.
+ */
+ public function stream_eof() {
+ return FALSE;
+ }
+
+ /**
+ * Support for fseek().
+ *
+ * @todo document why this returns false.
+ *
+ * @param int $offset
+ * The byte offset to got to.
+ * @param string $whence
+ * SEEK_SET, SEEK_CUR, or SEEK_END.
+ *
+ * @return bool
+ * TRUE on success
+ */
+ public function stream_seek($offset, $whence) {
+ return FALSE;
+ }
+
+ /**
+ * Support for fflush().
+ *
+ * @todo document why this returns false.
+ *
+ * @return bool
+ * TRUE if data was successfully stored (or there was no data to store).
+ */
+ public function stream_flush() {
+ return FALSE;
+ }
+
+ /**
+ * Support for ftell().
+ *
+ * @todo document why this returns false.
+ *
+ * @return bool
+ * The current offset in bytes from the beginning of file.
+ */
+ public function stream_tell() {
+ return FALSE;
+ }
+
+ /**
+ * Support for fstat().
+ *
+ * @return array
+ * An array with file status, or FALSE in case of an error - see fstat()
+ * for a description of this array.
+ */
+ public function stream_stat() {
+ return $this->_stat;
+ }
+
+ /**
+ * Support for fclose().
+ *
+ * @todo document why this returns TRUE.
+ *
+ * @return bool
+ * TRUE if stream was successfully closed.
+ */
+ public function stream_close() {
+ return TRUE;
+ }
+
+ /**
+ * Support for stat().
+ *
+ * @param string $url
+ * A string containing the url to get information about.
+ * @param bitmask $flags
+ * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
+ *
+ * @return array
+ * An array with file status, or FALSE in case of an error - see fstat()
+ * for a description of this array.
+ */
+ public function url_stat($url, $flags) {
+ return $this->stream_stat();
+ }
+
+ /**
+ * Support for opendir().
+ *
+ * @param string $url
+ * A string containing the url to the directory to open.
+ * @param int $options
+ * Whether or not to enforce safe_mode (0x04).
+ *
+ * @return bool
+ * TRUE on success.
+ */
+ public function dir_opendir($url, $options) {
+ return FALSE;
+ }
+
+ /**
+ * Support for readdir().
+ *
+ * @return bool
+ * The next filename, or FALSE if there are no more files in the directory.
+ */
+ public function dir_readdir() {
+ return FALSE;
+ }
+
+ /**
+ * Support for rewinddir().
+ *
+ * @return bool
+ * TRUE on success.
+ */
+ public function dir_rewinddir() {
+ return FALSE;
+ }
+
+ /**
+ * Support for closedir().
+ *
+ * @return bool
+ * TRUE on success.
+ */
+ public function dir_closedir() {
+ return FALSE;
+ }
+
+ /**
+ * Undocumented.
+ *
+ * @todo document.
+ */
+ public function getDirectoryPath() {
+ return '';
+ }
+
+ /**
+ * DrupalStreamWrapperInterface requires that these methods be implemented,
+ * but none of them apply to a read-only stream wrapper. On failure they
+ * are expected to return FALSE.
+ */
+
+ /**
+ * Implements DrupalStreamWrapperInterface::unlink().
+ */
+ public function unlink($uri) {
+ // Although the remote file itself can't be deleted, return TRUE so that
+ // file_delete() can remove the file record from the Drupal database.
+ return TRUE;
+ }
+
+ /**
+ * Implements DrupalStreamWrapperInterface::rename().
+ */
+ public function rename($from_uri, $to_uri) {
+ return FALSE;
+ }
+
+ /**
+ * Implements DrupalStreamWrapperInterface::mkdir().
+ */
+ public function mkdir($uri, $mode, $options) {
+ return FALSE;
+ }
+
+ /**
+ * Implements DrupalStreamWrapperInterface::rmdir().
+ */
+ public function rmdir($uri, $options) {
+ return FALSE;
+ }
+
+ /**
+ * Implements DrupalStreamWrapperInterface::chmod().
+ */
+ public function chmod($mode) {
+ return FALSE;
+ }
+
+ /**
+ * Implements DrupalStreamWrapperInterface::dirname().
+ */
+ public function dirname($uri = NULL) {
+ return FALSE;
+ }
+
+}
diff --git a/sites/all/modules/media/includes/media.admin.inc b/sites/all/modules/media/includes/media.admin.inc
new file mode 100644
index 000000000..9cc9fa145
--- /dev/null
+++ b/sites/all/modules/media/includes/media.admin.inc
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Administration page callbacks for the Media module.
+ */
+
+/**
+ * Displays the media administration page.
+ */
+function media_admin_config_browser($form, &$form_state) {
+ $theme_options = array();
+ $theme_options[NULL] = t('Default administration theme');
+
+ foreach (list_themes() as $key => $theme) {
+ if ($theme->status) {
+ $theme_options[$key] = $theme->info['name'];
+ }
+ }
+
+ $form['media_dialog_theme'] = array(
+ '#type' => 'select',
+ '#title' => t('Media browser theme'),
+ '#options' => $theme_options,
+ '#description' => t("This theme will be used for all media related dialogs. It can be different from your site's theme because many site themes do not work well in the small windows which media uses."),
+ '#default_value' => variable_get('media_dialog_theme', ''),
+ );
+
+ $form['array_filter'] = array(
+ '#type' => 'value',
+ '#value' => TRUE,
+ );
+
+ $form['mediapopup'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Media Popup'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['mediapopup']['media_dialogclass'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Dialog Class'),
+ '#default_value' => variable_get('media_dialogclass', 'media-wrapper'),
+ '#description' => t('The class used to identify the popup wrapper element.'),
+ );
+ $form['mediapopup']['media_modal'] = array(
+ '#type' => 'select',
+ '#title' => t('Modal'),
+ '#options' => array(
+ FALSE => t('False'),
+ TRUE => t('True'),
+ ),
+ '#default_value' => variable_get('media_modal', TRUE),
+ '#description' => t('Open as modal window.'),
+ );
+ $form['mediapopup']['media_draggable'] = array(
+ '#type' => 'select',
+ '#title' => t('Draggable'),
+ '#options' => array(
+ FALSE => t('False'),
+ TRUE => t('True'),
+ ),
+ '#default_value' => variable_get('media_draggable', FALSE),
+ '#description' => t('Draggable modal window.'),
+ );
+ $form['mediapopup']['media_resizable'] = array(
+ '#type' => 'select',
+ '#title' => t('Resizable'),
+ '#options' => array(
+ FALSE => t('False'),
+ TRUE => t('True'),
+ ),
+ '#default_value' => variable_get('media_resizable', FALSE),
+ '#description' => t('Resizable modal window.'),
+ );
+ $form['mediapopup']['media_minwidth'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Min Width'),
+ '#default_value' => variable_get('media_minwidth', 500),
+ '#description' => t('CSS property min-width.'),
+ );
+ $form['mediapopup']['media_width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Width'),
+ '#default_value' => variable_get('media_width', 670),
+ '#description' => t('CSS property width.'),
+ );
+ $form['mediapopup']['media_height'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Height'),
+ '#default_value' => variable_get('media_height', 280),
+ '#description' => t('CSS property height.'),
+ );
+ $form['mediapopup']['media_position'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Position'),
+ '#default_value' => variable_get('media_position', 'center'),
+ '#description' => t('CSS property position.'),
+ );
+ $form['mediapopup']['media_zindex'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Z-Index'),
+ '#default_value' => variable_get('media_zindex', 10000),
+ '#description' => t('CSS property z-index.'),
+ );
+ $form['mediapopup']['overlay'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Overlay'),
+ );
+ $form['mediapopup']['overlay']['media_backgroundcolor'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Background Color'),
+ '#default_value' => variable_get('media_backgroundcolor', '#000000'),
+ '#description' => t('CSS property background-color; used with overlay.'),
+ );
+ $form['mediapopup']['overlay']['media_opacity'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Opacity'),
+ '#default_value' => variable_get('media_opacity', 0.4),
+ '#description' => t('CSS property opacity; used with overlay.'),
+ );
+
+ $form['#submit'][] = 'media_admin_config_browser_pre_submit';
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form submission handler for media_admin_config_browser().
+ */
+function media_admin_config_browser_pre_submit(&$form, &$form_state) {
+ if (!$form_state['values']['media_dialog_theme']) {
+ variable_del('media_dialog_theme');
+ unset($form_state['values']['media_dialog_theme']);
+ }
+}
diff --git a/sites/all/modules/media/includes/media.browser.inc b/sites/all/modules/media/includes/media.browser.inc
new file mode 100644
index 000000000..d5285f0a4
--- /dev/null
+++ b/sites/all/modules/media/includes/media.browser.inc
@@ -0,0 +1,244 @@
+<?php
+
+/**
+ * @file
+ * Summon plugins and render the media browser.
+ */
+
+/**
+ * Media browser page callback.
+ */
+function media_browser($selected = NULL) {
+ $output = array();
+ $output['#attached']['library'][] = array('media', 'media_browser_page');
+
+ $params = media_get_browser_params();
+
+ // If one or more files have been selected, the browser interaction is now
+ // complete. Return empty page content to the dialog which now needs to close,
+ // but populate Drupal.settings with information about the selected files.
+ if (isset($params['fid'])) {
+ $fids = is_array($params['fid']) ? $params['fid'] : array($params['fid']);
+ if (!is_numeric($fids[0])) {
+ throw new Exception('Error selecting media, fid param is not an fid or an array of fids');
+ }
+ $files = file_load_multiple($fids);
+ foreach ($files as $file) {
+ $view_mode = isset($params['view_mode']) ? $params['view_mode'] : 'preview';
+ media_browser_build_media_item($file, $view_mode);
+ }
+ $setting = array('media' => array('selectedMedia' => array_values($files)));
+ drupal_add_js($setting, 'setting');
+ return $output;
+ }
+
+ $plugins = media_get_browser_plugin_info();
+
+ // Allow parameters to provide a list of enabled or disabled media browser
+ // plugins.
+ if (!empty($params['enabledPlugins'])) {
+ $plugins = array_intersect_key($plugins, array_fill_keys($params['enabledPlugins'], 1));
+ }
+ elseif (!empty($params['disabledPlugins'])) {
+ $plugins = array_diff_key($plugins, array_fill_keys($params['disabledPlugins'], 1));
+ }
+
+ // Render plugins.
+ $plugin_output = array();
+ foreach ($plugins as $key => $plugin_info) {
+ // Support the old CTools style handler definition.
+ if (!isset($plugin_info['class']) && !empty($plugin_info['handler'])) {
+ if (is_string($plugin_info['handler'])) {
+ $plugin_info['class'] = $plugin_info['handler'];
+ }
+ elseif (isset($plugin_info['handler']['class'])) {
+ $plugin_info['class'] = $plugin_info['handler']['class'];
+ }
+ }
+
+ if (empty($plugin_info['class']) || !class_exists($plugin_info['class'])) {
+ continue;
+ }
+
+ $plugin = new $plugin_info['class']($plugin_info, $params);
+ if ($plugin->access()) {
+ $plugin_output[$key] = $plugin->view();
+ if (!empty($plugin_output[$key]) && is_array($plugin_output[$key])) {
+ $plugin_output[$key] += array(
+ '#title' => $plugin_info['title'],
+ '#weight' => isset($plugin_info['weight']) ? $plugin_info['weight'] : 0,
+ );
+ }
+ else {
+ unset($plugin_output[$key]);
+ continue;
+ }
+ }
+ else {
+ continue;
+ }
+
+ // We need to ensure that a submit button is available on each tab. If the
+ // plugin is not returning a form element we need to add a submit button.
+ // This is a fairly broad assumption.
+ if (empty($plugin_output[$key]['#form']) && !empty($plugin_output[$key]['#markup'])) {
+ $fake_buttons = '<div class="form-actions form-wrapper">';
+ $fake_buttons .= l(t('Submit'), '', array(
+ 'attributes' => array(
+ 'class' => array('button', 'button-yes', 'fake-submit', $key),
+ ),
+ ));
+ $fake_buttons .= '</div>';
+ $plugin_output[$key]['#markup'] .= $fake_buttons;
+ }
+ }
+
+ // Allow modules to change the tab names or whatever else they want to change
+ // before we render. Perhaps this should be an alter on the theming function
+ // that we should write to be making the tabs.
+ drupal_alter('media_browser_plugins', $plugin_output);
+
+ $tabs = array();
+ $settings = array('media' => array('browser' => array()));
+
+ foreach (element_children($plugin_output, TRUE) as $key) {
+ // Add any JavaScript settings from the browser tab.
+ if (!empty($plugin_output[$key]['#settings'])) {
+ $settings['media']['browser'][$key] = $plugin_output[$key]['#settings'];
+ }
+
+ // If this is a "ajax" style tab, add the href, otherwise an id. jQuery UI
+ // will use an href value to load content from that url
+ $tabid = 'media-tab-' . check_plain($key);
+ if (!empty($plugin_output[$key]['#callback'])) {
+ $href = $plugin_output[$key]['#callback'];
+ }
+ else {
+ $attributes = array(
+ 'class' => array('media-browser-tab'),
+ 'id' => $tabid,
+ 'data-tabid' => $key,
+ );
+ // Create a div for each tab's content.
+ $plugin_output[$key] += array(
+ '#prefix' => '<div '. drupal_attributes($attributes) . ">\n",
+ '#suffix' => "</div>\n",
+ );
+ }
+
+ $attributes = array(
+ 'href' => '#' . $tabid,
+ 'data-tabid' => $key,
+ 'title' => $plugin_output[$key]['#title'],
+ );
+ $tabs[]['element'] = array(
+ '#markup' => '<li><a' . drupal_attributes($attributes) . '>' . check_plain($plugin_output[$key]['#title']) . "</a></li>\n",
+ );
+ }
+
+ drupal_add_js($settings, 'setting');
+
+ $output['tabset']['tabs'] = array(
+ '#theme' => 'menu_local_tasks',
+ '#attributes' => array('class' => array('tabs', 'primary')),
+ '#primary' => $tabs,
+ );
+
+ $output['tabset']['panes'] = $plugin_output;
+
+ return $output;
+}
+
+/**
+ * Menu callback for testing the media browser.
+ */
+function media_browser_testbed($form) {
+ $form['#attached']['library'][] = array('media', 'media_browser');
+ $form['#attached']['library'][] = array('media', 'media_browser_settings');
+
+ $form['test_element'] = array(
+ '#type' => 'media',
+ '#media_options' => array(
+ 'global' => array(
+ 'types' => array('video', 'audio'),
+ ),
+ ),
+ );
+
+ $launcher = '<a href="#" id="launcher"> Launch Media Browser</a>';
+
+ $form['options'] = array(
+ '#type' => 'textarea',
+ '#title' => 'Options (JSON)',
+ '#rows' => 10,
+ );
+
+ $form['launcher'] = array(
+ '#markup' => $launcher,
+ );
+
+ $form['result'] = array(
+ '#type' => 'textarea',
+ '#title' => 'Result',
+ );
+
+ $js = <<<EOF
+ Drupal.behaviors.mediaTest = {
+ attach: function(context, settings) {
+ var delim = "---";
+ var recentOptions = [];
+ var recentOptionsCookie = jQuery.cookie("recentOptions");
+ if (recentOptionsCookie) {
+ recentOptions = recentOptionsCookie.split("---");
+ }
+
+ var recentSelectBox = jQuery('<select id="recent_options" style="width:100%"></select>').change(function() { jQuery('#edit-options').val(jQuery(this).val())});
+
+ jQuery('.form-item-options').append('<label for="recent_options">Recent</a>');
+ jQuery('.form-item-options').append(recentSelectBox);
+ jQuery('.form-item-options').append(jQuery('<a href="#">Reset</a>').click(function() {alert('reset'); jQuery.cookie("recentOptions", null); window.location.reload(); }));
+
+ jQuery.each(recentOptions, function (idx, val) {
+ recentSelectBox.append(jQuery('<option></option>').val(val).html(val));
+ });
+
+
+ jQuery('#launcher').click(function () {
+ jQuery('#edit-result').val('');
+ var options = {};
+ var optionsTxt = jQuery('#edit-options').val();
+ if (optionsTxt) {
+ // Store it in the recent box
+ recentOptionsCookie += "---" + optionsTxt
+ jQuery.cookie("recentOptions", recentOptionsCookie, { expires: 7 });
+ recentSelectBox.append(jQuery('<option></option>').val(optionsTxt).html(optionsTxt));
+ options = eval('(' + optionsTxt + ')');
+ }
+ Drupal.media.popups.mediaBrowser(Drupal.behaviors.mediaTest.mediaSelected, options);
+ return false;
+ });
+ },
+
+ mediaSelected: function(selectedMedia) {
+ var result = JSON.stringify(selectedMedia);
+ jQuery('#edit-result').val(result);
+ }
+ }
+
+EOF;
+
+ drupal_add_js($js, array('type' => 'inline'));
+ return $form;
+}
+
+/**
+ * Adds additional properties to a file which are needed by the browser JS code.
+ *
+ * @param object $file
+ * A Drupal file object.
+ */
+function media_browser_build_media_item($file, $view_mode = 'preview') {
+ $preview = media_get_thumbnail_preview($file, NULL, $view_mode);
+ $file->preview = drupal_render($preview);
+ $file->url = file_create_url($file->uri);
+}
diff --git a/sites/all/modules/media/includes/media.fields.inc b/sites/all/modules/media/includes/media.fields.inc
new file mode 100644
index 000000000..0820fc164
--- /dev/null
+++ b/sites/all/modules/media/includes/media.fields.inc
@@ -0,0 +1,622 @@
+<?php
+
+/**
+ * @file
+ * Provide the media selector widget and media field formatters to the Fields
+ * API.
+ */
+
+/**
+ * Implements hook_field_widget_info().
+ */
+function media_field_widget_info() {
+ return array(
+ 'media_generic' => array(
+ 'label' => t('Media browser'),
+ 'field types' => array('file', 'image'),
+ 'settings' => array(
+ 'allowed_types' => array(
+ 'image' => 'image',
+ ),
+ 'browser_plugins' => array(),
+ 'allowed_schemes' => array(
+ 'public' => 'public',
+ ),
+ ),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ 'default value' => FIELD_BEHAVIOR_NONE,
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_widget_settings_form().
+ */
+function media_field_widget_settings_form($field, $instance) {
+ $widget = $instance['widget'];
+ $settings = $widget['settings'];
+
+ $plugins = media_get_browser_plugin_info();
+ $options = array();
+ foreach ($plugins as $key => $plugin) {
+ $options[$key] = check_plain($plugin['title']);
+ }
+
+ $form['browser_plugins'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Enabled browser plugins'),
+ '#options' => $options,
+ '#default_value' => $settings['browser_plugins'],
+ '#description' => t('Media browser plugins which are allowed for this field. If no plugins are selected, they will all be available.'),
+ );
+
+ $form['allowed_types'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Allowed file types'),
+ '#options' => file_entity_type_get_names(),
+ '#default_value' => $settings['allowed_types'],
+ '#description' => t('File types which are allowed for this field. If no file types are selected, they will all be available.'),
+ );
+
+ $visible_steam_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE);
+ $options = array();
+ foreach ($visible_steam_wrappers as $scheme => $information) {
+ $options[$scheme] = check_plain($information['name']);
+ }
+
+ $form['allowed_schemes'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Allowed URI schemes'),
+ '#options' => $options,
+ '#default_value' => $settings['allowed_schemes'],
+ '#description' => t('URI schemes which are allowed for this field. If no schemes are selected, they will all be available.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Implements hook_field_widget_form().
+ */
+function media_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
+ $defaults = array(
+ 'fid' => 0,
+ 'display' => !empty($field['settings']['display_default']),
+ 'description' => '',
+ );
+
+ // Load the items for form rebuilds from the field state as they might not be
+ // in $form_state['values'] because of validation limitations. Also, they are
+ // only passed in as $items when editing existing entities.
+ $field_state = field_form_get_state($element['#field_parents'], $field['field_name'], $langcode, $form_state);
+ if (isset($field_state['items'])) {
+ $items = $field_state['items'];
+ }
+
+ $field_settings = $instance['settings'];
+ $widget_settings = $instance['widget']['settings'];
+
+ // Essentially we use the media type, extended with some enhancements.
+ $element_info = element_info('media');
+ $element += array(
+ '#type' => 'media',
+ '#value_callback' => 'media_field_widget_value',
+ '#process' => array_merge($element_info['#process'], array('media_field_widget_process')),
+ '#media_options' => array(
+ 'global' => array(
+ 'types' => array_filter($widget_settings['allowed_types']),
+ 'enabledPlugins' => array_filter($instance['widget']['settings']['browser_plugins']),
+ 'schemes' => array_filter($widget_settings['allowed_schemes']),
+ 'file_directory' => isset($field_settings['file_directory']) ? $field_settings['file_directory'] : '',
+ 'file_extensions' => isset($field_settings['file_extensions']) ? $field_settings['file_extensions'] : variable_get('file_entity_default_allowed_extensions', 'jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp mp3 mov mp4 m4a m4v mpeg avi ogg oga ogv weba webp webm'),
+ 'max_filesize' => isset($field_settings['max_filesize']) ? $field_settings['max_filesize'] : 0,
+ 'uri_scheme' => !empty($field['settings']['uri_scheme']) ? $field['settings']['uri_scheme'] : file_default_scheme(),
+ ),
+ ),
+ // Allows this field to return an array instead of a single value.
+ '#extended' => TRUE,
+ );
+
+ // Add image field specific validators.
+ if ($field['type'] == 'image') {
+ if ($field_settings['min_resolution'] || $field_settings['max_resolution']) {
+ $element['#media_options']['global']['min_resolution'] = $field_settings['min_resolution'];
+ $element['#media_options']['global']['max_resolution'] = $field_settings['max_resolution'];
+ }
+ }
+
+ if ($field['cardinality'] == 1) {
+ // Set the default value.
+ $element['#default_value'] = !empty($items) ? $items[0] : $defaults;
+ // If there's only one field, return it as delta 0.
+ if (empty($element['#default_value']['fid'])) {
+ $element['#description'] = theme('media_upload_help', array('description' => $element['#description']));
+ }
+ $elements = array($element);
+ }
+ else {
+ // If there are multiple values, add an element for each existing one.
+ foreach ($items as $item) {
+ $elements[$delta] = $element;
+ $elements[$delta]['#default_value'] = $item;
+ $elements[$delta]['#weight'] = $delta;
+ $delta++;
+ }
+ // And then add one more empty row for new uploads except when this is a
+ // programmed form as it is not necessary.
+ if (($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta < $field['cardinality']) && empty($form_state['programmed'])) {
+ $elements[$delta] = $element;
+ $elements[$delta]['#default_value'] = $defaults;
+ $elements[$delta]['#weight'] = $delta;
+ $elements[$delta]['#required'] = ($element['#required'] && $delta == 0);
+ }
+ // The group of elements all-together need some extra functionality
+ // after building up the full list (like draggable table rows).
+ $elements['#file_upload_delta'] = $delta;
+ $elements['#theme'] = 'media_widget_multiple';
+ $elements['#theme_wrappers'] = array('fieldset');
+ $elements['#process'] = array('media_field_widget_process_multiple');
+ $elements['#title'] = $element['#title'];
+ $elements['#description'] = $element['#description'];
+ $elements['#field_name'] = $element['#field_name'];
+ $elements['#language'] = $element['#language'];
+ $elements['#display_field'] = intval(!empty($field['settings']['display_field']));
+
+ // Add some properties that will eventually be added to the media upload
+ // field. These are added here so that they may be referenced easily through
+ // a hook_form_alter().
+ $elements['#file_upload_title'] = t('Attach media');
+ $elements['#file_upload_description'] = theme('media_upload_help', array('description' => ''));
+ }
+
+ return $elements;
+}
+
+/**
+ * The #value_callback for the media field element.
+ */
+function media_field_widget_value($element, $input = FALSE, $form_state) {
+ if ($input) {
+ // Checkboxes lose their value when empty.
+ // If the display field is present make sure its unchecked value is saved.
+ $field = field_widget_field($element, $form_state);
+ if (empty($input['display'])) {
+ $input['display'] = intval(!empty($field['settings']['display_field']));
+ }
+ }
+
+ // We depend on the media element to handle uploads.
+ $return = media_file_value($element, $input, $form_state);
+
+ // Ensure that all the required properties are returned even if empty.
+ $return += array(
+ 'fid' => 0,
+ 'display' => 1,
+ 'description' => '',
+ );
+
+ return $return;
+}
+
+/**
+ * An element #process callback for the media field type.
+ *
+ * Expands the media type to include the description and display fields.
+ */
+function media_field_widget_process($element, &$form_state, $form) {
+ $item = $element['#value'];
+ $item['fid'] = $element['fid']['#value'];
+
+ $field = field_widget_field($element, $form_state);
+ $instance = field_widget_instance($element, $form_state);
+ $settings = $instance['widget']['settings'];
+
+ $element['#theme'] = 'media_widget';
+
+ // Add the display field if enabled.
+ if (!empty($field['settings']['display_field']) && $item['fid']) {
+ $element['display'] = array(
+ '#type' => empty($item['fid']) ? 'hidden' : 'checkbox',
+ '#title' => t('Include file in display'),
+ '#value' => isset($item['display']) ? $item['display'] : $field['settings']['display_default'],
+ '#attributes' => array('class' => array('file-display')),
+ );
+ }
+ else {
+ $element['display'] = array(
+ '#type' => 'hidden',
+ '#value' => '1',
+ );
+ }
+
+ // Add the description field if enabled.
+ if (!empty($instance['settings']['description_field']) && $item['fid']) {
+ $element['description'] = array(
+ '#type' => variable_get('file_description_type', 'textfield'),
+ '#title' => t('Description'),
+ '#value' => isset($item['description']) ? $item['description'] : '',
+ '#maxlength' => variable_get('file_description_length', 128),
+ '#description' => t('The description may be used as the label of the link to the file.'),
+ );
+ }
+
+ // Adjust the Ajax settings so that on upload and remove of any individual
+ // file, the entire group of file fields is updated together.
+ if ($field['cardinality'] != 1) {
+ $parents = array_slice($element['#array_parents'], 0, -1);
+ $new_path = 'media/ajax/' . implode('/', $parents) . '/' . $form['form_build_id']['#value'];
+ $field_element = drupal_array_get_nested_value($form, $parents);
+ $new_wrapper = $field_element['#id'] . '-ajax-wrapper';
+ foreach (element_children($element) as $key) {
+ if (isset($element[$key]['#ajax'])) {
+ $element[$key]['#ajax']['path'] = $new_path;
+ $element[$key]['#ajax']['wrapper'] = $new_wrapper;
+ }
+ }
+ unset($element['#prefix'], $element['#suffix']);
+ }
+
+ // Add another submit handler to the upload and remove buttons, to implement
+ // functionality needed by the field widget. This submit handler, along with
+ // the rebuild logic in media_field_widget_form() requires the entire field,
+ // not just the individual item, to be valid.
+ foreach (array('attach_button', 'remove_button') as $key) {
+ $element[$key]['#submit'][] = 'media_field_widget_submit';
+ $element[$key]['#limit_validation_errors'] = array(array_slice($element['#parents'], 0, -1));
+ }
+
+ return $element;
+}
+
+/**
+ * An element #process callback for a group of media fields.
+ *
+ * Adds the weight field to each row so it can be ordered and adds a new Ajax
+ * wrapper around the entire group so it can be replaced all at once.
+ */
+function media_field_widget_process_multiple($element, &$form_state, $form) {
+ $element_children = element_children($element, TRUE);
+ $count = count($element_children);
+
+ foreach ($element_children as $delta => $key) {
+ if ($key != $element['#file_upload_delta']) {
+ $description = _media_field_get_description_from_element($element[$key]);
+ $element[$key]['_weight'] = array(
+ '#type' => 'weight',
+ '#title' => $description ? t('Weight for @title', array('@title' => $description)) : t('Weight for new file'),
+ '#title_display' => 'invisible',
+ '#delta' => $count,
+ '#default_value' => $delta,
+ );
+ }
+ else {
+ // The title needs to be assigned to the attach field so that validation
+ // errors include the correct widget label.
+ $element[$key]['#title'] = $element['#title'];
+ $element[$key]['_weight'] = array(
+ '#type' => 'hidden',
+ '#default_value' => $delta,
+ );
+ }
+ }
+
+ // Add a new wrapper around all the elements for Ajax replacement.
+ $element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
+ $element['#suffix'] = '</div>';
+
+ return $element;
+}
+
+/**
+ * Retrieves the file description from a media field element.
+ *
+ * This helper function is used by media_field_widget_process_multiple().
+ *
+ * @param $element
+ * The element being processed.
+ *
+ * @return
+ * A description of the file suitable for use in the administrative interface.
+ */
+function _media_field_get_description_from_element($element) {
+ // Use the actual file description, if it's available.
+ if (!empty($element['#default_value']['description'])) {
+ return $element['#default_value']['description'];
+ }
+ // Otherwise, fall back to the filename.
+ if (!empty($element['#default_value']['filename'])) {
+ return $element['#default_value']['filename'];
+ }
+ // This is probably a newly uploaded file; no description is available.
+ return FALSE;
+}
+
+/**
+ * Form submission handler for attach/remove button of media_field_widget_form().
+ *
+ * This runs in addition to and after media_field_widget_submit().
+ *
+ * @see media_field_widget_submit()
+ * @see media_field_widget_form()
+ * @see media_field_widget_process()
+ */
+function media_field_widget_submit($form, &$form_state) {
+ // During the form rebuild, media_field_widget_form() will create field item
+ // widget elements using re-indexed deltas, so clear out $form_state['input']
+ // to avoid a mismatch between old and new deltas. The rebuilt elements will
+ // have #default_value set appropriately for the current state of the field,
+ // so nothing is lost in doing this.
+ $parents = array_slice($form_state['triggering_element']['#parents'], 0, -2);
+ drupal_array_set_nested_value($form_state['input'], $parents, NULL);
+
+ $button = $form_state['triggering_element'];
+
+ // Go one level up in the form, to the widgets container.
+ $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
+ $field_name = $element['#field_name'];
+ $langcode = $element['#language'];
+ $parents = $element['#field_parents'];
+
+ $submitted_values = drupal_array_get_nested_value($form_state['values'], array_slice($button['#parents'], 0, -2));
+ foreach ($submitted_values as $delta => $submitted_value) {
+ if (!$submitted_value['fid']) {
+ unset($submitted_values[$delta]);
+ }
+ }
+
+ // Re-index deltas after removing empty items.
+ $submitted_values = array_values($submitted_values);
+
+ // Update form_state values.
+ drupal_array_set_nested_value($form_state['values'], array_slice($button['#parents'], 0, -2), $submitted_values);
+
+ // Update items.
+ $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
+ $field_state['items'] = $submitted_values;
+ field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
+}
+
+/**
+ * Returns HTML for an individual media widget.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: A render element representing the widget.
+ *
+ * @ingroup themeable
+ */
+function theme_media_widget($variables) {
+ $element = $variables['element'];
+ $output = '';
+
+ // The "form-media" class is required for proper Ajax functionality.
+ $output .= '<div id="' . $element['#id'] . '" class="media-widget form-media clearfix">';
+ $output .= drupal_render_children($element);
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Returns HTML for a group of media widgets.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: A render element representing the widgets.
+ *
+ * @ingroup themeable
+ */
+function theme_media_widget_multiple($variables) {
+ $element = $variables['element'];
+
+ // Special ID and classes for draggable tables.
+ $weight_class = $element['#id'] . '-weight';
+ $table_id = $element['#id'] . '-table';
+
+ // Build up a table of applicable fields.
+ $headers = array();
+ $headers[] = t('File information');
+ if ($element['#display_field']) {
+ $headers[] = array(
+ 'data' => t('Display'),
+ 'class' => array('checkbox'),
+ );
+ }
+ $headers[] = t('Weight');
+ $headers[] = t('Operations');
+
+ // Get our list of widgets in order (needed when the form comes back after
+ // preview or failed validation).
+ $widgets = array();
+ foreach (element_children($element) as $key) {
+ $widgets[] = &$element[$key];
+ }
+ usort($widgets, '_field_sort_items_value_helper');
+
+ $rows = array();
+ foreach ($widgets as $key => &$widget) {
+ // Save the uploading row for last.
+ if ($widget['#file'] == FALSE) {
+ $widget['#title'] = $element['#file_upload_title'];
+ $widget['#description'] = $element['#file_upload_description'];
+ continue;
+ }
+
+ // Delay rendering of the buttons, so that they can be rendered later in the
+ // "operations" column.
+ $operations_elements = array();
+ foreach (element_children($widget) as $sub_key) {
+ if (isset($widget[$sub_key]['#type']) && ($widget[$sub_key]['#type'] == 'submit' || $widget[$sub_key]['#type'] == 'link')) {
+ hide($widget[$sub_key]);
+ $operations_elements[] = &$widget[$sub_key];
+ }
+ }
+
+ // Delay rendering of the "Display" option and the weight selector, so that
+ // each can be rendered later in its own column.
+ if ($element['#display_field']) {
+ hide($widget['display']);
+ }
+ hide($widget['_weight']);
+
+ // Render everything else together in a column, without the normal wrappers.
+ $widget['#theme_wrappers'] = array();
+ $information = drupal_render($widget);
+
+ // Render the previously hidden elements, using render() instead of
+ // drupal_render(), to undo the earlier hide().
+ $operations = '';
+ foreach ($operations_elements as $operation_element) {
+ $operations .= render($operation_element);
+ }
+ $display = '';
+ if ($element['#display_field']) {
+ unset($widget['display']['#title']);
+ $display = array(
+ 'data' => render($widget['display']),
+ 'class' => array('checkbox'),
+ );
+ }
+ $widget['_weight']['#attributes']['class'] = array($weight_class);
+ $weight = render($widget['_weight']);
+
+ // Arrange the row with all of the rendered columns.
+ $row = array();
+ $row[] = $information;
+ if ($element['#display_field']) {
+ $row[] = $display;
+ }
+ $row[] = $weight;
+ $row[] = $operations;
+ $rows[] = array(
+ 'data' => $row,
+ 'class' => isset($widget['#attributes']['class']) ? array_merge($widget['#attributes']['class'], array('draggable')) : array('draggable'),
+ );
+ }
+
+ drupal_add_tabledrag($table_id, 'order', 'sibling', $weight_class);
+
+ $output = '';
+ $output = empty($rows) ? '' : theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('id' => $table_id)));
+ $output .= drupal_render_children($element);
+ return $output;
+}
+
+/**
+ * Returns HTML for help text.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - description: The normal description for this field, specified by the
+ * user.
+ *
+ * @ingroup themeable
+ */
+function theme_media_upload_help($variables) {
+ $description = $variables['description'];
+
+ $descriptions = array();
+
+ if (strlen($description)) {
+ $descriptions[] = $description;
+ }
+
+ return implode('<br />', $descriptions);
+}
+
+/**
+ * Implements hook_field_formatter_info().
+ *
+ * Provides legacy support for the "Large filetype icon" file field formatter.
+ * This was originally used when media entities contained file fields. The
+ * current file entity architecture no longer needs this, but people may
+ * have used this formatter for other file fields on their website.
+ *
+ * @todo Some day, remove this.
+ */
+function media_field_formatter_info() {
+ $formatters = array(
+ 'media_large_icon' => array(
+ 'label' => t('Large filetype icon'),
+ 'field types' => array('file'),
+ 'settings' => array(
+ 'image_style' => '',
+ ),
+ ),
+ );
+
+ return $formatters;
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ *
+ * Legacy support for the "Large filetype icon" file field formatter.
+ * @see media_field_formatter_info()
+ */
+function media_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $image_styles = image_style_options(FALSE, PASS_THROUGH);
+ $element['image_style'] = array(
+ '#title' => t('Image style'),
+ '#type' => 'select',
+ '#default_value' => $settings['image_style'],
+ '#empty_option' => t('None (original image)'),
+ '#options' => $image_styles,
+ );
+
+ return $element;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ *
+ * Legacy support for the "Large filetype icon" file field formatter.
+ * @see media_field_formatter_info()
+ */
+function media_field_formatter_settings_summary($field, $instance, $view_mode) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $summary = array();
+
+ $image_styles = image_style_options(FALSE, PASS_THROUGH);
+ // Unset possible 'No defined styles' option.
+ unset($image_styles['']);
+ // Styles could be lost because of enabled/disabled modules that defines
+ // their styles in code.
+ if (isset($image_styles[$settings['image_style']])) {
+ $summary[] = t('Image style: @style', array('@style' => $image_styles[$settings['image_style']]));
+ }
+ else {
+ $summary[] = t('Original image');
+ }
+
+ return implode('<br />', $summary);
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ *
+ * Legacy support for the "Large filetype icon" file field formatter.
+ * @see media_field_formatter_info()
+ */
+function media_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+ $element = array();
+
+ if ($display['type'] == 'media_large_icon') {
+ foreach ($items as $delta => $item) {
+ $element[$delta] = array(
+ '#theme' => 'media_formatter_large_icon',
+ '#file' => (object) $item,
+ '#style_name' => $display['settings']['image_style'],
+ );
+ }
+ }
+
+ return $element;
+}
diff --git a/sites/all/modules/media/includes/media.pages.inc b/sites/all/modules/media/includes/media.pages.inc
new file mode 100644
index 000000000..b22a8b140
--- /dev/null
+++ b/sites/all/modules/media/includes/media.pages.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Common pages for the Media module.
+ */
+
+/**
+ * CTools modal callback for editing a file.
+ */
+function media_file_edit_modal($form, &$form_state, $file, $js) {
+ ctools_include('modal');
+ ctools_include('ajax');
+
+ $form_state['ajax'] = $js;
+ form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
+
+ $output = ctools_modal_form_wrapper('file_entity_edit', $form_state);
+
+ if ($js) {
+ $commands = $output;
+
+ if ($form_state['executed']) {
+ $commands = array(ctools_modal_command_dismiss(t('File saved')));
+ }
+
+ print ajax_render($commands);
+ exit();
+ }
+
+ // Otherwise, just return the output.
+ return $output;
+}
diff --git a/sites/all/modules/media/includes/media.theme.inc b/sites/all/modules/media/includes/media.theme.inc
new file mode 100644
index 000000000..2b57782e7
--- /dev/null
+++ b/sites/all/modules/media/includes/media.theme.inc
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Media Theming
+ *
+ * Theming functions for the Media module.
+ */
+
+/**
+ * Adds a wrapper around a preview of a media file.
+ */
+function theme_media_thumbnail($variables) {
+ $label = '';
+ $element = $variables['element'];
+
+ // Wrappers to go around the thumbnail.
+ $attributes = array(
+ 'title' => $element['#name'],
+ 'class' => 'media-item',
+ 'data-fid' => $element['#file']->fid,
+ );
+ $prefix = '<div ' . drupal_attributes($attributes) . '><div class="media-thumbnail">';
+ $suffix = '</div></div>';
+
+ // Arguments for the thumbnail link.
+ $thumb = $element['#children'];
+ if (file_entity_access('update', $element['#file'])) {
+ $target = 'file/' . $element['#file']->fid . '/edit';
+ $title = t('Click to edit details');
+ }
+ else {
+ $target = 'file/' . $element['#file']->fid;
+ $title = t('Click to view details');
+ }
+ $options = array(
+ 'query' => drupal_get_destination(),
+ 'html' => TRUE,
+ 'attributes' => array('title' => $title),
+ );
+
+ // Element should be a field renderable array. This should be improved.
+ if (!empty($element['#show_names']) && $element['#name']) {
+ $label = '<div class="label-wrapper"><label class="media-filename">' . $element['#name'] . '</label></div>';
+ }
+
+ $output = $prefix;
+ if (!empty($element['#add_link'])) {
+ $output .= l($thumb, $target, $options);
+ }
+ else {
+ $output .= $thumb;
+ }
+ $output .= $label . $suffix;
+ return $output;
+}
+
+/**
+ * Preprocess the media thumbnail.
+ */
+function template_preprocess_media_thumbnail(&$variables) {
+ // Set the name for the thumbnail to be the filename. This is done here so
+ // that other modules can hijack the name displayed if it should not be the
+ // filename.
+ $variables['element']['#name'] = isset($variables['element']['#file']->filename) ? check_plain($variables['element']['#file']->filename) : NULL;
+}
+
+/**
+ * Field formatter for displaying a file as a large icon.
+ */
+function theme_media_formatter_large_icon($variables) {
+ $file = $variables['file'];
+ $icon_dir = variable_get('media_icon_base_directory', 'public://media-icons') . '/' . variable_get('media_icon_set', 'default');
+ $icon = file_icon_path($file, $icon_dir);
+ $variables['path'] = $icon;
+
+ // theme_image() requires the 'alt' attribute passed as its own variable.
+ // @see http://drupal.org/node/999338
+ if (!isset($variables['alt']) && isset($variables['attributes']['alt'])) {
+ $variables['alt'] = $variables['attributes']['alt'];
+ }
+
+ // Add image height and width for the image theme functions.
+ if ($info = image_get_info($icon)) {
+ $variables += $info;
+ }
+
+ if ($variables['style_name']) {
+ $output = theme('image_style', $variables);
+ }
+ else {
+ $output = theme('image', $variables);
+ }
+ return $output;
+}
+
+/**
+ * Add messages to the page.
+ */
+function template_preprocess_media_dialog_page(&$variables) {
+ $variables['messages'] = theme('status_messages');
+}
diff --git a/sites/all/modules/media/includes/media_views_plugin_display_media_browser.inc b/sites/all/modules/media/includes/media_views_plugin_display_media_browser.inc
new file mode 100644
index 000000000..eba27ba63
--- /dev/null
+++ b/sites/all/modules/media/includes/media_views_plugin_display_media_browser.inc
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @file
+ * Contains the media browser tab display plugin.
+ */
+
+/**
+ * Display plugin to provide a view as a tab in the media browser.
+ *
+ * This is currently just a stub--there's nothing special we need to set up for
+ * rendering a view as a tab in the media browser. It would be possible to
+ * provide a special method that returns the plugin info for
+ * media_media_browser_plugin_info() and the render array for
+ * media_media_browser_plugin_view().
+ *
+ * @ingroup views_display_plugins
+ */
+class media_views_plugin_display_media_browser extends views_plugin_display {
+}
diff --git a/sites/all/modules/media/includes/media_views_plugin_style_media_browser.inc b/sites/all/modules/media/includes/media_views_plugin_style_media_browser.inc
new file mode 100644
index 000000000..413111600
--- /dev/null
+++ b/sites/all/modules/media/includes/media_views_plugin_style_media_browser.inc
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * The media browser style plugin.
+ */
+
+/**
+ * Media Views style plugin.
+ *
+ * Style plugin to render media items as an interactive grid for the media
+ * browser.
+ *
+ * @ingroup views_style_plugins
+ */
+class media_views_plugin_style_media_browser extends views_plugin_style_list {
+
+ // Stores the files loaded with pre_render.
+ public $files = array();
+
+ /**
+ * Set default options.
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['type'] = array('default' => 'ul');
+ $options['class'] = array('default' => 'media-list-thumbnails');
+ $options['wrapper_class'] = array('default' => '');
+
+ return $options;
+ }
+
+ /**
+ * Prevents a problem with views when get_row_class() is not set.
+ */
+ public function get_row_class($row_index) {
+ }
+
+ /**
+ * Add the base field (fid) to the query.
+ */
+ public function query() {
+ if (method_exists($this->view->query, 'add_field')) {
+ // Normal file_managed based view.
+ $this->view->query->add_field($this->view->base_table, $this->view->base_field);
+ }
+ if (method_exists($this->view->query, 'addField')) {
+ // Search API based view.
+ $this->view->query->addField('fid');
+ }
+ parent::query();
+ }
+
+}
diff --git a/sites/all/modules/media/js/media.admin.js b/sites/all/modules/media/js/media.admin.js
new file mode 100644
index 000000000..058e9318c
--- /dev/null
+++ b/sites/all/modules/media/js/media.admin.js
@@ -0,0 +1,77 @@
+/**
+ * @file
+ * Javascript for the interface at admin/content/media and also for interfaces
+ * related to setting up media fields and for media type administration.
+ *
+ * Basically, if it's on the /admin path, it's probably here.
+ */
+
+(function ($) {
+
+/**
+ * Functionality for the administrative file listings.
+ */
+Drupal.behaviors.mediaAdmin = {
+ attach: function (context) {
+ // Show a javascript confirmation dialog if a user has files selected and
+ // they try to switch between the "Thumbnail" and "List" local tasks.
+ $('.tabs.secondary a').once('media-admin').bind('click', function () {
+ if ($(':checkbox:checked', $('.file-entity-admin-file-form')).length != 0) {
+ return confirm(Drupal.t('If you switch views, you will lose your selection.'));
+ }
+ });
+
+ if ($('.media-display-thumbnails').length && !$('.media-thumbnails-select').length) {
+ // Implements 'select all/none' for thumbnail view.
+ // @TODO: Support grabbing more than one page of thumbnails.
+ var allLink = $('<a href="#">' + Drupal.t('all') + '</a>')
+ .click(function () {
+ $('.media-display-thumbnails', $(this).parents('form')).find(':checkbox').attr('checked', true).change();
+ return false;
+ });
+ var noneLink = $('<a href="#">' + Drupal.t('none') + '</a>')
+ .click(function () {
+ $('.media-display-thumbnails', $(this).parents('form')).find(':checkbox').attr('checked', false).change();
+ return false;
+ });
+ $('<div class="media-thumbnails-select" />')
+ .append('<strong>' + Drupal.t('Select') + ':</strong> ')
+ .append(allLink)
+ .append(', ')
+ .append(noneLink)
+ .prependTo('.media-display-thumbnails')
+ // If the media item is clicked anywhere other than on the image itself
+ // check the checkbox. For the record, JS thinks this is wonky.
+ $('.media-item').bind('click', function (e) {
+ if ($(e.target).is('img, a')) {
+ return;
+ }
+ var checkbox = $(this).parent().find(':checkbox');
+ if (checkbox.is(':checked')) {
+ checkbox.attr('checked', false).change();
+ } else {
+ checkbox.attr('checked', true).change();
+ }
+ });
+
+ // Add an extra class to selected thumbnails.
+ $('.media-display-thumbnails :checkbox').each(function () {
+ var checkbox = $(this);
+ if (checkbox.is(':checked')) {
+ $(checkbox.parents('li').find('.media-item')).addClass('selected');
+ }
+
+ checkbox.bind('change.media', function () {
+ if (checkbox.is(':checked')) {
+ $(checkbox.parents('li').find('.media-item')).addClass('selected');
+ }
+ else {
+ $(checkbox.parents('li').find('.media-item')).removeClass('selected');
+ }
+ });
+ });
+ }
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/media/js/media.browser.js b/sites/all/modules/media/js/media.browser.js
new file mode 100644
index 000000000..88490e598
--- /dev/null
+++ b/sites/all/modules/media/js/media.browser.js
@@ -0,0 +1,124 @@
+/**
+ * @file
+ * Provides default functions for the media browser
+ */
+
+(function ($) {
+namespace('Drupal.media.browser');
+
+Drupal.media.browser.selectedMedia = [];
+Drupal.media.browser.mediaAdded = function () {};
+Drupal.media.browser.selectionFinalized = function (selectedMedia) {
+ // This is intended to be overridden if a callee wants to be triggered
+ // when the media selection is finalized from inside the browser.
+ // This is used for the file upload form for instance.
+};
+
+Drupal.behaviors.MediaBrowser = {
+ attach: function (context) {
+ if (Drupal.settings.media && Drupal.settings.media.selectedMedia) {
+ Drupal.media.browser.selectMedia(Drupal.settings.media.selectedMedia);
+ // Fire a confirmation of some sort.
+ Drupal.media.browser.finalizeSelection();
+ }
+
+ // Instantiate the tabs.
+ var showFunc = function(event, ui) {
+ // Store index of the tab being activated.
+ if (parent_iframe = Drupal.media.browser.getParentIframe(window)) {
+ $(parent_iframe).attr('current_tab', $('#media-tabs-wrapper > ul > li.ui-state-active').index());
+ }
+ };
+ var activeTab = Drupal.media.browser.tabFromHash();
+ $('#media-browser-tabset').once('MediaBrowser').tabs({
+ selected: activeTab, // jquery < 1.9
+ active: activeTab, // jquery >= 1.9
+ show: showFunc, // jquery ui < 1.8
+ activate: showFunc // jquery ui >= 1.8
+ });
+
+ $('.media-browser-tab').each( Drupal.media.browser.validateButtons );
+ }
+ // Wait for additional params to be passed in.
+};
+
+Drupal.media.browser.getParentIframe = function (window) {
+ var arrFrames = parent.document.getElementsByTagName("IFRAME");
+ for (var i = 0; i < arrFrames.length; i++) {
+ if (arrFrames[i].contentWindow === window) {
+ return arrFrames[i];
+ }
+ }
+}
+
+/**
+ * Get index of the active tab from window.location.hash
+ */
+Drupal.media.browser.tabFromHash = function () {
+ if (parent_iframe = Drupal.media.browser.getParentIframe(window)) {
+ return $(parent_iframe).attr('current_tab');
+ }
+ return 0;
+};
+
+Drupal.media.browser.launch = function () {
+
+};
+
+Drupal.media.browser.validateButtons = function() {
+ // The media browser runs in an IFRAME. The Drupal.media.popups.mediaBrowser()
+ // function sets up the IFRAME and an "OK" button that is outside of the
+ // IFRAME, so that its click handlers can destroy the IFRAME while retaining
+ // information about what media items were selected. However, Drupal UI
+ // convention is to place all action buttons on the same "line" at the bottom
+ // of the form, so if the form within the IFRAME contains a "Submit" button or
+ // other action buttons, then the "OK" button will appear below the IFRAME
+ // which breaks this convention and is confusing to the user. Therefore, we
+ // add a "Submit" button inside the IFRAME, and have its click action trigger
+ // the click action of the corresponding "OK" button that is outside the
+ // IFRAME. media.css contains CSS rules that hide the outside buttons.
+
+ // If a submit button is present, another round-trip to the server is needed
+ // before the user's selection is finalized. For these cases, when the form's
+ // real Submit button is clicked, the server either returns another form for
+ // the user to fill out, or else a completion page that contains or sets the
+ // Drupal.media.browser.selectedMedia variable. If the latter, then
+ // Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad() auto-triggers the
+ // "OK" button action to finalize the selection and remove the IFRAME.
+
+ // We need to check for the fake submit button that is used on non-form based
+ // pane content. On these items we need to bind the clicks so that media can
+ // be selected or the window can be closed. This is still a hacky approach,
+ // but it is a step in the right direction.
+
+ $('a.button.fake-submit', this).once().bind('click', Drupal.media.browser.submit);
+};
+
+Drupal.media.browser.submit = function () {
+ // @see Drupal.media.browser.validateButtons().
+ var buttons = $(parent.window.document.body).find('#mediaBrowser').parent('.ui-dialog').find('.ui-dialog-buttonpane button');
+ buttons[0].click();
+
+ // Return false to prevent the fake link "click" from continuing.
+ return false;
+}
+
+Drupal.media.browser.selectMedia = function (selectedMedia) {
+ Drupal.media.browser.selectedMedia = selectedMedia;
+};
+
+Drupal.media.browser.selectMediaAndSubmit = function (selectedMedia) {
+ Drupal.media.browser.selectedMedia = selectedMedia;
+ Drupal.media.browser.submit();
+};
+
+Drupal.media.browser.finalizeSelection = function () {
+ if (!Drupal.media.browser.selectedMedia) {
+ throw new exception(Drupal.t('Cannot continue, nothing selected'));
+ }
+ else {
+ Drupal.media.browser.selectionFinalized(Drupal.media.browser.selectedMedia);
+ }
+};
+
+}(jQuery));
diff --git a/sites/all/modules/media/js/media.core.js b/sites/all/modules/media/js/media.core.js
new file mode 100644
index 000000000..f99971c85
--- /dev/null
+++ b/sites/all/modules/media/js/media.core.js
@@ -0,0 +1,19 @@
+
+/**
+ * Creates a namespace.
+ *
+ * @return
+ * The created namespace object.
+ */
+function namespace () {
+ var a=arguments, o=null, i, j, d;
+ for (i=0; i<a.length; i=i+1) {
+ d=a[i].split(".");
+ o=window;
+ for (j=0; j<d.length; j=j+1) {
+ o[d[j]]=o[d[j]] || {};
+ o=o[d[j]];
+ }
+ }
+ return o;
+};
diff --git a/sites/all/modules/media/js/media.js b/sites/all/modules/media/js/media.js
new file mode 100644
index 000000000..5288dd307
--- /dev/null
+++ b/sites/all/modules/media/js/media.js
@@ -0,0 +1,125 @@
+/**
+ * @file
+ * Provides JavaScript additions to the media field widget.
+ *
+ * This file provides support for launching the media browser to select existing
+ * files and disabling of other media fields during Ajax uploads (which prevents
+ * separate media fields from accidentally attaching files).
+ */
+
+(function ($) {
+
+/**
+ * Attach behaviors to media element browse fields.
+ */
+Drupal.behaviors.mediaElement = {
+ attach: function (context, settings) {
+ if (settings.media && settings.media.elements) {
+ $.each(settings.media.elements, function(selector) {
+ $(selector, context).once('media-browser-launch', function () {
+ var configuration = settings.media.elements[selector];
+ // The user has JavaScript enabled, so display the browse field and hide
+ // the upload and attach fields which are only used as a fallback in
+ // case the user is unable to use the media browser.
+ $(selector, context).children('.browse').show();
+ $(selector, context).children('.upload').hide();
+ $(selector, context).children('.attach').hide();
+ $(selector, context).children('.browse').bind('click', {configuration: configuration}, Drupal.media.openBrowser);
+ });
+ });
+ }
+ }
+};
+
+/**
+ * Attach behaviors to the media attach and remove buttons.
+ */
+Drupal.behaviors.mediaButtons = {
+ attach: function (context) {
+ $('input.form-submit', context).bind('mousedown', Drupal.media.disableFields);
+ },
+ detach: function (context) {
+ $('input.form-submit', context).unbind('mousedown', Drupal.media.disableFields);
+ }
+};
+
+/**
+ * Media attach utility functions.
+ */
+Drupal.media = Drupal.media || {};
+
+/**
+ * Opens the media browser with the element's configuration settings.
+ */
+Drupal.media.openBrowser = function (event) {
+ var clickedButton = this;
+ var configuration = event.data.configuration.global;
+
+ // Find the file ID, preview and upload fields.
+ var fidField = $(this).siblings('.fid');
+ var previewField = $(this).siblings('.preview');
+ var uploadField = $(this).siblings('.upload');
+
+ // Find the edit and remove buttons.
+ var editButton = $(this).siblings('.edit');
+ var removeButton = $(this).siblings('.remove');
+
+ // Launch the media browser.
+ Drupal.media.popups.mediaBrowser(function (mediaFiles) {
+ // Ensure that there was at least one media file selected.
+ if (mediaFiles.length < 0) {
+ return;
+ }
+
+ // Grab the first of the selected media files.
+ var mediaFile = mediaFiles[0];
+
+ // Set the value of the hidden file ID field and trigger a change.
+ uploadField.val(mediaFile.fid);
+ uploadField.trigger('change');
+
+ // Find the attach button and automatically trigger it.
+ var attachButton = uploadField.siblings('.attach');
+ attachButton.trigger('mousedown');
+
+ // Display a preview of the file using the selected media file's display.
+ previewField.html(mediaFile.preview);
+ }, configuration);
+
+ return false;
+};
+
+/**
+ * Prevent media browsing when using buttons not intended to browse.
+ */
+Drupal.media.disableFields = function (event) {
+ var clickedButton = this;
+
+ // Only disable browse fields for Ajax buttons.
+ if (!$(clickedButton).hasClass('ajax-processed')) {
+ return;
+ }
+
+ // Check if we're working with an "Attach" button.
+ var $enabledFields = [];
+ if ($(this).closest('div.media-widget').length > 0) {
+ $enabledFields = $(this).closest('div.media-widget').find('input.attach');
+ }
+
+ // Temporarily disable attach fields other than the one we're currently
+ // working with. Filter out fields that are already disabled so that they
+ // do not get enabled when we re-enable these fields at the end of behavior
+ // processing. Re-enable in a setTimeout set to a relatively short amount
+ // of time (1 second). All the other mousedown handlers (like Drupal's Ajax
+ // behaviors) are excuted before any timeout functions are called, so we
+ // don't have to worry about the fields being re-enabled too soon.
+ // @todo If the previous sentence is true, why not set the timeout to 0?
+ var $fieldsToTemporarilyDisable = $('div.media-widget input.attach').not($enabledFields).not(':disabled');
+ $fieldsToTemporarilyDisable.attr('disabled', 'disabled');
+ setTimeout(function (){
+ $fieldsToTemporarilyDisable.attr('disabled', false);
+ }, 1000);
+};
+
+})(jQuery);
+
diff --git a/sites/all/modules/media/js/media.popups.js b/sites/all/modules/media/js/media.popups.js
new file mode 100644
index 000000000..2abb96e6f
--- /dev/null
+++ b/sites/all/modules/media/js/media.popups.js
@@ -0,0 +1,384 @@
+
+/**
+ * @file: Popup dialog interfaces for the media project.
+ *
+ * Drupal.media.popups.mediaBrowser
+ * Launches the media browser which allows users to pick a piece of media.
+ *
+ * Drupal.media.popups.mediaStyleSelector
+ * Launches the style selection form where the user can choose
+ * what format / style they want their media in.
+ *
+ */
+
+(function ($) {
+namespace('Drupal.media.popups');
+
+/**
+ * Media browser popup. Creates a media browser dialog.
+ *
+ * @param {function}
+ * onSelect Callback for when dialog is closed, received (Array
+ * media, Object extra);
+ * @param {Object}
+ * globalOptions Global options that will get passed upon initialization of the browser.
+ * @see Drupal.media.popups.mediaBrowser.getDefaults();
+ *
+ * @param {Object}
+ * pluginOptions Options for specific plugins. These are passed
+ * to the plugin upon initialization. If a function is passed here as
+ * a callback, it is obviously not passed, but is accessible to the plugin
+ * in Drupal.settings.variables.
+ *
+ * Example
+ * pluginOptions = {library: {url_include_patterns:'/foo/bar'}};
+ *
+ * @param {Object}
+ * widgetOptions Options controlling the appearance and behavior of the
+ * modal dialog.
+ * @see Drupal.media.popups.mediaBrowser.getDefaults();
+ */
+Drupal.media.popups.mediaBrowser = function (onSelect, globalOptions, pluginOptions, widgetOptions) {
+ var options = Drupal.media.popups.mediaBrowser.getDefaults();
+ options.global = $.extend({}, options.global, globalOptions);
+ options.plugins = pluginOptions;
+ options.widget = $.extend({}, options.widget, widgetOptions);
+
+ // Create it as a modal window.
+ var browserSrc = options.widget.src;
+ if ($.isArray(browserSrc) && browserSrc.length) {
+ browserSrc = browserSrc[browserSrc.length - 1];
+ }
+ // Params to send along to the iframe. WIP.
+ var params = {};
+ $.extend(params, options.global);
+ params.plugins = options.plugins;
+
+ browserSrc += '&' + $.param(params);
+ var mediaIframe = Drupal.media.popups.getPopupIframe(browserSrc, 'mediaBrowser');
+ // Attach the onLoad event
+ mediaIframe.bind('load', options, options.widget.onLoad);
+
+ /**
+ * Setting up the modal dialog
+ */
+ var ok = 'OK';
+ var notSelected = 'You have not selected anything!';
+
+ if (Drupal && Drupal.t) {
+ ok = Drupal.t(ok);
+ notSelected = Drupal.t(notSelected);
+ }
+
+ // @todo: let some options come through here. Currently can't be changed.
+ var dialogOptions = options.dialog;
+
+ dialogOptions.buttons[ok] = function () {
+ var selected = this.contentWindow.Drupal.media.browser.selectedMedia;
+ if (selected.length < 1) {
+ alert(notSelected);
+ return;
+ }
+ onSelect(selected);
+ $(this).dialog("close");
+ };
+
+ var dialog = mediaIframe.dialog(dialogOptions);
+
+ Drupal.media.popups.sizeDialog(dialog);
+ Drupal.media.popups.resizeDialog(dialog);
+ Drupal.media.popups.scrollDialog(dialog);
+ Drupal.media.popups.overlayDisplace(dialog.parents(".ui-dialog"));
+
+ return mediaIframe;
+};
+
+Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad = function (e) {
+ var options = e.data;
+ if (this.contentWindow.Drupal.media == undefined) return;
+
+ if (this.contentWindow.Drupal.media.browser.selectedMedia.length > 0) {
+ var ok = (Drupal && Drupal.t) ? Drupal.t('OK') : 'OK';
+ var ok_func = $(this).dialog('option', 'buttons')[ok];
+ ok_func.call(this);
+ return;
+ }
+};
+
+Drupal.media.popups.mediaBrowser.getDefaults = function () {
+ return {
+ global: {
+ types: [], // Types to allow, defaults to all.
+ activePlugins: [] // If provided, a list of plugins which should be enabled.
+ },
+ widget: { // Settings for the actual iFrame which is launched.
+ src: Drupal.settings.media.browserUrl, // Src of the media browser (if you want to totally override it)
+ onLoad: Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad // Onload function when iFrame loads.
+ },
+ dialog: Drupal.media.popups.getDialogOptions()
+ };
+};
+
+Drupal.media.popups.mediaBrowser.finalizeSelection = function () {
+ var selected = this.contentWindow.Drupal.media.browser.selectedMedia;
+ if (selected.length < 1) {
+ alert(notSelected);
+ return;
+ }
+ onSelect(selected);
+ $(this).dialog("close");
+}
+
+/**
+ * Style chooser Popup. Creates a dialog for a user to choose a media style.
+ *
+ * @param mediaFile
+ * The mediaFile you are requesting this formatting form for.
+ * @todo: should this be fid? That's actually all we need now.
+ *
+ * @param Function
+ * onSubmit Function to be called when the user chooses a media
+ * style. Takes one parameter (Object formattedMedia).
+ *
+ * @param Object
+ * options Options for the mediaStyleChooser dialog.
+ */
+Drupal.media.popups.mediaStyleSelector = function (mediaFile, onSelect, options) {
+ var defaults = Drupal.media.popups.mediaStyleSelector.getDefaults();
+ // @todo: remove this awful hack :(
+ if (typeof defaults.src === 'string' ) {
+ defaults.src = defaults.src.replace('-media_id-', mediaFile.fid) + '&fields=' + encodeURIComponent(JSON.stringify(mediaFile.fields));
+ }
+ else {
+ var src = defaults.src.shift();
+ defaults.src.unshift(src);
+ defaults.src = src.replace('-media_id-', mediaFile.fid) + '&fields=' + encodeURIComponent(JSON.stringify(mediaFile.fields));
+ }
+ options = $.extend({}, defaults, options);
+ // Create it as a modal window.
+ var mediaIframe = Drupal.media.popups.getPopupIframe(options.src, 'mediaStyleSelector');
+ // Attach the onLoad event
+ mediaIframe.bind('load', options, options.onLoad);
+
+ /**
+ * Set up the button text
+ */
+ var ok = 'OK';
+ var notSelected = 'Very sorry, there was an unknown error embedding media.';
+
+ if (Drupal && Drupal.t) {
+ ok = Drupal.t(ok);
+ notSelected = Drupal.t(notSelected);
+ }
+
+ // @todo: let some options come through here. Currently can't be changed.
+ var dialogOptions = Drupal.media.popups.getDialogOptions();
+
+ dialogOptions.buttons[ok] = function () {
+
+ var formattedMedia = this.contentWindow.Drupal.media.formatForm.getFormattedMedia();
+ if (!formattedMedia) {
+ alert(notSelected);
+ return;
+ }
+ onSelect(formattedMedia);
+ $(this).dialog("close");
+ };
+
+ var dialog = mediaIframe.dialog(dialogOptions);
+
+ Drupal.media.popups.sizeDialog(dialog);
+ Drupal.media.popups.resizeDialog(dialog);
+ Drupal.media.popups.scrollDialog(dialog);
+ Drupal.media.popups.overlayDisplace(dialog.parents(".ui-dialog"));
+
+ return mediaIframe;
+};
+
+Drupal.media.popups.mediaStyleSelector.mediaBrowserOnLoad = function (e) {
+};
+
+Drupal.media.popups.mediaStyleSelector.getDefaults = function () {
+ return {
+ src: Drupal.settings.media.styleSelectorUrl,
+ onLoad: Drupal.media.popups.mediaStyleSelector.mediaBrowserOnLoad
+ };
+};
+
+
+/**
+ * Style chooser Popup. Creates a dialog for a user to choose a media style.
+ *
+ * @param mediaFile
+ * The mediaFile you are requesting this formatting form for.
+ * @todo: should this be fid? That's actually all we need now.
+ *
+ * @param Function
+ * onSubmit Function to be called when the user chooses a media
+ * style. Takes one parameter (Object formattedMedia).
+ *
+ * @param Object
+ * options Options for the mediaStyleChooser dialog.
+ */
+Drupal.media.popups.mediaFieldEditor = function (fid, onSelect, options) {
+ var defaults = Drupal.media.popups.mediaFieldEditor.getDefaults();
+ // @todo: remove this awful hack :(
+ defaults.src = defaults.src.replace('-media_id-', fid);
+ options = $.extend({}, defaults, options);
+ // Create it as a modal window.
+ var mediaIframe = Drupal.media.popups.getPopupIframe(options.src, 'mediaFieldEditor');
+ // Attach the onLoad event
+ // @TODO - This event is firing too early in IE on Windows 7,
+ // - so the height being calculated is too short for the content.
+ mediaIframe.bind('load', options, options.onLoad);
+
+ /**
+ * Set up the button text
+ */
+ var ok = 'OK';
+ var notSelected = 'Very sorry, there was an unknown error embedding media.';
+
+ if (Drupal && Drupal.t) {
+ ok = Drupal.t(ok);
+ notSelected = Drupal.t(notSelected);
+ }
+
+ // @todo: let some options come through here. Currently can't be changed.
+ var dialogOptions = Drupal.media.popups.getDialogOptions();
+
+ dialogOptions.buttons[ok] = function () {
+ var formattedMedia = this.contentWindow.Drupal.media.formatForm.getFormattedMedia();
+ if (!formattedMedia) {
+ alert(notSelected);
+ return;
+ }
+ onSelect(formattedMedia);
+ $(this).dialog("close");
+ };
+
+ var dialog = mediaIframe.dialog(dialogOptions);
+
+ Drupal.media.popups.sizeDialog(dialog);
+ Drupal.media.popups.resizeDialog(dialog);
+ Drupal.media.popups.scrollDialog(dialog);
+ Drupal.media.popups.overlayDisplace(dialog);
+
+ return mediaIframe;
+};
+
+Drupal.media.popups.mediaFieldEditor.mediaBrowserOnLoad = function (e) {
+
+};
+
+Drupal.media.popups.mediaFieldEditor.getDefaults = function () {
+ return {
+ // @todo: do this for real
+ src: '/media/-media_id-/edit?render=media-popup',
+ onLoad: Drupal.media.popups.mediaFieldEditor.mediaBrowserOnLoad
+ };
+};
+
+
+/**
+ * Generic functions to both the media-browser and style selector
+ */
+
+/**
+ * Returns the commonly used options for the dialog.
+ */
+Drupal.media.popups.getDialogOptions = function () {
+ return {
+ buttons: {},
+ dialogClass: Drupal.settings.media.dialogOptions.dialogclass,
+ modal: Drupal.settings.media.dialogOptions.modal,
+ draggable: Drupal.settings.media.dialogOptions.draggable,
+ resizable: Drupal.settings.media.dialogOptions.resizable,
+ minWidth: Drupal.settings.media.dialogOptions.minwidth,
+ width: Drupal.settings.media.dialogOptions.width,
+ height: Drupal.settings.media.dialogOptions.height,
+ position: Drupal.settings.media.dialogOptions.position,
+ overlay: {
+ backgroundColor: Drupal.settings.media.dialogOptions.overlay.backgroundcolor,
+ opacity: Drupal.settings.media.dialogOptions.overlay.opacity
+ },
+ zIndex: Drupal.settings.media.dialogOptions.zindex,
+ close: function (event, ui) {
+ $(event.target).remove();
+ }
+ };
+};
+
+/**
+ * Get an iframe to serve as the dialog's contents. Common to both plugins.
+ */
+Drupal.media.popups.getPopupIframe = function (src, id, options) {
+ var defaults = {width: '100%', scrolling: 'auto'};
+ var options = $.extend({}, defaults, options);
+
+ return $('<iframe class="media-modal-frame"/>')
+ .attr('src', src)
+ .attr('width', options.width)
+ .attr('id', id)
+ .attr('scrolling', options.scrolling);
+};
+
+Drupal.media.popups.overlayDisplace = function (dialog) {
+ if (parent.window.Drupal.overlay && jQuery.isFunction(parent.window.Drupal.overlay.getDisplacement)) {
+ var overlayDisplace = parent.window.Drupal.overlay.getDisplacement('top');
+ if (dialog.offset().top < overlayDisplace) {
+ dialog.css('top', overlayDisplace);
+ }
+ }
+}
+
+/**
+ * Size the dialog when it is first loaded and keep it centered when scrolling.
+ *
+ * @param jQuery dialogElement
+ * The element which has .dialog() attached to it.
+ */
+Drupal.media.popups.sizeDialog = function (dialogElement) {
+ if (!dialogElement.is(':visible')) {
+ return;
+ }
+ var windowWidth = $(window).width();
+ var dialogWidth = windowWidth * 0.8;
+ var windowHeight = $(window).height();
+ var dialogHeight = windowHeight * 0.8;
+
+ dialogElement.dialog("option", "width", dialogWidth);
+ dialogElement.dialog("option", "height", dialogHeight);
+ dialogElement.dialog("option", "position", 'center');
+
+ $('.media-modal-frame').width('100%');
+}
+
+/**
+ * Resize the dialog when the window changes.
+ *
+ * @param jQuery dialogElement
+ * The element which has .dialog() attached to it.
+ */
+Drupal.media.popups.resizeDialog = function (dialogElement) {
+ $(window).resize(function() {
+ Drupal.media.popups.sizeDialog(dialogElement);
+ });
+}
+
+/**
+ * Keeps the dialog centered when the window is scrolled.
+ *
+ * @param jQuery dialogElement
+ * The element which has .dialog() attached to it.
+ */
+Drupal.media.popups.scrollDialog = function (dialogElement) {
+ // Keep the dialog window centered when scrolling.
+ $(window).scroll(function() {
+ if (!dialogElement.is(':visible')) {
+ return;
+ }
+ dialogElement.dialog("option", "position", 'center');
+ });
+}
+
+})(jQuery);
diff --git a/sites/all/modules/media/js/plugins/media.views.js b/sites/all/modules/media/js/plugins/media.views.js
new file mode 100644
index 000000000..455288d65
--- /dev/null
+++ b/sites/all/modules/media/js/plugins/media.views.js
@@ -0,0 +1,173 @@
+/**
+ * @file
+ * Handles the JS for the views file browser.
+ *
+ * Note that this does not currently support multiple file selection
+ */
+
+(function ($) {
+
+namespace('Drupal.media.browser.views');
+Drupal.behaviors.mediaViews = {
+ attach: function (context, settings) {
+
+ // Make sure when pressing enter on text inputs, the form isn't submitted
+ $('.ctools-auto-submit-full-form .views-exposed-form input:text, input:text.ctools-auto-submit', context)
+ .filter(':not(.ctools-auto-submit-exclude)')
+ .bind('keydown keyup', function (e) {
+ if(e.keyCode === 13) {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ }
+ });
+ // Disable the links on media items list
+ $('.view-content ul.media-list-thumbnails a').click(function() {
+ return false;
+ });
+
+ // We loop through the views listed in Drupal.settings.media.browser.views
+ // and set them up individually.
+ var views_ids = [];
+ for(var key in Drupal.settings.media.browser.views){
+ views_ids.push(key);
+ }
+
+ for (var i = 0; i < views_ids.length; i++) {
+ var views_id = views_ids[i];
+ for (var j= 0; j < Drupal.settings.media.browser.views[views_id].length; j++) {
+ var views_display_id = Drupal.settings.media.browser.views[views_id][j],
+ view = $('.view-id-' + views_id + '.view-display-id-' + views_display_id);
+ if (view.length) {
+ Drupal.media.browser.views.setup(view);
+ }
+ }
+ }
+
+ // Reset the state on tab-changes- bind on the 'select' event on the tabset
+ $('#media-browser-tabset').bind('tabsselect', function(event, ui) {
+ var view = $('.view', ui.panel);
+ if (view.length) {
+ Drupal.media.browser.views.select(view);
+ }
+ });
+
+ }
+}
+
+/**
+ * Event-function that is called with a view, when the tab containing that
+ * view is selected.
+ */
+Drupal.media.browser.views.select = function(view) {
+ // Reset the list of selected files
+ Drupal.media.browser.selectMedia([]);
+
+ // Reset all 'selected'-status.
+ $('.view-content .media-item', view).removeClass('selected');
+}
+
+/**
+ * Setup function. Called once for every Media Browser view.
+ *
+ * Sets up event-handlers for selecting items in the view.
+ */
+Drupal.media.browser.views.setup = function(view) {
+ // Ensure we only setup each view once..
+ if ($(view).hasClass('media-browser-views-processed')) {
+ return;
+ }
+
+ // Reset the list of selected files
+ Drupal.media.browser.selectMedia([]);
+
+ // Catch double click to submit a single item.
+ $('.view-content .media-item', view).bind('dblclick', function () {
+ var fid = $(this).closest('.media-item[data-fid]').data('fid'),
+ selectedFiles = new Array();
+
+ // Remove all currently selected files
+ $('.view-content .media-item', view).removeClass('selected');
+
+ // Mark it as selected
+ $(this).addClass('selected');
+
+ // Because the files are added using drupal_add_js() and due to the fact
+ // that drupal_get_js() runs a drupal_array_merge_deep() which re-numbers
+ // numeric key values, we have to search in Drupal.settings.media.files
+ // for the matching file ID rather than referencing it directly.
+ for (index in Drupal.settings.media.files) {
+ if (Drupal.settings.media.files[index].fid == fid) {
+ selectedFiles.push(Drupal.settings.media.files[index]);
+
+ // If multiple tabs contains the same file, it will be present in the
+ // files-array multiple times, so we break out early so we don't have
+ // it in the selectedFiles array multiple times.
+ // This would interfer with multi-selection, so...
+ break;
+ }
+ }
+ Drupal.media.browser.selectMediaAndSubmit(selectedFiles);
+ });
+
+
+ // Catch the click on a media item
+ $('.view-content .media-item', view).bind('click', function () {
+ var fid = $(this).closest('.media-item[data-fid]').data('fid'),
+ selectedFiles = new Array();
+
+ // Remove all currently selected files
+ $('.view-content .media-item', view).removeClass('selected');
+
+ // Mark it as selected
+ $(this).addClass('selected');
+
+ // Multiselect!
+ if (Drupal.settings.media.browser.params.multiselect) {
+ // Loop through the already selected files
+ for (index in Drupal.media.browser.selectedMedia) {
+ var currentFid = Drupal.media.browser.selectedMedia[index].fid;
+
+ // If the current file exists in the list of already selected
+ // files, we deselect instead of selecting
+ if (currentFid == fid) {
+ $(this).removeClass('selected');
+ // If we change the fid, the later matching won't
+ // add it back again because it can't find it.
+ fid = NaN;
+
+ // The previously selected file wasn't clicked, so we retain it
+ // as an active file
+ }
+ else {
+ // Add to list of already selected files
+ selectedFiles.push(Drupal.media.browser.selectedMedia[index]);
+
+ // Mark it as selected
+ $('.view-content *[data-fid=' + currentFid + '].media-item', view).addClass('selected');
+ }
+ }
+ }
+
+ // Because the files are added using drupal_add_js() and due to the fact
+ // that drupal_get_js() runs a drupal_array_merge_deep() which re-numbers
+ // numeric key values, we have to search in Drupal.settings.media.files
+ // for the matching file ID rather than referencing it directly.
+ for (index in Drupal.settings.media.files) {
+ if (Drupal.settings.media.files[index].fid == fid) {
+ selectedFiles.push(Drupal.settings.media.files[index]);
+
+ // If multiple tabs contains the same file, it will be present in the
+ // files-array multiple times, so we break out early so we don't have
+ // it in the selectedFiles array multiple times.
+ // This would interfer with multi-selection, so...
+ break;
+ }
+ }
+ Drupal.media.browser.selectMedia(selectedFiles);
+ });
+
+ // Add the processed class, so we dont accidentally process the same element twice..
+ $(view).addClass('media-browser-views-processed');
+}
+
+}(jQuery));
diff --git a/sites/all/modules/media/js/util/ba-debug.min.js b/sites/all/modules/media/js/util/ba-debug.min.js
new file mode 100644
index 000000000..1c50e7fb8
--- /dev/null
+++ b/sites/all/modules/media/js/util/ba-debug.min.js
@@ -0,0 +1,12 @@
+/*
+ * debug - v0.3 - 6/8/2009
+ * http://benalman.com/projects/javascript-debug-console-log/
+ *
+ * Copyright (c) 2009 "Cowboy" Ben Alman
+ * Licensed under the MIT license
+ * http://benalman.com/about/license/
+ *
+ * With lots of help from Paul Irish!
+ * http://paulirish.com/
+ */
+window.debug=(function(){var c=this,e=Array.prototype.slice,b=c.console,i={},f,g,j=9,d=["error","warn","info","debug","log"],m="assert clear count dir dirxml group groupEnd profile profileEnd time timeEnd trace".split(" "),k=m.length,a=[];while(--k>=0){(function(n){i[n]=function(){j!==0&&b&&b[n]&&b[n].apply(b,arguments)}})(m[k])}k=d.length;while(--k>=0){(function(n,o){i[o]=function(){var q=e.call(arguments),p=[o].concat(q);a.push(p);h(p);if(!b||!l(n)){return}b.firebug?b[o].apply(c,q):b[o]?b[o](q):b.log(q)}})(k,d[k])}function h(n){if(f&&(g||!b||!b.log)){f.apply(c,n)}}i.setLevel=function(n){j=typeof n==="number"?n:9};function l(n){return j>0?j>n:d.length+j<=n}i.setCallback=function(){var o=e.call(arguments),n=a.length,p=n;f=o.shift()||null;g=typeof o[0]==="boolean"?o.shift():false;p-=typeof o[0]==="number"?o.shift():n;while(p<n){h(a[p++])}};return i})(); \ No newline at end of file
diff --git a/sites/all/modules/media/js/util/json2.js b/sites/all/modules/media/js/util/json2.js
new file mode 100644
index 000000000..39d8f3706
--- /dev/null
+++ b/sites/all/modules/media/js/util/json2.js
@@ -0,0 +1,481 @@
+/*
+ http://www.JSON.org/json2.js
+ 2009-09-29
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ this.JSON = {};
+}
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
diff --git a/sites/all/modules/media/media-views-view-media-browser.tpl.php b/sites/all/modules/media/media-views-view-media-browser.tpl.php
new file mode 100644
index 000000000..b37f0cf52
--- /dev/null
+++ b/sites/all/modules/media/media-views-view-media-browser.tpl.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * @file media-views-view-media-browser.tpl.php
+ * View template to display a grid of media previews in the media browser.
+ *
+ * @see views-view-list.tpl.php
+ * @see template_preprocess_media_views_view_media_browser()
+ * @ingroup views_templates
+ */
+?>
+
+<?php print $wrapper_prefix; ?>
+ <div class="clearfix">
+ <?php print $list_type_prefix; ?>
+ <?php foreach ($rows as $id => $row): ?>
+ <li id="media-item-<?php print $row->fid; ?>" class="<?php print $classes_array[$id]; ?>">
+ <?php print $row->preview; ?>
+ </li>
+ <?php endforeach; ?>
+ <?php print $list_type_suffix; ?>
+ <div id="status"></div>
+ </div>
+<?php print $wrapper_suffix; ?>
diff --git a/sites/all/modules/media/media.api.php b/sites/all/modules/media/media.api.php
new file mode 100644
index 000000000..ecde61b24
--- /dev/null
+++ b/sites/all/modules/media/media.api.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Media module.
+ */
+
+/**
+ * Parses a url or embedded code into a unique URI.
+ *
+ * @param string $url
+ * The original URL or embed code to parse.
+ *
+ * @return array
+ * The unique URI for the file, based on its stream wrapper, or NULL.
+ *
+ * @see hook_media_parse_alter()
+ * @see media_parse_to_file()
+ * @see media_add_from_url_validate()
+ */
+function hook_media_parse($url) {
+ // Only parse URLs from our website of choice: examplevideo.com
+ if (substr($url, 0, 27) == 'http://www.examplevideo.com') {
+ // Each video has a 5 digit ID, i.e. http://www.examplevideo.com/12345
+ // Grab the ID and use it in our URI.
+ $id = substr($url, 28, 33);
+ return file_stream_wrapper_uri_normalize('examplevideo://video/' . $id);
+ }
+}
+
+/**
+ * Alters the parsing of urls and embedded codes into unique URIs.
+ *
+ * @param string $success
+ * The unique URI for the file, based on its stream wrapper, or NULL.
+ * @param array $context
+ * A nested array of contextual information containing the following keys:
+ * - url: The original URL or embed code to parse.
+ * - module: The name of the module which is attempting to parse the url or
+ * embedded code into a unique URI.
+ *
+ * @see hook_media_parse()
+ * @see hook_media_browser_plugin_info()
+ * @see media_get_browser_plugin_info()
+ */
+function hook_media_parse_alter(&$success, $context) {
+ $url = $context['url'];
+ $url_info = parse_url($url);
+
+ // Restrict users to only embedding secure links.
+ if ($url_info['scheme'] != 'https') {
+ $success = NULL;
+ }
+
+ // Use a custom handler for detecting YouTube videos.
+ if ($context['module' == 'media_youtube']) {
+ $handler = new CustomYouTubeHandler($url);
+ $success = $handler->parse($url);
+ }
+}
+
+/**
+ * Returns a list of plugins for the media browser.
+ *
+ * @return array
+ * A nested array of plugin information, keyed by plugin name. Each plugin
+ * info array may have the following keys:
+ * - title: (required) A name for the tab in the media browser.
+ * - class: (required) The class name of the handler. This class must
+ * implement a view() method, and may (should) extend the
+ * @link MediaBrowserPlugin MediaBrowserPlugin @endlink class.
+ * - weight: (optional) Integer to determine the tab order. Defaults to 0.
+ * - access callback: (optional) A callback for user access checks.
+ * - access arguments: (optional) An array of arguments for the user access
+ * check.
+ *
+ * Additional custom keys may be provided for use by the handler.
+ *
+ * @see hook_media_browser_plugin_info_alter()
+ * @see media_get_browser_plugin_info()
+ */
+function hook_media_browser_plugin_info() {
+ $info['media_upload'] = array(
+ 'title' => t('Upload'),
+ 'class' => 'MediaBrowserUpload',
+ 'weight' => -10,
+ 'access callback' => 'user_access',
+ 'access arguments' => array('create files'),
+ );
+
+ return $info;
+}
+
+/**
+ * Alter the list of plugins for the media browser.
+ *
+ * @param array $info
+ * The associative array of media browser plugin definitions from
+ * hook_media_browser_plugin_info().
+ *
+ * @see hook_media_browser_plugin_info()
+ * @see media_get_browser_plugin_info()
+ */
+function hook_media_browser_plugin_info_alter(&$info) {
+ $info['media_upload']['title'] = t('Upload 2.0');
+ $info['media_upload']['class'] = 'MediaBrowserUploadImproved';
+}
+
+/**
+ * Alter the plugins before they are rendered.
+ *
+ * @param array $plugin_output
+ * The associative array of media browser plugin information from
+ * media_get_browser_plugin_info().
+ *
+ * @see hook_media_browser_plugin_info()
+ * @see media_get_browser_plugin_info()
+ */
+function hook_media_browser_plugins_alter(&$plugin_output) {
+ $plugin_output['upload']['form']['upload']['#title'] = t('Upload 2.0');
+ $plugin_output['media_internet']['form']['embed_code']['#size'] = 100;
+}
+
+/**
+ * Alter a singleton of the params passed to the media browser.
+ *
+ * @param array $stored_params
+ * An array of parameters provided when a media_browser is launched.
+ *
+ * @see media_browser()
+ * @see media_set_browser_params()
+ */
+function hook_media_browser_params_alter(&$stored_params) {
+ $stored_params['view_mode'] = 'custom';
+ $stored_params['types'][] = 'document';
+ unset($stored_params['enabledPlugins'][0]);
+}
diff --git a/sites/all/modules/media/media.file_default_displays.inc b/sites/all/modules/media/media.file_default_displays.inc
new file mode 100644
index 000000000..3e3e2c136
--- /dev/null
+++ b/sites/all/modules/media/media.file_default_displays.inc
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Default display configuration for the default file types.
+ */
+
+/**
+ * Implements hook_file_default_displays().
+ */
+function media_file_default_displays() {
+ $file_displays = array();
+
+ // Audio previews should be displayed using a large filetype icon.
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'audio__preview__file_field_media_large_icon';
+ $file_display->weight = 49;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'image_style' => 'media_thumbnail',
+ );
+ $file_displays['audio__preview__file_field_media_large_icon'] = $file_display;
+
+ // Document previews should be displayed using a large filetype icon.
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'document__preview__file_field_media_large_icon';
+ $file_display->weight = 49;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'image_style' => 'media_thumbnail',
+ );
+ $file_displays['document__preview__file_field_media_large_icon'] = $file_display;
+
+ // Image previews should be displayed using a large filetype icon.
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'image__preview__file_field_media_large_icon';
+ $file_display->weight = 49;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'image_style' => 'media_thumbnail',
+ );
+ $file_displays['image__preview__file_field_media_large_icon'] = $file_display;
+
+ // Video previews should be displayed using a large filetype icon.
+ $file_display = new stdClass();
+ $file_display->api_version = 1;
+ $file_display->name = 'video__preview__file_field_media_large_icon';
+ $file_display->weight = 49;
+ $file_display->status = TRUE;
+ $file_display->settings = array(
+ 'image_style' => 'media_thumbnail',
+ );
+ $file_displays['video__preview__file_field_media_large_icon'] = $file_display;
+
+ return $file_displays;
+}
+
+/**
+ * Implements hook_file_default_displays_alter().
+ */
+function media_file_default_displays_alter(&$file_displays) {
+ // Image previews should be displayed using the media image style.
+ if (isset($file_displays['image__preview__file_field_image'])) {
+ $file_displays['image__preview__file_field_image']->settings['image_style'] = 'media_thumbnail';
+ }
+}
diff --git a/sites/all/modules/media/media.info b/sites/all/modules/media/media.info
new file mode 100644
index 000000000..c7b8f8710
--- /dev/null
+++ b/sites/all/modules/media/media.info
@@ -0,0 +1,32 @@
+name = Media
+description = Provides the core Media API
+package = Media
+core = 7.x
+
+dependencies[] = file_entity
+dependencies[] = image
+dependencies[] = views
+
+test_dependencies[] = token
+
+files[] = includes/MediaReadOnlyStreamWrapper.inc
+files[] = includes/MediaBrowserPluginInterface.inc
+files[] = includes/MediaBrowserPlugin.inc
+files[] = includes/MediaBrowserUpload.inc
+files[] = includes/MediaBrowserView.inc
+files[] = includes/MediaEntityTranslationHandler.inc
+files[] = includes/media_views_plugin_display_media_browser.inc
+files[] = includes/media_views_plugin_style_media_browser.inc
+files[] = tests/media.test
+
+configure = admin/config/media/browser
+
+; We have to add a fake version so Git checkouts do not fail Media dependencies
+version = 7.x-2.x-dev
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/media.install b/sites/all/modules/media/media.install
new file mode 100644
index 000000000..1e7ae31da
--- /dev/null
+++ b/sites/all/modules/media/media.install
@@ -0,0 +1,1182 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Media module.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function media_install() {
+ // Make sure that we set the icon base directory variable if it is not
+ // already set.
+ $base = variable_get('media_icon_base_directory', NULL);
+ if (!isset($base)) {
+ $default_base = 'public://media-icons';
+ variable_set('media_icon_base_directory', $default_base);
+ }
+ try {
+ _media_install_copy_icons();
+ }
+ catch (Exception $e) {
+ watchdog_exception('media', $e);
+ }
+}
+
+/**
+ * Copy the media file icons to files directory for use with image styles.
+ */
+function _media_install_copy_icons() {
+ $destination = variable_get('media_icon_base_directory', 'public://media-icons') . '/' . variable_get('media_icon_set', 'default');
+ if (!file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) {
+ throw new Exception("Unable to create directory $destination.");
+ }
+ // @todo If we ever add another default icon set, this should copy all images from one directory up.
+ $source = drupal_get_path('module', 'media') . '/images/icons/' . variable_get('media_icon_set', 'default');
+ $files = file_scan_directory($source, '/.*\.(png|jpg)$/');
+ foreach ($files as $file) {
+ $result = file_unmanaged_copy($file->uri, $destination, FILE_EXISTS_REPLACE);
+ if (!$result) {
+ throw new Exception("Unable to copy {$file->uri} to $destination.");
+ }
+ }
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function media_uninstall() {
+ // Remove variables.
+ variable_del('media_dialog_theme');
+ variable_del('media_icon_base_directory');
+ variable_del('media_icon_set');
+ variable_del('media_show_deprecated_view_modes');
+
+ // Dialog options.
+ variable_del('media_dialogclass');
+ variable_del('media_modal');
+ variable_del('media_draggable');
+ variable_del('media_resizable');
+ variable_del('media_minwidth');
+ variable_del('media_width');
+ variable_del('media_height');
+ variable_del('media_position');
+ variable_del('media_zindex');
+ variable_del('media_backgroundcolor');
+ variable_del('media_opacity');
+}
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function media_update_dependencies() {
+ // media_update_7200() needs to convert old 'media' permissions to new 'file'
+ // permissions, so it must run before file_entity_7208 which updates existing
+ // 'file' permissions to be split per file type.
+ $dependencies['file_entity'][7208] = array(
+ 'media' => 7200,
+ );
+ // This update function requires field_update_7002() to run before it since
+ // the field_bundle_settings variable has been split into separate variables
+ // per entity type and bundle.
+ $dependencies['media'][7016] = array(
+ 'field' => 7002,
+ 'rules' => 7205,
+ );
+ // Those updates require {file_type} table created.
+ $dependencies['media'][7204] = array(
+ 'file_entity' => 7201,
+ );
+ // Require {file_type}.mimetypes column before updating them.
+ $dependencies['media'][7208] = array(
+ 'file_entity' => 7210,
+ );
+ $dependencies['media'][7212] = array(
+ 'file_entity' => 7210,
+ );
+ return $dependencies;
+}
+
+/**
+ * Implements hook_requirements().
+ */
+function media_requirements($phase) {
+ $t = get_t();
+ // Make sure that file_entity module is 2.x version.
+ // We can't add this check in .info file because drupal.org testbot can't
+ // handle it. See #1734648.
+ $requirements = array();
+
+ if ($phase == 'update') {
+ $info = system_get_info('module', 'file_entity');
+ if (strpos($info['version'], '7.x-2') === FALSE) {
+ $requirements['file_entity'] = array(
+ 'title' => $t('File entity 2.x'),
+ 'value' => $t('Wrong version'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => $t('Media 2.x requires <a href="@url">File entity 2.x</a>. Please download the correct version and make sure you have deleted the file_entity folder inside the media module directory.', array('@url' => 'http://drupal.org/project/file_entity')),
+ );
+ }
+ }
+
+ return $requirements;
+}
+
+/**
+ * Deprecated update function.
+ */
+function media_update_7000() {
+}
+
+/**
+ * Deprecated update function.
+ */
+function media_update_7001() {
+}
+
+/**
+ * Create the media_type table from the media_types variable.
+ */
+function media_update_7002() {
+ if (db_table_exists('media_type')) {
+ return;
+ }
+
+ $schema['media_type'] = array(
+ 'description' => 'Stores the settings for media types.',
+ 'fields' => array(
+ 'name' => array(
+ 'description' => 'The machine name of the media type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'label' => array(
+ 'description' => 'The label of the media type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'base' => array(
+ 'description' => 'If this is a base type (i.e. cannot be deleted)',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'weight' => array(
+ 'description' => 'Weight of media type. Determines which one wins when claiming a piece of media (first wins)',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'normal',
+ ),
+ 'type_callback' => array(
+ 'description' => 'Callback to determine if provided media is of this type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ 'default' => '',
+ ),
+ 'type_callback_args' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ 'description' => 'A serialized array of name value pairs that will be passed to the callback function',
+ ),
+ ),
+ 'primary key' => array('name'),
+ );
+ db_create_table('media_type', $schema['media_type']);
+
+ drupal_load('module', 'media');
+ $old_types = variable_get('media_types', array());
+ foreach ($old_types as $type) {
+ // Was an error in the original creation.
+ if (isset($type->callbacks)) {
+ unset($type->callbacks);
+ }
+ $type->name = $type->machine_name;
+ unset($type->machine_name);
+ db_merge('media_type')
+ ->key(array('name' => $type->name))
+ ->fields((array) $type)
+ ->execute();
+ }
+ variable_del('media_types');
+}
+
+/**
+ * We now prefix media namespaced variables with media__, so fix old variables.
+ */
+function media_update_7003() {
+ drupal_load('module', 'media');
+ foreach (media_variable_default() as $variable => $value) {
+ if (($test = variable_get('media_' . $variable, TRUE)) == variable_get('media_' . $variable, FALSE)) {
+ media_variable_set($variable, $test);
+ variable_del('media_' . $variable);
+ }
+ }
+}
+
+/**
+ * Empty update function to trigger a menu rebuild.
+ */
+function media_update_7004() {
+}
+
+/**
+ * Deprecated update function.
+ */
+function media_update_7005() {
+}
+
+/**
+ * Rename the file table to file_managed in case head2head was used.
+ */
+function media_update_7006() {
+ if (db_table_exists('file') && !db_table_exists('file_managed')) {
+ db_rename_table('file', 'file_managed');
+ }
+}
+
+/**
+ * Deprecated update function.
+ */
+function media_update_7007() {
+}
+
+/**
+ * Empty function.
+ */
+function media_update_7008() {
+}
+
+/**
+ * Deprecated update function.
+ */
+function media_update_7009() {
+}
+
+/**
+ * Deprecated update function.
+ */
+function media_update_7010() {
+}
+
+/**
+ * Empty update function.
+ */
+function media_update_7011() {
+}
+
+/**
+ * Empty update function.
+ */
+function media_update_7012() {
+}
+
+/**
+ * Work around a core bug where text format cacheability is not updated.
+ *
+ * @see http://drupal.org/node/993230
+ */
+function media_update_7013() {
+ $formats = filter_formats();
+ foreach ($formats as $format) {
+ $format->filters = filter_list_format($format->format);
+ // filter_format_save() expects filters to be an array, however
+ // filter_list_format() gives us objects.
+ foreach ($format->filters as $key => $value) {
+ $format->filters[$key] = (array) $value;
+ }
+ filter_format_save($format);
+ }
+}
+
+/**
+ * Rename the media__dialog_get_theme_name variable to media__dialog_theme.
+ */
+function media_update_7014() {
+ if ($old_value = variable_get('media__dialog_get_theme_name')) {
+ variable_del('media__dialog_get_theme_name');
+ variable_set('media__dialog_theme', $old_value);
+ }
+}
+
+/**
+ * Empty update function to trigger a registry rebuild.
+ */
+function media_update_7015() {
+}
+
+/**
+ * Convert Media entities to File entities.
+ *
+ * This update function requires field_update_7002() to run before it since
+ * the field_bundle_settings variable has been split into separate variables
+ * per entity type and bundle.
+ *
+ * @see http://drupal.org/node/1418708
+ * @see http://drupal.org/node/1211008
+ */
+function media_update_7016() {
+ // Allow File Entity module to take over the {file_managed}.type field. It
+ // will create new indexes as it needs to, but it doesn't know about old ones,
+ // so delete them.
+ if (db_index_exists('file_managed', 'file_type')) {
+ db_drop_index('file_managed', 'file_type');
+ }
+ module_enable(array('file_entity'));
+
+ // Move all field instances from Media entity to File entity.
+ $instances = field_read_instances(array('entity_type' => 'media'), array('include_inactive' => TRUE, 'include_deleted' => TRUE));
+ foreach ($instances as $instance) {
+ // Skip the old self-referencing file field. It will be deleted later in
+ // this function.
+ if ($instance['field_name'] === 'file') {
+ continue;
+ }
+
+ // @todo Convert this to use _update_7000_field_read_fields()
+ $fields = field_read_fields(array('id' => $instance['field_id']), array('include_inactive' => TRUE, 'include_deleted' => TRUE));
+ $field = $fields[$instance['field_id']];
+
+ // There is no API for updating the entity_type foreign key within field
+ // data storage. We can do a direct db_update() for when the default SQL
+ // storage back-end is being used, but must skip updating fields that use a
+ // different storage type.
+ if ($field['storage']['type'] !== 'field_sql_storage' || !module_exists('field_sql_storage') || !$field['storage']['active']) {
+ $messages[] = t('Cannot update field %id (%field_name) because it does not use the field_sql_storage storage type.', array(
+ '%id' => $field['id'],
+ '%field_name' => $field['field_name'],
+ ));
+ continue;
+ }
+
+ // Update the data tables.
+ $table_name = _field_sql_storage_tablename($field);
+ $revision_name = _field_sql_storage_revision_tablename($field);
+ db_update($table_name)
+ ->fields(array('entity_type' => 'file'))
+ ->condition('entity_type', 'media')
+ ->condition('bundle', $instance['bundle'])
+ ->execute();
+ db_update($revision_name)
+ ->fields(array('entity_type' => 'file'))
+ ->condition('entity_type', 'media')
+ ->condition('bundle', $instance['bundle'])
+ ->execute();
+
+ // Once all the data has been updated, update the {field_config_instance}
+ // record.
+ db_update('field_config_instance')
+ ->fields(array('entity_type' => 'file'))
+ ->condition('id', $instance['id'])
+ ->execute();
+ }
+
+ // Update the field_bundle_settings configuration variable: move media bundle
+ // settings to file bundles, and move settings of the old self-referencing
+ // file field to the new file pseudo-field.
+ foreach ($instances as $instance) {
+ if ($instance['field_name'] === 'file' && !$instance['deleted']) {
+ $file_settings = field_bundle_settings('file', $instance['bundle']);
+ $media_settings = field_bundle_settings('media', $instance['bundle']);
+ $file_settings = array_merge($file_settings, $media_settings);
+ if (isset($instance['widget']['weight'])) {
+ $file_settings['extra_fields']['form']['file']['weight'] = $instance['widget']['weight'];
+ }
+ if (isset($instance['display'])) {
+ foreach ($instance['display'] as $view_mode => $display) {
+ if (isset($display['weight'])) {
+ $file_settings['extra_fields']['display']['file'][$view_mode]['weight'] = $display['weight'];
+ }
+ if (isset($display['type'])) {
+ $file_settings['extra_fields']['display']['file'][$view_mode]['visible'] = ($display['type'] != 'hidden');
+ }
+ }
+ }
+ field_bundle_settings('file', $instance['bundle'], $file_settings);
+ }
+ }
+ // Delete old media bundle settings.
+ db_delete('variable')
+ ->condition('name', db_like('field_bundle_settings_media__') . '%', 'LIKE')
+ ->execute();
+
+ // Copy field formatter settings of old self-referencing file field to file
+ // pseudo-field formatter settings.
+ $file_displays = variable_get('file_displays', array());
+ foreach ($instances as $instance) {
+ if ($instance['field_name'] === 'file' && !$instance['deleted']) {
+ if (isset($instance['display'])) {
+ foreach ($instance['display'] as $view_mode => $display) {
+ if (isset($display['type']) && $display['type'] != 'hidden') {
+ $file_formatter = 'file_field_' . $display['type'];
+ $file_displays[$instance['bundle']][$view_mode][$file_formatter]['status'] = TRUE;
+ if (isset($display['settings'])) {
+ $file_displays[$instance['bundle']][$view_mode][$file_formatter]['settings'] = $display['settings'];
+ }
+ }
+ }
+ }
+ }
+ }
+ variable_set('file_displays', $file_displays);
+
+ // Delete the old self-referencing file field instances. If all instances are
+ // deleted, field_delete_instance() will delete the field too.
+ foreach ($instances as $instance) {
+ if ($instance['field_name'] === 'file' && !$instance['deleted']) {
+ field_delete_instance($instance);
+ }
+ }
+
+ field_cache_clear();
+}
+
+/**
+ * Move file display configuration.
+ *
+ * Move file display configurations from the 'file_displays' variable to the
+ * {file_display} table.
+ */
+function media_update_7017() {
+ // If the {file_display} table doesn't exist, then the File Entity module's
+ // update functions will automatically take care of migrating the
+ // configurations. However, if file_entity_update_7001() has already run
+ // prior to media_update_7016(), run it again in order to capture those
+ // configurations too.
+ if (db_table_exists('file_display') && function_exists('file_entity_update_7001')) {
+ module_load_include('install', 'file_entity', 'file_entity');
+ file_entity_update_7001();
+ }
+}
+
+/**
+ * Empty update function to trigger a menu rebuild.
+ */
+function media_update_7018() {
+}
+
+/**
+ * Update old view mode formaters.
+ *
+ * Update old per-view-mode media field formatters to the generic media
+ * formatter with a setting.
+ */
+function media_update_7019() {
+ $instances = array();
+ $fields = field_read_fields(array('type' => 'media'), array('include_inactive' => TRUE));
+ foreach ($fields as $field) {
+ $instances = array_merge($instances, field_read_instances(array('field_id' => $field['id']), array('include_inactive' => TRUE)));
+ }
+ foreach ($instances as $instance) {
+ $update_instance = FALSE;
+ foreach ($instance['display'] as $view_mode => $display) {
+ if (in_array($display['type'], array('media_link', 'media_preview', 'media_small', 'media_large', 'media_original'))) {
+ $update_instance = TRUE;
+ $instance['display'][$view_mode]['type'] = 'media';
+ $instance['display'][$view_mode]['settings'] = array('file_view_mode' => $display['type']);
+ }
+ }
+ if ($update_instance) {
+ field_update_instance($instance);
+ }
+ }
+}
+
+/**
+ * Delete the wysiwyg_allowed_types variable if it is the same as default.
+ */
+function media_update_7020() {
+ if (variable_get('media__wysiwyg_allowed_types') == array('image', 'video')) {
+ variable_del('media__wysiwyg_allowed_types');
+ }
+}
+
+/**
+ * Rerun media_update_7002() due to a typo that would prevent table creation.
+ */
+function media_update_7021() {
+ media_update_7002();
+}
+
+/**
+ * Replace 'view media' perm from all users having the role with 'view file'.
+ */
+function media_update_7200() {
+ $perms = user_permission_get_modules();
+ if (!isset($perms['view files'])) {
+ throw new DrupalUpdateException('The File Entity module needs to be upgraded before continuing.');
+ }
+ else {
+ $roles = user_roles(FALSE, 'view media');
+ $permissions = array(
+ 'view media' => FALSE,
+ 'view files' => TRUE,
+ );
+ foreach ($roles as $rid => $role) {
+ user_role_change_permissions($rid, $permissions);
+ }
+ $roles = user_roles(FALSE, 'edit media');
+ $permissions = array(
+ 'edit media' => FALSE,
+ 'edit any files' => TRUE,
+ );
+ if (function_exists('file_entity_list_permissions')) {
+ unset($permissions['edit any files']);
+
+ foreach (file_entity_permissions_get_configured_types() as $type) {
+ $permissions += file_entity_list_permissions($type);
+ }
+ }
+ foreach ($roles as $rid => $role) {
+ user_role_change_permissions($rid, $permissions);
+ }
+ $roles = user_roles(FALSE, 'administer media');
+ $permissions = array(
+ 'administer media' => FALSE,
+ 'administer files' => TRUE,
+ );
+ foreach ($roles as $rid => $role) {
+ user_role_change_permissions($rid, $permissions);
+ }
+ }
+}
+
+/**
+ * Handle existing media fields.
+ *
+ * Enable the new Media Field module if this site uses "media" fields. File
+ * fields are now preferred for storing media.
+ */
+function media_update_7201() {
+ $fields = field_info_fields();
+ foreach ($fields as $field) {
+ if ($field['type'] == 'media') {
+ // This update function may run even if Media is not enabled. Don't enable
+ // Media Field if its dependencies aren't already enabled.
+ module_enable(array('mediafield'), FALSE);
+
+ // Update entries in file_usage so that they are associated with Media
+ // Field rather than Media.
+ // @TODO This update function may conflict with
+ // http://drupal.org/node/1268116
+ db_update('file_usage')
+ ->condition('module', 'media')
+ ->fields(array('module' => 'mediafield'))
+ ->execute();
+
+ return t('The "Media" field type has been moved to the new "Media Field" module. This site uses media fields, so the Media Field module has been enabled.');
+ }
+ }
+ return t('The "Media" field type has been moved to the new "Media Field" module. File fields can be used to store media.');
+}
+
+/**
+ * Enable the Views module if it is not already enabled.
+ */
+function media_update_7202() {
+ module_enable(array('views'));
+ if (!module_exists('views')) {
+ throw new DrupalUpdateException('The <a href="https://drupal.org/project/views">Views module</a> must be downloaded and available for Media updates to proceed.');
+ }
+}
+
+/**
+ * Empty update function to trigger cache clear.
+ */
+function media_update_7203() {
+ // Do nothing.
+}
+
+/**
+ * Update old Media view modes to the new File Entity ones.
+ */
+function media_update_7204() {
+ $view_mode_updates = array(
+ 'media_preview' => 'preview',
+ 'media_small' => 'teaser',
+ 'media_large' => 'full',
+ );
+
+ // Update the media__wysiwyg_default_view_mode variable.
+ $wysiwyg_default_view_mode = variable_get('media__wysiwyg_default_view_mode');
+ if (isset($wysiwyg_default_view_mode) && isset($view_mode_updates[$wysiwyg_default_view_mode])) {
+ $wysiwyg_default_view_mode = $view_mode_updates[$wysiwyg_default_view_mode];
+ variable_set('media__wysiwyg_default_view_mode', $wysiwyg_default_view_mode);
+ }
+
+ // Update view mode references in the 'field_bundle_settings' variable.
+ $field_bundle_settings = variable_get('field_bundle_settings');
+ if (!empty($field_bundle_settings['file'])) {
+ foreach ($field_bundle_settings['file'] as $file_type => $info) {
+ // Per-bundle information about the view modes.
+ foreach ($view_mode_updates as $old_view_mode => $new_view_mode) {
+ if (isset($info['view_modes'][$old_view_mode])) {
+ $field_bundle_settings['file'][$file_type]['view_modes'][$new_view_mode] = $info['view_modes'][$old_view_mode];
+ unset($field_bundle_settings['file'][$file_type]['view_modes'][$old_view_mode]);
+ }
+ // The File Entity module defaults to not use custom settings for the
+ // new view modes, but the Media module used to default to using custom
+ // settings, so if this variable is not defined, use the prior default.
+ if (!isset($field_bundle_settings['file'][$file_type]['view_modes'][$new_view_mode]['custom_settings'])) {
+ $field_bundle_settings['file'][$file_type]['view_modes'][$new_view_mode]['custom_settings'] = TRUE;
+ }
+ }
+
+ // Settings for the "extra fields" configured on the Manage Display page.
+ if (!empty($info['extra_fields']['display'])) {
+ foreach ($info['extra_fields']['display'] as $extra_field_name => $extra_field_info) {
+ foreach ($view_mode_updates as $old_view_mode => $new_view_mode) {
+ if (isset($extra_field_info[$old_view_mode])) {
+ $field_bundle_settings['file'][$file_type]['extra_fields']['display'][$extra_field_name][$new_view_mode] = $extra_field_info[$old_view_mode];
+ unset($field_bundle_settings['file'][$file_type]['extra_fields']['display'][$extra_field_name][$old_view_mode]);
+ }
+ }
+ }
+ }
+ }
+ }
+ variable_set('field_bundle_settings', $field_bundle_settings);
+
+ // Move settings for fields attached to files from the old view modes to the
+ // new ones.
+ $instances = field_read_instances(array('entity_type' => 'file'));
+ foreach ($instances as $instance) {
+ $updated = FALSE;
+ foreach ($view_mode_updates as $old_view_mode => $new_view_mode) {
+ if (isset($instance['display'][$old_view_mode])) {
+ $instance['display'][$new_view_mode] = $instance['display'][$old_view_mode];
+ unset($instance['display'][$old_view_mode]);
+ $updated = TRUE;
+ }
+ }
+ if ($updated) {
+ field_update_instance($instance);
+ }
+ }
+
+ // Move "Manage file display" settings from old view modes to new ones.
+ $file_display_names = db_query('SELECT name FROM {file_display}')->fetchCol();
+ foreach ($file_display_names as $old_file_display_name) {
+ list($file_type, $view_mode, $formatter) = explode('__', $old_file_display_name, 3);
+ if (isset($view_mode_updates[$view_mode])) {
+ $view_mode = $view_mode_updates[$view_mode];
+ $new_file_display_name = implode('__', array($file_type, $view_mode, $formatter));
+ db_delete('file_display')->condition('name', $new_file_display_name)->execute();
+ db_update('file_display')->fields(array('name' => $new_file_display_name))->condition('name', $old_file_display_name)->execute();
+ }
+ }
+
+ // Update file/image/media fields that use a formatter that reference an old
+ // file view modes to reference the new ones.
+ foreach (field_read_instances() as $instance) {
+ if (!empty($instance['display'])) {
+ $updated = FALSE;
+ foreach ($instance['display'] as $instance_view_mode => $display) {
+ if (isset($display['settings']['file_view_mode']) && isset($view_mode_updates[$display['settings']['file_view_mode']])) {
+ $instance['display'][$instance_view_mode]['settings']['file_view_mode'] = $view_mode_updates[$display['settings']['file_view_mode']];
+ $updated = TRUE;
+ }
+ }
+ if ($updated) {
+ field_update_instance($instance);
+ }
+ }
+ }
+
+ // Update formatter settings that reference the old view modes within saved
+ // Views.
+ if (db_table_exists('views_display')) {
+ $result = db_select('views_display', 'v')->fields('v', array('vid', 'id', 'display_options'))->execute();
+ foreach ($result as $record) {
+ if (!empty($record->display_options)) {
+ $display_options = unserialize($record->display_options);
+ if (_media_update_7204_update_views_display_options($display_options, $view_mode_updates)) {
+ db_update('views_display')
+ ->fields(array('display_options' => serialize($display_options)))
+ ->condition('vid', $record->vid)
+ ->condition('id', $record->id)
+ ->execute();
+ }
+ }
+ }
+ }
+
+ // Update formatter settings that reference the old view modes within unsaved
+ // Views in the CTools object cache. Objects in the CTools cache are instances
+ // of classes, so the Views module must be enabled to unserialize it
+ // correctly.
+ if (db_table_exists('ctools_object_cache') && module_exists('views')) {
+ $result = db_select('ctools_object_cache', 'c')->fields('c', array('sid', 'name', 'obj', 'data'))->condition('obj', 'view')->execute();
+ foreach ($result as $record) {
+ $view = unserialize($record->data);
+ if (!empty($view->display)) {
+ $updated = FALSE;
+ foreach ($view->display as $display_name => $display) {
+ if (!empty($display->display_options) && _media_update_7204_update_views_display_options($display->display_options, $view_mode_updates)) {
+ $updated = TRUE;
+ }
+ }
+ if ($updated) {
+ db_update('ctools_object_cache')
+ ->fields(array('data' => serialize($view)))
+ ->condition('sid', $record->sid)
+ ->condition('name', $record->name)
+ ->condition('obj', $record->obj)
+ ->execute();
+ }
+ }
+ }
+ }
+
+ // Clear caches that might contain stale Views displays.
+ if (module_exists('views')) {
+ cache_clear_all('*', 'cache_views', TRUE);
+ cache_clear_all('*', 'cache_views_data', TRUE);
+ }
+ if (module_exists('block')) {
+ cache_clear_all('*', 'cache_block', TRUE);
+ }
+ cache_clear_all('*', 'cache_page', TRUE);
+
+ // We still have the old media_link and media_original view modes that must be
+ // supported for now.
+ // @TODO: Make this apply only to updates from Media 1.x.
+ // @see media_entity_info_alter()
+ variable_set('media__show_deprecated_view_modes', TRUE);
+}
+
+/**
+ * Drop the unused {media_list_type} table.
+ */
+function media_update_7205() {
+ if (db_table_exists('media_list_type')) {
+ db_drop_table('media_list_type');
+ return t('Dropped the unused {media_list_type} table.');
+ }
+}
+
+/**
+ * Move default file display configurations to the database.
+ */
+function media_update_7206() {
+ module_load_include('inc', 'file_entity', 'file_entity.file_api');
+ module_load_include('inc', 'ctools', 'includes/export');
+ $default_image_styles = array(
+ 'preview' => 'square_thumbnail',
+ 'teaser' => 'medium',
+ 'full' => 'large',
+ );
+
+ // Only needed by sites that updated from Media 1.x.
+ // @see media_entity_info_alter()
+ if (variable_get('media__show_deprecated_view_modes')) {
+ $default_image_styles['media_original'] = '';
+ }
+
+ // Clear out the ctools cache so that the old default implementations
+ // are removed.
+ ctools_export_load_object_reset('file_display');
+ foreach ($default_image_styles as $view_mode => $image_style) {
+ $existing_displays = file_displays_load('image', $view_mode, TRUE);
+ // Only insert default config into the database if no existing
+ // configuration is found.
+ if (!isset($existing_displays['file_image'])) {
+ $display_name = 'image__' . $view_mode . '__file_image';
+ $display = array(
+ 'api_version' => 1,
+ 'name' => $display_name,
+ 'status' => 1,
+ 'weight' => 5,
+ 'settings' => array('image_style' => $image_style),
+ 'export_type' => NULL,
+ );
+ file_display_save((object) $display);
+ }
+ }
+}
+
+/**
+ * Trigger cache clear.
+ *
+ * Empty update function to trigger cache clear after changing access callbacks
+ * to file_entity_access.
+ */
+function media_update_7207() {
+ // Do nothing.
+}
+
+/**
+ * Drop the media_types table and migrate files to file_entity types.
+ */
+function media_update_7208() {
+ // Reset static cache to ensure our new file types are recognized
+ drupal_static_reset('ctools_export_load_object_table_exists');
+
+ if (!db_table_exists('media_type')) {
+ // No types to migrate.
+ return;
+ }
+ // @see http://drupal.org/node/1292382
+ if (!function_exists('file_type_get_enabled_types')) {
+ throw new DrupalUpdateException('The File Entity module needs to be upgraded before continuing.');
+ }
+ else {
+ $existing_types = db_select('media_type', 'mt')
+ ->orderBy('weight')
+ ->fields('mt')
+ ->execute()
+ // Will key by the name field.
+ ->fetchAllAssoc('name');
+ foreach ($existing_types as &$type) {
+ $type->type_callback_args = unserialize($type->type_callback_args);
+ }
+
+ include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
+ $mapping = file_mimetype_mapping();
+ // We do not migrate this type, since there is no way to handle its weight.
+ unset($existing_types['default']);
+ foreach ($existing_types as $type) {
+ $extensions = isset($type->type_callback_args['extensions']) ? $type->type_callback_args['extensions'] : array();
+ $mimetypes = isset($type->type_callback_args['mimetypes']) ? $type->type_callback_args['mimetypes'] : array();
+ // Add mimetypes by extensions.
+ foreach ($extensions as $extension) {
+ if (isset($mapping['extensions'][$extension])) {
+ $type->mimetypes[] = $mapping['mimetypes'][$mapping['extensions'][$extension]];
+ }
+ }
+ // Add rest mimetypes.
+ foreach ($mimetypes as $mimetype) {
+ // Mimetype is a regex pattern.
+ foreach ($mapping['mimetypes'] as $mapping_mimetype) {
+ if (preg_match($mimetype, $mapping_mimetype) && !in_array($mapping_mimetype, $type->mimetypes)) {
+ $type->mimetypes[] = $mapping_mimetype;
+ }
+ }
+ }
+ $type->streams = isset($type->type_callback_args['streams']) ? $type->type_callback_args['streams'] : array();
+ $type->type = $type->name;
+ // Merge existing type with new ones.
+ if ($new_type = file_type_load($type->name)) {
+ $new_type->mimetypes = array_merge($type->mimetypes, $new_type->mimetypes);
+ $new_type->streams = array_merge($type->streams, $new_type->streams);
+ }
+ else {
+ $new_type = $type;
+ }
+ file_type_save($new_type);
+ }
+ db_drop_table('media_type');
+
+ // Special treatment for old media application type to new file_entity
+ // document one. Add some more mimetypes to document.
+ $document_type = file_type_load('document');
+ if (!$document_type) {
+ return;
+ }
+ foreach ($mapping['mimetypes'] as $mimetype) {
+ $is_document = strpos($mimetype, 'document') !== FALSE || strpos($mimetype, 'application/vnd.ms-') !== FALSE;
+ if ($is_document && !in_array($mimetype, $document_type->mimetypes)) {
+ $document_type->mimetypes[] = $mimetype;
+ }
+ }
+ file_type_save($document_type);
+ }
+}
+
+/**
+ * Enable the hidden media_migrate_file_types module to provide a UI to update
+ * {file_managed}.type with the new file types provided by file_entity.
+ */
+function media_update_7209() {
+ drupal_load('module', 'media_migrate_file_types');
+
+ if (_media_migrate_file_types_get_migratable_file_types()) {
+ module_enable(array('media_migrate_file_types'));
+ }
+}
+
+/**
+ * Delete deceprated media__type_icon_directory variable.
+ */
+function media_update_7210() {
+ variable_del('media__type_icon_directory');
+}
+
+/**
+ * Save a square_thumbnail image style in the database for legacy support if one
+ * does not already exist.
+ */
+function media_update_7211() {
+ $default_style = array(
+ 'name' => 'square_thumbnail'
+ );
+
+ // Clear the image cache to remove any old image styles that only exist in
+ // code.
+ cache_clear_all('*', 'cache_image', TRUE);
+
+ // Check if the square_thumbnail image style exists.
+ // The style will only exist if the user has customized it, otherwise it would
+ // have been removed by clearing the image style cache.
+ $existing_style = image_style_load('square_thumbnail');
+
+ // Save a square_thumbnail image style in the database for legacy support.
+ // This is only necessary if a square_thumbnail image style doesn't already
+ // exist.
+ if (empty($existing_style)) {
+ $style = image_style_save($default_style);
+
+ $effect = array(
+ 'name' => 'image_scale_and_crop',
+ 'data' => array(
+ 'width' => 180,
+ 'height' => 180,
+ 'weight' => 0,
+ ),
+ 'isid' => $style['isid'],
+ );
+
+ image_effect_save($effect);
+ }
+
+ return t('Saved a square_thumbnail image style in the database for legacy support if one did not already exist.');
+}
+
+/**
+ * Utility function for update 7204. Updates display options within Views.
+ */
+function _media_update_7204_update_views_display_options(&$display_options, $view_mode_updates) {
+ $updated = FALSE;
+
+ // Update fields that use a formatter with a file_view_mode formatter setting.
+ if (!empty($display_options['fields'])) {
+ foreach ($display_options['fields'] as $field_name => $field_display) {
+ if (isset($field_display['settings']['file_view_mode']) && isset($view_mode_updates[$field_display['settings']['file_view_mode']])) {
+ $display_options['fields'][$field_name]['settings']['file_view_mode'] = $view_mode_updates[$field_display['settings']['file_view_mode']];
+ $updated = TRUE;
+ }
+ }
+ }
+
+ // Update Views that display files directly using a row plugin with a view
+ // mode setting.
+ if (isset($display_options['row_plugin']) && $display_options['row_plugin'] === 'file' && isset($display_options['row_options']['view_mode']) && isset($view_mode_updates[$display_options['row_options']['view_mode']])) {
+ $display_options['row_options']['view_mode'] = $view_mode_updates[$display_options['row_options']['view_mode']];
+ $updated = TRUE;
+ }
+ return $updated;
+}
+
+/**
+ * Re-create application file type for legacy reasons.
+ */
+function media_update_7212() {
+ module_load_include('inc', 'file_entity', 'file_entity.file_api');
+ if (!file_type_load('application')) {
+ $application = (object) array(
+ 'api_version' => 1,
+ 'type' => 'application',
+ 'label' => t('Application'),
+ 'description' => t('Multipurpose type - kept to support older sites.'),
+ 'mimetypes' => array(),
+ 'streams' => array(
+ 'public',
+ ),
+ );
+
+ file_type_save($application);
+ $application = file_type_load('application');
+ file_type_disable($application);
+ }
+}
+
+/**
+ * Remove the obsolete file_extensions variable.
+ */
+function media_update_7213() {
+ $media_file_extensions = explode(' ', variable_get('media__file_extensions'));
+ $file_entity_file_extensions = explode(' ', variable_get('file_entity_default_allowed_extensions', 'jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp mp3 mov mp4 m4a m4v mpeg avi ogg oga ogv weba webp webm'));
+
+ // Preserve any custom file extensions.
+ if (array_diff($media_file_extensions, $file_entity_file_extensions)) {
+ $combined_file_extensions = array_unique(array_merge($file_entity_file_extensions, $media_file_extensions));
+ variable_set('file_entity_default_allowed_extensions', implode(' ' , $combined_file_extensions));
+ }
+
+ variable_del('media__file_extensions');
+}
+
+/**
+ * Drop the legacy {media_filter_usage} table.
+ */
+function media_update_7214() {
+ if (db_table_exists('media_filter_usage')) {
+ db_drop_table('media_filter_usage');
+ }
+}
+
+/**
+ * Skipped to run media_update_7217().
+ */
+function media_update_7216() {
+ // Do nothing.
+}
+
+/**
+ * Copy file type icons to public files directory.
+ */
+function media_update_7217() {
+ // Remove any trailing slashes from the icon base directory variable.
+ $dir = variable_get('media__icon_base_directory');
+ if (!empty($dir)) {
+ $dir = rtrim($dir, '/');
+ variable_set('media__icon_base_directory', $dir);
+ }
+
+ try {
+ _media_install_copy_icons();
+ }
+ catch (Exception $e) {
+ throw new DrupalUpdateException($e->getMessage());
+ }
+}
+
+/**
+ * Drop the legacy {cache_media_xml} table.
+ */
+function media_update_7218() {
+ if (db_table_exists('cache_media_xml')) {
+ db_drop_table('cache_media_xml');
+ }
+
+ variable_del('media__xml_cache_expire');
+}
+
+/**
+ * Enable the Media WYSIWYG submodule.
+ */
+function media_update_7219() {
+ if (module_exists('wysiwyg')) {
+ module_enable(array('media_wysiwyg'));
+ }
+}
+
+/**
+ * Delete the deprecated media__file_list_size variable.
+ */
+function media_update_7220() {
+ variable_del('media__file_list_size');
+}
+
+/**
+ * Enable the Media Bulk Upload submodule.
+ */
+function media_update_7221() {
+ if (module_exists('multiform') && module_exists('plupload')) {
+ module_enable(array('media_bulk_upload'));
+ }
+}
+
+/**
+ * Delete the deprecated media__display_types_migration_mess variable.
+ */
+function media_update_7222() {
+ variable_del('media__display_types_migration_mess');
+}
+
+/**
+ * Delete legacy variables.
+ */
+function media_update_7223() {
+ variable_del('media__max_filesize');
+ variable_del('media__debug');
+ variable_del('media__xml_cache_expire');
+ variable_del('media__show_file_type_rebuild_nag');
+ variable_del('media__field_select_media_text');
+ variable_del('media__field_remove_media_text');
+ variable_del('media__browser_library_empty_message');
+ variable_del('media__browser_pager_limit');
+ variable_del('media__browser_viewtype_default');
+}
+
+/**
+ * Rename variables, removing variable namespace.
+ */
+function media_update_7224() {
+ // Create an array of variables sans 'media' prefix.
+ $variables = array('wysiwyg_title', 'wysiwyg_icon_title', 'wysiwyg_default_view_mode', 'wysiwyg_upload_directory', 'wysiwyg_allowed_types', 'wysiwyg_allowed_attributes', 'wysiwyg_browser_plugins', 'dialog_theme', 'import_batch_size', 'fromurl_supported_schemes', 'icon_base_directory', 'icon_set', 'show_deprecated_view_modes');
+
+ foreach ($variables as $variable) {
+ // Find the value of the old variable.
+ $value = variable_get('media__' . $variable);
+
+ // Port the value of the variable if it was set.
+ if (!is_null($value)) {
+ variable_set('media_' . $variable, $value);
+ }
+
+ // Remove the old variable.
+ variable_del('media__' . $variable);
+ }
+}
+
+/**
+ * Migrate variables to appropriate submodules.
+ */
+function media_update_7225() {
+ $data = array(
+ 'media_wysiwyg' => array(
+ 'wysiwyg_title',
+ 'wysiwyg_icon_title',
+ 'wysiwyg_default_view_mode',
+ 'wysiwyg_upload_directory',
+ 'wysiwyg_allowed_types',
+ 'wysiwyg_allowed_attributes',
+ 'wysiwyg_browser_plugins',
+ ),
+ 'media_internet' => array(
+ 'fromurl_supported_schemes',
+ ),
+ 'media_bulk_upload' => array(
+ 'import_batch_size',
+ ),
+ );
+
+ foreach ($data as $module => $variables) {
+ foreach ($variables as $variable) {
+ // Only port variables to submodules if the submodule exists.
+ if (module_exists($module)) {
+ // Find the value of the old variable.
+ $value = variable_get('media_' . $variable);
+
+ // Port the value of the variable if it was set.
+ if (!is_null($value)) {
+ variable_set($module . '_' . $variable, $value);
+ }
+ }
+
+ // Remove the old variable.
+ variable_del('media_' . $variable);
+ }
+ }
+}
+
+/**
+ * Grant existing user access to new media browser permission.
+ */
+function media_update_7226() {
+ $roles = user_roles(FALSE, 'create files');
+
+ foreach ($roles as $rid => $role) {
+ user_role_grant_permissions($rid, array('access media browser'));
+ }
+}
diff --git a/sites/all/modules/media/media.media.inc b/sites/all/modules/media/media.media.inc
new file mode 100644
index 000000000..2700265f8
--- /dev/null
+++ b/sites/all/modules/media/media.media.inc
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Media module integration for the Media module.
+ */
+
+/**
+ * Implements hook_media_browser_plugin_info().
+ */
+function media_media_browser_plugin_info() {
+ $info['upload'] = array(
+ 'title' => t('Upload'),
+ 'weight' => -10,
+ 'class' => 'MediaBrowserUpload',
+ );
+
+ // Add a plugin for each View display using the 'media_browser' display type.
+ $view_weight = 10;
+ foreach (views_get_enabled_views() as $view) {
+ foreach ($view->display as $display) {
+ if ($display->display_plugin == 'media_browser') {
+ $title = $display->display_title;
+ if (!empty($display->display_options['title'])) {
+ $title = $display->display_options['title'];
+ }
+ $info["{$view->name}--{$display->id}"] = array(
+ 'title' => $title,
+ // @TODO make this configurable.
+ 'weight' => $view_weight++,
+ 'class' => 'MediaBrowserView',
+ 'view_name' => $view->name,
+ 'view_display_id' => $display->id,
+ );
+ }
+ }
+ }
+
+ return $info;
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * @todo: Potentially move this into media.module in a future version of Media.
+ */
+function media_query_media_browser_alter($query) {
+ // Ensure that the query is against the file_managed table.
+ $tables = $query->getTables();
+
+ if (empty($tables['file_managed'])) {
+ throw new Exception(t('Media browser being queried without the file_managed table.'));
+ }
+
+ $alias = $tables['file_managed']['alias'];
+
+ // How do we validate these? I don't know.
+ // I think PDO should protect them, but I'm not 100% certain.
+ $params = media_get_browser_params();
+
+ // Gather any file restrictions.
+ $types = !empty($params['types']) ? $params['types'] : array();
+ $schemes = !empty($params['schemes']) ? $params['schemes'] : array();
+ $extensions = !empty($params['file_extensions']) ? explode(' ', $params['file_extensions']) : array();
+
+ $or = db_or();
+
+ // Filter out files with restricted types.
+ if (!empty($types)) {
+ $query->condition($alias . '.type', $types, 'IN');
+ }
+
+ // Filter out files with restricted schemes.
+ if (!empty($schemes)) {
+ $local_or = db_or();
+ $local_and = db_and();
+
+ // Gather all of the local stream wrappers.
+ $local_stream_wrappers = media_get_local_stream_wrappers();
+
+ foreach ($schemes as $scheme) {
+ // Only local files have extensions.
+ // Filter out files with restricted extensions.
+ if (!empty($extensions) && isset($local_stream_wrappers[$scheme])) {
+ $mimetypes = array();
+ foreach ($extensions as $extension) {
+ $mimetype = media_get_extension_mimetype($extension);
+ if ($mimetype) {
+ $mimetypes[] = $mimetype;
+ }
+ }
+ $local_and->condition($alias . '.uri', db_like($scheme . '://') . '%', 'LIKE');
+ if (count($mimetypes)) {
+ $local_and->condition($alias . '.filemime', $mimetypes, 'IN');
+ }
+ $local_or->condition($local_and);
+ $local_and = db_and();
+ }
+ else {
+ $local_or->condition($alias . '.uri', db_like($scheme . '://') . '%', 'LIKE');
+ }
+ }
+
+ $or->condition($local_or);
+ }
+
+ if ($or->count()) {
+ $query->condition($or);
+ }
+}
diff --git a/sites/all/modules/media/media.module b/sites/all/modules/media/media.module
new file mode 100644
index 000000000..1f97bcb7a
--- /dev/null
+++ b/sites/all/modules/media/media.module
@@ -0,0 +1,1402 @@
+<?php
+
+/**
+ * @file
+ * Media API
+ *
+ * The core Media API.
+ * See http://drupal.org/project/media for more details.
+ */
+
+// Code relating to using media as a field.
+require_once dirname(__FILE__) . '/includes/media.fields.inc';
+
+/**
+ * Implements hook_hook_info().
+ */
+function media_hook_info() {
+ $hooks = array(
+ 'media_parse',
+ 'media_browser_plugin_info',
+ 'media_browser_plugin_info_alter',
+ 'media_browser_plugins_alter',
+ 'media_browser_params_alter',
+ 'query_media_browser_alter',
+ );
+
+ return array_fill_keys($hooks, array('group' => 'media'));
+}
+
+/**
+ * Implements hook_help().
+ */
+function media_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#media':
+ $output = '';
+ $output .= '<h3>' . t('About') . '</h3>';
+ $output .= '<p>' . t('The Media module is a File Browser to the Internet, media provides a framework for managing files and multimedia assets, regardless of whether they are hosted on your own site or a 3rd party site. It replaces the Drupal core upload field with a unified User Interface where editors and administrators can upload, manage, and reuse files and multimedia assets. Media module also provides rich integration with WYSIWYG module to let content creators access media assets in rich text editor. Javascript is required to use the Media module. For more information check <a href="@media_faq">Media Module page</a>', array('@media_faq' => 'http://drupal.org/project/media')) . '.</p>';
+ $output .= '<h3>' . t('Uses') . '</h3>';
+ $output .= '<dl>';
+ $output .= '<dt>' . t('Media Repository') . '</dt>';
+ $output .= '<dd>' . t('Media module allows you to maintain a <a href="@mediarepo">media asset repository</a> where in you can add, remove, reuse your media assets. You can add the media file using upload form or from a url and also do bulk operations on the media assets.', array('@mediarepo' => url('admin/content/media'))) . '</dd>';
+ $output .= '<dt>' . t('Attaching media assets to content types') . '</dt>';
+ $output .= '<dd>' . t('Media assets can be attached to content types as fields. To add a media field to a <a href="@content-type">content type</a>, go to the content type\'s <em>manage fields</em> page, and add a new field of type <em>Multimedia Asset</em>.', array('@content-type' => url('admin/structure/types'))) . '</dd>';
+ $output .= '<dt>' . t('Using media assets in WYSIWYG') . '</dt>';
+ $output .= '<dd>' . t('Media module provides rich integration with WYSIWYG editors, using Media Browser plugin you can select media asset from library to add to the rich text editor moreover you can add media asset from the media browser itself using either upload method or add from url method. To configure media with WYSIWYG you need two steps of configuration:');
+ $output .= '<ul><li>' . t('Enable WYSIWYG plugin on your desired <a href="@wysiwyg-profile">WYSIWYG profile</a>. Please note that you will need to have <a href="@wysiwyg">WYSIWYG</a> module enabled.', array('@wysiwyg-profile' => url('admin/config/content/wysiwyg'), '@wysiwyg' => 'http://drupal.org/project/wysiwyg')) . '</li>';
+ $output .= '<li>' . t('Enable the <em>Convert Media tags to markup</em> filter on the <a href="@input-format">Input format</a> you are using with the WYSIWYG profile.', array('@input-format' => url('admin/config/content/formats'))) . '</li></ul></dd>';
+ return $output;
+ }
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function media_entity_info_alter(&$entity_info) {
+ // For sites that updated from Media 1.x, continue to provide these deprecated
+ // view modes.
+ // @see http://drupal.org/node/1051090
+ if (variable_get('media_show_deprecated_view_modes', FALSE)) {
+ $entity_info['file']['view modes']['media_link'] = array(
+ 'label' => t('Link'),
+ 'custom settings' => TRUE,
+ );
+ $entity_info['file']['view modes']['media_original'] = array(
+ 'label' => t('Original'),
+ 'custom settings' => TRUE,
+ );
+ }
+
+ if (module_exists('entity_translation')) {
+ $entity_info['file']['translation']['entity_translation']['class'] = 'MediaEntityTranslationHandler';
+ }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function media_menu() {
+ // For managing different types of media and the fields associated with them.
+ $items['admin/config/media/browser'] = array(
+ 'title' => 'Media browser settings',
+ 'description' => 'Configure the behavior and display of the media browser.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_admin_config_browser'),
+ 'access arguments' => array('administer media browser'),
+ 'file' => 'includes/media.admin.inc',
+ );
+
+ // Administrative screens for managing media.
+ $items['admin/content/file/thumbnails'] = array(
+ 'title' => 'Thumbnails',
+ 'description' => 'Manage files used on your site.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_admin_file'),
+ 'access arguments' => array('administer files'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'file_entity.admin.inc',
+ 'file path' => drupal_get_path('module', 'file_entity'),
+ 'weight' => 10,
+ );
+
+ $items['media/ajax'] = array(
+ 'page callback' => 'media_ajax_upload',
+ 'delivery callback' => 'ajax_deliver',
+ 'access arguments' => array('access content'),
+ 'theme callback' => 'ajax_base_page_theme',
+ 'type' => MENU_CALLBACK,
+ );
+
+ $items['media/browser'] = array(
+ 'title' => 'Media browser',
+ 'description' => 'Media Browser for picking media and uploading new media',
+ 'page callback' => 'media_browser',
+ 'access arguments' => array('access media browser'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/media.browser.inc',
+ 'theme callback' => 'media_dialog_get_theme_name',
+ );
+
+ // A testbed to try out the media browser with different launch commands.
+ $items['media/browser/testbed'] = array(
+ 'title' => 'Media Browser test',
+ 'description' => 'Make it easier to test media browser',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_browser_testbed'),
+ 'access arguments' => array('administer files'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/media.browser.inc',
+ );
+
+ // We could re-use the file/%file/edit path for the modal callback, but
+ // it is just easier to use our own namespace here.
+ $items['media/%file/edit/%ctools_js'] = array(
+ 'title' => 'Edit',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_file_edit_modal', 1, 3),
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('update', 1),
+ 'theme callback' => 'ajax_base_page_theme',
+ 'file' => 'includes/media.pages.inc',
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ */
+function media_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ // Add action link to 'file/add' on 'admin/content/file/thumbnails' page.
+ if ($root_path == 'admin/content/file/thumbnails') {
+ $item = menu_get_item('file/add');
+ if (!empty($item['access'])) {
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $item,
+ '#weight' => $item['weight'],
+ );
+ }
+ }
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function media_admin_paths() {
+ $paths['media/*/edit/*'] = TRUE;
+ $paths['media/*/format-form'] = TRUE;
+
+ // If the media browser theme is set to the admin theme, ensure it gets set
+ // as an admin path as well.
+ $dialog_theme = variable_get('media_dialog_theme', '');
+ if (empty($dialog_theme) || $dialog_theme == variable_get('admin_theme')) {
+ $paths['media/browser'] = TRUE;
+ $paths['media/browser/*'] = TRUE;
+ }
+
+ return $paths;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function media_permission() {
+ return array(
+ 'administer media browser' => array(
+ 'title' => t('Administer media browser'),
+ 'description' => t('Access media browser settings.'),
+ ),
+ 'access media browser' => array(
+ 'title' => t('Use the media browser'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_theme().
+ */
+function media_theme() {
+ return array(
+ // media.module.
+ 'media_element' => array(
+ 'render element' => 'element',
+ ),
+
+ // media.field.inc.
+ 'media_widget' => array(
+ 'render element' => 'element',
+ ),
+ 'media_widget_multiple' => array(
+ 'render element' => 'element',
+ ),
+ 'media_upload_help' => array(
+ 'variables' => array('description' => NULL),
+ ),
+
+ // media.theme.inc.
+ 'media_thumbnail' => array(
+ 'render element' => 'element',
+ 'file' => 'includes/media.theme.inc',
+ ),
+ 'media_formatter_large_icon' => array(
+ 'variables' => array('file' => NULL, 'attributes' => array(), 'style_name' => 'media_thumbnail'),
+ 'file' => 'includes/media.theme.inc',
+ ),
+ 'media_dialog_page' => array(
+ 'render element' => 'page',
+ 'template' => 'templates/media-dialog-page',
+ 'file' => 'includes/media.theme.inc',
+ ),
+ );
+}
+
+/**
+ * Menu callback; Shared Ajax callback for media attachment and deletions.
+ *
+ * This rebuilds the form element for a particular field item. As long as the
+ * form processing is properly encapsulated in the widget element the form
+ * should rebuild correctly using FAPI without the need for additional callbacks
+ * or processing.
+ */
+function media_ajax_upload() {
+ $form_parents = func_get_args();
+ $form_build_id = (string) array_pop($form_parents);
+
+ if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) {
+ // Invalid request.
+ drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
+ $commands = array();
+ $commands[] = ajax_command_replace(NULL, theme('status_messages'));
+ return array('#type' => 'ajax', '#commands' => $commands);
+ }
+
+ list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
+
+ if (!$form) {
+ // Invalid form_build_id.
+ drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
+ $commands = array();
+ $commands[] = ajax_command_replace(NULL, theme('status_messages'));
+ return array('#type' => 'ajax', '#commands' => $commands);
+ }
+
+ // Get the current element and count the number of files.
+ $current_element = $form;
+ foreach ($form_parents as $parent) {
+ $current_element = $current_element[$parent];
+ }
+ $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0;
+
+ // Process user input. $form and $form_state are modified in the process.
+ drupal_process_form($form['#form_id'], $form, $form_state);
+
+ // Retrieve the element to be rendered.
+ foreach ($form_parents as $parent) {
+ $form = $form[$parent];
+ }
+
+ // Add the special Ajax class if a new file was added.
+ if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
+ $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
+ }
+ // Otherwise just add the new content class on a placeholder.
+ else {
+ $form['#suffix'] .= '<span class="ajax-new-content"></span>';
+ }
+
+ $output = theme('status_messages') . drupal_render($form);
+ $js = drupal_add_js();
+ $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']);
+
+ $commands[] = ajax_command_replace(NULL, $output, $settings);
+ return array('#type' => 'ajax', '#commands' => $commands);
+}
+
+/**
+ * Implements hook_image_default_styles().
+ */
+function media_image_default_styles() {
+ $styles = array();
+ $styles['media_thumbnail'] = array(
+ 'label' => 'Media thumbnail (100x100)',
+ 'effects' => array(
+ array(
+ 'name' => 'image_scale_and_crop',
+ 'data' => array('width' => 100, 'height' => 100),
+ 'weight' => 0,
+ ),
+ ),
+ );
+ return $styles;
+}
+
+/**
+ * Implements hook_page_alter().
+ *
+ * This is used to use our alternate template when ?render=media-popup is passed
+ * in the URL.
+ */
+function media_page_alter(&$page) {
+ if (isset($_GET['render']) && $_GET['render'] == 'media-popup') {
+ $page['#theme'] = 'media_dialog_page';
+
+ // Disable administration modules from adding output to the popup.
+ // @see http://drupal.org/node/914786
+ module_invoke_all('suppress', TRUE);
+
+ foreach (element_children($page) as $key) {
+ if ($key != 'content') {
+ unset($page[$key]);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_form_FIELD_UI_FIELD_EDIT_FORM_alter().
+ *
+ * @todo: Respect field settings in 7.x-2.x and handle them in the media widget
+ * UI.
+ */
+function media_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
+ // On file fields that use the media widget we need remove specific fields.
+ if ($form['#field']['type'] == 'file' && $form['instance']['widget']['type']['#value'] == 'media_generic') {
+ $form['instance']['settings']['file_extensions']['#title'] = t('Allowed file extensions for uploaded files');
+ $form['instance']['settings']['file_extensions']['#maxlength'] = 255;
+ }
+
+ // On image fields using the media widget we remove the alt/title fields.
+ if ($form['#field']['type'] == 'image' && $form['instance']['widget']['type']['#value'] == 'media_generic') {
+ $form['instance']['settings']['alt_field']['#access'] = FALSE;
+ $form['instance']['settings']['title_field']['#access'] = FALSE;
+ $form['instance']['settings']['file_extensions']['#title'] = t('Allowed file extensions for uploaded files');
+ // Do not increase maxlength of file extensions for image fields, since
+ // presumably they will not need a long list of extensions.
+ }
+
+ // Add a validation function to any field instance which uses the media widget
+ // to ensure that the upload destination scheme is one of the allowed schemes
+ // if any defined by settings.
+ if ($form['instance']['widget']['type']['#value'] == 'media_generic' && isset($form['#field']['settings']['uri_scheme'])) {
+ $form['#validate'][] = 'media_field_instance_validate';
+ }
+
+ if ($form['#instance']['entity_type'] == 'file') {
+ $form['instance']['settings']['wysiwyg_override'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Override in WYSIWYG'),
+ '#description' => t('If checked, then this field may be overridden in the WYSIWYG editor.'),
+ '#default_value' => isset($form['#instance']['settings']['wysiwyg_override']) ? $form['#instance']['settings']['wysiwyg_override'] : TRUE,
+ );
+ }
+}
+
+/**
+ * Validation handler; ensure that the upload destination scheme is one of the
+ * allowed schemes.
+ */
+function media_field_instance_validate($form, &$form_state) {
+ $allowed_schemes = array_filter($form_state['values']['instance']['widget']['settings']['allowed_schemes']);
+ $upload_destination = $form_state['values']['field']['settings']['uri_scheme'];
+
+ if (!empty($allowed_schemes) && !in_array($upload_destination, $allowed_schemes)) {
+ form_set_error('allowed_schemes', t('The upload destination must be one of the allowed schemes.'));
+ }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function media_form_alter(&$form, &$form_state, $form_id) {
+ // If we're in the media browser, set the #media_browser key to true
+ // so that if an ajax request gets sent to a different path, the form
+ // still uses the media_browser_form_submit callback.
+ if (current_path() == 'media/browser') {
+ if ($form_id == 'views_exposed_form') {
+ $form['render'] = array('#type' => 'hidden', '#value' => 'media-popup');
+ $form['#action'] = '/media/browser';
+ } else {
+ $form_state['#media_browser'] = TRUE;
+ }
+ }
+
+ // If the #media_browser key isset and is true we are using the browser
+ // popup, so add the media_browser submit handler.
+ if (!empty($form_state['#media_browser'])) {
+ $form['#submit'][] = 'media_browser_form_submit';
+ }
+}
+
+/**
+ * Submit handler; direction form submissions in the media browser.
+ */
+function media_browser_form_submit($form, &$form_state) {
+ $url = NULL;
+ $parameters = array();
+
+ // Single upload.
+ if (!empty($form_state['file'])) {
+ $file = $form_state['file'];
+ $url = 'media/browser';
+ $parameters = array('query' => array('render' => 'media-popup', 'fid' => $file->fid));
+ }
+
+ // If $url is set, we had some sort of upload, so redirect the form.
+ if (!empty($url)) {
+ $form_state['redirect'] = array($url, $parameters);
+ }
+}
+
+/**
+ * Implements hook_library().
+ */
+function media_library() {
+ $path = drupal_get_path('module', 'media');
+ $info = system_get_info('module', 'media');
+
+ $common = array(
+ 'website' => 'http://drupal.org/project/media',
+ 'version' => !empty($info['version']) ? $info['version'] : '7.x-2.x',
+ );
+
+ // Contains libraries common to other media modules.
+ $libraries['media_base'] = array(
+ 'title' => 'Media base',
+ 'js' => array(
+ $path . '/js/media.core.js' => array('group' => JS_LIBRARY, 'weight' => -5),
+ $path . '/js/util/json2.js' => array('group' => JS_LIBRARY),
+ $path . '/js/util/ba-debug.min.js' => array('group' => JS_LIBRARY),
+ ),
+ 'css' => array(
+ $path . '/css/media.css',
+ ),
+ );
+
+ // Includes resources needed to launch the media browser. Should be included
+ // on pages where the media browser needs to be launched from.
+ $libraries['media_browser'] = array(
+ 'title' => 'Media Browser popup libraries',
+ 'js' => array(
+ $path . '/js/media.popups.js' => array('group' => JS_DEFAULT),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.resizable'),
+ array('system', 'ui.draggable'),
+ array('system', 'ui.dialog'),
+ array('media', 'media_base'),
+ ),
+ );
+
+ // Resources needed in the media browser itself.
+ $libraries['media_browser_page'] = array(
+ 'title' => 'Media browser',
+ 'js' => array(
+ $path . '/js/media.browser.js' => array('group' => JS_DEFAULT),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.tabs'),
+ array('system', 'ui.draggable'),
+ array('system', 'ui.dialog'),
+ array('media', 'media_base'),
+ ),
+ );
+
+ // Settings for the dialog etc.
+ $settings = array(
+ 'browserUrl' => url('media/browser', array(
+ 'query' => array(
+ 'render' => 'media-popup'
+ ))
+ ),
+ 'styleSelectorUrl' => url('media/-media_id-/format-form', array(
+ 'query' => array(
+ 'render' => 'media-popup'
+ ))
+ ),
+ 'dialogOptions' => array(
+ 'dialogclass' => variable_get('media_dialogclass', 'media-wrapper'),
+ 'modal' => (boolean)variable_get('media_modal', TRUE),
+ 'draggable' => (boolean)variable_get('media_draggable', FALSE),
+ 'resizable' => (boolean)variable_get('media_resizable', FALSE),
+ 'minwidth' => (int)variable_get('media_minwidth', 500),
+ 'width' => (int)variable_get('media_width', 670),
+ 'height' => (int)variable_get('media_height', 280),
+ 'position' => variable_get('media_position', 'center'),
+ 'overlay' => array(
+ 'backgroundcolor' => variable_get('media_backgroundcolor', '#000000'),
+ 'opacity' => (float)variable_get('media_opacity', 0.4),
+ ),
+ 'zindex' => (int)variable_get('media_zindex', 10000),
+ ),
+ );
+
+ $libraries['media_browser_settings'] = array(
+ 'title' => 'Media browser settings',
+ 'js' => array(
+ 0 => array(
+ 'data' => array(
+ 'media' => $settings,
+ ),
+ 'type' => 'setting',
+ ),
+ ),
+ );
+
+ foreach ($libraries as &$library) {
+ $library += $common;
+ }
+ return $libraries;
+}
+
+/**
+ * Theme callback used to identify when we are in a popup dialog.
+ *
+ * Generally the default theme will look terrible in the media browser. This
+ * will default to the administration theme, unless set otherwise.
+ */
+function media_dialog_get_theme_name() {
+ return variable_get('media_dialog_theme', variable_get('admin_theme'));
+}
+
+/**
+ * This will parse a url or embedded code into a unique URI.
+ *
+ * The function will call all modules implementing hook_media_parse($url),
+ * which should return either a string containing a parsed URI or NULL.
+ *
+ * @NOTE The implementing modules may throw an error, which will not be caught
+ * here; it's up to the calling function to catch any thrown errors.
+ *
+ * @NOTE In emfield, we originally also accepted an array of regex patterns
+ * to match against. However, that module used a registration for providers,
+ * and simply stored the match in the database keyed to the provider object.
+ * However, other than the stream wrappers, there is currently no formal
+ * registration for media handling. Additionally, few, if any, stream wrappers
+ * will choose to store a straight match from the parsed URL directly into
+ * the URI. Thus, we leave both the matching and the final URI result to the
+ * implementing module in this implementation.
+ *
+ * An alternative might be to do the regex pattern matching here, and pass a
+ * successful match back to the implementing module. However, that would
+ * require either an overloaded function or a new hook, which seems like more
+ * overhead than it's worth at this point.
+ *
+ * @TODO Once hook_module_implements_alter() is in core (see the issue at
+ * http://drupal.org/node/692950) we may want to implement media_media_parse()
+ * to ensure we were passed a valid URL, rather than an unsupported or
+ * malformed embed code that wasn't caught earlier. It will needed to be
+ * weighted so it's called after all other streams have a go, as the fallback,
+ * and will need to throw an error.
+ *
+ * @param string $url
+ * The original URL or embed code to parse.
+ *
+ * @return string
+ * The unique URI for the file, based on its stream wrapper, or NULL.
+ *
+ * @see media_parse_to_file()
+ * @see media_add_from_url_validate()
+ */
+function media_parse_to_uri($url) {
+ // Trim any whitespace before parsing.
+ $url = trim($url);
+ foreach (module_implements('media_parse') as $module) {
+ $success = module_invoke($module, 'media_parse', $url);
+ $context = array(
+ 'url' => $url,
+ 'module' => $module,
+ );
+ drupal_alter('media_parse', $success, $context);
+ if (isset($success)) {
+ return $success;
+ }
+ }
+}
+
+/**
+ * Parse a URL or embed code and return a file object.
+ *
+ * If a remote stream doesn't claim the parsed URL in media_parse_to_uri(),
+ * then we'll copy the file locally.
+ *
+ * @NOTE The implementing modules may throw an error, which will not be caught
+ * here; it's up to the calling function to catch any thrown errors.
+ *
+ * @see media_parse_to_uri()
+ * @see media_add_from_url_submit()
+ */
+function media_parse_to_file($url) {
+ try {
+ $uri = media_parse_to_uri($url);
+ }
+ catch (Exception $e) {
+ // Pass the error along.
+ throw $e;
+ return;
+ }
+
+ if (isset($uri)) {
+ // Attempt to load an existing file from the unique URI.
+ $select = db_select('file_managed', 'f')
+ ->extend('PagerDefault')
+ ->fields('f', array('fid'))
+ ->condition('uri', $uri);
+
+ $fid = $select->execute()->fetchCol();
+ if (!empty($fid)) {
+ $file = file_load(array_pop($fid));
+ return $file;
+ }
+ }
+
+ if (isset($uri)) {
+ // The URL was successfully parsed to a URI, but does not yet have an
+ // associated file: save it!
+ $file = file_uri_to_object($uri);
+ file_save($file);
+ }
+ else {
+ // The URL wasn't parsed. We'll try to save a remote file.
+ // Copy to temporary first.
+ $source_uri = file_stream_wrapper_uri_normalize('temporary://' . basename($url));
+ if (!@copy(@$url, $source_uri)) {
+ throw new Exception('Unable to add file ' . $url);
+ return;
+ }
+ $source_file = file_uri_to_object($source_uri);
+ $scheme = variable_get('file_default_scheme', 'public') . '://';
+ $uri = file_stream_wrapper_uri_normalize($scheme . $source_file->filename);
+ // Now to its new home.
+ $file = file_move($source_file, $uri, FILE_EXISTS_RENAME);
+ }
+
+ return $file;
+}
+
+/**
+ * Utility function to recursively run check_plain on an array.
+ *
+ * @todo There is probably something in core I am not aware of that does this.
+ */
+function media_recursive_check_plain(&$value, $key) {
+ $value = check_plain($value);
+}
+
+/**
+ * Implements hook_element_info().
+ */
+function media_element_info() {
+ $types['media'] = array(
+ '#input' => TRUE,
+ '#process' => array('media_element_process'),
+ '#value_callback' => 'media_file_value',
+ '#element_validate' => array('media_element_validate'),
+ '#pre_render' => array('media_element_pre_render'),
+ '#theme' => 'media_element',
+ '#theme_wrappers' => array('form_element'),
+ '#size' => 22,
+ '#extended' => FALSE,
+ '#media_options' => array(
+ 'global' => array(
+ // Example: array('image', 'audio');
+ 'types' => array(),
+ // Example: array('http', 'ftp', 'flickr');
+ 'schemes' => array(),
+ ),
+ ),
+ '#attached' => array(
+ 'library' => array(
+ array('media', 'media_browser'),
+ ),
+ ),
+ );
+
+ $setting = array();
+ $setting['media']['global'] = $types['media']['#media_options'];
+
+ $types['media']['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => $setting,
+ );
+
+ return $types;
+}
+
+/**
+ * Process callback for the media form element.
+ */
+function media_element_process($element, &$form_state, $form) {
+ ctools_include('modal');
+ ctools_include('ajax');
+ ctools_modal_add_js();
+
+ // Append the '-upload' to the #id so the field label's 'for' attribute
+ // corresponds with the textfield element.
+ $original_id = $element['#id'];
+ $element['#id'] .= '-upload';
+ $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0;
+
+ // Set some default element properties.
+ $element['#file'] = $fid ? file_load($fid) : FALSE;
+ $element['#tree'] = TRUE;
+
+ $ajax_settings = array(
+ 'path' => 'media/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
+ 'wrapper' => $original_id . '-ajax-wrapper',
+ 'effect' => 'fade',
+ );
+
+ // Set up the buttons first since we need to check if they were clicked.
+ $element['attach_button'] = array(
+ '#name' => implode('_', $element['#parents']) . '_attach_button',
+ '#type' => 'submit',
+ '#value' => t('Attach'),
+ '#validate' => array(),
+ '#submit' => array('media_file_submit'),
+ '#limit_validation_errors' => array($element['#parents']),
+ '#ajax' => $ajax_settings,
+ '#attributes' => array('class' => array('attach')),
+ '#weight' => -1,
+ );
+
+ $element['preview'] = array(
+ 'content' => array(),
+ '#prefix' => '<div class="preview">',
+ '#suffix' => '</div>',
+ '#ajax' => $ajax_settings,
+ '#weight' => -10,
+ );
+
+ // Substitute the JS preview for a true file thumbnail once the file is
+ // attached.
+ if ($fid && $element['#file']) {
+ $element['preview']['content'] = media_get_thumbnail_preview($element['#file']);
+ }
+
+ // The file ID field itself.
+ $element['upload'] = array(
+ '#name' => 'media[' . implode('_', $element['#parents']) . ']',
+ '#type' => 'textfield',
+ '#title' => t('Enter the ID of an existing file'),
+ '#title_display' => 'invisible',
+ '#field_prefix' => t('File ID'),
+ '#size' => $element['#size'],
+ '#theme_wrappers' => array(),
+ '#attributes' => array('class' => array('upload')),
+ '#weight' => -9,
+ );
+
+ $element['browse_button'] = array(
+ '#type' => 'link',
+ '#href' => '',
+ '#title' => t('Browse'),
+ '#attributes' => array('class' => array('button', 'browse', 'element-hidden')),
+ '#options' => array('fragment' => FALSE, 'external' => TRUE),
+ '#weight' => -8,
+ );
+
+ // Force the progress indicator for the remove button to be either 'none' or
+ // 'throbber', even if the upload button is using something else.
+ $ajax_settings['progress']['type'] = 'throbber';
+ $ajax_settings['progress']['message'] = NULL;
+ $ajax_settings['effect'] = 'none';
+ $element['edit'] = array(
+ '#type' => 'link',
+ '#href' => 'media/' . $fid . '/edit/nojs',
+ '#title' => t('Edit'),
+ '#attributes' => array(
+ 'class' => array(
+ // Required for CTools modal to work.
+ 'ctools-use-modal', 'use-ajax',
+ 'ctools-modal-media-file-edit', 'button', 'edit',
+ ),
+ ),
+ '#weight' => 20,
+ '#access' => $element['#file'] ? file_entity_access('update', $element['#file']) : FALSE,
+ );
+ $element['remove_button'] = array(
+ '#name' => implode('_', $element['#parents']) . '_remove_button',
+ '#type' => 'submit',
+ '#value' => t('Remove'),
+ '#validate' => array(),
+ '#submit' => array('media_file_submit'),
+ '#limit_validation_errors' => array($element['#parents']),
+ '#ajax' => $ajax_settings,
+ '#attributes' => array('class' => array('remove')),
+ '#weight' => 0,
+ );
+
+ $element['fid'] = array(
+ '#type' => 'hidden',
+ '#value' => $fid,
+ '#attributes' => array('class' => array('fid')),
+ );
+
+ // Media browser attach code.
+ $element['#attached']['js'][] = drupal_get_path('module', 'media') . '/js/media.js';
+
+ // Add the media options to the page as JavaScript settings.
+ $element['browse_button']['#attached']['js'] = array(
+ array(
+ 'type' => 'setting',
+ 'data' => array('media' => array('elements' => array('#' . $element['#id'] => $element['#media_options'])))
+ )
+ );
+
+ $element['#attached']['library'][] = array('media', 'media_browser');
+ $element['#attached']['library'][] = array('media', 'media_browser_settings');
+
+ // Prefix and suffix used for Ajax replacement.
+ $element['#prefix'] = '<div id="' . $original_id . '-ajax-wrapper">';
+ $element['#suffix'] = '</div>';
+
+ return $element;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_form_file_entity_edit_alter(&$form, &$form_state) {
+ // Make adjustments to the file edit form when used in a CTools modal.
+ if (!empty($form_state['ajax'])) {
+ // Remove the preview and the delete button.
+ $form['preview']['#access'] = FALSE;
+ $form['actions']['delete']['#access'] = FALSE;
+
+ // Convert the cancel link to a button which triggers a modal close.
+ $form['actions']['cancel']['#attributes']['class'][] = 'button';
+ $form['actions']['cancel']['#attributes']['class'][] = 'button-no';
+ $form['actions']['cancel']['#attributes']['class'][] = 'ctools-close-modal';
+ }
+}
+
+/**
+ * The #value_callback for a media type element.
+ */
+function media_file_value(&$element, $input = FALSE, $form_state = NULL) {
+ $fid = 0;
+
+ // Find the current value of this field from the form state.
+ $form_state_fid = $form_state['values'];
+ foreach ($element['#parents'] as $parent) {
+ $form_state_fid = isset($form_state_fid[$parent]) ? $form_state_fid[$parent] : 0;
+ }
+
+ if ($element['#extended'] && isset($form_state_fid['fid'])) {
+ $fid = $form_state_fid['fid'];
+ }
+ elseif (is_numeric($form_state_fid)) {
+ $fid = $form_state_fid;
+ }
+
+ // Process any input and attach files.
+ if ($input !== FALSE) {
+ $return = $input;
+
+ // Attachments take priority over all other values.
+ if ($file = media_attach_file($element)) {
+ $fid = $file->fid;
+ }
+ else {
+ // Check for #filefield_value_callback values.
+ // Because FAPI does not allow multiple #value_callback values like it
+ // does for #element_validate and #process, this fills the missing
+ // functionality to allow File fields to be extended through FAPI.
+ if (isset($element['#file_value_callbacks'])) {
+ foreach ($element['#file_value_callbacks'] as $callback) {
+ $callback($element, $input, $form_state);
+ }
+ }
+ // Load file if the FID has changed to confirm it exists.
+ if (isset($input['fid']) && $file = file_load($input['fid'])) {
+ $fid = $file->fid;
+ }
+ }
+ }
+
+ // If there is no input, set the default value.
+ else {
+ if ($element['#extended']) {
+ $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0;
+ $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0);
+ }
+ else {
+ $default_fid = isset($element['#default_value']) ? $element['#default_value'] : 0;
+ $return = array('fid' => 0);
+ }
+
+ // Confirm that the file exists when used as a default value.
+ if ($default_fid && $file = file_load($default_fid)) {
+ $fid = $file->fid;
+ }
+ }
+
+ $return['fid'] = $fid;
+
+ return $return;
+}
+
+/**
+ * Validate media form elements.
+ *
+ * The file type is validated during the upload process, but this is necessary
+ * necessary in order to respect the #required property.
+ */
+function media_element_validate(&$element, &$form_state) {
+ $clicked_button = end($form_state['triggering_element']['#parents']);
+
+ // Check required property based on the FID.
+ if ($element['#required'] && empty($element['fid']['#value']) && !in_array($clicked_button, array('attach_button', 'remove_button'))) {
+ form_error($element['browse_button'], t('!name field is required.', array('!name' => $element['#title'])));
+ }
+
+ // Consolidate the array value of this field to a single FID.
+ if (!$element['#extended']) {
+ form_set_value($element, $element['fid']['#value'], $form_state);
+ }
+}
+
+/**
+ * Form submission handler for attach / remove buttons of media elements.
+ *
+ * @see media_element_process()
+ */
+function media_file_submit($form, &$form_state) {
+ // Determine whether it was the attach or remove button that was clicked, and
+ // set $element to the managed_file element that contains that button.
+ $parents = $form_state['triggering_element']['#array_parents'];
+ $button_key = array_pop($parents);
+ $element = drupal_array_get_nested_value($form, $parents);
+
+ // No action is needed here for the attach button, because all media
+ // attachments on the form are processed by media_file_value() regardless of
+ // which button was clicked. Action is needed here for the remove button,
+ // because we only remove a file in response to its remove button being
+ // clicked.
+ if ($button_key == 'remove_button') {
+ // If it's a temporary file we can safely remove it immediately, otherwise
+ // it's up to the implementing module to clean up files that are in use.
+ if ($element['#file'] && $element['#file']->status == 0) {
+ file_delete($element['#file']);
+ }
+ // Update both $form_state['values'] and $form_state['input'] to reflect
+ // that the file has been removed, so that the form is rebuilt correctly.
+ // $form_state['values'] must be updated in case additional submit handlers
+ // run, and for form building functions that run during the rebuild, such as
+ // when the media element is part of a field widget.
+ // $form_state['input'] must be updated so that media_file_value() has
+ // correct information during the rebuild.
+ $values_element = $element['#extended'] ? $element['fid'] : $element;
+ form_set_value($values_element, NULL, $form_state);
+ drupal_array_set_nested_value($form_state['input'], $values_element['#parents'], NULL);
+ }
+
+ // Set the form to rebuild so that $form is correctly updated in response to
+ // processing the file removal. Since this function did not change $form_state
+ // if the upload button was clicked, a rebuild isn't necessary in that
+ // situation and setting $form_state['redirect'] to FALSE would suffice.
+ // However, we choose to always rebuild, to keep the form processing workflow
+ // consistent between the two buttons.
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Attaches any files that have been referenced by a media element.
+ *
+ * @param $element
+ * The FAPI element whose files are being attached.
+ *
+ * @return
+ * The file object representing the file that was attached, or FALSE if no
+ * file was attached.
+ */
+function media_attach_file($element) {
+ $upload_name = implode('_', $element['#parents']);
+ if (empty($_POST['media'][$upload_name])) {
+ return FALSE;
+ }
+
+ if (!$file = file_load($_POST['media'][$upload_name])) {
+ watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name));
+ form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title'])));
+ return FALSE;
+ }
+
+ return $file;
+}
+
+/**
+ * Returns HTML for a managed file element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: A render element representing the file.
+ *
+ * @ingroup themeable
+ */
+function theme_media_element($variables) {
+ $element = $variables['element'];
+
+ $attributes = array();
+ if (isset($element['#id'])) {
+ $attributes['id'] = $element['#id'];
+ }
+ if (!empty($element['#attributes']['class'])) {
+ $attributes['class'] = (array) $element['#attributes']['class'];
+ }
+ $attributes['class'][] = 'form-media';
+
+ // This wrapper is required to apply JS behaviors and CSS styling.
+ $output = '';
+ $output .= '<div' . drupal_attributes($attributes) . '>';
+ $output .= drupal_render_children($element);
+ $output .= '</div>';
+ return $output;
+}
+
+/**
+ * #pre_render callback to hide display of the browse/attach or remove controls.
+ *
+ * Browse/attach controls are hidden when a file is already attached.
+ * Remove controls are hidden when there is no file attached. Controls are
+ * hidden here instead of in media_element_process(), because #access for these
+ * buttons depends on the media element's #value. See the documentation of
+ * form_builder() for more detailed information about the relationship between
+ * #process, #value, and #access.
+ *
+ * Because #access is set here, it affects display only and does not prevent
+ * JavaScript or other untrusted code from submitting the form as though access
+ * were enabled. The form processing functions for these elements should not
+ * assume that the buttons can't be "clicked" just because they are not
+ * displayed.
+ *
+ * @see media_element_process()
+ * @see form_builder()
+ */
+function media_element_pre_render($element) {
+ // If we already have a file, we don't want to show the browse and attach
+ // controls.
+ if (!empty($element['#value']['fid'])) {
+ $element['upload']['#access'] = FALSE;
+ $element['browse_button']['#access'] = FALSE;
+ $element['attach_button']['#access'] = FALSE;
+ }
+ // If we don't already have a file, there is nothing to remove.
+ else {
+ $element['remove_button']['#access'] = FALSE;
+ }
+ return $element;
+}
+
+/**
+ * Generates a thumbnail preview of a file.
+ *
+ * Provides default fallback images if an image of the file cannot be generated.
+ *
+ * @param object $file
+ * A Drupal file object.
+ * @param boolean $link
+ * (optional) Boolean indicating whether the thumbnail should be linked to the
+ * file. Defaults to FALSE.
+ * @param string $view_mode
+ * (optional) The view mode to use when rendering the thumbnail. Defaults to
+ * 'preview'.
+ *
+ * @return array
+ * Renderable array suitable for drupal_render() with the necessary classes
+ * and CSS to support a media thumbnail.
+ */
+function media_get_thumbnail_preview($file, $link = FALSE, $view_mode = 'preview') {
+ // If a file has an invalid type, allow file_view_file() to work.
+ if (!file_type_is_enabled($file->type)) {
+ $file->type = file_get_type($file);
+ }
+
+ $preview = file_view_file($file, $view_mode);
+ $preview['#show_names'] = TRUE;
+ $preview['#add_link'] = $link;
+ $preview['#theme_wrappers'][] = 'media_thumbnail';
+ $preview['#attached']['css'][] = drupal_get_path('module', 'media') . '/css/media.css';
+
+ return $preview;
+}
+
+/**
+ * Check that the media is one of the selected types.
+ *
+ * @param object $file
+ * A Drupal file object.
+ * @param array $types
+ * An array of media type names
+ *
+ * @return array
+ * If the file type is not allowed, it will contain an error message.
+ *
+ * @see hook_file_validate()
+ */
+function media_file_validate_types($file, $types) {
+ $errors = array();
+ if (!in_array(file_get_type($file), $types)) {
+ $errors[] = t('Only the following types of files are allowed to be uploaded: %types-allowed', array('%types-allowed' => implode(', ', $types)));
+ }
+
+ return $errors;
+}
+
+/**
+ * Implements hook_file_displays_alter().
+ */
+function media_file_displays_alter(&$displays, $file, $view_mode) {
+ if ($view_mode == 'preview' && empty($displays)) {
+ // We re in the media browser and this file has no formatters enabled.
+ // Instead of letting it go through theme_file_link(), pass it through
+ // theme_media_formatter_large_icon() to get our cool file icon instead.
+ $displays['file_field_media_large_icon'] = array(
+ 'weight' => 0,
+ 'status' => 1,
+ 'settings' => NULL,
+ );
+ }
+
+ // Override the fields of the file when requested by the WYSIWYG.
+ if (isset($file->override) && isset($file->override['fields'])) {
+ $instance = field_info_instances('file', $file->type);
+ foreach ($file->override['fields'] as $field_name => $value) {
+ if (!isset($instance[$field_name]['settings']) || !isset($instance[$field_name]['settings']['wysiwyg_override']) || $instance[$field_name]['settings']['wysiwyg_override']) {
+ $file->{$field_name} = $value;}
+ }
+ }
+
+ // Alt and title are special.
+ // @see file_entity_file_load
+ $alt = variable_get('file_entity_alt', '[file:field_file_image_alt_text]');
+ $title = variable_get('file_entity_title', '[file:field_file_image_title_text]');
+
+ $replace_options = array(
+ 'clear' => TRUE,
+ 'sanitize' => FALSE,
+ );
+
+ // Load alt and title text from fields.
+ if (!empty($alt)) {
+ $file->alt = token_replace($alt, array('file' => $file), $replace_options);
+ }
+ if (!empty($title)) {
+ $file->title = token_replace($title, array('file' => $file), $replace_options);
+ }
+}
+
+/**
+ * For sanity in grammar.
+ *
+ * @see media_set_browser_params()
+ */
+function media_get_browser_params() {
+ return media_set_browser_params();
+}
+
+/**
+ * Provides a singleton of the params passed to the media browser.
+ *
+ * This is useful in situations like form alters because callers can pass
+ * id="wysiywg_form" or whatever they want, and a form alter could pick this up.
+ * We may want to change the hook_media_browser_plugin_view() implementations to
+ * use this function instead of being passed params for consistency.
+ *
+ * It also offers a chance for some meddler to meddle with them.
+ *
+ * @see media_browser()
+ */
+function media_set_browser_params() {
+ $params = &drupal_static(__FUNCTION__, array());
+
+ if (empty($params)) {
+ // Build out browser settings. Permissions- and security-related behaviors
+ // should not rely on these parameters, since they come from the HTTP query.
+ // @TODO make sure we treat parameters as user input.
+ $params = drupal_get_query_parameters() + array(
+ 'types' => array(),
+ 'multiselect' => FALSE,
+ );
+
+ // Transform text 'true' and 'false' to actual booleans.
+ foreach ($params as $k => $v) {
+ if ($v === 'true') {
+ $params[$k] = TRUE;
+ }
+ elseif ($v === 'false') {
+ $params[$k] = FALSE;
+ }
+ }
+
+ array_walk_recursive($params, 'media_recursive_check_plain');
+
+ // Allow modules to alter the parameters.
+ drupal_alter('media_browser_params', $params);
+ }
+
+ return $params;
+}
+
+/**
+ * Implements hook_ctools_plugin_api().
+ *
+ * Lets CTools know which plugin APIs are implemented by Media module.
+ */
+function media_ctools_plugin_api($module, $api) {
+ if ($module == 'file_entity' && $api == 'file_default_displays') {
+ return array(
+ 'version' => 1,
+ );
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * This alter enhances the default admin/content/file page, addding JS and CSS.
+ * It also makes modifications to the thumbnail view by replacing the existing
+ * checkboxes and table with thumbnails.
+ */
+function media_form_file_entity_admin_file_alter(&$form, $form_state) {
+ if (!empty($form_state['values']['operation'])) {
+ // The form is being rebuilt because an operation requiring confirmation
+ // We don't want to be messing with it in this case.
+ return;
+ }
+
+ // Add the "Add file" local action, and notify users if they have files
+ // selected and they try to switch between the "Thumbnail" and "List" local
+ // tasks.
+ $path = drupal_get_path('module', 'media');
+ $form['#attributes']['class'][] = 'file-entity-admin-file-form';
+ $form['#attached']['js'][] = $path . '/js/media.admin.js';
+ $form['#attached']['css'][] = $path . '/css/media.css';
+
+ // By default, this form contains a table select element called "files". For
+ // the 'thumbnails' tab, Media generates a thumbnail for each file and
+ // replaces the tableselect with a grid of thumbnails.
+ if (arg(3) == 'thumbnails') {
+ if (empty($form['admin']['files']['#options'])) {
+ // Display empty text if there are no files.
+ $form['admin']['files'] = array(
+ '#markup' => '<p>' . $form['admin']['files']['#empty'] . '</p>',
+ );
+ }
+ else {
+ $files = file_load_multiple(array_keys($form['admin']['files']['#options']));
+
+ $form['admin']['files'] = array(
+ '#tree' => TRUE,
+ '#prefix' => '<div class="media-display-thumbnails media-clear clearfix"><ul id="media-browser-library-list" class="media-list-thumbnails">',
+ '#suffix' => '</ul></div>',
+ );
+
+ foreach ($files as $file) {
+ $preview = media_get_thumbnail_preview($file, TRUE);
+ $form['admin']['files'][$file->fid] = array(
+ '#type' => 'checkbox',
+ '#title' => '',
+ '#prefix' => '<li>' . drupal_render($preview),
+ '#suffix' => '</li>',
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function media_views_api() {
+ return array(
+ 'api' => 3,
+ 'path' => drupal_get_path('module', 'media'),
+ );
+}
+
+/**
+ * Implements hook_views_default_views().
+ */
+function media_views_default_views() {
+ return media_load_all_exports('media', 'views', 'view.inc', 'view');
+}
+
+/**
+ * Fetches an array of exportables from files.
+ *
+ * @param string $module
+ * The module invoking this request. (Can be called by other modules.)
+ * @param string $directory
+ * The subdirectory in the custom module.
+ * @param string $extension
+ * The file extension.
+ * @param string $name
+ * The name of the variable found in each file. Defaults to the same as
+ * $extension.
+ *
+ * @return array
+ * Array of $name objects.
+ */
+function media_load_all_exports($module, $directory, $extension, $name = NULL) {
+ if (!$name) {
+ $name = $extension;
+ }
+
+ $return = array();
+ // Find all the files in the directory with the correct extension.
+ $files = file_scan_directory(drupal_get_path('module', $module) . "/$directory", "/.$extension/");
+ foreach ($files as $path => $file) {
+ require $path;
+ if (isset($$name)) {
+ $return[$$name->name] = $$name;
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Returns metadata describing Media browser plugins.
+ *
+ * @return
+ * An associative array of plugin information, keyed by plugin.
+ *
+ * @see hook_media_browser_plugin_info()
+ * @see hook_media_browser_plugin_info_alter()
+ */
+function media_get_browser_plugin_info() {
+ $info = &drupal_static(__FUNCTION__);
+
+ if (!isset($info)) {
+ $info = module_invoke_all('media_browser_plugin_info');
+ drupal_alter('media_browser_plugin_info', $info);
+ }
+
+ return $info;
+}
+
+/**
+ * Gets the MIME type mapped to a given extension.
+ *
+ * @param string $extension
+ * A file extension.
+ *
+ * @return string
+ * The MIME type associated with the extension or FALSE if the extension does
+ * not have an associated MIME type.
+ *
+ * @see file_mimetype_mapping()
+ */
+function media_get_extension_mimetype($extension) {
+ include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
+ $mimetype_mappings = file_mimetype_mapping();
+
+ if (isset($mimetype_mappings['extensions'][$extension])) {
+ $id = $mimetype_mappings['extensions'][$extension];
+ return $mimetype_mappings['mimetypes'][$id];
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Helper function to get a list of local stream wrappers.
+ */
+function media_get_local_stream_wrappers() {
+ return file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL);
+}
+
+/**
+ * Helper function to get a list of remote stream wrappers.
+ */
+function media_get_remote_stream_wrappers() {
+ $wrappers = file_get_stream_wrappers();
+ $wrappers = array_diff_key($wrappers, file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL));
+ $wrappers = array_diff_key($wrappers, file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_HIDDEN));
+ return $wrappers;
+}
diff --git a/sites/all/modules/media/media.views.inc b/sites/all/modules/media/media.views.inc
new file mode 100644
index 000000000..aed55fa48
--- /dev/null
+++ b/sites/all/modules/media/media.views.inc
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Provide Views data and handlers for media.module
+ */
+
+/**
+ * Implements hook_views_plugins().
+ *
+ * Generate a list of which base-tables to enabled the plugins for.
+ */
+function media_views_plugins() {
+ $plugins = array();
+
+ // Always allow the actual file-table
+ $base = array('file_managed');
+
+ if (module_exists('search_api')) {
+ // If the Search API module exists, also allow indices of the file-entity
+ // that has the fid field indexed.
+ $indices = search_api_index_load_multiple(NULL);
+ foreach ($indices as $machine_name => $index) {
+ if ($index->item_type == 'file' && isset($index->options['fields']['fid'])) {
+ $base[] = 'search_api_index_' . $machine_name;
+ }
+ }
+ }
+
+ // Display plugin.
+ $plugins['display']['media_browser'] = array(
+ 'title' => t('Media browser tab'),
+ 'help' => t('Display as a tab in the media browser.'),
+ 'handler' => 'media_views_plugin_display_media_browser',
+ 'theme' => 'views_view',
+ 'base' => $base,
+ 'use ajax' => TRUE,
+ 'use pager' => TRUE,
+ 'accept attachments' => TRUE,
+ );
+
+ // Style plugin.
+ $plugins['style']['media_browser'] = array(
+ 'title' => t('Media browser'),
+ 'help' => t('Displays rows as an HTML list.'),
+ 'handler' => 'media_views_plugin_style_media_browser',
+ 'theme' => 'media_views_view_media_browser',
+ 'base' => $base,
+ 'uses row plugin' => FALSE,
+ 'uses row class' => FALSE,
+ 'uses options' => FALSE,
+ 'uses fields' => FALSE,
+ 'type' => 'normal',
+ 'help topic' => 'style-media-browser',
+ );
+ return $plugins;
+}
+
+/**
+ * Display the view as a media browser.
+ */
+function template_preprocess_media_views_view_media_browser(&$vars) {
+ module_load_include('inc', 'media', 'includes/media.browser');
+ // Load file objects for each View result.
+ $fids = array();
+ foreach ($vars['rows'] as $index => $row) {
+ // The Search API module returns the row in a slightly different format,
+ // so convert it to the format that the normal file_managed table returns.
+ if (!empty($row->entity->fid)) {
+ $vars['rows'][$index]->fid = $row->entity->fid;
+ }
+ $fids[$index] = $row->fid;
+ }
+ $files = file_load_multiple($fids);
+
+ // Render the preview for each file.
+ $params = media_get_browser_params();
+ $view_mode = isset($params['view_mode']) ? $params['view_mode'] : 'preview';
+
+ foreach ($vars['rows'] as $index => $row) {
+ $file = $files[$row->fid];
+ // Add url/preview to the file object.
+ media_browser_build_media_item($file, $view_mode);
+ $vars['rows'][$index] = $file;
+ $vars['rows'][$index]->preview = $file->preview;
+ }
+
+ // Add the files to JS so that they are accessible inside the browser.
+ drupal_add_js(array('media' => array('files' => array_values($files))), 'setting');
+
+ // Add the browser parameters to the settings and that this display exists.
+ drupal_add_js(array(
+ 'media' => array(
+ 'browser' => array(
+ 'params' => media_get_browser_params(),
+ 'views' => array(
+ $vars['view']->name => array(
+ $vars['view']->current_display,
+ ),
+ ),
+ ),
+ ),
+ ), 'setting');
+
+ // Add classes and wrappers from the style plugin.
+ $handler = $vars['view']->style_plugin;
+
+ $class = explode(' ', $handler->options['class']);
+ $class = array_map('drupal_clean_css_identifier', $class);
+
+ $wrapper_class = explode(' ', $handler->options['wrapper_class']);
+ $wrapper_class = array_map('drupal_clean_css_identifier', $wrapper_class);
+
+ $vars['class'] = implode(' ', $class);
+ $vars['wrapper_class'] = implode(' ', $wrapper_class);
+ $vars['wrapper_prefix'] = '<div class="' . implode(' ', $wrapper_class) . '">';
+ $vars['wrapper_suffix'] = '</div>';
+ $vars['list_type_prefix'] = '<' . $handler->options['type'] . ' id="media-browser-library-list" class="' . implode(' ', $class) . '">';
+ $vars['list_type_suffix'] = '</' . $handler->options['type'] . '>';
+
+ // Run theming variables through a standard Views preprocess function.
+ template_preprocess_views_view_unformatted($vars);
+
+ // Add media browser javascript and CSS.
+ drupal_add_js(drupal_get_path('module', 'media') . '/js/plugins/media.views.js');
+}
+
+/**
+ * Implements hook_views_invalidate_cache().
+ */
+function media_views_invalidate_cache() {
+ drupal_static_reset('media_get_browser_plugin_info');
+}
diff --git a/sites/all/modules/media/modules/media_bulk_upload/includes/MediaBrowserBulkUpload.inc b/sites/all/modules/media/modules/media_bulk_upload/includes/MediaBrowserBulkUpload.inc
new file mode 100644
index 000000000..9dbc98332
--- /dev/null
+++ b/sites/all/modules/media/modules/media_bulk_upload/includes/MediaBrowserBulkUpload.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaBrowserBulkUpload.
+ */
+
+/**
+ * Media browser plugin for showing the bulk upload form.
+ *
+ * @deprecated
+ */
+class MediaBrowserBulkUpload extends MediaBrowserUpload {
+ /**
+ * Overrides MediaBrowserPlugin::view().
+ */
+ public function view() {
+ module_load_include('inc', 'file_entity', 'file_entity.pages');
+
+ $build = array();
+ if ($this->params['multiselect']) {
+ $build['form'] = drupal_get_form('file_entity_add_upload_multiple', $this->params);
+ }
+ else {
+ $build['form'] = drupal_get_form('file_entity_add_upload', $this->params);
+ }
+
+ return $build;
+ }
+}
diff --git a/sites/all/modules/media/modules/media_bulk_upload/includes/media_bulk_upload.admin.inc b/sites/all/modules/media/modules/media_bulk_upload/includes/media_bulk_upload.admin.inc
new file mode 100644
index 000000000..bc0b77f6b
--- /dev/null
+++ b/sites/all/modules/media/modules/media_bulk_upload/includes/media_bulk_upload.admin.inc
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * @file
+ * This file contains the admin functions for the Media Bulk Upload module.
+ */
+
+/**
+ * Form callback for mass import.
+ */
+function media_bulk_upload_import($form, &$form_state) {
+ if (!isset($form_state['storage']['files'])) {
+ $form_state['storage']['step'] = 'choose';
+ $form_state['storage']['next_step'] = 'preview';
+ $form['directory'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Directory'),
+ '#description' => t('Enter the absolute directory on the web server to look for files. Subdirectories inside this directory will not be scanned.'),
+ '#required' => TRUE,
+ );
+
+ $form['pattern'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Pattern'),
+ '#description' => t("Only files matching these patterns will be imported. Enter one pattern per line. The '*' character is a wildcard. Example patterns are %png_example to import all PNG files.", array('%png_example' => '*.png')),
+ '#default_value' => '*',
+ '#required' => TRUE,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Preview'),
+ );
+ $form['actions']['cancel'] = array(
+ '#type' => 'link',
+ '#title' => t('Cancel'),
+ '#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/content/file',
+ );
+ }
+ else {
+ $form['preview'] = array(
+ '#markup' => theme('item_list', array('items' => $form_state['storage']['files'])),
+ );
+
+ $form = confirm_form($form, t('Import these files?'), 'admin/content/file/import');
+ }
+ return $form;
+
+}
+
+/**
+ * Validate handler for media_import().
+ */
+function media_bulk_upload_import_validate($form, &$form_state) {
+ if ($form_state['values']['op'] != t('Confirm')) {
+ $directory = $form_state['values']['directory'];
+ $pattern = $form_state['values']['pattern'];
+ if (!is_dir($directory)) {
+ form_set_error('directory', t('The provided directory does not exist.'));
+ }
+ if (!is_readable($directory)) {
+ form_set_error('directory', t('The provided directory is not readable.'));
+ }
+
+ $pattern_quoted = preg_quote($pattern, '/');
+ $pattern_quoted = preg_replace('/(\r\n?|\n)/', '|', $pattern_quoted);
+ $pattern_quoted = strtr($pattern_quoted, array(
+ '\\|' => '|',
+ '\\*' => '.*',
+ '\\?' => '.?',
+ ));
+ $files = file_scan_directory($directory, '/^(' . $pattern_quoted . ')$/', array('recurse' => FALSE));
+ $files = array_keys($files);
+ if (empty($files)) {
+ form_set_error('pattern', t('No files were found in %directory matching the regular expression %pattern', array('%directory' => $directory, '%pattern' => $pattern_quoted)));
+ }
+ $form_state['storage']['files'] = $files;
+ }
+}
+
+/**
+ * Submit handler for media_import().
+ */
+function media_bulk_upload_import_submit($form, &$form_state) {
+ if ($form_state['values']['op'] == t('Confirm')) {
+ $files = $form_state['storage']['files'];
+ $batch = array(
+ 'title' => t('Importing'),
+ 'operations' => array(
+ array('media_bulk_upload_import_batch_import_files', array($files)),
+ ),
+ 'finished' => 'media_bulk_upload_import_batch_import_complete',
+ 'file' => drupal_get_path('module', 'media_bulk_upload') . '/includes/media_bulk_upload.admin.inc',
+ );
+ batch_set($batch);
+ return;
+
+ }
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * BatchAPI callback op for media import.
+ */
+function media_bulk_upload_import_batch_import_files($files, &$context) {
+ if (!isset($context['sandbox']['files'])) {
+ // This runs the first time the batch runs.
+ // This is stupid, but otherwise, I don't think it will work...
+ $context['results'] = array('success' => array(), 'errors' => array());
+ $context['sandbox']['max'] = count($files);
+ $context['sandbox']['files'] = $files;
+ }
+ $files =& $context['sandbox']['files'];
+
+ // Take a cut of files. Let's do 10 at a time.
+ $import_batch_size = variable_get('media_bulk_upload_import_batch_size', 20);
+ $length = (count($files) > $import_batch_size) ? $import_batch_size : count($files);
+ $to_process = array_splice($files, 0, $length);
+ $image_in_message = '';
+
+ foreach ($to_process as $file) {
+ try {
+ $file_obj = media_parse_to_file($file);
+ $context['results']['success'][] = $file;
+ if (!$image_in_message) {
+ // @todo Is this load step really necessary? When there's time, test
+ // this, and either remove it, or comment why it's needed.
+ $loaded_file = file_load($file_obj->fid);
+ $image_in_message = file_view_file($loaded_file, 'preview');
+ }
+ }
+ catch (Exception $e) {
+ $context['results']['errors'][] = $file . " Reason: " . $e->getMessage();
+ }
+ }
+
+ $context['message'] = "Importing " . theme('item_list', array('items' => $to_process));
+ // Show the image that is being imported.
+ $context['message'] .= drupal_render($image_in_message);
+
+ $context['finished'] = ($context['sandbox']['max'] - count($files)) / $context['sandbox']['max'];
+}
+
+/**
+ * BatchAPI complete callback for media import.
+ */
+function media_bulk_upload_import_batch_import_complete($success, $results, $operations) {
+ if ($results['errors']) {
+ drupal_set_message(theme('item_list', array('items' => $results['errors'])), 'error');
+ }
+ if ($results['success']) {
+ drupal_set_message(theme('item_list', array('items' => $results['success'])));
+ }
+}
diff --git a/sites/all/modules/media/modules/media_bulk_upload/includes/media_bulk_upload.pages.inc b/sites/all/modules/media/modules/media_bulk_upload/includes/media_bulk_upload.pages.inc
new file mode 100644
index 000000000..41287ebb0
--- /dev/null
+++ b/sites/all/modules/media/modules/media_bulk_upload/includes/media_bulk_upload.pages.inc
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Common pages for the Media Bulk Upload module.
+ */
+
+/**
+ * Menu callback; Edit multiple files on the same page using multiform module.
+ *
+ * @todo When http://drupal.org/node/1227706 is fixed, filter the $files
+ * array using file_access($file, 'edit').
+ *
+ * @see media_bulk_upload_file_operation_edit_multiple()
+ */
+function media_bulk_upload_file_page_edit_multiple($files) {
+ if (empty($files)) {
+ return MENU_ACCESS_DENIED;
+ }
+
+ $forms = array();
+ foreach ($files as $file) {
+ // To maintain unique form_ids, append the file id.
+ $forms[] = array('media_edit_' . $file->fid, $file);
+ }
+
+ $form = call_user_func_array('multiform_get_form', $forms);
+ $form['#attributes']['class'][] = 'media-bulk-upload-multiedit-form';
+
+ // Improve the display of each file form.
+ foreach (element_children($form['multiform']) as $key) {
+ $fid = $form['multiform'][$key]['fid']['#value'];
+ $file = $files[$fid];
+
+ // Add the filename to each 'subform'.
+ $title = t('<em>Edit @type</em> @title', array('@type' => $file->type, '@title' => $file->filename));
+ $form['multiform'][$key]['#prefix'] = '<h2>' . $title . '</h2>';
+
+ // Remove the 'replace file' functionality.
+ $form['multiform'][$key]['replace_upload']['#access'] = FALSE;
+
+ // Remove any actions.
+ $form['multiform'][$key]['actions']['#access'] = FALSE;
+
+ // Hide additional settings under a collapsible fieldset.
+ $form['multiform'][$key]['settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Additional settings'),
+ '#weight' => 99,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ // FAPI #collapsed and #collapsible not available in a render array.
+ '#attached' => array(
+ 'js' => array(
+ 'misc/form.js',
+ 'misc/collapse.js',
+ ),
+ ),
+ '#attributes' => array(
+ 'class' => array('collapsible', 'collapsed'),
+ ),
+ );
+
+ $form['multiform'][$key]['settings']['additional_settings'] = $form['multiform'][$key]['additional_settings'];
+ unset($form['multiform'][$key]['additional_settings']);
+ }
+
+ if (isset($form['buttons']['Delete'])) {
+ $form['buttons']['Delete']['#access'] = FALSE;
+ }
+
+ // Add a cancel button at the bottom of the form.
+ $form['buttons']['cancel'] = array(
+ '#type' => 'link',
+ '#title' => t('Cancel'),
+ '#weight' => 50,
+ );
+ if (isset($_GET['destination'])) {
+ $form['buttons']['cancel']['#href'] = $_GET['destination'];
+ }
+ else if (user_access('administer files')) {
+ $form['buttons']['cancel']['#href'] = 'admin/content/file';
+ }
+ else {
+ $form['buttons']['cancel']['#href'] = '<front>';
+ }
+
+ // Override the page title since each file form sets a title.
+ drupal_set_title(t('Edit multiple files'));
+
+ // Allow other modules to alter the form.
+ drupal_alter('media_bulk_upload_edit_multiple_form', $form);
+
+ return $form;
+}
diff --git a/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.info b/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.info
new file mode 100644
index 000000000..9763f529f
--- /dev/null
+++ b/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.info
@@ -0,0 +1,21 @@
+name = Media Bulk Upload
+description = Adds support for uploading multiple files at a time.
+package = Media
+core = 7.x
+
+dependencies[] = media
+dependencies[] = multiform
+dependencies[] = plupload
+
+test_dependencies[] = multiform
+test_dependencies[] = plupload
+
+files[] = includes/MediaBrowserBulkUpload.inc
+files[] = tests/media_bulk_upload.test
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.install b/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.install
new file mode 100644
index 000000000..a7b0741c7
--- /dev/null
+++ b/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.install
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Media Bulk Upload module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function media_bulk_upload_uninstall() {
+ // Remove variables.
+ variable_del('media_bulk_upload_import_batch_size');
+}
diff --git a/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.module b/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.module
new file mode 100644
index 000000000..9bd0d7af4
--- /dev/null
+++ b/sites/all/modules/media/modules/media_bulk_upload/media_bulk_upload.module
@@ -0,0 +1,203 @@
+<?php
+
+/**
+ * @file
+ * Primarily Drupal hooks.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function media_bulk_upload_menu() {
+ $items['admin/content/file/import'] = array(
+ 'title' => 'Import files',
+ 'description' => 'Import files into your media library.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_bulk_upload_import'),
+ 'access arguments' => array('import media'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'includes/media_bulk_upload.admin.inc',
+ 'weight' => 10,
+ );
+ $items['admin/content/file/thumbnails/import'] = $items['admin/content/file/import'];
+
+ // @todo Investigate passing file IDs in query string rather than a menu
+ // argument and then deprecate media_multi_load().
+ $items['admin/content/file/edit-multiple/%media_bulk_upload_multi'] = array(
+ 'title' => 'Edit multiple files',
+ 'page callback' => 'media_bulk_upload_file_page_edit_multiple',
+ 'page arguments' => array(4),
+ 'access callback' => '_media_bulk_upload_file_entity_access_recursive',
+ 'access arguments' => array(4, 'update'),
+ 'file' => 'includes/media_bulk_upload.pages.inc',
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function media_bulk_upload_permission() {
+ return array(
+ 'import media' => array(
+ 'title' => t('Import media files from the local filesystem'),
+ 'description' => t('Simple file importer'),
+ 'restrict access' => TRUE,
+ ),
+ );
+}
+
+/**
+ * Implements hook_media_browser_plugin_info_alter().
+ */
+function media_bulk_upload_media_browser_plugin_info_alter(&$info) {
+ $info['upload']['class'] = 'MediaBrowserBulkUpload';
+}
+
+/**
+ * Implements hook_file_operations().
+ */
+function media_bulk_upload_file_operations() {
+ return array(
+ 'edit_multiple' => array(
+ 'label' => t('Edit selected files'),
+ 'callback' => 'media_bulk_upload_file_operation_edit_multiple',
+ ),
+ );
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function media_bulk_upload_form_alter(&$form, &$form_state, $form_id) {
+ // If we're in the media browser, set the #media_browser key to true
+ // so that if an ajax request gets sent to a different path, the form
+ // still uses the media_browser_form_submit callback.
+ if (current_path() == 'media/browser' && $form_id != 'views_exposed_form') {
+ $form_state['#media_browser'] = TRUE;
+ }
+
+ // If the #media_browser key isset and is true we are using the browser
+ // popup, so add the media_browser submit handler.
+ if (!empty($form_state['#media_browser'])) {
+ $form['#submit'][] = 'media_bulk_upload_browser_form_submit';
+ }
+}
+
+/**
+ * Submit handler; direction form submissions in the media browser.
+ */
+function media_bulk_upload_browser_form_submit($form, &$form_state) {
+ $url = NULL;
+ $parameters = array();
+
+ // Multi upload.
+ if (!empty($form_state['files'])) {
+ $files = $form_state['files'];
+ $url = 'media/browser';
+ $parameters = array('query' => array('render' => 'media-popup', 'fid' => array_keys($files)));
+ }
+
+ // If $url is set, we had some sort of upload, so redirect the form.
+ if (!empty($url)) {
+ $form_state['redirect'] = array($url, $parameters);
+ }
+}
+
+/**
+ * Return a URL for editing an files.
+ *
+ * Works with an array of fids or a single fid.
+ *
+ * @param mixed $fids
+ * An array of file IDs or a single file ID.
+ */
+function media_bulk_upload_file_edit_url($fids) {
+ if (!is_array($fids)) {
+ $fids = array($fids);
+ }
+
+ if (count($fids) > 1) {
+ return 'admin/content/file/edit-multiple/' . implode(' ', $fids);
+ }
+ else {
+ return 'file/' . reset($fids) . '/edit';
+ }
+}
+
+/**
+ * Callback for the edit operation.
+ *
+ * Redirects the user to the edit multiple files page.
+ *
+ * @param array $fids
+ * An array of file IDs.
+ *
+ * @see media_file_page_edit_multiple()
+ */
+function media_bulk_upload_file_operation_edit_multiple($fids) {
+ // The thumbnail browser returns TRUE/FALSE for each item, so use array keys.
+ $fids = array_keys(array_filter($fids));
+ drupal_goto(media_bulk_upload_file_edit_url($fids), array('query' => drupal_get_destination()));
+}
+
+/**
+ * Implements hook_forms().
+ */
+function media_bulk_upload_forms($form_id, $args) {
+ $forms = array();
+ // To support the multiedit form, each form has to have a unique ID.
+ // So we name all the forms media_edit_N where the first requested form is
+ // media_edit_0, 2nd is media_edit_1, etc.
+ module_load_include('inc', 'file_entity', 'file_entity.pages');
+ if ($form_id != 'media_edit' && (strpos($form_id, 'media_edit') === 0)) {
+ $forms[$form_id] = array(
+ 'callback' => 'file_entity_edit',
+ 'wrapper_callback' => 'media_bulk_upload_prepare_edit_form',
+ );
+ }
+ return $forms;
+}
+
+function media_bulk_upload_prepare_edit_form($form, &$form_state) {
+ form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
+}
+
+/**
+ * Access callback for the media-multi form.
+ *
+ * @param $files
+ * An array of files being editing on the multiform.
+ * @param $op
+ * A string containing the operation requested, such as 'update'.
+ * @return
+ * TRUE if the current user has access to edit all of the files, otherwise FALSE.
+ */
+function _media_bulk_upload_file_entity_access_recursive($files, $op) {
+ // Check that the current user can access each file.
+ if (!empty($files)) {
+ foreach ($files as $file) {
+ if (!file_entity_access($op, $file)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Load callback for %media_multi placeholder in menu paths.
+ *
+ * @param string $fids
+ * Separated by space (e.g., "3 6 12 99"). This often appears as "+" within
+ * URLs (e.g., "3+6+12+99"), but Drupal automatically decodes paths when
+ * intializing $_GET['q'].
+ *
+ * @return array
+ * An array of corresponding file entities.
+ */
+function media_bulk_upload_multi_load($fids) {
+ return file_load_multiple(explode(' ', $fids));
+}
diff --git a/sites/all/modules/media/modules/media_bulk_upload/tests/media_bulk_upload.test b/sites/all/modules/media/modules/media_bulk_upload/tests/media_bulk_upload.test
new file mode 100644
index 000000000..6bd21987c
--- /dev/null
+++ b/sites/all/modules/media/modules/media_bulk_upload/tests/media_bulk_upload.test
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Tests for media_bulk_upload.module.
+ */
+
+/**
+ * Provides methods specifically for testing Media Bulk Upload module's bulk file uploading capabilities.
+ */
+class MediaBulkUploadTestHelper extends DrupalWebTestCase {
+ function setUp() {
+ // Since this is a base class for many test cases, support the same
+ // flexibility that DrupalWebTestCase::setUp() has for the modules to be
+ // passed in as either an array or a variable number of string arguments.
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
+ }
+ $modules[] = 'media_bulk_upload';
+ parent::setUp($modules);
+ }
+
+ /**
+ * Retrieves a sample file of the specified type.
+ */
+ function getTestFile($type_name, $size = NULL) {
+ // Get a file to upload.
+ $file = current($this->drupalGetTestFiles($type_name, $size));
+
+ // Add a filesize property to files as would be read by file_load().
+ $file->filesize = filesize($file->uri);
+
+ return $file;
+ }
+
+ /**
+ * Get a file from the database based on its filename.
+ *
+ * @param $filename
+ * A file filename, usually generated by $this->randomName().
+ * @param $reset
+ * (optional) Whether to reset the internal file_load() cache.
+ *
+ * @return
+ * A file object matching $filename.
+ */
+ function getFileByFilename($filename, $reset = FALSE) {
+ $files = file_load_multiple(array(), array('filename' => $filename), $reset);
+ // Load the first file returned from the database.
+ $returned_file = reset($files);
+ return $returned_file;
+ }
+}
+
+/**
+ * Test bulk file editing.
+ */
+class MediaBulkUploadEditTestCase extends MediaBulkUploadTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Bulk file editing',
+ 'description' => 'Test file editing with multiple files.',
+ 'group' => 'Media Bulk Upload',
+ 'dependencies' => array('multiform', 'plupload'),
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $web_user = $this->drupalCreateUser(array('create files', 'edit any document files', 'edit any image files'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Tests editing with multiple files.
+ */
+ function testBulkFileEditing() {
+ $files = array();
+
+ // Create multiple files for testing.
+ foreach (array('image', 'text') as $type_name) {
+ $test_file = $this->getTestFile($type_name);
+ $file = file_save($test_file);
+ $files[$file->fid] = $file;
+ }
+
+ // Visit the bulk file edit page and verify that it performs as expected.
+ $path = media_bulk_upload_file_edit_url(array_keys($files));
+ $this->drupalGet($path);
+
+ foreach ($files as $file) {
+ // Verify that a filename for each file is present on the page.
+ $title = t('<em>Edit @type</em> @title', array('@type' => $file->type, '@title' => $file->filename));
+ $this->assertRaw('<h2>' . $title . '</h2>', 'The file has the correct filename.');
+
+ // Verify that the 'replace file' functionality is disabled.
+ $this->assertNoField('multiform[media_edit_' . $file->fid . '_' . ($file->fid - 1) . '][files][replace_upload]', 'Replace file field found.');
+
+ // Verify that the action buttons have been removed.
+ $this->assertNoLinkByHref('file/' . $file->fid);
+ }
+ }
+}
diff --git a/sites/all/modules/media/modules/media_internet/includes/MediaBrowserInternet.inc b/sites/all/modules/media/modules/media_internet/includes/MediaBrowserInternet.inc
new file mode 100644
index 000000000..b719dc9ac
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/includes/MediaBrowserInternet.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaBrowserInternet.
+ */
+
+/**
+ * Media browser plugin for Media Internet sources.
+ */
+class MediaBrowserInternet extends MediaBrowserPlugin {
+ /**
+ * Implements MediaBrowserPluginInterface::access().
+ */
+ public function access($account = NULL) {
+ return media_internet_access($account);
+ }
+
+ /**
+ * Implements MediaBrowserPlugin::view().
+ */
+ public function view() {
+ module_load_include('inc', 'file_entity', 'file_entity.pages');
+
+ $build = array();
+ $params = $this->params;
+ $params['internet_media'] = TRUE;
+ $build['form'] = drupal_get_form('media_internet_add_upload', $params);
+
+ return $build;
+ }
+}
diff --git a/sites/all/modules/media/modules/media_internet/includes/MediaInternetBaseHandler.inc b/sites/all/modules/media/modules/media_internet/includes/MediaInternetBaseHandler.inc
new file mode 100644
index 000000000..abd71d51e
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/includes/MediaInternetBaseHandler.inc
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaInternetBaseHandler.
+ */
+
+/**
+ * A base class for managing the addition of Internet media.
+ *
+ * Classes extending this class manage the addition of Internet media. To
+ * achieve this, the class should parse user-submitted embed code, claim it
+ * when appropriate and save it as a managed file.
+ */
+abstract class MediaInternetBaseHandler {
+
+ /**
+ * The constructor for the MediaInternetBaseHandler class. This method is also called
+ * from the classes that extend this class and override this method.
+ */
+ public function __construct($embedCode) {
+ $this->embedCode = $embedCode;
+ }
+
+ /**
+ * Determines if this handler should claim the item.
+ *
+ * @param string $embed_code
+ * A string of user-submitted embed code.
+ *
+ * @return boolean
+ * Pass TRUE to claim the item.
+ */
+ abstract public function claim($embed_code);
+
+ /**
+ * Returns a file object which can be used for validation.
+ *
+ * @return StdClass
+ */
+ abstract public function getFileObject();
+
+ /**
+ * If required, implementors can validate the embedCode.
+ */
+ public function validate() {
+ }
+
+ /**
+ * Before the file has been saved, implementors may do additional operations.
+ *
+ * @param object $file_obj
+ */
+ public function preSave(&$file_obj) {
+ }
+
+ /**
+ * Saves a file to the file_managed table (with file_save).
+ *
+ * @return StdClass
+ */
+ public function save() {
+ $file_obj = $this->getFileObject();
+ $this->preSave($file_obj);
+ file_save($file_obj);
+ $this->postSave($file_obj);
+ return $file_obj;
+ }
+
+ /**
+ * After the file has been saved, implementors may do additional operations.
+ *
+ * @param object $file_obj
+ */
+ public function postSave(&$file_obj) {
+ }
+}
diff --git a/sites/all/modules/media/modules/media_internet/includes/MediaInternetFileHandler.inc b/sites/all/modules/media/modules/media_internet/includes/MediaInternetFileHandler.inc
new file mode 100644
index 000000000..c5b65cf5b
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/includes/MediaInternetFileHandler.inc
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaInternetFileHandler.
+ */
+
+/**
+ * A class for managing the addition of Internet files.
+ */
+class MediaInternetFileHandler extends MediaInternetBaseHandler {
+
+ public $fileObject;
+
+ public function preSave(&$file_obj) {
+ // Coppies the remote file locally.
+ $remote_uri = $file_obj->uri;
+ //@TODO: we should follow redirection here an save the final filename, not just the basename.
+ $local_filename = basename($remote_uri);
+ $local_filename = file_munge_filename($local_filename, variable_get('file_entity_default_allowed_extensions', 'jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp mp3 mov mp4 m4a m4v mpeg avi ogg oga ogv weba webp webm'), FALSE);
+ $local_uri = file_stream_wrapper_uri_normalize('temporary://' . $local_filename);
+ if (!@copy($remote_uri, $local_uri)) {
+ throw new Exception('Unable to add file ' . $remote_uri);
+ return;
+ }
+ // Make the current fileObject point to the local_uri, not the remote one.
+ $file_obj = file_uri_to_object($local_uri);
+ }
+
+ public function postSave(&$file_obj) {
+ $scheme = variable_get('file_default_scheme', 'public') . '://';
+ module_load_include('inc', 'file_entity', 'file_entity.pages');
+ $destination_uri = file_entity_upload_destination_uri(array());
+ $uri = file_stream_wrapper_uri_normalize($destination_uri . '/' . $file_obj->filename);
+ // Now to its new home.
+ $file_obj = file_move($file_obj, $uri, FILE_EXISTS_RENAME);
+ }
+
+ public function getFileObject() {
+ if (!$this->fileObject) {
+ $this->fileObject = file_uri_to_object($this->embedCode);
+ }
+ return $this->fileObject;
+ }
+
+ public function claim($embedCode) {
+ // Claim only valid URLs using a supported scheme.
+ if (!valid_url($embedCode, TRUE) || !in_array(file_uri_scheme($embedCode), variable_get('media_fromurl_supported_schemes', array('http', 'https', 'ftp', 'smb', 'ftps')))) {
+ return FALSE;
+ }
+
+ // This handler is intended for regular files, so don't claim URLs
+ // containing query strings or fragments.
+ if (preg_match('/[\?\#]/', $embedCode)) {
+ return FALSE;
+ }
+
+ // Since this handler copies the remote file to the local web server, do not
+ // claim a URL with an extension disallowed for media uploads.
+ $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote(variable_get('file_entity_default_allowed_extensions', 'jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp mp3 mov mp4 m4a m4v mpeg avi ogg oga ogv weba webp webm'))) . ')$/i';
+ if (!preg_match($regex, basename($embedCode))) {
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+}
diff --git a/sites/all/modules/media/modules/media_internet/includes/MediaInternetNoHandlerException.inc b/sites/all/modules/media/modules/media_internet/includes/MediaInternetNoHandlerException.inc
new file mode 100644
index 000000000..5a199cc37
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/includes/MediaInternetNoHandlerException.inc
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaInternetNoHandlerException.
+ */
+
+/**
+ * A custom exception class for handling embed code with no handler.
+ */
+class MediaInternetNoHandlerException extends Exception {
+
+}
diff --git a/sites/all/modules/media/modules/media_internet/includes/MediaInternetValidationException.inc b/sites/all/modules/media/modules/media_internet/includes/MediaInternetValidationException.inc
new file mode 100644
index 000000000..36322b3ed
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/includes/MediaInternetValidationException.inc
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaInternetValidationException.
+ */
+
+/**
+ * A custom exception class for handling file validation errors.
+ */
+class MediaInternetValidationException extends Exception {
+
+}
diff --git a/sites/all/modules/media/modules/media_internet/media_internet.api.php b/sites/all/modules/media/modules/media_internet/media_internet.api.php
new file mode 100644
index 000000000..4a6afc5a3
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/media_internet.api.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the media_internet module.
+ */
+
+/**
+ * Returns a list of Internet media providers for URL/embed code testing.
+ *
+ * @return array
+ * A nested array of provider information, keyed by class name. This class
+ * must implement a claim() method and may (should) extend the
+ * @link MediaInternetBaseHandler MediaInternetBaseHandler @endlink class.
+ * Each provider info array may have the following keys:
+ * - title: (required) A name to be used when listing the currently supported
+ * providers on the web tab of the media browser.
+ * - hidden: (optional) Boolean to prevent the provider title from being
+ * listed on the web tab of the media browser.
+ * - weight: (optional) Integer to determine the tab order. Defaults to 0.
+ *
+ * @see hook_media_internet_providers_alter()
+ * @see media_internet_get_providers()
+ */
+function hook_media_internet_providers() {
+ return array(
+ 'MyModuleYouTubeHandler' => array(
+ 'title' => t('YouTube'),
+ 'hidden' => TRUE,
+ ),
+ );
+}
+
+/**
+ * Alter the list of Internet media providers.
+ *
+ * @param array $providers
+ * The associative array of Internet media provider definitions from
+ * hook_media_internet_providers().
+ *
+ * @see hook_media_internet_providers()
+ * @see media_internet_get_providers()
+ */
+function hook_media_internet_providers_alter(&$providers) {
+ $providers['MyModuleYouTubeHandler']['title'] = t('Google video hosting');
+ $providers['MyModuleYouTubeHandler']['weight'] = 42;
+}
diff --git a/sites/all/modules/media/modules/media_internet/media_internet.info b/sites/all/modules/media/modules/media_internet/media_internet.info
new file mode 100644
index 000000000..56ee57965
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/media_internet.info
@@ -0,0 +1,20 @@
+name = Media Internet Sources
+description = Provides an API for accessing media on various internet services
+package = Media
+core = 7.x
+
+dependencies[] = media
+
+files[] = includes/MediaBrowserInternet.inc
+files[] = includes/MediaInternetBaseHandler.inc
+files[] = includes/MediaInternetFileHandler.inc
+files[] = includes/MediaInternetNoHandlerException.inc
+files[] = includes/MediaInternetValidationException.inc
+files[] = tests/media_internet.test
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/modules/media_internet/media_internet.install b/sites/all/modules/media/modules/media_internet/media_internet.install
new file mode 100644
index 000000000..03264d416
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/media_internet.install
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Media Internet module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function media_internet_uninstall() {
+ // Remove variables.
+ variable_del('media_internet_fromurl_supported_schemes');
+}
+
+/**
+ * Rebuild the registry in order to accommodate moved classes.
+ */
+function media_internet_update_7000() {
+ registry_rebuild();
+}
diff --git a/sites/all/modules/media/modules/media_internet/media_internet.media.inc b/sites/all/modules/media/modules/media_internet/media_internet.media.inc
new file mode 100644
index 000000000..33f953964
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/media_internet.media.inc
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Media module integration for the Media internet module.
+ */
+
+/**
+ * Implements hook_media_browser_plugin_info().
+ */
+function media_internet_media_browser_plugin_info() {
+ $info['media_internet'] = array(
+ 'title' => t('Web'),
+ 'class' => 'MediaBrowserInternet',
+ );
+
+ return $info;
+}
+
+/**
+ * Implements hook_media_internet_providers().
+ *
+ * Provides a very basic handler which copies files from remote sources to the
+ * local files directory.
+ */
+function media_internet_media_internet_providers() {
+ return array(
+ 'MediaInternetFileHandler' => array(
+ 'title' => 'Files',
+ 'hidden' => TRUE,
+ // Make it go last.
+ 'weight' => 10000,
+ ),
+ );
+}
diff --git a/sites/all/modules/media/modules/media_internet/media_internet.module b/sites/all/modules/media/modules/media_internet/media_internet.module
new file mode 100644
index 000000000..d94b47926
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/media_internet.module
@@ -0,0 +1,322 @@
+<?php
+
+/**
+ * Implements hook_hook_info().
+ */
+function media_internet_hook_info() {
+ $hooks = array(
+ 'media_internet_providers',
+ );
+
+ return array_fill_keys($hooks, array('group' => 'media'));
+}
+
+/**
+ * Implements hook_menu().
+ */
+function media_internet_menu() {
+ $items['file/add/web'] = array(
+ 'title' => 'Web',
+ 'description' => 'Add internet files to your media library.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_internet_add_upload'),
+ 'access callback' => 'media_internet_access',
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'file_entity.pages.inc',
+ 'file path' => drupal_get_path('module', 'file_entity'),
+ );
+
+ return $items;
+}
+
+/**
+ * Access callback for the media_internet media browser plugin.
+ */
+function media_internet_access($account = NULL) {
+ return user_access('administer files', $account) || user_access('add media from remote sources', $account);
+}
+
+/**
+ * Implement hook_permission().
+ */
+function media_internet_permission() {
+ return array(
+ 'add media from remote sources' => array(
+ 'title' => t('Add media from remote services'),
+ 'description' => t('Add media from remote sources such as other websites, YouTube, etc'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_theme().
+ */
+function media_internet_theme() {
+ return array(
+ // media_internet.pages.inc.
+ 'media_internet_embed_help' => array(
+ 'variables' => array('description' => NULL, 'supported_providers' => NULL),
+ ),
+ );
+}
+
+/**
+ * Gets the list of Internet media providers.
+ *
+ * Each 'Provider' has a title and a class which can handle saving remote files.
+ * Providers are each given a turn at parsing a user-submitted URL or embed code
+ * and, if they recognize that it belongs to a service or protocol they support,
+ * they store a representation of it as a file object in file_managed.
+ *
+ * @return array
+ * An associative array of provider information keyed by provider name.
+ */
+function media_internet_get_providers() {
+ $providers = &drupal_static(__FUNCTION__);
+
+ if (!isset($providers)) {
+ foreach (module_implements('media_internet_providers') as $module) {
+ foreach (module_invoke($module, 'media_internet_providers') as $class => $info) {
+ $providers[$class] = $info;
+
+ // Store the name of the module which declared the provider.
+ $providers[$class]['module'] = $module;
+
+ // Assign a default value to providers which don't specify a weight.
+ if (!isset($providers[$class]['weight'])) {
+ $providers[$class]['weight'] = 0;
+ }
+ }
+ }
+
+ // Allow modules to alter the list of providers.
+ drupal_alter('media_internet_providers', $providers);
+
+ // Sort the providers by weight.
+ uasort($providers, 'drupal_sort_weight');
+ }
+
+ return $providers;
+}
+
+/**
+ * Finds the appropriate provider for a given URL or embed_string
+ *
+ * Each provider has a claim() method which it uses to tell media_internet
+ * that it should handle this input. We cycle through all providers to find
+ * the right one.
+ *
+ * @todo: Make this into a normal hook or something because we have to instantiate
+ * each class to test and that's not right.
+ */
+function media_internet_get_provider($embed_string) {
+ foreach (media_internet_get_providers() as $class_name => $nothing) {
+ $p = new $class_name($embed_string);
+ if ($p->claim($embed_string)) {
+ return $p;
+ }
+ }
+ throw new MediaInternetNoHandlerException(t('Unable to handle the provided embed string or URL.'));
+}
+
+/**
+ * Returns HTML for help text based on supported internet media providers.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - description: The normal description for this field, specified by the
+ * user.
+ * - supported_providers: A string of supported providers.
+ *
+ * @ingroup themeable
+ */
+function theme_media_internet_embed_help($variables) {
+ $description = $variables['description'];
+ $supported_providers = $variables['supported_providers'];
+
+ $descriptions = array();
+
+ if (strlen($description)) {
+ $descriptions[] = $description;
+ }
+ if (!empty($supported_providers)) {
+ $descriptions[] = t('Supported internet media providers: !providers.', array('!providers' => '<strong>' . $supported_providers . '</strong>'));
+ }
+
+ return implode('<br />', $descriptions);
+}
+
+/**
+ * Implements hook_forms().
+ */
+function media_internet_forms($form_id, $args) {
+ $forms = array();
+
+ // Create a copy of the upload wizard form for internet media.
+ if ($form_id == 'media_internet_add_upload') {
+ $forms[$form_id] = array(
+ 'callback' => 'file_entity_add_upload',
+ );
+ }
+
+ return $forms;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_internet_form_file_entity_add_upload_alter(&$form, &$form_state, $form_id) {
+ $step = $form['#step'];
+ $options = $form['#options'];
+
+ // Swap the upload field for an embed field when on the first step of the web
+ // tab.
+ if ($form_id == 'media_internet_add_upload' && $step == 1) {
+ unset($form['upload']);
+
+ $form['embed_code'] = array(
+ '#type' => 'textfield',
+ '#title' => t('File URL'),
+ '#description' => t('Enter a URL to a file.'),
+ '#attributes' => array('class' => array('media-add-from-url')),
+ // There is no standard specifying a maximum length for a URL. Internet
+ // Explorer supports up to 2083 (http://support.microsoft.com/kb/208427)
+ // so we assume publicly available media URLs are within this limit.
+ '#maxlength' => 2083,
+ '#required' => TRUE,
+ '#default_value' => isset($form_state['storage']['embed_code']) ? $form_state['storage']['embed_code'] : NULL,
+ );
+
+ // Create an array to hold potential Internet media providers.
+ $providers = array();
+
+ // Determine if there are any visible providers.
+ foreach (media_internet_get_providers() as $key => $provider) {
+ if (empty($provider['hidden']) || $provider['hidden'] != TRUE) {
+ $providers[] = check_plain($provider['title']);
+ }
+ }
+
+ $form['#providers'] = $providers;
+
+ // Notify the user of any available providers.
+ if ($providers) {
+ // If any providers are enabled it is assumed that some kind of embed is supported.
+ $form['embed_code']['#title'] = t('File URL or media resource');
+ $form['embed_code']['#description'] = t('Enter a URL to a file or media resource. Many media providers also support identifying media via the embed code used to embed the media into external websites.');
+
+ $form['embed_code']['#description'] = theme('media_internet_embed_help', array('description' => $form['embed_code']['#description'], 'supported_providers' => implode(', ', $providers)));
+ }
+
+ $form['#validators'] = array();
+
+ if (!empty($options['types'])) {
+ $form['#validators']['media_file_validate_types'] = array($options['types']);
+ }
+
+ // Add validation and submission handlers to the form and ensure that they
+ // run first.
+ array_unshift($form['#validate'], 'media_internet_add_validate');
+ array_unshift($form['#submit'], 'media_internet_add_submit');
+ }
+}
+
+/**
+ * Allow stream wrappers to have their chance at validation.
+ *
+ * Any module that implements hook_media_parse will have an
+ * opportunity to validate this.
+ *
+ * @see media_parse_to_uri()
+ */
+function media_internet_add_validate($form, &$form_state) {
+ // Supporting providers can now claim this input. It might be a URL, but it
+ // might be an embed code as well.
+ $embed_code = $form_state['values']['embed_code'];
+
+ try {
+ $provider = media_internet_get_provider($embed_code);
+ $provider->validate();
+ }
+ catch (MediaInternetNoHandlerException $e) {
+ form_set_error('embed_code', $e->getMessage());
+ return;
+ }
+ catch (MediaInternetValidationException $e) {
+ form_set_error('embed_code', $e->getMessage());
+ return;
+ }
+
+ $validators = $form['#validators'];
+ $file = $provider->getFileObject();
+
+ if ($validators) {
+ try {
+ $file = $provider->getFileObject();
+ }
+ catch (Exception $e) {
+ form_set_error('embed_code', $e->getMessage());
+ return;
+ }
+
+ // Check for errors. @see media_add_upload_validate calls file_save_upload().
+ // this code is ripped from file_save_upload because we just want the validation part.
+ // Call the validation functions specified by this function's caller.
+ $errors = file_validate($file, $validators);
+
+ if (!empty($errors)) {
+ $message = t('%url could not be added.', array('%url' => $embed_code));
+ if (count($errors) > 1) {
+ $message .= theme('item_list', array('items' => $errors));
+ }
+ else {
+ $message .= ' ' . array_pop($errors);
+ }
+ form_set_error('embed_code', $message);
+ return FALSE;
+ }
+ }
+
+ // @TODO: Validate that if we have no $uri that this is a valid file to
+ // save. For instance, we may only be interested in images, and it would
+ // be helpful to let the user know they passed the HTML page containing
+ // the image accidentally. That would also save us from saving the file
+ // in the submit step.
+
+ // This is kinda a hack of the same.
+
+ // This should use the file_validate routines that the upload form users.
+ // We need to fix the media_parse_to_file routine to allow for a validation.
+}
+
+/**
+ * Upload a file from a URL.
+ *
+ * This will copy a file from a remote location and store it locally.
+ *
+ * @see media_parse_to_uri()
+ * @see media_parse_to_file()
+ */
+function media_internet_add_submit($form, &$form_state) {
+ $embed_code = $form_state['values']['embed_code'];
+
+ try {
+ // Save the remote file
+ $provider = media_internet_get_provider($embed_code);
+ // Providers decide if they need to save locally or somewhere else.
+ // This method returns a file object
+ $file = $provider->save();
+ }
+ catch (Exception $e) {
+ form_set_error('embed_code', $e->getMessage());
+ return;
+ }
+
+ if (!$file->fid) {
+ form_set_error('embed_code', t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $embed_code)));
+ return;
+ }
+ else {
+ $form_state['storage']['upload'] = $file->fid;
+ }
+}
diff --git a/sites/all/modules/media/modules/media_internet/tests/includes/MediaInternetTestHandler.inc b/sites/all/modules/media/modules/media_internet/tests/includes/MediaInternetTestHandler.inc
new file mode 100644
index 000000000..764279caf
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/tests/includes/MediaInternetTestHandler.inc
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Extends the MediaInternetBaseHandler class to handle videos from an imaginary example.com.
+ */
+
+/**
+ * Implementation of MediaInternetBaseHandler.
+ *
+ * @see hook_media_internet_providers().
+ */
+class MediaInternetTestHandler extends MediaInternetBaseHandler {
+ public function parse($embedCode) {
+ // http://example.com/video/*
+ $patterns = array(
+ '@example\.com/video/(\d+)@i',
+ );
+
+ foreach ($patterns as $pattern) {
+ preg_match($pattern, $embedCode, $matches);
+ if (isset($matches[1])) {
+ return file_stream_wrapper_uri_normalize('mediainternettest://video/' . $matches[1]);
+ }
+ }
+ }
+
+ public function claim($embedCode) {
+ if ($this->parse($embedCode)) {
+ return TRUE;
+ }
+ }
+
+ public function getFileObject() {
+ $uri = $this->parse($this->embedCode);
+ $file = file_uri_to_object($uri, TRUE);
+
+ // Override the default filename for testing purposes.
+ if (empty($file->fid)) {
+ $file->filename = 'Drupal';
+ }
+
+ return $file;
+ }
+}
diff --git a/sites/all/modules/media/modules/media_internet/tests/includes/MediaInternetTestStreamWrapper.inc b/sites/all/modules/media/modules/media_internet/tests/includes/MediaInternetTestStreamWrapper.inc
new file mode 100644
index 000000000..202538389
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/tests/includes/MediaInternetTestStreamWrapper.inc
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Extends the MediaReadOnlyStreamWrapper class to handle videos from an imaginary example.com.
+ */
+
+/**
+ * Create an instance like this:
+ * $media_internet_test = new MediaInternetTestStreamWrapper('mediainternettest://video/[video-code]');
+ */
+class MediaInternetTestStreamWrapper extends MediaReadOnlyStreamWrapper {
+ protected $base_url = 'http://example.com';
+
+ static function getMimeType($uri, $mapping = NULL) {
+ return 'video/mediainternettest';
+ }
+
+ function interpolateUrl() {
+ if ($parameters = $this->get_parameters()) {
+ return $this->base_url . '/' . $parameters['video'];
+ }
+ }
+}
diff --git a/sites/all/modules/media/modules/media_internet/tests/media_internet.test b/sites/all/modules/media/modules/media_internet/tests/media_internet.test
new file mode 100644
index 000000000..04b3243b6
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/tests/media_internet.test
@@ -0,0 +1,394 @@
+<?php
+
+/**
+ * @file
+ * Tests for media_internet.module.
+ */
+
+/**
+ * Provides methods specifically for testing Media Internet module's remote media handling.
+ */
+class MediaInternetTestHelper extends DrupalWebTestCase {
+ function setUp() {
+ // Since this is a base class for many test cases, support the same
+ // flexibility that DrupalWebTestCase::setUp() has for the modules to be
+ // passed in as either an array or a variable number of string arguments.
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
+ }
+ $modules[] = 'media_internet';
+ parent::setUp($modules);
+ }
+
+ /**
+ * Retrieves a sample file of the specified type.
+ */
+ function getTestFile($type_name, $size = NULL) {
+ // Get a file to upload.
+ $file = current($this->drupalGetTestFiles($type_name, $size));
+
+ // Add a filesize property to files as would be read by file_load().
+ $file->filesize = filesize($file->uri);
+
+ return $file;
+ }
+
+ /**
+ * Retrieves the fid of the last inserted file.
+ */
+ function getLastFileId() {
+ return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
+ }
+
+ /**
+ * Get a file from the database based on its filename.
+ *
+ * @param $filename
+ * A file filename, usually generated by $this->randomName().
+ * @param $reset
+ * (optional) Whether to reset the internal file_load() cache.
+ *
+ * @return
+ * A file object matching $filename.
+ */
+ function getFileByFilename($filename, $reset = FALSE) {
+ $files = file_load_multiple(array(), array('filename' => $filename), $reset);
+ // Load the first file returned from the database.
+ $returned_file = reset($files);
+ return $returned_file;
+ }
+
+ protected function createFileType($overrides = array()) {
+ $type = new stdClass();
+ $type->type = 'test';
+ $type->label = "Test";
+ $type->description = '';
+ $type->mimetypes = array('image/jpeg', 'image/gif', 'image/png', 'image/tiff');
+
+ foreach ($overrides as $k => $v) {
+ $type->$k = $v;
+ }
+
+ file_type_save($type);
+ return $type;
+ }
+}
+
+/**
+ * Tests the media browser 'Web' tab.
+ */
+class MediaInternetBrowserWebTabTestCase extends MediaInternetTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media browser web tab test',
+ 'description' => 'Tests the media browser web tab.',
+ 'group' => 'Media Internet',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $web_user = $this->drupalCreateUser(array('access media browser', 'add media from remote sources'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Tests that the views sorting works on the media browser 'Library' tab.
+ */
+ function testMediaBrowserWebTab() {
+ // Load only the 'Library' tab of the media browser.
+ $options = array(
+ 'query' => array(
+ 'enabledPlugins' => array(
+ 'media_internet' => 'media_internet',
+ ),
+ ),
+ );
+
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+
+ // Check that the web tab is available and has an 'embed code' field.
+ $this->assertRaw(t('Web'), t('The web tab was found.'));
+ $this->assertFieldByName('embed_code', '', t('Embed code form field found.'));
+ }
+}
+
+/**
+ * Test the default MediaInternetFileHandler provider.
+ */
+class MediaInternetRemoteFileTestCase extends MediaInternetTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Remote media file handler provider',
+ 'description' => 'Test the default remote file handler provider.',
+ 'group' => 'Media Internet',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Disable the private file system which is automatically enabled by
+ // DrupalTestCase so we can test the upload wizard correctly.
+ variable_del('file_private_path');
+
+ $web_user = $this->drupalCreateUser(array('create files', 'add media from remote sources'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Tests the default remote file handler.
+ */
+ function testRemoteFileHandling() {
+ // Step 1: Add a basic document file by providing a URL to the file.
+ $edit = array();
+ $edit['embed_code'] = file_create_url('README.txt');
+ $this->drupalPost('file/add/web', $edit, t('Next'));
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the video file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Document', '%name' => $file->filename)), t('Document file uploaded.'));
+ }
+}
+
+/**
+ * Tests custom media provider APIs.
+ */
+class MediaInternetProviderTestCase extends MediaInternetTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Custom media provider test',
+ 'description' => 'Tests the custom media provider APIs.',
+ 'group' => 'Media Internet',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('media_internet_test');
+
+ // Disable the private file system which is automatically enabled by
+ // DrupalTestCase so we can test the upload wizard correctly.
+ variable_del('file_private_path');
+
+ // Enable media_internet_test.module's hook_media_internet_providers()
+ // implementation.
+ variable_set('media_internet_test_media_internet_providers', TRUE);
+
+ $web_user = $this->drupalCreateUser(array('create files', 'view own private files', 'add media from remote sources'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Test the basic file upload wizard functionality.
+ */
+ function testMediaInternetCustomProviderWizardBasic() {
+ $this->drupalGet('file/add/web');
+ $this->assertResponse(200);
+
+ // Check that the provider is listed as supported.
+ $this->assertRaw(t('Supported internet media providers: !providers.', array('!providers' => '<strong>' . 'Media Internet Test' . '</strong>')), t('The example media provider is enabled.'));
+
+ // Enable media_internet_test.module's
+ // hook_media_browser_plugin_info_alter_alter() implementation and ensure it
+ // is working as designed.
+ variable_set('media_internet_test_media_internet_providers_alter', TRUE);
+
+ $this->drupalGet('file/add/web');
+ $this->assertRaw(t('Supported internet media providers: !providers.', array('!providers' => '<strong>' . 'Altered provider title' . '</strong>')), t('The example media provider was successfully altered.'));
+
+ // Step 1: Upload a basic video file.
+ $edit = array();
+ $edit['embed_code'] = 'http://www.example.com/video/123';
+ $this->drupalPost('file/add/web', $edit, t('Next'));
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the video file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Video', '%name' => $file->filename)), t('Video file uploaded.'));
+ }
+
+ /**
+ * Test the file upload wizard type step.
+ */
+ function testMediaInternetCustomProviderWizardTypes() {
+ // Create multiple file types with the same mime types.
+ $this->createFileType(array('type' => 'video1', 'label' => 'Video 1', 'mimetypes' => array('video/mediainternettest')));
+ $this->createFileType(array('type' => 'video2', 'label' => 'Video 2', 'mimetypes' => array('video/mediainternettest')));
+
+ // Step 1: Upload a basic video file.
+ $edit = array();
+ $edit['embed_code'] = 'http://www.example.com/video/123';
+ $this->drupalPost('file/add/web', $edit, t('Next'));
+
+ // Step 2: File type selection.
+ $edit = array();
+ $edit['type'] = 'video2';
+ $this->drupalPost(NULL, $edit, t('Next'));
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the video file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Video 2', '%name' => $file->filename)), t('Video 2 file uploaded.'));
+ }
+
+ /**
+ * Test the file upload wizard scheme step.
+ */
+ function testMediaInternetCustomProviderWizardSchemes() {
+ // Enable the private file system.
+ variable_set('file_private_path', $this->private_files_directory);
+
+ // Step 1: Upload a basic video file.
+ $edit = array();
+ $edit['embed_code'] = 'http://www.example.com/video/123';
+ $this->drupalPost('file/add/web', $edit, t('Next'));
+
+ // Step 3: Users should not be able to select a scheme for files with
+ // read-only stream wrappers.
+ $this->assertNoFieldByName('scheme');
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the video file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Video', '%name' => $file->filename)), t('Video file uploaded.'));
+ }
+
+ /**
+ * Test the file upload wizard field step.
+ */
+ function testMediaInternetCustomProviderWizardFields() {
+ $filename = $this->randomName();
+
+ // Add a text field to the video file type.
+ $field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $field = array('field_name' => $field_name, 'type' => 'text');
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => $field_name,
+ 'entity_type' => 'file',
+ 'bundle' => 'video',
+ 'label' => $this->randomName() . '_label',
+ );
+ field_create_instance($instance);
+
+ // Step 1: Upload a basic video file.
+ $edit = array();
+ $edit['embed_code'] = 'http://www.example.com/video/123';
+ $this->drupalPost('file/add/web', $edit, t('Next'));
+
+ // Step 4: Attached fields.
+ $edit = array();
+ $edit['filename'] = $filename;
+ $edit[$field_name . '[' . LANGUAGE_NONE . '][0][value]'] = $this->randomName();
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Check that the video file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => 'Video', '%name' => $filename)), t('Video file uploaded.'));
+ }
+
+ /**
+ * Test skipping each of the file upload wizard steps.
+ */
+ function testMediaInternetCustomProviderWizardStepSkipping() {
+ $filename = $this->randomName();
+
+ // Ensure that the file is affected by every step.
+ variable_set('file_private_path', $this->private_files_directory);
+
+ $this->createFileType(array('type' => 'video1', 'label' => 'Video 1', 'mimetypes' => array('video/mediainternettest')));
+ $this->createFileType(array('type' => 'video2', 'label' => 'Video 2', 'mimetypes' => array('video/mediainternettest')));
+
+ $field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $field = array('field_name' => $field_name, 'type' => 'text');
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => $field_name,
+ 'entity_type' => 'file',
+ 'bundle' => 'video2',
+ 'label' => $this->randomName() . '_label',
+ );
+ field_create_instance($instance);
+
+ // Test skipping each upload wizard step.
+ foreach (array('types', 'schemes', 'fields') as $step) {
+ // Step to skip.
+ switch ($step) {
+ case 'types':
+ variable_set('file_entity_file_upload_wizard_skip_file_type', TRUE);
+ break;
+ case 'schemes':
+ variable_set('file_entity_file_upload_wizard_skip_scheme', TRUE);
+ break;
+ case 'fields':
+ variable_set('file_entity_file_upload_wizard_skip_fields', TRUE);
+ break;
+ }
+
+ // Step 1: Upload a basic video file.
+ $edit = array();
+ $edit['embed_code'] = 'http://www.example.com/video/123';
+ $this->drupalPost('file/add/web', $edit, t('Next'));
+
+ // Step 2: File type selection.
+ if ($step != 'types') {
+ $edit = array();
+ $edit['type'] = 'video2';
+ $this->drupalPost(NULL, $edit, t('Next'));
+ }
+
+ // Step 3: Users should not be able to select a scheme for files with
+ // read-only stream wrappers.
+ $this->assertNoFieldByName('scheme');
+
+ // Step 4: Attached fields.
+ if ($step != 'fields') {
+ // Skipping file type selection essentially skips this step as well
+ // because the file will not be assigned a type so no fields will be
+ // available.
+ if ($step != 'types') {
+ $edit = array();
+ $edit['filename'] = $filename;
+ $edit[$field_name . '[' . LANGUAGE_NONE . '][0][value]'] = $this->randomName();
+ $this->drupalPost(NULL, $edit, t('Save'));
+ }
+ }
+
+ // Check that the file exists in the database.
+ $fid = $this->getLastFileId();
+ $file = file_load($fid);
+ $this->assertTrue($file, t('File found in database.'));
+
+ // Determine the file's file type.
+ $type = file_type_load($file->type);
+
+ // Check that the video file has been uploaded.
+ $this->assertRaw(t('!type %name was uploaded.', array('!type' => $type->label, '%name' => $file->filename)), t('Video file uploaded.'));
+
+ // Reset 'skip' variables.
+ variable_del('file_entity_file_upload_wizard_skip_file_type');
+ variable_del('file_entity_file_upload_wizard_skip_scheme');
+ variable_del('file_entity_file_upload_wizard_skip_fields');
+ }
+ }
+}
diff --git a/sites/all/modules/media/modules/media_internet/tests/media_internet_test.info b/sites/all/modules/media/modules/media_internet/tests/media_internet_test.info
new file mode 100644
index 000000000..09f831c40
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/tests/media_internet_test.info
@@ -0,0 +1,15 @@
+name = Media Internet Test
+description = Provides hooks for testing Media Internet module functionality.
+package = Media
+core = 7.x
+hidden = TRUE
+
+files[] = includes/MediaInternetTestStreamWrapper.inc
+files[] = includes/MediaInternetTestHandler.inc
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/modules/media_internet/tests/media_internet_test.module b/sites/all/modules/media/modules/media_internet/tests/media_internet_test.module
new file mode 100644
index 000000000..cfcd69a26
--- /dev/null
+++ b/sites/all/modules/media/modules/media_internet/tests/media_internet_test.module
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Provides Media Internet module hook implementations for testing purposes.
+ */
+
+/**
+ * Implements hook_media_internet_providers().
+ */
+function media_internet_test_media_internet_providers() {
+ // Allow tests to enable or disable this hook.
+ if (!variable_get('media_internet_test_media_internet_providers', FALSE)) {
+ return array();
+ }
+
+ return array(
+ 'MediaInternetTestHandler' => array(
+ 'title' => t('Media Internet Test'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_media_browser_plugin_info_alter_alter().
+ */
+function media_internet_test_media_internet_providers_alter(&$providers) {
+ // Allow tests to enable or disable this hook.
+ if (!variable_get('media_internet_test_media_internet_providers_alter', FALSE)) {
+ return;
+ }
+
+ $providers['MediaInternetTestHandler']['title'] = t('Altered provider title');
+}
+
+/**
+ * Implements hook_stream_wrappers().
+ */
+function media_internet_test_stream_wrappers() {
+ return array(
+ 'mediainternettest' => array(
+ 'name' => t('Media Internet Test'),
+ 'class' => 'MediaInternetTestStreamWrapper',
+ 'description' => t('Media Internet Test.'),
+ 'type' => STREAM_WRAPPERS_READ_VISIBLE,
+ ),
+ );
+}
+
+/**
+ * Implements hook_media_parse().
+ *
+ * @todo This hook should be deprecated. Refactor Media module to not call it
+ * any more, since media_internet should be able to automatically route to the
+ * appropriate handler.
+ */
+function media_internet_test_media_parse($embed_code) {
+ $handler = new MediaInternetTestHandler($embed_code);
+ return $handler->parse($embed_code);
+}
+
+ /**
+ * Implements hook_file_mimetype_mapping_alter().
+ */
+function media_internet_test_file_mimetype_mapping_alter(&$mapping) {
+ $mapping['mimetypes'][] = 'video/mediainternettest';
+}
diff --git a/sites/all/modules/media/modules/media_migrate_file_types/includes/media_migrate_file_types.pages.inc b/sites/all/modules/media/modules/media_migrate_file_types/includes/media_migrate_file_types.pages.inc
new file mode 100644
index 000000000..78131e88e
--- /dev/null
+++ b/sites/all/modules/media/modules/media_migrate_file_types/includes/media_migrate_file_types.pages.inc
@@ -0,0 +1,203 @@
+<?php
+
+/**
+ * @file
+ * Common pages for the Media Migrate File Types module.
+ */
+
+/**
+ * File type migration page.
+ *
+ * Allows site administrator to execute migration of old/disabled/deleted
+ * file types to new ones.
+ */
+function media_migrate_file_types_upgrade_file_types($form, &$form_state) {
+ $migratable_types = _media_migrate_file_types_get_migratable_file_types();
+
+ // Silently return if there are no file types that need migration.
+ if (empty($migratable_types)) {
+ return array(
+ 'message' => array(
+ '#markup' => t('There are no file types that need migration. The Media Medigrate File Types module can now be safely <a href="@modules">disabled</a>.', array('@modules' => url('admin/modules'))),
+ ),
+ );
+ }
+
+ $form['message'] = array(
+ 'message' => array(
+ '#markup' => t('This page allows you to migrate deprecated and/or disabled file types to new ones. It will migrate files from old type to new one and optionally migrate fields and delete old type.'),
+ ),
+ );
+
+ $form['migrate_fields'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Migrate fields'),
+ '#default_value' => TRUE,
+ '#description' => t('Migrate fields and their values from old file types to new ones.'),
+ );
+ $form['delete_old_type'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Delete old type'),
+ '#default_value' => FALSE,
+ '#description' => t('Delete old file type if migration was successful and delete operation is possible (type is not exported in code).'),
+ );
+ $form['migrate_mimes'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Migrate type mime-type'),
+ '#default_value' => TRUE,
+ '#description' => t('Move mime-type from old type to new one.'),
+ );
+
+ $form['upgradable_types'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Upgradable file types'),
+ );
+
+ $options = array('- ' . t('Do not upgrade') . ' -');
+ foreach (file_type_get_enabled_types() as $type) {
+ $options[$type->type] = $type->label;
+ }
+
+ foreach ($migratable_types as $machine_name) {
+ $type = file_type_load($machine_name);
+ if (!$type) {
+ $type = new stdClass;
+ $type->label = $type->type = $machine_name;
+ }
+ $form['upgradable_types'][$machine_name] = array(
+ '#type' => 'select',
+ '#title' => $type->label,
+ '#options' => $options,
+ '#description' => t(
+ 'Select file type which you want to migrate @type to. Select %no_upgrade if type should stay as it is.',
+ array('@type' => $type->label, '%no_upgrade' => '- ' . t('Do not upgrade') . ' -')),
+ );
+ }
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Start migration'),
+ );
+
+ return $form;
+}
+
+/**
+ * File type migration page submit handler.
+ */
+function media_migrate_file_types_upgrade_file_types_submit($form, &$form_state) {
+ $migratable_types = _media_migrate_file_types_get_migratable_file_types();
+ $migrate = FALSE;
+ foreach ($migratable_types as $type) {
+ if ($form_state['values'][$type]) {
+ $migrate = TRUE;
+ break;
+ }
+ }
+
+ // Return silently if no types were selected for migration.
+ if (!$migrate) {
+ return;
+ }
+
+ // Use confirmation page/form.
+ $query = $form_state['values'];
+ unset($query['op']);
+ unset($query['submit']);
+ unset($query['form_id']);
+ unset($query['form_token']);
+ unset($query['form_build_id']);
+
+ $form_state['redirect'] = array(
+ 'admin/structure/file-types/upgrade/confirm',
+ array('query' => $query),
+ );
+}
+
+/**
+ * File types migration confirmation page.
+ */
+function media_migrate_file_types_upgrade_file_types_confirm($form, &$form_state) {
+ return confirm_form(
+ $form,
+ t('Do you really want to migrate selected file types?'),
+ 'admin/structure/file-types/upgrade',
+ NULL,
+ t('Migrate')
+ );
+}
+
+/**
+ * File types migration confirmation page submit. Executes actual migration.
+ */
+function media_migrate_file_types_upgrade_file_types_confirm_submit($form, &$form_state) {
+ $migratable_types = _media_migrate_file_types_get_migratable_file_types();
+ foreach ($migratable_types as $type) {
+ if ($_GET[$type] && $bundle_new = file_type_load($_GET[$type])) {
+ // Old bundle might be deleted so let's fake some values.
+ $bundle_old = file_type_load($type);
+ if (empty($bundle_old)) {
+ $bundle_old = new stdClass;
+ $bundle_old->type = $type;
+ $bundle_old->mimetypes = array();
+ $bundle_old->export_type = 2;
+ }
+
+ // Migrate fields to new bundle.
+ if ($_GET['migrate_fields']) {
+ $old_fields = db_select('field_config_instance', 'fc')->fields('fc', array('field_name'))->condition('entity_type', 'file')->condition('bundle', $bundle_old->type)->execute()->fetchCol();
+ $new_fields = db_select('field_config_instance', 'fc')->fields('fc', array('field_name'))->condition('entity_type', 'file')->condition('bundle', $bundle_new->type)->execute()->fetchCol();
+ $fields_to_move = array_diff($old_fields, $new_fields);
+ $fields_to_drop = array_diff($old_fields, $fields_to_move);
+
+ if (!empty($fields_to_move)) {
+ db_update('field_config_instance')
+ ->fields(array('bundle' => $bundle_new->type))
+ ->condition('entity_type', 'file')
+ ->condition('bundle', $bundle_old->type)
+ ->condition('field_name', $fields_to_move, 'IN')
+ ->execute();
+ }
+
+ if (!empty($fields_to_drop)) {
+ db_delete('field_config_instance')
+ ->condition('entity_type', 'file')
+ ->condition('bundle', $bundle_old->type)
+ ->condition('field_name', $fields_to_drop, 'IN')
+ ->execute();
+ }
+
+ field_cache_clear();
+ module_invoke_all('field_attach_rename_bundle', 'file', $bundle_old->type, $bundle_new->type);
+ }
+
+ // Migrate mimetypes to new bundle.
+ if ($_GET['migrate_mimes']) {
+ $changed = FALSE;
+ foreach ($bundle_old->mimetypes as $mime) {
+ if (!file_entity_match_mimetypes($bundle_new->mimetypes, $mime)) {
+ $bundle_new->mimetypes[] = $mime;
+ $changed = TRUE;
+ }
+ }
+
+ if ($changed) {
+ file_type_save($bundle_new);
+ }
+ }
+
+ // Delete old bundle.
+ if ($_GET['delete_old_type'] && $bundle_old->export_type == 1) {
+ file_type_delete($bundle_old);
+ }
+
+ // Migrate files.
+ db_update('file_managed')
+ ->fields(array('type' => $bundle_new->type))
+ ->condition('type', $bundle_old->type)
+ ->execute();
+ }
+ }
+
+ $form_state['redirect'] = 'admin/structure/file-types';
+}
diff --git a/sites/all/modules/media/modules/media_migrate_file_types/media_migrate_file_types.info b/sites/all/modules/media/modules/media_migrate_file_types/media_migrate_file_types.info
new file mode 100644
index 000000000..b872ec514
--- /dev/null
+++ b/sites/all/modules/media/modules/media_migrate_file_types/media_migrate_file_types.info
@@ -0,0 +1,16 @@
+name = Media Migrate File Types
+description = Provides a UI for updating legacy media types with the new file types provided by File Entity.
+package = Media
+core = 7.x
+hidden = TRUE
+
+dependencies[] = media
+
+configure = admin/structure/file-types/upgrade
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/modules/media_migrate_file_types/media_migrate_file_types.module b/sites/all/modules/media/modules/media_migrate_file_types/media_migrate_file_types.module
new file mode 100644
index 000000000..c9c6d46c5
--- /dev/null
+++ b/sites/all/modules/media/modules/media_migrate_file_types/media_migrate_file_types.module
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Primarily Drupal hooks.
+ */
+
+/*
+ * Implements hook_system_info_alter()
+ */
+function media_migrate_file_types_system_info_alter(&$info, $file, $type) {
+ if ($type == 'module' && $file->name == 'media_migrate_file_types') {
+ $info['hidden'] = FALSE;
+ }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function media_migrate_file_types_menu() {
+ $items['admin/structure/file-types/upgrade'] = array(
+ 'title' => 'Upgrade types',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_migrate_file_types_upgrade_file_types'),
+ 'access arguments' => array('administer file types'),
+ 'file' => 'includes/media_migrate_file_types.pages.inc',
+ 'type' => MENU_CALLBACK,
+ );
+ $items['admin/structure/file-types/upgrade/confirm'] = array(
+ 'title' => 'Upgrade types',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_migrate_file_types_upgrade_file_types_confirm'),
+ 'access arguments' => array('administer file types'),
+ 'file' => 'includes/media_migrate_file_types.pages.inc',
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_help().
+ */
+function media_migrate_file_types_help($path, $arg) {
+ switch ($path) {
+ case 'admin/structure/file-types':
+ if (_media_migrate_file_types_get_migratable_file_types()) {
+ drupal_set_message(t('There are disabled/deleted file types that can be migrated to their new alternatives. Visit <a href="!url">migration page</a> to get more information.', array('!url' => url('admin/structure/file-types/upgrade'))));
+ }
+ break;
+ }
+}
+
+/**
+ * Checks if there are any files that belong to disabled or deleted file
+ * types.
+ *
+ * @return Array of file types (machine names) that are candidates for
+ * migration.
+ */
+function _media_migrate_file_types_get_migratable_file_types() {
+ $query = db_select('file_managed', 'f')
+ ->fields('f', array('type'))
+ ->distinct();
+ $types = $query->execute()->fetchCol();
+
+ $enabled_types = array();
+ foreach (file_type_get_enabled_types() as $type) {
+ $enabled_types[] = $type->type;
+ }
+
+ return array_diff($types, $enabled_types);
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/css/media_wysiwyg.css b/sites/all/modules/media/modules/media_wysiwyg/css/media_wysiwyg.css
new file mode 100644
index 000000000..7b6f0190c
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/css/media_wysiwyg.css
@@ -0,0 +1,20 @@
+/**
+ * @file
+ * Styles for the format form.
+ *
+ * The display and layout of the Media browser assumes Drupal's Seven theme as
+ * the theme active when this is displayed.
+ */
+
+#media-wysiwyg-format-form {
+ margin: 20px;
+}
+
+#media-wysiwyg-format-form .media-item {
+ float: left;
+ margin-right: 10px;
+}
+
+#media-wysiwyg-format-form .form-item-format label {
+ display: inline;
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/images/wysiwyg-media.gif b/sites/all/modules/media/modules/media_wysiwyg/images/wysiwyg-media.gif
new file mode 100644
index 000000000..495e71d37
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/images/wysiwyg-media.gif
Binary files differ
diff --git a/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.file_usage.inc b/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.file_usage.inc
new file mode 100644
index 000000000..cbc1e841b
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.file_usage.inc
@@ -0,0 +1,179 @@
+<?php
+
+/**
+ * @file
+ * Functions related to the tracking the file usage of embedded media.
+ */
+
+/**
+ * Implements hook_field_attach_insert().
+ *
+ * Track file usage for media files included in formatted text. Note that this
+ * is heavy-handed, and should be replaced when Drupal's filter system is
+ * context-aware.
+ */
+function media_wysiwyg_field_attach_insert($entity_type, $entity) {
+ _media_wysiwyg_filter_add_file_usage_from_fields($entity_type, $entity);
+}
+
+/**
+ * Implements hook_field_attach_update().
+ *
+ * @see media_field_attach_insert().
+ */
+function media_wysiwyg_field_attach_update($entity_type, $entity) {
+ _media_wysiwyg_filter_add_file_usage_from_fields($entity_type, $entity);
+}
+
+/**
+ * Add file usage from file references in an entity's text fields.
+ */
+function _media_wysiwyg_filter_add_file_usage_from_fields($entity_type, $entity) {
+ // Track the total usage for files from all fields combined.
+ $entity_files = media_wysiwyg_entity_field_count_files($entity_type, $entity);
+
+ list($entity_id, $entity_vid, $entity_bundle) = entity_extract_ids($entity_type, $entity);
+
+ // When an entity has revisions and then is saved again NOT as new version the
+ // previous revision of the entity has be loaded to get the last known good
+ // count of files. The saved data is compared against the last version
+ // so that a correct file count can be created for that (the current) version
+ // id. This code may assume some things about entities that are only true for
+ // node objects. This should be reviewed.
+ // @TODO this conditional can probably be condensed
+ if (empty($entity->revision) && empty($entity->old_vid) && empty($entity->is_new) && ! empty($entity->original)) {
+ $old_files = media_wysiwyg_entity_field_count_files($entity_type, $entity->original);
+ foreach ($old_files as $fid => $old_file_count) {
+ // Were there more files on the node just prior to saving?
+ if (empty($entity_files[$fid])) {
+ $entity_files[$fid] = 0;
+ }
+ if ($old_file_count > $entity_files[$fid]) {
+ $deprecate = $old_file_count - $entity_files[$fid];
+ // Now deprecate this usage
+ $file = file_load($fid);
+ if ($file) {
+ file_usage_delete($file, 'media', $entity_type, $entity_id, $deprecate);
+ }
+ // Usage is deleted, nothing more to do with this file
+ unset($entity_files[$fid]);
+ }
+ // There are the same number of files, nothing to do
+ elseif ($entity_files[$fid] == $old_file_count) {
+ unset($entity_files[$fid]);
+ }
+ // There are more files now, adjust the difference for the greater number.
+ // file_usage incrementing will happen below.
+ else {
+ // We just need to adjust what the file count will account for the new
+ // images that have been added since the increment process below will
+ // just add these additional ones in
+ $entity_files[$fid] = $entity_files[$fid] - $old_file_count;
+ }
+ }
+ }
+
+ // Each entity revision counts for file usage. If versions are not enabled
+ // the file_usage table will have no entries for this because of the delete
+ // query above.
+ foreach ($entity_files as $fid => $entity_count) {
+ if ($file = file_load($fid)) {
+ file_usage_add($file, 'media', $entity_type, $entity_id, $entity_count);
+ }
+ }
+}
+
+/**
+ * Parse file references from an entity's text fields and return them as an array.
+ */
+function media_wysiwyg_filter_parse_from_fields($entity_type, $entity) {
+ $file_references = array();
+
+ foreach (media_wysiwyg_filter_fields_with_text_filtering($entity_type, $entity) as $field_name) {
+ if ($field_items = field_get_items($entity_type, $entity, $field_name)) {
+ foreach ($field_items as $field_item) {
+ preg_match_all(MEDIA_WYSIWYG_TOKEN_REGEX, $field_item['value'], $matches);
+ foreach ($matches[0] as $tag) {
+ $tag = str_replace(array('[[', ']]'), '', $tag);
+ $tag_info = drupal_json_decode($tag);
+ if (isset($tag_info['fid']) && $tag_info['type'] == 'media') {
+ $file_references[] = $tag_info;
+ }
+ }
+
+ preg_match_all(MEDIA_WYSIWYG_TOKEN_REGEX, $field_item['value'], $matches_alt);
+ foreach ($matches_alt[0] as $tag) {
+ $tag = urldecode($tag);
+ $tag_info = drupal_json_decode($tag);
+ if (isset($tag_info['fid']) && $tag_info['type'] == 'media') {
+ $file_references[] = $tag_info;
+ }
+ }
+ }
+ }
+ }
+
+ return $file_references;
+}
+
+/**
+ * Utility function to get the file count in this entity
+ *
+ * @param type $entity
+ * @param type $entity_type
+ * @return int
+ */
+function media_wysiwyg_entity_field_count_files($entity_type, $entity) {
+ $entity_files = array();
+ foreach (media_wysiwyg_filter_parse_from_fields($entity_type, $entity) as $file_reference) {
+ if (empty($entity_files[$file_reference['fid']])) {
+ $entity_files[$file_reference['fid']] = 1;
+ }
+ else {
+ $entity_files[$file_reference['fid']]++;
+ }
+ }
+ return $entity_files;
+}
+
+/**
+ * Implements hook_entity_delete().
+ */
+function media_wysiwyg_entity_delete($entity, $type) {
+ list($entity_id) = entity_extract_ids($type, $entity);
+
+ db_delete('file_usage')
+ ->condition('module', 'media')
+ ->condition('type', $type)
+ ->condition('id', $entity_id)
+ ->execute();
+}
+
+/**
+ * Implements hook_field_attach_delete_revision().
+ *
+ * @param type $entity_type
+ * @param type $entity
+ */
+function media_wysiwyg_field_attach_delete_revision($entity_type, $entity) {
+ list($entity_id) = entity_extract_ids($entity_type, $entity);
+ $files = media_wysiwyg_entity_field_count_files($entity_type, $entity);
+ foreach ($files as $fid => $count) {
+ if ($file = file_load($fid)) {
+ file_usage_delete($file, 'media', $entity_type , $entity_id, $count);
+ }
+ }
+}
+
+/**
+ * Implements hook_entity_dependencies().
+ */
+function media_wysiwyg_entity_dependencies($entity, $entity_type) {
+ // Go through all the entity's text fields and add a dependency on any files
+ // that are referenced there.
+ $dependencies = array();
+ foreach (media_wysiwyg_filter_parse_from_fields($entity_type, $entity) as $file_reference) {
+ $dependencies[] = array('type' => 'file', 'id' => $file_reference['fid']);
+ }
+ return $dependencies;
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc b/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc
new file mode 100644
index 000000000..73d6b5ff4
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc
@@ -0,0 +1,372 @@
+<?php
+
+/**
+ * @file
+ * Functions related to the WYSIWYG editor and the media input filter.
+ */
+
+define('MEDIA_WYSIWYG_TOKEN_REGEX', '/\[\[.*?\]\]/s');
+
+/**
+ * Filter callback for media markup filter.
+ *
+ * @TODO check for security probably pass text through filter_xss
+ */
+function media_wysiwyg_filter($text) {
+ $text = preg_replace_callback(MEDIA_WYSIWYG_TOKEN_REGEX, 'media_wysiwyg_token_to_markup', $text);
+ return $text;
+}
+
+/**
+ * Parses the contents of a CSS declaration block.
+ *
+ * @param string $declarations
+ * One or more CSS declarations delimited by a semicolon. The same as a CSS
+ * declaration block (see http://www.w3.org/TR/CSS21/syndata.html#rule-sets),
+ * but without the opening and closing curly braces. Also the same as the
+ * value of an inline HTML style attribute.
+ *
+ * @return array
+ * A keyed array. The keys are CSS property names, and the values are CSS
+ * property values.
+ */
+function media_wysiwyg_parse_css_declarations($declarations) {
+ $properties = array();
+ foreach (array_map('trim', explode(";", $declarations)) as $declaration) {
+ if ($declaration != '') {
+ list($name, $value) = array_map('trim', explode(':', $declaration, 2));
+ $properties[strtolower($name)] = $value;
+ }
+ }
+ return $properties;
+}
+
+/**
+ * Replace callback to convert a media file tag into HTML markup.
+ *
+ * @param string $match
+ * Takes a match of tag code
+ * @param bool $wysiwyg
+ * Set to TRUE if called from within the WYSIWYG text area editor.
+ *
+ * @return string
+ * The HTML markup representation of the tag, or an empty string on failure.
+ *
+ * @see media_wysiwyg_get_file_without_label()
+ * @see hook_media_wysiwyg_token_to_markup_alter()
+ */
+function media_wysiwyg_token_to_markup($match, $wysiwyg = FALSE) {
+ static $recursion_stop;
+ $settings = array();
+ $match = str_replace("[[", "", $match);
+ $match = str_replace("]]", "", $match);
+ $tag = $match[0];
+
+ try {
+ if (!is_string($tag)) {
+ throw new Exception('Unable to find matching tag');
+ }
+
+ $tag_info = drupal_json_decode($tag);
+
+ if (!isset($tag_info['fid'])) {
+ throw new Exception('No file Id');
+ }
+
+ // Ensure the 'link_text' key is always defined.
+ if (!isset($tag_info['link_text'])) {
+ $tag_info['link_text'] = NULL;
+ }
+
+ // Ensure a valid view mode is being requested.
+ if (!isset($tag_info['view_mode'])) {
+ $tag_info['view_mode'] = variable_get('media_wysiwyg_wysiwyg_default_view_mode', 'full');
+ }
+ elseif ($tag_info['view_mode'] != 'default') {
+ $file_entity_info = entity_get_info('file');
+ if (!in_array($tag_info['view_mode'], array_keys($file_entity_info['view modes']))) {
+ // Media 1.x defined some old view modes that have been superseded by
+ // more semantically named ones in File Entity. The media_update_7203()
+ // function updates field settings that reference the old view modes,
+ // but it's impractical to update all text content, so adjust
+ // accordingly here.
+ static $view_mode_updates = array(
+ 'media_preview' => 'preview',
+ 'media_small' => 'teaser',
+ 'media_large' => 'full',
+ );
+ if (isset($view_mode_updates[$tag_info['view_mode']])) {
+ $tag_info['view_mode'] = $view_mode_updates[$tag_info['view_mode']];
+ }
+ else {
+ throw new Exception('Invalid view mode');
+ }
+ }
+ }
+
+ $file = file_load($tag_info['fid']);
+ if (!$file) {
+ throw new Exception('Could not load media object');
+ }
+ // Check if we've got a recursion. Happens because a file_load() may
+ // triggers file_entity_is_page() which then again triggers a file load.
+ if (isset($recursion_stop[$file->fid])) {
+ return '';
+ }
+ $recursion_stop[$file->fid] = TRUE;
+
+ $tag_info['file'] = $file;
+
+ // The class attributes is a string, but drupal requires it to be
+ // an array, so we fix it here.
+ if (!empty($tag_info['attributes']['class'])) {
+ $tag_info['attributes']['class'] = explode(" ", $tag_info['attributes']['class']);
+ }
+
+ // Grab the potentially overrided fields from the file.
+ $fields = media_wysiwyg_filter_field_parser($tag_info);
+ foreach ($fields as $key => $value) {
+ $file->{$key} = $value;
+ }
+
+ $attributes = is_array($tag_info['attributes']) ? $tag_info['attributes'] : array();
+ $attribute_whitelist = variable_get('media_wysiwyg_wysiwyg_allowed_attributes', _media_wysiwyg_wysiwyg_allowed_attributes_default());
+ $settings['attributes'] = array_intersect_key($attributes, array_flip($attribute_whitelist));
+ $settings['fields'] = $fields;
+
+ if (!empty($tag_info['attributes']) && is_array($tag_info['attributes'])) {
+ $settings['attributes'] = array_intersect_key($tag_info['attributes'], array_flip($attribute_whitelist));
+ $settings['fields'] = $fields;
+
+ // Many media formatters will want to apply width and height independently
+ // of the style attribute or the corresponding HTML attributes, so pull
+ // these two out into top-level settings. Different WYSIWYG editors have
+ // different behavior with respect to whether they store user-specified
+ // dimensions in the HTML attributes or the style attribute - check both.
+ // Per http://www.w3.org/TR/html5/the-map-element.html#attr-dim-width, the
+ // HTML attributes are merely hints: CSS takes precedence.
+ if (isset($settings['attributes']['style'])) {
+ $css_properties = media_wysiwyg_parse_css_declarations($settings['attributes']['style']);
+ foreach (array('width', 'height') as $dimension) {
+ if (isset($css_properties[$dimension]) && substr($css_properties[$dimension], -2) == 'px') {
+ $settings[$dimension] = substr($css_properties[$dimension], 0, -2);
+ }
+ elseif (isset($settings['attributes'][$dimension])) {
+ $settings[$dimension] = $settings['attributes'][$dimension];
+ }
+ }
+ }
+ }
+ }
+ catch (Exception $e) {
+ watchdog('media', 'Unable to render media from %tag. Error: %error', array('%tag' => $tag, '%error' => $e->getMessage()));
+ return '';
+ }
+
+ // If the tag has link text stored with it, override the filename with it for
+ // the rest of this function, so that if the file is themed as a link, the
+ // desired text will be used (see, for example, theme_file_link()).
+ // @todo: Try to find a less hacky way to do this.
+ if (isset($tag_info['link_text'])) {
+ // The link text will have characters such as "&" encoded for HTML, but the
+ // filename itself needs the raw value when it is used to build the link,
+ // in order to avoid double encoding.
+ $file->filename = decode_entities($tag_info['link_text']);
+ }
+
+ if ($wysiwyg) {
+ $settings['wysiwyg'] = $wysiwyg;
+ // If sending markup to a WYSIWYG, we need to pass the file information so
+ // that an inline macro can be generated when the WYSIWYG is detached.
+ // The WYSIWYG plugin is expecting this information in the
+ // Drupal.settings.mediaDataMap variable.
+ $element = media_wysiwyg_get_file_without_label($file, $tag_info['view_mode'], $settings);
+ $data = array(
+ 'type' => 'media',
+ 'fid' => $file->fid,
+ 'view_mode' => $tag_info['view_mode'],
+ 'link_text' => $tag_info['link_text'],
+ );
+ drupal_add_js(array('mediaDataMap' => array($file->fid => $data)), 'setting');
+ $element['#attributes']['data-fid'] = $file->fid;
+ $element['#attributes']['data-media-element'] = '1';
+ $element['#attributes']['class'][] = 'media-element';
+ }
+ else {
+ // Display the field elements.
+ $element = array();
+ // Render the file entity, for sites using the file_entity rendering method.
+ if (variable_get('media_wysiwyg_default_render', 'file_entity') == 'file_entity') {
+ $element['content'] = file_view($file, $tag_info['view_mode']);
+ }
+ $element['content']['file'] = media_wysiwyg_get_file_without_label($file, $tag_info['view_mode'], $settings);
+ // Overwrite or set the file #alt attribute if it has been set in this
+ // instance.
+ if (!empty($element['content']['file']['#attributes']['alt'])) {
+ $element['content']['file']['#alt'] = $element['content']['file']['#attributes']['alt'];
+ }
+ // Overwrite or set the file #title attribute if it has been set in this
+ // instance.
+ if (!empty($element['content']['file']['#attributes']['title'])) {
+ $element['content']['file']['#title'] = $element['content']['file']['#attributes']['title'];
+ }
+ // For sites using the legacy field_attach rendering method, attach fields.
+ if (variable_get('media_wysiwyg_default_render', 'file_entity') == 'field_attach') {
+ field_attach_prepare_view('file', array($file->fid => $file), $tag_info['view_mode']);
+ entity_prepare_view('file', array($file->fid => $file));
+ $element['content'] += field_attach_view('file', $file, $tag_info['view_mode']);
+ }
+ if (count(element_children($element['content'])) > 1) {
+ // Add surrounding divs to group them together.
+ // We dont want divs when there are no additional fields to allow files
+ // to display inline with text, without breaking p tags.
+ $element['content']['#type'] = 'container';
+ $element['content']['#attributes']['class'] = array(
+ 'media',
+ 'media-element-container',
+ 'media-' . $element['content']['file']['#view_mode']
+ );
+ }
+
+ // Conditionally add a pre-render if the media filter output is be cached.
+ $filters = filter_get_filters();
+ if (!isset($filters['media_filter']['cache']) || $filters['media_filter']['cache']) {
+ $element['#pre_render'][] = 'media_wysiwyg_pre_render_cached_filter';
+ }
+ }
+ drupal_alter('media_wysiwyg_token_to_markup', $element, $tag_info, $settings);
+ $output = drupal_render($element);
+ unset($recursion_stop[$file->fid]);
+ return $output;
+}
+
+/**
+ * Parse the field array from the collapsed AJAX string.
+ */
+function media_wysiwyg_filter_field_parser($tag_info) {
+ $fields = array();
+ if (isset($tag_info['fields'])) {
+ foreach($tag_info['fields'] as $field_name => $field_value) {
+ if (strpos($field_name, 'field_') === 0) {
+ $parsed_field = explode('[', str_replace(']', '', $field_name));
+ $ref = &$fields;
+
+ // Each key of the field needs to be the child of the previous key.
+ foreach ($parsed_field as $key) {
+ if (!isset($ref[$key])) {
+ $ref[$key] = array();
+ }
+ $ref = &$ref[$key];
+ }
+
+ // The value should be set at the deepest level.
+ // Fields that use rich-text markup will be urlencoded.
+ $ref = urldecode($field_value);
+ }
+ }
+ }
+ return $fields;
+}
+
+/**
+ * Creates map of inline media tags.
+ *
+ * Generates an array of [inline tags] => <html> to be used in filter
+ * replacement and to add the mapping to JS.
+ *
+ * @param string $text
+ * The String containing text and html markup of textarea
+ *
+ * @return array
+ * An associative array with tag code as key and html markup as the value.
+ *
+ * @see media_process_form()
+ * @see media_token_to_markup()
+ */
+function _media_wysiwyg_generate_tagMap($text) {
+ // Making $tagmap static as this function is called many times and
+ // adds duplicate markup for each tag code in Drupal.settings JS,
+ // so in media_process_form it adds something like tagCode:<markup>,
+ // <markup> and when we replace in attach see two duplicate images
+ // for one tagCode. Making static would make function remember value
+ // between function calls. Since media_process_form is multiple times
+ // with same form, this function is also called multiple times.
+ static $tagmap = array();
+ preg_match_all("/\[\[.*?\]\]/s", $text, $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ // We see if tagContent is already in $tagMap, if not we add it
+ // to $tagmap. If we return an empty array, we break embeddings of the same
+ // media multiple times.
+ if (empty($tagmap[$match[0]])) {
+ // @TODO: Total HACK, but better than nothing.
+ // We should find a better way of cleaning this up.
+ if ($markup_for_media = media_wysiwyg_token_to_markup($match, TRUE)) {
+ $tagmap[$match[0]] = $markup_for_media;
+ }
+ else {
+ $missing = file_create_url(drupal_get_path('module', 'media') . '/images/icons/default/image-x-generic.png');
+ $tagmap[$match[0]] = '<div><img src="' . $missing . '" width="100px" height="100px"/></div>';
+ }
+ }
+ }
+ return $tagmap;
+}
+
+/**
+ * Return a list of view modes allowed for a file embedded in the WYSIWYG.
+ *
+ * @param object $file
+ * A file entity.
+ *
+ * @return array
+ * An array of view modes that can be used on the file when embedded in the
+ * WYSIWYG.
+ */
+function media_wysiwyg_get_wysiwyg_allowed_view_modes($file) {
+ $enabled_view_modes = &drupal_static(__FUNCTION__, array());
+
+ // @todo Add more caching for this.
+ if (!isset($enabled_view_modes[$file->type])) {
+ $enabled_view_modes[$file->type] = array();
+
+ // Add the default view mode by default.
+ $enabled_view_modes[$file->type]['default'] = array('label' => t('Default'), 'custom settings' => TRUE);
+
+ $entity_info = entity_get_info('file');
+ $view_mode_settings = field_view_mode_settings('file', $file->type);
+ foreach ($entity_info['view modes'] as $view_mode => $view_mode_info) {
+ // Do not show view modes that don't have their own settings and will
+ // only fall back to the default view mode.
+ if (empty($view_mode_settings[$view_mode]['custom_settings'])) {
+ continue;
+ }
+
+ // Don't present the user with an option to choose a view mode in which
+ // the file is hidden.
+ $extra_fields = field_extra_fields_get_display('file', $file->type, $view_mode);
+ if (empty($extra_fields['file']['visible'])) {
+ continue;
+ }
+
+ // Add the view mode to the list of enabled view modes.
+ $enabled_view_modes[$file->type][$view_mode] = $view_mode_info;
+ }
+ }
+
+ $view_modes = $enabled_view_modes[$file->type];
+ drupal_alter('media_wysiwyg_wysiwyg_allowed_view_modes', $view_modes, $file);
+ return $view_modes;
+}
+
+/**
+ * #pre_render callback: Modify the element if the render cache is filtered.
+ */
+function media_wysiwyg_pre_render_cached_filter($element) {
+ // Remove contextual links since they are not compatible with cached filtered
+ // text.
+ if (isset($element['content']['#contextual_links'])) {
+ unset($element['content']['#contextual_links']);
+ }
+
+ return $element;
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.pages.inc b/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.pages.inc
new file mode 100644
index 000000000..72653b8ff
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.pages.inc
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Common pages for the Media WYSIWYG module.
+ */
+
+/**
+ * Form callback used when embedding media.
+ *
+ * Allows the user to pick a format for their media file.
+ * Can also have additional params depending on the media type.
+ */
+function media_wysiwyg_format_form($form, &$form_state, $file) {
+ $form_state['file'] = $file;
+
+ // Allow for overrides to the fields.
+ $query_fields = isset($_GET['fields']) ? drupal_json_decode($_GET['fields']) : array();
+ $fields = media_wysiwyg_filter_field_parser(array('fields' => $query_fields), $file);
+
+ $view_modes = media_wysiwyg_get_wysiwyg_allowed_view_modes($file);
+ $formats = $options = array();
+ foreach ($view_modes as $view_mode => $view_mode_info) {
+ // @TODO: Display more verbose information about which formatter and what it
+ // does.
+ $options[$view_mode] = $view_mode_info['label'];
+ $element = media_wysiwyg_get_file_without_label($file, $view_mode, array('wysiwyg' => TRUE));
+
+ // Make a pretty name out of this.
+ $formats[$view_mode] = drupal_render($element);
+ }
+
+ // Add the previews back into the form array so they can be altered.
+ $form['#formats'] = &$formats;
+
+ if (!count($formats)) {
+ throw new Exception('Unable to continue, no available formats for displaying media.');
+ return;
+ }
+
+ // Allow for overrides to the display format.
+ $default_view_mode = is_array($query_fields) && isset($query_fields['format']) ? $query_fields['format'] : variable_get('media_wysiwyg_wysiwyg_default_view_mode', 'full');
+ if (!isset($formats[$default_view_mode])) {
+ $default_view_mode = key($formats);
+ }
+
+ // Add the previews by reference so that they can easily be altered by
+ // changing $form['#formats'].
+ $settings['media']['formatFormFormats'] = &$formats;
+ $form['#attached']['js'][] = array('data' => $settings, 'type' => 'setting');
+
+ // Add the required libraries, JavaScript and CSS for the form.
+ $form['#attached']['library'][] = array('media', 'media_base');
+ $form['#attached']['library'][] = array('system', 'form');
+ $form['#attached']['css'][] = drupal_get_path('module', 'media_wysiwyg') . '/css/media_wysiwyg.css';
+ $form['#attached']['js'][] = drupal_get_path('module', 'media_wysiwyg') . '/js/media_wysiwyg.format_form.js';
+
+ $form['title'] = array(
+ '#markup' => t('Embedding %filename', array('%filename' => $file->filename)),
+ );
+
+ $preview = media_get_thumbnail_preview($file);
+
+ $form['preview'] = array(
+ '#type' => 'markup',
+ '#title' => check_plain(basename($file->uri)),
+ '#markup' => drupal_render($preview),
+ );
+
+ // These will get passed on to WYSIWYG.
+ $form['options'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('options'),
+ );
+
+ $form['options']['format'] = array(
+ '#type' => 'select',
+ '#title' => t('Display as'),
+ '#options' => $options,
+ '#default_value' => $default_view_mode,
+ '#description' => t('Choose the type of display you would like for this
+ file. Please be aware that files may display differently than they do when
+ they are inserted into an editor.')
+ );
+
+ // Add fields from the file, so that we can override them if necessary.
+ $form['options']['fields'] = array();
+ foreach ($fields as $field_name => $field_value) {
+ $file->{$field_name} = $field_value;
+ }
+ field_attach_form('file', $file, $form['options']['fields'], $form_state);
+ $instance = field_info_instances('file', $file->type);
+ foreach ($instance as $field_name => $field_value) {
+ if (isset($instance[$field_name]['settings']) && isset($instance[$field_name]['settings']['wysiwyg_override']) && !$instance[$field_name]['settings']['wysiwyg_override']) {
+ unset($form['options']['fields'][$field_name]);
+ }
+ }
+
+ // Similar to a form_alter, but we want this to run first so that
+ // media.types.inc can add the fields specific to a given type (like alt tags
+ // on media). If implemented as an alter, this might not happen, making other
+ // alters not be able to work on those fields.
+ // @todo: We need to pass in existing values for those attributes.
+ drupal_alter('media_wysiwyg_format_form_prepare', $form, $form_state, $file);
+
+ if (!element_children($form['options'])) {
+ $form['options']['#attributes'] = array('style' => 'display:none');
+ }
+
+ return $form;
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.uuid.inc b/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.uuid.inc
new file mode 100644
index 000000000..3d228e3d0
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/includes/media_wysiwyg.uuid.inc
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Functions related to adding UUID support to embedded media.
+ */
+
+/**
+ * Implements hook_entity_uuid_load().
+ */
+function media_wysiwyg_entity_uuid_load(&$entities, $entity_type) {
+ // Go through all the entity's text fields and replace file IDs in media
+ // tokens with the corresponding UUID.
+ foreach ($entities as $entity) {
+ media_wysiwyg_filter_replace_tokens_in_all_text_fields($entity_type, $entity, 'media_wysiwyg_token_fid_to_uuid');
+ }
+}
+
+/**
+ * Implements hook_entity_uuid_presave().
+ */
+function media_wysiwyg_entity_uuid_presave(&$entity, $entity_type) {
+ // Go through all the entity's text fields and replace UUIDs in media tokens
+ // with the corresponding file ID.
+ media_wysiwyg_filter_replace_tokens_in_all_text_fields($entity_type, $entity, 'media_wysiwyg_token_uuid_to_fid');
+}
+
+/**
+ * Replaces media tokens in an entity's text fields, using the specified callback function.
+ */
+function media_wysiwyg_filter_replace_tokens_in_all_text_fields($entity_type, $entity, $callback) {
+ $text_field_names = media_wysiwyg_filter_fields_with_text_filtering($entity_type, $entity);
+ foreach ($text_field_names as $field_name) {
+ if (!empty($entity->{$field_name})) {
+ $field = field_info_field($field_name);
+ $all_languages = field_available_languages($entity_type, $field);
+ $field_languages = array_intersect($all_languages, array_keys($entity->{$field_name}));
+ foreach ($field_languages as $language) {
+ if (!empty($entity->{$field_name}[$language])) {
+ foreach ($entity->{$field_name}[$language] as &$item) {
+ $item['value'] = preg_replace_callback(MEDIA_WYSIWYG_TOKEN_REGEX, $callback, $item['value']);
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Callback to replace file IDs with UUIDs in a media token.
+ */
+function media_wysiwyg_token_fid_to_uuid($matches) {
+ return _media_wysiwyg_token_uuid_replace($matches, 'entity_get_uuid_by_id');
+}
+
+/**
+ * Callback to replace UUIDs with file IDs in a media token.
+ */
+function media_wysiwyg_token_uuid_to_fid($matches) {
+ return _media_wysiwyg_token_uuid_replace($matches, 'entity_get_id_by_uuid');
+}
+
+/**
+ * Helper function to replace UUIDs with file IDs or vice versa.
+ *
+ * @param array $matches
+ * An array of matches for media tokens, from a preg_replace_callback()
+ * callback function.
+ * @param string $entity_uuid_function
+ * Either 'entity_get_uuid_by_id' (to replace file IDs with UUIDs in the
+ * token) or 'entity_get_id_by_uuid' (to replace UUIDs with file IDs).
+ *
+ * @return string
+ * A string representing the JSON-encoded token, with the appropriate
+ * replacement between file IDs and UUIDs.
+ */
+function _media_wysiwyg_token_uuid_replace($matches, $entity_uuid_function) {
+ $tag = $matches[0];
+ $tag = str_replace(array('[[', ']]'), '', $tag);
+ $tag_info = drupal_json_decode($tag);
+ if (isset($tag_info['fid'])) {
+ if ($new_ids = $entity_uuid_function('file', array($tag_info['fid']))) {
+ $new_id = reset($new_ids);
+ $tag_info['fid'] = $new_id;
+ }
+ }
+ return '[[' . drupal_json_encode($tag_info) . ']]';
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/js/media_wysiwyg.filter.js b/sites/all/modules/media/modules/media_wysiwyg/js/media_wysiwyg.filter.js
new file mode 100644
index 000000000..efccd59c0
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/js/media_wysiwyg.filter.js
@@ -0,0 +1,287 @@
+/**
+ * @file
+ * File with utilities to handle media in html editing.
+ */
+(function ($) {
+
+ Drupal.media = Drupal.media || {};
+ /**
+ * Utility to deal with media tokens / placeholders.
+ */
+ Drupal.media.filter = {
+ /**
+ * Replaces media tokens with the placeholders for html editing.
+ * @param content
+ */
+ replaceTokenWithPlaceholder: function(content) {
+ Drupal.media.filter.ensure_tagmap();
+ var matches = content.match(/\[\[.*?\]\]/g);
+
+ if (matches) {
+ for (var i = 0; i < matches.length; i++) {
+ var match = matches[i];
+ if (match.indexOf('"type":"media"') == -1) {
+ continue;
+ }
+
+ // Check if the macro exists in the tagmap. This ensures backwards
+ // compatibility with existing media and is moderately more efficient
+ // than re-building the element.
+ var media = Drupal.settings.tagmap[match];
+ var media_json = match.replace('[[', '').replace(']]', '');
+
+ // Ensure that the media JSON is valid.
+ try {
+ var media_definition = JSON.parse(media_json);
+ }
+ catch (err) {
+ // @todo: error logging.
+ // Content should be returned to prevent an empty editor.
+ return content;
+ }
+
+ // Re-build the media if the macro has changed from the tagmap.
+ if (!media && media_definition.fid) {
+ Drupal.media.filter.ensureSourceMap();
+ var source = Drupal.settings.mediaSourceMap[media_definition.fid];
+ media = document.createElement(source.tagName);
+ media.src = source.src;
+ media.innerHTML = source.innerHTML;
+ }
+
+ // Apply attributes.
+ var element = Drupal.media.filter.create_element(media, media_definition);
+ var markup = Drupal.media.filter.outerHTML(element);
+
+ // Use split and join to replace all instances of macro with markup.
+ content = content.split(match).join(markup);
+ }
+ }
+
+ return content;
+ },
+
+ /**
+ * Replaces media elements with tokens.
+ *
+ * @param content (string)
+ * The markup within the wysiwyg instance.
+ */
+ replacePlaceholderWithToken: function(content) {
+ Drupal.media.filter.ensure_tagmap();
+
+ // Rewrite the tagmap in case any of the macros have changed.
+ Drupal.settings.tagmap = {};
+
+ // Replace all media placeholders with their JSON macro representations.
+ //
+ // There are issues with using jQuery to parse the WYSIWYG content (see
+ // http://drupal.org/node/1280758), and parsing HTML with regular
+ // expressions is a terrible idea (see http://stackoverflow.com/a/1732454/854985)
+ //
+ // WYSIWYG editors act wacky with complex placeholder markup anyway, so an
+ // image is the most reliable and most usable anyway: images can be moved by
+ // dragging and dropping, and can be resized using interactive handles.
+ //
+ // Media requests a WYSIWYG place holder rendering of the file by passing
+ // the wysiwyg => 1 flag in the settings array when calling
+ // media_get_file_without_label().
+ //
+ // Finds the media-element class.
+ var classRegex = 'class=[\'"][^\'"]*?media-element';
+ // Image tag with the media-element class.
+ var regex = '<img[^>]+' + classRegex + '[^>]*?>';
+ // Or a span with the media-element class (used for documents).
+ // \S\s catches any character, including a linebreak; JavaScript does not
+ // have a dotall flag.
+ regex += '|<span[^>]+' + classRegex + '[^>]*?>[\\S\\s]+?</span>';
+ var matches = content.match(RegExp(regex, 'gi'));
+ if (matches) {
+ for (i = 0; i < matches.length; i++) {
+ markup = matches[i];
+ macro = Drupal.media.filter.create_macro($(markup));
+ Drupal.settings.tagmap[macro] = markup;
+ content = content.replace(markup, macro);
+ }
+ }
+
+ return content;
+ },
+
+ /**
+ * Serializes file information as a url-encoded JSON object and stores it
+ * as a data attribute on the html element.
+ *
+ * @param html (string)
+ * A html element to be used to represent the inserted media element.
+ * @param info (object)
+ * A object containing the media file information (fid, view_mode, etc).
+ */
+ create_element: function (html, info) {
+ if ($('<div>').append(html).text().length === html.length) {
+ // Element is not an html tag. Surround it in a span element so we can
+ // pass the file attributes.
+ html = '<span>' + html + '</span>';
+ }
+ var element = $(html);
+
+ // Parse out link wrappers. They will be re-applied when the image is
+ // rendered on the front-end.
+ if (element.is('a')) {
+ element = element.children();
+ }
+
+ // Move attributes from the file info array to the placeholder element.
+ if (info.attributes) {
+ $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
+ if (info.attributes[a]) {
+ element.attr(a, info.attributes[a]);
+ }
+ });
+ delete(info.attributes);
+
+ // Store information to rebuild the element later, if necessary.
+ Drupal.media.filter.ensureSourceMap();
+ Drupal.settings.mediaSourceMap[info.fid] = {
+ tagName: element[0].tagName,
+ src: element[0].src,
+ innerHTML: element[0].innerHTML
+ }
+ }
+
+ info.type = info.type || "media";
+
+ // Store the data in the data map.
+ Drupal.media.filter.ensureDataMap();
+ Drupal.settings.mediaDataMap[info.fid] = info;
+
+ // Store the fid in the DOM to retrieve the data from the info map.
+ element.attr('data-fid', info.fid);
+
+ // Add data-media-element attribute so we can find the markup element later.
+ element.attr('data-media-element', '1')
+
+ var classes = ['media-element'];
+ if (info.view_mode) {
+ classes.push('file-' + info.view_mode.replace(/_/g, '-'));
+ }
+ element.addClass(classes.join(' '));
+
+ // Apply link_text if present.
+ if (info.link_text) {
+ $('a', element).html(info.link_text);
+ }
+
+ return element;
+ },
+
+ /**
+ * Create a macro representation of the inserted media element.
+ *
+ * @param element (jQuery object)
+ * A media element with attached serialized file info.
+ */
+ create_macro: function (element) {
+ var file_info = Drupal.media.filter.extract_file_info(element);
+ if (file_info) {
+ if (typeof file_info.link_text == 'string') {
+ // Make sure the link_text-html-tags are properly escaped.
+ file_info.link_text = file_info.link_text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ }
+ return '[[' + JSON.stringify(file_info) + ']]';
+ }
+ return false;
+ },
+
+ /**
+ * Extract the file info from a WYSIWYG placeholder element as JSON.
+ *
+ * @param element (jQuery object)
+ * A media element with associated file info via a file id (fid).
+ */
+ extract_file_info: function (element) {
+ var fid, file_info, value;
+
+ if (fid = element.data('fid')) {
+ Drupal.media.filter.ensureDataMap();
+
+ if (file_info = Drupal.settings.mediaDataMap[fid]) {
+ file_info.attributes = {};
+
+ $.each(Drupal.settings.media.wysiwyg_allowed_attributes, function(i, a) {
+ if (value = element.attr(a)) {
+ // Replace &quot; by \" to avoid error with JSON format.
+ if (typeof value == 'string') {
+ value = value.replace('&quot;', '\\"');
+ }
+ file_info.attributes[a] = value;
+ }
+ });
+
+ // Extract the link text, if there is any.
+ file_info.link_text = element.find('a').html();
+ }
+ }
+
+ return file_info;
+ },
+
+ /**
+ * Gets the HTML content of an element.
+ *
+ * @param element (jQuery object)
+ */
+ outerHTML: function (element) {
+ return element[0].outerHTML || $('<div>').append(element.eq(0).clone()).html();
+ },
+
+ /**
+ * Gets the wrapped HTML content of an element to insert into the wysiwyg.
+ *
+ * It also registers the element in the tag map so that the token
+ * replacement works.
+ *
+ * @param element (jQuery object) The element to insert.
+ *
+ * @see Drupal.media.filter.replacePlaceholderWithToken()
+ */
+ getWysiwygHTML: function (element) {
+ // Create the markup and the macro.
+ var markup = Drupal.media.filter.outerHTML(element),
+ macro = Drupal.media.filter.create_macro(element);
+
+ // Store macro/markup in the tagmap.
+ Drupal.media.filter.ensure_tagmap();
+ Drupal.settings.tagmap[macro] = markup;
+
+ // Return the html code to insert in an editor and use it with
+ // replacePlaceholderWithToken()
+ return markup;
+ },
+
+ /**
+ * Ensures the src tracking has been initialized and returns it.
+ */
+ ensureSourceMap: function() {
+ Drupal.settings.mediaSourceMap = Drupal.settings.mediaSourceMap || {};
+ return Drupal.settings.mediaSourceMap;
+ },
+
+ /**
+ * Ensures the data tracking has been initialized and returns it.
+ */
+ ensureDataMap: function() {
+ Drupal.settings.mediaDataMap = Drupal.settings.mediaDataMap || {};
+ return Drupal.settings.mediaDataMap;
+ },
+
+ /**
+ * Ensures the tag map has been initialized and returns it.
+ */
+ ensure_tagmap: function () {
+ Drupal.settings.tagmap = Drupal.settings.tagmap || {};
+ return Drupal.settings.tagmap;
+ }
+ }
+
+})(jQuery);
diff --git a/sites/all/modules/media/modules/media_wysiwyg/js/media_wysiwyg.format_form.js b/sites/all/modules/media/modules/media_wysiwyg/js/media_wysiwyg.format_form.js
new file mode 100644
index 000000000..4345a345f
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/js/media_wysiwyg.format_form.js
@@ -0,0 +1,69 @@
+
+/**
+ * @file
+ * Attach behaviors to formatter radio select when selecting a media's display
+ * formatter.
+ */
+
+(function ($) {
+namespace('Drupal.media.formatForm');
+
+Drupal.media.mediaFormatSelected = {};
+
+Drupal.behaviors.mediaFormatForm = {
+ attach: function (context, settings) {
+ // Add the "Submit" button inside the IFRAME that trigger the behavior of
+ // the hidden "OK" button that is outside the IFRAME.
+ // @see Drupal.media.browser.validateButtons() for more details.
+
+ // @note I think this should be handled in media.browser.js in
+ // Drupal.media.browser.validateButtons but I'm not sure how crufty this
+ // particular functionality is. We should evaluate if it is still needed.
+
+ // @TODO can these be added to the content being displayed via form_alter?
+
+ // Adding the buttons should only be done once in order to prevent multiple
+ // buttons from being added if part of the form is updated via AJAX
+ $('#media-wysiwyg-format-form').once('format', function() {
+ $('<a class="button fake-ok">' + Drupal.t('Submit') + '</a>').appendTo($('#media-wysiwyg-format-form')).bind('click', Drupal.media.formatForm.submit);
+ });
+ }
+};
+
+Drupal.media.formatForm.getOptions = function () {
+ // Get all the values
+ var ret = {};
+
+ $.each($('#media-wysiwyg-format-form .fieldset-wrapper *').serializeArray(), function (i, field) {
+ ret[field.name] = field.value;
+
+ // When a field uses a WYSIWYG format, the value needs to be extracted.
+ if (field.name.match(/\[format\]/i)) {
+ field.name = field.name.replace(/\[format\]/i, '[value]');
+ field.key = 'edit-' + field.name.replace(/[_\[]/g, '-').replace(/[\]]/g, '');
+
+ if (Drupal.wysiwyg && Drupal.wysiwyg.instances[field.key]) {
+ // Retrieve the content from the WYSIWYG instance.
+ ret[field.name] = Drupal.wysiwyg.instances[field.key].getContent();
+
+ // Encode the content to play nicely within JSON.
+ ret[field.name] = encodeURIComponent(ret[field.name]);
+ }
+ }
+ });
+
+ return ret;
+};
+
+Drupal.media.formatForm.getFormattedMedia = function () {
+ var formatType = $("#edit-format").val();
+ return { type: formatType, options: Drupal.media.formatForm.getOptions(), html: Drupal.settings.media.formatFormFormats[formatType] };
+};
+
+Drupal.media.formatForm.submit = function () {
+ // @see Drupal.behaviors.mediaFormatForm.attach().
+ var buttons = $(parent.window.document.body).find('#mediaStyleSelector').parent('.ui-dialog').find('.ui-dialog-buttonpane button');
+ buttons[0].click();
+}
+
+})(jQuery);
diff --git a/sites/all/modules/media/modules/media_wysiwyg/js/wysiwyg-media.js b/sites/all/modules/media/modules/media_wysiwyg/js/wysiwyg-media.js
new file mode 100644
index 000000000..d694b85b0
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/js/wysiwyg-media.js
@@ -0,0 +1,186 @@
+
+/**
+ * @file
+ * Attach Media WYSIWYG behaviors.
+ */
+
+(function ($) {
+
+Drupal.media = Drupal.media || {};
+
+/**
+ * Register the plugin with WYSIWYG.
+ */
+Drupal.wysiwyg.plugins.media = {
+
+ /**
+ * Determine whether a DOM element belongs to this plugin.
+ *
+ * @param node
+ * A DOM element
+ */
+ isNode: function(node) {
+ return $(node).is('img[data-media-element]');
+ },
+
+ /**
+ * Execute the button.
+ *
+ * @param data
+ * An object containing data about the current selection:
+ * - format: 'html' when the passed data is HTML content, 'text' when the
+ * passed data is plain-text content.
+ * - node: When 'format' is 'html', the focused DOM element in the editor.
+ * - content: The textual representation of the focused/selected editor
+ * content.
+ * @param settings
+ * The plugin settings, as provided in the plugin's PHP include file.
+ * @param instanceId
+ * The ID of the current editor instance.
+ */
+ invoke: function (data, settings, instanceId) {
+ if (data.format == 'html') {
+ var insert = new InsertMedia(instanceId);
+ if (this.isNode(data.node)) {
+ // Change the view mode for already-inserted media.
+ var media_file = Drupal.media.filter.extract_file_info($(data.node));
+ insert.onSelect([media_file]);
+ }
+ else {
+ // Insert new media.
+ insert.prompt(settings.global);
+ }
+ }
+ },
+
+ /**
+ * Attach function, called when a rich text editor loads.
+ * This finds all [[tags]] and replaces them with the html
+ * that needs to show in the editor.
+ *
+ * This finds all JSON macros and replaces them with the HTML placeholder
+ * that will show in the editor.
+ */
+ attach: function (content, settings, instanceId) {
+ content = Drupal.media.filter.replaceTokenWithPlaceholder(content);
+ return content;
+ },
+
+ /**
+ * Detach function, called when a rich text editor detaches
+ */
+ detach: function (content, settings, instanceId) {
+ content = Drupal.media.filter.replacePlaceholderWithToken(content);
+ return content;
+ }
+};
+/**
+ * Defining InsertMedia object to manage the sequence of actions involved in
+ * inserting a media element into the WYSIWYG.
+ * Keeps track of the WYSIWYG instance id.
+ */
+var InsertMedia = function (instance_id) {
+ this.instanceId = instance_id;
+ return this;
+};
+
+InsertMedia.prototype = {
+ /**
+ * Prompt user to select a media item with the media browser.
+ *
+ * @param settings
+ * Settings object to pass on to the media browser.
+ * TODO: Determine if this is actually necessary.
+ */
+ prompt: function (settings) {
+ Drupal.media.popups.mediaBrowser($.proxy(this, 'onSelect'), settings);
+ },
+
+ /**
+ * On selection of a media item, display item's display configuration form.
+ */
+ onSelect: function (media_files) {
+ this.mediaFile = media_files[0];
+ Drupal.media.popups.mediaStyleSelector(this.mediaFile, $.proxy(this, 'insert'), {});
+ },
+
+ /**
+ * When display config has been set, insert the placeholder markup into the
+ * wysiwyg and generate its corresponding json macro pair to be added to the
+ * tagmap.
+ */
+ insert: function (formatted_media) {
+ var element = Drupal.media.filter.create_element(formatted_media.html, {
+ fid: this.mediaFile.fid,
+ view_mode: formatted_media.type,
+ attributes: formatted_media.options,
+ fields: formatted_media.options
+ });
+ // Get the markup and register it for the macro / placeholder handling.
+ var markup = Drupal.media.filter.getWysiwygHTML(element);
+
+ // Insert placeholder markup into wysiwyg.
+ Drupal.wysiwyg.instances[this.instanceId].insert(markup);
+ }
+};
+
+/** Helper functions */
+
+/**
+ * Ensures the tag map has been initialized.
+ */
+function ensure_tagmap () {
+ return Drupal.media.filter.ensure_tagmap();
+}
+
+/**
+ * Serializes file information as a url-encoded JSON object and stores it as a
+ * data attribute on the html element.
+ *
+ * @param html (string)
+ * A html element to be used to represent the inserted media element.
+ * @param info (object)
+ * A object containing the media file information (fid, view_mode, etc).
+ *
+ * @deprecated
+ */
+function create_element (html, info) {
+ return Drupal.media.filter.create_element(html, info);
+}
+
+/**
+ * Create a macro representation of the inserted media element.
+ *
+ * @param element (jQuery object)
+ * A media element with attached serialized file info.
+ *
+ * @deprecated
+ */
+function create_macro (element) {
+ return Drupal.media.filter.create_macro(element);
+}
+
+/**
+ * Extract the file info from a WYSIWYG placeholder element as JSON.
+ *
+ * @param element (jQuery object)
+ * A media element with attached serialized file info.
+ *
+ * @deprecated
+ */
+function extract_file_info (element) {
+ return Drupal.media.filter.extract_file_info(element);
+}
+
+/**
+ * Gets the HTML content of an element.
+ *
+ * @param element (jQuery object)
+ *
+ * @deprecated
+ */
+function outerHTML (element) {
+ return Drupal.media.filter.outerHTML(element);
+}
+
+})(jQuery);
diff --git a/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.api.php b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.api.php
new file mode 100644
index 000000000..894e89be7
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.api.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Media WYSIWYG module.
+ */
+
+/**
+ * Alter a list of view modes allowed for a file embedded in the WYSIWYG.
+ *
+ * @param array $view_modes
+ * An array of view modes that can be used on the file when embedded in the
+ * WYSIWYG.
+ * @param object $file
+ * A file entity.
+ *
+ * @see media_get_wysiwyg_allowed_view_modes()
+ */
+function hook_media_wysiwyg_wysiwyg_allowed_view_modes_alter(&$view_modes, $file) {
+ $view_modes['default']['label'] = t('Display an unmodified version of the file');
+ unset($view_modes['preview']);
+}
+
+/**
+ * Alter the WYSIWYG view mode selection form.
+ *
+ * Similar to a form_alter, but runs first so that modules can add
+ * fields specific to a given file type (like alt tags on images) before alters
+ * begin to work on the fields.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param array $form_state
+ * An associative array containing the current state of the form.
+ * @param object $file
+ * A file entity.
+ *
+ * @see media_format_form()
+ */
+function hook_media_wysiwyg_format_form_prepare_alter(&$form, &$form_state, $file) {
+ $form['preview']['#access'] = FALSE;
+
+ $file = $form_state['file'];
+ $form['heading']['#markup'] = t('Embedding %filename of type %filetype', array('%filename' => $file->filename, '%filetype' => $file->type));
+}
+
+/**
+ * Alter the output generated by Media filter tags.
+ *
+ * @param array $element
+ * The renderable array of output generated for the filter tag.
+ * @param array $tag_info
+ * The filter tag converted into an associative array by
+ * media_token_to_markup() with the following elements:
+ * - 'fid': The ID of the media file being rendered.
+ * - 'file': The object from file_load() of the media file being rendered.
+ * - 'view_mode': The view mode being used to render the file.
+ * - 'attributes': An additional array of attributes that could be output
+ * with media_get_file_without_label().
+ * @param array $settings
+ * An additional array of settings.
+ * - 'wysiwyg': A boolean if the output is for the WYSIWYG preview or FALSE
+ * if for normal rendering.
+ *
+ * @see media_token_to_markup()
+ */
+function hook_media_wysiwyg_token_to_markup_alter(&$element, $tag_info, $settings) {
+ if (empty($settings['wysiwyg'])) {
+ $element['#attributes']['alt'] = t('This media has been output using the @mode view mode.', array('@mode' => $tag_info['view_mode']));
+ }
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.ckeditor.inc b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.ckeditor.inc
new file mode 100644
index 000000000..a00b15835
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.ckeditor.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Provides WYSIWYG integration for CKEditor.
+ */
+
+/**
+ * Implements hook_ckeditor_plugin_alter().
+ */
+function media_wysiwyg_ckeditor_plugin_alter(&$plugins) {
+ // Override the default CKEditor Media plugin.
+ $plugins['media'] = array(
+ 'name' => 'media',
+ 'desc' => t('Plugin for inserting images from Drupal media module'),
+ 'path' => '%base_path%' . drupal_get_path('module', 'media_wysiwyg') . '/wysiwyg_plugins/media_ckeditor/',
+ 'buttons' => array(
+ 'Media' => array(
+ 'icon' => 'images/icon.gif',
+ 'label' => 'Add media',
+ ),
+ ),
+ 'default' => 'f',
+ );
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.info b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.info
new file mode 100644
index 000000000..60191dea8
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.info
@@ -0,0 +1,23 @@
+name = Media WYSIWYG
+description = Adds support for embedding media using client-side WYSIWYG editors.
+package = Media
+core = 7.x
+
+dependencies[] = media
+
+test_dependencies[] = ckeditor
+test_dependencies[] = token
+test_dependencies[] = wysiwyg
+
+files[] = media_wysiwyg.test
+files[] = tests/media_wysiwyg.file_usage.test
+files[] = tests/media_wysiwyg.macro.test
+
+configure = admin/config/media/browser
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.install b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.install
new file mode 100644
index 000000000..a0a0d798d
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.install
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Media WYSIWYG module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function media_wysiwyg_uninstall() {
+ // Remove variables.
+ variable_del('media_wysiwyg_wysiwyg_title');
+ variable_del('media_wysiwyg_wysiwyg_icon_title');
+ variable_del('media_wysiwyg_wysiwyg_default_view_mode');
+ variable_del('media_wysiwyg_wysiwyg_upload_directory');
+ variable_del('media_wysiwyg_wysiwyg_allowed_types');
+ variable_del('media_wysiwyg_wysiwyg_allowed_attributes');
+ variable_del('media_wysiwyg_wysiwyg_browser_plugins');
+}
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function media_wysiwyg_update_dependencies() {
+ // Ensure the "access media browser" permission is granted to users before
+ // using it to grant the "use media wysiwyg" permission.
+ $dependencies['media_wysiwyg'][7201] = array(
+ 'media' => 7226,
+ );
+}
+
+/**
+ * Grant existing user access to new media wysiwyg permission.
+ */
+function media_wysiwyg_update_7201() {
+ $roles = user_roles(TRUE, 'access media browser');
+ foreach ($roles as $rid => $role) {
+ user_role_grant_permissions($rid, array('use media wysiwyg'));
+ }
+
+ return t('Use Media WYSIWYG permission was granted to: @roles.', array(
+ '@roles' => check_plain(implode(', ', $roles)),
+ ));
+}
+
+/**
+ * Use the legacy file entity rendering method for existing sites.
+ *
+ * Existing sites can change this setting at admin/config/media/browser.
+ */
+function media_wysiwyg_update_7202() {
+ variable_set('media_wysiwyg_default_render', 'field_attach');
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.module b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.module
new file mode 100644
index 000000000..162e60bb2
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.module
@@ -0,0 +1,371 @@
+<?php
+
+/**
+ * @file
+ * Primarily Drupal hooks.
+ */
+
+// Functions for tracking the file usage of [[inline tags]].
+require_once dirname(__FILE__) . '/includes/media_wysiwyg.file_usage.inc';
+
+// Functions for working with [[inline tags]] and wysiwyg editors.
+require_once dirname(__FILE__) . '/includes/media_wysiwyg.filter.inc';
+
+// Functions for UUID support to embedded media.
+require_once dirname(__FILE__) . '/includes/media_wysiwyg.uuid.inc';
+
+/**
+ * Implements hook_hook_info().
+ */
+function media_wysiwyg_hook_info() {
+ $hooks = array(
+ 'media_wysiwyg_token_to_markup_alter',
+ 'media_wysiwyg_allowed_view_modes_alter',
+ 'media_wysiwyg_format_form_prepare_alter',
+ );
+
+ return array_fill_keys($hooks, array('group' => 'media_wysiwyg'));
+}
+
+/**
+ * Implements hook_menu().
+ */
+function media_wysiwyg_menu() {
+ $items = array();
+
+ $items['media/%file/format-form'] = array(
+ 'title' => 'Style selector',
+ 'description' => 'Choose a format for a piece of media',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_wysiwyg_format_form', 1),
+ 'access callback' => 'media_wysiwyg_access',
+ 'access arguments' => array('view', 1),
+ 'file' => 'includes/media_wysiwyg.pages.inc',
+ 'theme callback' => 'media_dialog_get_theme_name',
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function media_wysiwyg_permission() {
+ return array(
+ 'use media wysiwyg' => array(
+ 'title' => t('Use Media WYSIWYG in an editor'),
+ // Marked restrict because the WYSIWYG forms generate image derivatives,
+ // which could lead to a DoS security vulnerability.
+ 'restrict access' => TRUE,
+ ),
+ );
+}
+
+/**
+ * Access callback for WYSIWYG Media.
+ */
+function media_wysiwyg_access($op, $file = NULL, $account = NULL) {
+ return user_access('use media wysiwyg', $account) && file_entity_access($op, $file, $account);
+}
+
+/**
+ * Implements hook_element_info_alter().
+ */
+function media_wysiwyg_element_info_alter(&$types) {
+ $types['text_format']['#pre_render'][] = 'media_wysiwyg_pre_render_text_format';
+}
+
+/**
+ * Builds a map of media tags in the element.
+ *
+ * Builds a map of the media tags in an element that are being rendered to their
+ * rendered HTML. The map is stored in JS, so we can transform them when the
+ * editor is being displayed.
+ */
+function media_wysiwyg_pre_render_text_format($element) {
+ // filter_process_format() copies properties to the expanded 'value' child
+ // element.
+ if (!isset($element['format'])) {
+ return $element;
+ }
+
+ $field = &$element['value'];
+ $settings = array(
+ 'field' => $field['#id'],
+ );
+
+ if (!isset($field['#value'])) {
+ return $element;
+ }
+
+ $tagmap = _media_wysiwyg_generate_tagMap($field['#value']);
+
+ if (isset($tagmap)) {
+ $element['#attached']['js'][] = array(
+ 'data' => array(
+ 'tagmap' => $tagmap,
+ ),
+ 'type' => 'setting',
+ );
+ }
+
+ // Load the media browser library.
+ $element['#attached']['library'][] = array('media', 'media_browser');
+ $element['#attached']['library'][] = array('media', 'media_browser_settings');
+
+ // Add wysiwyg-specific settings.
+ $settings = array('wysiwyg_allowed_attributes' => variable_get('media_wysiwyg_wysiwyg_allowed_attributes', _media_wysiwyg_wysiwyg_allowed_attributes_default()));
+ $element['#attached']['js'][] = array(
+ 'data' => array(
+ 'media' => $settings,
+ ),
+ 'type' => 'setting',
+ );
+
+ // Add filter handling.
+ $element['#attached']['js'][] = drupal_get_path('module', 'media_wysiwyg') . '/js/media_wysiwyg.filter.js';
+
+ // Add CKEditor-specific JS.
+ if (module_exists('ckeditor')) {
+ $element['#attached']['js'][] = array(
+ 'data' => drupal_get_path('module', 'media_wysiwyg') . '/wysiwyg_plugins/media_ckeditor/library.js',
+ 'type' => 'file',
+ 'scope' => 'footer',
+ 'weight' => -20,
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_wysiwyg_form_wysiwyg_profile_form_alter(&$form, &$form_state) {
+ // Add warnings if the media filter is disabled for the WYSIWYG's text format.
+ $form['buttons']['drupal']['media']['#element_validate'][] = 'media_wysiwyg_wysiwyg_button_element_validate';
+ $form['buttons']['drupal']['media']['#after_build'][] = 'media_wysiwyg_wysiwyg_button_element_validate';
+ form_load_include($form_state, 'inc', 'media_wysiwyg', 'wysiwyg_plugins/media');
+}
+
+/**
+ * Element validate callback for the media WYSIWYG button.
+ */
+function media_wysiwyg_wysiwyg_button_element_validate($element, &$form_state) {
+ if (!empty($element['#value'])) {
+ $format = filter_format_load($form_state['build_info']['args'][0]->format);
+ $filters = filter_list_format($format->format);
+ if (empty($filters['media_filter']->status)) {
+ form_error($element, t('The <em>Convert Media tags to markup</em> filter must be enabled for the <a href="@format-link">@format format</a> in order to use the Media browser WYSIWYG button.', array(
+ '@format-link' => url('admin/config/content/formats/' . $format->format, array('query' => array('destination' => $_GET['q']))),
+ '@format' => $format->name,
+ )));
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_wysiwyg_form_media_admin_config_browser_alter(&$form, &$form_state) {
+ $form['wysiwyg'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('WYSIWYG configuration'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ );
+ $form['wysiwyg']['media_wysiwyg_wysiwyg_browser_plugins'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Enabled browser plugins'),
+ '#options' => array(),
+ '#required' => FALSE,
+ '#default_value' => variable_get('media_wysiwyg_wysiwyg_browser_plugins', array()),
+ '#description' => t('If no plugins are selected, they will all be available.'),
+ );
+
+ $plugins = media_get_browser_plugin_info();
+
+ foreach ($plugins as $key => $plugin) {
+ $form['wysiwyg']['media_wysiwyg_wysiwyg_browser_plugins']['#options'][$key] = !empty($plugin['title']) ? $plugin['title'] : $key;
+ }
+
+ $form['wysiwyg']['media_wysiwyg_wysiwyg_upload_directory'] = array(
+ '#type' => 'textfield',
+ '#title' => t("File directory for uploaded media"),
+ '#default_value' => variable_get('media_wysiwyg_wysiwyg_upload_directory', ''),
+ '#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.'),
+ );
+
+ if (module_exists('token')) {
+ $form['wysiwyg']['media_wysiwyg_wysiwyg_upload_directory']['#description'] .= t('This field supports tokens.');
+ $form['wysiwyg']['tokens'] = array(
+ '#theme' => 'token_tree',
+ '#dialog' => TRUE,
+ );
+ }
+
+ $form['wysiwyg']['media_wysiwyg_default_render'] = array(
+ '#type' => 'radios',
+ '#title' => t('How should file entities be rendered within a text field?'),
+ '#description' => t("Full file entity rendering is the best choice for most sites. It respects the file entity's display settings specified at admin/structure/file-types. If your site already uses the legacy method, note that changing this option will affect your site's markup. Testing it on a non-production site is recommended."),
+ '#options' => array(
+ 'file_entity' => 'Full file entity rendering',
+ 'field_attach' => 'Legacy rendering (using field attach)',
+ ),
+ '#default_value' => variable_get('media_wysiwyg_default_render', 'file_entity'),
+ );
+
+ $form['wysiwyg']['media_wysiwyg_wysiwyg_allowed_types'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Allowed types in WYSIWYG'),
+ '#options' => file_entity_type_get_names(),
+ '#default_value' => variable_get('media_wysiwyg_wysiwyg_allowed_types', array('audio', 'image', 'video', 'document')),
+ );
+
+ $form['#submit'][] = 'media_wysiwyg_admin_config_browser_pre_submit';
+}
+
+/**
+ * Manipulate values before form is submitted.
+ */
+function media_wysiwyg_admin_config_browser_pre_submit(&$form, &$form_state) {
+ $wysiwyg_browser_plugins = array_unique(array_values($form_state['values']['media_wysiwyg_wysiwyg_browser_plugins']));
+ if (empty($wysiwyg_browser_plugins[0])) {
+ variable_del('media_wysiwyg_wysiwyg_browser_plugins');
+ unset($form_state['values']['media_wysiwyg_wysiwyg_browser_plugins']);
+ }
+}
+
+/**
+ * Implements hook_filter_info().
+ */
+function media_wysiwyg_filter_info() {
+ $filters['media_filter'] = array(
+ 'title' => t('Convert Media tags to markup'),
+ 'description' => t('This filter will convert [[{type:media... ]] tags into markup. This must be enabled for the Media WYSIWYG integration to work with this input format.'),
+ 'process callback' => 'media_wysiwyg_filter',
+ 'weight' => 2,
+ // @TODO not implemented
+ 'tips callback' => 'media_filter_tips',
+ );
+
+ return $filters;
+}
+
+/**
+ * Implements hook_wysiwyg_include_directory().
+ */
+function media_wysiwyg_wysiwyg_include_directory($type) {
+ switch ($type) {
+ case 'plugins':
+ return 'wysiwyg_plugins';
+
+ break;
+ }
+}
+
+/**
+ * Returns the default set of allowed attributes for use with WYSIWYG.
+ *
+ * @return array
+ * An array of whitelisted attributes.
+ */
+function _media_wysiwyg_wysiwyg_allowed_attributes_default() {
+ return array(
+ 'alt',
+ 'title',
+ 'height',
+ 'width',
+ 'hspace',
+ 'vspace',
+ 'border',
+ 'align',
+ 'style',
+ 'class',
+ 'id',
+ 'usemap',
+ 'data-picture-group',
+ 'data-picture-align',
+ 'data-picture-mapping',
+ );
+}
+
+/**
+ * Returns a drupal_render() array for just the file portion of a file entity.
+ *
+ * Optional custom settings can override how the file is displayed.
+ */
+function media_wysiwyg_get_file_without_label($file, $view_mode, $settings = array()) {
+ $file->override = $settings;
+
+ $element = file_view_file($file, $view_mode);
+
+ // The formatter invoked by file_view_file() can use $file->override to
+ // customize the returned render array to match the requested settings. To
+ // support simple formatters that don't do this, set the element attributes to
+ // what was requested, but not if the formatter applied its own logic for
+ // element attributes.
+ if (isset($settings['attributes'])) {
+ if (empty($element['#attributes'])) {
+ $element['#attributes'] = $settings['attributes'];
+ }
+
+ // While this function may be called for any file type, images are a common
+ // use-case, and image theme functions have their own structure for render
+ // arrays.
+ if (isset($element['#theme'])) {
+ // theme_image() and theme_image_style() require the 'alt' attributes to
+ // be passed separately from the 'attributes' array. (see
+ // http://drupal.org/node/999338). Until that's fixed, implement this
+ // special-case logic. Image formatters using other theme functions are
+ // responsible for their own 'alt' attribute handling. See
+ // theme_media_formatter_large_icon() for an example.
+ if (in_array($element['#theme'], array('image', 'image_style'))) {
+ if (empty($element['#alt']) && isset($settings['attributes']['alt'])) {
+ $element['#alt'] = $settings['attributes']['alt'];
+ }
+ }
+ // theme_image_formatter() and any potential replacements, such as
+ // theme_colorbox_image_formatter(), also require attribute handling.
+ elseif (strpos($element['#theme'], 'image_formatter') !== FALSE) {
+ // theme_image_formatter() requires the attributes to be
+ // set on the item rather than the element itself.
+ if (empty($element['#item']['attributes'])) {
+ $element['#item']['attributes'] = $settings['attributes'];
+ }
+
+ // theme_image_formatter() also requires alt, title, height, and
+ // width attributes to be set on the item rather than within its
+ // attributes array.
+ foreach (array('alt', 'title', 'width', 'height') as $attr) {
+ if (isset($settings['attributes'][$attr])) {
+ $element['#item'][$attr] = $settings['attributes'][$attr];
+ }
+ }
+ }
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Returns an array containing the names of all fields that perform text filtering.
+ */
+function media_wysiwyg_filter_fields_with_text_filtering($entity_type, $entity) {
+ list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
+ $fields = field_info_instances($entity_type, $bundle);
+
+ // Get all of the fields on this entity that allow text filtering.
+ $fields_with_text_filtering = array();
+ foreach ($fields as $field_name => $field) {
+ if (!empty($field['settings']['text_processing'])) {
+ $fields_with_text_filtering[] = $field_name;
+ }
+ }
+
+ return $fields_with_text_filtering;
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.test b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.test
new file mode 100644
index 000000000..cdb672a19
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.test
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Tests for media.module.
+ */
+
+/**
+ * Defines base class for media test cases.
+ */
+class MediaWYSIWYGTestHelper extends DrupalWebTestCase {
+
+ /**
+ * Enable media and file entity modules for testing.
+ */
+ public function setUp() {
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
+ }
+ $modules[] = 'media_wysiwyg';
+ parent::setUp($modules);
+ }
+
+ /**
+ * Generates markup to be inserted for a file.
+ *
+ * This is a PHP version of InsertMedia.insert() from js/wysiwyg-media.js.
+ *
+ * @param int $fid
+ * Drupal file id
+ * @param int $count
+ * Quantity of markup to insert
+ * @param array $attributes
+ * Extra attributes to insert.
+ * @param array $fields
+ * Extra field values to insert.
+ *
+ * @return string
+ * Filter markup.
+ */
+ protected function generateJsonTokenMarkup($fid, $count = 1, array $attributes = array(), array $fields = array()) {
+ $markup = '';
+ // Merge default atttributes.
+ $attributes += array(
+ 'height' => 100,
+ 'width' => 100,
+ 'classes' => 'media-element file_preview',
+ );
+
+ // Build the data that is used in a media tag.
+ $data = array(
+ 'fid' => $fid,
+ 'type' => 'media',
+ 'view_mode' => 'preview',
+ 'attributes' => $attributes,
+ 'fields' => $fields,
+ );
+
+ // Create the file usage markup.
+ for ($i = 1; $i <= $count; $i++) {
+ $markup .= '<p>[[' . drupal_json_encode($data) . ']]</p>';
+ }
+
+ return $markup;
+ }
+
+ /**
+ * Utility function to create a test node.
+ *
+ * @param int $fid
+ * Create the node with media markup in the body field
+ * @param array $attributes
+ * Extra attributes to insert to the file.
+ * @param array $fields
+ * Extra field values to insert.
+ *
+ * @return int
+ * Returns the node id
+ */
+ protected function createNode($fid = FALSE, array $attributes = array(), array $fields = array()) {
+ $markup = '';
+ if (! empty($fid)) {
+ $markup = $this->generateJsonTokenMarkup($fid, 1, $attributes, $fields);
+ }
+
+ // Create an article node with file markup in the body field.
+ $edit = array(
+ 'title' => $this->randomName(8),
+ 'body[und][0][value]' => $markup,
+ );
+ // Save the article node. First argument is the URL, then the value array
+ // and the third is the label the button that should be "clicked".
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Get the article node that was saved by the unique title.
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+ return $node->nid;
+ }
+
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.variable.inc b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.variable.inc
new file mode 100644
index 000000000..206905a08
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/media_wysiwyg.variable.inc
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Declare Media WYSIWYG variables.
+ */
+
+/**
+ * Implements hook_variable_group_info().
+ */
+function media_wysiwyg_variable_group_info() {
+ $groups['media_wysiwyg'] = array(
+ 'title' => t('Media WYSIWYG'),
+ 'description' => t('Settings for Media WYSIWYG integration.'),
+ 'access' => 'administer media browser',
+ 'path' => 'admin/config/media/browser',
+ );
+
+ return $groups;
+}
+
+/**
+* Implements hook_variable_info().
+*/
+function media_wysiwyg_variable_info($options) {
+ $variables['media_wysiwyg_wysiwyg_title'] = array(
+ 'type' => 'string',
+ 'title' => t('WYSIWYG Title', array(), $options),
+ 'default' => t('Media browser', array(), $options),
+ 'description' => t('The WYSIWYG media plugin title.', array(), $options),
+ 'group' => 'media_wysiwyg',
+ );
+ $variables['media_wysiwyg_wysiwyg_icon_title'] = array(
+ 'type' => 'string',
+ 'title' => t('WYSIWYG Icon Title', array(), $options),
+ 'default' => t('Add media', array(), $options),
+ 'description' => t('The WYSIWYG media button title to display on hover.', array(), $options),
+ 'group' => 'media_wysiwyg',
+ );
+
+ return $variables;
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/tests/media_wysiwyg.file_usage.test b/sites/all/modules/media/modules/media_wysiwyg/tests/media_wysiwyg.file_usage.test
new file mode 100644
index 000000000..bd82ecfa4
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/tests/media_wysiwyg.file_usage.test
@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * @file
+ * Tests for the file usage in entity fields with the Media filter markup.
+ */
+
+class MediaWYSIWYGFileUsageTest extends MediaWYSIWYGTestHelper {
+
+ /**
+ * Provide test information.
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('File usage tracking'),
+ 'description' => t('Tests tracking of usage for files in text fields.'),
+ 'group' => t('Media WYSIWYG'),
+ );
+ }
+
+ /**
+ * Enable media and file entity modules for testing.
+ */
+ public function setUp() {
+ parent::setUp();
+
+ // Create and log in a user.
+ $account = $this->drupalCreateUser(array('administer nodes', 'create article content'));
+ $this->drupalLogin($account);
+ }
+
+ /**
+ * Tests the tracking of file usages for files submitted via the WYSIWYG editor.
+ */
+ public function testFileUsageIncrementing() {
+ // Create a file.
+ $files = $this->drupalGetTestFiles('image');
+ $file = file_save($files[0]);
+ $fid = $file->fid;
+
+ // There should be zero usages of this file prior to node creation,
+ $file_uses = file_usage_list($file);
+ $this->assertEqual(empty($file_uses), TRUE, t('Created a new file with zero uses.'));
+
+ // Create a node to test with.
+ $nid = $this->createNode($fid);
+
+ // Get the new file usage count.
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual($file_uses['media']['node'][$nid], 1, t('File usage increases when added to a new node.'));
+
+ // Create a new revision that has the file on it. File usage will be 2.
+ $node = node_load($nid);
+ $node->revision = TRUE;
+ node_save($node);
+
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+ $revisions = count(node_revision_list($node));
+ // Keep track of this VID to test deletion later on.
+ $delete_one = $node->vid;
+
+ $this->assertEqual($revisions, 2, t('Node save created a second revision'));
+ $this->assertEqual($file_uses['media']['node'][$nid], 2, t('File usage incremented with a new node revision.'));
+
+ // Create a new revision that has two instances of the file. File usage will
+ // be 4.
+ $node = node_load($nid);
+ $node->body[LANGUAGE_NONE][0]['value'] = $this->generateJsonTokenMarkup($fid, 2);
+ $node->revision = TRUE;
+ node_save($node);
+
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+ $revisions = count(node_revision_list($node));
+ // Keep track of this VID to test deletion later on.
+ $delete_two = $node->vid;
+
+ $this->assertEqual($revisions, 3, t('Node save created a third revision.'));
+ $this->assertEqual($file_uses['media']['node'][$nid], 4, t('File usage incremented with multiple files and a new node revision.'));
+
+ // Create a new revision that has no file on it. File usage will be 4.
+ $node = node_load($nid);
+ $node->body[LANGUAGE_NONE][0]['value'] = '';
+ $node->revision = TRUE;
+ node_save($node);
+
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+ $revisions = count(node_revision_list($node));
+ // Keep track of this VID to test deletion later on.
+ $delete_zero = $node->vid;
+
+ $this->assertEqual($revisions, 4, t('Node save created a fourth revision.'));
+ $this->assertEqual($file_uses['media']['node'][$nid], 4, t('File usage does not change with a new revision of the node without the file'));
+
+ // Create a new revision that has the file on it. File usage will be 5.
+ $node = node_load($nid);
+ $node->body[LANGUAGE_NONE][0]['value'] = $this->generateJsonTokenMarkup($fid, 1);
+ $node->revision = TRUE;
+ node_save($node);
+
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+ $revisions = count(node_revision_list($node));
+
+ $this->assertEqual($revisions, 5, t('Node save created a new revision.'));
+ $this->assertEqual($file_uses['media']['node'][$nid], 5, t('File usage incremented with a single file on a new node revision.'));
+
+ // Delete a revision that has the file on it once. File usage will be 4.
+ node_revision_delete($delete_one);
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+ $this->assertEqual($file_uses['media']['node'][$nid], 4, t('Deleting revision with file decreases file usage'));
+
+ // Delete a revision that has no file on it. File usage will be 4.
+ node_revision_delete($delete_zero);
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+ $this->assertEqual($file_uses['media']['node'][$nid], 4, t('Deleting revision without a file does not change file usage.'));
+
+ // Delete a revision that has the file on it twice. File usage will be 2.
+ node_revision_delete($delete_two);
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+ $this->assertEqual($file_uses['media']['node'][$nid], 2, t('Deleting revision with file decreases file usage'));
+
+ // Create a new revision with the file on it twice. File usage will be 4.
+ $node = node_load($nid);
+ $node->body[LANGUAGE_NONE][0]['value'] = $this->generateJsonTokenMarkup($fid, 2);
+ $node->revision = TRUE;
+ node_save($node);
+
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual($file_uses['media']['node'][$nid], 4, t('File usage incremented with files on a new node revision.'));
+
+ // Re-save current revision with file on it once instead of twice. File
+ // usage will be 3.
+ $node = node_load($nid);
+ $node->body[LANGUAGE_NONE][0]['value'] = $this->generateJsonTokenMarkup($fid, 1);
+ $saved_vid = $node->vid;
+ node_save($node);
+
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual($node->vid, $saved_vid, t('Resaved node revision does not create new revision.'));
+ $this->assertEqual($file_uses['media']['node'][$nid], 3, t('Resaved node revision with fewer files reduces file usage.'));
+
+ // Delete the node. File usage will be 0.
+ $node = node_load($nid);
+ node_delete($nid);
+
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual(empty($node), TRUE, t('Node has been deleted.'));
+ $this->assertEqual(empty($file_uses), TRUE, t('Deleting the node removes all file uses.'));
+ }
+
+ /**
+ * Tests the behavior of node and file deletion.
+ */
+ public function testFileUsageIncrementingDelete() {
+ // Create a node with file markup in the body field with a new file.
+ $files = $this->drupalGetTestFiles('image');
+ $file = file_save($files[1]);
+ $fid = $file->fid;
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual(empty($file_uses), TRUE, t('Created a new file with zero uses.'));
+
+ // Create a new node with file markup.
+ $nid = $this->createNode($fid);
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual($file_uses['media']['node'][$nid], 1, t('Incremented file usage on node save.'));
+
+ // Try to delete the file. file_delete() should return file_usage().
+ $deleted = file_delete($file);
+ $this->assertTrue(is_array($deleted), t('File cannot be deleted while in use by a node.'));
+
+ // Delete the node.
+ node_delete($nid);
+ $node = node_load($nid);
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual(empty($node), TRUE, t('Node has been deleted.'));
+ $this->assertEqual(empty($file_uses), TRUE, t('File has zero usage after node is deleted.'));
+
+ $deleted = file_delete($file);
+ $this->assertTrue($deleted, t('File can be deleted with no usage.'));
+
+ $file = file_load($fid);
+ $this->assertTrue(empty($file), t('File no longer exists after delete.'));
+ }
+
+
+ /**
+ * Tests if node still remains updatable if file was deleted.
+ */
+ public function testFileUsageForcedDelete() {
+ // Create a node with file markup in the body field with a new file.
+ $files = $this->drupalGetTestFiles('image');
+ $file = file_save($files[1]);
+ $fid = $file->fid;
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual(empty($file_uses), TRUE, t('Created a new file with zero uses.'));
+
+ // Create a new node with file markup.
+ $nid = $this->createNode($fid);
+ $file_uses = file_usage_list($file);
+
+ $this->assertEqual($file_uses['media']['node'][$nid], 1, t('Incremented file usage on node save.'));
+
+ // Force the file to delete.
+ $deleted = file_delete($file, TRUE);
+ $this->assertTrue($deleted, t('File was deleted although in use sice we forced it.'));
+
+ // Try to update the node that uses broken file.
+ $account = $this->drupalCreateUser(array('edit any article content'));
+ $node = node_load($nid);
+ $this->drupalLogin($account);
+ $this->drupalGet('node/' . $nid . '/edit');
+ $this->assertRaw(check_plain($node->body['und'][0]['value']), t('Reference to deleted file found in node body.'));
+ $edit = array(
+ 'body[und][0][value]' => '',
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $type = node_type_load($node->type);
+ $this->assertRaw(t('@type %title has been updated.', array('@type' => $type->name, '%title' => $node->title)), t('Node without reference to deleted file saved successfully.'));
+ }
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/tests/media_wysiwyg.macro.test b/sites/all/modules/media/modules/media_wysiwyg/tests/media_wysiwyg.macro.test
new file mode 100644
index 000000000..7ac1bc4c7
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/tests/media_wysiwyg.macro.test
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Tests for ensuring media macros render properly.
+ */
+
+/**
+ * Defines media macro test cases.
+ */
+class MediaWYSIWYGWYSIWYGOverridesTest extends MediaWYSIWYGTestHelper {
+
+ /**
+ * Provide test information.
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Media WYSIWYG WYSIWYG overrides'),
+ 'description' => t('Tests that overridden attributes display correct.'),
+ 'group' => t('Media WYSIWYG'),
+ 'dependencies' => array('token'),
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('token');
+
+ // Create and log in a user.
+ $account = $this->drupalCreateUser(array('create article content', 'administer filters', 'use text format filtered_html'));
+ $this->drupalLogin($account);
+
+ // Enable the media filter for full html.
+ $edit = array(
+ 'filters[media_filter][status]' => TRUE,
+ 'filters[filter_html][status]' => FALSE,
+ );
+ $this->drupalPost('admin/config/content/formats/filtered_html', $edit, t('Save configuration'));
+ }
+
+ /**
+ * Test image media overrides.
+ */
+ public function testAttributeOverrides() {
+ $files = $this->drupalGetTestFiles('image');
+ $file = file_save($files[0]);
+
+ // Create a node to test with.
+ $nid = $this->createNode($file->fid);
+
+ $this->drupalGet('node/' . $nid);
+ $this->assertRaw('width="100"', t('Image displays with default width attribute.'));
+ $this->assertRaw('height="100"', t('Image displays with default height attribute.'));
+
+ // Create a node with a style attribute.
+ $attributes = array(
+ 'style' => 'float: left; width: 50px;',
+ );
+ $nid = $this->createNode($file->fid, $attributes);
+ $this->drupalGet('node/' . $nid);
+ $this->assertRaw(drupal_attributes($attributes), t('Image displays with overriden attributes.'));
+
+ // Create a node with overriden alt/title attributes.
+ $attributes = array(
+ 'alt' => $this->randomName(),
+ 'title' => $this->randomName(),
+ );
+ $nid = $this->createNode($file->fid, $attributes);
+ $this->drupalGet('node/' . $nid);
+ $this->assertRaw(drupal_attributes($attributes), t('Image displays with alt/title set as attributes.'));
+
+ // Create a node with overriden alt/title fields.
+ $fields = $attributes = array();
+ $attributes['alt'] = $fields['field_file_image_alt_text[und][0][value]'] = $this->randomName();
+ $attributes['title'] = $fields['field_file_image_title_text[und][0][value]'] = $this->randomName();
+
+ $nid = $this->createNode($file->fid, array(), $fields);
+ $this->drupalGet('node/' . $nid);
+ // Ensure that the alt/title from attributes display.
+ $this->assertRaw(drupal_attributes($attributes), t('Image displays with alt/title set as fields.'));
+
+ // Create a node with overriden alt/title fields as well as attributes.
+ $attributes = array(
+ 'alt' => $this->randomName(),
+ 'title' => $this->randomName(),
+ );
+ $fields = array(
+ 'field_file_image_alt_text[und][0][value]' => $this->randomName(),
+ 'field_file_image_title_text[und][0][value]' => $this->randomName(),
+ );
+ $nid = $this->createNode($file->fid, $attributes, $fields);
+ $this->drupalGet('node/' . $nid);
+ // Ensure that the alt/title from attributes display rather the field ones.
+ $this->assertRaw(drupal_attributes($attributes), t('Image displays with alt/title set as attributes overriding field values.'));
+ }
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media.inc b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media.inc
new file mode 100644
index 000000000..9cd03bbd2
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media.inc
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Define the WYSIWYG browser plugin.
+ */
+
+/**
+ * Implements WYSIWYG's hook_INCLUDE_plugin().
+ */
+function media_wysiwyg_media_plugin() {
+ $plugins['media'] = array(
+ 'title' => variable_get('media_wysiwyg_wysiwyg_title', t('Media browser')),
+ 'vendor url' => 'http://drupal.org/project/media',
+ 'icon path' => drupal_get_path('module', 'media_wysiwyg') . '/wysiwyg_plugins/media_ckeditor/images',
+ 'icon file' => 'icon.gif',
+ 'icon title' => variable_get('media_wysiwyg_wysiwyg_icon_title', t('Add media')),
+ // @todo: move this to the plugin directory for the wysiwyg plugin.
+ 'js path' => drupal_get_path('module', 'media_wysiwyg') . '/js',
+ 'js file' => 'wysiwyg-media.js',
+ 'css path' => drupal_get_path('module', 'media_wysiwyg') . '/css',
+ 'css file' => 'media_wysiwyg.css',
+ 'settings' => array(
+ 'global' => array(
+ 'enabledPlugins' => variable_get('media_wysiwyg_wysiwyg_browser_plugins', array()),
+ 'file_directory' => variable_get('media_wysiwyg_wysiwyg_upload_directory', ''),
+ 'types' => variable_get('media_wysiwyg_wysiwyg_allowed_types', array('audio', 'image', 'video', 'document')),
+ 'id' => 'media_wysiwyg',
+ ),
+ ),
+ );
+
+ return $plugins;
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/images/icon.gif b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/images/icon.gif
new file mode 100644
index 000000000..bf9b501c3
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/images/icon.gif
Binary files differ
diff --git a/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/lang/en.js b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/lang/en.js
new file mode 100644
index 000000000..efc1d6853
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/lang/en.js
@@ -0,0 +1,14 @@
+/*
+ Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+ */
+CKEDITOR.plugins.setLang( 'media', 'en', {
+ alt: 'Alternative Text', // Inherit from image plugin.
+ captioned: 'Captioned image', // NEW property.
+ lockRatio: 'Lock Ratio', // Inherit from image plugin.
+ menu: 'Image Properties', // Inherit from image plugin.
+ resetSize: 'Reset Size', // Inherit from image plugin.
+ resizer: 'Click and drag to resize', // NEW property.
+ title: 'Image Properties', // Inherit from image plugin.
+ urlMissing: 'Image source URL is missing.' // Inherit from image plugin.
+} );
diff --git a/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/library.js b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/library.js
new file mode 100644
index 000000000..b097d170b
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/library.js
@@ -0,0 +1,183 @@
+
+/**
+ * @file
+ * Attach Media ckeditor behaviors.
+ */
+
+(function ($) {
+ Drupal.media = Drupal.media || {};
+
+ Drupal.settings.ckeditor.plugins['media'] = {
+ /**
+ * Execute the button.
+ */
+ invoke: function (data, settings, instanceId) {
+ if (data.format == 'html') {
+ if (jQuery(data.node).is('[data-media-element]')) {
+ // Change the view mode for already-inserted media.
+ var mediaFile = Drupal.media.filter.extract_file_info(jQuery(data.node));
+ Drupal.media.popups.mediaStyleSelector(mediaFile, function (mediaFiles) {
+ Drupal.settings.ckeditor.plugins['media'].insertMediaFile(mediaFile, mediaFiles, CKEDITOR.instances[instanceId]);
+ }, settings['global']);
+ }
+ else {
+ Drupal.media.popups.mediaBrowser(function (mediaFiles) {
+ Drupal.settings.ckeditor.plugins['media'].mediaBrowserOnSelect(mediaFiles, instanceId);
+ }, settings['global']);
+ }
+ }
+ },
+
+ /**
+ * Respond to the mediaBrowser's onSelect event.
+ */
+ mediaBrowserOnSelect: function (mediaFiles, instanceId) {
+ var mediaFile = mediaFiles[0];
+ var options = {};
+ Drupal.media.popups.mediaStyleSelector(mediaFile, function (formattedMedia) {
+ Drupal.settings.ckeditor.plugins['media'].insertMediaFile(mediaFile, formattedMedia, CKEDITOR.instances[instanceId]);
+ }, options);
+
+ return;
+ },
+
+ insertMediaFile: function (mediaFile, formattedMedia, ckeditorInstance) {
+ // Customization of Drupal.media.filter.registerNewElement().
+ var element = Drupal.media.filter.create_element(formattedMedia.html, {
+ fid: mediaFile.fid,
+ view_mode: formattedMedia.type,
+ attributes: formattedMedia.options
+ });
+
+ // Use own wrapper element to be able to properly deal with selections.
+ // Check prepareDataForWysiwygMode() in plugin.js for details.
+ var wysiwygHTML = Drupal.media.filter.getWysiwygHTML(element);
+
+ // Insert element. Use CKEDITOR.dom.element.createFromHtml to ensure our
+ // custom wrapper element is preserved.
+ if (wysiwygHTML.indexOf("<!--MEDIA-WRAPPER-START-") !== -1) {
+ ckeditorInstance.plugins.media.mediaLegacyWrappers = true;
+ wysiwygHTML = wysiwygHTML.replace(/<!--MEDIA-WRAPPER-START-(\d+)-->(.*?)<!--MEDIA-WRAPPER-END-\d+-->/gi, '');
+ } else {
+ wysiwygHTML = '<mediawrapper data="">' + wysiwygHTML + '</mediawrapper>';
+ }
+
+ var editorElement = CKEDITOR.dom.element.createFromHtml(wysiwygHTML);
+ ckeditorInstance.insertElement(editorElement);
+
+ // Initialize widget on our html if possible.
+ if (parseFloat(CKEDITOR.version) >= 4.3 && typeof(CKEDITOR.plugins.registered.widget) != 'undefined') {
+ ckeditorInstance.widgets.initOn( editorElement, 'mediabox' );
+ }
+ },
+
+ /**
+ * Forces custom attributes into the class field of the specified image.
+ *
+ * Due to a bug in some versions of Firefox
+ * (http://forums.mozillazine.org/viewtopic.php?f=9&t=1991855), the
+ * custom attributes used to share information about the image are
+ * being stripped as the image markup is set into the rich text
+ * editor. Here we encode these attributes into the class field so
+ * the data survives.
+ *
+ * @param imgElement
+ * The image
+ * @fid
+ * The file id.
+ * @param view_mode
+ * The view mode.
+ * @param additional
+ * Additional attributes to add to the image.
+ */
+ forceAttributesIntoClass: function (imgElement, fid, view_mode, additional) {
+ var wysiwyg = imgElement.attr('wysiwyg');
+ if (wysiwyg) {
+ imgElement.addClass('attr__wysiwyg__' + wysiwyg);
+ }
+ var format = imgElement.attr('format');
+ if (format) {
+ imgElement.addClass('attr__format__' + format);
+ }
+ var typeOf = imgElement.attr('typeof');
+ if (typeOf) {
+ imgElement.addClass('attr__typeof__' + typeOf);
+ }
+ if (fid) {
+ imgElement.addClass('img__fid__' + fid);
+ }
+ if (view_mode) {
+ imgElement.addClass('img__view_mode__' + view_mode);
+ }
+ if (additional) {
+ for (var name in additional) {
+ if (additional.hasOwnProperty(name)) {
+ switch (name) {
+ case 'field_file_image_alt_text[und][0][value]':
+ imgElement.attr('alt', additional[name]);
+ break;
+ case 'field_file_image_title_text[und][0][value]':
+ imgElement.attr('title', additional[name]);
+ break;
+ default:
+ imgElement.addClass('attr__' + name + '__' + additional[name]);
+ break;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Retrieves encoded attributes from the specified class string.
+ *
+ * @param classString
+ * A string containing the value of the class attribute.
+ * @return
+ * An array containing the attribute names as keys, and an object
+ * with the name, value, and attribute type (either 'attr' or
+ * 'img', depending on whether it is an image attribute or should
+ * be it the attributes section)
+ */
+ getAttributesFromClass: function (classString) {
+ var actualClasses = [];
+ var otherAttributes = [];
+ var classes = classString.split(' ');
+ var regexp = new RegExp('^(attr|img)__([^\S]*)__([^\S]*)$');
+ for (var index = 0; index < classes.length; index++) {
+ var matches = classes[index].match(regexp);
+ if (matches && matches.length === 4) {
+ otherAttributes[matches[2]] = {
+ name: matches[2],
+ value: matches[3],
+ type: matches[1]
+ };
+ }
+ else {
+ actualClasses.push(classes[index]);
+ }
+ }
+ if (actualClasses.length > 0) {
+ otherAttributes['class'] = {
+ name: 'class',
+ value: actualClasses.join(' '),
+ type: 'attr'
+ };
+ }
+ return otherAttributes;
+ },
+
+ sortAttributes: function (a, b) {
+ var nameA = a.name.toLowerCase();
+ var nameB = b.name.toLowerCase();
+ if (nameA < nameB) {
+ return -1;
+ }
+ if (nameA > nameB) {
+ return 1;
+ }
+ return 0;
+ }
+ };
+
+})(jQuery);
diff --git a/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/plugin.js b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/plugin.js
new file mode 100644
index 000000000..37fd3b9d3
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg/wysiwyg_plugins/media_ckeditor/plugin.js
@@ -0,0 +1,217 @@
+/*
+Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+
+/**
+ * @file Plugin for inserting images from Drupal media module
+ *
+ * @TODO Remove all the legecy media wrapper once it's sure nobody uses that
+ * anymore.
+ */
+( function() {
+ var mediaPluginDefinition = {
+ icons: 'media',
+ requires: ['button'],
+ // Check if this instance has widget support. All the default distributions
+ // of the editor have the widget plugin disabled by default.
+ hasWidgetSupport: typeof(CKEDITOR.plugins.registered.widget) != 'undefined',
+ mediaLegacyWrappers: false,
+
+ // Wrap Drupal plugin in a proxy plugin.
+ init: function(editor){
+ editor.addCommand( 'media',
+ {
+ exec: function (editor) {
+ var data = {
+ format: 'html',
+ node: null,
+ content: ''
+ };
+ var selection = editor.getSelection();
+
+ if (selection) {
+ data.node = selection.getSelectedElement();
+ if (data.node) {
+ data.node = data.node.$;
+ }
+ if (selection.getType() == CKEDITOR.SELECTION_TEXT) {
+ if (CKEDITOR.env.ie && CKEDITOR.env.version < 10) {
+ data.content = selection.getNative().createRange().text;
+ }
+ else {
+ data.content = selection.getNative().toString();
+ }
+ }
+ else if (data.node) {
+ // content is supposed to contain the "outerHTML".
+ data.content = data.node.parentNode.innerHTML;
+ }
+ }
+ Drupal.settings.ckeditor.plugins['media'].invoke(data, Drupal.settings.ckeditor.plugins['media'], editor.name);
+ }
+ });
+
+ editor.ui.addButton( 'Media',
+ {
+ label: 'Add media',
+ command: 'media',
+ icon: this.path + 'images/icon.gif'
+ });
+
+ var ckeditorversion = parseFloat(CKEDITOR.version);
+
+ // Because the media comment wrapper don't work well for CKEditor we
+ // replace them by using a custom mediawrapper element.
+ // Instead having
+ // <!--MEDIA-WRAPPER-START-1--><img /><!--MEDIA-WRAPPER-END-1-->
+ // We wrap the placeholder with
+ // <mediawrapper data="1"><img /></mediawrapper>
+ // That way we can deal better with selections - see selectionChange.
+ CKEDITOR.dtd['mediawrapper'] = CKEDITOR.dtd;
+ CKEDITOR.dtd.$blockLimit['mediawrapper'] = 1;
+ CKEDITOR.dtd.$inline['mediawrapper'] = 1;
+ CKEDITOR.dtd.$nonEditable['mediawrapper'] = 1;
+ if (ckeditorversion >= 4.1) {
+ // Register allowed tag for advanced filtering.
+ editor.filter.allow( 'mediawrapper[!data]', 'mediawrapper', true);
+ // Don't remove the data-file_info attribute added by media!
+ editor.filter.allow( '*[!data-file_info]', 'mediawrapper', true);
+ // Ensure image tags accept all kinds of attributes.
+ editor.filter.allow( 'img[*]{*}(*)', 'mediawrapper', true);
+ // Objects should be selected as a whole in the editor.
+ CKEDITOR.dtd.$object['mediawrapper'] = 1;
+ }
+ function prepareDataForWysiwygMode(data) {
+ data = Drupal.media.filter.replaceTokenWithPlaceholder(data);
+ // Legacy media wrapper.
+ mediaPluginDefinition.mediaLegacyWrappers = (data.indexOf("<!--MEDIA-WRAPPER-START-") !== -1);
+ data = data.replace(/<!--MEDIA-WRAPPER-START-(\d+)-->(.*?)<!--MEDIA-WRAPPER-END-\d+-->/gi, '<mediawrapper data="$1">$2</mediawrapper>');
+ return data;
+ }
+ function prepareDataForSourceMode(data) {
+ var replacement = '$2';
+ // Legacy wrapper
+ if (mediaPluginDefinition.mediaLegacyWrappers) {
+ replacement = '<!--MEDIA-WRAPPER-START-$1-->$2<!--MEDIA-WRAPPER-END-$1-->';
+ }
+ data = data.replace(/<mediawrapper data="(.*)">(.*?)<\/mediawrapper>/gi, replacement);
+ data = Drupal.media.filter.replacePlaceholderWithToken(data);
+ return data;
+ }
+
+ // Ensure the tokens are replaced by placeholders while editing.
+ // Check for widget support.
+ if (mediaPluginDefinition.hasWidgetSupport) {
+ editor.widgets.add( 'mediabox',
+ {
+ button: 'Create a mediabox',
+ template: '<mediawrapper></mediawrapper>',
+ editables: {},
+ allowedContent: '*',
+ upcast: function( element ) {
+ if (element.name != 'mediawrapper') {
+ // Ensure media tokens are converted to media placeholdes.
+ element.setHtml(prepareDataForWysiwygMode(element.getHtml()));
+ }
+ return element.name == 'mediawrapper';
+ },
+
+ downcast: function( widgetElement ) {
+ var token = prepareDataForSourceMode(widgetElement.getOuterHtml());
+ return new CKEDITOR.htmlParser.text(token);
+ return element.name == 'mediawrapper';
+ }
+ });
+ }
+ else if (ckeditorversion >= 4) {
+ // CKEditor >=4.0
+ editor.on('setData', function( event ) {
+ event.data.dataValue = prepareDataForWysiwygMode(event.data.dataValue);
+ });
+ }
+ else {
+ // CKEditor >=3.6 behaviour.
+ editor.on( 'beforeSetMode', function( event, data ) {
+ event.removeListener();
+ var wysiwyg = editor._.modes[ 'wysiwyg' ];
+ var source = editor._.modes[ 'source' ];
+ wysiwyg.loadData = CKEDITOR.tools.override( wysiwyg.loadData, function( org )
+ {
+ return function( data ) {
+ return ( org.call( this, prepareDataForWysiwygMode(data)) );
+ };
+ } );
+ source.loadData = CKEDITOR.tools.override( source.loadData, function( org )
+ {
+ return function( data ) {
+ return ( org.call( this, prepareDataForSourceMode(data) ) );
+ };
+ } );
+ });
+ }
+
+ // Provide alternative to the widget functionality introduced in 4.3.
+ if (!mediaPluginDefinition.hasWidgetSupport) {
+ // Ensure tokens instead the html element is saved.
+ editor.on('getData', function( event ) {
+ event.data.dataValue = prepareDataForSourceMode(event.data.dataValue);
+ });
+
+ // Ensure our enclosing wrappers are always included in the selection.
+ editor.on('selectionChange', function( event ) {
+ var ranges = editor.getSelection().getRanges().createIterator();
+ var newRanges = [];
+ var currRange;
+ while(currRange = ranges.getNextRange()) {
+ var commonAncestor = currRange.getCommonAncestor(false);
+ if (commonAncestor && typeof(commonAncestor.getName) != 'undefined' && commonAncestor.getName() == 'mediawrapper') {
+ var range = new CKEDITOR.dom.range( editor.document );
+ if (currRange.collapsed === true) {
+ // Don't allow selection within the wrapper element.
+ if (currRange.startOffset == 0) {
+ // While v3 plays nice with setting start and end to avoid
+ // editing within the media wrapper element, v4 ignores that.
+ // Thus we try to move the cursor further away.
+ if (parseInt(CKEDITOR.version) > 3) {
+ range.setStart(commonAncestor.getPrevious());
+ range.setEnd(commonAncestor.getPrevious());
+ }
+ else {
+ range.setStartBefore(commonAncestor);
+ }
+ }
+ else {
+ // While v3 plays nice with setting start and end to avoid
+ // editing within the media wrapper element, v4 ignores that.
+ // Thus we try to move the cursor further away.
+ if (parseInt(CKEDITOR.version) > 3) {
+ range.setStart(commonAncestor.getNext(), 1);
+ range.setEnd(commonAncestor.getNext(), 1);
+ }
+ else {
+ range.setStartAfter(commonAncestor);
+ }
+ }
+ }
+ else {
+ // Always select the whole wrapper element.
+ range.setStartBefore(commonAncestor);
+ range.setEndAfter(commonAncestor);
+ }
+ newRanges.push(range);
+ }
+ }
+ if (newRanges.length) {
+ editor.getSelection().selectRanges(newRanges);
+ }
+ });
+ }
+ }
+ };
+ // Add dependency to widget plugin if possible.
+ if (parseFloat(CKEDITOR.version) >= 4.3 && mediaPluginDefinition.hasWidgetSupport) {
+ mediaPluginDefinition.requires.push('widget');
+ }
+ CKEDITOR.plugins.add( 'media', mediaPluginDefinition);
+} )();
diff --git a/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.admin.inc b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.admin.inc
new file mode 100644
index 000000000..4d1f0a60d
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.admin.inc
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Generate configuration form and save settings.
+ */
+
+/**
+ * Configuration form for Media's WYSIWYG view modes.
+ */
+function media_wysiwyg_view_mode_configuration_form($form, &$form_state) {
+ $options = array();
+
+ // Add the default view mode by default
+ $options['default'] = t('Default');
+
+ $entity_info = entity_get_info('file');
+ foreach ($entity_info['view modes'] as $view_mode => $view_mode_info) {
+ $options[$view_mode] = check_plain($view_mode_info['label']);
+ }
+
+ $form['media_wysiwyg_view_mode_wysiwyg_restricted_view_modes'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('WYSIWYG allowed view modes'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ '#description' => t('Restrict the allowed view modes when embedding files inside of the the WYSIWYG editor.'),
+ );
+
+ foreach (file_type_get_enabled_types() as $type) {
+ $form['media_wysiwyg_view_mode_wysiwyg_restricted_view_modes']["media_wysiwyg_view_mode_{$type->type}_wysiwyg_restricted_view_modes_status"] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Restrict allowed view modes for %type', array('%type' => $type->label)),
+ '#default_value' => variable_get("media_wysiwyg_view_mode_{$type->type}_wysiwyg_restricted_view_modes_status", FALSE),
+ );
+ $form['media_wysiwyg_view_mode_wysiwyg_restricted_view_modes']["media_wysiwyg_view_mode_{$type->type}_wysiwyg_restricted_view_modes"] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Restrict view modes'),
+ '#options' => $options,
+ '#default_value' => variable_get("media_wysiwyg_view_mode_{$type->type}_wysiwyg_restricted_view_modes", array()),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="media_wysiwyg_view_mode_' . $type->type . '_wysiwyg_restricted_view_modes_status"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+
+ $form['media_wysiwyg_view_mode_file_wysiwyg_view_mode'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('File WYSIWYG view mode'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ '#description' => t('Use a custom view mode when displaying files inside of the WYSIWYG editor.'),
+ );
+
+ foreach (file_type_get_enabled_types() as $type) {
+ $form['media_wysiwyg_view_mode_file_wysiwyg_view_mode']["media_wysiwyg_view_mode_{$type->type}_file_wysiwyg_view_mode_status"] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use a custom view mode for %type', array('%type' => $type->label)),
+ '#default_value' => variable_get("media_wysiwyg_view_mode_{$type->type}_file_wysiwyg_view_mode_status", FALSE),
+ );
+ $form['media_wysiwyg_view_mode_file_wysiwyg_view_mode']["media_wysiwyg_view_mode_{$type->type}_file_wysiwyg_view_mode"] = array(
+ '#type' => 'select',
+ '#title' => t('View mode'),
+ '#options' => $options,
+ '#default_value' => variable_get("media_wysiwyg_view_mode_{$type->type}_file_wysiwyg_view_mode", 'wysiwyg'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="media_wysiwyg_view_mode_' . $type->type . '_file_wysiwyg_view_mode_status"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+
+ return system_settings_form($form);
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info
new file mode 100644
index 000000000..a17131e0e
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info
@@ -0,0 +1,17 @@
+name = Media WYSIWYG View Mode
+description = Enables files inside of the WYSIWYG editor to be displayed using a separate view mode.
+package = Media
+core = 7.x
+
+dependencies[] = media_wysiwyg
+
+configure = admin/config/media/wysiwyg-view-mode
+
+files[] = media_wysiwyg_view_mode.test
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.install b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.install
new file mode 100644
index 000000000..56b706fa7
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.install
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Media WYSIWYG View Mode module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function media_wysiwyg_view_mode_uninstall() {
+ db_delete('variable')
+ ->condition('name', "media_wysiwyg_view_mode_%", "LIKE")
+ ->execute();
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.module b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.module
new file mode 100644
index 000000000..d36bec716
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.module
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * @file
+ * Primarily Drupal hooks.
+ */
+
+/**
+ * Implements hook_permission().
+ */
+function media_wysiwyg_view_mode_permission() {
+ return array(
+ 'administer media wysiwyg view mode' => array(
+ 'title' => t('Administer Media WYSIWYG View Mode'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function media_wysiwyg_view_mode_menu() {
+ $items['admin/config/media/wysiwyg-view-mode'] = array(
+ 'title' => 'Media WYSIWYG View Mode',
+ 'description' => 'Configure view mode settings for files embedded into and displayed inside of the WYSIWYG editor.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_wysiwyg_view_mode_configuration_form'),
+ 'access arguments' => array('administer media wysiwyg view mode'),
+ 'file' => 'media_wysiwyg_view_mode.admin.inc',
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_help().
+ */
+function media_wysiwyg_view_mode_help($path, $arg) {
+ switch ($path) {
+ case 'admin/config/media/wysiwyg-view-mode':
+ $output = '';
+ $output .= '<p>' . t('Configure view modes for files displayed inside of the WYSIWYG editor.') . '</p>';
+ $output .= '<p>' . t('View modes can be configured per file type. Only enabled view modes are selectable.') . '</p>';
+ return $output;
+ }
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function media_wysiwyg_view_mode_entity_info_alter(&$entity_info) {
+ $entity_info['file']['view modes'] += array(
+ 'wysiwyg' => array(
+ 'label' => t('WYSIWYG'),
+ 'custom settings' => TRUE,
+ ),
+ );
+}
+
+/**
+ * Implements hook_media_wysiwyg_wysiwyg_allowed_view_modes_alter().
+ */
+function media_wysiwyg_view_mode_media_wysiwyg_wysiwyg_allowed_view_modes_alter(&$view_modes, &$file) {
+ if (variable_get("media_wysiwyg_view_mode_{$file->type}_wysiwyg_restricted_view_modes_status", FALSE) == TRUE) {
+ $restricted_view_modes = variable_get("media_wysiwyg_view_mode_{$file->type}_wysiwyg_restricted_view_modes", array());
+
+ foreach ($restricted_view_modes as $restricted_view_mode) {
+ if (array_key_exists($restricted_view_mode, $view_modes)) {
+ unset($view_modes[$restricted_view_mode]);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_media_wysiwyg_token_to_markup_alter().
+ */
+function media_wysiwyg_view_mode_media_wysiwyg_token_to_markup_alter(&$element, $tag_info, $settings) {
+ if (!empty($settings['wysiwyg'])) {
+ $file = $tag_info['file'];
+
+ if (variable_get("media_wysiwyg_view_mode_{$file->type}_file_wysiwyg_view_mode_status", FALSE) == TRUE) {
+ $element = media_wysiwyg_get_file_without_label($file, variable_get("media_wysiwyg_view_mode_{$file->type}_file_wysiwyg_view_mode", 'wysiwyg'), $settings);
+ }
+ else {
+ $element = media_wysiwyg_get_file_without_label($file, $tag_info['view_mode'], $settings);
+ }
+ }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function media_wysiwyg_view_mode_form_alter(&$form, $form_state, $form_id) {
+ switch ($form_id) {
+ case 'media_wysiwyg_format_form':
+ $file = $form_state['file'];
+
+ // Check to see if a view mode ("format") has already been specified for
+ // this media item. First, check for a standard form-submitted value.
+ if (!empty($form_state['values']['format'])) {
+ $view_mode = $form_state['values']['format'];
+ }
+ // Second, check the request for a JSON-encoded value.
+ elseif (isset($_GET['fields'])) {
+ $query_fields = drupal_json_decode($_GET['fields']);
+ if (isset($query_fields['format'])) {
+ $view_mode = $query_fields['format'];
+ }
+ }
+ // If we were unable to determine a view mode, or we found a view mode
+ // that does not exist in the list of format options presented on this
+ // form, use the default view mode.
+ if (!isset($view_mode) || !array_key_exists($view_mode, $form['options']['format']['#options'])) {
+ $view_mode = variable_get('media_wysiwyg_wysiwyg_default_view_mode', 'full');
+ }
+
+ $form['preview'] = file_view_file($file, $view_mode);
+ $form['preview']['#prefix'] = '<div id="preview">';
+ $form['preview']['#suffix'] = '</div>';
+
+ if (!isset($form['options']['format']['#default_value'])) {
+ $form['options']['format']['#default_value'] = $view_mode;
+ }
+ $form['options']['format']['#ajax'] = array(
+ 'callback' => 'media_format_form_preview',
+ 'wrapper' => 'preview',
+ );
+
+ $view_modes = media_wysiwyg_get_wysiwyg_allowed_view_modes($file);
+ $formats = $options = array();
+ foreach ($view_modes as $view_mode => $view_mode_info) {
+ //@TODO: Display more verbose information about which formatter and what it does.
+ $options[$view_mode] = $view_mode_info['label'];
+
+ if (variable_get("media_wysiwyg_view_mode_{$file->type}_file_wysiwyg_view_mode_status", FALSE) == TRUE) {
+ $element = media_wysiwyg_get_file_without_label($file, variable_get("media_wysiwyg_view_mode_{$file->type}_file_wysiwyg_view_mode", 'wysiwyg'), array('wysiwyg' => TRUE));
+ }
+ else {
+ $element = media_wysiwyg_get_file_without_label($file, $view_mode, array('wysiwyg' => TRUE));
+ }
+
+ // Make a pretty name out of this.
+ $formats[$view_mode] = drupal_render($element);
+ }
+
+ $form['#formats'] = $formats;
+ break;
+ }
+}
+
+/**
+ * AJAX callback to select the portion of the format form to be updated with a preview.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param array $form_state
+ * An associative array containing the current state of the form.
+ *
+ * @return array
+ * The preview form item.
+ */
+function media_format_form_preview($form, $form_state) {
+ return $form['preview'];
+}
diff --git a/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.test b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.test
new file mode 100644
index 000000000..3d27e0749
--- /dev/null
+++ b/sites/all/modules/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.test
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Tests for media_wysiwyg_view_mode.module.
+ */
+
+/**
+ * Defines base class for media_wysiwyg_view_mode test cases.
+ */
+class MediaWYSIWYGViewModeTestHelper extends MediaWYSIWYGTestHelper {
+ function setUp() {
+ parent::setUp('media_wysiwyg_view_mode');
+
+ $web_user = $this->drupalCreateUser(array('administer media wysiwyg view mode', 'view files', 'use media wysiwyg'));
+ $this->drupalLogin($web_user);
+ }
+}
+
+/**
+ * Test configuring view modes available on the format form.
+ */
+class FormatFormViewModesTest extends MediaWYSIWYGViewModeTestHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Format Form WYSIWYG View Modes',
+ 'description' => 'Test configuring view modes available on the format form.',
+ 'group' => 'Media WYSIWYG View Mode',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ * Configure format form view mode restrictions and ensure that they are followed.
+ */
+ function testAllowedFormatFormViewModes() {
+ // Load the Media WYSIWYG View Mode administration page.
+ $this->drupalGet('admin/config/media/wysiwyg-view-mode');
+ $this->assertResponse(200, t('The privileged user can access the Media WYSIWYG View Mode administration page.'));
+
+ // Create an image file to test with.
+ $files = $this->drupalGetTestFiles('image');
+ $files[0]->status = FILE_STATUS_PERMANENT;
+ $file = file_save($files[0]);
+ $fid = $file->fid;
+
+ // The default view mode should be selected by default.
+ $this->drupalGet('media/' . $fid . '/format-form');
+ $this->assertOptionSelected('edit-format', 'default');
+
+ // Restrict the use of the default view mode.
+ variable_set('media_wysiwyg_view_mode_image_wysiwyg_restricted_view_modes_status', TRUE);
+ $restricted_view_modes = array(
+ 'default' => 'default',
+ );
+ variable_set('media_wysiwyg_view_mode_image_wysiwyg_restricted_view_modes', $restricted_view_modes);
+
+ // The teaser view mode should now be selected by default.
+ $this->drupalGet('media/' . $fid . '/format-form');
+ $this->assertOptionSelected('edit-format', 'teaser');
+ }
+}
diff --git a/sites/all/modules/media/modules/mediafield/mediafield.info b/sites/all/modules/media/modules/mediafield/mediafield.info
new file mode 100644
index 000000000..d1ab05b6e
--- /dev/null
+++ b/sites/all/modules/media/modules/mediafield/mediafield.info
@@ -0,0 +1,12 @@
+name = Media Field
+description = "Provides a field type that stores media-specific data. <em>Deprecated by the core File field type.</em>"
+package = Media
+core = 7.x
+dependencies[] = media
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/modules/mediafield/mediafield.install b/sites/all/modules/media/modules/mediafield/mediafield.install
new file mode 100644
index 000000000..3e867b89a
--- /dev/null
+++ b/sites/all/modules/media/modules/mediafield/mediafield.install
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Install and schema hooks for mediafield.
+ */
+
+/**
+ * Implements hook_field_schema().
+ */
+function mediafield_field_schema($field) {
+ return array(
+ 'columns' => array(
+ 'fid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
+ 'title' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ),
+ 'data' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ // 'description' => 'Used for storing additional information.
+ // Can be harnessed by widgets',
+ ),
+ ),
+ 'indexes' => array(
+ 'fid' => array('fid'),
+ ),
+ 'foreign keys' => array(
+ 'file_managed' => array(
+ 'table' => 'file_managed',
+ 'columns' => array('fid' => 'fid'),
+ ),
+ ),
+ );
+}
diff --git a/sites/all/modules/media/modules/mediafield/mediafield.module b/sites/all/modules/media/modules/mediafield/mediafield.module
new file mode 100644
index 000000000..2dd77f3f2
--- /dev/null
+++ b/sites/all/modules/media/modules/mediafield/mediafield.module
@@ -0,0 +1,323 @@
+<?php
+
+/**
+ * @file
+ * Provide a "Multimedia asset" field.
+ */
+
+/**
+ * Implements hook_field_info().
+ */
+function mediafield_field_info() {
+ return array(
+ 'media' => array(
+ 'label' => t('Multimedia asset'),
+ 'description' => t('This field stores a reference to a multimedia asset.'),
+ 'settings' => array(),
+ 'instance_settings' => array(
+ 'file_extensions' => variable_get('file_entity_default_allowed_extensions', 'jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp mp3 mov mp4 m4a m4v mpeg avi ogg oga ogv weba webp webm'),
+ ),
+ 'default_widget' => 'media_generic',
+ 'default_formatter' => 'media',
+ 'property_type' => 'field_item_file',
+ 'property_callbacks' => array('entity_metadata_field_file_callback'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_widget_info_alter().
+ *
+ * Alter the media file selector so it is available for media fields.
+ */
+function mediafield_field_widget_info_alter(&$info) {
+ $info['media_generic']['field types'][] = 'media';
+}
+
+/**
+ * Implements hook_field_instance_settings_form().
+ */
+function mediafield_field_instance_settings_form($field, $instance) {
+ $settings = $instance['settings'];
+
+ // Make the extension list a little more human-friendly by comma-separation.
+ $extensions = str_replace(' ', ', ', $settings['file_extensions']);
+ $form['file_extensions'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Allowed file extensions for uploaded files'),
+ '#default_value' => $extensions,
+ '#description' => t('Separate extensions with a space or comma and do not include the leading dot.'),
+ '#element_validate' => array('_file_generic_settings_extensions'),
+ // By making this field required, we prevent a potential security issue
+ // that would allow files of any type to be uploaded.
+ '#required' => TRUE,
+ '#maxlength' => 255,
+ );
+
+ return $form;
+}
+
+/**
+ * Implements hook_field_is_empty().
+ */
+function mediafield_field_is_empty($item, $field) {
+ return empty($item['fid']);
+}
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function mediafield_field_formatter_info() {
+ $formatters = array(
+ 'media' => array(
+ 'label' => t('Media'),
+ 'field types' => array('media'),
+ 'settings' => array('file_view_mode' => 'default'),
+ ),
+ );
+
+ return $formatters;
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ */
+function mediafield_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $element = array();
+
+ if ($display['type'] == 'media') {
+ $entity_info = entity_get_info('file');
+ $options = array('default' => t('Default'));
+ foreach ($entity_info['view modes'] as $file_view_mode => $file_view_mode_info) {
+ $options[$file_view_mode] = $file_view_mode_info['label'];
+ }
+ $element['file_view_mode'] = array(
+ '#title' => t('File view mode'),
+ '#type' => 'select',
+ '#default_value' => $settings['file_view_mode'],
+ '#options' => $options,
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function mediafield_field_formatter_settings_summary($field, $instance, $view_mode) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $summary = '';
+
+ if ($display['type'] == 'media') {
+ $entity_info = entity_get_info('file');
+ $file_view_mode_label = isset($entity_info['view modes'][$settings['file_view_mode']]) ? $entity_info['view modes'][$settings['file_view_mode']]['label'] : t('Default');
+ $summary = t('File view mode: @view_mode', array('@view_mode' => $file_view_mode_label));
+ }
+
+ return $summary;
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ */
+function mediafield_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+ $element = array();
+
+ $files = array();
+ foreach ($items as $delta => $item) {
+ if (!empty($item['file'])) {
+ $files[$item['fid']] = $item['file'];
+ }
+ }
+
+ if (!empty($files)) {
+ $output = file_view_multiple($files, $display['settings']['file_view_mode'], 0, $langcode);
+ // Remove the first level from the output array.
+ $element = reset($output);
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_field_prepare_view().
+ */
+function mediafield_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
+ // Collect all file IDs that need loading.
+ $fids = array();
+ foreach ($entities as $id => $entity) {
+ // Load the files from the files table.
+ foreach ($items[$id] as $delta => $item) {
+ if (!empty($item['fid'])) {
+ $fids[] = $item['fid'];
+ }
+ }
+ }
+
+ // Load the file entities.
+ $files = file_load_multiple($fids);
+
+ // Add the loaded file entities to the field item array.
+ foreach ($entities as $id => $entity) {
+ foreach ($items[$id] as $delta => $item) {
+ // If the file does not exist, mark the entire item as empty.
+ if (empty($files[$item['fid']])) {
+ unset($items[$id][$delta]);
+ }
+ else {
+ $items[$id][$delta]['file'] = $files[$item['fid']];
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_field_validate().
+ *
+ * Possible error codes:
+ * - 'media_remote_file_type_not_allowed': The remote file is not an allowed
+ * file type.
+ */
+function mediafield_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) {
+ $allowed_types = array_keys(array_filter($instance['widget']['settings']['allowed_types']));
+
+ // @TODO: merge in stuff from media_uri_value
+ foreach ($items as $delta => $item) {
+ if (empty($item['fid'])) {
+ return TRUE;
+ //@TODO: make support for submiting with just a URI here?
+ }
+
+ $file = file_load($item['fid']);
+
+ // Only validate allowed types if the file is remote and not local.
+ if (!file_entity_file_is_local($file)) {
+ if (!in_array($file->type, $allowed_types)) {
+ $errors[$field['field_name']][$langcode][$delta][] = array(
+ 'error' => 'media_remote_file_type_not_allowed',
+ 'message' => t('%name: Only remote files with the following types are allowed: %types-allowed.', array('%name' => t($instance['label']), '%types-allowed' => !empty($allowed_types) ? implode(', ', $allowed_types) : t('no file types selected'))),
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Implements_hook_field_widget_error().
+ */
+function mediafield_field_widget_error($element, $error, $form, &$form_state) {
+ form_error($element['fid'], $error['message']);
+}
+
+/**
+ * @todo The following hook_field_(insert|update|delete|delete_revision)
+ * implementations are nearly identical to the File module implementations of
+ * the same field hooks. The only differences are:
+ * - We pass 'media' rather than 'file' as the module argument to the
+ * file_usage_(add|delete)() functions.
+ * - We do not delete the file / media entity when its usage count goes to 0.
+ * We should submit a core patch to File module to make it flexible with
+ * respect to the above, so that we can reuse its implementation rather than
+ * duplicating it.
+ */
+
+/**
+ * Implements hook_field_insert().
+ */
+function mediafield_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+ list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+
+ // Add a new usage of each uploaded file.
+ foreach ($items as $item) {
+ $file = (object) $item;
+ file_usage_add($file, 'mediafield', $entity_type, $id);
+ }
+}
+
+/**
+ * Implements hook_field_update().
+ *
+ * Checks for files that have been removed from the object.
+ */
+function mediafield_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
+ list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+
+ // On new revisions, all files are considered to be a new usage and no
+ // deletion of previous file usages are necessary.
+ if (!empty($entity->revision)) {
+ foreach ($items as $item) {
+ $file = (object) $item;
+ file_usage_add($file, 'mediafield', $entity_type, $id);
+ }
+ return;
+ }
+
+ // Build a display of the current FIDs.
+ $current_fids = array();
+ foreach ($items as $item) {
+ $current_fids[] = $item['fid'];
+ }
+
+ // Compare the original field values with the ones that are being saved.
+ $original_fids = array();
+ if (!empty($entity->original->{$field['field_name']}[$langcode])) {
+ foreach ($entity->original->{$field['field_name']}[$langcode] as $original_item) {
+ $original_fids[] = $original_item['fid'];
+ if (isset($original_item['fid']) && !in_array($original_item['fid'], $current_fids)) {
+ // Decrement the file usage count by 1.
+ $file = (object) $original_item;
+ file_usage_delete($file, 'mediafield', $entity_type, $id, 1);
+ }
+ }
+ }
+
+ // Add new usage entries for newly added files.
+ foreach ($items as $item) {
+ if (!in_array($item['fid'], $original_fids)) {
+ $file = (object) $item;
+ file_usage_add($file, 'mediafield', $entity_type, $id);
+ }
+ }
+}
+
+/**
+ * Implements hook_field_delete().
+ */
+function mediafield_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
+ list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+
+ // Delete all file usages within this entity.
+ foreach ($items as $delta => $item) {
+ $file = (object) $item;
+ file_usage_delete($file, 'mediafield', $entity_type, $id, 0);
+ }
+}
+
+/**
+ * Implements hook_field_delete_revision().
+ */
+function mediafield_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) {
+ list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+ foreach ($items as $delta => $item) {
+ // @TODO: Not sure if this is correct
+ $file = (object)$item;
+ if (file_usage_delete($file, 'mediafield', $entity_type, $id, 1)) {
+ $items[$delta] = NULL;
+ }
+ }
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function mediafield_views_api() {
+ return array(
+ 'api' => 3,
+ );
+}
diff --git a/sites/all/modules/media/modules/mediafield/mediafield.views.inc b/sites/all/modules/media/modules/mediafield/mediafield.views.inc
new file mode 100644
index 000000000..bc1e24065
--- /dev/null
+++ b/sites/all/modules/media/modules/mediafield/mediafield.views.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Provide Views data and handlers for mediafield.
+ */
+
+/**
+ * Implements hook_field_views_data().
+ */
+function mediafield_field_views_data($field) {
+ $data = field_views_field_default_views_data($field);
+ foreach ($data as $table_name => $table_data) {
+ // Add the relationship only on the fid field.
+ $data[$table_name][$field['field_name'] . '_fid']['relationship'] = array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'file_managed',
+ 'entity type' => 'file',
+ 'base field' => 'fid',
+ 'label' => t('file from !field_name', array('!field_name' => $field['field_name'])),
+ );
+ }
+
+ return $data;
+}
diff --git a/sites/all/modules/media/templates/media-dialog-page.tpl.php b/sites/all/modules/media/templates/media-dialog-page.tpl.php
new file mode 100644
index 000000000..f779c1dc2
--- /dev/null
+++ b/sites/all/modules/media/templates/media-dialog-page.tpl.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to display a single Drupal page.
+ *
+ * Available variables:
+ *
+ * General utility variables:
+ * - $base_path: The base URL path of the Drupal installation. At the very
+ * least, this will always default to /.
+ * - $directory: The directory the template is located in, e.g. modules/system
+ * or themes/garland.
+ * - $is_front: TRUE if the current page is the front page.
+ * - $logged_in: TRUE if the user is registered and signed in.
+ * - $is_admin: TRUE if the user has permission to access administration pages.
+ *
+ * Site identity:
+ * - $front_page: The URL of the front page. Use this instead of $base_path,
+ * when linking to the front page. This includes the language domain or
+ * prefix.
+ * - $logo: The path to the logo image, as defined in theme configuration.
+ * - $site_name: The name of the site, empty when display has been disabled
+ * in theme settings.
+ * - $site_slogan: The slogan of the site, empty when display has been disabled
+ * in theme settings.
+ *
+ * Navigation:
+ * - $main_menu (array): An array containing the Main menu links for the
+ * site, if they have been configured.
+ * - $secondary_menu (array): An array containing the Secondary menu links for
+ * the site, if they have been configured.
+ * - $breadcrumb: The breadcrumb trail for the current page.
+ *
+ * Page content (in order of occurrence in the default page.tpl.php):
+ * - $title_prefix (array): An array containing additional output populated by
+ * modules, intended to be displayed in front of the main title tag that
+ * appears in the template.
+ * - $title: The page title, for use in the actual HTML content.
+ * - $title_suffix (array): An array containing additional output populated by
+ * modules, intended to be displayed after the main title tag that appears in
+ * the template.
+ * - $messages: HTML for status and error messages. Should be displayed
+ * prominently.
+ * - $tabs (array): Tabs linking to any sub-pages beneath the current page
+ * (e.g., the view and edit tabs when displaying a node).
+ * - $action_links (array): Actions local to the page, such as 'Add menu' on the
+ * menu administration interface.
+ * - $feed_icons: A string of all feed icons for the current page.
+ * - $node: The node object, if there is an automatically-loaded node
+ * associated with the page, and the node ID is the second argument
+ * in the page's path (e.g. node/12345 and node/12345/revisions, but not
+ * comment/reply/12345).
+ *
+ * Regions:
+ * - $page['help']: Dynamic help text, mostly for admin pages.
+ * - $page['highlight']: Items for the highlighted content region.
+ * - $page['content']: The main content of the current page.
+ * - $page['sidebar_first']: Items for the first sidebar.
+ * - $page['sidebar_second']: Items for the second sidebar.
+ * - $page['header']: Items for the header region.
+ * - $page['footer']: Items for the footer region.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_page()
+ * @see template_process()
+ */
+?>
+
+<?php if (isset($messages)) { print $messages; } ?>
+<div id="media-browser-page-wrapper">
+ <div id="media-browser-page">
+ <div id="media-browser-tabset">
+ <div id="branding" class="clearfix">
+ <div>
+ <h1><?php print render($page['content']['system_main']['title']); ?></h1>
+ </div>
+ <div id="media-tabs-wrapper">
+ <?php print render($page['content']['system_main']['tabset']['tabs']); ?>
+ </div>
+ </div>
+ <?php print render($page['content']['system_main']['tabset']['panes']); ?>
+ </div> <!-- /#media-tabs-set -->
+ </div> <!-- /#media-browser-page -->
+</div> <!-- /#media-browser-page-wrapper -->
+
+<?php
+ hide($page['content']['system_main']['tabset']);
+ hide($page['content']['system_main']['title']);
+ print render($page['content']);
+?>
diff --git a/sites/all/modules/media/tests/includes/MediaModuleTest.inc b/sites/all/modules/media/tests/includes/MediaModuleTest.inc
new file mode 100644
index 000000000..5e6c5d5e9
--- /dev/null
+++ b/sites/all/modules/media/tests/includes/MediaModuleTest.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Definition of MediaModuleTest.
+ */
+
+/**
+ * Test browser plugin.
+ */
+class MediaModuleTest extends MediaBrowserPlugin {
+ /**
+ * Implements MediaBrowserPluginInterface::access().
+ */
+ public function access($account = NULL) {
+ return TRUE;
+ }
+
+ /**
+ * Implements MediaBrowserPlugin::view().
+ */
+ public function view() {
+ $build = array();
+ $build['test'] = array(
+ '#markup' => '<p>' . t('Test browser plugin output.') . '</p>',
+ );
+
+ return $build;
+ }
+}
diff --git a/sites/all/modules/media/tests/media.test b/sites/all/modules/media/tests/media.test
new file mode 100644
index 000000000..3594aaf01
--- /dev/null
+++ b/sites/all/modules/media/tests/media.test
@@ -0,0 +1,1187 @@
+<?php
+
+/**
+ * @file
+ * Tests for media.module.
+ */
+
+/**
+ * Provides methods specifically for testing Media module's field handling.
+ */
+class MediaFileFieldTestCase extends DrupalWebTestCase {
+ protected $admin_user;
+
+ function setUp() {
+ // Since this is a base class for many test cases, support the same
+ // flexibility that DrupalWebTestCase::setUp() has for the modules to be
+ // passed in as either an array or a variable number of string arguments.
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
+ }
+ $modules[] = 'media';
+ $modules[] = 'media_module_test';
+ parent::setUp($modules);
+ $this->admin_user = $this->drupalCreateUser(array('access content', 'view files', 'view own files', 'access media browser', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer nodes', 'administer files', 'bypass node access', 'bypass file access'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Retrieves a sample file of the specified type.
+ */
+ function getTestFile($type_name, $size = NULL) {
+ // Get a file to upload.
+ $file = current($this->drupalGetTestFiles($type_name, $size));
+
+ // Add a filesize property to files as would be read by file_load().
+ $file->filesize = filesize($file->uri);
+
+ return $file;
+ }
+
+ /**
+ * Creates a new file entity.
+ *
+ * @param $settings
+ * A list of settings that will be added to the entity defaults.
+ */
+ protected function createFileEntity($settings = array()) {
+ $file = new stdClass();
+
+ // Populate defaults array.
+ $settings += array(
+ 'filepath' => 'Файл для тестирования ' . $this->randomName(), // Prefix with non-latin characters to ensure that all file-related tests work with international filenames.
+ 'filemime' => 'text/plain',
+ 'uid' => 1,
+ 'timestamp' => REQUEST_TIME,
+ 'status' => FILE_STATUS_PERMANENT,
+ 'contents' => "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.",
+ 'scheme' => file_default_scheme(),
+ 'type' => NULL,
+ );
+
+ $filepath = $settings['scheme'] . '://' . $settings['filepath'];
+
+ file_put_contents($filepath, $settings['contents']);
+ $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
+
+ $file = new stdClass();
+ $file->uri = $filepath;
+ $file->filename = drupal_basename($file->uri);
+ $file->filemime = $settings['filemime'];
+ $file->uid = $settings['uid'];
+ $file->timestamp = $settings['timestamp'];
+ $file->filesize = filesize($file->uri);
+ $file->status = $settings['status'];
+ $file->type = $settings['type'];
+
+ // The file type is used as a bundle key, and therefore, must not be NULL.
+ if (!isset($file->type)) {
+ $file->type = FILE_TYPE_NONE;
+ }
+
+ // If the file isn't already assigned a real type, determine what type should
+ // be assigned to it.
+ if ($file->type === FILE_TYPE_NONE) {
+ $type = file_get_type($file);
+ if (isset($type)) {
+ $file->type = $type;
+ }
+ }
+
+ // Write the record directly rather than calling file_save() so we don't
+ // invoke the hooks.
+ $this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, t('The file was added to the database.'), 'Create test file');
+
+ return $file;
+ }
+
+ /**
+ * Creates a new file field.
+ *
+ * @param $name
+ * The name of the new field (all lowercase), exclude the "field_" prefix.
+ * @param $type_name
+ * The node type that this field will be added to.
+ * @param $field_settings
+ * A list of field settings that will be added to the defaults.
+ * @param $instance_settings
+ * A list of instance settings that will be added to the instance defaults.
+ * @param $widget_settings
+ * A list of widget settings that will be added to the widget defaults.
+ */
+ function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) {
+ $field = array(
+ 'field_name' => $name,
+ 'type' => 'file',
+ 'settings' => array(),
+ 'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1,
+ );
+ $field['settings'] = array_merge($field['settings'], $field_settings);
+ field_create_field($field);
+
+ $this->attachFileField($name, 'node', $type_name, $instance_settings, $widget_settings);
+ }
+
+ /**
+ * Attaches a file field to an entity.
+ *
+ * @param $name
+ * The name of the new field (all lowercase), exclude the "field_" prefix.
+ * @param $entity_type
+ * The entity type this field will be added to.
+ * @param $bundle
+ * The bundle this field will be added to.
+ * @param $field_settings
+ * A list of field settings that will be added to the defaults.
+ * @param $instance_settings
+ * A list of instance settings that will be added to the instance defaults.
+ * @param $widget_settings
+ * A list of widget settings that will be added to the widget defaults.
+ */
+ function attachFileField($name, $entity_type, $bundle, $instance_settings = array(), $widget_settings = array()) {
+ $instance = array(
+ 'field_name' => $name,
+ 'label' => $name,
+ 'entity_type' => $entity_type,
+ 'bundle' => $bundle,
+ 'required' => !empty($instance_settings['required']),
+ 'settings' => array(),
+ 'widget' => array(
+ 'type' => 'media_generic',
+ 'settings' => array(),
+ ),
+ );
+ $instance['settings'] = array_merge($instance['settings'], $instance_settings);
+ $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);
+ field_create_instance($instance);
+ }
+
+ /**
+ * Attaches a file to a node.
+ */
+ function attachNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE, $extras = array()) {
+ $langcode = LANGUAGE_NONE;
+ $edit = array(
+ "title" => $this->randomName(),
+ 'revision' => (string) (int) $new_revision,
+ );
+
+ if (is_numeric($nid_or_type)) {
+ $nid = $nid_or_type;
+ }
+ else {
+ // Add a new node.
+ $extras['type'] = $nid_or_type;
+ $node = $this->drupalCreateNode($extras);
+ $nid = $node->nid;
+ // Save at least one revision to better simulate a real site.
+ $this->drupalCreateNode(get_object_vars($node));
+ $node = node_load($nid, NULL, TRUE);
+ $this->assertNotEqual($nid, $node->vid, 'Node revision exists.');
+ }
+
+ // Attach a file to the node.
+ $edit['media[' . $field_name . '_' . $langcode . '_0]'] = $file->fid;
+ $this->drupalPost("node/$nid/edit", $edit, t('Save'));
+
+ return $nid;
+ }
+
+ /**
+ * Replaces a file within a node.
+ */
+ function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
+ $edit = array(
+ $field_name . '[' . LANGUAGE_NONE . '][0][fid]' => $file->fid,
+ 'revision' => (string) (int) $new_revision,
+ );
+
+ $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
+ $this->drupalPost(NULL, $edit, t('Save'));
+ }
+
+ /**
+ * Asserts that a file exists physically on disk.
+ */
+ function assertFileExists($file, $message = NULL) {
+ $message = isset($message) ? $message : format_string('File %file exists on the disk.', array('%file' => $file->uri));
+ $this->assertTrue(is_file($file->uri), $message);
+ }
+
+ /**
+ * Asserts that a file exists in the database.
+ */
+ function assertFileEntryExists($file, $message = NULL) {
+ entity_get_controller('file')->resetCache();
+ $db_file = file_load($file->fid);
+ $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', array('%file' => $file->uri));
+ $this->assertEqual($db_file->uri, $file->uri, $message);
+ }
+
+ /**
+ * Asserts that a file does not exist on disk.
+ */
+ function assertFileNotExists($file, $message = NULL) {
+ $message = isset($message) ? $message : format_string('File %file exists on the disk.', array('%file' => $file->uri));
+ $this->assertFalse(is_file($file->uri), $message);
+ }
+
+ /**
+ * Asserts that a file does not exist in the database.
+ */
+ function assertFileEntryNotExists($file, $message) {
+ entity_get_controller('file')->resetCache();
+ $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', array('%file' => $file->uri));
+ $this->assertFalse(file_load($file->fid), $message);
+ }
+
+ /**
+ * Asserts that a file's status is set to permanent in the database.
+ */
+ function assertFileIsPermanent($file, $message = NULL) {
+ $message = isset($message) ? $message : format_string('File %file is permanent.', array('%file' => $file->uri));
+ $this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message);
+ }
+}
+
+/**
+ * Tests the 'media' element type.
+ *
+ * @todo Create a MediaFileTestCase base class and move MediaFileFieldTestCase
+ * methods that aren't related to fields into it.
+ */
+class MediaElementTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media element test',
+ 'description' => 'Tests the media element type.',
+ 'group' => 'Media',
+ );
+ }
+
+ /**
+ * Tests the media element type.
+ */
+ function testMedia() {
+ // Check that $element['#size'] is passed to the child upload element.
+ $this->drupalGet('media/test');
+ $this->assertFieldByXpath('//input[@name="media[nested_media]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.');
+
+ // Perform the tests with all permutations of $form['#tree'] and
+ // $element['#extended'].
+ foreach (array(0, 1) as $tree) {
+ foreach (array(0, 1) as $extended) {
+ $test_file = $this->getTestFile('text');
+ $test_file->uid = $this->admin_user->uid;
+ $test_file = file_save($test_file);
+ $path = 'media/test/' . $tree . '/' . $extended;
+ $input_base_name = $tree ? 'nested_media' : 'media';
+
+ // Submit without a file.
+ $this->drupalPost($path, array(), t('Save'));
+ $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submitted without a file.');
+
+ // Submit a new file, without using the Attach button.
+ $edit = array('media[' . $input_base_name . ']' => $test_file->fid);
+ $this->drupalPost($path, $edit, t('Save'));
+ $this->assertRaw(t('The file id is %fid.', array('%fid' => $test_file->fid)), 'Submit handler has correct file info.');
+
+ // Now, test the Attach and Remove buttons, with and without Ajax.
+ foreach (array(FALSE) as $ajax) {
+ // Attach, then Submit.
+ $this->drupalGet($path);
+ $edit = array('media[' . $input_base_name . ']' => $test_file->fid);
+ if ($ajax) {
+ $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_attach_button');
+ }
+ else {
+ $this->drupalPost(NULL, $edit, t('Attach'));
+ }
+ $this->drupalPost(NULL, array(), t('Save'));
+ $this->assertRaw(t('The file id is %fid.', array('%fid' => $test_file->fid)), 'Submit handler has correct file info.');
+
+ // Attach, then Remove, then Submit.
+ $this->drupalGet($path);
+ $edit = array('media[' . $input_base_name . ']' => $test_file->fid);
+ if ($ajax) {
+ $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_attach_button');
+ $this->drupalPostAJAX(NULL, array(), $input_base_name . '_remove_button');
+ }
+ else {
+ $this->drupalPost(NULL, $edit, t('Attach'));
+ $this->drupalPost(NULL, array(), t('Remove'));
+ }
+ $this->drupalPost(NULL, array(), t('Save'));
+ $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submission after file attachment and removal was successful.');
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Test media file administration page functionality.
+ */
+class MediaAdminTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media file administration',
+ 'description' => 'Test media file administration page functionality.',
+ 'group' => 'Media',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ // Remove the "view files" permission which is set
+ // by default for all users so we can test this permission
+ // correctly.
+ $roles = user_roles();
+ foreach ($roles as $rid => $role) {
+ user_role_revoke_permissions($rid, array('view files'));
+ }
+
+ $this->base_user_1 = $this->drupalCreateUser(array('administer files'));
+ $this->base_user_2 = $this->drupalCreateUser(array('administer files', 'view own private files'));
+ $this->base_user_3 = $this->drupalCreateUser(array('administer files', 'view private files'));
+ $this->base_user_4 = $this->drupalCreateUser(array('administer files', 'edit any document files', 'delete any document files', 'edit any image files', 'delete any image files'));
+ }
+
+ /**
+ * Tests that the table sorting works on the files admin pages.
+ */
+ function testFilesAdminSort() {
+ $i = 0;
+ foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) {
+ $this->createFileEntity(array('filepath' => $prefix . $this->randomName(6), 'timestamp' => $i));
+ $i++;
+ }
+
+ // Test that the default sort by file_managed.timestamp DESC actually fires properly.
+ $files_query = db_select('file_managed', 'fm')
+ ->fields('fm', array('fid'))
+ ->orderBy('timestamp', 'DESC')
+ ->execute()
+ ->fetchCol();
+
+ $files_form = array();
+ $this->drupalGet('admin/content/file/thumbnails');
+ foreach ($this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]/@data-fid') as $input) {
+ $files_form[] = $input;
+ }
+ $this->assertEqual($files_query, $files_form, 'Files are sorted in the form according to the default query.');
+
+ // Compare the rendered HTML node list to a query for the files ordered by
+ // filename to account for possible database-dependent sort order.
+ $files_query = db_select('file_managed', 'fm')
+ ->fields('fm', array('fid'))
+ ->orderBy('filename')
+ ->execute()
+ ->fetchCol();
+
+ $files_form = array();
+ $this->drupalGet('admin/content/file/thumbnails', array('query' => array('sort' => 'asc', 'order' => 'Title')));
+ foreach ($this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]/@data-fid') as $input) {
+ $files_form[] = $input;
+ }
+ $this->assertEqual($files_query, $files_form, 'Files are sorted in the form the same as they are in the query.');
+ }
+
+ /**
+ * Tests files overview with different user permissions.
+ */
+ function testFilesAdminPages() {
+ $files['public_image'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_1->uid, 'type' => 'image'));
+ $files['public_document'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_2->uid, 'type' => 'document'));
+ $files['private_image'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_1->uid, 'type' => 'image'));
+ $files['private_document'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_2->uid, 'type' => 'document'));
+
+ // Verify view and edit links for any file.
+ $this->drupalGet('admin/content/file/thumbnails');
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $this->assertLinkByHref('file/' . $file->fid);
+ $this->assertLinkByHref('file/' . $file->fid . '/edit');
+ // Verify tableselect.
+ $this->assertFieldByName('files[' . $file->fid . ']', '', t('Tableselect found.'));
+ }
+
+ // Verify no operation links are displayed for regular users.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_1);
+ $this->drupalGet('admin/content/file/thumbnails');
+ $this->assertResponse(200);
+ $this->assertLinkByHref('file/' . $files['public_image']->fid);
+ $this->assertLinkByHref('file/' . $files['public_document']->fid);
+ $this->assertNoLinkByHref('file/' . $files['public_image']->fid . '/edit');
+ $this->assertNoLinkByHref('file/' . $files['public_document']->fid . '/edit');
+
+ // Verify no tableselect.
+ $this->assertNoFieldByName('files[' . $files['public_image']->fid . ']', '', t('No tableselect found.'));
+
+ // Verify private file is displayed with permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_2);
+ $this->drupalGet('admin/content/file/thumbnails');
+ $this->assertResponse(200);
+ $this->assertLinkByHref('file/' . $files['private_document']->fid);
+ // Verify no operation links are displayed.
+ $this->assertNoLinkByHref('file/' . $files['private_document']->fid . '/edit');
+
+ // Verify user cannot see private file of other users.
+ $this->assertNoLinkByHref('file/' . $files['private_image']->fid);
+ $this->assertNoLinkByHref('file/' . $files['private_image']->fid . '/edit');
+
+ // Verify no tableselect.
+ $this->assertNoFieldByName('files[' . $files['private_document']->fid . ']', '', t('No tableselect found.'));
+
+ // Verify private file is displayed with permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_3);
+ $this->drupalGet('admin/content/file/thumbnails');
+ $this->assertResponse(200);
+
+ // Verify user can see private file of other users.
+ $this->assertLinkByHref('file/' . $files['private_document']->fid);
+ $this->assertLinkByHref('file/' . $files['private_image']->fid);
+
+ // Verify operation links are displayed for users with appropriate permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_4);
+ $this->drupalGet('admin/content/file/thumbnails');
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $this->assertLinkByHref('file/' . $file->fid);
+ $this->assertLinkByHref('file/' . $file->fid . '/edit');
+ }
+
+ // Verify file access can be bypassed.
+ $this->drupalLogout();
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet('admin/content/file/thumbnails');
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $this->assertLinkByHref('file/' . $file->fid);
+ $this->assertLinkByHref('file/' . $file->fid . '/edit');
+ }
+ }
+}
+
+/**
+ * Tests the media hooks.
+ */
+class MediaHooksTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media hooks test',
+ 'description' => 'Tests the Media hooks.',
+ 'group' => 'Media',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ * Tests that the media browser hooks.
+ */
+ function testMediaBrowserHooks() {
+ // Enable media_module_test.module's hook_media_browser_plugin_info()
+ // implementation and ensure it is working as designed.
+ variable_set('media_module_test_media_browser_plugin_info', TRUE);
+
+ $this->drupalGet('media/browser');
+ $this->assertRaw(t('Media module test'), 'Custom browser plugin found.');
+
+ // Enable media_module_test.module's hook_media_browser_plugin_info_alter()
+ // implementation and ensure it is working as designed.
+ variable_set('media_module_test_media_browser_plugin_info_alter', TRUE);
+
+ $this->drupalGet('media/browser');
+ $this->assertRaw(t('Altered plugin title'), 'Custom browser plugin was successfully altered.');
+
+ // Enable media_module_test.module's hook_media_browser_plugins_alter()
+ // implementation and ensure it is working as designed.
+ variable_set('media_module_test_media_browser_plugins_alter', TRUE);
+
+ $this->drupalGet('media/browser');
+ $this->assertRaw(t('Altered browser plugin output.'), 'Custom browser plugin was successfully altered before being rendered.');
+ }
+}
+
+/**
+ * Tests the media browser 'Library' tab.
+ */
+class MediaBrowserLibraryTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media browser library test',
+ 'description' => 'Tests the media browser library tab.',
+ 'group' => 'Media',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->base_user_1 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files'));
+ $this->base_user_2 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files', 'view own private files'));
+ $this->base_user_3 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files', 'view private files'));
+ }
+
+ /**
+ * Tests that the views sorting works on the media browser 'Library' tab.
+ */
+ function testFilesBrowserSort() {
+ // Load only the 'Library' tab of the media browser.
+ $options = array(
+ 'query' => array(
+ 'enabledPlugins' => array(
+ 'media_default--media_browser_1' => 'media_default--media_browser_1',
+ ),
+ ),
+ );
+
+ $i = 0;
+ foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) {
+ $this->createFileEntity(array('filepath' => $prefix . $this->randomName(6), 'timestamp' => $i));
+ $i++;
+ }
+
+ // Test that the default sort by file_managed.timestamp DESC actually fires properly.
+ $files_query = db_select('file_managed', 'fm')
+ ->fields('fm', array('fid'))
+ ->orderBy('timestamp', 'DESC')
+ ->execute()
+ ->fetchCol();
+
+ $files_form = array();
+ $this->drupalGet('media/browser', $options);
+ foreach ($this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]/@data-fid') as $input) {
+ $files_form[] = $input;
+ }
+ $this->assertEqual($files_query, $files_form, 'Files are sorted in the form according to the default query.');
+ }
+
+ /**
+ * Tests media browser 'Library' tab with different user permissions.
+ */
+ function testFilesBrowserLibrary() {
+ // Load only the 'Library' tab of the media browser.
+ $options = array(
+ 'query' => array(
+ 'enabledPlugins' => array(
+ 'media_default--media_browser_1' => 'media_default--media_browser_1',
+ ),
+ ),
+ );
+
+ $files['public_image'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_1->uid, 'type' => 'image'));
+ $files['public_document'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_2->uid, 'type' => 'document'));
+ $files['private_image'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_1->uid, 'type' => 'image'));
+ $files['private_document'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_2->uid, 'type' => 'document'));
+
+ // Verify all files are displayed for administrators.
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $file->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $file->fid)));
+ }
+
+ // Verify public files are displayed.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_1);
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['public_image']->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['public_image']->fid)));
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['public_document']->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['public_document']->fid)));
+
+ // Verify private file is displayed with permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_2);
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['private_document']->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['private_document']->fid)));
+
+ // Verify user cannot see private file of other users.
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['private_image']->fid,
+ ));
+ $this->assertNoFieldByXPath($xpath, TRUE, format_string('File with file ID %fid not found.', array('%fid' => $files['private_image']->fid)));
+
+ // Verify private file is displayed with permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_3);
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+
+ // Verify user can see private file of other users.
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['private_document']->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['private_document']->fid)));
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['private_image']->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['private_image']->fid)));
+
+ // Verify file access can be bypassed.
+ $this->drupalLogout();
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $file->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $file->fid)));
+ }
+ }
+}
+
+/**
+ * Tests the media browser settings.
+ */
+class MediaBrowserSettingsTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media browser settings test',
+ 'description' => 'Tests the media browser settings.',
+ 'group' => 'Media',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ }
+
+ /**
+ * Tests the media browser settings.
+ */
+ function testBrowserSettings() {
+ $settings = array(
+ 'scheme' => array('public', 'private'),
+ 'type' => array('image', 'document'),
+ 'extension' => array('jpg', 'txt'),
+ );
+
+ // Perform the tests with unique permutations of $scheme, $type and
+ // $extension.
+ foreach ($settings['scheme'] as $scheme) {
+ foreach ($settings['type'] as $type) {
+ foreach ($settings['extension'] as $extension) {
+ $file = $this->createFileEntity(array('scheme' => $scheme, 'uid' => $this->admin_user->uid, 'type' => $type, 'filemime' => media_get_extension_mimetype($extension)));
+
+ $options = array(
+ 'query' => array(
+ 'enabledPlugins' => array(
+ 'media_default--media_browser_1' => 'media_default--media_browser_1',
+ ),
+ 'schemes' => array($scheme),
+ 'types' => array($type),
+ 'file_extensions' => $extension,
+ ),
+ );
+
+ // Verify that the file is displayed.
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $file->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $file->fid)));
+
+ // Verify that no other files are also displayed.
+ $files = $this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]');
+ $this->assertEqual(count($files), 1, 'There is only one file that matches the current browser configuration.');
+ }
+ }
+ }
+
+ // Perform the tests with none and all of the restrictions.
+ foreach (array('none', 'all') as $restrictions) {
+ $options = array(
+ 'query' => array(
+ 'enabledPlugins' => array(
+ 'media_default--media_browser_1' => 'media_default--media_browser_1',
+ ),
+ ),
+ );
+
+ switch ($restrictions) {
+ case 'none':
+ $options['query']['schemes'] = array();
+ $options['query']['types'] = array();
+ $options['query']['file_extensions'] = array();
+ break;
+ case 'all':
+ $options['query']['schemes'] = $settings['scheme'];
+ $options['query']['types'] = $settings['type'];
+ $options['query']['file_extensions'] = implode(' ', $settings['extension']);
+ break;
+ }
+
+ // Verify that all of the files are displayed.
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ $files = $this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]');
+ $this->assertEqual(count($files), 8, format_string('All of the files were displayed when %restrictions of the restrictions were enabled.', array('%restrictions' => $restrictions)));
+ }
+
+ // Verify that extension restrictions do not affect remote files.
+ $scheme = 'dummy-remote';
+ $type = 'video';
+ $extension = 'mp4';
+
+ $file = $this->createFileEntity(array('scheme' => $scheme, 'uid' => $this->admin_user->uid, 'type' => $type, 'filemime' => media_get_extension_mimetype($extension)));
+
+ $options = array(
+ 'query' => array(
+ 'enabledPlugins' => array(
+ 'media_default--media_browser_1' => 'media_default--media_browser_1',
+ ),
+ 'schemes' => array($scheme, 'public'), // Include a local stream wrapper in order to trigger extension restrictions.
+ 'types' => array($type),
+ 'file_extensions' => 'fake', // Use an invalid file extension to ensure that it does not affect restrictions.
+ ),
+ );
+
+ // Verify that the file is displayed.
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $file->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $file->fid)));
+
+ // Verify that no other files are also displayed.
+ $files = $this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]');
+ $this->assertEqual(count($files), 1, 'There is only one file that matches the current browser configuration.');
+ }
+}
+
+/**
+ * Tests the media browser 'My files' tab.
+ */
+class MediaBrowserMyFilesTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media browser my files test',
+ 'description' => 'Tests the media browser my files tab.',
+ 'group' => 'Media',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->base_user_1 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files'));
+ $this->base_user_2 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files', 'view own files'));
+ $this->base_user_3 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files', 'view own files', 'view own private files'));
+ }
+
+ /**
+ * Tests media browser 'My files' tab with different user permissions.
+ */
+ function testFilesBrowserMyFiles() {
+ // Load only the 'My files' tab of the media browser.
+ $options = array(
+ 'query' => array(
+ 'enabledPlugins' => array(
+ 'media_default--media_browser_my_files' => 'media_default--media_browser_my_files',
+ ),
+ ),
+ );
+
+ $files['public_image'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_2->uid, 'type' => 'image'));
+ $files['public_document'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_3->uid, 'type' => 'document'));
+ $files['private_image'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_2->uid, 'type' => 'image'));
+ $files['private_document'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_3->uid, 'type' => 'document'));
+
+ // Verify administrators do not have any special access to files.
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ foreach ($files as $file) {
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $file->fid,
+ ));
+ $this->assertNoFieldByXPath($xpath, TRUE, format_string('File with file ID %fid not found.', array('%fid' => $file->fid)));
+ }
+
+ // Verify users require the 'view own files' permission in order to access
+ // the 'My files' tab.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_1);
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ $xpath = $this->buildXPathQuery('//div[@class="media_default--media_browser_my_files"]');
+ $this->assertNoFieldByXPath($xpath, TRUE, 'User with insufficient permissions was unable to view the My files tab.');
+
+ // Verify own public files are displayed.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_2);
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['public_image']->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['public_image']->fid)));
+
+ // Verify own private file is displayed with permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_3);
+ $this->drupalGet('media/browser', $options);
+ $this->assertResponse(200);
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['private_document']->fid,
+ ));
+ $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['private_document']->fid)));
+
+ // Verify user cannot see files of other users.
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['public_image']->fid,
+ ));
+ $this->assertNoFieldByXPath($xpath, TRUE, format_string('File with file ID %fid not found.', array('%fid' => $files['public_image']->fid)));
+ $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array(
+ ':fid' => $files['private_image']->fid,
+ ));
+ $this->assertNoFieldByXPath($xpath, TRUE, format_string('File with file ID %fid not found.', array('%fid' => $files['private_image']->fid)));
+ }
+}
+
+/**
+ * Tests the 'media' element type settings.
+ */
+class MediaElementSettingsTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media element settings test',
+ 'description' => 'Tests the media element type JavaScript settings.',
+ 'group' => 'Media',
+ );
+ }
+
+ /**
+ * Tests the media element type settings.
+ */
+ function testElementSettings() {
+ $form = array(
+ '#type' => 'media',
+ );
+ drupal_render($form);
+ $javascript = drupal_get_js();
+ $global = array(
+ 'media' => array(
+ 'global' => array(
+ 'global' => array(
+ 'types' => array(),
+ 'schemes' => array(),
+ ),
+ ),
+ ),
+ );
+ $settings = drupal_json_encode(drupal_array_merge_deep_array($global));
+ $this->assertTrue(strpos($javascript, $settings) > 0, 'Rendered media element adds the global settings.');
+ }
+
+ /**
+ * Tests the media file field widget settings.
+ */
+ function testWidgetSettings() {
+ // Use 'page' instead of 'article', so that the 'article' image field does
+ // not conflict with this test. If in the future the 'page' type gets its
+ // own default file or image field, this test can be made more robust by
+ // using a custom node type.
+ $type_name = 'page';
+ $field_name = strtolower($this->randomName());
+ $this->createFileField($field_name, $type_name);
+ $field = field_info_field($field_name);
+ $instance = field_info_instance('node', $field_name, $type_name);
+
+ $javascript = $this->drupalGet("node/add/$type_name");
+ $field_widget = array(
+ 'elements' => array(
+ '#edit-' . $field_name . '-' . LANGUAGE_NONE . '-0-upload' => array(
+ 'global' => array(
+ 'types' => array(
+ 'image' => 'image',
+ ),
+ 'enabledPlugins' => array(),
+ 'schemes' => array(
+ 'public' => 'public',
+ ),
+ 'file_directory' => '',
+ 'file_extensions' => 'txt',
+ 'max_filesize' => '',
+ 'uri_scheme' => 'public',
+ ),
+ ),
+ ),
+ );
+ $settings = drupal_json_encode(drupal_array_merge_deep_array($field_widget));
+ $this->assertTrue(strpos($javascript, $settings) > 0, 'Media file field widget adds element-specific settings.');
+ }
+}
+
+/**
+ * Tests file handling with node revisions.
+ */
+class MediaFileFieldRevisionTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media file field revision test',
+ 'description' => 'Test creating and deleting revisions with files attached.',
+ 'group' => 'Media',
+ );
+ }
+
+ /**
+ * Tests creating multiple revisions of a node and managing attached files.
+ *
+ * Expected behaviors:
+ * - Adding a new revision will make another entry in the field table, but
+ * the original file will not be duplicated.
+ * - Deleting a revision should not delete the original file if the file
+ * is in use by another revision.
+ * - When the last revision that uses a file is deleted, the original file
+ * should be deleted also.
+ */
+ function testRevisions() {
+ $type_name = 'article';
+ $field_name = strtolower($this->randomName());
+ $this->createFileField($field_name, $type_name);
+ $field = field_info_field($field_name);
+ $instance = field_info_instance('node', $field_name, $type_name);
+
+ // Attach the same fields to users.
+ $this->attachFileField($field_name, 'user', 'user');
+
+ $test_file = $this->getTestFile('text');
+ $test_file->uid = $this->admin_user->uid;
+ $test_file = file_save($test_file);
+
+ // Create a new node with the uploaded file.
+ $nid = $this->attachNodeFile($test_file, $field_name, $type_name);
+
+ // Check that the file exists on disk and in the database.
+ $node = node_load($nid, NULL, TRUE);
+ $node_file_r1 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+ $node_vid_r1 = $node->vid;
+ $this->assertFileExists($node_file_r1, 'New file saved to disk on node creation.');
+ $this->assertFileEntryExists($node_file_r1, 'File entry exists in database on node creation.');
+ $this->assertFileIsPermanent($node_file_r1, 'File is permanent.');
+
+ // Upload another file to the same node in a new revision.
+ $this->replaceNodeFile($test_file, $field_name, $nid);
+ $node = node_load($nid, NULL, TRUE);
+ $node_file_r2 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+ $node_vid_r2 = $node->vid;
+ $this->assertFileExists($node_file_r2, 'Replacement file exists on disk after creating new revision.');
+ $this->assertFileEntryExists($node_file_r2, 'Replacement file entry exists in database after creating new revision.');
+ $this->assertFileIsPermanent($node_file_r2, 'Replacement file is permanent.');
+
+ // Check that the original file is still in place on the first revision.
+ $node = node_load($nid, $node_vid_r1, TRUE);
+ $this->assertEqual($node_file_r1, (object) $node->{$field_name}[LANGUAGE_NONE][0], 'Original file still in place after replacing file in new revision.');
+ $this->assertFileExists($node_file_r1, 'Original file still in place after replacing file in new revision.');
+ $this->assertFileEntryExists($node_file_r1, 'Original file entry still in place after replacing file in new revision');
+ $this->assertFileIsPermanent($node_file_r1, 'Original file is still permanent.');
+
+ // Save a new version of the node without any changes.
+ // Check that the file is still the same as the previous revision.
+ $this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save'));
+ $node = node_load($nid, NULL, TRUE);
+ $node_file_r3 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+ $node_vid_r3 = $node->vid;
+ $this->assertEqual($node_file_r2, $node_file_r3, 'Previous revision file still in place after creating a new revision without a new file.');
+ $this->assertFileIsPermanent($node_file_r3, 'New revision file is permanent.');
+
+ // Revert to the first revision and check that the original file is active.
+ $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert'));
+ $node = node_load($nid, NULL, TRUE);
+ $node_file_r4 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+ $node_vid_r4 = $node->vid;
+ $this->assertEqual($node_file_r1, $node_file_r4, 'Original revision file still in place after reverting to the original revision.');
+ $this->assertFileIsPermanent($node_file_r4, 'Original revision file still permanent after reverting to the original revision.');
+
+ // Delete the second revision and check that the file is kept (since it is
+ // still being used by the third revision).
+ $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete'));
+ $this->assertFileExists($node_file_r3, 'Second file is still available after deleting second revision, since it is being used by the third revision.');
+ $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting second revision, since it is being used by the third revision.');
+ $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting second revision, since it is being used by the third revision.');
+
+ // Attach the second file to a user.
+ $user = $this->drupalCreateUser();
+ $edit = (array) $user;
+ $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3;
+ user_save($user, $edit);
+ $this->drupalGet('user/' . $user->uid . '/edit');
+
+ // Delete the third revision and check that the file is not deleted yet.
+ $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete'));
+ $this->assertFileExists($node_file_r3, 'Second file is still available after deleting third revision, since it is being used by the user.');
+ $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting third revision, since it is being used by the user.');
+ $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting third revision, since it is being used by the user.');
+
+ // Delete the user and check that the file still exists.
+ user_delete($user->uid);
+ // TODO: This seems like a bug in File API. Clearing the stat cache should
+ // not be necessary here. The file really exists, but stream wrappers
+ // doesn't seem to think so unless we clear the PHP file stat() cache.
+ clearstatcache();
+ // @todo Files referenced from entity revisions cannot currently be deleted after the entity is deleted.
+ // @see https://drupal.org/node/1613290
+ // $this->assertFileNotExists($node_file_r3, 'Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.');
+ // $this->assertFileEntryNotExists($node_file_r3, 'Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.');
+
+ // Delete the entire node and check that the original file is deleted.
+ $this->drupalPost('node/' . $nid . '/delete', array(), t('Delete'));
+ $this->assertFileNotExists($node_file_r1, 'Original file is deleted after deleting the entire node with two revisions remaining.');
+ $this->assertFileEntryNotExists($node_file_r1, 'Original file entry is deleted after deleting the entire node with two revisions remaining.');
+ }
+}
+
+/**
+ * Tests various validations.
+ */
+class MediaFileFieldValidateTestCase extends MediaFileFieldTestCase {
+ protected $field;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media file field validation tests',
+ 'description' => 'Tests validation functions such as required.',
+ 'group' => 'Media',
+ );
+ }
+
+ /**
+ * Tests the required property on file fields.
+ */
+ function testRequired() {
+ $type_name = 'article';
+ $field_name = strtolower($this->randomName());
+ $this->createFileField($field_name, $type_name, array(), array('required' => '1'));
+ $field = field_info_field($field_name);
+ $instance = field_info_instance('node', $field_name, $type_name);
+
+ $test_file = $this->getTestFile('text');
+ $test_file->uid = $this->admin_user->uid;
+ $test_file = file_save($test_file);
+
+ // Try to post a new node without attaching a file.
+ $langcode = LANGUAGE_NONE;
+ $edit = array("title" => $this->randomName());
+ $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
+ $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), 'Node save failed when required file field was empty.');
+
+ // Create a new node with the attached file.
+ $nid = $this->attachNodeFile($test_file, $field_name, $type_name);
+ $this->assertTrue($nid !== FALSE, format_string('attachNodeFile(@test_file, @field_name, @type_name) succeeded', array('@test_file' => $test_file->uri, '@field_name' => $field_name, '@type_name' => $type_name)));
+
+ $node = node_load($nid, NULL, TRUE);
+
+ $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+ $this->assertFileExists($node_file, 'File exists after attaching to the required field.');
+ $this->assertFileEntryExists($node_file, 'File entry exists after attaching to the required field.');
+
+ // Try again with a multiple value field.
+ field_delete_field($field_name);
+ $this->createFileField($field_name, $type_name, array('cardinality' => FIELD_CARDINALITY_UNLIMITED), array('required' => '1'));
+
+ // Try to post a new node without attaching a file in the multivalue field.
+ $edit = array('title' => $this->randomName());
+ $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
+ $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), 'Node save failed when required multiple value file field was empty.');
+
+ // Create a new node with the attached file into the multivalue field.
+ $nid = $this->attachNodeFile($test_file, $field_name, $type_name);
+ $node = node_load($nid, NULL, TRUE);
+ $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+ $this->assertFileExists($node_file, 'File exists after attaching to the required multiple value field.');
+ $this->assertFileEntryExists($node_file, 'File entry exists after attaching to the required multipel value field.');
+
+ // Remove our file field.
+ field_delete_field($field_name);
+ }
+}
+
+/**
+ * Tests that formatters are working properly.
+ */
+class MediaFileFieldDisplayTestCase extends MediaFileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media file field display tests',
+ 'description' => 'Test the display of file fields in node and views.',
+ 'group' => 'Media',
+ );
+ }
+
+ /**
+ * Tests normal formatter display on node display.
+ */
+ function testNodeDisplay() {
+ $field_name = strtolower($this->randomName());
+ $type_name = 'article';
+ $field_settings = array(
+ 'display_field' => '1',
+ 'display_default' => '1',
+ );
+ $instance_settings = array(
+ 'description_field' => '1',
+ );
+ $widget_settings = array();
+ $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings);
+ $field = field_info_field($field_name);
+ $instance = field_info_instance('node', $field_name, $type_name);
+
+ // Create a new node *without* the file field set, and check that the field
+ // is not shown for each node display.
+ $node = $this->drupalCreateNode(array('type' => $type_name));
+ $file_formatters = array('file_default', 'file_table', 'file_url_plain', 'hidden');
+ foreach ($file_formatters as $formatter) {
+ $edit = array(
+ "fields[$field_name][type]" => $formatter,
+ );
+ $this->drupalPost("admin/structure/types/manage/$type_name/display", $edit, t('Save'));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertNoText($field_name, format_string('Field label is hidden when no file attached for formatter %formatter', array('%formatter' => $formatter)));
+ }
+
+ $test_file = $this->getTestFile('text');
+ $test_file->uid = $this->admin_user->uid;
+ $test_file = file_save($test_file);
+
+ // Create a new node with the attached file.
+ $nid = $this->attachNodeFile($test_file, $field_name, $type_name);
+ $this->drupalGet('node/' . $nid . '/edit');
+
+ // Check that the media thumbnail is displaying with the file name.
+ $node = node_load($nid, NULL, TRUE);
+ $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+ $thumbnail = media_get_thumbnail_preview($node_file);
+ $default_output = drupal_render($thumbnail);
+ $this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.');
+
+ // Turn the "display" option off and check that the file is no longer displayed.
+ $edit = array($field_name . '[' . LANGUAGE_NONE . '][0][display]' => FALSE);
+ $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));
+
+ $this->assertNoRaw($default_output, 'Field is hidden when "display" option is unchecked.');
+
+ }
+}
diff --git a/sites/all/modules/media/tests/media_module_test.info b/sites/all/modules/media/tests/media_module_test.info
new file mode 100644
index 000000000..e62679488
--- /dev/null
+++ b/sites/all/modules/media/tests/media_module_test.info
@@ -0,0 +1,14 @@
+name = Media test
+description = Provides hooks for testing Media module functionality.
+package = Media
+core = 7.x
+hidden = TRUE
+
+files[] = includes/MediaModuleTest.inc
+
+; Information added by Drupal.org packaging script on 2015-07-14
+version = "7.x-2.0-beta1"
+core = "7.x"
+project = "media"
+datestamp = "1436895542"
+
diff --git a/sites/all/modules/media/tests/media_module_test.module b/sites/all/modules/media/tests/media_module_test.module
new file mode 100644
index 000000000..49c0c4098
--- /dev/null
+++ b/sites/all/modules/media/tests/media_module_test.module
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @file
+ * Provides Media module pages for testing purposes.
+ */
+
+/**
+ * Implements hook_media_browser_plugin_info().
+ */
+function media_module_test_media_browser_plugin_info() {
+ // Allow tests to enable or disable this hook.
+ if (!variable_get('media_module_test_media_browser_plugin_info', FALSE)) {
+ return array();
+ }
+
+ $info['media_module_test'] = array(
+ 'title' => t('Media module test'),
+ 'class' => 'MediaModuleTest',
+ 'weight' => 50,
+ );
+
+ return $info;
+}
+
+/**
+ * Implements hook_media_browser_plugin_info_alter().
+ */
+function media_module_test_media_browser_plugin_info_alter(&$info) {
+ // Allow tests to enable or disable this hook.
+ if (!variable_get('media_module_test_media_browser_plugin_info_alter', FALSE)) {
+ return;
+ }
+
+ $info['media_module_test']['title'] = t('Altered plugin title');
+}
+
+/**
+ * Implements hook_media_browser_plugins_alter().
+ */
+function media_module_test_media_browser_plugins_alter(&$plugin_output) {
+ // Allow tests to enable or disable this hook.
+ if (!variable_get('media_module_test_media_browser_plugins_alter', FALSE)) {
+ return;
+ }
+
+ $plugin_output['media_module_test']['test']['#markup'] = '<p>' . t('Altered browser plugin output.') . '</p>';
+}
+
+/**
+ * Implements hook_menu().
+ */
+function media_module_test_menu() {
+ $items = array();
+
+ $items['media/test'] = array(
+ 'title' => 'Media test',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_module_test_form'),
+ 'access arguments' => array('view files'),
+ );
+
+ return $items;
+}
+
+/**
+ * Form constructor for testing a 'media' element.
+ *
+ * @see media_module_test_form_submit()
+ * @ingroup forms
+ */
+function media_module_test_form($form, &$form_state, $tree = TRUE, $extended = FALSE) {
+ $form['#tree'] = (bool) $tree;
+
+ $form['nested']['media'] = array(
+ '#type' => 'media',
+ '#title' => t('Media'),
+ '#extended' => (bool) $extended,
+ '#size' => 13,
+ );
+
+ $form['textfield'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Type a value and ensure it stays'),
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+
+ return $form;
+}
+
+/**
+ * Form submission handler for media_module_test_form().
+ */
+function media_module_test_form_submit($form, &$form_state) {
+ if ($form['#tree']) {
+ $fid = $form['nested']['media']['#extended'] ? $form_state['values']['nested']['media']['fid'] : $form_state['values']['nested']['media'];
+ }
+ else {
+ $fid = $form['nested']['media']['#extended'] ? $form_state['values']['media']['fid'] : $form_state['values']['media'];
+ }
+ drupal_set_message(t('The file id is %fid.', array('%fid' => $fid)));
+}
diff --git a/sites/all/modules/media/views/media_default.view.inc b/sites/all/modules/media/views/media_default.view.inc
new file mode 100644
index 000000000..54074650e
--- /dev/null
+++ b/sites/all/modules/media/views/media_default.view.inc
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * @file
+ * The default view for the media browser library tab.
+ */
+
+$view = new view();
+$view->name = 'media_default';
+$view->description = 'Default view for the media browser library tab.';
+$view->tag = 'media, default';
+$view->base_table = 'file_managed';
+$view->human_name = 'Media browser';
+$view->core = 7;
+$view->api_version = '3.0';
+$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+/* Display: Master */
+$handler = $view->new_display('default', 'Master', 'default');
+$handler->display->display_options['use_ajax'] = TRUE;
+$handler->display->display_options['use_more_always'] = FALSE;
+$handler->display->display_options['group_by'] = TRUE;
+$handler->display->display_options['access']['type'] = 'perm';
+$handler->display->display_options['access']['perm'] = 'view files';
+$handler->display->display_options['cache']['type'] = 'none';
+$handler->display->display_options['query']['type'] = 'views_query';
+$handler->display->display_options['query']['options']['query_tags'] = array(
+ 0 => 'media_browser',
+);
+$handler->display->display_options['exposed_form']['type'] = 'basic';
+$handler->display->display_options['exposed_form']['options']['reset_button'] = FALSE;
+$handler->display->display_options['pager']['type'] = 'full';
+$handler->display->display_options['pager']['options']['items_per_page'] = '25';
+$handler->display->display_options['pager']['options']['offset'] = '0';
+$handler->display->display_options['pager']['options']['id'] = '0';
+$handler->display->display_options['style_plugin'] = 'media_browser';
+/* No results behavior: Global: Text area */
+$handler->display->display_options['empty']['area']['id'] = 'area';
+$handler->display->display_options['empty']['area']['table'] = 'views';
+$handler->display->display_options['empty']['area']['field'] = 'area';
+$handler->display->display_options['empty']['area']['content'] = 'No files available.';
+$handler->display->display_options['empty']['area']['format'] = filter_fallback_format();
+/* Field: File: Name */
+$handler->display->display_options['fields']['filename']['id'] = 'filename';
+$handler->display->display_options['fields']['filename']['table'] = 'file_managed';
+$handler->display->display_options['fields']['filename']['field'] = 'filename';
+$handler->display->display_options['fields']['filename']['label'] = '';
+$handler->display->display_options['fields']['filename']['alter']['word_boundary'] = FALSE;
+$handler->display->display_options['fields']['filename']['alter']['ellipsis'] = FALSE;
+$handler->display->display_options['fields']['filename']['link_to_file'] = TRUE;
+/* Sort criterion: File: Upload date */
+$handler->display->display_options['sorts']['timestamp']['id'] = 'timestamp';
+$handler->display->display_options['sorts']['timestamp']['table'] = 'file_managed';
+$handler->display->display_options['sorts']['timestamp']['field'] = 'timestamp';
+$handler->display->display_options['sorts']['timestamp']['order'] = 'DESC';
+$handler->display->display_options['sorts']['timestamp']['exposed'] = TRUE;
+$handler->display->display_options['sorts']['timestamp']['expose']['label'] = 'Upload date';
+/* Sort criterion: SUM(File Usage: Use count) */
+$handler->display->display_options['sorts']['count']['id'] = 'count';
+$handler->display->display_options['sorts']['count']['table'] = 'file_usage';
+$handler->display->display_options['sorts']['count']['field'] = 'count';
+$handler->display->display_options['sorts']['count']['group_type'] = 'sum';
+$handler->display->display_options['sorts']['count']['exposed'] = TRUE;
+$handler->display->display_options['sorts']['count']['expose']['label'] = 'Use count';
+/* Filter criterion: File: Status */
+$handler->display->display_options['filters']['status']['id'] = 'status';
+$handler->display->display_options['filters']['status']['table'] = 'file_managed';
+$handler->display->display_options['filters']['status']['field'] = 'status';
+$handler->display->display_options['filters']['status']['value'] = array(
+ 1 => '1',
+);
+/* Filter criterion: File: Name */
+$handler->display->display_options['filters']['filename']['id'] = 'filename';
+$handler->display->display_options['filters']['filename']['table'] = 'file_managed';
+$handler->display->display_options['filters']['filename']['field'] = 'filename';
+$handler->display->display_options['filters']['filename']['operator'] = 'contains';
+$handler->display->display_options['filters']['filename']['exposed'] = TRUE;
+$handler->display->display_options['filters']['filename']['expose']['operator_id'] = 'filename_op';
+$handler->display->display_options['filters']['filename']['expose']['label'] = 'File name';
+$handler->display->display_options['filters']['filename']['expose']['operator'] = 'filename_op';
+$handler->display->display_options['filters']['filename']['expose']['identifier'] = 'filename';
+/* Filter criterion: File: Type */
+$handler->display->display_options['filters']['type']['id'] = 'type';
+$handler->display->display_options['filters']['type']['table'] = 'file_managed';
+$handler->display->display_options['filters']['type']['field'] = 'type';
+$handler->display->display_options['filters']['type']['exposed'] = TRUE;
+$handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+$handler->display->display_options['filters']['type']['expose']['label'] = 'Type';
+$handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op';
+$handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+
+/* Display: Media browser */
+$handler = $view->new_display('media_browser', 'Media browser', 'media_browser_1');
+$handler->display->display_options['defaults']['title'] = FALSE;
+$handler->display->display_options['title'] = 'Library';
+$handler->display->display_options['defaults']['hide_admin_links'] = FALSE;
+
+/* Display: My files */
+$handler = $view->new_display('media_browser', 'My files', 'media_browser_my_files');
+$handler->display->display_options['defaults']['title'] = FALSE;
+$handler->display->display_options['title'] = 'My files';
+$handler->display->display_options['defaults']['hide_admin_links'] = FALSE;
+$handler->display->display_options['defaults']['access'] = FALSE;
+$handler->display->display_options['access']['type'] = 'perm';
+$handler->display->display_options['access']['perm'] = 'view own files';
+$handler->display->display_options['defaults']['relationships'] = FALSE;
+/* Relationship: File: User who uploaded */
+$handler->display->display_options['relationships']['uid']['id'] = 'uid';
+$handler->display->display_options['relationships']['uid']['table'] = 'file_managed';
+$handler->display->display_options['relationships']['uid']['field'] = 'uid';
+$handler->display->display_options['relationships']['uid']['required'] = TRUE;
+$handler->display->display_options['defaults']['arguments'] = FALSE;
+$handler->display->display_options['defaults']['filter_groups'] = FALSE;
+$handler->display->display_options['defaults']['filters'] = FALSE;
+/* Filter criterion: File: Status */
+$handler->display->display_options['filters']['status']['id'] = 'status';
+$handler->display->display_options['filters']['status']['table'] = 'file_managed';
+$handler->display->display_options['filters']['status']['field'] = 'status';
+$handler->display->display_options['filters']['status']['value'] = array(
+ 1 => '1',
+);
+/* Filter criterion: File: Name */
+$handler->display->display_options['filters']['filename']['id'] = 'filename';
+$handler->display->display_options['filters']['filename']['table'] = 'file_managed';
+$handler->display->display_options['filters']['filename']['field'] = 'filename';
+$handler->display->display_options['filters']['filename']['operator'] = 'contains';
+$handler->display->display_options['filters']['filename']['exposed'] = TRUE;
+$handler->display->display_options['filters']['filename']['expose']['operator_id'] = 'filename_op';
+$handler->display->display_options['filters']['filename']['expose']['label'] = 'File name';
+$handler->display->display_options['filters']['filename']['expose']['operator'] = 'filename_op';
+$handler->display->display_options['filters']['filename']['expose']['identifier'] = 'filename';
+/* Filter criterion: File: Type */
+$handler->display->display_options['filters']['type']['id'] = 'type';
+$handler->display->display_options['filters']['type']['table'] = 'file_managed';
+$handler->display->display_options['filters']['type']['field'] = 'type';
+$handler->display->display_options['filters']['type']['exposed'] = TRUE;
+$handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+$handler->display->display_options['filters']['type']['expose']['label'] = 'Type';
+$handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op';
+$handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+/* Filter criterion: User: Current */
+$handler->display->display_options['filters']['uid_current']['id'] = 'uid_current';
+$handler->display->display_options['filters']['uid_current']['table'] = 'users';
+$handler->display->display_options['filters']['uid_current']['field'] = 'uid_current';
+$handler->display->display_options['filters']['uid_current']['relationship'] = 'uid';
+$handler->display->display_options['filters']['uid_current']['value'] = '1';
+$translatables['media_default'] = array(
+ t('Master'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('« first'),
+ t('‹ previous'),
+ t('next ›'),
+ t('last »'),
+ t('No files available.'),
+ t('Upload date'),
+ t('Use count'),
+ t('File name'),
+ t('Type'),
+ t('Media browser'),
+ t('Library'),
+ t('My files'),
+ t('User who uploaded'),
+);
diff --git a/sites/all/modules/media_browser_plus/LICENSE.txt b/sites/all/modules/media_browser_plus/LICENSE.txt
new file mode 100755
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/media_browser_plus/README.txt b/sites/all/modules/media_browser_plus/README.txt
new file mode 100644
index 000000000..953c5c768
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/README.txt
@@ -0,0 +1,9 @@
+Media Browser Plus (MBP) provides an enhaced UI and additional features for
+managing files.
+
+This version of MBP (Branch 3.x) works only with Media >=7.x-2.0-unstable7
+Visit http://drupal.org/project/media_browser_plus for details.
+
+Credits:
+Icons: http://openiconlibrary.sourceforge.net/
+jQuery Plugin Pattern: http://coding.smashingmagazine.com/2011/10/11/essential-jquery-plugin-patterns/
diff --git a/sites/all/modules/media_browser_plus/css/media_browser_plus.views.css b/sites/all/modules/media_browser_plus/css/media_browser_plus.views.css
new file mode 100644
index 000000000..0c5125bb3
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/css/media_browser_plus.views.css
@@ -0,0 +1,91 @@
+/**
+ * Formatting the media browser plus views.
+ */
+
+.view .mbp-folders,
+.view .mbp-file-list {
+ vertical-align: top;
+}
+
+.view .mbp-folders ol {
+ padding: 0px;
+ margin: 0px;
+}
+
+.view .mbp-folders ol ol {
+ padding-left: 15px;
+}
+
+.view .mbp-folders li,
+.view .mbp-folders li.open {
+ cursor: pointer;
+ list-style-type: none;
+ margin-bottom: 2px;
+ margin-top: 2px;
+}
+
+.view .mbp-folders li .icon {
+ line-height: 20px;
+ display: inline-block;
+ background: url('../images/icons/folder_icons.png') 0 0 no-repeat;
+ width: 31px;
+ height: 20px;
+}
+
+.view .mbp-folders li.open > div.folder-name .icon {
+ background: url('../images/icons/folder_icons.png') 0 -20px no-repeat;
+}
+.view .mbp-folders li.active > div.folder-name .icon {
+ background: url('../images/icons/folder_icons.png') -31px 0px no-repeat;
+}
+.view .mbp-folders li.active.open > div.folder-name .icon {
+ background: url('../images/icons/folder_icons.png') -31px -20px no-repeat;
+}
+
+.view .mbp-folders li.active > div.folder-name {
+ background-color: #E0E0E0;
+}
+
+.view .mbp-folders li div.folder-name.drag-hover {
+ background-color: #CCCCCC;
+}
+
+.view .mbp-folders li ol {
+ display: none;
+}
+
+.view .mbp-folders li .ajax-progress-throbber {
+ float: right;
+}
+
+.view .mbp-file-list {
+ border-left: 1px solid #cccccc;
+ padding-left: 10px;
+}
+
+/**
+ * VBO based field handler
+ */
+div.view-media-browser-plus .media-list-thumbnails .form-type-checkbox {
+ bottom: 0px;
+ left: 0px;
+}
+
+/**
+ * Media Basket
+ */
+.mbp-file-basket-list li {
+ float: left;
+ list-style: none;
+ margin: 0 10px 10px 0;
+}
+.mbp-file-basket-list .form-type-checkbox{
+ bottom: 0px;
+ left: 0px;
+ margin: 0px;
+ padding: 0px;
+}
+
+.mbp-file-basket.drag-hover {
+ background-color: #e0e0d8;
+}
diff --git a/sites/all/modules/media_browser_plus/images/icons/folder_icons.png b/sites/all/modules/media_browser_plus/images/icons/folder_icons.png
new file mode 100644
index 000000000..d922d078e
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/images/icons/folder_icons.png
Binary files differ
diff --git a/sites/all/modules/media_browser_plus/images/view-refresh-5.png b/sites/all/modules/media_browser_plus/images/view-refresh-5.png
new file mode 100644
index 000000000..3b416cc92
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/images/view-refresh-5.png
Binary files differ
diff --git a/sites/all/modules/media_browser_plus/includes/media_browser_plus.admin.inc b/sites/all/modules/media_browser_plus/includes/media_browser_plus.admin.inc
new file mode 100644
index 000000000..ea6696e94
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/includes/media_browser_plus.admin.inc
@@ -0,0 +1,95 @@
+<?php
+/**
+ * @file
+ * Administrative stuff.
+ */
+
+/**
+ * Media browser plus settings form.
+ *
+ * @see system_settings_form()
+ */
+function media_browser_plus_media_settings($form, &$form_state = array()) {
+ $form['media_browser_plus_thumbnails_as_default_browser'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use Media Browser Plus thumbnails view as default.'),
+ '#description' => t('If enabled the thumbnails view will displayed when accessing "admin/content/file".'),
+ '#default_value' => variable_get('media_browser_plus_thumbnails_as_default_browser', TRUE),
+ );
+ $form['media_browser_plus_thumbnails_as_default_browser_current'] = array(
+ '#type' => 'value',
+ '#default_value' => variable_get('media_browser_plus_thumbnails_as_default_browser', TRUE),
+ );
+ $form['media_browser_plus_disable_default_view'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Disable the provided default view.'),
+ '#description' => t('Disableds the view shipped with the module. Handy when using features and a modified view.'),
+ '#default_value' => variable_get('media_browser_plus_disable_default_view', FALSE),
+ );
+
+ $form['advanced'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Advanced settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['advanced']['media_browser_plus_filesystem_folders'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create folders in filesystem'),
+ '#default_value' => variable_get('media_browser_plus_filesystem_folders', TRUE),
+ '#description' => t('If enabled the "virtual" folder structure will be created in the filesystem too. This helps to organize the files on the disk.'),
+ );
+ $form['advanced']['root_folder'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Media Root folder'),
+ '#default_value' => variable_get('media_root_folder'),
+ '#description' => t("The root folder for files handled by the media module. <strong>Attention: Changing this will move all existing files in the file system too!</strong>"),
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name="media_browser_plus_filesystem_folders"]' => array('checked' => FALSE),
+ ),
+ ),
+ );
+ $form['advanced']['current_root_folder'] = array(
+ '#type' => 'value',
+ '#default_value' => variable_get('media_root_folder'),
+ );
+
+ $form['#submit'][] = 'media_browser_plus_media_settings_submit';
+
+ $form = system_settings_form($form);
+ if (variable_get('media_browser_plus_filesystem_folders', TRUE)) {
+ $form['actions']['rebuild_folder_structure'] = array(
+ '#type' => 'submit',
+ '#value' => t('Rebuild folder structure'),
+ '#name' => 'rebuild_folder_structure',
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Submit handler for the Media browser plus settings form.
+ *
+ * The settings itself are stored by system_settings_form_submit()
+ */
+function media_browser_plus_media_settings_submit($form, &$form_state) {
+ // Check if the root folder was changed and move files if necessary.
+ $old_root = trim($form_state['values']['current_root_folder'], '/');
+ $new_root = trim($form_state['values']['root_folder'], '/');
+ if ($old_root != $new_root) {
+ module_load_include('inc', 'media_browser_plus', 'includes/media_browser_plus.folders');
+ media_browser_plus_move_root_folder($old_root, $new_root);
+ }
+ // Check if a folder structure rebuild was requested.
+ if ($form_state['triggering_element']['#name'] == 'rebuild_folder_structure') {
+ module_load_include('inc', 'media_browser_plus', 'includes/media_browser_plus.folders');
+ media_browser_plus_rebuild_folder_structure();
+ }
+
+ // If the menu position of MBP was changed, rebuild the menu.
+ if ($form_state['values']['media_browser_plus_thumbnails_as_default_browser_current'] != $form_state['values']['media_browser_plus_thumbnails_as_default_browser']) {
+ menu_rebuild();
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/includes/media_browser_plus.folders.inc b/sites/all/modules/media_browser_plus/includes/media_browser_plus.folders.inc
new file mode 100644
index 000000000..e8620d439
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/includes/media_browser_plus.folders.inc
@@ -0,0 +1,306 @@
+<?php
+/**
+ * @file
+ * Folder manipulation functions.
+ */
+
+/**
+ * Moves the root folder of media files.
+ *
+ * Updates the variable media_root_folder too.
+ *
+ * @param string $source
+ * Source path.
+ * @param string $destination
+ * Destination path.
+ */
+function media_browser_plus_move_root_folder($source, $destination) {
+ if (!empty($source)) {
+ $source .= '/';
+ }
+ if (!empty($destination)) {
+ $destination .= '/';
+ }
+ // Load root folder term.
+ $root_folder_term = media_browser_plus_get_media_root_folder();
+
+ // Prepare destination.
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $directory = file_stream_wrapper_uri_normalize($scheme . '://' . $destination);
+ file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+ }
+ variable_set('media_root_folder', trim($destination, '/'));
+
+ // Move media files in root folder itself. We do this because if the root
+ // folder was located in the default file directory of Drupal we can't move
+ // all files / folders.
+ $file_query = new EntityFieldQuery();
+ $files = $file_query
+ ->entityCondition('entity_type', 'file')
+ ->fieldCondition('field_folder', 'tid', $root_folder_term->tid)
+ ->execute();
+ if (!empty($files['file'])) {
+ $file_entities = file_load_multiple(array_keys($files['file']));
+ foreach ($file_entities as $file_entity) {
+ file_move($file_entity, file_stream_wrapper_uri_normalize(file_uri_scheme($file_entity->uri) . '://' . $destination));
+ }
+ }
+ // Collect the subfolder operations.
+ $batch = array(
+ 'title' => t('Move root folder'),
+ 'operations' => array(),
+ 'finished' => 'media_browser_plus_move_root_folder_complete',
+ 'file' => drupal_get_path('module', 'media_browser_plus') . '/includes/media_browser_plus.folders.inc',
+ );
+
+ // Set the media root folder variable to the new destination.
+ $batch['operations'] = array();
+
+ // Move subfolders.
+ $root_subfolders = taxonomy_get_children($root_folder_term->tid);
+ foreach ($root_subfolders as $subfolder) {
+ $subfolder_source = media_browser_plus_construct_dir_path($subfolder);
+ $subfolder_destination = $destination . str_replace($source, '', $subfolder_source);
+ // Already takes care of multiple stream wrappers.
+ $batch['operations'] = array_merge($batch['operations'], media_browser_plus_move_subfolder($subfolder, $subfolder_source, $subfolder_destination));
+ }
+ batch_set($batch);
+}
+
+/**
+ * Finish-callback for the move root folder batch.
+ */
+function media_browser_plus_move_root_folder_complete($success, $results, $operations) {
+ if ($success) {
+ drupal_set_message(t('Successfully changed media root folder and all file URIs'));
+ }
+ else {
+ drupal_set_message(t('Error while changing media root folder'), 'error');
+ }
+}
+
+/**
+ * Helper function to move a subfolder - and update the related files.
+ *
+ * @param object $folder
+ * The _updated_ folder term. Is used to generate the destination.
+ * @param string $source
+ * The current path without scheme.
+ * @param string $destination
+ * The new path. Including the directory name but without scheme!
+ *
+ * @return array
+ * A list of batch operations to execute to finish the job.
+ */
+function media_browser_plus_move_subfolder($folder, $source, $destination) {
+ $operations = array();
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $source_path = file_stream_wrapper_uri_normalize($scheme . '://' . $source);
+ $destination_path = file_stream_wrapper_uri_normalize($scheme . '://' . $destination);
+ if ($source != $destination) {
+ // This will move all subfolders too. Thus we have to handle all child
+ // folder terms as well.
+ file_prepare_directory($destination_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+ media_browser_plus_move_physical_folder($source_path, $destination_path);
+ }
+ // @todo This could move into the condition, but this way it's more stable.
+ // Update files in folder and subfolders get folders.
+ $folders = array($folder->tid);
+
+ // Fetch all sub-folders.
+ $sub_folders = taxonomy_get_tree($folder->vid, $folder->tid);
+ foreach ($sub_folders as $sub_folder) {
+ $folders[] = $sub_folder->tid;
+ }
+ $operations[] = array('media_browser_plus_folder_update_file_locations_batch', array($folders));
+ }
+
+ return $operations;
+}
+
+/**
+ * Batch function to move files to the location according the assigned folder.
+ *
+ * Adjust the file uri to match the path given by the assigned folder. If the
+ * uri is adjusted hook_file_move() is invoked.
+ * If the file isn't located in the related directory on the disk it's moved.
+ * The legacy path of the file is logged in $context['handled_directories'].
+ *
+ * @param array $folders
+ * A list of folder ids (term ids).
+ * @param array $context
+ * Batch context array.
+ */
+function media_browser_plus_folder_update_file_locations_batch($folders, &$context) {
+ $step_size = 25;
+
+ $file_query = new EntityFieldQuery();
+ $file_query
+ ->entityCondition('entity_type', 'file');
+ if (!empty($folders)) {
+ $file_query->fieldCondition('field_folder', 'tid', $folders, 'IN');
+ }
+
+ if (empty($context['sandbox'])) {
+ $context['sandbox']['progress'] = 0;
+ $files = $file_query->execute();
+ $context['sandbox']['max'] = (!empty($files['file'])) ? count($files['file']) : 0;
+ $context['local_stream_wrappers'] = media_get_local_stream_wrappers();
+ }
+ if (!isset($context['results'])) {
+ $context['results'] = array('success' => array(), 'errors' => array());
+ }
+
+ $file_query->range($context['sandbox']['progress'], $step_size);
+ $files = $file_query->execute();
+ $file_entities = array();
+ if (!empty($files['file'])) {
+ $file_entities = file_load_multiple(array_keys($files['file']));
+ }
+ // Checking media.
+ foreach ($file_entities as $file) {
+ // Only handle locale files with a folder id.
+ if (isset($context['local_stream_wrappers'][file_uri_scheme($file->uri)]) && isset($file->field_folder[LANGUAGE_NONE][0]['tid'])) {
+ // Update file path.
+ $source = clone $file;
+ $path = media_browser_plus_construct_dir_path(taxonomy_term_load($file->field_folder[LANGUAGE_NONE][0]['tid']));
+ $file->uri = file_stream_wrapper_uri_normalize(file_uri_scheme($file->uri) . '://' . $path . '/' . drupal_basename($file->uri));
+ // Check if the uri has changed.
+ if ($file->uri !== $source->uri) {
+ // Check if the source file still exists, if so move the file.
+ clearstatcache();
+ if (file_exists(drupal_realpath($source->uri))) {
+ $context['handled_directories'][drupal_dirname($source->uri)] = drupal_dirname($source->uri);
+ $destination = drupal_dirname($file->uri);
+ file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+ file_unmanaged_move($source->uri, $destination, FILE_EXISTS_RENAME);
+ }
+ // Save all the changes and inform other modules.
+ file_save($file);
+ // Inform modules that the file has been moved.
+ module_invoke_all('file_move', $file, $source);
+ }
+ }
+ }
+ // Increment progress but make sure start is not above max (for progress).
+ $context['sandbox']['progress'] = min($context['sandbox']['max'], ($context['sandbox']['progress'] + $step_size));
+ // Set other context values.
+ $context['message'] = t('Relocating files') . '...(' . $context['sandbox']['progress'] . '/' . $context['sandbox']['max'] . ') ';
+ $context['finished'] = 1;
+ if ($context['sandbox']['progress'] < $context['sandbox']['max']) {
+ $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ }
+}
+
+/**
+ * Cut-paste a directory with its children into a new filesystem location.
+ *
+ * @param string $source
+ * The current path.
+ * @param string $destination
+ * The new path.
+ *
+ * @return bool
+ * TRUE on success.
+ */
+function media_browser_plus_move_physical_folder($source, $destination) {
+ $destination = drupal_realpath($destination);
+ $source = drupal_realpath($source);
+ $jail = drupal_realpath(variable_get('file_default_scheme', 'public') . '://');
+ $files = @scandir($source);
+ if ($files && count($files) > 2) {
+ $transfer = new FileTransferLocal($jail);
+ clearstatcache();
+ // We need to copy the children first and later handle the source folder
+ // since this is how FileTransferLocal works.
+ foreach ($files as $file) {
+ if (!in_array($file, array('.', '..'))) {
+ $source_path = $source . DIRECTORY_SEPARATOR . $file;
+ $copy_destination = $destination . DIRECTORY_SEPARATOR . $file;
+ if (is_file($source_path)) {
+ $transfer->copyFile($source_path, $copy_destination);
+ $transfer->removeFile($source_path);
+ }
+ else {
+ $transfer->copyDirectory($source_path, $copy_destination);
+ $transfer->removeDirectory($source_path);
+ }
+ }
+ }
+ // All stuff is moved to destination, delete the source now.
+ $transfer->removeDirectory($source);
+ }
+ else {
+ // The folder is empty so just delete and create the new one.
+ if (file_exists($source)) {
+ drupal_rmdir($source);
+ }
+ file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+ }
+ return TRUE;
+}
+
+
+/**
+ * Rebuilds the folder structure on the disk.
+ *
+ * All local files that have a folder id will be processed. If a file isn't
+ * located where it should be it's moved to the correct place.
+ * This will trigger hook_file_move() so that other modules can act.
+ * After all files are processed the legacy directories are deleted if they're
+ * empty.
+ *
+ * @see hook_file_move()
+ */
+function media_browser_plus_rebuild_folder_structure() {
+ // Prepare batch.
+ $batch = array(
+ 'title' => t('Rebuild folder structure'),
+ 'operations' => array(
+ array('media_browser_plus_rebuild_folder_structure_process', array()),
+ ),
+ 'finished' => 'media_browser_plus_rebuild_folder_structure_complete',
+ 'file' => drupal_get_path('module', 'media_browser_plus') . '/includes/media_browser_plus.folders.inc',
+ );
+ batch_set($batch);
+}
+
+/**
+ * Batch process of folder rebuild moves files and delete leftover directories.
+ *
+ * @see media_browser_plus_rebuild_folder_structure()
+ */
+function media_browser_plus_rebuild_folder_structure_process(&$context) {
+ // Reuse existing code to move the files.
+ media_browser_plus_folder_update_file_locations_batch(array(), $context);
+ // Cleanup empty directories.
+ if ($context['finished'] >= 1 && !empty($context['handled_directories'])) {
+ clearstatcache();
+ foreach ($context['handled_directories'] as $uri) {
+ $directory = drupal_realpath($uri);
+ if (is_dir($directory)) {
+ foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $filename => $file) {
+ if ($file->isDir()) {
+ @drupal_rmdir($filename);
+ }
+ elseif ($file->isFile()) {
+ // If there's a file left, don't delete the folder.
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Finish callback for the folder structure rebuild batch.
+ */
+function media_browser_plus_rebuild_folder_structure_complete($success, $results, $operations) {
+ if ($success) {
+ drupal_set_message(t('Successfully rebuild the folder structure'));
+ }
+ else {
+ drupal_set_message(t('Error while rebuilding folder structure'), 'error');
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/js/media_browser_plus.js b/sites/all/modules/media_browser_plus/js/media_browser_plus.js
new file mode 100644
index 000000000..76aa6b193
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/js/media_browser_plus.js
@@ -0,0 +1,358 @@
+/*!
+ * jQuery lightweight plugin boilerplate
+ * Original author: @ajpiano
+ * Further changes, comments: @addyosmani
+ * Licensed under the MIT license
+ * http://coding.smashingmagazine.com/2011/10/11/essential-jquery-plugin-patterns/
+ */
+;(function ( $, window, document, undefined ) {
+ // Create the defaults once
+ var pluginName = 'mbp',
+ defaults = {
+ folderManagementEnabled: false,
+ fileIdRegexp: /^.*media-item-([0-9]*).*$/,
+ folderIdRegexp: /^.*folder-id-([0-9]*).*$/,
+ mbpActionRegexp: /^.*mbp-action-(.*?)( .*|)$/,
+ actions: {}
+ };
+
+ // The actual plugin constructor
+ function MBP( element, options ) {
+ var plugin = this;
+ this.element = $(element);
+ this.options = $.extend( {}, defaults, options) ;
+ this._defaults = defaults;
+ this._name = pluginName;
+
+ // Add static methods.
+
+ this.removeItemFromBasket = function(item) {
+ // Extract file id and store it - in cookie and exposed form.
+ var file_id = this.id.replace(plugin.options.fileIdRegexp, '$1');
+ $('input[name=mbp_basket_files]', plugin.element).val($('input[name=mbp_basket_files]', plugin.element).val().replace(file_id, '').replace(' ', ' '));
+ $.cookie(
+ 'Drupal.visitor.mbp.basket',
+ $('input[name=mbp_basket_files]', plugin.element).val(),
+ {path: Drupal.settings.basePath}
+ );
+ // Update links.
+ $('a[href*="mbp_basket_files="]', plugin.element).each(function(index, link) {
+ link.href = link.href.replace(/(&|)mbp_basket_files=.*?(\#|\&|$)/, '$1mbp_basket_files=' + encodeURIComponent($('input[name=mbp_basket_files]', plugin.element).val()) + '$2');
+ });
+ $(this).remove();
+ };
+
+ this.addItemToBasked = function (item) {
+ if (!$('.mbp-file-basket-list', plugin.element).find('#' + item.id).length) {
+ // Extract file id and store it - in cookie and exposed form.
+ var file_id = item.id.replace(plugin.options.fileIdRegexp, '$1');
+ $('input[name=mbp_basket_files]', plugin.element).val($('input[name=mbp_basket_files]', plugin.element).val() + ' ' + file_id);
+ $.cookie(
+ 'Drupal.visitor.mbp.basket',
+ $('input[name=mbp_basket_files]', plugin.element).val(),
+ {path: Drupal.settings.basePath}
+ );
+ // Update links.
+ $('a[href*="mbp_basket_files="]', plugin.element).each(function(index, link) {
+ link.href = link.href.replace(/(&|)mbp_basket_files=.*?(\#|\&|$)/, '$1mbp_basket_files=' + encodeURIComponent($('input[name=mbp_basket_files]', plugin.element).val()) + '$2');
+ });
+ // Add item to list.
+ $('.mbp-file-basket-list', plugin.element).append(item);
+ $(item)
+ .click(plugin.removeItemFromBasket)
+ .find('a').click(function(e) {
+ e.preventDefault();
+ });
+ }
+ }
+
+ this.init();
+ }
+
+ MBP.prototype.init = function () {
+ var plugin = this;
+ $('.mbp-folders li', this.element).bind('click.mbp', function(e) {
+ // A click on the icon just opens the folder structure.
+ if (!$(e.target).hasClass('icon')) {
+ plugin.loadFiles($(this).children('div.folder-name').attr('class').replace(plugin.options.folderIdRegexp, '$1'));
+ }
+ else {
+ if ($(this).hasClass('open')) {
+ plugin.folderClose($(this));
+ }
+ else {
+ plugin.folderOpen($(this));
+ }
+ }
+ e.stopPropagation();
+ });
+
+ // Hide exposed folder filter.
+ $('div.form-item-mbp-current-folder:has(:input[name=mbp_current_folder])', this.element).hide();
+ // Initialize the folder structure.
+ var currentFolder = $(':input[name=mbp_current_folder]', this.element).val();
+ if (currentFolder) {
+ this.element
+ .find('li:has(>.folder-id-' + currentFolder + ')').addClass('active')
+ .find('ol:first').show();
+ var folder = $('.folder-id-' + currentFolder, this.element).show();
+ folder.parents('ol').show();
+ folder.parents('li').addClass('open');
+ }
+
+ // Enable drag n drop.
+ if (this.options.files_draggable) {
+ $('.mbp-file-list li', this.element).draggable({
+ iframeFix: true,
+ opacity: 0.7,
+ revert: 'invalid',
+ zIndex: 999,
+ helper: function(){
+ // Support grouping of draggables.
+ var selected = $('li:has(input.vbo-select:checked)', plugin.element);
+ if (selected.length === 0) {
+ selected = $(this);
+ }
+ var container = $('<div/>').attr('id', 'draggingContainer');
+ container.append(selected.clone());
+ return container;
+ }
+ });
+ }
+ // @todo Add folder management.
+ if (this.options.folders_draggable) {
+// $('.mbp-folders li', this.element).draggable({
+// iframeFix: true,
+// opacity: 0.7,
+// revert: 'invalid'
+// });
+ }
+ if (this.options.files_draggable || this.options.folders_draggable) {
+ $('.mbp-folders li div.folder-name', this.element).droppable({
+ hoverClass: 'drag-hover',
+ tolerance: 'pointer',
+ drop: function(event, ui) {
+ if (ui.helper.data('mbpDragHoverTimeout')) {
+ window.clearTimeout(ui.helper.data('mbpDragHoverTimeout'));
+ }
+ var target = $(this);
+ var folder_id = target.attr('class').replace(plugin.options.folderIdRegexp, '$1');
+ // Since we support grouping of draggables iterate over each item.
+ ui.helper.find('li').each(function(index, item){
+ item = $(item);
+ var file_id = item.attr('id').replace(plugin.options.fileIdRegexp, '$1');
+ var url = Drupal.settings.basePath + 'admin/content/file/' + file_id + '/move-to-folder/' + folder_id;
+ // Add throbber to folder.
+ target.prepend('<div class="ajax-progress ajax-progress-throbber media-item-' + file_id + '"><div class="throbber">&nbsp;</div></div>');
+ $('#' + item.attr('id'), plugin.element).remove();
+ $.ajax({
+ url: url,
+ success: function(data) {
+ target.find('.ajax-progress.media-item-' + file_id).remove();
+ },
+ error: function(data) {
+ alert(Drupal.t('An error occured, please refresh the page and try again.'));
+ target.find('.ajax-progress.media-item-' + file_id).remove();
+ }
+ });
+ });
+ },
+ over: function(event, ui) {
+ // Open subfolder after 1 second hovering.
+ if (ui.helper.data('mbpDragHoverTimeout')) {
+ window.clearTimeout(ui.helper.data('mbpDragHoverTimeout'));
+ }
+ var target = $(this);
+ ui.helper.data('mbpDragHoverTimeout', window.setTimeout(function(){
+ //@todo Figure out why subfolders aren't initialized droppables.
+ plugin.folderOpen(target.parent());
+ }, 1000));
+ },
+ out: function(event, ui ) {
+ if (ui.helper.data('mbpDragHoverTimeout')) {
+ window.clearTimeout(ui.helper.data('mbpDragHoverTimeout'));
+ ui.helper.data('mbpDragHoverTimeout', false);
+ }
+ }
+ });
+ }
+
+ // Make media basked.
+ if (this.options.media_basket ) {
+ $('.mbp-file-basket-list li', this.element)
+ .click(this.removeItemFromBasket);
+ $('.mbp-file-basket input[name=mbp_basket_files_download]', this.element).click(function() {
+ window.setTimeout(function(){
+ $('.mbp-file-basket-list li', plugin.element).trigger('click');
+ }, 1000);
+ window.location.href = Drupal.settings.basePath + Drupal.settings.pathPrefix + 'admin/content/file/download-multiple/' + $('input[name=mbp_basket_files]', plugin.element).val();
+ });
+
+ if (this.options.files_draggable) {
+ $('.mbp-file-basket', this.element).droppable({
+ hoverClass: 'drag-hover',
+ drop: function(event, ui) {
+ var target = $(this);
+ ui.helper.find('li').each(function(index, item){
+ plugin.addItemToBasked(item);
+ });
+ }
+ });
+ }
+ }
+
+ // Hide the vbo checkboxes and handle them by JS.
+ $('.mbp-file-list li:has(.vbo-select)', this.element)
+ .bind('click.mbp', function(e) {
+ if (!$(e.target).hasClass('vbo-select')) {
+ $(this).find('input.vbo-select')
+ .attr('checked', !$(this).find('input.vbo-select').attr('checked'))
+ .trigger('change');
+ }
+ });
+ $('.mbp-file-list li input.vbo-select', this.element)
+ .bind('change.mbp', function(e) {
+ var media_item = $('#media-item-' + this.value + ' .media-item', plugin.element);
+ if (this.checked) {
+ media_item.addClass('selected');
+ }
+ else {
+ media_item.removeClass('selected');
+ }
+ })
+ .hide();
+ $('.vbo-select-this-page', this.element).click(function() {
+ $('input.vbo-select', this.element)
+ .attr('checked', this.checked)
+ .trigger('change');
+ });
+ $('.vbo-select-all-pages', this.element).click(function() {
+ $('input.vbo-select', this.element)
+ .attr('checked', this.checked)
+ .trigger('change');
+ });
+ // If there are links and vbo selects navigate only on dbl clicks.
+ $('.mbp-file-list li:has(.vbo-select):has(a)', this.element)
+ .bind('dblclick.mbp', function(e) {
+ window.location.href = $(this).find('a').attr('href');
+ })
+ .find('a').bind('click.mbp', function(e) {
+ e.preventDefault();
+ });
+
+ // Register actions
+ for (var action in this.options.actions) {
+ if (this[action + 'Files']) {
+ $('.mbp-action-' + action, this.element).bind('click.mbp',function() {
+ var action = $(this).attr('class').replace(plugin.options.mbpActionRegexp, '$1');
+ plugin[action + 'Files']();
+ });
+ }
+ }
+ };
+
+ MBP.prototype.destroy = function () {
+ $('.mbp-folders li', this.element)
+ .unbind('.mbp')
+ .draggable('destroy')
+ .droppable('destroy');
+ $('.mbp-file-list li', this.element)
+ .unbind('.mbp')
+ .draggable('destroy')
+ .find('input.vbo-select')
+ .unbind('.mbp')
+ .show();
+ $('div.views-exposed-widget:has(:input[name=mbp_current_folder])', this.element).show();
+ $('.mbp-action-', this.element).unbind('.mbp');
+ };
+
+ MBP.prototype.folderOpen = function(folder) {
+ $(folder)
+ .addClass('open')
+ .find('ol:first').show();
+ }
+
+ MBP.prototype.folderClose = function(folder) {
+ $(folder)
+ .removeClass('open')
+ .find('ol').hide();
+ }
+
+ // Loads the files of a folder.
+ MBP.prototype.loadFiles = function(folder_id) {
+ if ($(':input[name=mbp_current_folder]', this.element).length && $(':input[name=mbp_current_folder]', this.element).val() != folder_id) {
+ $(':input[name=mbp_current_folder]', this.element).val(folder_id).trigger('change');
+ $('li.active', this.element).removeClass('active');
+ $('li:has(>.folder-id-' + folder_id + ')', this.element)
+ .addClass('active')
+ .prepend('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
+ }
+ }
+
+ MBP.prototype.getSelectedFiles = function () {
+ var fids = [];
+ var plugin = this;
+ $('.mbp-file-list li:has(.media-item.selected)', this.element).each(function(i, item) {
+ fids.push(item.id.replace(plugin.options.fileIdRegexp, '$1'));
+ });
+ return fids;
+ }
+
+ MBP.prototype.deleteFiles = function () {
+ var fids = this.getSelectedFiles();
+ if (fids.length) {
+ window.location.href = Drupal.settings.basePath + Drupal.settings.pathPrefix + 'admin/content/file/delete-multiple/' + fids.join(' ');
+ }
+ }
+
+ MBP.prototype.basketFiles = function () {
+ var plugin = this;
+ var items = $('.mbp-file-list li:has(.media-item.selected)', this.element)
+ if (items.length) {
+ items.each(function(index, item) {
+ plugin.addItemToBasked(item);
+ });
+ }
+ }
+
+ MBP.prototype.editFiles = function () {
+ var fids = this.getSelectedFiles();
+ if (fids.length) {
+ window.location.href = Drupal.settings.basePath + Drupal.settings.pathPrefix + 'admin/content/file/edit-multiple/' + fids.join(' ');
+ }
+ }
+
+ MBP.prototype.downloadFiles = function () {
+ var fids = this.getSelectedFiles();
+ if (fids.length) {
+ window.location.href = Drupal.settings.basePath + Drupal.settings.pathPrefix + 'admin/content/file/download-multiple/' + fids.join(' ');
+ }
+ }
+
+ $.fn[pluginName] = function ( options ) {
+ return this.each(function () {
+ if (!$.data(this, 'plugin_' + pluginName)) {
+ $.data(this, 'plugin_' + pluginName,
+ new MBP( this, options ));
+ }
+ else {
+ if (options == 'options') {
+ return $.data(this, 'plugin_' + pluginName).options;
+ }
+ }
+ });
+ }
+
+ Drupal.behaviors.media_browser_plus_views = {
+ attach: function (context) {
+ if (Drupal.settings.mbp.views) {
+ for(var i in Drupal.settings.mbp.views) {
+ var view_id = Drupal.settings.mbp.views[i].view_id;
+ var view_display_id = Drupal.settings.mbp.views[i].view_display_id;
+ $('.view-id-' + view_id + '.view-display-id-' + view_display_id).mbp(Drupal.settings.mbp.views[i]);
+ }
+ }
+ }
+ }
+
+})( jQuery, window, document );
diff --git a/sites/all/modules/media_browser_plus/media_browser_plus.file.inc b/sites/all/modules/media_browser_plus/media_browser_plus.file.inc
new file mode 100644
index 000000000..2bceb57cb
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/media_browser_plus.file.inc
@@ -0,0 +1,24 @@
+<?php
+/**
+ * @file
+ * File entity specific hooks.
+ */
+
+/**
+ * Implements hook_field_attach_presave().
+ */
+function media_browser_plus_file_presave($entity) {
+ // Set appropriate default folder if necessary.
+ if (empty($entity->field_folder[LANGUAGE_NONE][0]['tid'])) {
+ $root = media_browser_plus_get_media_root_folder();
+ $entity->field_folder[LANGUAGE_NONE] = array(array('tid' => $root->tid));
+ }
+
+ // Ensure file is stored in the appropriate folder.
+ $folder = taxonomy_term_load($entity->field_folder[LANGUAGE_NONE][0]['tid']);
+ $new_path = file_stream_wrapper_uri_normalize(file_uri_scheme($entity->uri) . '://' . media_browser_plus_construct_dir_path($folder) . '/' . basename($entity->uri));
+ // Only move file if necessary.
+ if ($entity->uri !== $new_path) {
+ media_browser_plus_move_file($folder->tid, $entity, FILE_EXISTS_RENAME, FALSE);
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/media_browser_plus.info b/sites/all/modules/media_browser_plus/media_browser_plus.info
new file mode 100644
index 000000000..8c00f11f8
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/media_browser_plus.info
@@ -0,0 +1,24 @@
+name = Media Browser Plus for Media 2.x
+description = Provides enhanced UX and addtional features for Media.
+package = "Media"
+core = 7.x
+
+dependencies[] = media
+dependencies[] = taxonomy
+dependencies[] = views_tree
+dependencies[] = views_bulk_operations
+dependencies[] = multiform
+
+files[] = views/media_browser_plus_views_plugin_style_media_browser.inc
+files[] = views/media_browser_plus_views_handler_field_preview.inc
+files[] = views/media_browser_plus_views_handler_field_preview_vbo.inc
+files[] = views/media_browser_plus_views_handler_area_actions.inc
+files[] = views/media_browser_plus_views_handler_area_basket.inc
+files[] = views/media_browser_plus_views_handler_area_navigation.inc
+
+; Information added by Drupal.org packaging script on 2014-05-20
+version = "7.x-3.0-beta3"
+core = "7.x"
+project = "media_browser_plus"
+datestamp = "1400584428"
+
diff --git a/sites/all/modules/media_browser_plus/media_browser_plus.install b/sites/all/modules/media_browser_plus/media_browser_plus.install
new file mode 100644
index 000000000..2d19f362c
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/media_browser_plus.install
@@ -0,0 +1,147 @@
+<?php
+/**
+ * @file
+ * Install file for media_browser_plus.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function media_browser_plus_install() {
+ // This should be load after views is ready.
+ db_query("UPDATE {system} SET weight = 11 WHERE name = 'media_browser_plus'");
+}
+
+/**
+ * Implements hook_enable().
+ *
+ * Ensure the necessary structures exist.
+ */
+function media_browser_plus_enable() {
+ // Folder vocabulary and root term.
+ $root_folder = media_browser_plus_get_media_root_folder(TRUE);
+ $vocabulary = taxonomy_vocabulary_machine_name_load('media_folders');
+
+ // Create the folder field.
+ $field = array(
+ 'field_name' => 'field_folder',
+ 'label' => st('Media Folder'),
+ 'type' => 'taxonomy_term_reference',
+ // Media file can only be in one folder at a time.
+ 'cardinality' => 1,
+ 'entity_type' => 'file',
+ 'bundle' => 'image',
+ 'required' => TRUE,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ $field_info = field_info_field($field['field_name']);
+ if (empty($field_info)) {
+ field_create_field($field);
+ }
+
+ // Backward compatibility handling.
+ // @TODO Version compatibility cleanup.
+ switch (TRUE) {
+ // 23. April 2013 http://drupal.org/node/1977728
+ case function_exists('file_type_load_all'):
+ $file_types = array_keys(file_type_load_all());
+ break;
+
+ // Old.
+ case function_exists('file_type_get_all_types'):
+ $file_types = array_keys(file_type_get_all_types());
+ break;
+
+ // Very old.
+ default:
+ $file_types = array_keys(file_info_file_types());
+ }
+ // Ensure instance for each file bundle.
+ foreach ($file_types as $bundle) {
+ $field['bundle'] = $bundle;
+ $instance_info = field_info_instance($field['entity_type'], $field['field_name'], $field['bundle']);
+ if (empty($instance_info)) {
+ field_create_instance($field);
+ }
+ }
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function media_browser_plus_uninstall() {
+ variable_del('media_root_folder');
+ variable_del('media_browser_plus_thumbnails_as_default_browser');
+ variable_del('media_browser_plus_filesystem_folders');
+ variable_del('media_browser_plus_disable_default_view');
+ variable_del('media_browser_plus_root_folder_tid');
+}
+
+/**
+ * Implements of hook_requirements().
+ */
+function media_browser_plus_requirements($phase) {
+ $requirements = array();
+ // Ensure translations don't break during installation.
+ $t = get_t();
+
+ if ($phase == 'runtime') {
+ // Check if we've to rely on and if it's enabled.
+ $result = db_query("SELECT * FROM {system} WHERE name = 'media_bulk_upload' and type = 'module'")->rowCount();
+ if ($result) {
+ $requirements['mbp_media_bulk_upload'] = array(
+ 'title' => $t('Media Browser Plus: Media Bulk Upload enabled'),
+ 'value' => $t('Media Browser Plus needs Media Bulk Upload to work properly.'),
+ 'severity' => REQUIREMENT_OK,
+ );
+ if (!module_exists('media_bulk_upload')) {
+ $requirements['mbp_media_bulk_upload']['severity'] = REQUIREMENT_WARNING;
+ $requirements['mbp_media_bulk_upload']['value'] .= $t(
+ ' (Enable the module in the !module_admin_link)',
+ array('!module_admin_link' => l($t('module administration'), 'admin/modules'))
+ );
+ }
+ }
+
+ $requirements['mbp_archiver'] = array(
+ 'title' => $t('Media Browser Plus: Archiver found'),
+ 'value' => $t('Media Browser Plus needs an archiver to provide multifile downloads.'),
+ 'severity' => REQUIREMENT_OK,
+ );
+ if (!count(archiver_get_info())) {
+ $requirements['mbp_archiver']['severity'] = REQUIREMENT_WARNING;
+ $requirements['mbp_archiver']['value'] .= l($t('(further information)'), 'https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_archiver_info/7');
+ }
+ }
+ return $requirements;
+}
+
+/**
+ * Update from 2.x to 3.x branch.
+ */
+function media_browser_plus_update_7300() {
+ // Set weight.
+ db_query("UPDATE {system} SET weight = 11 WHERE name = 'media_browser_plus'");
+
+ // Fetch the media root term and store it in the related variable.
+ $results = taxonomy_get_term_by_name('Media Root', 'media_folders');
+ if (!empty($results)) {
+ $root_folder = reset($results);
+ }
+ variable_set('media_browser_plus_root_folder_tid', $root_folder->tid);
+
+ // Remove legacy variables.
+ variable_del('media_media_per_page');
+ variable_del('media_grid_window_height');
+ variable_del('media_page_items_per_page');
+
+ // Make sure the dependency modules are enabled.
+ module_enable(array('views_tree', 'views_bulk_operations'));
+}
diff --git a/sites/all/modules/media_browser_plus/media_browser_plus.module b/sites/all/modules/media_browser_plus/media_browser_plus.module
new file mode 100644
index 000000000..9feab9caa
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/media_browser_plus.module
@@ -0,0 +1,882 @@
+<?php
+/**
+ * @file
+ * Media Browser Plus - enhanced file management functions.
+ */
+
+/**
+ * Implements hook_views_api().
+ */
+function media_browser_plus_views_api() {
+ return array(
+ 'api' => 3,
+ 'path' => drupal_get_path('module', 'media_browser_plus') . '/views',
+ );
+}
+
+/**
+ * Implements hook_menu().
+ * @see hook_menu()
+ */
+function media_browser_plus_menu() {
+ $path = drupal_get_path('module', 'media_browser_plus');
+ $items['admin/content/file/%file/move-to-folder/%taxonomy_term'] = array(
+ 'title' => 'Load Media Entities',
+ 'page callback' => 'media_browser_plus_move_file_callback',
+ 'page arguments' => array(3, 5),
+ 'access callback' => 'media_browser_plus_access',
+ 'delivery callback' => 'drupal_json_output',
+ 'type' => MENU_CALLBACK,
+ );
+ $items['admin/config/media/media_browser_plus_settings'] = array(
+ 'title' => 'Media Browser Plus Settings',
+ 'description' => 'Change the behaviour and layout of the media browser plus UI',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_browser_plus_media_settings'),
+ 'access arguments' => array('administer files'),
+ 'file path' => $path . '/includes',
+ 'file' => 'media_browser_plus.admin.inc',
+ );
+
+ // Expose further "Mutliple" actions.
+ if (module_exists('multiform')) {
+ // Following approach comes from the media module.
+ // @todo Investigate passing file IDs in query string rather than a menu
+ // argument and then deprecate media_multi_load().
+
+ // @TODO Version compatibility cleanup.
+ $callback_function = '%media_multi';
+ if (module_exists('media_bulk_upload')) {
+ $callback_function = '%media_bulk_upload_multi';
+ }
+ $items['admin/content/file/delete-multiple/' . $callback_function] = array(
+ 'title' => 'Delete multiple files',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('file_entity_multiple_delete_confirm', 4),
+ 'access callback' => 'media_browser_plus_file_entity_access_wrapper',
+ 'access arguments' => array('delete', 4),
+ 'file' => 'file_entity.admin.inc',
+ 'file path' => drupal_get_path('module', 'file_entity'),
+ );
+ }
+ // If there's an archiver available provide the multi download.
+ if (count(archiver_get_info())) {
+ // @TODO Version compatibility cleanup.
+ $callback_function = '%media_multi';
+ if (module_exists('media_bulk_upload')) {
+ $callback_function = '%media_bulk_upload_multi';
+ }
+ $items['admin/content/file/download-multiple/' . $callback_function] = array(
+ 'title' => 'Download multiple files',
+ 'page callback' => 'media_browser_plus_download_multiple_files',
+ 'page arguments' => array(4),
+ 'access callback' => 'media_browser_plus_file_entity_access_wrapper',
+ 'access arguments' => array('view', 4),
+ 'file' => 'file_entity.admin.inc',
+ 'file path' => drupal_get_path('module', 'file_entity'),
+ );
+ }
+ return $items;
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function media_browser_plus_menu_alter(&$items) {
+ // If enabled replace the default file browser by mbp.
+ if (variable_get('media_browser_plus_thumbnails_as_default_browser', TRUE) && isset($items['admin/content/file/mbp'])) {
+ $items['admin/content/file/list'] = $items['admin/content/file'];
+
+ $file_title = $items['admin/content/file']['title'];
+ $items['admin/content/file'] = $items['admin/content/file/mbp'];
+ $items['admin/content/file']['title'] = $file_title;
+ $items['admin/content/file/mbp']['type'] = MENU_DEFAULT_LOCAL_TASK;
+ $items['admin/content/file/mbp']['weight'] = -1;
+ }
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ */
+function media_browser_plus_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ // Add action links on 'admin/content/file/mbp' page.
+ // @todo can this be done in the related view?
+ if ($root_path == 'admin/content/file/mbp') {
+ $item = menu_get_item('file/add');
+ if (!empty($item['access'])) {
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $item,
+ '#weight' => $item['weight'],
+ );
+ }
+ $item = menu_get_item('admin/content/file/import');
+ if (!empty($item['access'])) {
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $item,
+ '#weight' => $item['weight'],
+ );
+ }
+ }
+ if ($root_path == 'admin/content/file/mbp' || (variable_get('media_browser_plus_thumbnails_as_default_browser', TRUE) && $root_path == 'admin/content/file')) {
+ $item = menu_get_item('admin/structure/taxonomy/media_folders');
+ if (!empty($item['access'])) {
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $item,
+ '#weight' => 10,
+ );
+ }
+ }
+}
+
+/**
+ * Implements hook_library().
+ */
+function media_browser_plus_library() {
+ $path = drupal_get_path('module', 'media_browser_plus');
+ $libraries['media_browser_plus'] = array(
+ 'title' => 'Media Browser Plus',
+ 'version' => '1',
+ 'js' => array(
+ $path . '/js/media_browser_plus.js' => array(),
+ ),
+ 'css' => array(
+ $path . '/css/media_browser_plus.views.css' => array(
+ 'type' => 'file',
+ 'media' => 'screen',
+ ),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.draggable'),
+ array('system', 'ui.droppable'),
+ array('system', 'jquery.cookie'),
+ ),
+ );
+ return $libraries;
+}
+
+/**
+ * Implements hook_field_attach_create_bundle().
+ */
+function media_browser_plus_field_attach_create_bundle($entity_type, $bundle) {
+ // Ensure the folder field is added if a new file bundle is created.
+ if ($entity_type == 'file') {
+ $field = field_info_field('field_folder');
+ $field['bundle'] = $bundle;
+ $instance_info = field_info_instance($field['entity_type'], $field['field_name'], $field['bundle']);
+ if (empty($instance_info)) {
+ field_create_instance($field);
+ }
+ }
+}
+
+/**
+ * Implements hook_action_info().
+ */
+function media_browser_plus_action_info() {
+ // If there's an archiver available provide the download functionality.
+ if (count(archiver_get_info())) {
+ return array(
+ 'media_browser_plus_download_action' => array(
+ 'type' => 'file',
+ 'label' => t('Download file(s)'),
+ 'configurable' => TRUE,
+ 'vbo_configurable' => TRUE,
+ 'triggers' => array('any'),
+ ),
+ );
+ }
+}
+
+/**
+ * Configuration form shown to the user before the action gets executed.
+ *
+ * @todo Replace with proper integration as soon as VBO supports non batch
+ * operations.
+ */
+function media_browser_plus_download_action_form($context, $form_state) {
+ // We hijack the whole process here because there's now way yet to skip the
+ // batch processing in VBO 3.1.
+ $vbo = _views_bulk_operations_get_field($context['view']);
+ $selection = _views_bulk_operations_get_selection($vbo, $form_state);
+
+ $files = file_load_multiple($selection);
+ // Check permissions. If one fails - stop whole operation!
+ if (!media_browser_plus_file_entity_access_wrapper('view', $files)) {
+ drupal_access_denied();
+ drupal_exit();
+ }
+ media_browser_plus_download_multiple_files($selection);
+}
+
+/**
+ * Callback for the action.
+ */
+function media_browser_plus_download_action($file, &$context = array()) {
+ drupal_set_message('How the heck did you reach this function? Please open an issue in the issue queue, thanks! :)', 'warning');
+}
+
+/**
+ * Download multiple files.
+ *
+ * Creates n archive for multiple files - directly sends a single file.
+ *
+ * @param array $files
+ * A list if file id's or file objects.
+ */
+function media_browser_plus_download_multiple_files($files) {
+ $fids = array();
+ // Check if the list consists of /contains file ids.
+ foreach ($files as $key => $file) {
+ if (!is_object($file)) {
+ $fids[] = $file;
+ unset($files[$key]);
+ }
+ }
+ // If file ids were found populate list of file objects.
+ if (!empty($fids)) {
+ $files = array_merge($files, file_load_multiple($fids));
+ }
+
+ if (count($files) > 1) {
+ $file_name = 'file_download_' . time() . '.zip';
+ $archive = drupal_tempnam('temporary://', 'mbp');
+ // Abuse the existing archiver action. Do like we run an action ;)
+ module_load_include('inc', 'views_bulk_operations', 'actions/archive.action');
+ $archiver_context['destination'] = $archive;
+ $archiver_context['progress']['current'] = 1;
+ $archiver_context['progress']['total'] = count($files);
+ $archiver_context['settings']['temporary'] = TRUE;
+ foreach ($files as $file) {
+ views_bulk_operations_archive_action($file, $archiver_context);
+ }
+
+ // Register cleanup function. The created archive has to be removed again.
+ $cleanup_context = &drupal_static('media_browser_plus_download_action', $archiver_context['destination']);
+ drupal_register_shutdown_function('media_browser_plus_download_action_cleanup');
+ }
+ elseif (count($files) == 1) {
+ $file = reset($files);
+ $archive = $file->uri;
+ $file_name = drupal_basename($file->uri);
+ }
+ else {
+ drupal_not_found();
+ drupal_exit();
+ }
+
+ // Ensure we've the latest file information.
+ clearstatcache();
+ // Prepare headers.
+ $headers['Pragma'] = 'public';
+ $headers['Expires'] = '0';
+ $headers['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0';
+ $headers['Content-type'] = 'application/zip';
+ $headers['Content-Disposition'] = 'attachment; filename=' . $file_name;
+ $headers['Content-length'] = filesize($archive);
+ file_transfer($archive, $headers);
+}
+
+/**
+ * Delete temporary download archive.
+ */
+function media_browser_plus_download_action_cleanup() {
+ $file = &drupal_static('media_browser_plus_download_action', array());
+ if (!empty($file)) {
+ drupal_unlink($file);
+ }
+}
+
+/**
+ * Move the file to another folder.
+ *
+ * @param stdClass $file
+ * The file object to update.
+ * @param stdClass $folder
+ * The folder object to use for the file.
+ *
+ * @return bool
+ * FALSE on error.
+ */
+function media_browser_plus_move_file_callback($file, $folder) {
+ if (empty($file->field_folder[LANGUAGE_NONE][0]['tid']) || $file->field_folder[LANGUAGE_NONE][0]['tid'] != $folder->tid) {
+ $file->field_folder[LANGUAGE_NONE] = array(array('tid' => $folder->tid));
+ return media_browser_plus_move_file($folder->tid, $file);
+ }
+ return TRUE;
+}
+
+/**
+ * Manages access for media browser plus actions.
+ *
+ * @param string $op
+ * The permission, such as "administer nodes", being checked for.
+ *
+ * @return bool
+ * TRUE if the user has the permission.
+ */
+function media_browser_plus_access($op) {
+ return (user_access('administer files') || user_access($op));
+}
+
+/**
+ * Wrapper around file_entity_access() to deal with multiple files.
+ */
+function media_browser_plus_file_entity_access_wrapper($op, $files = NULL, $account = NULL) {
+ // If there's files parameter, ensure it is an array to handle.
+ if (!is_array($files) && !empty($files)) {
+ $files = array($files);
+ }
+ if (!empty($files)) {
+ foreach ($files as $file) {
+ // Even if one is not accessible return FALSE.
+ if (!file_entity_access($op, $file, $account)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+ return file_entity_access($op, $files, $account);
+}
+
+/**
+ * Loads and (if $autocreate is set) creates the default media folder object.
+ *
+ * @param bool $autocreate
+ * Creates the folder if necessary.
+ *
+ * @return object|FALSE
+ * The folder term or FALSE if not found. During installation this can return
+ * FALSE!
+ */
+function media_browser_plus_get_media_root_folder($autocreate = FALSE) {
+ $root_folder = FALSE;
+ $vocabulary = taxonomy_vocabulary_machine_name_load('media_folders');
+ if (!$vocabulary) {
+ $vocabulary = (object) array(
+ 'name' => 'Media Folders',
+ 'description' => st('Use media folders to organize your media'),
+ 'machine_name' => 'media_folders',
+ 'hierarchy' => 1,
+ 'help' => st('Enter a concise name for the media folder'),
+ );
+ taxonomy_vocabulary_save($vocabulary);
+ }
+ if ($vocabulary) {
+ $root_folder = taxonomy_term_load(variable_get('media_browser_plus_root_folder_tid'));
+ if ($root_folder === FALSE) {
+ if ($autocreate) {
+ $root_folder = new stdClass();
+ $root_folder->name = 'Media Root';
+ $root_folder->description = 'default media folder';
+ $root_folder->vid = $vocabulary->vid;
+ $root_folder->weight = '-10';
+ $root_folder->parent = 0;
+ taxonomy_term_save($root_folder);
+ variable_set('media_browser_plus_root_folder_tid', $root_folder->tid);
+ }
+ else {
+ watchdog(
+ 'media_browser_plus',
+ 'Unable to load the media root folder term. Please check the folder management!',
+ array(),
+ WATCHDOG_ERROR,
+ 'admin/structure/taxonomy/' . $vocabulary->machine_name
+ );
+ }
+ }
+ }
+ return $root_folder;
+}
+
+/**
+ * Construct the path of a media_folder term without scheme.
+ *
+ * Always returns the same path if the filesystem handling is disabled.
+ *
+ * @param object|NULL $term
+ * Containing term id and term name. If left empty the root folder will be
+ * returned.
+ *
+ * @return string
+ * The path to the requested folder. Without the scheme and without a trailing
+ * slash.
+ */
+function media_browser_plus_construct_dir_path($term = NULL) {
+ $path = '';
+ if ($root_folder = variable_get('media_root_folder')) {
+ $path = $root_folder;
+ }
+ // Always return the path to the defined media_root_folder if the folder
+ // handling is disabled.
+ if (!variable_get('media_browser_plus_filesystem_folders', TRUE)) {
+ return trim($path, '/');
+ }
+ // $root_folder_term can be FALSE during the installation of the module.
+ $root_folder_term = media_browser_plus_get_media_root_folder();
+ if ($term && $root_folder_term && $term->tid != $root_folder_term->tid) {
+ $parents = array_reverse(taxonomy_get_parents_all($term->tid));
+ array_pop($parents);
+ if (is_array($parents) && !empty($parents)) {
+ foreach ($parents as $parent) {
+ if ($parent->tid != $root_folder_term->tid) {
+ if (function_exists('transliteration_clean_filename')) {
+ $parent->name = transliteration_clean_filename($parent->name);
+ }
+ $path = file_create_filename($parent->name, $path);
+ }
+ }
+ }
+ if (function_exists('transliteration_clean_filename')) {
+ $term->name = transliteration_clean_filename($term->name);
+ }
+ $path = file_create_filename($term->name, $path);
+ }
+ $path = trim($path, '/');
+ return $path;
+}
+
+/**
+ * Moves and saves a file.
+ *
+ * Every managed file that is saved or updated, should pass through this to
+ * ensure the filesystem location matches the folder term.
+ *
+ * @param int $tid
+ * The folder's term id.
+ * @param stdClass $file
+ * The file object.
+ * @param int $replace
+ * Replace behavior when the destination file already exists.
+ * @param bool $save
+ * Enables or disables saving the file object. Handy for cases in which the
+ * file object is saved anyway.
+ *
+ * @return bool
+ * TRUE on success.
+ */
+function media_browser_plus_move_file($tid, $file, $replace = FILE_EXISTS_RENAME, $save = TRUE) {
+ // No need to process the file if its location is not managed.
+ if (!variable_get('media_browser_plus_filesystem_folders', TRUE)) {
+ return TRUE;
+ }
+
+ $local_stream_wrappers = media_get_local_stream_wrappers();
+ $scheme = file_uri_scheme($file->uri);
+
+ if (function_exists('transliteration_clean_filename')) {
+ $file->filename = transliteration_clean_filename($file->filename);
+ }
+
+ // Don't change the uri for non-local files.
+ if (!isset($local_stream_wrappers[$scheme])) {
+ if ($save) {
+ file_save($file);
+ }
+ }
+ else {
+ // Media translation module does need this since it allows the creation of
+ // file references which shouldn't move the referenced file itself when
+ // moved. See http://drupal.org/node/1331818 for details.
+ if (module_exists('media_translation') && media_translation_is_virtual_file($file->fid)) {
+ file_save($file);
+ return TRUE;
+ }
+
+ $folder = taxonomy_term_load($tid);
+ $path = file_stream_wrapper_uri_normalize(file_uri_scheme($file->uri) . '://' . media_browser_plus_construct_dir_path($folder));
+ file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+ if ($save) {
+ return file_move($file, $path, $replace);
+ }
+ else {
+ if ($uri = file_unmanaged_move($file->uri, $path, $replace)) {
+ $file->uri = $uri;
+ return TRUE;
+ }
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Implements hook_taxonomy_term_presave().
+ *
+ * @see media_browser_plus_taxonomy_term_update()
+ */
+function media_browser_plus_taxonomy_term_presave($term) {
+ // Figure out if this is a folder term and if so store the current file path
+ // for further processing in media_browser_plus_taxonomy_term_update().
+ $vocabulary = taxonomy_vocabulary_machine_name_load('media_folders');
+ if (!empty($vocabulary) && $term->vid == $vocabulary->vid) {
+ $parent = NULL;
+ $root_folder = media_browser_plus_get_media_root_folder();
+ // Check if parent handling is necessary.
+ if (isset($term->parent)) {
+ // Ensure we're dealing with an array.
+ if (!is_array($term->parent)) {
+ $term->parent = array($term->parent);
+ }
+ // A folder term can just have one parent.
+ if (count($term->parent) > 1) {
+ $term->parent = array(reset($term->parent));
+ }
+ // Fetch the used parent.
+ $parent = reset($term->parent);
+ }
+ // A subfolder term is always child of the root folder. Condition ensures
+ // we don't interfere while installing the module.
+ if ($root_folder && (!isset($parent) || (empty($parent) && $term->tid != $root_folder->tid))) {
+ $term->parent = array($root_folder->tid);
+ }
+ // Actions if this is an existing term.
+ if (!empty($term->tid)) {
+ // Store current path the check later if the folder was moved.
+ $term->media_browser_plus_original_path = media_browser_plus_construct_dir_path($term->original);
+ }
+ }
+}
+
+/**
+ * Implements hook_taxonomy_term_insert().
+ */
+function media_browser_plus_taxonomy_term_insert($term) {
+ if ($term->vocabulary_machine_name == 'media_folders') {
+ // Prepare path for new folder terms.
+ $dir = media_browser_plus_construct_dir_path($term);
+ $error = FALSE;
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $path = file_stream_wrapper_uri_normalize($scheme . '://' . $dir);
+ if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ $error = TRUE;
+ }
+ }
+ if (!$error) {
+ drupal_set_message(t('Folder %term_name created successfully', array('%term_name' => $term->name)));
+ }
+ else {
+ drupal_set_message(t('Folder %term_name created successfully as term but failed to create as physical folder.Please do it manually', array('%term_name' => $term->name)), 'warning');
+ }
+
+ // Clear view cache for media browser plus folders.
+ media_browser_plus_clear_views_cache('media_browser_plus_folders');
+ }
+}
+
+/**
+ * Implements hook_taxonomy_term_update().
+ *
+ * @see media_browser_plus_taxonomy_term_presave()
+ */
+function media_browser_plus_taxonomy_term_update($term) {
+
+ if ($term->vocabulary_machine_name == 'media_folders') {
+ // Check if the folder term was moved. Only folder terms have this property.
+ if (!empty($term->media_browser_plus_original_path)) {
+ $destination = media_browser_plus_construct_dir_path($term);
+ if ($term->media_browser_plus_original_path != $destination) {
+ module_load_include('inc', 'media_browser_plus', '/includes/media_browser_plus.folders');
+ // Prepare batch to move folder and files.
+ $batch = array(
+ 'title' => t('Updating file locations'),
+ 'operations' => media_browser_plus_move_subfolder($term, $term->media_browser_plus_original_path, $destination),
+ 'file' => drupal_get_path('module', 'media_browser_plus') . '/includes/media_browser_plus.folders.inc',
+ );
+ // If necessary start the batch to update the folder structure.
+ if (!empty($batch['operations'])) {
+ batch_set($batch);
+ }
+ }
+ }
+
+ // Clear view cache for media browser plus folders.
+ media_browser_plus_clear_views_cache('media_browser_plus_folders');
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for taxonomy_form_term().
+ *
+ * @see media_browser_plus_form_taxonomy_term_confirm_delete_submit()
+ */
+function media_browser_plus_form_taxonomy_form_term_alter(&$form, &$form_state) {
+ // Just modify if this is is / contains the confirm delete form.
+ if (isset($form_state['confirm_delete'])) {
+ array_unshift($form['#submit'], 'media_browser_plus_form_taxonomy_term_confirm_delete_submit');
+ }
+}
+
+/**
+ * Submit handler for preparing for term deletion.
+ *
+ * Because hook_taxonomy_term_delete() is invoked after all data are removed
+ * from the db we fill the static cache so that it's still possible to build
+ * the necessary paths to clean the filesystem.
+ *
+ * @see media_browser_plus_taxonomy_term_delete()
+ */
+function media_browser_plus_form_taxonomy_term_confirm_delete_submit(&$form, &$form_state) {
+ // Fill the static caches needed to clean the filesystem.
+ taxonomy_get_parents($form_state['values']['tid']);
+ taxonomy_get_parents_all($form_state['values']['tid']);
+}
+
+/**
+ * Implements hook_taxonomy_term_delete().
+ *
+ * @see media_browser_plus_form_taxonomy_term_confirm_delete_submit()
+ */
+function media_browser_plus_taxonomy_term_delete($term) {
+ static $term_hierarchy_filter;
+ // Figure out if this is a folder term and if so handle the related files.
+ $vocabulary = taxonomy_vocabulary_machine_name_load('media_folders');
+ if (!empty($vocabulary) && $term->vid == $vocabulary->vid) {
+ // Skip if this term is already handled here by its parent term.
+ if (isset($term_hierarchy_filter[$term->tid])) {
+ unset($term_hierarchy_filter[$term->tid]);
+ return;
+ }
+
+ // Create an array of all the folders to handle.
+ $folders = array(
+ '0:' . $term->tid => $term,
+ );
+ // Fetch all sub-folders.
+ $tree = taxonomy_get_tree($term->vid, $term->tid);
+ foreach ($tree as $subterm) {
+ $folders[($subterm->depth + 1) . ':' . $subterm->tid] = $subterm;
+ $term_hierarchy_filter[$subterm->tid] = $subterm->tid;
+ }
+ // Ensure the order for processing is right.
+ krsort($folders);
+
+ $all_files_deleted = TRUE;
+ $used_files = array();
+ foreach ($folders as $folder) {
+ // Fetch all files from the folder.
+ $file_query = new EntityFieldQuery();
+ $files = $file_query
+ ->entityCondition('entity_type', 'file')
+ ->fieldCondition('field_folder', 'tid', $folder->tid)
+ ->execute();
+ // If there are files, delete them if possible.
+ if (!empty($files['file'])) {
+ $files = file_load_multiple(array_keys($files['file']));
+ foreach ($files as $file) {
+ if (($file_usage = file_delete($file)) !== TRUE) {
+ $all_files_deleted = FALSE;
+ if (is_array($file_usage)) {
+ $used_files[$file->fid] = $file_usage;
+ }
+ }
+ }
+ }
+ // Also delete the folder when it's empty.
+ if ($all_files_deleted && ($dir = media_browser_plus_construct_dir_path($folder))) {
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $folder_path = file_stream_wrapper_uri_normalize($scheme . '://' . $dir);
+ if (!@drupal_rmdir($folder_path)) {
+ drupal_set_message(t('Unable to delete the folder (!path) on the disk', array('!path' => $folder_path)), 'error');
+ }
+ }
+ }
+ }
+ if (!$all_files_deleted) {
+ $list = array();
+ foreach ($used_files as $fid => $usage) {
+ $file = file_load($fid);
+ $list['items'][] = l($file->filename, 'file/' . $fid . '/usage');
+ }
+ drupal_set_message(t("Some of the files in the folder are used and can't be deleted:") . theme('item_list', $list), 'error');
+ }
+
+ // Clear view cache for media browser plus folders.
+ media_browser_plus_clear_views_cache('media_browser_plus_folders');
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * This is necessary since altering the hierarchy or weight of terms in the
+ * overview won't trigger any term hooks *blargh* :|
+ */
+function media_browser_plus_form_taxonomy_overview_terms_alter(&$form, &$form_state, $vocabulary) {
+ if ($form['#vocabulary']->machine_name == 'media_folders') {
+ $form['#validate'][] = 'media_browser_plus_form_taxonomy_overview_terms_validate';
+ $form['#submit'][] = 'media_browser_plus_form_taxonomy_overview_terms_submit';
+ }
+}
+
+/**
+ * Validation handler for the taxonomy term overview list.
+ *
+ * This is necessary since altering the hierarchy or weight of terms in the
+ * overview won't trigger any term hooks *blargh* :|
+ */
+function media_browser_plus_form_taxonomy_overview_terms_validate(&$form, &$form_state) {
+ $vocabulary = $form['#vocabulary'];
+ $tree = taxonomy_get_tree($vocabulary->vid);
+ foreach ($tree as $term) {
+ $form_state['#mbp_original_paths'][$term->tid] = media_browser_plus_construct_dir_path($term);
+ }
+}
+
+/**
+ * Submit handler for the taxonomy term overview list.
+ *
+ * This is necessary since altering the hierarchy or weight of terms in the
+ * overview won't trigger any term hooks *blargh* :|
+ */
+function media_browser_plus_form_taxonomy_overview_terms_submit(&$form, &$form_state) {
+ module_load_include('inc', 'media_browser_plus', 'includes/media_browser_plus.folders');
+ $vocabulary = $form['#vocabulary'];
+ taxonomy_terms_static_reset();
+ $root_folder = media_browser_plus_get_media_root_folder();
+ $tree = taxonomy_get_tree($vocabulary->vid);
+
+ // Prepare batch.
+ $batch = array(
+ 'title' => t('Updating Media'),
+ 'operations' => array(),
+ 'finished' => 'media_browser_plus_update_folder_hierarchy_batch_complete',
+ 'file' => drupal_get_path('module', 'media_browser_plus') . '/includes/media_browser_plus.folders.inc',
+ );
+ foreach ($tree as $term) {
+ // Deal only with subfolders.
+ if ($term->tid != $root_folder->tid) {
+ // A subfolder term is always child of the root folder.
+ if (empty($term->parents[0])) {
+ // The presave hook will take care of fixing this.
+ taxonomy_term_save($term);
+ }
+ $path = media_browser_plus_construct_dir_path($term);
+ if ($form_state['#mbp_original_paths'][$term->tid] != $path) {
+ $batch['operations'] = array_merge($batch['operations'], media_browser_plus_move_subfolder($term, $form_state['#mbp_original_paths'][$term->tid], $path));
+ }
+ }
+ }
+ // If necessary start the batch to update the structure.
+ if (!empty($batch['operations'])) {
+ batch_set($batch);
+ }
+}
+
+/**
+ * Batch process finish callback for updating the folder hierarchy.
+ */
+function media_browser_plus_update_folder_hierarchy_batch_complete($success, $results, $operations) {
+ if ($success) {
+ drupal_set_message(t('Successfully updated all folders'));
+ }
+ else {
+ drupal_set_message(t('Error while updating folder structure'), 'error');
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_browser_plus_form_file_entity_add_upload_multiple_alter(&$form, &$form_state) {
+ // Abuse the taxonomy term field widget to get the available options.
+ $field['settings']['allowed_values'][] = array(
+ 'vocabulary' => 'media_folders',
+ 'parent' => 0,
+ );
+ $form['field_folder'] = array(
+ '#type' => 'select',
+ '#title' => t('Folder'),
+ '#description' => t('Defines the folder where the uploaded files will be saved'),
+ '#options' => taxonomy_allowed_values($field),
+ );
+ // Add own submit handler to set the selected folder in the file objects.
+ $form['#submit'][] = 'media_browser_plus_form_file_entity_add_upload_multiple_submit';
+}
+
+/**
+ * Submit handler to set the folder selected in the multiple upload form.
+ */
+function media_browser_plus_form_file_entity_add_upload_multiple_submit(&$form, &$form_state) {
+ // Iterate over all saved files and add the folder setting.
+ if (isset($form_state['values']['field_folder'])) {
+ $folder = $form_state['values']['field_folder'];
+ foreach ($form_state['files'] as $fid => $file) {
+ // Only set folder if not set or different to what will be set.
+ if (empty($file->field_folder[LANGUAGE_NONE][0]['tid']) || $file->field_folder[LANGUAGE_NONE][0]['tid'] != $folder) {
+ $file->field_folder[LANGUAGE_NONE][0]['tid'] = $folder;
+ file_save($file);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_browser_plus_form_file_entity_add_upload_alter(&$form, &$form_state) {
+ // This isn't really necessary but ensures the usage consistency over all
+ // upload forms.
+ switch ($form['#step']) {
+ // Add folder selection to the upload form.
+ case 1:
+ // By re-using the field form structure we can inject the value into the
+ // next step.
+ // @todo Check if we can replace this somehow by the real field widget.
+ $form['field_folder'] = array(
+ '#type' => 'container',
+ '#tree' => TRUE,
+ );
+ // Abuse the taxonomy term field widget to get the available options.
+ $field['settings']['allowed_values'][] = array(
+ 'vocabulary' => 'media_folders',
+ 'parent' => 0,
+ );
+ $form['field_folder'][LANGUAGE_NONE] = array(
+ '#type' => 'select',
+ '#title' => t('Folder'),
+ '#description' => t('Defines the folder where the uploaded files will be saved'),
+ '#options' => taxonomy_allowed_values($field),
+ );
+ break;
+
+ // Nothing to do for now.
+ case 2:
+ case 3:
+ }
+}
+
+/**
+ * Clears the cache for a specific view.
+ *
+ * This method is copied from the cache_actions module.
+ *
+ * @param string $view_name
+ * The name of the view to clear the cache.
+ */
+function media_browser_plus_clear_views_cache($view_name) {
+
+ $view = views_get_view($view_name);
+
+ if (is_object($view)) {
+ // Go through all displays and clear the cache.
+ foreach ($view->display as $display) {
+ // Set display handler.
+ $view->set_display($display->id);
+
+ // If we don't have our own cache plugin, then we need to copy the cache
+ // settings from default.
+ if (!isset($display->display_options['cache']) && isset($view->display['default'])) {
+ $display->display_options['cache'] = $view->display['default']->display_options['cache'];
+ }
+ $cache_plugin = views_get_plugin('cache', $display->display_options['cache']['type']);
+ // If we have a cache plugin, then initiate it and flush the cache.
+ if (isset($cache_plugin)) {
+ $cache_plugin->init($view, $display);
+ $cache_plugin->cache_flush();
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/tests/media_browser_plus.base.test b/sites/all/modules/media_browser_plus/tests/media_browser_plus.base.test
new file mode 100644
index 000000000..4adbcbf37
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/tests/media_browser_plus.base.test
@@ -0,0 +1,169 @@
+<?php
+/**
+ * @file
+ * Media Browser Plus tests base.
+ */
+
+/**
+ * provides some basic functionality for all MBP tests.
+ */
+class MediaBrowserPlusTestBase extends DrupalWebTestCase {
+
+ /**
+ * Admin user to test with.
+ *
+ * @var stdClass|FALSE
+ */
+ protected $adminUser = FALSE;
+
+ public function setUp() {
+ parent::setUp(array(
+ 'file_entity',
+ 'media',
+ 'views_tree',
+ 'views_bulk_operations',
+ 'media_browser_plus',
+ ));
+
+ // Create user.
+ $this->adminUser = $this->drupalCreateUser(array(
+ 'administer files',
+ 'administer taxonomy',
+ ));
+ }
+
+ /**
+ * Helper to create a folder structure based on an given array.
+ *
+ * @param array $hierarchy
+ * An array that represents the hierarchy to create. E.g.
+ * array(
+ * 'root_folder' => array(
+ * 'level_1_1' => array(
+ * 'level_1_1_1' => array(),
+ * ),
+ * 'level_1_2' => array(
+ * 'level_1_2_1' => array(),
+ * ),
+ * ),
+ * );
+ * @param bool $run_asserts
+ * Enable/disable the test assertions.
+ * @param int $pid
+ * The tid of the parent the term has to be assigned to.
+ */
+ protected function folderCreationHelper($hierarchy, $run_asserts = TRUE, $pid = 0) {
+ if (is_array($hierarchy)) {
+ foreach ($hierarchy as $parent => $folder_name) {
+ if ($parent == 'root_folder') {
+ $folder = media_browser_plus_get_media_root_folder();
+ $parent = $folder->name;
+ }
+ else {
+ $this->folderCreationHelper($parent, $run_asserts, $pid);
+ $folders = taxonomy_get_term_by_name($parent);
+ $folder = reset($folders);
+ }
+ if (!empty($folder_name)) {
+ $this->folderCreationHelper($folder_name, $run_asserts, $folder->tid);
+ }
+ }
+ }
+ else {
+ $description = $this->randomString(50);
+ $edit = array(
+ 'name' => $hierarchy,
+ 'description[value]' => $description,
+ 'parent[]' => array($pid),
+ );
+ $this->drupalPost('admin/structure/taxonomy/media_folders/add', $edit, t('Save'));
+ if ($run_asserts) {
+ // Check if the creation was successful.
+ $this->assertText('Folder ' . $hierarchy . ' created successfully');
+
+ // Check if the related folder was created in the filesystem.
+ $folders = taxonomy_get_term_by_name($hierarchy);
+ $folder = reset($folders);
+ $parents = taxonomy_get_parents_all($folder->tid);
+ $parent_count = count($parents) - 2;
+ $path = media_browser_plus_construct_dir_path($folder);
+ $this->assertEqual(substr_count($path, '/'), $parent_count, 'Folder hierarchy structure matches');
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $folder_path = file_stream_wrapper_uri_normalize($scheme . '://' . $path);
+ $this->assertTrue(is_dir($folder_path), format_string('Folder found. (!path)', array('!path' => $folder_path)));
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a test file.
+ *
+ * @param string $mime
+ * Allowed values text/plain, image/jpg.
+ * @param null|term|integer $folder
+ * NULL to use the root folder, term object or term id of a folder.
+ * @param string $scheme
+ * The scheme where the file is created.
+ *
+ * @return file_entity|FALSE
+ * The file entity of the newly created file or FALSE on error.
+ */
+ protected function createTestFile($mime = 'text/plain', $folder = NULL, $scheme = 'public') {
+
+ $tmp_file = 'temporary://' . uniqid('mbp_test_file');
+
+ if (empty($folder)) {
+ $folder = media_browser_plus_get_media_root_folder();
+ }
+ elseif (is_numeric($folder)) {
+ $folder = taxonomy_term_load($folder);
+ }
+
+ switch ($mime) {
+ case 'image/jpg':
+ $type = 'image';
+ // Make an image split into 4 sections with random colors.
+ $im = imagecreate(800, 600);
+ for ($n = 0; $n < 4; $n++) {
+ $color = imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255));
+ $x = 800 / 2 * ($n % 2);
+ $y = 600 / 2 * (int) ($n >= 2);
+ imagefilledrectangle($im, $x, $y, $x + 800 / 2, $y + 600 / 2, $color);
+ }
+
+ // Make a perfect circle in the image middle.
+ $color = imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255));
+ $smaller_dimension = min(800, 600);
+ $smaller_dimension = ($smaller_dimension % 2) ? $smaller_dimension : $smaller_dimension;
+ imageellipse($im, 800 / 2, 600 / 2, $smaller_dimension, $smaller_dimension, $color);
+
+ $tmp_file = $tmp_file . '.jpg';
+ imagejpeg($im, drupal_realpath($tmp_file));
+ break;
+
+ case 'text/plain':
+ default:
+ $type = FILE_TYPE_NONE;
+ $tmp_file = $tmp_file . '.txt';
+ $fp = fopen($tmp_file, 'w');
+ fwrite($fp, str_repeat('01', 512));
+ fclose($fp);
+ break;
+ }
+
+ $source = new stdClass();
+ $source->uri = $tmp_file;
+ $source->uid = 1;
+ $source->filemime = $mime;
+ $source->type = $type;
+ $source->filename = basename($tmp_file);
+ $source->field_folder[LANGUAGE_NONE][0]['tid'] = $folder->tid;
+
+ // Move the file to the right directory.
+ $destination_path = file_stream_wrapper_uri_normalize($scheme . '://' . media_browser_plus_construct_dir_path($folder));
+ $file = file_move($source, $destination_path, FILE_CREATE_DIRECTORY);
+
+ return $file;
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/tests/media_browser_plus.root_folder.test b/sites/all/modules/media_browser_plus/tests/media_browser_plus.root_folder.test
new file mode 100644
index 000000000..9cf97ba62
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/tests/media_browser_plus.root_folder.test
@@ -0,0 +1,157 @@
+<?php
+/**
+ * @file
+ * Media Browser Plus root folder test.
+ */
+
+/**
+ * Tests the behaviour when changing the MBP root folder.
+ */
+class MediaBrowserPlusRootFolderTest extends MediaBrowserPlusTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media Browser Plus Root Folder',
+ 'description' => 'Tests the root folder handling of Media Browser Plus',
+ 'group' => 'Media',
+ );
+ }
+
+ /**
+ * Test the ability to move the root folder for media files.
+ */
+ public function testMovingRootFolder() {
+ $this->drupalLogin($this->adminUser);
+
+ // Create test folder structure.
+ $folders = array(
+ 'root_folder' => 'Media Folders',
+ 'source' => $this->randomName(),
+ 'source_child' => $this->randomName(),
+ 'source_child_child' => $this->randomName(),
+ );
+ $hierarchy = array(
+ 'root_folder' => array(
+ $folders['source'] => array(
+ $folders['source_child'] => array(
+ $folders['source_child_child'] => array(),
+ ),
+ ),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy, FALSE);
+
+ // Create test files.
+ $files = array();
+ foreach ($folders as $type => $folder_name) {
+ $terms = taxonomy_get_term_by_name($folder_name);
+ $folder = $folders[$type] = reset($terms);
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $files[$scheme][] = $this->createTestFile('text/plain', $folder, $scheme);
+ $files[$scheme][] = $this->createTestFile('image/jpg', $folder, $scheme);
+ }
+ }
+ // Just to have a verbose output.
+ $this->drupalGet('admin/content/file/list');
+
+ // Create mbp independent folders and files.
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $scheme_path = drupal_realpath($scheme . '://');
+ $independent_folders[$scheme] = $scheme_path . '/non_mbp';
+ file_prepare_directory($independent_folders[$scheme], FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+ $independent_files[$scheme] = array(
+ $independent_folders[$scheme] . '/non_mpp.txt',
+ $scheme_path . '/non_mpp.txt',
+ );
+ foreach ($independent_files[$scheme] as $independent_file) {
+ file_put_contents($independent_file, str_repeat('01', 512));
+ }
+ }
+
+ // Reconfigure the root folder.
+ $data = array(
+ 'root_folder' => 'mbp',
+ );
+ $this->drupalPost('admin/config/media/media_browser_plus_settings', $data, t('Save configuration'));
+
+ // Reset the different caches.
+ drupal_static_reset('taxonomy_get_parents');
+ drupal_static_reset('taxonomy_get_parents_all');
+ entity_get_controller('taxonomy_term')->resetCache();
+ entity_get_controller('file')->resetCache();
+ clearstatcache();
+
+ // Check if managed files were moved.
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ foreach ($files[$scheme] as $file) {
+ // Refresh cached object.
+ $file = file_load($file->fid);
+ // Check if the new path is a child folder of the destination.
+ $this->assertTrue(
+ stristr($file->uri, $scheme . '://mbp/') !== FALSE,
+ 'Managed file is moved to new destination.'
+ );
+ $this->assertTrue(file_exists(drupal_realpath($file->uri)), 'File found in filesystem.');
+ }
+ }
+
+ // Check if independent folder wasn't moved.
+ foreach ($independent_folders as $scheme => $independent_folder) {
+ $this->assertTrue(
+ is_dir($independent_folder),
+ format_string('Independent folder is untouched. %scheme', array('%' => $scheme))
+ );
+ // Check if independent files weren't moved.
+ foreach ($independent_files[$scheme] as $file) {
+ $this->assertTrue(
+ file_exists($file),
+ format_string('Independent file is untouched. %scheme', array('%' => $scheme))
+ );
+ }
+ }
+
+ // And move all the stuff back.
+ // Reconfigure the root folder.
+ $data = array(
+ 'root_folder' => '',
+ );
+ $this->drupalPost('admin/config/media/media_browser_plus_settings', $data, t('Save configuration'));
+
+ // Reset the different caches.
+ drupal_static_reset('taxonomy_get_parents');
+ drupal_static_reset('taxonomy_get_parents_all');
+ entity_get_controller('taxonomy_term')->resetCache();
+ entity_get_controller('file')->resetCache();
+ clearstatcache();
+
+ // Check if managed files were moved.
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ foreach ($files[$scheme] as $file) {
+ // Refresh cached object.
+ $file = file_load($file->fid);
+ // Check if the new path is a child folder of the destination.
+ $this->assertTrue(
+ stristr($file->uri, $scheme . '://mpb/') === FALSE,
+ 'Managed file is moved to new destination.'
+ );
+ $this->assertTrue(file_exists(drupal_realpath($file->uri)), 'File found in filesystem.');
+ }
+ }
+
+ // Check if independent folder wasn't moved.
+ foreach ($independent_folders as $scheme => $independent_folder) {
+ $this->assertTrue(
+ is_dir($independent_folder),
+ format_string('Independent folder is untouched. %scheme', array('%' => $scheme))
+ );
+ // Check if independent files weren't moved.
+ foreach ($independent_files[$scheme] as $file) {
+ $this->assertTrue(
+ file_exists($file),
+ format_string('Independent file is untouched. %scheme', array('%' => $scheme))
+ );
+ }
+ }
+ }
+
+}
diff --git a/sites/all/modules/media_browser_plus/tests/media_browser_plus.test b/sites/all/modules/media_browser_plus/tests/media_browser_plus.test
new file mode 100644
index 000000000..50dd0fb8c
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/tests/media_browser_plus.test
@@ -0,0 +1,518 @@
+<?php
+/**
+ * @file
+ * Media Browser Plus basic tests.
+ */
+
+/**
+ * Basic media browser plus tests.
+ */
+class MediaBrowserPlusTest extends MediaBrowserPlusTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Media Browser Plus Basic Tests',
+ 'description' => 'Tests Media Browser Plus basic features',
+ 'group' => 'Media',
+ );
+ }
+
+ /**
+ * Test the ability to create folders.
+ */
+ public function testFolderCreation() {
+ $this->drupalLogin($this->adminUser);
+
+ // Ensure the link to the taxonomy management is available.
+ // Just to have a verbose output.
+ $this->drupalGet('admin/content/file/mbp');
+ $this->assertLinkByHref('admin/structure/taxonomy/media_folders', 0, 'Found link to folder taxonomy');
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+
+ // Create a normal folder structure.
+ $hierarchy = array(
+ 'root_folder' => array(
+ $this->randomName() => array(
+ $this->randomName() => array(),
+ ),
+ $this->randomName() => array(
+ $this->randomName() => array(),
+ ),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy);
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+ }
+
+ /**
+ * Test if invalid structures are automatically fixed.
+ */
+ public function testInvalidFolderCreation() {
+ $this->drupalLogin($this->adminUser);
+
+ // Ensure the link to the taxonomy management is available.
+ // Just to have a verbose output.
+ $this->drupalGet('admin/content/file/mbp');
+ $this->assertLinkByHref('admin/structure/taxonomy/media_folders', 0, 'Found link to folder taxonomy');
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+
+ // Create a normal folder structure.
+ $invalid_hierarchy_term = $this->randomName();
+ $hierarchy = array(
+ 'root_folder' => array(
+ $this->randomName() => array(
+ $this->randomName() => array(),
+ ),
+ $this->randomName() => array(
+ $this->randomName() => array(),
+ ),
+ ),
+ $invalid_hierarchy_term => array(
+ $this->randomName() => array(
+ $this->randomName() => array(),
+ ),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy, FALSE);
+
+ $root_folder_term = media_browser_plus_get_media_root_folder();
+ $folders = taxonomy_get_term_by_name($invalid_hierarchy_term);
+ $folder = reset($folders);
+ $parents = taxonomy_get_parents($folder->tid);
+ if ($this->assertEqual(count($parents), 1, 'Invalid intialized folder has a parent folder.')) {
+ $this->assertEqual($parents[$root_folder_term->tid]->tid, $root_folder_term->tid, 'Invalid intialized folder now belongs to the root folder.');
+ }
+ $path = media_browser_plus_construct_dir_path($folder);
+ $this->assertEqual($path, $invalid_hierarchy_term, 'Path for folder matches.');
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+ }
+
+ /**
+ * Test the ability to move folders.
+ */
+ public function testFolderMovemet() {
+ $this->drupalLogin($this->adminUser);
+
+ // Create test folder structure.
+ $folders = array(
+ 'source' => $this->randomName(),
+ 'source_child' => $this->randomName(),
+ 'source_child_child' => $this->randomName(),
+ 'destination' => $this->randomName(),
+ );
+ $hierarchy = array(
+ 'root_folder' => array(
+ $folders['source'] => array(
+ $folders['source_child'] => array(
+ $folders['source_child_child'] => array(),
+ ),
+ ),
+ $folders['destination'] => array(),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy, FALSE);
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+
+ // Create test files.
+ $files = array();
+ foreach ($folders as $type => $folder_name) {
+ $terms = taxonomy_get_term_by_name($folder_name);
+ $folder = $folders[$type] = reset($terms);
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $files[$type][$scheme][] = $this->createTestFile('text/plain', $folder, $scheme);
+ $files[$type][$scheme][] = $this->createTestFile('image/jpg', $folder, $scheme);
+ }
+ }
+ // Just to have a verbose output.
+ $this->drupalGet('admin/content/file/list');
+
+ $source_path = media_browser_plus_construct_dir_path($folders['source']);
+
+ // Now move the folder.
+ $data['parent[]'] = array($folders['destination']->tid);
+ $this->drupalPost('taxonomy/term/' . $folders['source']->tid . '/edit', $data, t('Save'));
+ // Reset the different caches.
+ drupal_static_reset('taxonomy_get_parents');
+ drupal_static_reset('taxonomy_get_parents_all');
+ entity_get_controller('taxonomy_term')->resetCache();
+ entity_get_controller('file')->resetCache();
+
+ $updated_source_path = media_browser_plus_construct_dir_path($folders['source']);
+
+ // And now check if all files and folders were moved properly.
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $original_source_path_full = file_stream_wrapper_uri_normalize($scheme . '://' . $source_path);
+ $updated_source_path_full = file_stream_wrapper_uri_normalize($scheme . '://' . $updated_source_path);
+
+ // Check if the path of the source has changed.
+ if ($this->assertNotEqual($original_source_path_full, $updated_source_path_full, 'Source folder changed')) {
+ // Check paths and files of source and its children.
+ $new_parent_folder = 'destination';
+ foreach (array('source', 'source_child', 'source_child_child') as $type) {
+ $folders[$type] = taxonomy_term_load($folders[$type]->tid);
+ $source_path = file_stream_wrapper_uri_normalize($scheme . '://' . media_browser_plus_construct_dir_path($folders[$type]));
+ $destination_path = file_stream_wrapper_uri_normalize($scheme . '://' . media_browser_plus_construct_dir_path($folders[$new_parent_folder]));
+
+ // Check if the new path is a child folder of the destination.
+ $this->assertTrue(
+ stristr($source_path, $destination_path) !== FALSE,
+ 'Source folder is child folder of destination.'
+ );
+ // Check if the old path was really deleted.
+ $this->assertFalse(is_dir($original_source_path_full), 'Old source folder deleted');
+
+ // Now check the files.
+ foreach ($files[$type][$scheme] as $file) {
+ // Refresh cached object.
+ $file = file_load($file->fid);
+ // Check if the new path is a child folder of the destination.
+ $this->assertTrue(
+ stristr($file->uri, $destination_path) !== FALSE,
+ 'Source file is child of new destination.'
+ );
+ }
+ $new_parent_folder = $type;
+ }
+ }
+ }
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+ }
+
+ /**
+ * Test the ability to delete folders.
+ */
+ public function testFolderDeletion() {
+ $this->drupalLogin($this->adminUser);
+
+ // Create test folder structure.
+ $folders = array(
+ 'source' => $this->randomName(),
+ 'source_child' => $this->randomName(),
+ 'source_child_child' => $this->randomName(),
+ );
+ $hierarchy = array(
+ 'root_folder' => array(
+ $folders['source'] => array(
+ $folders['source_child'] => array(
+ $folders['source_child_child'] => array(),
+ ),
+ ),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy, FALSE);
+
+ // Create test files.
+ $files = array();
+ foreach ($folders as $type => $folder_name) {
+ $terms = taxonomy_get_term_by_name($folder_name);
+ $folder = $folders[$type] = reset($terms);
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $files[$scheme][$type][] = $this->createTestFile('text/plain', $folder, $scheme);
+ $files[$scheme][$type][] = $this->createTestFile('image/jpg', $folder, $scheme);
+ }
+ }
+ // Just to have a verbose output.
+ $this->drupalGet('admin/content/file/list');
+
+ $folder_path = media_browser_plus_construct_dir_path($folders['source']);
+ // Delete the folder.
+ $this->drupalPost('taxonomy/term/' . $folders['source']->tid . '/edit', array(), t('Delete'));
+ $this->drupalPost(NULL, array(), t('Delete'));
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $directory = file_stream_wrapper_uri_normalize($scheme . '://' . $folder_path);
+ $this->assertFalse(file_exists($directory), 'Folder and all children deleted');
+ }
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+ }
+
+ /**
+ * Test the ability to delete sub-folders.
+ *
+ * Introduced due https://drupal.org/node/2176317
+ * @link https://drupal.org/node/2176317
+ *
+ * At the moment writing this test hook_taxonomy_term_delete() is invoked
+ * after all data are removed from the db. However, there are static caches in
+ * effect that still allow to build the necessary paths to clean up the
+ * filesystem. Unfortunately this is only true for terms that have children.
+ * media_browser_plus_form_taxonomy_term_confirm_delete_submit() fixes that.
+ *
+ * @see media_browser_plus_form_taxonomy_term_confirm_delete_submit()
+ */
+ public function testSubFolderDeletion() {
+ $this->drupalLogin($this->adminUser);
+
+ // Create test folder structure.
+ $folders = array(
+ 'source' => $this->randomName(),
+ 'source_child' => $this->randomName(),
+ 'source_child_child' => $this->randomName(),
+ );
+ $hierarchy = array(
+ 'root_folder' => array(
+ $folders['source'] => array(
+ $folders['source_child'] => array(
+ $folders['source_child_child'] => array(),
+ ),
+ ),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy, FALSE);
+
+ $folders = taxonomy_get_term_by_name($folders['source_child_child']);
+ $folder = reset($folders);
+ $folder_path = media_browser_plus_construct_dir_path($folder);
+
+ // Delete the folder.
+ $this->drupalPost('taxonomy/term/' . $folder->tid . '/edit', array(), t('Delete'));
+ $this->drupalPost(NULL, array(), t('Delete'));
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $directory = file_stream_wrapper_uri_normalize($scheme . '://' . $folder_path);
+ $this->assertFalse(file_exists($directory), 'Folder deleted');
+ }
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+ }
+
+ /**
+ * Test the ability to delete folders when files are used.
+ */
+ public function testFolderDeletionWithUsedFiles() {
+ $this->drupalLogin($this->adminUser);
+
+ // Create test folder structure.
+ $folders = array(
+ 'source' => $this->randomName(),
+ 'source_child' => $this->randomName(),
+ 'source_child_child' => $this->randomName(),
+ );
+ $hierarchy = array(
+ 'root_folder' => array(
+ $folders['source'] => array(
+ $folders['source_child'] => array(
+ $folders['source_child_child'] => array(),
+ ),
+ ),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy, FALSE);
+
+ // Create test files.
+ $files = array();
+ foreach ($folders as $type => $folder_name) {
+ $terms = taxonomy_get_term_by_name($folder_name);
+ $folder = $folders[$type] = reset($terms);
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $files[$scheme][$type][] = $this->createTestFile('text/plain', $folder, $scheme);
+ $files[$scheme][$type][] = $file = $this->createTestFile('image/jpg', $folder, $scheme);
+ // Mark one file as used.
+ file_usage_add($file, 'node', 'node', 1);
+ }
+ }
+ // Just to have a verbose output.
+ $this->drupalGet('admin/content/file/list');
+
+ $folder_path = media_browser_plus_construct_dir_path($folders['source']);
+ // Delete the folder.
+ $this->drupalPost('taxonomy/term/' . $folders['source']->tid . '/edit', array(), t('Delete'));
+ $this->drupalPost(NULL, array(), t('Delete'));
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $directory = file_stream_wrapper_uri_normalize($scheme . '://' . $folder_path);
+ $this->assertTrue(file_exists($directory), 'Folder still exists');
+ }
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+ }
+
+ /**
+ * Test if the setting to make MBP the default file management works.
+ */
+ public function testMakeMBPDefaultHandler() {
+ $this->drupalLogin($this->adminUser);
+
+ // Disable MBP as default.
+ $data = array(
+ 'media_browser_plus_thumbnails_as_default_browser' => FALSE,
+ );
+ $this->drupalPost('admin/config/media/media_browser_plus_settings', $data, t('Save configuration'));
+ // I don't know why I've to do a menu rebuild here, but it doesn't pass
+ // without it.
+ menu_rebuild();
+
+ $this->drupalGet('admin/content/file');
+ $this->assertText('Show only items where', 'Default file list is shown');
+
+ // Enable mbp as default.
+ $data = array(
+ 'media_browser_plus_thumbnails_as_default_browser' => TRUE,
+ );
+ $this->drupalPost('admin/config/media/media_browser_plus_settings', $data, t('Save configuration'));
+ // I don't know why I've to do a menu rebuild here, but it doesn't pass
+ // without it.
+ menu_rebuild();
+
+ $this->drupalGet('admin/content/file');
+ $this->assertText('Media Basket', 'Media Browser plus is default file handler');
+ }
+
+ /**
+ * Test the ability to delete folders.
+ */
+ public function testFolderRename() {
+ $this->drupalLogin($this->adminUser);
+
+ // Create test folder structure.
+ $folders = array(
+ 'source' => $this->randomName(),
+ 'source_child' => $this->randomName(),
+ );
+ $hierarchy = array(
+ 'root_folder' => array(
+ $folders['source'] => array(
+ $folders['source_child'] => array(),
+ ),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy, FALSE);
+
+ $terms = taxonomy_get_term_by_name($folders['source']);
+ $source_folder = reset($terms);
+
+ // Create test files.
+ $files = array();
+ $folder_paths = array();
+ foreach ($folders as $type => $folder_name) {
+ $terms = taxonomy_get_term_by_name($folder_name);
+ $folder = reset($terms);
+ $folder_paths[$type] = media_browser_plus_construct_dir_path($folder);
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $files[$scheme][$type][] = $this->createTestFile('text/plain', $folder, $scheme);
+ $files[$scheme][$type][] = $this->createTestFile('image/jpg', $folder, $scheme);
+ }
+ }
+ // Just to have a verbose output.
+ $this->drupalGet('admin/content/file/list');
+
+ // Rename the source folder.
+ $new_folder_name = $this->randomName();
+ $data = array(
+ 'name' => $new_folder_name,
+ );
+ $this->drupalPost('taxonomy/term/' . $source_folder->tid . '/edit', $data, t('Save'));
+ $folders['source'] = $data['name'];
+
+ // Check if everything was moved properly.
+ entity_get_controller('taxonomy_term')->resetCache();
+ taxonomy_terms_static_reset();
+ foreach ($folders as $type => $folder_name) {
+ $terms = taxonomy_get_term_by_name($folder_name);
+ $folder = reset($terms);
+ $new_folder_path = media_browser_plus_construct_dir_path($folder);
+ $this->assertNotEqual(
+ $folder_paths[$type],
+ $new_folder_path,
+ format_string('Folder path changed: %source %destination', array(
+ '%source' => $folder_paths[$type],
+ '%destination' => $new_folder_path,
+ ))
+ );
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $this->assertTrue(file_exists(drupal_realpath($scheme . '://' . $new_folder_path)), 'Folder found');
+ foreach ($files[$scheme][$type] as $file) {
+ $file_path = $file->uri;
+ $file = file_load($file->fid);
+ $new_file_path = $file->uri;
+ $this->assertNotEqual($new_file_path, $file_path, 'File path changed');
+ $this->assertTrue(file_exists($new_file_path), 'File found');
+ }
+ }
+ }
+
+ // Just to have a verbose output.
+ $this->drupalGet('admin/structure/taxonomy/media_folders');
+ }
+
+
+ /**
+ * Test the ability to delete folders.
+ */
+ public function testFileMovement() {
+ $this->drupalLogin($this->adminUser);
+
+ $root_folder = media_browser_plus_get_media_root_folder();
+ // Create test folder structure.
+ $folders = array(
+ 'root_folder' => $root_folder->name,
+ 'source' => $this->randomName(),
+ 'source_child' => $this->randomName(),
+ );
+ $hierarchy = array(
+ 'root_folder' => array(
+ $folders['source'] => array(
+ $folders['source_child'] => array(),
+ ),
+ ),
+ );
+ $this->folderCreationHelper($hierarchy, FALSE);
+ $target_folders = array(
+ 'root_folder' => 'source',
+ 'source' => 'source_child',
+ 'source_child' => 'root_folder',
+ );
+
+ // Create test files.
+ $files = array();
+ $folder_paths = array();
+ foreach ($folders as $type => $folder_name) {
+ $terms = taxonomy_get_term_by_name($folder_name);
+ $folder = reset($terms);
+ $folder_paths[$type] = media_browser_plus_construct_dir_path($folder);
+ foreach (media_get_local_stream_wrappers() as $scheme => $scheme_info) {
+ $files[$scheme][$type][] = $this->createTestFile('text/plain', $folder, $scheme);
+ $files[$scheme][$type][] = $this->createTestFile('image/jpg', $folder, $scheme);
+ }
+ }
+ // Just to have a verbose output.
+ $this->drupalGet('admin/content/file/list');
+
+ // Set a new folder for the files and check if they moved accordingly.
+ $moved_files = array();
+ foreach ($files as $scheme => $source_files) {
+ foreach ($source_files as $folder => $folder_files) {
+ $target_folder = $target_folders[$folder];
+ $terms = taxonomy_get_term_by_name($folders[$target_folder]);
+ $target_folder = reset($terms);
+ $path = $scheme . '://' . media_browser_plus_construct_dir_path($target_folder);
+ foreach ($folder_files as $folder_file) {
+ $folder_file->field_folder[LANGUAGE_NONE][0]['tid'] = $target_folder->tid;
+ $source_uri = $folder_file->uri;
+ file_save($folder_file);
+
+ $files = entity_load('file', array($folder_file->fid), array(), TRUE);
+ $folder_file = reset($files);
+ $this->assertNotEqual($source_uri, $folder_file->uri, 'File uri changed');
+ $expected_uri = file_stream_wrapper_uri_normalize($path . '/' . basename($folder_file->uri));
+ $this->assertEqual($folder_file->uri, $expected_uri);
+ clearstatcache();
+ $this->assertTrue(file_exists(drupal_realpath($expected_uri)), 'File properly moved');
+ }
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/tests/media_browser_plus_tests.info b/sites/all/modules/media_browser_plus/tests/media_browser_plus_tests.info
new file mode 100644
index 000000000..e443e4f2c
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/tests/media_browser_plus_tests.info
@@ -0,0 +1,19 @@
+name = Media Browser Plus Tests
+description = Media Browser Plus Tests.
+package = Testing
+core = 7.x
+hidden = TRUE
+
+dependencies[] = media_browser_plus
+
+files[] = media_browser_plus.base.test
+files[] = media_browser_plus.test
+files[] = media_browser_plus.root_folder.test
+
+
+; Information added by Drupal.org packaging script on 2014-05-20
+version = "7.x-3.0-beta3"
+core = "7.x"
+project = "media_browser_plus"
+datestamp = "1400584428"
+
diff --git a/sites/all/modules/media_browser_plus/tests/media_browser_plus_tests.module b/sites/all/modules/media_browser_plus/tests/media_browser_plus_tests.module
new file mode 100644
index 000000000..45c595027
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/tests/media_browser_plus_tests.module
@@ -0,0 +1,5 @@
+<?php
+/**
+ * @file
+ * Media Browser Plus tests.
+ */
diff --git a/sites/all/modules/media_browser_plus/views/media-browser-plus-views-view-media-browser.tpl.php b/sites/all/modules/media_browser_plus/views/media-browser-plus-views-view-media-browser.tpl.php
new file mode 100644
index 000000000..1b1515586
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media-browser-plus-views-view-media-browser.tpl.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @file media-views-view-media-browser.tpl.php
+ * View template to display a grid of media previews in the media browser.
+ *
+ * @see views-view-list.tpl.php
+ * @see template_preprocess_media_views_view_media_browser()
+ * @ingroup views_templates
+ */
+?>
+<?php print $wrapper_prefix; ?>
+<table>
+ <thead>
+ <tr>
+ <?php if(!empty($folders)) : ?><th width="200"><?php print t('Folders') ?></th><?php endif; ?>
+ <th width="*"><?php print t('Media Files') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <?php if(!empty($folders)) : ?><td class="mbp-folders"><?php print $folders ?></td><?php endif; ?>
+ <td class="mbp-file-list">
+ <?php print $list_type_prefix; ?>
+ <?php foreach ($rows as $id => $row): ?>
+ <li id="media-item-<?php print $view->result[$id]->fid; ?>" class="<?php print $classes_array[$id]; ?>">
+ <?php print $row; ?>
+ </li>
+ <?php endforeach; ?>
+ <?php print $list_type_suffix; ?>
+ <div id="status"></div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+<?php print $wrapper_suffix; ?>
diff --git a/sites/all/modules/media_browser_plus/views/media_browser_plus.views.inc b/sites/all/modules/media_browser_plus/views/media_browser_plus.views.inc
new file mode 100644
index 000000000..a4e277cd5
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media_browser_plus.views.inc
@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * @file
+ * Provide Views data and handlers for media.module
+ */
+
+/**
+ * Implements hook_form_FORM_ID_alter() for views_exposed_form.
+ *
+ * Convert the media_browser_plus uri filter for filtering by file scheme into
+ * a select.
+ *
+ * @see file_filters()
+ */
+function media_browser_plus_form_views_exposed_form_alter(&$form, &$form_state) {
+ if ($form_state['view']->name == 'media_browser_plus' && isset($form['uri'])) {
+ $visible_steam_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE);
+ $options = array();
+ foreach ($visible_steam_wrappers as $scheme => $information) {
+ $options[$scheme] = check_plain($information['name']);
+ }
+ if (count($visible_steam_wrappers) > 1) {
+ $form['uri'] = array(
+ '#options' => array('' => '- ' . t('Any') . ' -') + $options,
+ '#type' => 'select',
+ '#default_value' => $form['uri']['#default_value'],
+ );
+ }
+ else {
+ // If there's only one stream wrapper hide the filter.
+ unset($form['#info']['filter-uri']);
+ $form['uri'] = array(
+ '#type' => 'hidden',
+ '#default_value' => '',
+ );
+ }
+ }
+}
+
+/**
+ * Implements hook_views_plugins().
+ *
+ * Generate a list of which base-tables to enabled the plugins for.
+ */
+function media_browser_plus_views_plugins() {
+ $plugins = array();
+
+ $path = drupal_get_path('module', 'media_browser_plus') . '/views';
+
+ // Always allow the actual file-table
+ $base = array('file_managed');
+ if (module_exists('search_api')) {
+ // If the Search API module exists, also allow indices of the file-entity
+ // that has the fid field indexed.
+ $indices = search_api_index_load_multiple(NULL);
+ foreach ($indices as $machine_name => $index) {
+ if ($index->item_type == 'file' && isset($index->options['fields']['fid'])) {
+ $base[] = 'search_api_index_' . $machine_name;
+ }
+ }
+ }
+
+ // Style plugin.
+ $plugins['style']['media_browser_plus'] = array(
+ 'title' => t('Media browser plus'),
+ 'help' => t('Displays rows as an HTML list including the folder management.'),
+ 'handler' => 'media_browser_plus_views_plugin_style_media_browser',
+ 'theme' => 'media_browser_plus_views_view_media_browser',
+ 'theme path' => $path,
+ 'base' => $base,
+ 'uses row plugin' => TRUE,
+ 'uses row class' => FALSE,
+ 'uses options' => TRUE,
+ 'uses fields' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-media-browser',
+ );
+ return $plugins;
+}
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function media_browser_plus_views_data_alter(&$data) {
+ $data['file_managed']['media_browser_plus_preview'] = array(
+ 'title' => t('Preview'),
+ 'group' => t('Media browser plus'),
+ 'help' => t('Preview of a file item as used in the media browsers.'),
+ 'real field' => 'fid',
+ 'field' => array(
+ 'handler' => 'media_browser_plus_views_handler_field_preview',
+ 'click sortable' => FALSE,
+ ),
+ );
+ $data['file_managed']['media_browser_plus_preview_vbo'] = array(
+ 'title' => t('Preview with operations'),
+ 'group' => t('Media browser plus'),
+ 'help' => t('Preview of a file item as used in the media browsers.'),
+ 'real field' => 'fid',
+ 'field' => array(
+ 'handler' => 'media_browser_plus_views_handler_field_preview_vbo',
+ 'click sortable' => FALSE,
+ ),
+ );
+
+ $data['file_managed']['media_browser_plus_views_handler_area_actions'] = array(
+ 'title' => t('Actions'),
+ 'group' => t('Media browser plus'),
+ 'help' => t('Area with different file related actions.'),
+ 'area' => array(
+ 'handler' => 'media_browser_plus_views_handler_area_actions',
+ ),
+ );
+ $data['file_managed']['media_browser_plus_views_handler_area_basket'] = array(
+ 'title' => t('Basket'),
+ 'group' => t('Media browser plus'),
+ 'help' => t('Area to collect and download files.'),
+ 'area' => array(
+ 'handler' => 'media_browser_plus_views_handler_area_basket',
+ ),
+ );
+ $data['file_managed']['media_browser_plus_views_handler_area_navigation'] = array(
+ 'title' => t('Navigation'),
+ 'group' => t('Media browser plus'),
+ 'help' => t('Area to navigate using the folder structure.'),
+ 'table' => 'field_data_field_folder',
+ 'area' => array(
+ 'handler' => 'media_browser_plus_views_handler_area_navigation',
+ ),
+ );
+}
+
+/**
+ * Display the view as a media browser.
+ *
+ * @see template_preprocess_media_views_view_media_browser()
+ */
+function template_preprocess_media_browser_plus_views_view_media_browser(&$vars) {
+ module_load_include('inc', 'media', 'includes/media.browser');
+
+ $view = $vars['view'];
+ $display_handler = $vars['view']->display_handler;
+ $handler = $vars['view']->style_plugin;
+
+ // If this is a media browser display add some more JS.
+ // Re-uses vast parts of template_preprocess_media_views_view_media_browser().
+ if ($display_handler instanceof media_views_plugin_display_media_browser) {
+ $fids = array();
+ foreach ($view->result as $index => $result) {
+ $fids[$index] = $result->fid;
+ }
+ $files = file_load_multiple($fids);
+ foreach ($files as $file) {
+ // Add url/preview to the file object.
+ media_browser_build_media_item($file);
+ }
+
+ // Add the files to JS so that they are accessible inside the browser.
+ drupal_add_js(array('media' => array('files' => array_values($files))), 'setting');
+
+ // Add the browser parameters to the settings and that this display exists.
+ drupal_add_js(array(
+ 'media' => array(
+ 'browser' => array(
+ 'params' => media_get_browser_params(),
+ 'views' => array(
+ $vars['view']->name => array(
+ $vars['view']->current_display,
+ ),
+ ),
+ ),
+ ),
+ ), 'setting');
+
+ // Add media browser javascript and CSS.
+ drupal_add_js(drupal_get_path('module', 'media') . '/js/plugins/media.views.js');
+ }
+
+ // Add classes and wrappers from the style plugin.
+ $handler = $vars['view']->style_plugin;
+
+ $class = explode(' ', $handler->options['class']);
+ $class = array_map('drupal_clean_css_identifier', $class);
+
+ $wrapper_class = explode(' ', $handler->options['wrapper_class']);
+ $wrapper_class = array_map('drupal_clean_css_identifier', $wrapper_class);
+
+ $vars['class'] = implode(' ', $class);
+ $vars['wrapper_class'] = implode(' ', $wrapper_class);
+ $vars['wrapper_prefix'] = '<div class="' . implode(' ', $wrapper_class) . '">';
+ $vars['wrapper_suffix'] = '</div>';
+ $vars['list_type_prefix'] = '<' . $handler->options['type'] . ' id="media-browser-library-list" class="' . implode(' ', $class) . '">';
+ $vars['list_type_suffix'] = '</' . $handler->options['type'] . '>';
+
+ // Run theming variables through a standard Views preprocess function.
+ template_preprocess_views_view_unformatted($vars);
+
+ // Now add our own stuff - based on the set options.
+ // Add the js / css library.
+ drupal_add_library('media_browser_plus', 'media_browser_plus');
+
+ // Add more information for the JS integration.
+ $vars['options']['view_id'] = $view->name;
+ $vars['options']['view_display_id'] = $view->current_display;
+
+ // Add settings for the js library.
+ drupal_add_js(array(
+ 'mbp' => array(
+ 'views' => array(
+ $view->name . ':' . $view->current_display => $vars['options'],
+ ),
+ )),
+ 'setting'
+ );
+
+ // Special handling of the navigation. We abuse a area handler to add and
+ // configure the navigation - that way it's very flexible.
+ // Check if a navigation handler is configured.
+ $vars['folders'] = NULL;
+ foreach (array('header', 'footer') as $area) {
+ foreach ($display_handler->handlers[$area] as $handler) {
+ if ($handler instanceof media_browser_plus_views_handler_area_navigation) {
+ $vars['folders'] = $handler->render_custom();
+ break(2);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_views_invalidate_cache().
+ */
+function media_browser_plus_views_invalidate_cache() {
+ cache_clear_all('media:browser:plus:plugin', 'cache', TRUE);
+ drupal_static_reset('media_browser_plus_get_browser_plugin_info');
+}
diff --git a/sites/all/modules/media_browser_plus/views/media_browser_plus.views_default.inc b/sites/all/modules/media_browser_plus/views/media_browser_plus.views_default.inc
new file mode 100644
index 000000000..fd3d0999d
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media_browser_plus.views_default.inc
@@ -0,0 +1,444 @@
+<?php
+/**
+ * @file
+ * Provides the necessary default views for MBP.
+ */
+
+
+/**
+ * Implements hook_views_default_views().
+ */
+function media_browser_plus_views_default_views() {
+ $views = array();
+
+ $view = media_browser_plus_views_taxonomy_tree();
+ $views[$view->name] = $view;
+
+ $view = media_browser_plus_views_management();
+ $view->disabled = variable_get('media_browser_plus_disable_default_view', FALSE);
+ $views[$view->name] = $view;
+
+ return $views;
+}
+
+/**
+ * View to display the folders in the library.
+ *
+ * @return view
+ * The exported views object.
+ */
+function media_browser_plus_views_taxonomy_tree() {
+ $view = new view();
+ $view->name = 'media_browser_plus_folders';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'taxonomy_term_data';
+ $view->human_name = 'Media browser plus folders';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'time';
+ $handler->display->display_options['cache']['results_lifespan'] = '518400';
+ $handler->display->display_options['cache']['results_lifespan_custom'] = '0';
+ $handler->display->display_options['cache']['output_lifespan'] = '518400';
+ $handler->display->display_options['cache']['output_lifespan_custom'] = '0';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'none';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['style_plugin'] = 'tree';
+ $handler->display->display_options['style_options']['type'] = 'ol';
+ $handler->display->display_options['style_options']['main_field'] = 'tid';
+ $handler->display->display_options['style_options']['parent_field'] = 'tid_1';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ $handler->display->display_options['row_options']['default_field_elements'] = FALSE;
+ /* Relationship: Taxonomy term: Parent term */
+ $handler->display->display_options['relationships']['parent']['id'] = 'parent';
+ $handler->display->display_options['relationships']['parent']['table'] = 'taxonomy_term_hierarchy';
+ $handler->display->display_options['relationships']['parent']['field'] = 'parent';
+ /* Field: Taxonomy term: Term ID */
+ $handler->display->display_options['fields']['tid']['id'] = 'tid';
+ $handler->display->display_options['fields']['tid']['table'] = 'taxonomy_term_data';
+ $handler->display->display_options['fields']['tid']['field'] = 'tid';
+ $handler->display->display_options['fields']['tid']['label'] = '';
+ $handler->display->display_options['fields']['tid']['exclude'] = TRUE;
+ $handler->display->display_options['fields']['tid']['element_label_colon'] = FALSE;
+ /* Field: Taxonomy term: Term ID */
+ $handler->display->display_options['fields']['tid_1']['id'] = 'tid_1';
+ $handler->display->display_options['fields']['tid_1']['table'] = 'taxonomy_term_data';
+ $handler->display->display_options['fields']['tid_1']['field'] = 'tid';
+ $handler->display->display_options['fields']['tid_1']['relationship'] = 'parent';
+ $handler->display->display_options['fields']['tid_1']['label'] = '';
+ $handler->display->display_options['fields']['tid_1']['exclude'] = TRUE;
+ $handler->display->display_options['fields']['tid_1']['element_label_colon'] = FALSE;
+ /* Field: Taxonomy term: Name */
+ $handler->display->display_options['fields']['name']['id'] = 'name';
+ $handler->display->display_options['fields']['name']['table'] = 'taxonomy_term_data';
+ $handler->display->display_options['fields']['name']['field'] = 'name';
+ $handler->display->display_options['fields']['name']['label'] = '';
+ $handler->display->display_options['fields']['name']['alter']['alter_text'] = TRUE;
+ $handler->display->display_options['fields']['name']['alter']['text'] = '<div class="icon"></div> [name]';
+ $handler->display->display_options['fields']['name']['alter']['word_boundary'] = FALSE;
+ $handler->display->display_options['fields']['name']['alter']['ellipsis'] = FALSE;
+ $handler->display->display_options['fields']['name']['element_type'] = '0';
+ $handler->display->display_options['fields']['name']['element_label_colon'] = FALSE;
+ $handler->display->display_options['fields']['name']['element_wrapper_type'] = 'div';
+ $handler->display->display_options['fields']['name']['element_wrapper_class'] = 'folder-name folder-id-[tid]';
+ $handler->display->display_options['fields']['name']['element_default_classes'] = FALSE;
+ /* Sort criterion: Taxonomy term: Weight */
+ $handler->display->display_options['sorts']['weight']['id'] = 'weight';
+ $handler->display->display_options['sorts']['weight']['table'] = 'taxonomy_term_data';
+ $handler->display->display_options['sorts']['weight']['field'] = 'weight';
+ /* Filter criterion: Taxonomy vocabulary: Machine name */
+ $handler->display->display_options['filters']['machine_name']['id'] = 'machine_name';
+ $handler->display->display_options['filters']['machine_name']['table'] = 'taxonomy_vocabulary';
+ $handler->display->display_options['filters']['machine_name']['field'] = 'machine_name';
+ $handler->display->display_options['filters']['machine_name']['value'] = array(
+ 'media_folders' => 'media_folders',
+ );
+ $translatables['media_browser_plus_folders'] = array(
+ t('Master'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Parent'),
+ t('.'),
+ t(','),
+ t('<div class="icon"></div> [name]'),
+ );
+
+ return $view;
+}
+
+
+/**
+ * View to display the folders in the library.
+ *
+ * @return view
+ * The exported views object.
+ */
+function media_browser_plus_views_management() {
+ $view = new view();
+ $view->name = 'media_browser_plus';
+ $view->description = 'Default view for the media browser library tab.';
+ $view->tag = 'media, default';
+ $view->base_table = 'file_managed';
+ $view->human_name = 'Media browser plus';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['use_ajax'] = TRUE;
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['group_by'] = TRUE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'administer files';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_tags'] = array(
+ 0 => 'media_browser',
+ );
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['exposed_form']['options']['autosubmit'] = TRUE;
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['style_plugin'] = 'media_browser_plus';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Header: Media browser plus: Navigation */
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['id'] = 'media_browser_plus_views_handler_area_navigation';
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['table'] = 'file_managed';
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['field'] = 'media_browser_plus_views_handler_area_navigation';
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['label'] = '';
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['view_to_insert'] = 'media_browser_plus_folders:default';
+ /* No results behavior: Global: Text area */
+ $handler->display->display_options['empty']['area']['id'] = 'area';
+ $handler->display->display_options['empty']['area']['table'] = 'views';
+ $handler->display->display_options['empty']['area']['field'] = 'area';
+ $handler->display->display_options['empty']['area']['empty'] = TRUE;
+ $handler->display->display_options['empty']['area']['content'] = 'No files found';
+ $handler->display->display_options['empty']['area']['format'] = 'filtered_html';
+ /* Field: Media browser plus: Preview */
+ $handler->display->display_options['fields']['media_browser_plus_preview']['id'] = 'media_browser_plus_preview';
+ $handler->display->display_options['fields']['media_browser_plus_preview']['table'] = 'file_managed';
+ $handler->display->display_options['fields']['media_browser_plus_preview']['field'] = 'media_browser_plus_preview';
+ $handler->display->display_options['fields']['media_browser_plus_preview']['label'] = '';
+ $handler->display->display_options['fields']['media_browser_plus_preview']['element_label_colon'] = FALSE;
+ /* Sort criterion: File: Upload date */
+ $handler->display->display_options['sorts']['timestamp']['id'] = 'timestamp';
+ $handler->display->display_options['sorts']['timestamp']['table'] = 'file_managed';
+ $handler->display->display_options['sorts']['timestamp']['field'] = 'timestamp';
+ $handler->display->display_options['sorts']['timestamp']['order'] = 'DESC';
+ $handler->display->display_options['sorts']['timestamp']['exposed'] = TRUE;
+ $handler->display->display_options['sorts']['timestamp']['expose']['label'] = 'Upload date';
+ /* Sort criterion: SUM(File Usage: Use count) */
+ $handler->display->display_options['sorts']['count']['id'] = 'count';
+ $handler->display->display_options['sorts']['count']['table'] = 'file_usage';
+ $handler->display->display_options['sorts']['count']['field'] = 'count';
+ $handler->display->display_options['sorts']['count']['group_type'] = 'sum';
+ $handler->display->display_options['sorts']['count']['exposed'] = TRUE;
+ $handler->display->display_options['sorts']['count']['expose']['label'] = 'Use count';
+ /* Filter criterion: File: Status */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'file_managed';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = array(
+ 1 => '1',
+ );
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ /* Filter criterion: File: Name */
+ $handler->display->display_options['filters']['filename']['id'] = 'filename';
+ $handler->display->display_options['filters']['filename']['table'] = 'file_managed';
+ $handler->display->display_options['filters']['filename']['field'] = 'filename';
+ $handler->display->display_options['filters']['filename']['operator'] = 'contains';
+ $handler->display->display_options['filters']['filename']['group'] = 1;
+ $handler->display->display_options['filters']['filename']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['filename']['expose']['operator_id'] = 'filename_op';
+ $handler->display->display_options['filters']['filename']['expose']['label'] = 'File name';
+ $handler->display->display_options['filters']['filename']['expose']['operator'] = 'filename_op';
+ $handler->display->display_options['filters']['filename']['expose']['identifier'] = 'filename';
+ /* Filter criterion: File: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'file_managed';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['group'] = 1;
+ $handler->display->display_options['filters']['type']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['label'] = 'Type';
+ $handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+ /* Filter criterion: File: Path */
+ $handler->display->display_options['filters']['uri']['id'] = 'uri';
+ $handler->display->display_options['filters']['uri']['table'] = 'file_managed';
+ $handler->display->display_options['filters']['uri']['field'] = 'uri';
+ $handler->display->display_options['filters']['uri']['operator'] = 'starts';
+ $handler->display->display_options['filters']['uri']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['uri']['expose']['operator_id'] = 'uri_op';
+ $handler->display->display_options['filters']['uri']['expose']['label'] = 'Scheme';
+ $handler->display->display_options['filters']['uri']['expose']['operator'] = 'uri_op';
+ $handler->display->display_options['filters']['uri']['expose']['identifier'] = 'uri';
+
+ /* Display: Media browser */
+ $handler = $view->new_display('media_browser', 'Media browser', 'media_browser_thumbnails');
+ $handler->display->display_options['defaults']['title'] = FALSE;
+ $handler->display->display_options['title'] = 'Library (MBP)';
+ $handler->display->display_options['defaults']['hide_admin_links'] = FALSE;
+
+ /* Display: My files */
+ $handler = $view->new_display('media_browser', 'My files', 'media_browser_my_files');
+ $handler->display->display_options['defaults']['title'] = FALSE;
+ $handler->display->display_options['title'] = 'My files (MBP)';
+ $handler->display->display_options['defaults']['hide_admin_links'] = FALSE;
+ $handler->display->display_options['defaults']['access'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'view own files';
+ $handler->display->display_options['defaults']['relationships'] = FALSE;
+ /* Relationship: File: User who uploaded */
+ $handler->display->display_options['relationships']['uid']['id'] = 'uid';
+ $handler->display->display_options['relationships']['uid']['table'] = 'file_managed';
+ $handler->display->display_options['relationships']['uid']['field'] = 'uid';
+ $handler->display->display_options['relationships']['uid']['required'] = TRUE;
+ $handler->display->display_options['defaults']['arguments'] = FALSE;
+ $handler->display->display_options['defaults']['filter_groups'] = FALSE;
+ $handler->display->display_options['defaults']['filters'] = FALSE;
+ /* Filter criterion: File: Status */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'file_managed';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = array(
+ 1 => '1',
+ );
+ /* Filter criterion: File: Name */
+ $handler->display->display_options['filters']['filename']['id'] = 'filename';
+ $handler->display->display_options['filters']['filename']['table'] = 'file_managed';
+ $handler->display->display_options['filters']['filename']['field'] = 'filename';
+ $handler->display->display_options['filters']['filename']['operator'] = 'contains';
+ $handler->display->display_options['filters']['filename']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['filename']['expose']['operator_id'] = 'filename_op';
+ $handler->display->display_options['filters']['filename']['expose']['label'] = 'File name';
+ $handler->display->display_options['filters']['filename']['expose']['operator'] = 'filename_op';
+ $handler->display->display_options['filters']['filename']['expose']['identifier'] = 'filename';
+ /* Filter criterion: File: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'file_managed';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['label'] = 'Type';
+ $handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+ /* Filter criterion: User: Current */
+ $handler->display->display_options['filters']['uid_current']['id'] = 'uid_current';
+ $handler->display->display_options['filters']['uid_current']['table'] = 'users';
+ $handler->display->display_options['filters']['uid_current']['field'] = 'uid_current';
+ $handler->display->display_options['filters']['uid_current']['relationship'] = 'uid';
+ $handler->display->display_options['filters']['uid_current']['value'] = '1';
+ /* Filter criterion: File: Path */
+ $handler->display->display_options['filters']['uri']['id'] = 'uri';
+ $handler->display->display_options['filters']['uri']['table'] = 'file_managed';
+ $handler->display->display_options['filters']['uri']['field'] = 'uri';
+ $handler->display->display_options['filters']['uri']['operator'] = 'starts';
+ $handler->display->display_options['filters']['uri']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['uri']['expose']['operator_id'] = 'uri_op';
+ $handler->display->display_options['filters']['uri']['expose']['label'] = 'Scheme';
+ $handler->display->display_options['filters']['uri']['expose']['operator'] = 'uri_op';
+ $handler->display->display_options['filters']['uri']['expose']['identifier'] = 'uri';
+
+ /* Display: Media Browser Plus */
+ $handler = $view->new_display('page', 'Media Browser Plus', 'media_browser_plus');
+ $handler->display->display_options['defaults']['header'] = FALSE;
+ /* Header: Media browser plus: Navigation */
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['id'] = 'media_browser_plus_views_handler_area_navigation';
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['table'] = 'file_managed';
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['field'] = 'media_browser_plus_views_handler_area_navigation';
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['label'] = '';
+ $handler->display->display_options['header']['media_browser_plus_views_handler_area_navigation']['view_to_insert'] = 'media_browser_plus_folders:default';
+ $handler->display->display_options['defaults']['footer'] = FALSE;
+ /* Footer: Media browser plus: Actions */
+ $handler->display->display_options['footer']['media_browser_plus_views_handler_area_actions']['id'] = 'media_browser_plus_views_handler_area_actions';
+ $handler->display->display_options['footer']['media_browser_plus_views_handler_area_actions']['table'] = 'file_managed';
+ $handler->display->display_options['footer']['media_browser_plus_views_handler_area_actions']['field'] = 'media_browser_plus_views_handler_area_actions';
+ $handler->display->display_options['footer']['media_browser_plus_views_handler_area_actions']['mbp_action_area']['actions'] = array(
+ 0 => 'view',
+ 1 => 'edit',
+ 2 => 'delete',
+ 3 => 'download',
+ 'edit' => 'edit',
+ 'delete' => 'delete',
+ 'download' => 'download',
+ 'basket' => 0,
+ );
+ /* Footer: Media browser plus: Basket */
+ $handler->display->display_options['footer']['media_browser_plus_views_handler_area_basket']['id'] = 'media_browser_plus_views_handler_area_basket';
+ $handler->display->display_options['footer']['media_browser_plus_views_handler_area_basket']['table'] = 'file_managed';
+ $handler->display->display_options['footer']['media_browser_plus_views_handler_area_basket']['field'] = 'media_browser_plus_views_handler_area_basket';
+ $handler->display->display_options['defaults']['fields'] = FALSE;
+ /* Field: Media browser plus: Preview with operations */
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['id'] = 'media_browser_plus_preview_vbo';
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['table'] = 'file_managed';
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['field'] = 'media_browser_plus_preview_vbo';
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['label'] = '';
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['element_label_colon'] = FALSE;
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['vbo_settings']['display_type'] = '0';
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['vbo_settings']['enable_select_all_pages'] = 0;
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['vbo_settings']['force_single'] = 0;
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['vbo_settings']['entity_load_capacity'] = '10';
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['vbo_operations'] = array(
+ 'action::views_bulk_operations_archive_action' => array(
+ 'selected' => 1,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ 'settings' => array(
+ 'scheme' => 'public',
+ 'temporary' => 1,
+ ),
+ ),
+ 'action::views_bulk_operations_delete_item' => array(
+ 'selected' => 1,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ ),
+ 'action::media_browser_plus_download_action' => array(
+ 'selected' => 1,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ ),
+ 'action::views_bulk_operations_script_action' => array(
+ 'selected' => 0,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ ),
+ 'action::views_bulk_operations_modify_action' => array(
+ 'selected' => 0,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ 'settings' => array(
+ 'show_all_tokens' => 1,
+ 'display_values' => array(
+ '_all_' => '_all_',
+ ),
+ ),
+ ),
+ 'action::views_bulk_operations_argument_selector_action' => array(
+ 'selected' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ 'settings' => array(
+ 'url' => '',
+ ),
+ ),
+ 'action::system_send_email_action' => array(
+ 'selected' => 0,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ ),
+ 'action::panelizer_set_status_action' => array(
+ 'selected' => 0,
+ 'postpone_processing' => 0,
+ 'skip_confirmation' => 0,
+ 'override_label' => 0,
+ 'label' => '',
+ ),
+ );
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['mbp_settings']['add_link']['type'] = 'e';
+ $handler->display->display_options['fields']['media_browser_plus_preview_vbo']['mbp_settings']['add_link']['colorbox_settings'] = 'e';
+ $handler->display->display_options['path'] = 'admin/content/file/mbp';
+ $handler->display->display_options['menu']['type'] = 'tab';
+ $handler->display->display_options['menu']['title'] = 'Thumbnails Plus';
+ $handler->display->display_options['menu']['weight'] = '0';
+ $handler->display->display_options['menu']['name'] = 'management';
+ $handler->display->display_options['menu']['context'] = 0;
+ $translatables['media_browser_plus'] = array(
+ t('Master'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('« first'),
+ t('‹ previous'),
+ t('next ›'),
+ t('last »'),
+ t('No files found'),
+ t('Upload date'),
+ t('Use count'),
+ t('File name'),
+ t('Type'),
+ t('Scheme'),
+ t('Media browser'),
+ t('Library (MBP)'),
+ t('My files'),
+ t('My files (MBP)'),
+ t('User who uploaded'),
+ t('Media Browser Plus'),
+ );
+ return $view;
+}
diff --git a/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_actions.inc b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_actions.inc
new file mode 100644
index 000000000..c4f34e0d6
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_actions.inc
@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * @file
+ * Definition of media_browser_plus_views_handler_area_actions.
+ */
+
+/**
+ * MBP area with file actions.
+ *
+ * @ingroup views_area_handlers
+ */
+class media_browser_plus_views_handler_area_actions extends views_handler_area {
+
+ /**
+ * Define the handler options.
+ */
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options['mbp_action_area'] = array(
+ 'contains' => array(
+ 'actions' => array(
+ 'default' => array('view', 'edit', 'delete', 'download'),
+ ),
+ ),
+ );
+ return $options;
+ }
+
+ /**
+ * Options form to configure the enabled actions.
+ */
+ public function options_form(&$form, &$form_state) {
+ $form['mbp_action_area'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Media Browser Plus Settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ '#weight' => -1,
+ );
+
+ $action_options = array(
+ 'edit' => t('Edit'),
+ 'delete' => t('Delete'),
+ // 'view' => t('View'),
+ 'download' => t('Download'),
+ // 'usage' => t('Usage'),
+ );
+ // Check if there's a basket area, if so add basket action.
+ foreach ($this->view->display_handler->handlers as $area => $handlers) {
+ foreach ($handlers as $handler) {
+ if ($handler instanceof media_browser_plus_views_handler_area_basket) {
+ $action_options['basket'] = t('Add to Media basket');
+ break(2);
+ }
+ }
+ }
+
+ $form['mbp_action_area']['actions'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Enabled actions'),
+ '#description' => t('Currently the actions are usable whe JavaScript is enabled'),
+ '#default_value' => $this->options['mbp_action_area']['actions'],
+ '#options' => $action_options,
+ );
+ }
+
+ /**
+ * Returns the action buttons and sets the JS configuration.
+ */
+ public function render($empty = FALSE) {
+ $output = '';
+ $actions = array();
+ foreach ($this->options['mbp_action_area']['actions'] as $key => $action) {
+ if ($key == $action && method_exists($this, 'render_action_' . $action)) {
+ $output .= $this->{'render_action_' . $action}();
+ $actions[$action] = $action;
+ }
+ }
+
+ // Add javascript.
+ drupal_add_js(array(
+ 'mbp' => array(
+ 'views' => array(
+ $this->view->name . ':' . $this->view->current_display => array('actions' => $actions),
+ ),
+ )),
+ 'setting'
+ );
+ drupal_add_library('media_browser_plus', 'media_browser_plus');
+ return $output;
+ }
+
+ /**
+ * Renders the output for the edit action.
+ */
+ public function render_action_edit() {
+ $element = array(
+ '#type' => 'button',
+ '#name' => 'mbp-action-edit',
+ '#value' => t('Edit selected'),
+ '#attributes' => array(
+ 'class' => array('mbp-action', 'mbp-action-edit'),
+ ),
+ );
+ return render($element);
+ }
+
+ /**
+ * Renders the output for the delete action.
+ */
+ public function render_action_delete() {
+ $element = array(
+ '#type' => 'button',
+ '#name' => 'mbp-action-delete',
+ '#value' => t('Delete selected'),
+ '#attributes' => array(
+ 'class' => array('mbp-action', 'mbp-action-delete'),
+ ),
+ );
+ return render($element);
+ }
+
+ /**
+ * Renders the output for the basket action.
+ */
+ public function render_action_basket() {
+ $element = array(
+ '#type' => 'button',
+ '#name' => 'mbp-action-basket',
+ '#value' => t('Add selected to basket'),
+ '#attributes' => array(
+ 'class' => array('mbp-action', 'mbp-action-basket'),
+ ),
+ );
+ return render($element);
+ }
+
+ /**
+ * Renders the output for the delete action.
+ */
+ public function render_action_download() {
+ $element = array(
+ '#type' => 'button',
+ '#name' => 'mbp-action-download',
+ '#value' => t('Download selected'),
+ '#attributes' => array(
+ 'class' => array('mbp-action', 'mbp-action-download'),
+ ),
+ );
+ return render($element);
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_basket.inc b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_basket.inc
new file mode 100644
index 000000000..67c87625b
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_basket.inc
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ * Definition of media_browser_plus_views_handler_area_actions.
+ */
+
+/**
+ * MBP area with file basket.
+ *
+ * @ingroup views_area_handlers
+ */
+class media_browser_plus_views_handler_area_basket extends views_handler_area {
+
+ /**
+ * Has no options, so don't return a form.
+ */
+ public function options_form(&$form, &$form_state) {
+ }
+
+ /**
+ * Returns the markup for the basket area.
+ */
+ public function render($empty = FALSE) {
+ $output['mbp_basket'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Media Basket'),
+ '#collapsed' => FALSE,
+ '#collapsible' => FALSE,
+ '#attributes' => array(
+ 'class' => array('mbp-file-basket'),
+ ),
+ );
+ // Check if there are files in the basket, if so add them to the list.
+ $selected_files = NULL;
+ $fids = array();
+ if (!empty($this->view->exposed_data['mbp_basket_files'])) {
+ $fids = explode(' ', trim($this->view->exposed_data['mbp_basket_files']));
+ // Save the basket into a cookie to make them more persistent.
+ user_cookie_save(array('mbp.basket' => implode(' ', $fids)));
+ }
+ if (empty($fids) && isset($_COOKIE['Drupal_visitor_mbp_basket'])) {
+ $fids = explode(' ', trim($_COOKIE['Drupal_visitor_mbp_basket']));
+ }
+ if (!empty($fids)) {
+ // Sanitize.
+ foreach ($fids as &$fid) {
+ $fid = (int) $fid;
+ }
+ $file_query = new EntityFieldQuery();
+ $files = $file_query
+ ->entityCondition('entity_type', 'file')
+ ->propertyCondition('fid', $fids)
+ ->execute();
+ if (!empty($files['file'])) {
+ $file_entities = file_load_multiple(array_keys($files['file']));
+ foreach ($file_entities as $file) {
+ $preview = media_get_thumbnail_preview($file);
+ $selected_files .= '<li id="media-item-' . $file->fid . '">' . render($preview) . '</li>';
+ }
+ }
+ }
+
+ $output['mbp_basket']['list'] = array(
+ '#type' => 'markup',
+ '#markup' => '<ul class="mbp-file-basket-list clearfix">' . $selected_files . '</ul>',
+ );
+ $output['mbp_basket']['download'] = array(
+ '#type' => 'button',
+ '#value' => t('Download files in Media Basket'),
+ '#name' => 'mbp_basket_files_download',
+ );
+ $output = render($output);
+
+ // Add javascript.
+ drupal_add_js(array(
+ 'mbp' => array(
+ 'views' => array(
+ $this->view->name . ':' . $this->view->current_display => array('media_basket' => TRUE),
+ ),
+ )),
+ 'setting'
+ );
+ drupal_add_library('media_browser_plus', 'media_browser_plus.area_actions');
+
+ // Hide this input from the pager. That way the cookie can do its work.
+ unset($this->view->exposed_raw_input['mbp_basket_files']);
+
+ return $output;
+ }
+
+ /**
+ * Always exposed.
+ */
+ public function can_expose() {
+ return TRUE;
+ }
+
+ /**
+ * Always exposed.
+ */
+ public function is_exposed() {
+ return TRUE;
+ }
+
+ /**
+ * Form element to track the media basket.
+ */
+ public function exposed_form(&$form, &$form_state) {
+ $default_value = NULL;
+ if (isset($_COOKIE['Drupal_visitor_mbp_basket'])) {
+ $fids = explode(' ', trim($_COOKIE['Drupal_visitor_mbp_basket']));
+ // Sanitize.
+ foreach ($fids as &$fid) {
+ $fid = (int) $fid;
+ }
+ $default_value = implode(' ', array_filter($fids));
+ }
+ $form['mbp_basket_files'] = array(
+ '#type' => 'hidden',
+ '#title' => 'Media Basket selected files',
+ '#default_value' => $default_value,
+ '#attributes' => array(
+ 'class' => array('mbp-basket-files'),
+ ),
+ );
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_navigation.inc b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_navigation.inc
new file mode 100644
index 000000000..008794a12
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_area_navigation.inc
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ * Definition of media_browser_plus_views_handler_area_navigation.
+ */
+
+/**
+ * MBP navigation handler.
+ *
+ * @todo Review / refactor to meet the proper views coding style.
+ *
+ * @ingroup views_area_handlers
+ */
+class media_browser_plus_views_handler_area_navigation extends views_handler_area_view {
+
+ /**
+ * Initialize the handler.
+ *
+ * This is kind of hackish, how is this done properly?
+ * @todo Review / refactor to meet the proper views coding style.
+ */
+ public function init(&$view, &$options) {
+ parent::init($view, $options);
+ // @todo Review / refactor to meet the proper views coding style.
+ $this->table = 'field_data_field_folder';
+ }
+
+ /**
+ * Define options.
+ *
+ * @todo Review / refactor to meet the proper views coding style.
+ */
+ public function option_definition() {
+ $options = parent::option_definition();
+ $this->definition['field'] = 'field_folder_tid';
+ $options['table'] = array('default' => 'field_data_field_folder');
+ $options['field'] = array('default' => 'field_folder_tid');
+ return $options;
+ }
+
+ /**
+ * Remove label from options form.
+ */
+ public function options_form(&$form, &$form_state) {
+ $options = parent::options_form($form, $form_state);
+ unset($form['label']);
+ return $options;
+ }
+
+ /**
+ * Add folder filter to the query.
+ */
+ public function query() {
+ if (!empty($this->view->exposed_data['mbp_current_folder'])) {
+ $this->value = (int) $this->view->exposed_data['mbp_current_folder'];
+ user_cookie_save(array('mbp.current_folder' => $this->value));
+ }
+ elseif (empty($_COOKIE['Drupal_visitor_mbp_current_folder']) && (int) $_COOKIE['Drupal_visitor_mbp_current_folder'] > 0) {
+ $this->value = (int) $_COOKIE['Drupal_visitor_mbp_current_folder'];
+ }
+ else {
+ $root_folder = media_browser_plus_get_media_root_folder();
+ $this->value = $root_folder->tid;
+ }
+ $this->ensure_my_table();
+ $this->query->add_where(1, "$this->table_alias.$this->real_field", $this->value, '=');
+ }
+
+ /**
+ * Always exposed.
+ */
+ public function can_expose() {
+ return TRUE;
+ }
+
+ /**
+ * Always expose.
+ */
+ public function is_exposed() {
+ return TRUE;
+ }
+
+ /**
+ * Doesn't output anything because it's hijacked by the MBP.
+ *
+ * @see template_preprocess_media_browser_plus_views_view_media_browser()
+ * @see media_browser_plus_views_handler_area_navigation::render_custom()
+ */
+ public function render($empty = FALSE) {}
+
+ /**
+ * Called to embed the view into the sidebar of the media browser plus style.
+ *
+ * @see template_preprocess_media_browser_plus_views_view_media_browser()
+ * @see media_browser_plus_views_handler_area_navigation::render_custom()
+ */
+ public function render_custom($empty = FALSE) {
+ return parent::render($empty);
+ }
+
+ /**
+ * Form element to track the media basket.
+ */
+ public function exposed_form(&$form, &$form_state) {
+ $root_folder = media_browser_plus_get_media_root_folder();
+ $folders = taxonomy_get_tree($root_folder->vid, $root_folder->tid);
+ $folder_options = array($root_folder->tid => $root_folder->name);
+ foreach ($folders as $folder) {
+ $folder_options[$folder->tid] = str_repeat('-', $folder->depth + 1) . $folder->name;
+ }
+
+ $default_folder = $root_folder->tid;
+ if (!empty($_COOKIE['Drupal_visitor_mbp_current_folder']) && (int) $_COOKIE['Drupal_visitor_mbp_current_folder'] > 0) {
+ $default_folder = (int) $_COOKIE['Drupal_visitor_mbp_current_folder'];
+ }
+
+ $form['mbp_current_folder'] = array(
+ '#type' => 'select',
+ '#title' => t('Folder'),
+ '#default_value' => $default_folder,
+ '#options' => $folder_options,
+ '#attributes' => array(
+ 'class' => array('mbp-selected-folder'),
+ ),
+ );
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_field_preview.inc b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_field_preview.inc
new file mode 100644
index 000000000..2075c4e1e
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_field_preview.inc
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * The media browser plus field handler.
+ */
+
+/**
+ * Field handler for the media browser plus.
+ *
+ * @ingroup views_field_handlers
+ */
+class media_browser_plus_views_handler_field_preview extends views_handler_field {
+
+ /**
+ * Define the handler options.
+ */
+ public function option_definition() {
+ $options = parent::option_definition();
+
+ $options['mbp_settings'] = array(
+ 'contains' => array(
+ 'add_link' => array(
+ 'contains' => array(
+ 'type' => array('default' => 'none'),
+ 'colorbox_settings' => array('default' => TRUE),
+ ),
+ ),
+ ),
+ );
+ return $options;
+ }
+
+ /**
+ * Options form.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['mbp_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Media Browser Plus Settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ '#weight' => -1,
+ );
+
+ $link_options = array(
+ 'none' => t('None'),
+ 'edit' => t('Edit'),
+ 'view' => t('View'),
+ 'delete' => t('Delete'),
+ 'download' => t('Download'),
+ 'usage' => t('Usage'),
+ 'file' => t('Raw-File'),
+ );
+ $form['mbp_settings']['add_link'] = array(
+ '#type' => 'select',
+ '#title' => t('Add link'),
+ '#default_value' => $this->options['mbp_settings']['add_link'],
+ '#options' => $link_options,
+ );
+ }
+
+ /**
+ * Returns the media browser preview item.
+ *
+ * @see media_get_thumbnail_preview()
+ */
+ public function render($values) {
+ $file = file_load($values->fid);
+
+ // Prepare file link.
+ switch ($this->options['mbp_settings']['add_link']) {
+ case 'delete':
+ case 'edit':
+ case 'usage':
+ case 'view':
+ case 'download':
+ $link = file_entity_uri($file) . '/' . $this->options['mbp_settings']['add_link'];
+ break;
+
+ case 'file':
+ $link = file_create_url($file->uri);
+ break;
+
+ default:
+ $link = NULL;
+ }
+
+ $preview = media_get_thumbnail_preview($file);
+ return drupal_render($preview);
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_field_preview_vbo.inc b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_field_preview_vbo.inc
new file mode 100644
index 000000000..8190bf0ef
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_handler_field_preview_vbo.inc
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * The media browser plus field handler.
+ */
+
+/**
+ * Field handler for the media browser plus.
+ *
+ * @ingroup views_field_handlers
+ */
+class media_browser_plus_views_handler_field_preview_vbo extends views_bulk_operations_handler_field_operations {
+
+ /**
+ * Define the handler options.
+ */
+ public function option_definition() {
+ $options = parent::option_definition();
+
+ $options['mbp_settings'] = array(
+ 'contains' => array(
+ 'add_link' => array(
+ 'contains' => array(
+ 'type' => array('default' => 'edit'),
+ 'colorbox_settings' => array('default' => TRUE),
+ ),
+ ),
+ ),
+ );
+ return $options;
+ }
+
+ /**
+ * Options form.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['mbp_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Media Browser Plus Settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ '#weight' => -1,
+ );
+
+ $link_options = array(
+ 'none' => t('None'),
+ 'edit' => t('Edit'),
+ 'view' => t('View'),
+ 'delete' => t('Delete'),
+ 'download' => t('Download'),
+ 'usage' => t('Usage'),
+ 'file' => t('Raw-File'),
+ );
+ $form['mbp_settings']['add_link'] = array(
+ '#type' => 'select',
+ '#title' => t('Add link'),
+ '#default_value' => $this->options['mbp_settings']['add_link'],
+ '#options' => $link_options,
+ );
+ }
+
+ /**
+ * Returns the media browser preview item and the VBO tokens.
+ *
+ * @see media_get_thumbnail_preview()
+ */
+ public function render($values) {
+ $output = parent::render($values);
+ $file = file_load($values->fid);
+
+ // Prepare file link.
+ switch ($this->options['mbp_settings']['add_link']) {
+ case 'delete':
+ case 'edit':
+ case 'usage':
+ case 'view':
+ case 'download':
+ $link = file_entity_uri($file) . '/' . $this->options['mbp_settings']['add_link'];
+ break;
+
+ case 'file':
+ $link = file_create_url($file->uri);
+ break;
+
+ default:
+ $link = NULL;
+ }
+
+ $preview = media_get_thumbnail_preview($file, $link);
+
+ $output .= drupal_render($preview);
+ return $output;
+ }
+}
diff --git a/sites/all/modules/media_browser_plus/views/media_browser_plus_views_plugin_style_media_browser.inc b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_plugin_style_media_browser.inc
new file mode 100644
index 000000000..7d467ccea
--- /dev/null
+++ b/sites/all/modules/media_browser_plus/views/media_browser_plus_views_plugin_style_media_browser.inc
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * The media browser plus style plugin.
+ */
+
+/**
+ * Media Browser Plus Views style plugin.
+ *
+ * Style plugin to render media items as an interactive grid for the media
+ * browser plus.
+ *
+ * @ingroup views_style_plugins
+ */
+class media_browser_plus_views_plugin_style_media_browser extends media_views_plugin_style_media_browser {
+
+ /**
+ * Set default options.
+ */
+ public function option_definition() {
+ $options = parent::option_definition();
+
+ $options['files_draggable'] = array('default' => TRUE);
+ $options['folders_draggable'] = array('default' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Always render this style to ensure the additions are displayed.
+ */
+ public function even_empty() {
+ return TRUE;
+ }
+
+ /**
+ * Provide options.
+ */
+ public function options_form(&$form, &$form_state) {
+ $form['files_draggable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable drag and drop for files'),
+ '#default_value' => $this->options['files_draggable'],
+ );
+ // @todo Add folder management and enable once it's available.
+// $form['folders_draggable'] = array(
+// '#type' => 'checkbox',
+// '#title' => t('Enable drag and drop for folders'),
+// '#default_value' => $this->options['folders_draggable'],
+// );
+ }
+}
diff --git a/sites/all/modules/media_gallery/LICENSE.txt b/sites/all/modules/media_gallery/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/media_gallery/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/media_gallery/README.txt b/sites/all/modules/media_gallery/README.txt
new file mode 100644
index 000000000..5ea65e7a1
--- /dev/null
+++ b/sites/all/modules/media_gallery/README.txt
@@ -0,0 +1,120 @@
+INSTALLATION
+------------
+
+1. Download Media Gallery from http://drupal.org/project/media_gallery
+
+ Always use the latest official release. Unpack it in your contributed
+ modules directory (usually sites/all/modules).
+
+2. Download other necessary projects.
+
+ In addition to Media Gallery itself, you'll need several other modules and
+ libraries on your site in order for your site's gallery functionality to
+ work as designed.
+
+ You should generally download the latest official release of each project.
+ However, this version of Media Gallery has been specifically tested with the
+ versions shown in parentheses below. If you do not use these exact versions,
+ it is likely that this release will not work correctly.
+
+ Required:
+
+ a. Media (latest 2.x dev)
+ - Download from http://drupal.org/project/media and unpack it in your
+ contributed modules directory (usually sites/all/modules).
+ b. Multiform (7.x-1.0)
+ - Download from http://drupal.org/project/multiform and unpack it in your
+ contributed modules directory (usually sites/all/modules).
+ c. ColorBox jQuery plugin (latest release, tested with 1.3.17)
+ - Download from http://colorpowered.com/colorbox and unpack it in
+ sites/all/libraries (if the directory doesn't exist, create it first).
+
+ Recommended for the best experience:
+
+ a. Media Browser Plus (latest release, tested with 7.x-1.0-beta3)
+ - Download from http://drupal.org/project/media_browser_plus and unpack
+ it in your contributed modules directory (usually sites/all/modules).
+ b. Media YouTube (latest release, tested with 7.x-1.0-beta3)
+ - Download from http://drupal.org/project/media_youtube and unpack it in
+ your contributed modules directory (usually sites/all/modules).
+ c. Plupload (latest release, tested with 7.x-1.0)
+ - First, download the module from http://drupal.org/project/plupload and
+ unpack it in your contributed modules directory (usually
+ sites/all/modules).
+ - Next, download the Plupload JavaScript library from
+ https://github.com/moxiecode/plupload/archive/v1.5.8.zip and
+ unpack it in sites/all/libraries (if the directory doesn't exist,
+ create it first). See the Plupload module's README.txt file for
+ up-to-date instructions.
+
+3. Enable the modules.
+
+ a. Visit your site's Administration > Modules page.
+ b. Enable Media Gallery itself to get the basic gallery functionality
+ working. This will automatically enable all required modules.
+ c. Optionally enable Media Browser Plus to get a better experience browsing
+ and adding media to your galleries.
+ d. Optionally enable Media YouTube to allow you to add YouTube videos to
+ your galleries.
+ e. Optionally enable Plupload to allow you to quickly add large numbers of
+ images to your gallery at once, rather than having to upload them one at
+ a time.
+
+GETTING STARTED
+---------------
+
+There are a lot of fun things you can do with this module. Here are some
+suggestions for getting started:
+
+1. Go to Add content, and create a gallery.
+2. The gallery will be empty by default, so use the "Add media" link to quickly
+ upload a few images from your computer. Now you have a completely functional
+ gallery, just like that!
+3. Use the "Add media" link again, and switch to the "Web" tab. Find a YouTube
+ video you like, and paste its URL there. Now it will be in the gallery too.
+ (Note that the "Library" tab allows you to add existing media items from
+ your site - for example, images that were uploaded using other modules - to
+ the gallery as well.)
+4. Back in the gallery, you can use drag-and-drop to rearrange the images and
+ videos to your liking.
+5. Use the "Edit media" tab to quickly edit all your items at once (for
+ example, to give each item in the gallery a description, or to tag it).
+6. Now click on one of the images in your gallery and watch it pop up in a
+ lightbox. Inside the lightbox, you can scroll through the gallery items
+ individually, or watch them play in a slideshow. Note that you can play your
+ video from inside the lightbox also.
+7. Back in the gallery, you can use the "Edit gallery" tab to customize several
+ ways in which the gallery displays, and also make the gallery available as a
+ block (which you can then place on your site by going to Administration >
+ Structure > Blocks).
+8. Your site's main menu should have a link called Galleries which was
+ automatically added when you turned on the module. This page displays a
+ collection of all the galleries you created. You can use drag-and-drop to
+ rearrange the galleries here also.
+9. The "Edit all galleries" tab that's available from here also lets you
+ customize some additional aspects of how the Galleries page is displayed.
+
+There's lots more to explore, so have fun!
+
+CAVEATS
+-------
+
+This module is still in beta, and many of the modules it depends on are in beta
+themselves, or even in alpha.
+
+We therefore don't recommend using it on production sites, unless you're able
+to tolerate bugs, maintain frequent database backups of your site, and keep
+up-to-date on the module's development so that you can quickly apply any
+patches if security issues are discovered. (As per Drupal's official policy at
+http://drupal.org/security-advisory-policy, security issues discovered in a
+module which has an alpha, beta, or other non-stable release will not result in
+an advisory being issued and may be discussed publicly before a fix is
+available.)
+
+If you find bugs, please use the "Issues for Media Gallery" section at
+http://drupal.org/project/media_gallery to search for existing bug reports
+about the same issue, and if there isn't one, file a new bug report. Please
+help us make this module better! In addition to coding and reporting bugs, we
+can use help managing the issue queue as well as writing documentation. If you
+want to help but don't know how, feel free to file a support request in the
+issue queue, and we'll get you started with some suggestions.
diff --git a/sites/all/modules/media_gallery/colorbox-display.js b/sites/all/modules/media_gallery/colorbox-display.js
new file mode 100644
index 000000000..2341ab28c
--- /dev/null
+++ b/sites/all/modules/media_gallery/colorbox-display.js
@@ -0,0 +1,80 @@
+Drupal.behaviors.mediaGalleryColorbox = {};
+
+Drupal.behaviors.mediaGalleryColorbox.attach = function (context, settings) {
+ var $ = jQuery, $galleries, $gallery, href, $links, $link, $dummyLinksPre, $dummyLinksPost, i, j;
+ if ($.fn.colorbox) {
+ // Add a colorbox group for each media gallery field on the page.
+ $galleries = $('.field-name-media-gallery-file');
+ for (i = 0; i < $galleries.length; i++) {
+ $gallery = $($galleries[i]);
+ $links = $('a.cbEnabled', $gallery);
+ $dummyLinksPre = $gallery.parent().find('ul:has(a.colorbox-supplemental-link.pre)');
+ $dummyLinksPost = $gallery.parent().find('ul:has(a.colorbox-supplemental-link.post)');
+ $dummyLinksPost.appendTo($gallery);
+ $links = $links.add('a', $dummyLinksPre).add('a', $dummyLinksPost);
+ $links.attr('rel', 'colorbox-' + i);
+ for (j = 0; j < $links.length; j++) {
+ // Change the link href to point to the lightbox version of the media.
+ $link = $($links[j]);
+ href = $link.attr('href');
+ $link.attr('href', href.replace(/\/detail\/([0-9]+)\/([0-9]+)/, '/lightbox/$1/$2'));
+ }
+ $links.not('.meta-wrapper').colorbox({
+ slideshow: true,
+ slideshowAuto: false,
+ slideshowStart: Drupal.t('Slideshow'),
+ slideshowStop: '[' + Drupal.t('stop slideshow') + ']',
+ slideshowSpeed: 4000,
+ current: Drupal.t('Item !current of !total', {'!current':'{current}', '!total':'{total}'}),
+ innerWidth: 'auto',
+ // If 'title' evaluates to false, Colorbox will use the title from the
+ // underlying <a> element, which we don't want. Using a space is the
+ // officially approved workaround. See
+ // http://groups.google.com/group/colorbox/msg/7671ae69708950bf
+ title: ' ',
+ transition: 'fade',
+ preloading: true,
+ fastIframe: false,
+ onComplete: function () {
+ $(this).colorbox.resize();
+ }
+ });
+ }
+ $('a.meta-wrapper').bind('click', Drupal.mediaGalleryColorbox.metaClick);
+ // Subscribe to the media_youtube module's load event, so we can pause
+ // the slideshow and play the video.
+ $(window).bind('media_youtube_load', Drupal.mediaGalleryColorbox.handleMediaYoutubeLoad);
+ }
+};
+
+Drupal.mediaGalleryColorbox = {};
+
+/**
+ * Handles the click event on the metadata.
+ */
+Drupal.mediaGalleryColorbox.metaClick = function (event) {
+ event.preventDefault();
+ jQuery(this).prev('.media-gallery-item').find('a.cbEnabled').click();
+};
+
+/**
+ * Handles the media_youtube module's load event.
+ *
+ * If the colorbox slideshow is playing, and it gets to a video, the video
+ * should play automatically, and the slideshow should pause until it's done.
+ */
+Drupal.mediaGalleryColorbox.handleMediaYoutubeLoad = function (event, videoSettings) {
+ var $ = jQuery;
+ var slideshowOn = $('#colorbox').hasClass('cboxSlideshow_on');
+ // Set a width on a wrapper for the video so that the colorbox will size properly.
+ $('#colorbox .media-gallery-item').width(videoSettings.width + 'px').height(videoSettings.height + 'px');
+ if (slideshowOn) {
+ videoSettings.options.autoplay = 1;
+ $('#cboxSlideshow')
+ // Turn off the slideshow while the video is playing.
+ .click() // No, there is not a better way.
+ .text('[' + Drupal.t('resume slideshow') + ']');
+ // TODO: If YouTube makes its JavaScript API available for iframe videos,
+ // set the slideshow to restart when the video is done playing.
+ }
+};
diff --git a/sites/all/modules/media_gallery/css/media_gallery.edit.css b/sites/all/modules/media_gallery/css/media_gallery.edit.css
new file mode 100644
index 000000000..cb42fb1df
--- /dev/null
+++ b/sites/all/modules/media_gallery/css/media_gallery.edit.css
@@ -0,0 +1,14 @@
+/**
+ * Styles for the multiform media edit screen
+ */
+
+ .media-item-form .form-wrapper {
+ zoom: 1; /* Needed for the IE filters */
+}
+
+.disabled {
+ opacity: 0.5;
+ filter:progid:DXImageTransform.Microsoft.Alpha(opacity=50);
+ -ms-filter:progid:DXImageTransform.Microsoft.Alpha(opacity=50);
+}
+
diff --git a/sites/all/modules/media_gallery/ellipsis.xml b/sites/all/modules/media_gallery/ellipsis.xml
new file mode 100644
index 000000000..3369aea43
--- /dev/null
+++ b/sites/all/modules/media_gallery/ellipsis.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<bindings
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+>
+ <binding id="ellipsis">
+ <content>
+ <xul:description crop="end" xbl:inherits="value=xbl:text"><children/></xul:description>
+ </content>
+ </binding>
+</bindings> \ No newline at end of file
diff --git a/sites/all/modules/media_gallery/fields_rsi_prevention.inc b/sites/all/modules/media_gallery/fields_rsi_prevention.inc
new file mode 100644
index 000000000..374b35899
--- /dev/null
+++ b/sites/all/modules/media_gallery/fields_rsi_prevention.inc
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * This file provides easier access on entity properties and methods.
+ */
+
+/**
+ * Decorates an entity to provide getters/setters.
+ *
+ * @example
+ *
+ * $node = new FieldRSIPreventor($node);
+ *
+ * // This still works,
+ * $node->created
+ *
+ * // Gets the first value of body for LANGUAGE_NONE.
+ * $node->getValue('body');
+ *
+ * // Gets the 2nd value of body in spanish
+ * $node->getValue('body', 2, 'esp');
+ */
+class FieldsRSIPreventor {
+ private $entity;
+
+ function __construct($entity) {
+ // Prevent this thing from chaining if people accidentally use it twice.
+ if ($entity instanceof FieldRSIPreventor) {
+ $entity = $entity->entity;
+ }
+ $this->entity = $entity;
+ }
+
+ function getValue($field_name, $delta = 0, $language = LANGUAGE_NONE) {
+ if ($item = $this->getItem($field_name, $delta, $language)) {
+ return $item['value'];
+ }
+ }
+
+ function getItem($field_name, $delta = 0, $language = LANGUAGE_NONE) {
+ if (!isset($this->entity->{$field_name}[$language]) || !isset($this->entity->{$field_name}[$language][$delta])) {
+ return FALSE;
+ }
+ return $this->entity->{$field_name}[$language][$delta];
+ }
+
+ function __get($key) {
+ return $this->entity->{$key};
+ }
+
+ function __set($key, $value) {
+ $this->entity->{$key} = $value;
+ }
+}
diff --git a/sites/all/modules/media_gallery/images/creative-commons-sprite.png b/sites/all/modules/media_gallery/images/creative-commons-sprite.png
new file mode 100644
index 000000000..ee210dfe3
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/creative-commons-sprite.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/images/draggable.png b/sites/all/modules/media_gallery/images/draggable.png
new file mode 100644
index 000000000..93d20d60d
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/draggable.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/images/empty_gallery.png b/sites/all/modules/media_gallery/images/empty_gallery.png
new file mode 100644
index 000000000..4997f9528
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/empty_gallery.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/images/gallery-icon-sprite.png b/sites/all/modules/media_gallery/images/gallery-icon-sprite.png
new file mode 100644
index 000000000..2e894a77d
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/gallery-icon-sprite.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/images/hover-bubble-middle.png b/sites/all/modules/media_gallery/images/hover-bubble-middle.png
new file mode 100644
index 000000000..541a4744c
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/hover-bubble-middle.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/images/hover-bubble.png b/sites/all/modules/media_gallery/images/hover-bubble.png
new file mode 100644
index 000000000..040d94363
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/hover-bubble.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/images/hover-dark-bg.png b/sites/all/modules/media_gallery/images/hover-dark-bg.png
new file mode 100644
index 000000000..1d90595d3
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/hover-dark-bg.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/images/prev_next.png b/sites/all/modules/media_gallery/images/prev_next.png
new file mode 100644
index 000000000..6512ebda1
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/prev_next.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/images/stack_bg.png b/sites/all/modules/media_gallery/images/stack_bg.png
new file mode 100644
index 000000000..6d3c6ad74
--- /dev/null
+++ b/sites/all/modules/media_gallery/images/stack_bg.png
Binary files differ
diff --git a/sites/all/modules/media_gallery/js/media_gallery.edit.js b/sites/all/modules/media_gallery/js/media_gallery.edit.js
new file mode 100644
index 000000000..6020df83e
--- /dev/null
+++ b/sites/all/modules/media_gallery/js/media_gallery.edit.js
@@ -0,0 +1,25 @@
+(function ($) {
+
+ Drupal.behaviors.mediaGalleryEdit = Drupal.behaviors.mediaGalleryEdit || {};
+
+ Drupal.behaviors.mediaGalleryEdit.attach = function (context, settings) {
+ // Get the set of remove checkboxes
+ $('.form-type-checkbox[class *= "remove"]').bind('change', function (event) {
+ // Get the value of the checkbox
+ var isChecked = event.target.checked;
+ // Get the containing media item
+ var mediaItem = $(this).closest('.media-edit-form');
+ // Get the inputs and wrapping form items in the media item
+ var mediaItemFields = mediaItem.find('.sidebar').nextAll().not('.form-actions');
+ var inputs = mediaItemFields.find(':input');
+ // If the checkbox is checked, disabled the form elements in the media item;
+ if (isChecked) {
+ mediaItemFields.addClass('disabled');
+ }
+ // else remove the disabled attribute and styling.
+ else {
+ mediaItemFields.removeClass('disabled');
+ }
+ });
+ };
+})(jQuery); \ No newline at end of file
diff --git a/sites/all/modules/media_gallery/media-gallery-media-item-thumbnail.tpl.php b/sites/all/modules/media_gallery/media-gallery-media-item-thumbnail.tpl.php
new file mode 100644
index 000000000..70ddee62a
--- /dev/null
+++ b/sites/all/modules/media_gallery/media-gallery-media-item-thumbnail.tpl.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Template file for displaying a media item (entity) as a thumbnail on the
+ * gallery page.
+ *
+ * The template-specific available variables are:
+ *
+ * - $media_gallery_item: The rendered media gallery item.
+ * - $media_gallery_meta: The rendered metadata about the item.
+ *
+ * @see template_preprocess_media_gallery_media_item_thumbnail()
+ */
+?>
+<div class="<?php print $classes; ?>"<?php print $attributes; ?>>
+ <?php print render($title_prefix); ?>
+ <?php if (!empty($title)): ?>
+ <h3<?php print $title_attributes; ?>><?php print $title ?></h3>
+ <?php endif; ?>
+ <?php print render($title_suffix); ?>
+
+ <?php print $media_gallery_item; ?>
+
+ <?php print $media_gallery_meta; ?>
+</div>
diff --git a/sites/all/modules/media_gallery/media.pages.inc b/sites/all/modules/media_gallery/media.pages.inc
new file mode 100644
index 000000000..940714756
--- /dev/null
+++ b/sites/all/modules/media_gallery/media.pages.inc
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * This file provieds media specific functions.
+ */
+
+
+/**
+ * @todo Move this code to the Media module's media.pages.inc file when it is
+ * ready to be committed to CVS.
+ */
+
+/**
+ * Menu callback; prompt the browser to download the file.
+ *
+ * This function is very similar to file_download(). Its primary difference is
+ * to force the Content-Disposition header to 'attachment', even for images and
+ * other file types that could normally be displayed by the browser. This is
+ * useful for "Download" links.
+ *
+ * By invoking hook_file_download() and checking access as file_download() does,
+ * this function is compatible with public and private files as well as other
+ * custom URI schemes that may be in use.
+ *
+ * @see file_download()
+ */
+function media_download($file) {
+ $uri = $file->uri;
+ $scheme = file_uri_scheme($uri);
+ if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
+ // Let other modules provide headers and controls access to the file.
+ // module_invoke_all() uses array_merge_recursive() which merges header
+ // values into a new array. To avoid that and allow modules to override
+ // headers instead, use array_merge() to merge the returned arrays.
+ $headers = array();
+ foreach (module_implements('file_download') as $module) {
+ $function = $module . '_file_download';
+ $result = $function($uri);
+ if ($result == -1) {
+ return drupal_access_denied();
+ }
+ if (isset($result) && is_array($result)) {
+ $headers = array_merge($headers, $result);
+ }
+ }
+ // Default Content-Type and Content-Length headers, in case no other
+ // hook_file_download() implementation set them.
+ $name = mime_header_encode($file->filename);
+ $type = mime_header_encode($file->filemime);
+ $headers += array(
+ 'Content-Type' => $type . '; name="' . $name . '"',
+ 'Content-Length' => $file->filesize,
+ );
+ // Force the Content-Disposition header, regardless of what other
+ // hook_file_download() implementations normally set.
+ $headers['Content-Disposition'] = 'attachment; filename="' . $name . '"';
+ file_transfer($uri, $headers);
+ }
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.addimage.js b/sites/all/modules/media_gallery/media_gallery.addimage.js
new file mode 100644
index 000000000..2341b6e7d
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.addimage.js
@@ -0,0 +1,49 @@
+(function ($) {
+
+Drupal.behaviors.media_gallery = {};
+Drupal.behaviors.media_gallery.attach = function (context, settings) {
+ // Bind a click handler to the 'add media' link.
+ $('a.media-gallery-add.launcher').once('media-gallery-add').bind('click', Drupal.media_gallery.open_browser);
+};
+
+Drupal.media_gallery = {};
+
+Drupal.media_gallery.open_browser = function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var pluginOptions = { 'id': 'media_gallery', 'multiselect' : true , 'types': Drupal.settings.mediaGalleryAllowedMediaTypes};
+ Drupal.media.popups.mediaBrowser(Drupal.media_gallery.add_media, pluginOptions);
+};
+
+Drupal.media_gallery.add_media = function (mediaFiles) {
+ // TODO AN-17966: Add the images to the node's media multifield on the client
+ // side instead of reloading the page.
+ var mediaAdded = function (returnedData, textStatus, XMLHttpRequest) {
+ parent.window.location.reload();
+ };
+
+ var errorCallback = function () {
+ //console.warn('Error: Media not added.');
+ };
+
+ var src = Drupal.settings.mediaGalleryAddImagesUrl;
+
+ var media = [];
+
+ for(var i = 0; i < mediaFiles.length; i++) {
+ media[i] = mediaFiles[i].fid;
+ }
+
+ $.ajax({
+ url: src,
+ type: 'POST',
+ dataType: 'json',
+ data: {files: media},
+ error: errorCallback,
+ success: mediaAdded
+ });
+
+ return false;
+}
+
+})(jQuery);
diff --git a/sites/all/modules/media_gallery/media_gallery.admin.inc b/sites/all/modules/media_gallery/media_gallery.admin.inc
new file mode 100644
index 000000000..39740593c
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.admin.inc
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * This file contains the admin functions for the Media Gallery module.
+ */
+
+/**
+ * The Galleries settings page will just return the term edit form for the "all galleries" tid.
+ *
+ */
+function media_gallery_admin_settings() {
+ $edit = media_gallery_get_default_gallery_collection();
+ $form_state['build_info']['args'] = array($edit);
+ form_load_include($form_state, 'inc', 'taxonomy', 'taxonomy.admin');
+ return drupal_build_form('taxonomy_form_term', $form_state);
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.css b/sites/all/modules/media_gallery/media_gallery.css
new file mode 100644
index 000000000..c318950b6
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.css
@@ -0,0 +1,622 @@
+
+/* @group Node edit form styles */
+
+.settings-group {
+ clear: both;
+ margin-top: 1.5em;
+}
+
+.settings-group > img {
+ float: left;
+ margin-bottom: 2em;
+ margin-right: 2em;
+}
+
+.settings-group .group-label {
+ font-weight: bold;
+ margin-bottom: 0.75em;
+}
+
+.settings-group .form-item {
+ padding: 0;
+}
+
+.settings-group .form-item label {
+ font-weight: normal;
+}
+
+.settings-group .form-item label.option {
+ font-size: 1em;
+ vertical-align: middle;
+}
+
+.settings-group .form-item div.description {
+ font-size: 0.87em;
+ margin-top: 0;
+}
+
+.settings-group .form-inline {
+ float: left;
+}
+
+.settings-group .form-inline label {
+ display: none;
+}
+
+.settings-group .form-inline select {
+ margin: 2em 1em 0 0;
+}
+
+.settings-group .form-inline.label label {
+ display: block;
+ position: absolute;
+}
+
+.settings-group .form-select.enhanced {
+ margin-right: 1em;
+}
+
+.no-overflow {
+ overflow: hidden;
+}
+
+.field-name-media-gallery-format .form-type-radio label.option,
+.field-name-media-gallery-lightbox-extras .form-type-checkbox label.option {
+ font-size: 0.94em;
+ line-height: 1.2em;
+}
+
+.field-name-media-gallery-format .form-type-radio label.option .description {
+ color: #666666;
+ display: block;
+ font-size: 0.86em;
+}
+
+.no-label label,
+.no-group-label > .form-item > label {
+ display: none;
+}
+
+.setting-icon {
+ background: url('images/gallery-icon-sprite.png') no-repeat left top transparent;
+ display: block;
+ float: left;
+ height: 44px;
+ margin: 0 0.5em;
+ width: 42px;
+}
+
+.settings-group .setting-icon {
+ height: 109px;
+ width: 104px;
+}
+
+.gallery-settings .setting-icon {
+ background-position: -442px -57px;
+}
+
+.presentation-settings .setting-icon {
+ background-position: 0 -57px;
+}
+
+.block-settings .setting-icon {
+ background-position: -331px -57px;
+}
+
+.galleries-settings .setting-icon {
+ background-position: -552px -57px;
+}
+
+/* @end */
+
+/* @group Galleries form styles */
+
+.form-media-gallery-collection .field-name-field-license {
+ clear: both;
+}
+
+#edit-media-gallery-lightbox-extras {
+ margin-left: 2em;
+ margin-top: -1em;
+}
+
+.presentation-settings .setting-icon.display-page {
+ background-position: -221px -57px;
+}
+
+.presentation-settings .setting-icon.display-lightbox {
+ background-position: 0 -57px;
+}
+
+.presentation-settings .setting-icon.display-extras {
+ background-position: -110px -57px;
+}
+
+/* @end */
+
+/* @group Gallery thumbnail styles */
+
+.media-gallery-collection a.media-gallery-thumb,
+.media-gallery-media a.media-gallery-thumb,
+.media-gallery-thumb img {
+ display: block;
+ overflow: hidden;
+ position: relative;
+}
+
+.media-gallery-media > .field-items > .field-item {
+ float: left;
+ position: relative;
+}
+
+.media-gallery-media .media-gallery-item-wrapper {
+ margin: 0.5em;
+ position: relative;
+}
+
+.media-gallery-media .media-gallery-item {
+ background: #FFFFFF;
+ border: 1px solid #666666;
+ padding: 2%;
+}
+
+.meta-wrapper,
+.meta-wrapper:link,
+.meta-wrapper:hover,
+.meta-wrapper:visited,
+.meta-wrapper:active {
+ font-size: 0.8462em;
+ line-height: 1.5em;
+ min-height: 3em;
+ vertical-align: top;
+}
+
+.meta-wrapper .media-title * .field-item,
+.meta-wrapper .media-title {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 100%;
+ -moz-binding: url('ellipsis.xml#ellipsis');
+}
+
+.meta-wrapper {
+ display: block;
+ padding: 0 9%;
+}
+
+.meta-wrapper.hover {
+ line-height: 1em;
+ padding: 0;
+}
+
+.meta-wrapper .media-description span {
+ white-space: nowrap;
+}
+
+.media-license {
+ display: block;
+ float: right;
+ font-size: 0;
+ line-height: 14px;
+}
+
+.media-gallery-media .media-license {
+ float: left;
+ margin: 4px 0 0;
+}
+
+.media-license span {
+ background: url('images/creative-commons-sprite.png') no-repeat left top transparent;
+ display: inline-block;
+ font-size: 1em;
+ height: 0;
+ padding: 7px;
+ width: 0;
+}
+
+.media-license span + span {
+ margin-left: 3px;
+}
+
+.media-license.dark .copyright {
+ background-position: -95px -1px;
+}
+
+.media-license.dark .attribution {
+ background-position: -2px -1px;
+}
+
+.media-license.dark .non-commercial {
+ background-position: -25px -1px;
+}
+
+.media-license.dark .share-alike {
+ background-position: -49px -1px;
+}
+
+.media-license.dark .no-deriv {
+ background-position: -72px -1px;
+}
+
+.media-license.light .copyright {
+ background-position: -95px -51px;
+}
+
+.media-license.light .attribution {
+ background-position: -2px -51px;
+}
+
+.media-license.light .non-commercial {
+ background-position: -25px -51px;
+}
+
+.media-license.light .share-alike {
+ background-position: -49px -51px;
+}
+
+.media-license.light .no-deriv {
+ background-position: -72px -51px;
+}
+
+.media-license.medium .copyright {
+ background-position: -95px -26px;
+}
+
+.media-license.medium .attribution {
+ background-position: -2px -26px;
+}
+
+.media-license.medium .non-commercial {
+ background-position: -25px -26px;
+}
+
+.media-license.medium .share-alike {
+ background-position: -49px -26px;
+}
+
+.media-license.medium .no-deriv {
+ background-position: -72px -26px;
+}
+
+.meta-wrapper.hover {
+ color: #666;
+ display: none;
+ left: -2px;
+ line-height: 1.25em;
+ margin: -7px 0 0;
+ min-height: 0;
+ min-width: 100%;
+ position: absolute;
+ *right: -2px;
+ top: 100%;
+ z-index: 1;
+}
+
+.meta-wrapper.hover .slider {
+ background: url('images/hover-bubble.png') no-repeat left top transparent;
+ display: block;
+}
+
+.meta-wrapper.hover .slider.top {
+ padding-left: 53px;
+}
+
+.meta-wrapper.hover .slider.top-inner {
+ background-position: right -48px;
+ height: 14px;
+}
+
+.meta-wrapper.hover .slider.meta-outer {
+ background: url('images/hover-bubble-middle.png') repeat-y left top transparent;
+ padding-left: 0.5em;
+}
+
+.meta-wrapper.hover .slider.meta-inner {
+ background: url('images/hover-bubble-middle.png') repeat-y right top transparent;
+ padding-right: 0.5em;
+}
+
+.meta-wrapper.hover .slider.bottom {
+ background-position: left -42px;
+ padding-left: 53px;
+}
+
+.meta-wrapper.hover .slider.bottom-inner {
+ background-position: right bottom;
+ height: 5px;
+}
+
+.meta-wrapper .media-title {
+ display: block;
+ font-size: 12px;
+ font-weight: bold;
+ height: 1.5em;
+ line-height: 1.5em;
+ white-space: nowrap;
+}
+
+.meta-wrapper.hover .media-title {
+ color: #000;
+}
+
+.meta-wrapper.hover .media-title a,
+.meta-wrapper.hover .media-title a:hover {
+ color: #000;
+}
+
+.media-gallery-item-wrapper:hover .meta-wrapper.hover,
+.media-collection-item-wrapper:hover + .meta-wrapper.hover,
+.meta-wrapper.hover:hover {
+ display: block;
+}
+
+.meta-inner span {
+ display: block;
+}
+
+.media-description span {
+ display: inline;
+ line-height: 1em;
+}
+
+/* @end */
+
+/* @group Gridding styles */
+
+.media-gallery-media {
+
+}
+
+.mg-col {
+ display: inline-block;
+ margin: 0 -0.5em 3em;
+ width: 100%;
+}
+
+.mg-col > .field-items {
+ width: 100%;
+}
+
+a.media-gallery-thumb img,
+.media-gallery-detail img {
+ height: auto;
+ overflow: auto;
+ width: 100%;
+}
+
+/**
+ * The following widths are inflected with !important to
+ * prevent users in the ThemeBuilder from destroying the
+ * presentation of their galleries if they alter the width
+ * of items with the .field class.
+ */
+.mg-col-1 > .field-items > .field-item,
+.mg-col-1 .mg-gallery,
+.media-gallery-detail .field-items > .field-item {
+ width: 100% !important;
+}
+
+.mg-col-2 > .field-items > .field-item,
+.mg-col-2 .mg-gallery {
+ width: 50% !important;
+}
+
+.mg-col-3 > .field-items > .field-item,
+.mg-col-3 .mg-gallery {
+ width: 33.3% !important;
+}
+
+.mg-col-4 > .field-items > .field-item,
+.mg-col-4 .mg-gallery {
+ width: 24.96% !important;
+}
+
+.mg-col-5 > .field-items > .field-item,
+.mg-col-5 .mg-gallery {
+ width: 20% !important;
+}
+
+.mg-col-6 > .field-items > .field-item,
+.mg-col-6 .mg-gallery {
+ width: 16.6666666665% !important;
+}
+
+.mg-col-7 > .field-items > .field-item,
+.mg-col-7 .mg-gallery {
+ width: 14.2857142857% !important;
+}
+
+.mg-col-8 > .field-items > .field-item,
+.mg-col-8 .mg-gallery {
+ width: 12.5% !important;
+}
+
+.mg-col-9 > .field-items > .field-item,
+.mg-col-9 .mg-gallery {
+ width: 11.1111111111% !important;
+}
+
+.mg-col-10 > .field-items > .field-item,
+.mg-col-10 .mg-gallery {
+ width: 10% !important;
+}
+
+.mg-col .mg-gallery.mg-teaser {
+ float: left;
+}
+
+.mg-col .mg-gallery.mg-teaser,
+.mg-col .mg-gallery.mg-teaser .content {
+ margin: 0;
+ outline: none;
+ position: relative;
+}
+
+.mg-col .mg-gallery.mg-teaser h2 {
+ display: none;
+}
+
+.mg-col .mg-gallery.mg-teaser .float-overflow {
+ margin: 0.5em;
+ overflow: visible;
+ position: relative;
+}
+
+/* @end */
+
+/* @group Detail page styles */
+
+.media-gallery-detail-wrapper {
+ margin-top: 1em;
+}
+
+.media-gallery-detail {
+ float: left;
+ margin-right: 1em;
+ line-height: normal;
+ max-width: 70%;
+}
+
+.media-gallery-detail-wrapper .field-name-media-description .field-item {
+ word-wrap: break-word;
+}
+
+.media-gallery-detail-info {
+ clear: both;
+ display: inline-block;
+ margin-top: 0.5em;
+ position: relative;
+ overflow: hidden;
+ width: 100%;
+}
+
+.media-gallery-detail-info .media-license {
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.media-gallery-detail-info + .media-gallery-detail-info {
+ margin-top: 1em;
+}
+
+.media-gallery-detail-image-info-wrapper {
+ clear: right;
+ float: right;
+}
+
+.media-gallery-image-count {
+ padding-right: 1em;
+}
+
+/* @end */
+
+/* @group Lightbox styles */
+
+#cboxSlideshow {
+ right: 40px;
+}
+
+.mg-lightbox-wrapper {
+ color: #666666;
+}
+
+.mg-lightbox-wrapper a,
+.lightbox-stack a {
+ color: #0074BD;
+}
+
+.mg-lightbox-wrapper a:link,
+.mg-lightbox-wrapper a:visited,
+.mg-lightbox-wrapper a:active,
+.mg-lightbox-wrapper a:hover,
+.lightbox-stack a:link,
+.lightbox-stack a:visited,
+.lightbox-stack a:active,
+.lightbox-stack a:hover{
+ color: #0074BD;
+}
+
+.mg-lightbox-wrapper a:hover,
+.lightbox-stack a:hover {
+ text-decoration: underline;
+}
+
+.mg-lightbox-wrapper .lightbox-title {
+ font-size: 1.8em;
+ font-weight: bold;
+ padding-bottom: 0.2em;
+ padding-top: 0.2em;
+}
+
+.mg-lightbox-detail {
+ float: left;
+ padding-right: 320px;
+}
+
+.mg-lightbox-description {
+ width: 300px;
+ margin-left: -300px;
+ float: left;
+}
+
+.lightbox-stack {
+ min-width: 250px;
+}
+
+/* @end */
+
+/* @group Gallery of Galleries styles */
+
+.media-collection-item-wrapper {
+ padding: 9%;
+ position: relative;
+}
+
+.stack-image {
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ z-index: 0;
+}
+
+.media-collection-item-wrapper .media-gallery-item {
+ border: none;
+ position: relative;
+}
+
+.mg-gallery img.stack-image,
+.mg-gallery .media-gallery-thumb img {
+ border: 0;
+ padding: 0;
+}
+
+/* @end */
+
+/* @group Gallery node styles */
+
+.colorbox-supplemental-links {
+ display: none;
+}
+
+/* @end */
+
+/* @group Overrides */
+
+a.contextual-links-trigger {
+ background-position: 2px -18px;
+}
+
+a.contextual-links-trigger:hover,
+div.contextual-links-active a.contextual-links-trigger {
+ background-position: 2px 0;
+}
+
+/* remove extra padding around the iframe */
+.ui-dialog .media-modal-frame {
+ padding: 0 !important;
+}
+
+/* @end */
diff --git a/sites/all/modules/media_gallery/media_gallery.dragdrop.css b/sites/all/modules/media_gallery/media_gallery.dragdrop.css
new file mode 100644
index 000000000..66900ee28
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.dragdrop.css
@@ -0,0 +1,52 @@
+
+.media-gallery-media .media-gallery-item-wrapper {
+ outline: none;
+}
+
+.draggable-wrapper {
+ cursor: move;
+}
+
+.media-gallery-item-wrapper.draggable:hover .media-gallery-item,
+.mg-col .mg-gallery.mg-teaser .float-overflow:hover .stack-image {
+ outline: 1px dashed #98C4DF;
+}
+
+/* Needs to be :hover */
+.draggable:hover .draggable-wrapper {
+ display: block;
+}
+
+.draggable-wrapper {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ background-color: #98C4DF;
+ height: 25px;
+ z-index: 2;
+}
+
+.draggable-wrapper .draggable-handle {
+ background: transparent url('images/draggable.png') no-repeat 0px -20px;
+ top: 5px;
+ left: 5px;
+ position: absolute;
+ height: 18px;
+ text-indent: 110%; /* LTR */
+ width: 18px;
+ outline: none;
+ overflow: hidden;
+}
+
+.media-gallery-sortable-processed.ui-sortable .ui-sortable-helper .meta-wrapper,
+.media-gallery-sortable-processed.ui-sortable .ui-sortable-helper .meta-wrapper.hover {
+ display: none;
+}
+
+/* Don't gray out images while drag-and-drop is disabled; webkit doesn't seem
+ to handle the opacity change correctly. */
+.field-name-media-gallery-file .media-gallery-sortable-processed.ui-state-disabled {
+ opacity: inherit;
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.dragdrop.js b/sites/all/modules/media_gallery/media_gallery.dragdrop.js
new file mode 100644
index 000000000..8af15cddf
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.dragdrop.js
@@ -0,0 +1,139 @@
+Drupal.mediaGallerySort = {};
+Drupal.behaviors.mediaGallerySort = {};
+
+Drupal.behaviors.mediaGallerySort.attach = function (context, settings) {
+ var $ = jQuery;
+ // Create a drag-and-drop editor for gallery collections.
+ var $collection = $('.media-gallery-collection', context).once('media-gallery-sortable');
+ $('.media-collection-item-wrapper', $collection).once('media-gallery-draggable', Drupal.mediaGallerySort.addDraggableIcon);
+ if ($collection.length && settings.mediaGallerySortCollectionUrl) {
+ $collection.sortable();
+ //Drupal.mediaGallerySort.setHeight($collection);
+ var callback = settings.mediaGallerySortCollectionUrl;
+ $collection.bind('sortupdate', {'callback': callback}, Drupal.mediaGallerySort.handle_update);
+ $collection.bind('sortstart', Drupal.mediaGallerySort.setHeight);
+ $collection.bind('sortstop', Drupal.mediaGallerySort.setHeight);
+ }
+ // Create a drag-and-drop editor for individual gallery grid pages.
+ var $gallery = $('.media-gallery-view-full.media-gallery-media > .field-items').once('media-gallery-sortable');
+ $('.media-gallery-item-wrapper', $gallery).once('media-gallery-draggable', Drupal.mediaGallerySort.addDraggableIcon);
+ if ($gallery.length && settings.mediaGallerySortGalleryUrl) {
+ $gallery.sortable();
+ //Drupal.mediaGallerySort.setHeight($gallery);
+ callback = settings.mediaGallerySortGalleryUrl;
+ $gallery.bind('sortupdate', {'callback': callback, 'reorder': 'true'}, Drupal.mediaGallerySort.handle_update);
+ $gallery.bind('sortstart', Drupal.mediaGallerySort.setHeight);
+ $gallery.bind('sortstop', Drupal.mediaGallerySort.setHeight);
+ }
+};
+
+/**
+ * Set an equal explicit, rounded height for all the items in the gallery to
+ * avoid float breaking.
+ */
+Drupal.mediaGallerySort.setHeight = function (event) {
+ var $ = jQuery;
+ var placeholder = $(this).children('.ui-sortable-placeholder');
+ if (placeholder.length) {
+ var height = placeholder.height();
+ $(this).children().height(height);
+ } else {
+ $(this).children().attr('style', '');
+ }
+};
+
+/**
+ * Adds the draggable icon to to each image.
+ */
+Drupal.mediaGallerySort.addDraggableIcon = function () {
+ var $ = jQuery;
+ $(this).addClass('draggable');
+ $(this).prepend($('<div class="draggable-wrapper"><a class="draggable-handle">Drag</a></div>'));
+}
+
+/**
+ * Event handler for the 'sortupdate' event. Sends the new order to the server.
+ *
+ * TODO: Refactor inline closures using jQuery.proxy or, if that's not enough,
+ * an equivalent to ThemeBuilder.bind().
+ */
+Drupal.mediaGallerySort.handle_update = function (event, ui) {
+ var $ = jQuery;
+ var sortable = $(this);
+ var reorder = event.data.reorder;
+ var post = {
+ order: sortable.sortable('toArray'),
+ page: $.deparam.querystring().page
+ };
+
+ /**
+ * Change ID attributes of sorted items to reflect the new order.
+ *
+ * Individual media items in the media_gallery_file field don't have a primary
+ * key, so we're sorting by their delta. When their delta changes on the server
+ * side, we need to reflect it on the client side as well.
+ */
+ var success_callback = function (data) {
+ if (reorder) {
+ var $ = jQuery;
+ var i, newId, $item;
+ var $toReorder = $('.media-gallery-view-full [id^=' + data.idPrefix + ']').addClass('media-gallery-to-reorder');
+ if ($toReorder.length !== data.order.length) {
+ // We seem to have the wrong set of objects to reorder, so just
+ // refresh the page; the server has the right order already.
+ window.location.reload();
+ return;
+ }
+ var newOrder = Drupal.mediaGallerySort._getNewOrder(data.order, data.idPrefix);
+ // Replacing IDs is tricky; we don't want to end up with two elements
+ // having the same ID. For a first pass, replace all elements in order
+ // with their new ID plus a placeholder.
+ for (i = 0; i < newOrder.length; i++) {
+ newId = data.idPrefix + newOrder[i] + '---new';
+ $toReorder.first().attr('id', newId);
+ $toReorder = $toReorder.not('#' + newId);
+ }
+ // Now remove the placeholder from each item's ID.
+ $toReorder = $('.media-gallery-to-reorder');
+ for (i = 0; i < newOrder.length; i++) {
+ $item = $($toReorder[i]);
+ newId = $item.attr('id').replace(/---new/, '');
+ $item.removeClass('media-gallery-to-reorder');
+ $item.attr('id', newId);
+ }
+ }
+ sortable.sortable('enable');
+ }
+
+ $.post(event.data.callback, post, success_callback);
+ sortable.sortable('disable');
+};
+
+/**
+ * Helper function that returns a sorted array of element IDs minus a prefix.
+ *
+ * @param {Array} ids
+ * The IDs to be reordered. Example: ['element-5', 'element-4', 'element-6']
+ * @param {String} prefix
+ * The prefix to be removed from each ID. Example: 'element-'
+ *
+ * @return {Array}
+ * The IDs, minus their prefix, sorted numerically. Example: [4, 5, 6]
+ */
+Drupal.mediaGallerySort._getNewOrder = function (ids, prefix) {
+ var i;
+ var newOrder = [];
+ for (i = 0; i < ids.length; i++) {
+ var re = new RegExp(prefix);
+ newOrder.push(parseInt(ids[i].replace(re, ''), 10));
+ }
+ newOrder.sort(Drupal.mediaGallerySort.numericAscending);
+ return newOrder;
+}
+
+/**
+ * Sort callback. Returns the lesser of two numeric inputs.
+ */
+Drupal.mediaGallerySort.numericAscending = function (a, b) {
+ return a - b;
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.fields.inc b/sites/all/modules/media_gallery/media_gallery.fields.inc
new file mode 100644
index 000000000..74a030cd7
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.fields.inc
@@ -0,0 +1,331 @@
+<?php
+
+/**
+ * @file
+ * Field API integration for media_gallery.module.
+ */
+
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function media_gallery_field_formatter_info() {
+ return array(
+ 'media_gallery' => array(
+ 'label' => t('Gallery media'),
+ 'field types' => array('file'),
+ 'settings' => array('file_view_mode' => 'media_gallery_thumbnail'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ */
+function media_gallery_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $element = array();
+
+ if ($display['type'] == 'media_gallery') {
+ $element['file_view_mode'] = array(
+ '#title' => t('File view mode'),
+ '#type' => 'select',
+ '#default_value' => $settings['file_view_mode'],
+ '#options' => media_gallery_file_view_modes(),
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function media_gallery_field_formatter_settings_summary($field, $instance, $view_mode) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $summary = '';
+
+ if ($display['type'] == 'media_gallery') {
+ $entity_info = entity_get_info('file');
+ $file_view_mode_label = isset($entity_info['view modes'][$settings['file_view_mode']]) ? $entity_info['view modes'][$settings['file_view_mode']]['label'] : t('Default');
+ $summary = t('File view mode: @view_mode', array('@view_mode' => $file_view_mode_label));
+ }
+
+ return $summary;
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ */
+function media_gallery_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+ $element = array();
+ if (!$items) {
+ return $element;
+ }
+ $file_view_mode = $display['settings']['file_view_mode'];
+
+ // The Formatter Reference module allows per-entity, rather than
+ // per-bundle, formatter assignment, but it only works when the field being
+ // formatted and the reference field are part of the same entity. For media
+ // galleries, we want to enable formatter reference fields on the gallery node
+ // to be used as formatters for fields within the file entities, so we pass
+ // along those field values to them. Here we just collect all formatter
+ // reference field names for the entity.
+ $formatter_reference_fields = array();
+ if (module_exists('formatter_reference')) {
+ $fields = field_info_fields();
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+ foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance_info) {
+ if ($fields[$field_name]['type'] == 'formatter_reference') {
+ $formatter_reference_fields[] = $field_name;
+ }
+ }
+ }
+
+ // Prepare the referenced file entities for viewing.
+ $files = array();
+ foreach ($items as $delta => $item) {
+ $file = (object)$item;
+
+ // Pass along formatter reference field values from the gallery to the file.
+ foreach ($formatter_reference_fields as $field_name) {
+ if (!isset($file->{$field_name}) && isset($entity->{$field_name})) {
+ $file->{$field_name} = $entity->{$field_name};
+ }
+ }
+
+ // Set a default value for the file's media_title field.
+ // @todo Eventually, fix this to take into account the possibility of a
+ // multilingual media title field.
+ if (isset($file->media_title) && !isset($file->media_title[LANGUAGE_NONE][0]['value'])) {
+ $file->media_title[LANGUAGE_NONE][0]['value'] = _media_gallery_get_media_title($file);
+ }
+
+ $files[$file->fid] = $file;
+ }
+ field_attach_prepare_view('file', $files, $file_view_mode);
+ entity_prepare_view('file', $files);
+
+ // View each file. We don't use file_view_multiple(), because we need the
+ // render array indexed by $delta rather than by file id.
+ foreach ($items as $delta => $item) {
+ // Use $file in $files instead of $item,
+ // because $item does not contain the object modifications.
+ $file = (object)$item;
+ $element[$delta] = file_view($files[$file->fid], $file_view_mode, $langcode);
+ $element[$delta]['#media_gallery_entity_type'] = $entity_type;
+ $element[$delta]['#media_gallery_entity'] = $entity;
+ switch ($file_view_mode) {
+ case 'media_gallery_thumbnail':
+ $element[$delta]['#theme'] = 'media_gallery_media_item_thumbnail';
+ break;
+ case 'media_gallery_lightbox':
+ $element[$delta]['#theme'] = 'media_gallery_media_item_lightbox';
+ break;
+ case 'media_gallery_detail':
+ $element[$delta]['#theme'] = 'media_gallery_media_item_detail';
+ break;
+ case 'media_gallery_block_thumbnail':
+ $element[$delta]['#theme'] = 'media_gallery_block_thumbnail';
+ break;
+ case 'media_gallery_collection_thumbnail':
+ $element[$delta]['#theme'] = 'media_gallery_collection_thumbnail';
+ break;
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Constructs a drupal_render() array for a media entity displayed within a gallery.
+ *
+ * During normal gallery node display, the Field Attach API ensures that all of
+ * its fields get rendered. Sometimes, however, we need to display just a single
+ * media entity (one item from the multi-valued "media_gallery_file" field)
+ * without displaying any other media entities within that field, and without
+ * displaying any other gallery node fields, but via a field formatter.
+ *
+ * @see media_gallery_detail_page()
+ * @see media_gallery_lightbox_page()
+ */
+function media_gallery_item_view($gallery_node, $file, $file_view_mode) {
+ $display = array('type' => 'media_gallery', 'settings' => array('file_view_mode' => $file_view_mode));
+ $id = $gallery_node->nid;
+ $field_name = 'media_gallery_file';
+ $field = field_info_field($field_name);
+ $instance = field_info_instance('node', $field_name, 'media_gallery');
+ $langcode = key($gallery_node->{$field_name});
+ $items = array(0 => $file);
+ $items_multi = array($id => &$items); // Taken by reference, so can't inline.
+ $formatter = field_info_formatter_types($display['type']);
+ // "prepare_view" function is optional.
+ $function = $formatter['module'] . '_field_formatter_prepare_view';
+ if (function_exists($function)) {
+ $function('node', array($id => $gallery_node), $field, array($id => $instance), $langcode, $items_multi, array($id => $display));
+ }
+ // "view" function is mandatory, so deliberately break if it doesn't exist.
+ $function = $formatter['module'] . '_field_formatter_view';
+ $content = $function('node', $gallery_node, $field, $instance, $langcode, $items, $display);
+ _media_gallery_attach_css_resources($content);
+ return $content;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * @see formatter_reference_form_field_ui_display_overview_form_alter()
+ * @see media_gallery_field_formatter_prepare_view()
+ */
+function media_gallery_form_field_ui_display_overview_form_alter(&$form) {
+ // See formatter_reference_form_field_ui_display_overview_form_alter() for
+ // more details about why this form needs to be altered. All we're doing here
+ // is allowing a gallery node's formatter reference field to format file
+ // entity fields.
+ if (module_exists('formatter_reference') && $form['#entity_type'] == 'file') {
+ $gallery_field_instances = field_info_instances('node', 'media_gallery');
+ $fields = field_info_fields();
+ foreach ($form['#fields'] as $field_name) {
+ // Iterate all the formatter options, not just what's in
+ // $form['settings'][$field_name]['type']['#options'], in case
+ // formatter_reference_form_field_ui_display_overview_form_alter() ran
+ // first, and already removed the option.
+ foreach (field_ui_formatter_options($fields[$field_name]['type']) as $formatter_name => $formatter_label) {
+ $parts = explode('__', $formatter_name);
+ if (count($parts) == 2 && $parts[0] == 'formatter_reference_router') {
+ $formatter_reference_field_name = $parts[1];
+ // If the formatter reference field exists in the 'media_gallery' node
+ // bundle, then ensure it's an option within the media entity. But in
+ // the edge case that the same reference field also exists in the
+ // media entity, then that takes priority, so don't change its label.
+ if (isset($gallery_field_instances[$formatter_reference_field_name]) && !field_info_instance($form['#entity_type'], $formatter_reference_field_name, $form['#bundle'])) {
+ $form['settings'][$field_name]['type']['#options'][$formatter_name] = t('Value of field "@field" in the containing gallery', array('@field' => $gallery_field_instances[$formatter_reference_field_name]['label']));
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Gets the title of a media entity either from the media_title field or based on the filename.
+ */
+function _media_gallery_get_media_title($file) {
+ // If the entity has a value for the title field, use it.
+ if (isset($file->media_title[LANGUAGE_NONE][0]['value'])) {
+ $title = $file->media_title[LANGUAGE_NONE][0]['value'];
+ }
+ // Otherwise, base it on the file's filename, but with some adjustments for
+ // human-friendly display.
+ else {
+ $replacements = array(
+ '/\..*/' => '', // Remove first "." and everything after.
+ '/[^a-zA-Z0-9]+/' => ' ', // Replace non letters or numbers with a single space.
+ '/([a-z])([A-Z])/' => '\1 \2', // Insert a space between a lowercase letter and an uppercase letter.
+ '/([a-zA-Z])([0-9])/' => '\1 \2', // Insert a space between a letter and a number.
+ '/([0-9])([a-zA-Z])/' => '\1 \2', // Insert a space between a number and a letter.
+ );
+ // In addition to above replacements, also capitalize the first letter of
+ // each word, and remove leading and trailing spaces.
+ $title = trim(ucwords(preg_replace(array_keys($replacements), array_values($replacements), $file->filename)));
+ }
+ return $title;
+}
+
+/**
+ * Allowed values callback for media_gallery_format list field.
+ */
+function _media_gallery_get_format_values() {
+ return array(
+ 'node' => t('Show media on a full page'),
+ 'lightbox' => t('Show media in a lightbox'),
+ );
+}
+
+/**
+ * Allowed values callback for media_gallery_lightbox_extras list field.
+ */
+function _media_gallery_get_lightbox_extras_values() {
+ return array(
+ 0 => t('Do not show title and description'),
+ 1 => t('Show title and description'),
+ );
+}
+
+/**
+ * Allowed values callback for media_gallery_image_info list field.
+ */
+function _media_gallery_get_image_info_values() {
+ return array(
+ 'nothing' => t('Nothing'),
+ 'title' => t('Title'),
+ 'title_license' => t('Title and license'),
+ );
+}
+
+/**
+ * Allowed values callback for media_gallery_image_info_where list field.
+ */
+function _media_gallery_get_image_info_placement_values() {
+ return array(
+ 'hover' => t('Show title on hover'),
+ 'below' => t('Show title below'),
+ 'nothing' => t('Show nothing'),
+ );
+}
+
+/**
+ * Allowed values callback for media_gallery_allow_download list field.
+ */
+function _media_gallery_get_allow_download_values() {
+ return array(
+ 0 => t('Do not allow downloading of the original image'),
+ 1 => t('Allow downloading of the original image'),
+ );
+}
+
+/**
+ * Allowed values callback for media_gallery_expose_block list field.
+ */
+function _media_gallery_get_expose_block_values() {
+ return array(
+ 0 => t('Do not create a block of most recently added media'),
+ 1 => t('Create a block of most recently added media'),
+ );
+}
+
+/**
+ * Allowed values callback for field_license list field.
+ *
+ * @todo: should be moved to media_cc module or something.
+ */
+function _media_gallery_get_field_license_values() {
+ return array(
+ 'none' => t('None (all rights reserved)'),
+ '' => t('-- Creative Commons --'),
+ 'cc_sa_nc' => t('Attribution, Non-Commercial, Share Alike'),
+ 'cc_nc' => t('Attribution, Non-Commercial'),
+ 'cc_nd_nc' => t('Attribution, Non-Commercial, No Derivative Works'),
+ 'cc' => t('Attribution'),
+ 'cc_sa' => t('Attribution, Share Alike'),
+ 'cc_nd' => t('Attribution, No Derivative Works'),
+ );
+}
+
+/**
+ * Allowed values callback for media_gallery_columns list field.
+ */
+function _media_gallery_get_columns_values() {
+ return drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10));
+}
+
+/**
+ * Allowed values callback for media_gallery_block_columns list field.
+ */
+function _media_gallery_get_block_columns_values() {
+ return drupal_map_assoc(array(1, 2, 3, 4));
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.form.js b/sites/all/modules/media_gallery/media_gallery.form.js
new file mode 100644
index 000000000..561b20c0f
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.form.js
@@ -0,0 +1,47 @@
+(function ($) {
+
+Drupal.behaviors.mediaGalleryFieldsetSummaries = {
+ attach: function (context) {
+ $('fieldset.block-form', context).drupalSetSummary(function (context) {
+ if ($('#edit-media-gallery-expose-block-und', context).attr('checked')) {
+ return Drupal.t('Enabled');
+ }
+ else {
+ return Drupal.t('Not enabled');
+ }
+ });
+ }
+};
+
+Drupal.behaviors.media_gallery_form = {};
+Drupal.behaviors.media_gallery_form.attach = function (context, settings) {
+ // Change the "Presentation settings" image to match the radio buttons / checkbox.
+ var inputs = $('.presentation-settings input', context);
+ if (inputs.length) {
+ inputs.bind('change', Drupal.behaviors.media_gallery_form.format_select);
+ Drupal.behaviors.media_gallery_form.format_select();
+ }
+};
+
+Drupal.behaviors.media_gallery_form.format_select = function (event) {
+ var radioValue = $('.presentation-settings input:radio:checked').val();
+ var icon = $('.presentation-settings .setting-icon');
+ var checkbox = $('.presentation-settings .field-name-media-gallery-lightbox-extras input');
+
+ // Depending on the radio button chosen add a class
+ if (radioValue == 'node') {
+ icon.attr('class', 'setting-icon display-page');
+ // Disable the checkbox
+ checkbox.attr('disabled', true);
+ } else {
+ icon.attr('class', 'setting-icon display-lightbox');
+ // Turn on the checkbox
+ checkbox.attr('disabled', false);
+ // Add a class if the checkbox is checked
+ if (checkbox.is(':checked')) {
+ icon.attr('class', 'setting-icon display-extras');
+ }
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/media_gallery/media_gallery.ie7.css b/sites/all/modules/media_gallery/media_gallery.ie7.css
new file mode 100644
index 000000000..fbeecdbdf
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.ie7.css
@@ -0,0 +1,19 @@
+
+/**
+ * Workarounds for IE7 bugs in the lightbox. When title/description are shown,
+ * the copyright icons overlap with the download link instead of being aligned
+ * right, so we override styles here to fix that.
+ *
+ */
+.lightbox-stack .media-gallery-detail-info {
+ display: inline-block;
+ width: 100%;
+ overflow: visible;
+}
+
+/* A little heavy handed, but it looks better than broken */
+.lightbox-stack .media-gallery-detail-info .media-license {
+ left: 146px;
+ top: 8px;
+ width: 60px;
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.info b/sites/all/modules/media_gallery/media_gallery.info
new file mode 100644
index 000000000..d593e5064
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.info
@@ -0,0 +1,25 @@
+name = Media Gallery
+description = A flexible gallery of media.
+core = 7.x
+package = Media
+
+dependencies[] = list
+dependencies[] = number
+dependencies[] = media
+dependencies[] = taxonomy
+dependencies[] = multiform
+dependencies[] = media_bulk_upload
+
+files[] = media_gallery.module
+files[] = media_gallery.admin.inc
+files[] = media_gallery.fields.inc
+files[] = media_gallery.theme.inc
+
+configure = admin/config/media/galleries
+
+; Information added by Drupal.org packaging script on 2015-10-17
+version = "7.x-2.x-dev"
+core = "7.x"
+project = "media_gallery"
+datestamp = "1445094841"
+
diff --git a/sites/all/modules/media_gallery/media_gallery.install b/sites/all/modules/media_gallery/media_gallery.install
new file mode 100644
index 000000000..c3a469483
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.install
@@ -0,0 +1,1374 @@
+<?php
+
+/**
+ * @file
+ * Install file for media_gallery. Includes field and instance definitions.
+ */
+
+/**
+ * Implements hook_enable().
+ */
+function media_gallery_enable() {
+ // Work around http://drupal.org/node/727876. (See also
+ // http://drupal.org/node/882364.)
+ //field_associate_fields('taxonomy');
+}
+
+/**
+ * Implements hook_install().
+ */
+function media_gallery_install() {
+ // Adjust bundle settings for the media gallery content type.
+ $bundle_settings = field_bundle_settings('node', 'media_gallery');
+
+ // Enable the gallery block and full view modes.
+ $bundle_settings['view_modes']['media_gallery_block']['custom_settings'] = TRUE;
+ $bundle_settings['view_modes']['full']['custom_settings'] = TRUE;
+
+ // Adjust the "Add media link" extra field so that it only displays on the
+ // full node view, with the correct weight relative to the other fields. In
+ // particular, we put it directly after the 'media_gallery_description'
+ // field; see _media_gallery_controlled_instances().
+ $bundle_settings['extra_fields']['display']['add_media_link']['full']['weight'] = 1;
+ $bundle_settings['extra_fields']['display']['add_media_link']['full']['visible'] = TRUE;
+ $bundle_settings['extra_fields']['display']['add_media_link']['default']['visible'] = FALSE;
+ $bundle_settings['extra_fields']['display']['add_media_link']['teaser']['visible'] = FALSE;
+ $bundle_settings['extra_fields']['display']['add_media_link']['media_gallery_block']['visible'] = FALSE;
+
+ // _field_extra_fields_pre_render() requires an explicit 'weight' value for
+ // all extra field settings stored in the database.
+ foreach ($bundle_settings['extra_fields']['display']['add_media_link'] as $name => $view_mode) {
+ $bundle_settings['extra_fields']['display']['add_media_link'][$name]['weight'] = 1;
+ }
+
+ // Save the new bundle settings.
+ field_bundle_settings('node', 'media_gallery', $bundle_settings);
+
+ // Enable custom display settings for gallery context view modes for file
+ // types that can be in a gallery.
+ foreach (array('audio', 'image', 'video') as $bundle) {
+ $bundle_settings = field_bundle_settings('file', $bundle);
+ foreach (media_gallery_file_view_modes() as $view_mode => $label) {
+ $bundle_settings['view_modes'][$view_mode]['custom_settings'] = TRUE;
+ }
+ field_bundle_settings('file', $bundle, $bundle_settings);
+ }
+
+ // Clear caches so that our implementation of hook_image_default_styles() is
+ // correctly used when all the fields created below need it to be.
+ // @todo There should obviously be a cleaner way to do this.
+ cache_clear_all('*', 'cache', TRUE);
+ drupal_static_reset('image_styles');
+ drupal_static_reset('image_effects');
+ if (module_exists('styles') && function_exists('styles_style_flush')) {
+ styles_style_flush();
+ }
+
+ // Add the taxonomy vocabulary for media gallery collections.
+ $vocabulary = media_gallery_create_taxonomy_vocab();
+
+ // Make sure the standard 'field_tags' field exists.
+ _media_gallery_ensure_field_tags();
+
+ // Create fields (but not instances yet) for media_gallery nodes and
+ // for the gallery collection vocabulary.
+ foreach (_media_gallery_controlled_fields() as $field) {
+ _media_gallery_ensure_field($field);
+ }
+ // Attach fields to gallery_collection taxonomy terms.
+ foreach (_media_gallery_controlled_instances('taxonomy_term') as $instance) {
+ _media_gallery_ensure_instance($instance);
+ }
+
+ // Now that the gallery_collection vocabulary exists and has fields attached,
+ // create an "All galleries" term for galleries to belong to by default.
+ media_gallery_create_taxonomy_term($vocabulary);
+
+ // Attach fields to the media gallery node type (including a term reference
+ // for the default collection).
+ foreach (_media_gallery_controlled_instances('node') as $instance) {
+ _media_gallery_ensure_instance($instance);
+ }
+
+ // Make sure all media bundles have the instances we expect.
+ _media_gallery_ensure_media_instances();
+
+ // Set variables for the media gallery node type.
+ variable_set('node_submitted_media_gallery', FALSE);
+ variable_set('node_options_media_gallery', array('status'));
+ variable_set('comment_media_gallery', 0);
+}
+
+/**
+ * Implements hook_requirements().
+ */
+function media_gallery_requirements() {
+ $requirements = array();
+
+ // If this module is part of an install profile, its requirements will be
+ // checked before the field system is available. The rest of this function is
+ // unneeded anyway in that case, so bail out here to avoid fatal errors.
+ if (!module_exists('field')) {
+ return $requirements;
+ }
+
+ $t = get_t();
+
+ $required_fields = _media_gallery_controlled_fields();
+ // In addition to the fields we control, we also need the standard field_tags
+ // that most sites will have gotten from their install profile.
+ $required_fields['field_tags'] = array('type' => 'taxonomy_term_reference');
+
+ foreach ($required_fields as $field_name => $field_definition) {
+ $field = field_info_field($field_name);
+ // If the field doesn't exist, we will create it on install.
+ if (!$field) {
+ continue;
+ }
+ // Between Media Gallery beta2 and beta3, field definitions were changed
+ // from list_number to list_float, to keep up with Drupal core changes.
+ // list_update_7001() handles the updating of existing fields, but
+ // update.php checks all requirements prior to running any update function.
+ if ($field['type'] == 'list_number' && $field_definition['type'] == 'list_float') {
+ continue;
+ }
+ if ($field['type'] != $field_definition['type']) {
+ $requirements['existing_field_' . $field_name] = array(
+ 'description' => $t("%field_name already exists and is not of type %type. Installation cannot continue. Please remove this field or change its type.", array('%field_name' => $field_name, '%type' => $field_definition['type'])),
+ 'severity' => REQUIREMENT_ERROR,
+ );
+ }
+ }
+ return $requirements;
+}
+
+/**
+ * Implements hook_schema().
+ */
+function media_gallery_schema() {
+ $schema['media_gallery_weight'] = array(
+ 'description' => 'The weight of media galleries within a given collection.',
+ 'fields' => array(
+ 'tid' => array(
+ 'description' => 'The taxonomy term id corresponding to a media gallery collection.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'nid' => array(
+ 'description' => 'The node id of the media gallery.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'weight' => array(
+ 'description' => 'The weight of the media gallery within the collection.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('tid', 'nid'),
+ );
+ return $schema;
+}
+
+/**
+ * Returns definitions for fields this module both creates and deletes.
+ */
+function _media_gallery_controlled_fields() {
+ $fields = array(
+ // The media items that make up the gallery.
+ 'media_gallery_file' => array(
+ 'field_name' => 'media_gallery_file',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'type' => 'file',
+ ),
+ // The gallery description.
+ 'media_gallery_description' => array(
+ 'field_name' => 'media_gallery_description',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'text_long',
+ ),
+ // How to format the gallery (if links go to lightbox or node display).
+ 'media_gallery_format' => array(
+ 'field_name' => 'media_gallery_format',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'list_text',
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_format_values',
+ ),
+ ),
+ // Whether or not the lightbox should show extra fields.
+ 'media_gallery_lightbox_extras' => array(
+ 'field_name' => 'media_gallery_lightbox_extras',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'list_boolean',
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_lightbox_extras_values',
+ ),
+ ),
+ // How many columns of thumbnails to show.
+ 'media_gallery_columns' => array(
+ 'field_name' => 'media_gallery_columns',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'list_float',
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_columns_values',
+ ),
+ ),
+ // How many rows of thumbnails to show.
+ 'media_gallery_rows' => array(
+ 'field_name' => 'media_gallery_rows',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'number_integer',
+ ),
+ // Whether to show title/license on hover or below thumbnail.
+ 'media_gallery_image_info_where' => array(
+ 'field_name' => 'media_gallery_image_info_where',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'list_text',
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_image_info_placement_values',
+ ),
+ ),
+ // Whether to show a "Download original image" checkbox.
+ 'media_gallery_allow_download' => array(
+ 'field_name' => 'media_gallery_allow_download',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'list_boolean',
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_allow_download_values',
+ ),
+ ),
+ // Whether to expose a block for this gallery.
+ 'media_gallery_expose_block' => array(
+ 'field_name' => 'media_gallery_expose_block',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'list_boolean',
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_expose_block_values',
+ ),
+ ),
+ // How many columns of thumbnails to show in the block.
+ 'media_gallery_block_columns' => array(
+ 'field_name' => 'media_gallery_block_columns',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'list_float',
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_block_columns_values',
+ ),
+ ),
+ // How many rows of thumbnails to show in the block.
+ 'media_gallery_block_rows' => array(
+ 'field_name' => 'media_gallery_block_rows',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'number_integer',
+ ),
+ 'media_gallery_collection' => array(
+ 'field_name' => 'media_gallery_collection',
+ 'type' => 'taxonomy_term_reference',
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => 'gallery_collections',
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ ),
+ // Fields to create on media items.
+ 'media_description' => array(
+ 'field_name' => 'media_description',
+ 'locked' => TRUE,
+ 'type' => 'text_long',
+ ),
+ 'media_title' => array(
+ 'field_name' => 'media_title',
+ 'locked' => TRUE,
+ 'type' => 'text',
+ ),
+ 'field_license' => array(
+ 'field_name' => 'field_license',
+ 'locked' => TRUE,
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_field_license_values',
+ ),
+ 'type' => 'list_text',
+ 'active' => TRUE,
+ 'cardinality' => 1,
+ ),
+ );
+
+ return $fields;
+}
+
+/**
+ * Returns definitions for instances this modules both creates and deletes.
+ *
+ * @param $group
+ * Optional. The group of instances to return. May be 'node' or
+ * 'taxonomy_term'. If omitted, returns all instances.
+ *
+ * @return
+ * A structured array of instances.
+ */
+function _media_gallery_controlled_instances($group = NULL) {
+ $t = get_t();
+ $node_instances = array(
+ // The gallery description.
+ 'media_gallery_description' => array(
+ 'field_name' => 'media_gallery_description',
+ 'label' => $t('Description'),
+ 'widget' => array(
+ 'type' => 'text_textarea',
+ 'settings' => array('rows' => 4),
+ ),
+ 'settings' => array(
+ 'text_processing' => 1,
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'text_default',
+ 'label' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'full' => array(
+ 'type' => 'text_default',
+ 'label' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ ),
+ 'media_gallery_file' => array(
+ 'field_name' => 'media_gallery_file',
+ 'label' => $t('Gallery media'),
+ 'widget' => array(
+ 'type' => 'media_generic',
+ 'settings' => array(
+ // Eventually other media types will be allowed.
+ 'allowed_types' => array('audio' => 'audio', 'image' => 'image', 'video' => 'video'),
+ 'allowed_schemes' => array('public' => 'public'),
+ ),
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'media_gallery',
+ 'settings' => array('file_view_mode' => 'media_gallery_thumbnail'),
+ 'label' => 'hidden',
+ 'weight' => 2,
+ ),
+ 'full' => array(
+ 'type' => 'media_gallery',
+ 'settings' => array('file_view_mode' => 'media_gallery_thumbnail'),
+ 'label' => 'hidden',
+ 'weight' => 2,
+ ),
+ 'teaser' => array(
+ 'type' => 'media_gallery',
+ 'settings' => array('file_view_mode' => 'media_gallery_collection_thumbnail'),
+ 'label' => 'hidden',
+ 'weight' => 2,
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'media_gallery',
+ 'settings' => array('file_view_mode' => 'media_gallery_block_thumbnail'),
+ 'label' => 'hidden',
+ 'weight' => 2,
+ ),
+ ),
+ ),
+ // How to format the gallery (if links go to lightbox or node display).
+ 'media_gallery_format' => array(
+ 'field_name' => 'media_gallery_format',
+ 'label' => $t('Gallery format'),
+ 'required' => TRUE,
+ 'default_value' => array(array('value' => 'lightbox')),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ 'widget' => array(
+ 'type' => 'options_buttons',
+ ),
+ ),
+ // Whether to show a "Exclude title and description" checkbox.
+ 'media_gallery_lightbox_extras' => array(
+ 'field_name' => 'media_gallery_lightbox_extras',
+ 'label' => 'Lightbox title and description',
+ 'description' => $t('Show title and description'),
+ 'default_value' => array(array('value' => 0)),
+ 'widget' => array(
+ 'type' => 'options_onoff',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ // How many columns to show.
+ 'media_gallery_columns' => array(
+ 'field_name' => 'media_gallery_columns',
+ 'label' => $t('Number of columns'),
+ 'default_value' => array(array('value' => 4)),
+ 'required' => TRUE,
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ // How many rows to show.
+ 'media_gallery_rows' => array(
+ 'field_name' => 'media_gallery_rows',
+ 'label' => $t('Number of rows'),
+ 'default_value' => array(array('value' => 3)),
+ 'settings' => array(
+ 'min' => '1',
+ ),
+ 'required' => TRUE,
+ 'widget' => array(
+ 'type' => 'number',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ // Whether to show title/license on hover or below thumbnail.
+ 'media_gallery_image_info_where' => array(
+ 'field_name' => 'media_gallery_image_info_where',
+ 'label' => $t('Media information'),
+ 'required' => TRUE,
+ 'default_value' => array(array('value' => 'hover')),
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ // Whether to show a "Download original image" checkbox.
+ 'media_gallery_allow_download' => array(
+ 'field_name' => 'media_gallery_allow_download',
+ 'label' => $t('Allow downloading of the original image'),
+ 'description' => $t('Display a "download original image" link'),
+ 'default_value' => array(array('value' => 1)),
+ 'widget' => array(
+ 'type' => 'options_onoff',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ // Whether to expose a block for this gallery.
+ 'media_gallery_expose_block' => array(
+ 'field_name' => 'media_gallery_expose_block',
+ 'label' => $t('Create a block of most recently added media'),
+ 'default_value' => array(array('value' => 0)),
+ 'widget' => array(
+ 'type' => 'options_onoff',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ // How many columns to show in the block.
+ 'media_gallery_block_columns' => array(
+ 'field_name' => 'media_gallery_block_columns',
+ 'label' => $t('Number of columns'),
+ 'default_value' => array(array('value' => 2)),
+ 'required' => TRUE,
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ // How many rows to show in the block.
+ 'media_gallery_block_rows' => array(
+ 'field_name' => 'media_gallery_block_rows',
+ 'label' => $t('Number of rows'),
+ 'default_value' => array(array('value' => 3)),
+ 'required' => TRUE,
+ 'settings' => array(
+ 'min' => 1,
+ ),
+ 'widget' => array(
+ 'type' => 'number',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ // The 'collection' tag field on media gallery nodes.
+ 'media_gallery_collection' => array(
+ 'field_name' => 'media_gallery_collection',
+ 'label' => $t('Gallery collection'),
+ 'default_value' => array(
+ array(
+ 'tid' => variable_get('media_gallery_default_collection_tid'),
+ ),
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ ),
+ );
+ foreach ($node_instances as &$instance) {
+ $instance['entity_type'] = 'node';
+ $instance['bundle'] = 'media_gallery';
+ }
+ unset($instance);
+
+ $instances = array_intersect_key($node_instances, array_flip(array('media_gallery_columns', 'media_gallery_rows', 'media_gallery_image_info_where')));
+ $instances['media_gallery_image_info_where']['label'] = $t('Gallery information');
+ $instances['field_license'] = array(
+ 'field_name' => 'field_license',
+ 'label' => $t('Default license settings'),
+ 'required' => TRUE,
+ 'default_value' => array(
+ array('value' => 'nothing'),
+ ),
+ 'description' => $t('Choose a default <a href="http://creativecommons.org">@cc_link</a> license for all Gallery media. Later you can change the license for each piece of media.', array('@cc_link' => 'Creative Commons')),
+ 'weight' => 14,
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ );
+
+ foreach ($instances as $key => $instance) {
+ // Since we are re-using fields which are defined for the node, we need to
+ // remove any additional view modes which don't belong to avoid E_NOTICE errors.
+ $instance['display'] = array_intersect_key($instance['display'], array_flip(array('default', 'full')));
+ $instance['entity_type'] = 'taxonomy_term';
+ $instance['bundle'] = 'gallery_collections';
+ $taxonomy_instances['taxo_term_' . $key] = $instance;
+ }
+
+ switch ($group) {
+ case 'node':
+ return $node_instances;
+ case 'taxonomy_term':
+ return $taxonomy_instances;
+ default:
+ return $node_instances + $taxonomy_instances;
+ }
+}
+
+/**
+ * Create a field, unless it exists already.
+ *
+ * Note that it's not necessary to check field type here, as that's done in the
+ * requirements step.
+ *
+ * @param $field
+ * The field definition.
+ */
+function _media_gallery_ensure_field($field) {
+ $existing_field = field_read_field($field['field_name'], array('include_inactive' => TRUE));
+ if (empty($existing_field)) {
+ field_create_field($field);
+ }
+}
+
+function _media_gallery_ensure_instance($instance) {
+ $existing_instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
+ if (empty($existing_instance)) {
+ field_create_instance($instance);
+ }
+}
+
+/**
+ * Returns definitions for instances this module requires on media bundles.
+ */
+function _media_required_instances() {
+ $t = get_t();
+ $media_instances = array(
+ 'media_title' => array(
+ 'field_name' => 'media_title',
+ 'label' => $t('Title'),
+ 'display' => array(
+ 'default' => array('type' => 'hidden'),
+ 'media_gallery_thumbnail' => array('type' => 'text_default', 'label' => 'hidden'),
+ 'media_gallery_lightbox' => array('type' => 'text_default', 'label' => 'hidden'),
+ 'media_gallery_detail' => array('type' => 'text_default', 'label' => 'hidden'),
+ ),
+ ),
+ 'media_description' => array(
+ 'field_name' => 'media_description',
+ 'label' => $t('Description'),
+ 'widget' => array(
+ 'type' => 'text_textarea',
+ 'settings' => array('rows' => 4),
+ ),
+ 'settings' => array(
+ 'text_processing' => 1,
+ ),
+ 'display' => array(
+ 'default' => array('type' => 'text_default', 'label' => 'above'),
+ 'media_gallery_thumbnail' => array('type' => 'text_default', 'label' => 'above'),
+ 'media_gallery_lightbox' => array('type' => 'text_default', 'label' => 'above'),
+ 'media_gallery_detail' => array('type' => 'text_default', 'label' => 'above'),
+ ),
+ ),
+ 'field_tags' => array(
+ 'field_name' => 'field_tags',
+ 'label' => $t('Tags'),
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ ),
+ 'display' => array(
+ 'default' => array('type' => 'hidden'),
+ ),
+ ),
+ 'field_license' => array(
+ 'field_name' => 'field_license',
+ 'label' => $t('License settings for this media'),
+ 'required' => TRUE,
+ 'default_value' => array(
+ array('value' => 'nothing'),
+ ),
+ 'description' => $t('Select a <a href="http://creativecommons.org" target="_new">Creative Commons</a> license for others who use this media.'),
+ 'display' => array(
+ 'default' => array('type' => 'hidden'),
+ 'media_gallery_thumbnail' => array('type' => 'list_default', 'label' => 'hidden'),
+ 'media_gallery_lightbox' => array('type' => 'list_default', 'label' => 'hidden'),
+ 'media_gallery_detail' => array('type' => 'list_default', 'label' => 'hidden'),
+ ),
+ ),
+ );
+ return $media_instances;
+}
+
+/**
+ * Make sure the field_tags field exists and is of the right type.
+ */
+function _media_gallery_ensure_field_tags() {
+ // Make sure the 'tags' vocabulary exists.
+ $vocabulary = taxonomy_vocabulary_machine_name_load('tags');
+ if (!$vocabulary) {
+ $description = st('Use tags to group articles on similar topics into categories.');
+ $help = st('Enter a comma-separated list of words to describe your content.');
+ $vocabulary = (object) array(
+ 'name' => 'Tags',
+ 'description' => $description,
+ 'machine_name' => 'tags',
+ 'help' => $help,
+ );
+ taxonomy_vocabulary_save($vocabulary);
+ }
+
+ $field = array(
+ 'field_name' => 'field_tags',
+ 'type' => 'taxonomy_term_reference',
+ // Set cardinality to unlimited for tagging.
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ _media_gallery_ensure_field($field);
+}
+
+/**
+ * Makes sure media entities have the fields media gallery requires.
+ */
+function _media_gallery_ensure_media_instances() {
+ $t = get_t();
+ $instances = _media_required_instances();
+ foreach (file_type_get_enabled_types() as $bundle => $type) {
+ foreach ($instances as $instance) {
+ $instance_copy = $instance;
+ $instance_copy += array(
+ 'entity_type' => 'file',
+ 'bundle' => $bundle,
+ );
+ $label = in_array($bundle, array('image', 'audio', 'video')) ? $bundle : 'file';
+ if ($instance_copy['field_name'] == 'field_tags' && !isset($instance_copy['description'])) {
+ $instance_copy['description'] = $t("Enter a comma-separated list of words to describe your $label.");
+ }
+ if ($instance_copy['field_name'] == 'field_license') {
+ $instance_copy['label'] = $t("License settings for this $label");
+ $instance_copy['description'] = $t('Select a <a href="http://creativecommons.org" target="_new">Creative Commons</a> license for others who use this ' . $label . '.');
+ }
+ _media_gallery_ensure_instance($instance_copy);
+ }
+ }
+}
+
+/**
+ * Helper function to create required taxonomy vocabulary.
+ */
+function media_gallery_create_taxonomy_vocab() {
+ $t = get_t();
+ $vocabulary = (object) array(
+ 'name' => 'Gallery collections',
+ 'description' => $t('Groups of rich media galleries'),
+ 'machine_name' => 'gallery_collections',
+ );
+
+ taxonomy_vocabulary_save($vocabulary);
+ variable_set('media_gallery_collection_vid', $vocabulary->vid);
+ return $vocabulary;
+}
+
+/**
+ * Helper function to create required taxonomy term.
+ */
+function media_gallery_create_taxonomy_term($vocabulary) {
+ // Create a taxonomy term for the "All Galleries" collection.
+ $term = new stdClass();
+ $term->vid = $vocabulary->vid;
+ $term->name = 'Galleries';
+ $term->description = '';
+ // Choose a text format that will prevent WYSIWYGs from appearing by default.
+ // When we allow people to create new gallery collections we'll have to
+ // (carefully) modify the form for adding new ones also.
+ $term->format = filter_fallback_format();
+ $term->path = array('alias' => 'galleries');
+ // Save the term, preventing Pathauto from aliasing it incorrectly.
+ _media_gallery_prevent_unwanted_pathauto_aliases();
+ taxonomy_term_save($term);
+ _media_gallery_allow_all_pathauto_aliases();
+
+ // Create a menu link for this taxonomy term. We set the link title to
+ // 'Taxonomy term' in order to match the title of the corresponding router
+ // item, since this is what triggers the menu system to display a dynamic
+ // title for the link.
+ $menu_item = array(
+ 'menu_name' => 'main-menu',
+ 'weight' => 10,
+ 'link_title' => 'Taxonomy term',
+ 'link_path' => 'taxonomy/term/' . $term->tid,
+ );
+ // If the router item doesn't exist yet (for example, if we are installing
+ // either the Taxonomy module or Drupal itself at the same time as Media
+ // Gallery), rebuild the menu before saving, to avoid errors.
+ $router_item_exists = (bool) db_query_range('SELECT 1 FROM {menu_router} WHERE path = :path', 0, 1, array(':path' => 'taxonomy/term/%'))->fetchField();
+ if (!$router_item_exists) {
+ menu_rebuild();
+ }
+ menu_link_save($menu_item);
+
+ // Save the term ID for future use.
+ variable_set('media_gallery_default_collection_tid', $term->tid);
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function media_gallery_uninstall() {
+ // Delete all existing galleries.
+ $nids = db_query('SELECT nid FROM {node} n WHERE n.type = :type', array(':type' => 'media_gallery'))->fetchCol();
+ node_delete_multiple($nids);
+
+ // Delete fields and instances.
+ foreach (array_keys(_media_gallery_controlled_fields()) as $field) {
+ field_delete_field($field);
+ }
+ $instances = _media_gallery_controlled_instances();
+ $instances += field_info_instances('node', 'media_gallery');
+ foreach ($instances as $instance_name => $instance) {
+ field_delete_instance($instance);
+ }
+
+ // Delete the content type itself.
+ // @todo: We can't run this since the content type already ceased to exist
+ // when the module was disabled. But we'd like to be able to, so that the
+ // proper hooks within node_type_delete() get fired.
+ // node_type_delete('media_gallery');
+
+ // Delete the taxonomy vocabulary.
+ $vid = variable_get('media_gallery_collection_vid');
+ if ($vid && function_exists('taxonomy_vocabulary_delete')) {
+ taxonomy_vocabulary_delete($vid);
+ }
+
+ // Delete variables for the media gallery node type.
+ variable_del('node_submitted_media_gallery');
+ variable_del('node_options_media_gallery');
+ variable_del('comment_media_gallery');
+ variable_del('media_gallery_collection_vid');
+ variable_del('media_gallery_default_collection_tid');
+}
+
+/**
+ * Set the default value of media_gallery_expose_block to zero
+ * In order to have the expose block checkbox appear above the column and row number dropdowns
+ * it was necessary to update those field instances with their initial values, essentially
+ * refreshing them.
+ */
+function media_gallery_update_7000() {
+ $t = get_t();
+
+ // Add a check to make sure the instance exists if we want to update it.
+ $instance = field_info_instance('node', 'media_gallery_expose_block', 'media_gallery');
+ if ($instance) {
+ $instance['default_value'] = array(array('value' => 0));
+ field_update_instance($instance);
+ }
+
+ // Update the media_display field so that it shows the label
+ foreach (array('video', 'image') as $bundle) {
+ field_update_instance( array(
+ 'field_name' => 'media_description',
+ 'bundle' => $bundle,
+ 'entity_type' => 'media',
+ 'display' => array(
+ 'default' => array('type' => 'text_default', 'label' => 'above'),
+ 'media_gallery_thumbnail' => array('type' => 'text_default', 'label' => 'above'),
+ 'media_gallery_lightbox' => array('type' => 'text_default', 'label' => 'above'),
+ 'media_gallery_detail' => array('type' => 'text_default', 'label' => 'above'),
+ ),
+ )
+ );
+ }
+
+ // Remove the media_gallery_gallery_info field
+ field_delete_field('media_gallery_gallery_info');
+ field_delete_field('media_gallery_image_info');
+
+ // Ensure that the new field exists
+ $extra_field = array(
+ 'field_name' => 'media_gallery_lightbox_extras',
+ 'cardinality' => 1,
+ 'locked' => TRUE,
+ 'type' => 'list_boolean',
+ 'settings' => array(
+ 'allowed_values_function' => '_media_gallery_get_lightbox_extras_values',
+ ),
+ );
+ _media_gallery_ensure_field($extra_field);
+
+ $new_instance = array(
+ 'field_name' => 'media_gallery_lightbox_extras',
+ 'label' => 'Lightbox title and description',
+ 'description' => $t('Show title and description'),
+ 'default_value' => array(array('value' => 0)),
+ 'entity_type' => 'node',
+ 'bundle' => 'media_gallery',
+ 'widget' => array(
+ 'type' => 'options_onoff',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ );
+ _media_gallery_ensure_instance($new_instance);
+}
+
+/**
+ * Updates all gallery nodes to use new default settings.
+ */
+function media_gallery_update_7001() {
+ $result = db_query("SELECT nid FROM {node} WHERE type = 'media_gallery'");
+ foreach ($result as $record) {
+ $node = node_load($record->nid);
+ if ($node->media_gallery_format[LANGUAGE_NONE][0]['value'] == 'lightbox_text') {
+ $node->media_gallery_format[LANGUAGE_NONE][0]['value'] = 'lightbox';
+ $node->media_gallery_lightbox_extras[LANGUAGE_NONE][0]['value'] = 0;
+ node_save($node);
+ }
+ }
+}
+
+/**
+ * Configure video formatters to desired defaults for gallery view modes.
+ */
+function media_gallery_update_7002() {
+ drupal_load('module', 'field');
+ $bundle = 'video';
+ $bundle_settings = field_bundle_settings('media', $bundle);
+ $bundle_settings['view_modes']['media_gallery_thumbnail']['custom_settings'] = TRUE;
+ $bundle_settings['view_modes']['media_gallery_lightbox']['custom_settings'] = TRUE;
+ $bundle_settings['view_modes']['media_gallery_detail']['custom_settings'] = TRUE;
+ $bundle_settings['view_modes']['media_gallery_block_thumbnail']['custom_settings'] = TRUE;
+ $bundle_settings['view_modes']['media_gallery_collection_thumbnail']['custom_settings'] = TRUE;
+ field_bundle_settings('media', $bundle, $bundle_settings);
+ $instance = field_info_instance('media', 'file', $bundle);
+ $instance['display']['media_gallery_thumbnail'] = array('type' => 'styles_file_media_gallery_thumbnail', 'label' => 'hidden');
+ $instance['display']['media_gallery_lightbox'] = array('type' => 'styles_file_media_gallery_large', 'label' => 'hidden');
+ $instance['display']['media_gallery_detail'] = array('type' => 'styles_file_media_gallery_large', 'label' => 'hidden');
+ $instance['display']['media_gallery_block_thumbnail'] = array('type' => 'styles_file_media_gallery_thumbnail', 'label' => 'hidden');
+ $instance['display']['media_gallery_collection_thumbnail'] = array('type' => 'styles_file_media_gallery_thumbnail', 'label' => 'hidden');
+ field_update_instance($instance);
+}
+
+/**
+ * Configure gallery nodes to allow video as well as image media.
+ */
+function media_gallery_update_7003() {
+ drupal_load('module', 'field');
+ $instance = field_info_instance('node', 'media_gallery_media', 'media_gallery');
+ // If the instance doesn't exist, we can't update it.
+ if ($instance) {
+ $instance['widget']['settings']['allowed_types'] = array('image' => 'image', 'video' => 'video');
+ field_update_instance($instance);
+ }
+}
+
+/**
+ * Configure media license field to have a per-type label and description.
+ */
+function media_gallery_update_7004() {
+ drupal_load('module', 'field');
+ drupal_load('module', 'media');
+ $t = get_t();
+ foreach (media_type_get_types() as $bundle => $type) {
+ $instance = field_info_instance('media', 'field_license', $bundle);
+ // If the instance doesn't exist, we can't update it.
+ if ($instance) {
+ $label = in_array($bundle, array('image', 'audio', 'video')) ? $bundle : 'file';
+ $instance['label'] = $t("License settings for this $label");
+ $instance['description'] = $t('Select a <a href="http://creativecommons.org" target="_new">Creative Commons</a> license for others who use this ' . $label . '.');
+ field_update_instance($instance);
+ }
+ }
+}
+
+/**
+ * There was an odd case where galleries created previous to youtube support
+ * lost some display settings on update. This update fixes those display settings.
+ */
+
+function media_gallery_update_7005() {
+
+ // Ensure that the media_description field has the proper label
+ foreach (array('video', 'image') as $bundle) {
+ $instance = field_info_instance('media', 'media_description', $bundle);
+ if ($instance) {
+ $instance['label'] = t('Description');
+ field_update_instance($instance);
+ }
+ }
+
+ // Ensure that media videos have the proper display formatters
+ $instance = field_info_instance('media', 'file', 'video');
+ if ($instance) {
+ $instance['display']['media_gallery_thumbnail'] = array('type' => 'styles_file_media_gallery_thumbnail', 'label' => 'hidden');
+ $instance['display']['media_gallery_lightbox'] = array('type' => 'styles_file_media_gallery_large', 'label' => 'hidden');
+ $instance['display']['media_gallery_detail'] = array('type' => 'styles_file_media_gallery_large', 'label' => 'hidden');
+ $instance['display']['media_gallery_block_thumbnail'] = array('type' => 'styles_file_media_gallery_thumbnail', 'label' => 'hidden');
+ $instance['display']['media_gallery_collection_thumbnail'] = array('type' => 'styles_file_media_gallery_thumbnail', 'label' => 'hidden');
+ field_update_instance($instance);
+ }
+
+ // Remove the old add_images_link extra field if it still exists
+ $bundle_settings = field_bundle_settings('node', 'media_gallery');
+ unset($bundle_settings['extra_fields']['display']['add_images_link']);
+ $bundle_settings['extra_fields']['display']['add_media_link']['full']['weight'] = 1;
+ $bundle_settings['extra_fields']['display']['add_media_link']['full']['visible'] = TRUE;
+ $bundle_settings['extra_fields']['display']['add_media_link']['default']['visible'] = FALSE;
+ $bundle_settings['extra_fields']['display']['add_media_link']['teaser']['visible'] = FALSE;
+ $bundle_settings['extra_fields']['display']['add_media_link']['media_gallery_block']['visible'] = FALSE;
+ field_bundle_settings('node', 'media_gallery', $bundle_settings);
+}
+
+/**
+ * Make sure the gallery node form includes the media_gallery_lightbox_extras
+ * checkbox.
+ */
+function media_gallery_update_7006() {
+
+ // Whether to show a "Exclude title and description" checkbox.
+ $field = array(
+ 'field_name' => 'media_gallery_lightbox_extras',
+ 'label' => 'Lightbox title and description',
+ 'description' => 'Show title and description',
+ 'default_value' => array(array('value' => 0)),
+ 'widget' => array(
+ 'type' => 'options_onoff',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ );
+
+ $instance = array(
+ 'field_name' => 'media_gallery_lightbox_extras',
+ 'label' => 'Lightbox title and description',
+ 'description' => 'Show title and description',
+ 'default_value' => array(array('value' => 0)),
+ 'entity_type' => 'node',
+ 'bundle' => 'media_gallery',
+ 'widget' => array(
+ 'type' => 'options_onoff',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'full' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ 'media_gallery_block' => array(
+ 'type' => 'hidden',
+ 'label' => 'hidden',
+ ),
+ ),
+ );
+ _media_gallery_ensure_field($field);
+ _media_gallery_ensure_instance($instance);
+
+}
+
+/**
+ * Fix stored display settings for the 'add_media_link' extra field to include 'weight'.
+ */
+function media_gallery_update_7007() {
+ // _field_extra_fields_pre_render() requires an explicit 'weight' value for
+ // all extra field settings stored in the database. Early versions of this
+ // module failed to include that.
+ $bundle_settings = field_bundle_settings('node', 'media_gallery');
+ if (isset($bundle_settings['extra_fields']['display']['add_media_link'])) {
+ foreach ($bundle_settings['extra_fields']['display']['add_media_link'] as $view_mode => &$settings) {
+ $settings += array('weight' => 1);
+ }
+ }
+ field_bundle_settings('node', 'media_gallery', $bundle_settings);
+}
+
+/**
+ * Ensure that the description fields for galleries and media allow filtered text.
+ */
+function media_gallery_update_7008() {
+ // Ensure that the media_description field is filtered text.
+ foreach (array('video', 'image') as $bundle) {
+ $instance = field_info_instance('media', 'media_description', $bundle);
+ if ($instance) {
+ $instance['settings']['text_processing'] = 1;
+ field_update_instance($instance);
+ }
+ }
+
+ // Ensure that the media_gallery_description field is filtered text.
+ $instance = field_info_instance('node', 'media_gallery_description', 'media_gallery');
+ if ($instance) {
+ $instance['settings']['text_processing'] = 1;
+ field_update_instance($instance);
+ }
+}
+
+/**
+ * Update old per-view-mode media_gallery_* field formatters to the generic media_gallery formatter with a setting.
+ */
+function media_gallery_update_7009() {
+ $instances = array();
+ $fields = field_read_fields(array('type' => 'media'), array('include_inactive' => TRUE));
+ foreach ($fields as $field) {
+ $instances = array_merge($instances, field_read_instances(array('field_id' => $field['id']), array('include_inactive' => TRUE)));
+ }
+ foreach ($instances as $instance) {
+ $update_instance = FALSE;
+ foreach ($instance['display'] as $view_mode => $display) {
+ if (in_array($display['type'], array('media_gallery_thumbnail', 'media_gallery_lightbox', 'media_gallery_detail', 'media_gallery_block_thumbnail', 'media_gallery_collection_thumbnail'))) {
+ $update_instance = TRUE;
+ $instance['display'][$view_mode]['type'] = 'media_gallery';
+ $instance['display'][$view_mode]['settings'] = array('file_view_mode' => $display['type']);
+ }
+ }
+ if ($update_instance) {
+ field_update_instance($instance);
+ }
+ }
+}
+
+/**
+ * Configure galleries to allow audio.
+ */
+function media_gallery_update_7010() {
+ drupal_load('module', 'field');
+ $bundle = 'audio';
+
+ // Enable audio for the node's media_gallery field.
+ if ($instance = field_info_instance('node', 'media_gallery_media', 'media_gallery')) {
+ $instance['widget']['settings']['allowed_types'][$bundle] = $bundle;
+ field_update_instance($instance);
+ }
+
+ // Enable gallery view modes for audio entities.
+ $bundle_settings = field_bundle_settings('file', $bundle);
+ foreach (array('media_gallery_thumbnail', 'media_gallery_lightbox', 'media_gallery_detail', 'media_gallery_block_thumbnail', 'media_gallery_collection_thumbnail') as $view_mode) {
+ $bundle_settings['view_modes'][$view_mode]['custom_settings'] = TRUE;
+ }
+ field_bundle_settings('file', $bundle, $bundle_settings);
+}
+
+/**
+ * Empty update function.
+ */
+function media_gallery_update_7011() {
+}
+
+/**
+ * Deprecated update function. Moved to 7200.
+ */
+function media_gallery_update_7012() {
+}
+
+/**
+ * Converts old media_gallery_media field to media_gallery_file field.
+ */
+function media_gallery_update_7200() {
+ // Create fields (but not instances yet) for media_gallery nodes and
+ // for the gallery collection vocabulary.
+ foreach (_media_gallery_controlled_fields() as $field) {
+ _media_gallery_ensure_field($field);
+ }
+
+ // Attach fields to the media gallery node type (including a term reference
+ // for the default collection).
+ foreach (_media_gallery_controlled_instances('node') as $instance) {
+ _media_gallery_ensure_instance($instance);
+ }
+
+ // Make sure all media bundles have the instances we expect.
+ _media_gallery_ensure_media_instances();
+
+ // Move content from media_gallery_media to media_gallery_file.
+ $result = db_query("SELECT nid FROM {node} WHERE type = 'media_gallery'");
+ foreach ($result as $record) {
+ $node = node_load($record->nid);
+ $change = FALSE;
+ // Check all languages.
+ foreach ($node->media_gallery_media as $langcode => $media) {
+ // Only update if the media_gallery_file field is not in use, but
+ // media_gallery_media is in use.
+ if (empty($node->media_gallery_file[$langcode]) && !empty($node->media_gallery_media[$langcode])) {
+ foreach ($media as $file) {
+ // Copy the file contents over.
+ $node->media_gallery_file[$langcode][] = array(
+ 'fid' => $file['fid'],
+ 'description' => $file['title'],
+ 'display' => 1,
+ );
+ $change = TRUE;
+ }
+ }
+ }
+
+ if ($change) {
+ // A change has occur, so we need to save the node.
+ node_save($node);
+ }
+ }
+
+ $instance = array(
+ 'field_name' => 'media_gallery_media',
+ 'entity_type' => 'node',
+ 'bundle' => 'media_gallery',
+ );
+ // Remove the old media_gallery_media field.
+ field_delete_instance($instance, TRUE);
+ field_delete_field($instance['field_name']);
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.js b/sites/all/modules/media_gallery/media_gallery.js
new file mode 100644
index 000000000..d9572aff8
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.js
@@ -0,0 +1,15 @@
+(function ($) {
+
+Drupal.behaviors.media_gallery = {};
+
+Drupal.behaviors.media_gallery.attach = function (context, settings) {
+ $(window).bind('media_youtube_load', Drupal.media_gallery.handleMediaYoutubeLoad);
+};
+
+Drupal.media_gallery = {};
+
+Drupal.media_gallery.handleMediaYoutubeLoad = function (event, videoSettings) {
+ $('.media-gallery-detail').width(videoSettings.width + 'px');
+};
+
+})(jQuery); \ No newline at end of file
diff --git a/sites/all/modules/media_gallery/media_gallery.module b/sites/all/modules/media_gallery/media_gallery.module
new file mode 100644
index 000000000..ac79579d1
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.module
@@ -0,0 +1,1905 @@
+<?php
+
+/**
+ * @file
+ * Lets users create galleries made up of media items.
+ */
+
+require_once(dirname(__FILE__) . '/media_gallery.fields.inc');
+require_once(dirname(__FILE__) . '/fields_rsi_prevention.inc');
+
+/**
+ * The pager element to use for paging through the media items in a gallery.
+ *
+ * We avoid using the default pager for now because there is too much risk of
+ * it colliding with other pagers initialized for the same page (for example,
+ * by the comment module, if the gallery node manages to get comment loading or
+ * display functions called on it).
+ */
+define('MEDIA_GALLERY_PAGER_ELEMENT', 1);
+
+/**
+ * Helper function to return the view modes used by this module for displaying files in gallery context.
+ */
+function media_gallery_file_view_modes() {
+ return array(
+ 'media_gallery_thumbnail' => t('Gallery thumbnail'),
+ 'media_gallery_lightbox' => t('Gallery lightbox'),
+ 'media_gallery_detail' => t('Gallery detail'),
+ 'media_gallery_block_thumbnail' => t('Gallery block thumbnail'),
+ 'media_gallery_collection_thumbnail' => t('Gallery collection thumbnail'),
+ );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function media_gallery_menu() {
+ $items['admin/config/media/galleries'] = array(
+ 'title' => 'Gallery settings',
+ 'description' => 'Configure settings for the "All galleries" page.',
+ 'access arguments' => array('administer media galleries'),
+ 'page callback' => 'media_gallery_admin_settings',
+ 'file' => 'media_gallery.admin.inc',
+ );
+ $items['media-gallery/sort/collection/%taxonomy_term/%'] = array(
+ 'title' => 'Gallery sort callback',
+ 'access callback' => 'media_gallery_edit_access_ajax',
+ 'access arguments' => array('collection', 3, 4),
+ 'page callback' => 'media_gallery_ajax_sort',
+ 'page arguments' => array('collection', 3),
+ 'file' => 'media_gallery.pages.inc',
+ );
+ $items['media-gallery/sort/gallery/%node/%'] = array(
+ 'title' => 'Gallery sort callback',
+ 'access callback' => 'media_gallery_edit_access_ajax',
+ 'access arguments' => array('gallery', 3, 4),
+ 'page callback' => 'media_gallery_ajax_sort',
+ 'page arguments' => array('gallery', 3),
+ 'file' => 'media_gallery.pages.inc',
+ );
+ $items['media-gallery/detail/%media_gallery_mg_node/%media_gallery_mg_item'] = array(
+ 'page callback' => 'media_gallery_detail_page',
+ 'page arguments' => array(2, 3),
+ 'access callback' => 'media_gallery_view_item_access',
+ 'access arguments' => array(2, 3),
+ 'load arguments' => array(2),
+ 'file' => 'media_gallery.pages.inc',
+ );
+ $items['media-gallery/detail/%media_gallery_mg_node/%media_gallery_mg_item/view'] = array(
+ 'title' => 'View',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ 'load arguments' => array(2),
+ );
+ // An in-gallery-context version of media/%file/edit.
+ $items['media-gallery/detail/%media_gallery_mg_node/%media_gallery_mg_item/edit'] = array(
+ 'title' => 'Edit file info',
+ 'page callback' => 'media_gallery_media_page_edit',
+ 'page arguments' => array(2, 3),
+ 'access callback' => 'media_gallery_edit_item_access',
+ 'access arguments' => array(2, 3),
+ 'load arguments' => array(2),
+ 'weight' => 0,
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ 'file' => 'media_gallery.pages.inc',
+ );
+ $items['media-gallery/detail/%media_gallery_mg_node/%media_gallery_mg_item/remove'] = array(
+ 'title' => 'Remove from gallery',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('media_gallery_remove_item_form', 2, 3),
+ 'access callback' => 'media_gallery_remove_item_access',
+ 'access arguments' => array(2, 3),
+ 'load arguments' => array(2),
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ 'file' => 'media_gallery.pages.inc',
+ );
+ $items['media-gallery/lightbox/%media_gallery_mg_node/%media_gallery_mg_item'] = array(
+ 'page callback' => 'media_gallery_lightbox_page',
+ 'page arguments' => array(2, 3),
+ 'access callback' => 'media_gallery_view_item_access',
+ 'access arguments' => array(2, 3),
+ 'load arguments' => array(2),
+ 'file' => 'media_gallery.pages.inc',
+ 'delivery callback' => 'media_gallery_lightbox_delivery_callback',
+ );
+ $items['media-gallery/add-images/%node/%'] = array(
+ 'access callback' => 'media_gallery_edit_access_ajax',
+ 'access arguments' => array('gallery', 2, 3),
+ 'page callback' => 'media_gallery_add_images',
+ 'page arguments' => array(2),
+ 'file' => 'media_gallery.pages.inc',
+ );
+ // An in-gallery-context version of media/%media_multi/edit.
+ $items['node/%node/multiedit'] = array(
+ 'title' => 'Edit media',
+ 'page callback' => 'media_gallery_media_page_multiedit',
+ 'page arguments' => array(1),
+ 'access callback' => 'media_gallery_multiedit_access',
+ 'access arguments' => array(1),
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'media_gallery.pages.inc',
+ );
+
+ // @todo Move to Media module once it is ready.
+ $items['media/%file/download'] = array(
+ 'title' => 'Download',
+ 'page callback' => 'media_download',
+ 'page arguments' => array(1),
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('view', 1),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'media.pages.inc',
+ );
+ return $items;
+}
+
+/**
+ * Load a node object from the database.
+ *
+ * @see node_load()
+ * @see media_gallery_menu()
+ *
+ * @param $nid
+ * The node ID.
+ * @param $nid2
+ * The same as $nid. Unused.
+ *
+ * @return
+ * A fully-populated node object, or FALSE if the node is not found.
+ */
+function media_gallery_mg_node_load($nid, $nid2 = NULL) {
+ return node_load($nid);
+}
+/**
+ * Load a file object from the database, if it is part of the media_gallery
+ * node.
+ *
+ * @see node_load()
+ * @see file_load()
+ * @see media_gallery_menu()
+ *
+ * @param $fid
+ * The file ID.
+ * @param $nid
+ * The media_gallery node ID.
+ *
+ * @return
+ * A fully-populated file object, or FALSE if the file is not part of the
+ * media_gallery.
+ */
+function media_gallery_mg_item_load($fid, $nid) {
+ $node = node_load($nid);
+ if ($node && $node->type == 'media_gallery' && in_array($fid, media_gallery_get_file_ids($node))) {
+ return file_load($fid);
+ }
+ return FALSE;
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function media_gallery_menu_alter(&$items) {
+ // Take over taxonomy term list pages by substituting our own callback.
+ // TODO: Use hook_entity_info_alter() to change the entity uri callback for
+ // gallery collections only.
+ $items['taxonomy/term/%taxonomy_term']['page callback'] = 'media_gallery_list_galleries';
+ $items['taxonomy/term/%taxonomy_term']['file'] = 'media_gallery.pages.inc';
+ $items['taxonomy/term/%taxonomy_term']['module'] = 'media_gallery';
+
+ // If you're viewing a media item in context somewhere (which we do inside
+ // media gallery nodes), that means it's being used on your site, which means
+ // you won't be allowed to delete it anyway. Therefore, do not show
+ // contextual links there.
+ // @todo Perhaps this should be changed in the media module itself?
+ $items['media/%file/delete']['context'] = MENU_CONTEXT_PAGE;
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function media_gallery_admin_paths() {
+ $paths = array(
+ 'media-gallery/detail/*/*/edit' => TRUE,
+ 'media-gallery/detail/*/*/remove' => TRUE,
+ 'node/*/multiedit' => TRUE,
+ );
+ return $paths;
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ */
+function media_gallery_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ // Rename the "Edit" tab on gallery nodes to "Edit gallery".
+ if (($node = menu_get_object()) && isset($node->type) && $node->type == 'media_gallery' && !empty($data['tabs'])) {
+ $tabs = &$data['tabs'][0]['output'];
+ foreach ($tabs as &$tab) {
+ if (isset($tab['#link']['path']) && $tab['#link']['path'] == 'node/%/edit') {
+ $tab['#link']['title'] = t('Edit gallery');
+ }
+ }
+ }
+
+ // Rename the "Edit" tab on the "All Galleries" taxonomy term to "Edit all
+ // galleries" and point it to our configuration page.
+ // @todo: Once we have additional gallery-related taxonomy terms and
+ // http://drupal.org/node/678592 is committed to core (so the term edit
+ // pages show the correct admin theme) we'll do something different here,
+ // perhaps not even alter anything at all.
+ if (($term = menu_get_object('taxonomy_term', 2)) && isset($term->vid) && $term->vid == variable_get('media_gallery_collection_vid')) {
+ $tabs = &$data['tabs'][0]['output'];
+ foreach ($tabs as &$tab) {
+ if (isset($tab['#link']['path']) && $tab['#link']['path'] == 'taxonomy/term/%/edit') {
+ $tab['#link']['href'] = 'admin/config/media/galleries';
+ $tab['#link']['title'] = t('Edit all galleries');
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_node_load().
+ */
+function media_gallery_node_load($nodes, $types) {
+ // Store a copy of the media_gallery_file field before mucking with it in
+ // media_gallery_view(). We use hook_node_load() instead of hook_load(),
+ // because the latter runs before fields are loaded.
+ if (in_array('media_gallery', $types)) {
+ foreach ($nodes as $node) {
+ if ($node->type == 'media_gallery') {
+ $node->media_gallery_file_original = $node->media_gallery_file;
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_file_delete().
+ *
+ * When an image is deleted, remove it from media_gallery nodes first.
+ */
+function media_gallery_file_delete($file) {
+ $query = new EntityFieldQuery();
+ $result = $query->entityCondition('entity_type', 'node')
+ ->fieldCondition('media_gallery_file', 'fid', $file->fid, '=')
+ ->execute();
+
+ if (isset($result['node'])) {
+ $nids = array_keys($result['node']);
+ $nodes = entity_load('node', $nids);
+ foreach ($nodes as $node) {
+ media_gallery_remove_item_from_gallery($node, $file);
+ }
+ }
+}
+
+/**
+ * Implements hook_view().
+ */
+function media_gallery_view($node, $view_mode) {
+ // Add display elements and resources for users who can edit the gallery.
+ if (node_access('update', $node)) {
+ // Add the "Add images" link, themed as a local action. Note that this
+ // element is listed in hook_field_extra_fields(), so whether or not it
+ // will *actually* be displayed in the current view mode depends on the
+ // site's configuration for the corresponding pseudo-field.
+ $node->content['add_media_link'] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => array(
+ 'title' => t('Add media'),
+ 'href' => 'media/browser',
+ 'localized_options' => array(
+ 'query' => array('render' => 'media-popup'),
+ 'attributes' => array(
+ 'class' => array(
+ 'media-gallery-add',
+ 'launcher',
+ ),
+ ),
+ ),
+ ),
+ // @todo Drupal could really use a theme_menu_local_actions() function...
+ '#prefix' => '<ul class="field action-links">',
+ '#suffix' => '</ul>',
+ );
+
+ // Prevent the overlay module to open an additional dialog.
+ if (module_exists('overlay')) {
+ $node->content['add_media_link']['#link']['localized_options']['attributes']['class'][] = 'overlay-exclude';
+ }
+
+ // Enable the "Add media" link to launch the media browser.
+ $node->content['add_media_link']['#attached']['library'][] = array('media', 'media_browser');
+ $node->content['add_media_link']['#attached']['library'][] = array('media', 'media_browser_settings');
+ $node->content['add_media_link']['#attached']['js'][] = drupal_get_path('module', 'media_gallery') . '/media_gallery.addimage.js';
+
+ // These JS settings are used by the "add media" link but some are also
+ // shared by the drag-and-drop code below.
+ $instance = field_info_instance('node', 'media_gallery_file', $node->type);
+ $token = drupal_get_token('media_gallery');
+ $gallery_js_settings = array(
+ 'mediaGalleryAddImagesUrl' => url('media-gallery/add-images/' . $node->nid . '/' . $token),
+ 'mediaGallerySortGalleryUrl' => url('media-gallery/sort/gallery/' . $node->nid . '/' . $token),
+ 'mediaGalleryAllowedMediaTypes' => array_filter($instance['widget']['settings']['allowed_types']),
+ );
+
+ // When viewing the full node, add front-end resources for drag-and-drop
+ // sorting.
+ if ($view_mode == 'full') {
+ drupal_add_css(drupal_get_path('module', 'media_gallery') . '/media_gallery.dragdrop.css');
+ drupal_add_library('system', 'ui.sortable');
+ drupal_add_library('system', 'jquery.bbq');
+ drupal_add_js(drupal_get_path('module', 'media_gallery') . '/media_gallery.dragdrop.js');
+ drupal_add_js($gallery_js_settings, array('type' => 'setting'));
+ }
+ else {
+ // Otherwise, attach the setting to the "add media" link, as per above.
+ $node->content['add_media_link']['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => $gallery_js_settings,
+ );
+ }
+ }
+
+ // For the teaser, only the first thumbnail needs to be displayed, so remove the
+ // rest from the node's field data, so that the field formatter doesn't waste
+ // time building the render structure for items that won't be shown.
+ if ($view_mode == 'teaser') {
+ if (!empty($node->media_gallery_file[LANGUAGE_NONE])) {
+ $first_item = array_shift($node->media_gallery_file[LANGUAGE_NONE]);
+ if (file_entity_access('view', (object)$first_item)) {
+ $node->media_gallery_file[LANGUAGE_NONE] = array($first_item);
+ }
+ else {
+ $node->media_gallery_file[LANGUAGE_NONE] = array();
+ }
+ }
+ }
+ // For the full display, implement pagination.
+ elseif ($view_mode == 'full' || $view_mode == 'media_gallery_block') {
+ $full = $view_mode == 'full' ? TRUE : FALSE;
+ if (!empty($node->media_gallery_file)) {
+ $media = media_gallery_filter_media_access($node->media_gallery_file[LANGUAGE_NONE], 'view');
+ }
+ else {
+ $media = array();
+ }
+
+ // Only display the requested number of media items per page.
+ $columns = $full ? $node->media_gallery_columns[LANGUAGE_NONE][0]['value'] : $node->media_gallery_block_columns[LANGUAGE_NONE][0]['value'];
+ $rows = $full ? $node->media_gallery_rows[LANGUAGE_NONE][0]['value'] : $node->media_gallery_block_rows[LANGUAGE_NONE][0]['value'];
+ $number_per_page = $columns * $rows;
+ $deltas = array_keys($media);
+ $total = count($deltas);
+
+ // Initialize the pager and find out what page we are viewing.
+ $page = $full ? pager_default_initialize($total, $number_per_page, MEDIA_GALLERY_PAGER_ELEMENT) : 0;
+
+ // Deny access to all media items that aren't on this page.
+ $min_on_page = $number_per_page * $page;
+ $max_on_page = $number_per_page * ($page + 1) - 1;
+ $pre_links = array();
+ $post_links = array();
+ foreach ($deltas as $key => $delta) {
+ $item = $media[$delta];
+ $fid = _media_gallery_get_media_fid($item);
+ if ($key < $min_on_page) {
+ $pre_links[$key] = array(
+ 'title' => '',
+ 'href' => 'media-gallery/detail/' . $node->nid . '/' . $fid,
+ 'attributes' => array('class' => array('colorbox-supplemental-link pre')),
+ );
+ unset($media[$delta]);
+ }
+ elseif ($key > $max_on_page) {
+ $post_links[$key] = array(
+ 'title' => '',
+ 'href' => 'media-gallery/detail/' . $node->nid . '/' . $fid,
+ 'attributes' => array('class' => array('colorbox-supplemental-link post')),
+ );
+ unset($media[$delta]);
+ }
+ }
+
+ // Field rendering requires sequential deltas, so re-key.
+ // @todo Open a Drupal core issue about this.
+ if ($media) {
+ $node->media_gallery_file[LANGUAGE_NONE] = array_values($media);
+ }
+ else {
+ $node->media_gallery_file[LANGUAGE_NONE] = array();
+ }
+
+ // Create a set of dummy links to media items that don't appear on this
+ // page, so colorbox can link to them in the slideshow.
+ // @todo If the gallery contains 1000 media, then rendering each link takes
+ // extra server-side time, extra network time to transfer the markup, and
+ // extra client-side time to initialize the DOM. Performance can likely be
+ // significantly improved if we only send the fids to Drupal.settings, and
+ // add JavaScript code to make that information usable by Colorbox.
+ $node->content['colorbox_links_pre'] = array(
+ '#theme' => 'links',
+ '#links' => $pre_links,
+ '#weight' => -20,
+ '#attributes' => array('class' => array('colorbox-supplemental-links')),
+ );
+ $node->content['colorbox_links_post'] = array(
+ '#theme' => 'links',
+ '#links' => $post_links,
+ '#weight' => 20,
+ '#attributes' => array('class' => array('colorbox-supplemental-links')),
+ );
+ // Finally, display the pager, with a high weight so it appears at the
+ // bottom.
+ if ($full) {
+ $node->content['media_gallery_pager'] = array(
+ '#theme' => 'pager',
+ '#element' => MEDIA_GALLERY_PAGER_ELEMENT,
+ '#weight' => 2000,
+ );
+ }
+ }
+ return $node;
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function media_gallery_field_extra_fields() {
+ // Allow the "Add media" link to be sorted with respect to the actual media
+ // gallery fields.
+ $extra['node']['media_gallery'] = array(
+ 'display' => array(
+ 'add_media_link' => array(
+ 'label' => t('"Add media" link'),
+ 'weight' => 1,
+ ),
+ ),
+ );
+
+ return $extra;
+}
+
+/**
+ * Access callback for AJAX-based gallery editing operations.
+ *
+ * @param string $type
+ * The type of item being edited: 'gallery' for an individual gallery or
+ * 'collection' for a gallery collection.
+ * @param mixed $item
+ * For a media gallery, the $node object for that gallery; for gallery
+ * collections, the taxonomy term corresponding to the collection.
+ * @param string $token
+ * A token from drupal_get_token.
+ */
+function media_gallery_edit_access_ajax($type, $item, $token) {
+ if (!drupal_valid_token($token, 'media_gallery')) {
+ return FALSE;
+ }
+ switch ($type) {
+ case 'gallery':
+ return node_access('update', $item);
+ break;
+ case 'collection':
+ return user_access('administer media galleries');
+ break;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function media_gallery_theme() {
+ return array(
+ 'media_gallery_collection' => array(
+ 'render element' => 'element',
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_teaser' => array(
+ 'render element' => 'element',
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_media_item_thumbnail' => array(
+ 'render element' => 'element',
+ 'file' => 'media_gallery.theme.inc',
+ 'template' => 'media-gallery-media-item-thumbnail',
+ ),
+ 'media_gallery_media_item_lightbox' => array(
+ 'render element' => 'element',
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_media_item_detail' => array(
+ 'render element' => 'element',
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_block_thumbnail' => array(
+ 'render element' => 'element',
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_collection_thumbnail' => array(
+ 'render element' => 'element',
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_download_link' => array(
+ 'variables' => array('file' => NULL, 'text' => NULL, 'options' => array()),
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_meta' => array(
+ 'variables' => array('display' => NULL, 'location' => NULL, 'title' => NULL, 'license' => NULL, 'description' => NULL),
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_item' => array(
+ 'variables' => array('image' => NULL, 'link_path' => NULL, 'classes' => NULL),
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_license' => array(
+ 'variables' => array('element' => NULL, 'color' => 'dark'),
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ 'media_gallery_file_field_inline' => array(
+ 'render element' => 'element',
+ 'file' => 'media_gallery.theme.inc',
+ ),
+ );
+}
+
+/**
+ * Implements hook_permission().
+ */
+function media_gallery_permission() {
+ return array(
+ 'administer media galleries' => array(
+ 'title' => t('Administer media galleries'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_node_info().
+ */
+function media_gallery_node_info() {
+ return array(
+ 'media_gallery' => array(
+ 'name' => t('Gallery'),
+ 'base' => 'media_gallery',
+ 'description' => t('A flexible gallery of media.'),
+ 'help' => t('Create a gallery of thumbnails including custom display settings. Once your gallery is saved, your media can be added.'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_update().
+ */
+function media_gallery_update($node) {
+ // If the media gallery node is being saved and is configured to not provide
+ // a block, remove all blocks associated with it from the database. The block
+ // module might not be installed, so we need to check that the database table
+ // exists before querying it.
+ if (empty($node->media_gallery_expose_block[LANGUAGE_NONE][0]['value']) && db_table_exists('block')) {
+ db_delete('block')
+ ->condition('module', 'media_gallery')
+ ->condition('delta', $node->nid)
+ ->execute();
+ }
+}
+
+/**
+ * Implements hook_block_info().
+ */
+function media_gallery_block_info() {
+ $blocks = array();
+
+ // Define a block for each media gallery node that is configured to expose
+ // one.
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'node');
+ $query->entityCondition('bundle', 'media_gallery');
+ $query->fieldCondition('media_gallery_expose_block', 'value', 1);
+ $result = $query->execute();
+ if (!empty($result['node'])) {
+ // There is no reason to waste going through a full node_load_multiple()
+ // when we only need the titles.
+ $nids = array_keys($result['node']);
+ $node_titles = db_query("SELECT nid, title FROM {node} WHERE nid IN (:nids)", array(':nids' => $nids))->fetchAllKeyed();
+ foreach ($node_titles as $nid => $title) {
+ // The 'info' element is escaped on display, so we pass it through
+ // unfiltered here.
+ $blocks[$nid]['info'] = t('Recent gallery items: !title', array('!title' => $title));
+ $blocks[$nid]['visibility'] = 0;
+ $blocks[$nid]['pages'] = 'node/' . $nid . "\ngalleries";
+ }
+ }
+
+ return $blocks;
+}
+
+/**
+ * Implements hook_block_view().
+ */
+function media_gallery_block_view($delta = '') {
+ $node = node_load($delta);
+ if (empty($node->media_gallery_expose_block[LANGUAGE_NONE][0]['value'])) {
+ // Bail out now if the node doesn't exist or if it is configured not to
+ // display a block.
+ $block['subject'] = NULL;
+ $block['content'] = '';
+ return $block;
+ }
+
+ // Collect an array of file IDs associated with this gallery. For
+ // simplicity we will assume (here and below) that this is not a
+ // multilingual field. Also note that the node may have been loaded and
+ // viewed elsewhere on the page, in which case the 'media_gallery_file'
+ // field was modified and does not contain what we want, so we have to go
+ // back to the original field value set in hook_node_load() instead, and
+ // also clone the node before changing it so our modifications do not
+ // affect other places where it might be being viewed.
+ $node = clone $node;
+ $node->media_gallery_file = $node->media_gallery_file_original;
+ $files = &$node->media_gallery_file[LANGUAGE_NONE];
+ $files = media_gallery_filter_media_access($files, 'view');
+ if (empty($files)) {
+ // Bail out now if there won't be any media items to show.
+ $block['subject'] = check_plain($node->title);
+ $block['content'] = t('No content available.');
+ return $block;
+ }
+
+ $gallery_fids = array();
+ foreach ($files as $file) {
+ $gallery_fids[] = _media_gallery_get_media_fid($file);
+ }
+ // Construct a list of file IDs that is limited to the specified number of
+ // items and ordered by most recent; these are the files that will be
+ // displayed in the block.
+ $columns = !empty($node->media_gallery_block_columns[LANGUAGE_NONE][0]['value']) ? $node->media_gallery_block_columns[LANGUAGE_NONE][0]['value'] : 1;
+ $rows = !empty($node->media_gallery_block_rows[LANGUAGE_NONE][0]['value']) ? $node->media_gallery_block_rows[LANGUAGE_NONE][0]['value'] : 1;
+ $number_to_show = $columns * $rows;
+ $block_fids = db_select('file_managed', 'f')
+ ->fields('f', array('fid'))
+ ->condition('fid', $gallery_fids, 'IN')
+ ->orderBy('timestamp', 'DESC')
+ ->range(0, $number_to_show)
+ ->execute()
+ ->fetchCol();
+ // Before sorting, remove any items that will not display in the block.
+ $fid_order = array_flip($block_fids);
+ if ($number_to_show < count($files)) {
+ foreach ($files as $key => $file) {
+ if (!isset($fid_order[_media_gallery_get_media_fid($file)])) {
+ unset($files[$key]);
+ }
+ }
+ }
+ // Prepare the sorting function with the list of file ID orders.
+ _media_gallery_sort_by_recent(NULL, NULL, $fid_order);
+ // Perform the sort.
+ usort($files, '_media_gallery_sort_by_recent');
+ // Display the block.
+ $block['subject'] = check_plain($node->title);
+ $block['content']['gallery'] = node_view($node, 'media_gallery_block');
+ // Move the node's contextual links so that they display on the block
+ // rather than the node (i.e., in the same dropdown as the "Configure
+ // block" link). This is also required in order to properly integrate with
+ // the code in media_gallery_contextual_links_view_alter().
+ if (isset($block['content']['gallery']['#contextual_links'])) {
+ $block['content']['#contextual_links'] = $block['content']['gallery']['#contextual_links'];
+ unset($block['content']['gallery']['#contextual_links']);
+ }
+ $block['content']['more_link'] = array(
+ '#theme' => 'more_link',
+ '#url' => 'node/' . $node->nid,
+ '#title' => t('Show the complete gallery'),
+ '#weight' => 1000,
+ );
+
+ return $block;
+}
+
+/**
+ * Implements hook_block_configure().
+ */
+function media_gallery_block_configure($delta = '') {
+ // Duplicate the form for configuring media gallery node fields, including
+ // our module's custom alterations. We can't use drupal_get_form() here
+ // because that will mess up the form submission, so for now we have to live
+ // without getting any other module's alterations to this part of the node
+ // form.
+ $node = node_load($delta);
+ $form = array();
+ $form_state = array();
+ field_attach_form('node', $node, $form, $form_state, $node->language);
+ media_gallery_form_media_gallery_node_form_alter($form, $form_state);
+
+ // Pull out only the parts of the node form that allow configuration of the
+ // block settings; these are the ones we want to display.
+ $block_settings = array('block' => $form['block']);
+
+ // Store a record of all node fields that we will need to save when
+ // hook_block_save() is called.
+ $block_settings['block']['media_gallery_block_fields'] = array(
+ '#type' => 'value',
+ '#value' => element_children($block_settings['block']),
+ );
+
+ // Don't allow people to destroy the block itself from the block
+ // configuration page.
+ $block_settings['block']['media_gallery_expose_block']['#access'] = FALSE;
+
+ // Customize the fieldset display.
+ $block_settings['block']['#collapsible'] = FALSE;
+ $block_settings['block']['#title'] = t('Block settings');
+ unset($block_settings['block']['#weight']);
+
+ // Add the necessary attached JS and CSS.
+ _media_gallery_attach_form_resources($block_settings['block']);
+
+ return $block_settings;
+}
+
+/**
+ * Implements hook_block_save().
+ */
+function media_gallery_block_save($delta = '', $edit = array()) {
+ // Save the block-related media gallery fields on the node.
+ $node = node_load($delta);
+ foreach ($edit['media_gallery_block_fields'] as $field) {
+ $node->{$field} = $edit[$field];
+ }
+ node_save($node);
+}
+
+/**
+ * Implements hook_library().
+ */
+function media_gallery_library() {
+ $colorbox_path = variable_get('media_gallery_library_path', FALSE);
+ if ($colorbox_path === FALSE ) {
+ $colorbox_path = module_exists('libraries') ? libraries_get_path('colorbox') : 'sites/all/libraries/colorbox';
+ }
+ else {
+ $colorbox_path .= '/colorbox';
+ }
+ $stylesheet = variable_get('media_gallery_colorbox_stylesheet', 'example1');
+ $libraries['colorbox'] = array(
+ 'title' => 'Colorbox',
+ 'website' => 'http://colorpowered.com/colorbox/',
+ 'version' => '1.3.25',
+ 'js' => array(
+ $colorbox_path . '/jquery.colorbox-min.js' => array(),
+ ),
+ 'css' => array(
+ $colorbox_path . '/' . $stylesheet . '/colorbox.css' => array(
+ 'type' => 'file',
+ 'media' => 'screen',
+ ),
+ ),
+ );
+ return $libraries;
+}
+
+/**
+ * Helper function to sort media gallery items by an ordered list of file IDs.
+ *
+ * Call once with $set_fid_order set to an array of file orders, keyed by the
+ * file ID, before performing the actual sort.
+ */
+function _media_gallery_sort_by_recent($a, $b, $set_fid_order = NULL) {
+ $fid_order = &drupal_static(__FUNCTION__, array());
+ // Stored the ordered list if this is a preparatory call.
+ if (isset($set_fid_order)) {
+ $fid_order = $set_fid_order;
+ return;
+ }
+ // Otherwise, perform the sort.
+ $a_order = $fid_order[_media_gallery_get_media_fid($a)];
+ $b_order = $fid_order[_media_gallery_get_media_fid($b)];
+ if ($a_order < $b_order) {
+ return -1;
+ }
+ elseif ($b_order < $a_order) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+/**
+ * Returns the number of files of each type attached to a media gallery node.
+ */
+function media_gallery_get_media_type_count($node, $media_field = 'media_gallery_file') {
+ $fids = media_gallery_get_file_ids($node, $media_field);
+ if (empty($fids)) {
+ return array();
+ }
+ $query = db_select('file_managed', 'f');
+ $type = $query->addField('f', 'type');
+ $query->addExpression('COUNT(*)');
+ $type_count = $query->condition('f.fid', $fids, 'IN')
+ ->groupBy($type)
+ ->execute()
+ ->fetchAllKeyed();
+ return $type_count;
+}
+
+/**
+ * Returns all file IDs attached to a media gallery node.
+ */
+function media_gallery_get_file_ids($node, $media_field = 'media_gallery_file') {
+ $fids = array();
+ $media_items = _media_gallery_field_get_items('node', $node, $media_field);
+ if ($media_items !== FALSE) {
+ foreach ($media_items as $item) {
+ $fids[] = _media_gallery_get_media_fid($item);
+ }
+ }
+ return drupal_map_assoc($fids);
+}
+
+/**
+ * media_gallery_file_original is not a field and therefore cannot be used by field_get_items.
+ */
+function _media_gallery_field_get_items($entity_type, $entity, $field_name, $langcode = NULL) {
+ if ($entity_type == 'node' && $field_name == 'media_gallery_file_original') {
+ $media = $entity->media_gallery_file;
+ $entity->media_gallery_file = $entity->media_gallery_file_original;
+ $field_name = 'media_gallery_file';
+ }
+ $items = field_get_items($entity_type, $entity, $field_name, $langcode);
+ if (isset($media)) {
+ $entity->media_gallery_file = $media;
+ }
+ return $items;
+}
+
+/**
+ * Determines the file ID from a media file array or object.
+ *
+ * This is ugly, but necessary since a media field attached to a node may
+ * be represented either as an array or a full object, depending on whether the
+ * node has been processed for viewing yet or not.
+ *
+ * @param $file
+ * Either a media file object or media file array.
+ *
+ * @return
+ * The value of the 'fid' object property or array key.
+ */
+function _media_gallery_get_media_fid($file) {
+ return is_object($file) ? $file->fid : $file['fid'];
+}
+
+/**
+ * Removes a media item from a gallery.
+ *
+ * @param $node
+ * The gallery node object.
+ * @param $file
+ * The file to remove from the gallery.
+ *
+ * @return
+ * The updated gallery node object.
+ */
+function media_gallery_remove_item_from_gallery($node, $file) {
+ $items = &$node->media_gallery_file[LANGUAGE_NONE];
+ foreach ($items as $key => $item) {
+ if ($file->fid == _media_gallery_get_media_fid($item)) {
+ unset($items[$key]);
+ }
+ }
+ node_save($node);
+ return $node;
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function media_gallery_entity_info_alter(&$entity_info) {
+ // Add view modes for displaying files in gallery contexts.
+ foreach (media_gallery_file_view_modes() as $view_mode => $label) {
+ $entity_info['file']['view modes'][$view_mode] = array('label' => $label, 'custom settings' => FALSE);
+ }
+
+ // Add a view mode for media_gallery_block_view() to use for displaying a
+ // media gallery node in a block. Drupal does not support restricting view
+ // modes to specific bundles.
+ $entity_info['node']['view modes']['media_gallery_block'] = array('label' => t('Media gallery block'), 'custom settings' => FALSE);
+}
+
+/**
+ * Implements hook_form().
+ */
+function media_gallery_form($node, $form_state) {
+ $form = node_content_form($node, $form_state);
+ return $form;
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function media_gallery_form_alter(&$form, &$form_state, $form_id) {
+ // $form_id = 'media_edit_*' by using multiple file editing
+ // $form_id = 'file_entity_edit' by using regular file edit
+ if (strpos($form_id, 'media_edit') === 0 || strpos($form_id, 'file_entity_edit')) {
+ // Act on both the regular and multiform versions of the edit form.
+ if ($form_id === 'file_entity_edit' || preg_match('/^media_edit_[0-9]+$/', $form_id)) {
+ // Prepopulate the file_entity_edit form with our best guess at the image title.
+ if (!empty($form['media_title']) && empty($form['media_title'][LANGUAGE_NONE][0]['value']['#default_value'])) {
+ $fid = $form['fid']['#value'];
+ $file = file_load($fid);
+ if ($file->type === 'image') {
+ $form['media_title'][LANGUAGE_NONE][0]['value']['#default_value'] = _media_gallery_get_media_title($file);
+ }
+ }
+ // Prepopulate the license field with the correct default.
+ if ($form['field_license'][LANGUAGE_NONE]['#default_value'] == '_none') {
+ $form['field_license'][LANGUAGE_NONE]['#default_value'] = 'none';
+ }
+ unset($form['field_license'][LANGUAGE_NONE]['#options']['_none']);
+ }
+ // Attach JavaScript and CSS needed to alter elements in the form.
+ _media_gallery_attach_edit_resources($form);
+ }
+}
+
+/*
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_gallery_form_media_gallery_node_form_alter(&$form, &$form_state) {
+ _media_gallery_attach_form_resources($form);
+
+ // The UI for the multi value media field and the node weight is elsewhere.
+ $form['media_gallery_file']['#access'] = FALSE;
+ $form['media_gallery_weight']['#access'] = FALSE;
+
+ // Hiding this field because we only support a single collection at the moment.
+ $form['media_gallery_collection']['#access'] = FALSE;
+
+ // Wrap a fieldset around the gallery settings.
+ $form['settings_wrapper'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Gallery settings'),
+ '#weight' => 10,
+ );
+
+ unset($form['media_gallery_lightbox_extras'][LANGUAGE_NONE]['#description']);
+ $form['media_gallery_lightbox_extras'][LANGUAGE_NONE]['#label'] = 'Show title and description';
+
+ // These are the items that need to be added to the fieldset. The array
+ // values represent a subgroup within the fieldset array that the items are
+ // further grouped by.
+ $fieldset = array(
+ 'media_gallery_columns' => 'gallery',
+ 'media_gallery_rows' => 'gallery',
+ 'media_gallery_image_info_where' => 'gallery',
+ 'media_gallery_allow_download' => 'presentation',
+ 'media_gallery_format' => 'presentation',
+ 'media_gallery_lightbox_extras' => 'presentation',
+ );
+
+ // Move the items to the fieldset.
+ foreach ($fieldset as $id => $subgroup) {
+ $form['settings_wrapper'][$subgroup][$id] = $form[$id];
+ // locale_field_node_form_submit() looks for field language information in
+ // a hard-coded part of $form.
+ // @todo Other modules may as well, so would be best to move form elements
+ // within #pre_render rather than in hook_form_alter().
+ $form[$id] = array('#language' => $form[$id]['#language']);
+ }
+
+ // Add a vertical tab menu for blocks
+ $form['block'] = array(
+ '#type' => 'fieldset',
+ '#title' => 'Blocks',
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ '#group' => 'additional_settings',
+ '#attached' => array(),
+ '#weight' => -100,
+ );
+
+ $fieldset = array(
+ 'media_gallery_expose_block' => 'block',
+ 'media_gallery_block_columns' => 'block',
+ 'media_gallery_block_rows' => 'block',
+ );
+
+ // Move the items to the fieldset.
+ foreach ($fieldset as $id => $subgroup) {
+ $form[$subgroup][$id] = $form[$id];
+ // locale_field_node_form_submit() looks for field language information in
+ // a hard-coded part of $form.
+ // @todo Other modules may as well, so would be best to move form elements
+ // within #pre_render rather than in hook_form_alter().
+ $form[$id] = array('#language' => $form[$id]['#language']);
+ }
+
+ // Add a class to the fieldset to target it in the js
+ $form['block']['#attributes']['class'] = array('block-form');
+
+ // Add classes where necessary for JS enhancement.
+ $form['settings_wrapper']['gallery']['media_gallery_image_info_where']['#attributes']['class'][] = 'form-inline label';
+ $form['settings_wrapper']['gallery']['media_gallery_image_info']['#attributes']['class'][] = 'form-inline';
+
+ // Use #prefix and #suffix to group the fields.
+ $form['settings_wrapper']['presentation']['media_gallery_format']['#attributes']['class'][] = 'media-gallery-show no-group-label';
+ $form['settings_wrapper']['gallery']['#prefix'] = '<div class="gallery-settings settings-group hidden clearfix"><div class="setting-icon"></div><div class="no-overflow">';
+ $form['settings_wrapper']['gallery']['#suffix'] = '</div></div>';
+
+ $form['settings_wrapper']['presentation']['#prefix'] = '<div class="presentation-settings settings-group hidden clearfix"><div class="group-label">' . t('Presentation settings') . '</div><div class="setting-icon"></div><div class="no-overflow">';
+ $form['settings_wrapper']['presentation']['#suffix'] = '</div></div>';
+
+ // Enhance the "number of rows" textfields by adding a dropdown element.
+ $form['settings_wrapper']['gallery']['media_gallery_rows']['#process'][] = 'media_gallery_process_dropdown';
+ $form['settings_wrapper']['gallery']['media_gallery_rows']['#media_gallery_dropdown_options'] = array('1', '3', '5', '10', 'other');
+ $form['block']['media_gallery_block_rows']['#process'][] = 'media_gallery_process_dropdown';
+ $form['block']['media_gallery_block_rows']['#media_gallery_dropdown_options'] = array('1', '2', '3', '4', 'other');
+
+ // Adjust the weight of the fields in the presentation wrapper
+ $form['settings_wrapper']['presentation']['media_gallery_allow_download']['#weight'] = 0;
+
+ // @todo At some point, it would be nice to have a functional preview display
+ // of gallery nodes, but until happens, remove the Preview button.
+ $form['actions']['preview']['#access'] = FALSE;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_gallery_form_file_entity_edit_alter(&$form, &$form_state) {
+ // Adjust the file entity edit form when it is shown within a gallery context.
+ if (isset($form_state['media_gallery']['gallery'])) {
+ // Remove the Delete button, since media entities can't be deleted when they
+ // are in use.
+ $form['actions']['delete']['#access'] = FALSE;
+
+ // Instead, provide a "Remove" checkbox to let users remove the item from
+ // the gallery.
+ _media_gallery_add_remove_checkbox($form, $form_state, $form_state['media_gallery']['gallery']);
+
+ // Add a submit handler to alter $form_state['redirect'] to the
+ // in-gallery-context View page. It's annoying to have to add a submit
+ // handler for this, but see http://drupal.org/node/579366#comment-2099836.
+ // Make sure to add this for the form-level submit handlers and also for the
+ // button-level submit handlers of the "Save" button, in case those are
+ // being used.
+ $form['#submit'][] = 'media_gallery_file_entity_edit_submit';
+ if (isset($form['actions']['submit']['#submit'])) {
+ $form['actions']['submit']['#submit'][] = 'media_gallery_file_entity_edit_submit';
+ }
+ }
+ // On the media gallery multiedit page, add a "Remove" checkbox to each item.
+ elseif (($node = menu_get_object()) && arg(2) === 'multiedit' && $node->type === 'media_gallery') {
+ _media_gallery_add_remove_checkbox($form, $form_state, $node);
+ }
+}
+
+/**
+ * Add a "remove" checkbox to the media edit form.
+ */
+function _media_gallery_add_remove_checkbox(&$form, &$form_state, $node) {
+ // Keep a reference to the gallery this media item belongs to, so we know
+ // what to remove it from.
+ $form['#gallery'] = $node;
+
+ // Put the remove checkbox inside the "preview" section, so it shows up
+ // underneath the thumbnail.
+ // @todo: Move into $form['preview']['remove'] when issue
+ // http://drupal.org/node/1055986 get committed.
+ $form['remove'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remove from gallery'),
+ '#description' => t('The original file remains in your <a href="@library">media library</a>.', array('@library' => url('admin/content/file'))),
+ '#access' => node_access('update', $node),
+ );
+
+ // Add our own submit handler. We need to add it to both the form and the
+ // submit button to make sure it gets fired.
+ // Also note that this requires one call to node_save() per media item
+ // removed. A better approach, if it were possible, would be to add a submit
+ // handler to the entire multiform (see http://drupal.org/node/1033258).
+ $form['#submit'][] = 'media_gallery_multiedit_remove_item';
+ if (isset($form['actions']['submit']['#submit'])) {
+ $form['actions']['submit']['#submit'][] = 'media_gallery_multiedit_remove_item';
+ }
+}
+
+/**
+ * Form submit handler to remove a media item from a gallery.
+ */
+function media_gallery_multiedit_remove_item($form, &$form_state) {
+ if (isset($form['#gallery'])) {
+ $gallery = $form['#gallery'];
+ if (!empty($form_state['values']['remove'])) {
+ // Remove the item from the gallery.
+ $file = file_load($form_state['values']['fid']);
+ media_gallery_remove_item_from_gallery($gallery, $file);
+ }
+ // Redirect to the gallery page.
+ $uri = entity_uri('node', $gallery);
+ $form_state['redirect'] = $uri;
+ }
+}
+
+/**
+ * Form submit handler for file_entity edit form in gallery context.
+ *
+ * @see media_gallery_form_file_entity_edit_alter()
+ */
+function media_gallery_file_entity_edit_submit($form, &$form_state) {
+ $form_state['redirect'] = 'media-gallery/detail/' . $form_state['media_gallery']['gallery']->nid . '/' . $form_state['values']['fid'];
+}
+
+/**
+ * Implements hook_field_attach_form().
+ */
+function media_gallery_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
+ // Remove the ability for a user to select a license for externally hosted
+ // media.
+ if ($entity_type == 'file') {
+ $scheme = file_uri_scheme($entity->uri);
+ // @todo Implement a more generic determination for when it makes sense for
+ // a user to select a license and when it doesn't.
+ if ($scheme == 'youtube') {
+ $form['field_license']['#access'] = FALSE;
+ }
+ }
+}
+
+/**
+ * Helper function to attach JS/CSS for media galleries to a form element.
+ */
+function _media_gallery_attach_form_resources(&$element) {
+ $element['#attached']['js'][] = drupal_get_path('module', 'media_gallery') . '/media_gallery.form.js';
+ _media_gallery_attach_css_resources($element);
+}
+
+/**
+ * Helper function to attach CSS for media galleries to a render element.
+ */
+function _media_gallery_attach_css_resources(&$element) {
+ $path = drupal_get_path('module', 'media_gallery');
+ $element['#attached']['css'][] = $path . '/media_gallery.css';
+ $element['#attached']['css'][] = array(
+ 'data' => $path . '/media_gallery.ie7.css',
+ 'browsers' => array('IE' => 'lt IE 8', '!IE' => FALSE),
+ );
+}
+
+/**
+ * Helper function to attach JS for media galleries to
+ */
+function _media_gallery_attach_edit_resources(&$element) {
+ $path = drupal_get_path('module', 'media_gallery');
+ $element['#attached']['js'][] = $path . '/js/media_gallery.edit.js';
+ $element['#attached']['css'][] = $path . '/css/media_gallery.edit.css';
+}
+
+/**
+ * Implements hook_node_view_alter().
+ */
+function media_gallery_node_view_alter(&$build) {
+ // This is for the Galleries plural page
+ if ($build['#bundle'] == 'media_gallery' && $build['#view_mode'] == 'teaser') {
+ // Hide node links.
+ $build['links']['#access'] = FALSE;
+ unset($build['#contextual_links']);
+ _media_gallery_attach_css_resources($build);
+ }
+ // This is for when a gallery is being shown in a block
+ elseif ($build['#view_mode'] == 'media_gallery_block') {
+ // Hide node links.
+ $build['links']['#access'] = FALSE;
+ _media_gallery_attach_css_resources($build);
+ }
+ // This is for when a gallery is being shown on its own, single gallery page.
+ elseif ($build['#bundle'] == 'media_gallery' && $build['#view_mode'] == 'full') {
+ if (!empty($build['media_gallery_file'])) {
+ foreach (element_children($build['media_gallery_file']) as $delta) {
+ // For each media item, add contextual links to the in-gallery-context
+ // tasks that can be performed on a media item.
+ $fid = $build['media_gallery_file'][$delta]['#file']->fid;
+ $build['media_gallery_file'][$delta]['#contextual_links']['media_gallery'] = array('media-gallery/detail', array($build['#node']->nid, $fid));
+ }
+ }
+ _media_gallery_attach_css_resources($build);
+ }
+}
+
+/**
+ * Implements MODULE_preprocess_node().
+ */
+function media_gallery_preprocess_node(&$variables) {
+ // Do not show the title when a node is being displayed in a media gallery
+ // block.
+ if ($variables['view_mode'] == 'media_gallery_block') {
+ $variables['title'] = '';
+ }
+
+ // Gallery teasers (for example, the ones that appear on the Galleries page)
+ // require special theming of their content. We set that up here instead of as
+ // part of hook_node_view_alter() or similar, because we want the node itself
+ // to have #theme='node' as normal, and only want to add special theming for
+ // the node content, but the content element isn't created until
+ // template_preprocess_node().
+ if ($variables['node']->type == 'media_gallery' && $variables['view_mode'] == 'teaser') {
+ $variables['content']['#theme'] = 'media_gallery_teaser';
+ $variables['content']['#node'] = $variables['node'];
+ $variables['classes_array'][] = 'mg-gallery';
+ $variables['classes_array'][] = 'mg-teaser';
+ }
+}
+
+/**
+ * Implements MODULE_preprocess_menu_local_task().
+ */
+function media_gallery_preprocess_menu_local_task(&$variables) {
+ // Persist the "page" URL query parameter from the "view" tab to the
+ // "multiedit" tab for gallery nodes. In the future, we may want to expand
+ // this to cover more than just the "multiedit" tab. Since this code runs for
+ // every local task of every page, we try to determine no-op conditions as
+ // quickly as possible.
+ if (isset($_GET['page']) && !isset($variables['element']['#link']['localized_options']['query']['page']) && strpos($variables['element']['#link']['href'], 'node/') == 0) {
+ $nid = arg(1, $variables['element']['#link']['href']);
+ if (is_numeric($nid) && arg(2, $variables['element']['#link']['href']) == 'multiedit') {
+ $node = node_load($nid);
+ if ($node->type == 'media_gallery') {
+ $page = pager_find_page(MEDIA_GALLERY_PAGER_ELEMENT);
+ if (is_int($page)) {
+ $variables['element']['#link']['localized_options']['query']['page'] = "$page";
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_contextual_links_view_alter().
+ */
+function media_gallery_contextual_links_view_alter(&$element, $items) {
+ // Modify the contextual links on gallery blocks; we only want to allow
+ // editing the gallery and configuring the block, and we want a more
+ // descriptive title for the edit link.
+ if (isset($element['#element']['#block']->module) && $element['#element']['#block']->module == 'media_gallery' && !empty($element['#links'])) {
+ $links = &$element['#links'];
+ foreach ($links as $key => &$link) {
+ if ($key != 'node-edit' && $key != 'block-configure') {
+ unset($links[$key]);
+ }
+ elseif ($key == 'node-edit') {
+ $link['title'] = t('Edit gallery');
+ }
+ }
+ }
+ // Remove 'Edit' button provided by File Entity.
+ if (isset($element['#links']['media-gallery-edit']) && isset($element['#links']['file-edit'])) {
+ unset($element['#links']['file-edit']);
+ }
+ // Move 'Delete' button to the bottom of the list and modify label.
+ if (isset($element['#links']['media-gallery-edit']) && isset($element['#links']['file-delete'])) {
+ $delete = $element['#links']['file-delete'];
+ unset($element['#links']['file-delete']);
+ $element['#links']['file-delete'] = $delete;
+ $element['#links']['file-delete']['title'] = 'Delete file';
+ }
+}
+
+/**
+ * Preprocess function for theme_field().
+ */
+function media_gallery_preprocess_field(&$variables, $hook) {
+ if ($variables['element']['#field_name'] === 'media_gallery_file') {
+ $columns = 1;
+ switch ($variables['element']['#view_mode']) {
+ case 'media_gallery_block':
+ $columns = $variables['element']['#object']->media_gallery_block_columns[LANGUAGE_NONE][0]['value'];
+ break;
+ case 'full':
+ $columns = $variables['element']['#object']->media_gallery_columns[LANGUAGE_NONE][0]['value'];
+ $media_gallery_view_mode_css = 'media-gallery-view-full';
+ break;
+ }
+ // Don't add the columning classes if the field is being displayed in a teaser
+ $variables['classes_array'][] = ($variables['element']['#view_mode'] != 'teaser') ? 'clearfix media-gallery-media mg-col mg-col-' . $columns : 'clearfix media-gallery-media';
+ // add css to find draggalbe elements (see media_gallery.dragdrop.js)
+ if (!empty($media_gallery_view_mode_css)) {
+ $variables['classes_array'][] = $media_gallery_view_mode_css;
+ }
+ foreach ($variables['items'] as $delta => $item) {
+ $variables['item_attributes_array'][$delta] = array('id' => 'media-gallery-media-' . $delta);
+ }
+ }
+}
+
+/**
+ * Media gallery equivalent to taxonomy_select_nodes().
+ */
+function media_gallery_select_galleries($tid, $pager = TRUE, $limit = FALSE) {
+ if (!variable_get('taxonomy_maintain_index_table', TRUE)) {
+ return array();
+ }
+ $query = db_select('taxonomy_index', 't');
+ $query->leftJoin('media_gallery_weight', 'mgw', 'mgw.nid = t.nid AND mgw.tid = :tid', array(':tid' => $tid));
+ $query->addTag('node_access');
+ $query->condition('t.tid', $tid);
+ if ($pager) {
+ $count_query = clone $query;
+ $count_query->addExpression('COUNT(t.nid)');
+
+ $query = $query->extend('PagerDefault');
+ if ($limit !== FALSE) {
+ $query = $query->limit($limit);
+ }
+ $query->setCountQuery($count_query);
+ }
+ else {
+ if ($limit !== FALSE) {
+ $query->range(0, $limit);
+ }
+ }
+ $query->addField('t', 'nid');
+ $query->addField('t', 'tid');
+ $query->addField('mgw', 'weight');
+ $query->orderBy('mgw.weight', 'ASC');
+ $query->orderBy('t.nid', 'ASC');
+ $result = $query->execute();
+ return $result->fetchCol();
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Used to modify the taxonomy term edit screen for gallery collection settings.
+ */
+function media_gallery_form_taxonomy_form_term_alter(&$form, &$form_state) {
+ // Don't do anything unless this is a gallery collection edit form.
+ if (isset($form_state['confirm_delete']) || !isset($form['#vocabulary']) || $form['#vocabulary']->vid != variable_get('media_gallery_collection_vid')) {
+ return;
+ }
+
+ $form['introduction'] = array(
+ '#weight' => -10,
+ '#markup' => check_plain(t('The following settings affect the "All galleries" page, which shows a thumbnail of every gallery created.')),
+ );
+ $form['#attributes']['class'][] = 'form-media-gallery-collection';
+ $form['name']['#title'] = t('Title');
+ $form['path']['alias']['#title'] = check_plain(t('"All galleries" URL'));
+ $form['path']['alias']['#weight'] = -1;
+ $form['path']['alias']['#field_prefix'] = url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=');
+ unset($form['path']['alias']['#description']);
+ _media_gallery_attach_form_resources($form);
+
+ // These are the items that need to be added to the fieldset. The array
+ // values represent a subgroup within the fieldset array that the items are
+ // further grouped by.
+ $fieldset = array(
+ 'media_gallery_columns' => 'gallery',
+ 'media_gallery_rows' => 'gallery',
+ 'media_gallery_image_info_where' => 'gallery',
+ );
+
+ // Move the items to the fieldset.
+ foreach ($fieldset as $id => $subgroup) {
+ $form['settings_wrapper'][$subgroup][$id] = $form[$id];
+ unset($form[$id]);
+ }
+
+ $form['settings_wrapper']['gallery']['media_gallery_image_info_where']['#attributes']['class'][] = 'form-inline label';
+ // Use #prefix and #suffix to group the fields.
+ $form['settings_wrapper']['gallery']['#prefix'] = '<div class="galleries-settings settings-group hidden"><div class="group-label">' . check_plain(t('"All galleries" layout settings')) . '</div><div class="setting-icon"></div><div class="no-overflow">';
+ $form['settings_wrapper']['gallery']['#suffix'] = '</div></div>';
+
+ // Enhance the "number of rows" textfield by adding a dropdown element.
+ $form['settings_wrapper']['gallery']['media_gallery_rows']['#process'][] = 'media_gallery_process_dropdown';
+ $form['settings_wrapper']['gallery']['media_gallery_rows']['#media_gallery_dropdown_options'] = array('1', '3', '5', '10', 'other');
+
+ $form['relations']['#access'] = FALSE;
+ $form['actions']['delete']['#access'] = FALSE;
+ $form['field_license']['#access'] = FALSE;
+
+ // Add a submit handler to change the "Updated term" message on submit.
+ $form['#submit'][] = 'media_gallery_taxonomy_form_term_submit';
+}
+
+/**
+ * Submit handler for the taxonomy_form_term form.
+ */
+function media_gallery_taxonomy_form_term_submit($form, &$form_state) {
+ // Change the "Updated term Galleries" message into something that makes
+ // sense in this context.
+ $term_name = $form_state['values']['name'];
+ $messages = $_SESSION['messages']['status'];
+ $updated_message = t('Updated term %term.', array('%term' => $term_name));
+ foreach ($messages as $key => $message) {
+ if ($message === $updated_message) {
+ $_SESSION['messages']['status'][$key] = t('The gallery settings have been saved.');
+ }
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Used to hide the gallery_collections taxonomy admin screens.
+ */
+function media_gallery_form_taxonomy_overview_vocabularies_alter(&$form, &$form_state) {
+ $gallery_collection_vid = variable_get('media_gallery_collection_vid');
+ unset($form[$gallery_collection_vid]);
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function media_gallery_module_implements_alter(&$implementations, $hook) {
+ switch ($hook) {
+ // TODO: All we really need to control here is
+ // form_taxonomy_form_term_alter; if D7 gets fixed to allow that level of
+ // control, this can be changed.
+ //case 'form_taxonomy_form_term_alter':
+ case 'form_alter':
+ if (!isset($implementations['media_gallery'])) {
+ break;
+ }
+ $group = $implementations['media_gallery'];
+ unset($implementations['media_gallery']);
+ $implementations['media_gallery'] = $group;
+ break;
+ // We need to ensure that these hooks run before the corresponding Pathauto
+ // implementations. Given what they do, it's harmless to put them at the
+ // very front of the list, so we do that because it's easiest.
+ case 'taxonomy_term_insert':
+ case 'taxonomy_term_update':
+ if (isset($implementations['media_gallery'])) {
+ $implementations = array('media_gallery' => $implementations['media_gallery']) + $implementations;
+ }
+ break;
+ }
+}
+
+/**
+ * Gets the first term in the media_gallery_collection vocabulary
+ */
+function media_gallery_get_default_gallery_collection() {
+ $gallery_collection_vid = variable_get('media_gallery_collection_vid');
+ $tid = db_select('taxonomy_term_data', 'ttd')
+ ->fields('ttd', array('tid'))
+ ->condition('vid', $gallery_collection_vid)
+ ->range(0, 1)
+ ->execute()
+ ->fetchField();
+
+ return taxonomy_term_load($tid);
+}
+
+/**
+ * Access callback for viewing parts of a node that are only relevant for media
+ * galleries.
+ */
+function media_gallery_view_access($node) {
+ if (!node_access('view', $node)) {
+ return FALSE;
+ }
+ if ($node->type == 'media_gallery') {
+ return TRUE;
+ }
+}
+
+/**
+ * Access callback for editing parts of a node that are only relevant for media
+ * galleries.
+ */
+function media_gallery_edit_access($node) {
+ if (!node_access('update', $node)) {
+ return FALSE;
+ }
+ if ($node->type == 'media_gallery') {
+ return TRUE;
+ }
+}
+
+/**
+ * Access callback for editing parts of a node that are only relevant for media
+ * galleries.
+ */
+function media_gallery_multiedit_access($node) {
+ if (!media_gallery_edit_access($node) )
+ return;
+
+ $media_items = field_get_items('node', $node, 'media_gallery_file');
+ if ($media_items === FALSE)
+ return;
+
+ $media_items = media_gallery_filter_media_access($media_items, 'update');
+ if (count($media_items) > 0) {
+ return TRUE;
+ }
+}
+
+/**
+ * Access callback for viewing a media item in a gallery.
+ *
+ * @param $node
+ * The gallery node object.
+ * @param $file
+ * The file to view.
+ *
+ * @return
+ * TRUE if access is granted; FALSE otherwise.
+ */
+function media_gallery_view_item_access($node, $file) {
+ // Only grant access if the user can view the gallery and the provided media.
+ return media_gallery_view_access($node) && file_entity_access('view', $file)
+ && in_array($file->fid, media_gallery_get_file_ids($node));
+}
+
+/**
+ * Access callback for editing a media item in a gallery.
+ *
+ * @param $node
+ * The gallery node object.
+ * @param $file
+ * The file to edit.
+ *
+ * @return
+ * TRUE if access is granted; FALSE otherwise.
+ */
+function media_gallery_edit_item_access($node, $file) {
+ // Only grant access if the user can edit the provided media
+ // and the media is part of the gallery.
+ return file_entity_access('update', $file)
+ && in_array($file->fid, media_gallery_get_file_ids($node));
+}
+
+/**
+ * Access callback for removing a media item from a gallery.
+ *
+ * @param $node
+ * The gallery node object.
+ * @param $file
+ * The file to remove from the gallery.
+ *
+ * @return
+ * TRUE if access is granted; FALSE otherwise.
+ */
+function media_gallery_remove_item_access($node, $file) {
+ // Only grant access if the user can edit the gallery and the provided media
+ // item is attached to the gallery.
+ return media_gallery_edit_access($node) && in_array($file->fid, media_gallery_get_file_ids($node));
+}
+
+/**
+ * Implements hook_image_default_styles().
+ */
+function media_gallery_image_default_styles() {
+ $styles = array();
+ $styles['media_gallery_thumbnail'] = array(
+ 'effects' => array(
+ array(
+ // @todo We want to not upscale if the user uploads a smaller image, but
+ // image_scale_and_crop doesn't support that option. Try to get
+ // http://drupal.org/node/872206 into core, or solve it in contrib.
+ 'name' => 'image_scale_and_crop',
+ 'data' => array('width' => 450, 'height' => 450, 'upscale' => FALSE),
+ 'weight' => 0,
+ ),
+ )
+ );
+ $styles['media_gallery_large'] = array(
+ 'effects' => array(
+ array(
+ 'name' => 'image_scale',
+ 'data' => array('width' => 900, 'height' => 900, 'upscale' => FALSE),
+ 'weight' => 0,
+ ),
+ )
+ );
+ return $styles;
+}
+
+/**
+ * Implements hook_styles_default_styles().
+ */
+function media_gallery_styles_default_styles() {
+ return array(
+ 'file' => array(
+ 'styles' => array(
+ 'media_gallery_thumbnail' => array(
+ 'label' => 'Media gallery thumbnail',
+ 'description' => 'A square thumbnail for display within a Media Gallery.',
+ ),
+ 'media_gallery_large' => array(
+ 'label' => 'Media gallery large',
+ 'description' => 'A large format for display within a Media Gallery.',
+ ),
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_styles_default_presets_alter().
+ */
+function media_gallery_styles_default_presets_alter(&$styles) {
+ foreach (array_keys(media_gallery_image_default_styles()) as $image_style) {
+ foreach (array('image', 'media_youtube') as $container) {
+ $styles['file']['containers'][$container]['styles'][$image_style]['default preset'] = $image_style;
+ $styles['file']['containers'][$container]['presets'][$image_style] = array(
+ array(
+ // @todo Styles 2.0-alpha6 and later uses 'imageStyle', while earlier
+ // versions use 'image_style'. Change to simply using 'imageStyle'
+ // when it's okay to drop compatibility with the earlier versions.
+ 'name' => (class_exists('FileStyles') && method_exists('FileStyles', 'imageStyle')) ? 'imageStyle' : 'image_style',
+ 'settings' => array(
+ 'image_style' => $image_style,
+ ),
+ ),
+ array(
+ 'name' => 'thumbnail',
+ 'settings' => array(),
+ ),
+ );
+ }
+ }
+
+ $styles['file']['containers']['media_youtube']['styles']['media_gallery_large']['default preset'] = 'video';
+}
+
+/**
+ * Implements hook_styles_presets().
+ *
+ * This function is for Styles 1.x only. For Styles 2.x,
+ * media_gallery_styles_default_presets_alter() is used instead.
+ */
+function media_gallery_styles_presets() {
+ $presets = array(
+ 'file' => array(
+ 'media_gallery_thumbnail' => array(
+ 'media_youtube' => array(
+ 'youtube_thumbnail_media_gallery_thumbnail',
+ ),
+ ),
+ 'media_gallery_large' => array(
+ 'media_youtube' => array(
+ 'youtube_full',
+ ),
+ ),
+ ),
+ );
+ return $presets;
+}
+
+/**
+ * Implements hook_media_wysiwyg_allowed_view_modes_alter().
+ *
+ * Removes view modes intended for gallery context only from the formatting
+ * options of media added to wysiwyg.
+ */
+function media_gallery_media_wysiwyg_allowed_view_modes_alter(&$view_modes) {
+ $view_modes = array_diff_key($view_modes, media_gallery_file_view_modes());
+}
+
+/**
+ * Form #process function for attaching dropdown-related classes and settings.
+ */
+function media_gallery_process_dropdown($element, &$form_state) {
+ $element['#attributes']['class'][] = 'media-gallery-dropdown';
+ $element['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => array(
+ 'media_gallery_dropdown_options' => array(
+ $element['#id'] => $element['#media_gallery_dropdown_options'],
+ ),
+ ),
+ );
+
+ return $element;
+}
+
+/**
+ * Implements hook_taxonomy_term_insert().
+ */
+function media_gallery_taxonomy_term_insert($term) {
+ // Note that in hook_module_implements_alter() we guarantee that this code
+ // will always run before Pathauto's implementation.
+ _media_gallery_prevent_unwanted_pathauto_aliases($term);
+}
+
+/**
+ * Implements hook_taxonomy_term_update().
+ */
+function media_gallery_taxonomy_term_update($term) {
+ // Note that in hook_module_implements_alter() we guarantee that this code
+ // will always run before Pathauto's implementation.
+ _media_gallery_prevent_unwanted_pathauto_aliases($term);
+}
+
+/**
+ * Implements hook_entity_insert().
+ */
+function media_gallery_entity_insert($entity, $type) {
+ // This hook is used because it always runs after Pathauto's
+ // hook_taxonomy_term_insert() implementation.
+ if ($type == 'taxonomy_term') {
+ _media_gallery_allow_all_pathauto_aliases();
+ }
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function media_gallery_entity_update($entity, $type) {
+ // This hook is used because it always runs after Pathauto's
+ // hook_taxonomy_term_update() implementation.
+ if ($type == 'taxonomy_term') {
+ _media_gallery_allow_all_pathauto_aliases();
+ }
+}
+
+/**
+ * Hack to prevent Pathauto from generating unwanted taxonomy aliases.
+ *
+ * This function can be called before allowing the Pathauto module to act on a
+ * saved term for the taxonomy vocabulary used for media galleries. It prevents
+ * Pathauto from generating an alias for the term based on the generic Pathauto
+ * taxonomy alias settings (i.e., an alias will only be generated if the site
+ * is specifically configured to have aliases generated for the vocabulary, not
+ * for taxonomy terms in general).
+ *
+ * The reason we want to do this is so that the URL alias people save on the
+ * media gallery settings page will actually work.
+ *
+ * If a $term object is not provided, then this function will always act; if
+ * one is provided, then it will only act if the term corresponds to the media
+ * gallery default collection.
+ *
+ * Call _media_gallery_allow_all_pathauto_aliases() after the term is saved to
+ * resume normal Pathauto behavior for the rest of the page request.
+ */
+function _media_gallery_prevent_unwanted_pathauto_aliases($term = NULL) {
+ if (!isset($term) || $term->tid == variable_get('media_gallery_default_collection_tid')) {
+ if (isset($GLOBALS['conf']['pathauto_taxonomy_pattern'])) {
+ $GLOBALS['conf']['media_gallery_original_pathauto_taxonomy_pattern'] = $GLOBALS['conf']['pathauto_taxonomy_pattern'];
+ }
+ $GLOBALS['conf']['pathauto_taxonomy_pattern'] = '';
+ }
+}
+
+/**
+ * Restores Pathauto behavior after we are done hacking with it.
+ *
+ * @see _media_gallery_prevent_unwanted_pathauto_aliases()
+ */
+function _media_gallery_allow_all_pathauto_aliases() {
+ if (isset($GLOBALS['conf']['media_gallery_original_pathauto_taxonomy_pattern'])) {
+ $GLOBALS['conf']['pathauto_taxonomy_pattern'] = $GLOBALS['conf']['media_gallery_original_pathauto_taxonomy_pattern'];
+ }
+}
+
+/**
+ * Implements hook_ctools_plugin_api().
+ *
+ * Lets CTools know which plugin APIs are implemented by this module.
+ */
+function media_gallery_ctools_plugin_api($owner, $api) {
+ static $api_versions = array(
+ 'file_entity' => array(
+ 'file_default_displays' => 1,
+ ),
+ );
+ if (isset($api_versions[$owner][$api])) {
+ return array('version' => $api_versions[$owner][$api]);
+ }
+}
+
+/**
+ * Implements hook_file_default_displays().
+ *
+ * Provides default display configurations for files displayed in gallery view
+ * modes.
+ *
+ * @see file_entity_schema()
+ */
+function media_gallery_file_default_displays() {
+ $default_displays = array();
+
+ $default_image_styles = array(
+ 'media_gallery_thumbnail' => 'media_gallery_thumbnail',
+ 'media_gallery_lightbox' => 'media_gallery_large',
+ 'media_gallery_detail' => 'media_gallery_large',
+ 'media_gallery_block_thumbnail' => 'media_gallery_thumbnail',
+ 'media_gallery_collection_thumbnail' => 'media_gallery_thumbnail',
+ );
+
+ // People updating from older versions of Media module will have Styles module
+ // formatters enabled at weight 0. By default, we want the following taking
+ // precedence, but we do not want to disable the Styles module ones since
+ // those might be capable of rendering files not covered by these. Therefore,
+ // set these at a lower weight.
+ $default_weight = -1;
+
+ foreach ($default_image_styles as $view_mode => $image_style) {
+ // Images.
+ $display_name = 'image__' . $view_mode . '__file_image';
+ $default_displays[$display_name] = (object) array(
+ 'api_version' => 1,
+ 'name' => $display_name,
+ 'status' => 1,
+ 'settings' => array('image_style' => $image_style),
+ 'weight' => $default_weight,
+ );
+
+ // YouTube.
+ if (module_exists('media_youtube')) {
+ if (in_array($view_mode, array('media_gallery_lightbox', 'media_gallery_detail'))) {
+ // Video. Omit settings to use media_youtube_video defaults.
+ $display_name = 'video__' . $view_mode . '__media_youtube_video';
+ $default_displays[$display_name] = (object) array(
+ 'api_version' => 1,
+ 'name' => $display_name,
+ 'status' => 1,
+ 'weight' => $default_weight,
+ );
+ }
+ else {
+ // Thumbnail.
+ $display_name = 'video__' . $view_mode . '__media_youtube_image';
+ $default_displays[$display_name] = (object) array(
+ 'api_version' => 1,
+ 'name' => $display_name,
+ 'status' => 1,
+ 'settings' => array('image_style' => $image_style),
+ 'weight' => $default_weight,
+ );
+ }
+ }
+ }
+
+ return $default_displays;
+}
+
+/**
+ * Menu page delivery callback.
+ * This is a delegate function. In case, the user has no access to the menu
+ * item, the menu system does not load the specified file and therefore can
+ * not use the custom deliver function.
+ */
+function media_gallery_lightbox_delivery_callback($page_content) {
+ if (!function_exists('media_gallery_lightbox_page_deliver')) {
+ module_load_include('inc', 'media_gallery', 'media_gallery.pages');
+ }
+ media_gallery_lightbox_page_deliver($page_content);
+}
+
+/**
+ * Returns all given media_files, on which the user has the right
+ * of the given operation.
+ *
+ * @param $media_files
+ * The files to perform the access check.
+ * @param $operation
+ * A access operation (for example: 'view', 'update', 'delete', 'create').
+ *
+ * @return
+ * An array of files.
+ */
+function media_gallery_filter_media_access($media_files, $operation) {
+ $media_accessable = array();
+
+ if (empty($media_files))
+ return $media_accessable;
+
+ foreach ($media_files as $key => $media) {
+ if (file_entity_access($operation, (object)$media)) {
+ $media_accessable[$key] = $media;
+ }
+ }
+ return $media_accessable;
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.pages.inc b/sites/all/modules/media_gallery/media_gallery.pages.inc
new file mode 100644
index 000000000..be618bf27
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.pages.inc
@@ -0,0 +1,419 @@
+<?php
+
+/**
+ * @file
+ * Common pages for the Media Gallery module.
+ */
+
+/**
+ * Page callback to list all galleries.
+ *
+ * TODO: When I grow up I want to be a View.
+ */
+function media_gallery_list_galleries($term) {
+ $collections_vid = variable_get('media_gallery_collection_vid', 0);
+ if ($term->vid !== $collections_vid) {
+ module_load_include('inc', 'taxonomy', 'taxonomy.pages');
+ return taxonomy_term_page($term);
+ }
+
+ // Add front-end resources for drag-and-drop sorting.
+ if (user_access('administer media galleries')) {
+ drupal_add_library('system', 'ui.sortable');
+ drupal_add_library('system', 'jquery.bbq');
+ drupal_add_js(drupal_get_path('module', 'media_gallery') . '/media_gallery.dragdrop.js');
+ drupal_add_css(drupal_get_path('module', 'media_gallery') . '/media_gallery.dragdrop.css');
+ $sort_collection_url = url('media-gallery/sort/collection/' . $term->tid . '/' . drupal_get_token('media_gallery'));
+ drupal_add_js(array('mediaGallerySortCollectionUrl' => $sort_collection_url), array('type' => 'setting'));
+ }
+ // Assign the term name as the page title.
+ drupal_set_title($term->name);
+
+ // Build breadcrumb based on the hierarchy of the term.
+ $current = (object) array(
+ 'tid' => $term->tid,
+ );
+ $breadcrumb = array();
+ while ($parents = taxonomy_get_parents($current->tid)) {
+ $current = array_shift($parents);
+ $breadcrumb[] = l($current->name, 'taxonomy/term/' . $current->tid);
+ }
+ $breadcrumb[] = l(t('Home'), NULL);
+ $breadcrumb = array_reverse($breadcrumb);
+ drupal_set_breadcrumb($breadcrumb);
+ drupal_add_feed('taxonomy/term/' . $term->tid . '/feed', 'RSS - ' . $term->name);
+
+ $build['term_heading'] = array(
+ '#prefix' => '<div class="term-listing-heading">',
+ '#suffix' => '</div>',
+ 'term' => taxonomy_term_view($term, 'full'),
+ );
+
+ // There is a small bug here with the "entity"
+ $term_entity = new FieldsRSIPreventor($term);
+ $limit = $term_entity->getValue('media_gallery_columns') * $term_entity->getValue('media_gallery_rows');
+ if (!$limit) {
+ $limit = 10;
+ }
+
+ $nids = media_gallery_select_galleries($term->tid, TRUE, $limit);
+
+ if (!empty($nids)) {
+ $nodes = node_load_multiple($nids);
+ // Add the nodes to the build array, with a weight of 5.
+ $build += node_view_multiple($nodes, 'teaser', 5);
+ $build['pager'] = array(
+ '#theme' => 'pager',
+ '#weight' => 10,
+ );
+ $build['#term'] = $term;
+ $build['#theme'] = 'media_gallery_collection';
+ }
+ else {
+ $build['no_content'] = array(
+ '#prefix' => '<p>',
+ '#markup' => t('No galleries have been set up yet.'),
+ '#suffix' => '</p>',
+ );
+ if (node_access('create', 'media-gallery')) {
+ $build['no_content']['#markup'] .= ' ' . t('<a href="@link">Add a gallery.</a>', array('@link' => url('node/add/media-gallery')));
+ }
+ }
+ return $build;
+}
+
+/**
+ * Menu callback; view a single gallery media entity as its own page.
+ */
+function media_gallery_detail_page($gallery_node, $file) {
+ // Set the breadcrumb.
+ $node_url_arguments = entity_uri('node', $gallery_node);
+ drupal_set_breadcrumb(array(
+ l(t('Home'), NULL),
+ l($gallery_node->title, $node_url_arguments['path'], $node_url_arguments['options']),
+ ));
+
+ // Set the title for the page
+ $title = _media_gallery_get_media_title($file);
+ drupal_set_title($title);
+
+ drupal_add_js(drupal_get_path('module', 'media_gallery') . '/media_gallery.js');
+
+ $return = media_gallery_item_view($gallery_node, $file, 'media_gallery_detail');
+ return $return;
+}
+
+/**
+ * Menu callback; view a single gallery media item as the content of a lightbox.
+ */
+function media_gallery_lightbox_page($gallery_node, $file) {
+ return media_gallery_item_view($gallery_node, $file, 'media_gallery_lightbox');
+}
+
+/**
+ * Menu page delivery callback; print content for a lightbox.
+ *
+ * This overrides drupal_deliver_html_page() for pages displayed within the
+ * lightbox, in order to skip printing any HTML except the main page content.
+ */
+function media_gallery_lightbox_page_deliver($page_content) {
+ // Display an error message if something went wrong.
+ if (!isset($page_content) || is_int($page_content)) {
+ if (is_int($page_content) && $page_content == MENU_ACCESS_DENIED) {
+ watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+ $content = array('#markup' => t('You are not authorized to access this page.'));
+ }
+ else {
+ $content = array('#markup' => t('An unexpected error occurred. Please try again later.'));
+ }
+ }
+ // This shouldn't happen, but just in case.
+ elseif (is_string($page_content)) {
+ $content = array('#markup' => $page_content);
+ }
+ // This is the part of the code we expect to reach.
+ else {
+ $content = $page_content;
+ }
+
+ // Render the main page content, and nothing else. We don't want to call
+ // drupal_render_page() because the results of this function are inserted
+ // into a <div> on the parent page, so we can't print a full HTML document.
+ // If we inserted them into an iframe within the lightbox, things would be
+ // different, but we don't want to do that because displaying the page in an
+ // iframe results in reduced functionality (you don't get the colorbox
+ // plugin's autoresizing behavior).
+ print drupal_render($content);
+ // Perform end-of-request tasks.
+ drupal_page_footer();
+}
+
+/**
+ * AJAX callback for drag-and-drop gallery sorting.
+ *
+ * @param string $type
+ * The type of item being sorted: 'gallery' for an individual gallery or
+ * 'collection' for a gallery collection.
+ * @param mixed $item
+ * For a media gallery, the $node object for that gallery; for gallery
+ * collections, the taxonomy term corresponding to the collection.
+ */
+function media_gallery_ajax_sort($type, $item) {
+ $order = $_POST['order'];
+ $result = FALSE;
+ $id_prefix = '';
+ switch ($type) {
+ case 'collection':
+ // There are some themes, which set a specific html-prefix for the node
+ // id. Parse the id to be compatible with all themes.
+ // @see drupal_html_id()
+ $search = empty($order[0]) ? '' : $order[0];
+ if (preg_match('/^([A-Za-z0-9\-_]+?-)[0-9]+(--[0-9]+)?$/', $search, $match)) {
+ $id_prefix = $match[1];
+ }
+ else {
+ $id_prefix = 'node-';
+ }
+ $order = _media_gallery_sanitize_ids($id_prefix, $order);
+ $result = media_gallery_reorder_collection($item, $order);
+ break;
+ case 'gallery':
+ $id_prefix = 'media-gallery-media-';
+ $order = _media_gallery_sanitize_ids($id_prefix, $order);
+ $result = media_gallery_reorder_gallery($item, $order);
+ break;
+ }
+ drupal_json_output(array('result' => $result, 'order' => $_POST['order'], 'idPrefix' => $id_prefix));
+}
+
+/**
+ * Helper function for media_gallery_ajax_sort() that sanitizes a list of IDs.
+ *
+ * Removes a string such as 'node-' from each item in an array, leaving only a
+ * numeric ID.
+ *
+ * @param string $prefix
+ * The prefix to remove from each ID in the list.
+ * @param array $list
+ * The list of IDs.
+ *
+ * @return array
+ * The list of sanitized IDs.
+ */
+function _media_gallery_sanitize_ids($prefix, $list) {
+ foreach ($list as &$value) {
+ $value = intval(str_replace($prefix, '', $value));
+ }
+ return $list;
+}
+
+/**
+ * Reorder a gallery collection.
+ */
+function media_gallery_reorder_collection($collection, $order) {
+ // Get a complete ordered list of the galleries in this collection.
+ $galleries = db_query('SELECT ti.nid FROM {taxonomy_index} ti LEFT JOIN {media_gallery_weight} mgw ON (ti.nid = mgw.nid AND ti.tid = mgw.tid) WHERE ti.tid = :tid ORDER BY mgw.weight ASC, ti.nid ASC', array(':tid' => $collection->tid))->fetchCol();
+ // Sort the list.
+ $galleries = _media_gallery_reorder($galleries, $order);
+ // Resave gallery weights for the entire collection.
+ db_delete('media_gallery_weight')
+ ->condition('tid', $collection->tid)
+ ->execute();
+ $query = db_insert('media_gallery_weight')
+ ->fields(array('tid', 'nid', 'weight'));
+ foreach ($galleries as $weight => $nid) {
+ $query->values(array(
+ 'tid' => $collection->tid,
+ 'nid' => $nid,
+ 'weight' => $weight,
+ ));
+ }
+ $query->execute();
+ return TRUE;
+}
+
+/**
+ * Reorder the media items in a gallery.
+ */
+function media_gallery_reorder_gallery($gallery, $order) {
+ $media_field = $gallery->media_gallery_file[LANGUAGE_NONE];
+ $offset = 0;
+ if (!empty($_POST['page'])) {
+ $page = array_pop(explode(',', $_POST['page']));
+ $num_per_page = $gallery->media_gallery_columns[LANGUAGE_NONE][0]['value'] * $gallery->media_gallery_rows[LANGUAGE_NONE][0]['value'];
+ $offset = $num_per_page * $page;
+ }
+
+ foreach ($order as $new_delta => $old_delta) {
+ $gallery->media_gallery_file[LANGUAGE_NONE][$new_delta + $offset] = $media_field[$old_delta + $offset];
+ }
+ node_save($gallery);
+ return TRUE;
+}
+
+/**
+ * Helper function to reorder a list when given a reordered subset of the list.
+ *
+ * @param array $list
+ * The list in its original order, as a numeric array.
+ * @param array $ordered_subset
+ * The part of the list that is to be reordered, as a numeric array.
+ * @return array
+ * The list in its new order.
+ */
+function _media_gallery_reorder($list, $ordered_subset) {
+ $list_by_id = array_flip($list);
+ // Determine the current position of each item in the subset that we're
+ // reordering.
+ $subset = array();
+ foreach ($ordered_subset as $position => $id) {
+ $subset[$list_by_id[$id]] = $id;
+ }
+ ksort($subset);
+ // Place each item of the reordered subset into the list in its new position.
+ foreach ($subset as $original_position => $id) {
+ $list[$original_position] = current($ordered_subset);
+ next($ordered_subset);
+ }
+ return $list;
+}
+
+/**
+ * Page callback to add images to a media gallery.
+ *
+ * @param $node
+ * The node to which the images should be added.
+ */
+function media_gallery_add_images($node) {
+ // The caller provides a numeric array of file ids that the user selected.
+ $fids = $_POST['files'];
+
+ // Determine which file ids are already included in this gallery.
+ $items = isset($node->media_gallery_file[LANGUAGE_NONE]) ? $node->media_gallery_file[LANGUAGE_NONE] : array();
+ foreach ($items as $item) {
+ $existing_fids[$item['fid']] = TRUE;
+ }
+
+ // Add the newly selected media items to the previous list of items.
+ foreach ($fids as $fid) {
+ // Only add a selected item if it isn't already in the gallery.
+ if (empty($existing_fids[$fid])) {
+ $file = array();
+ $file['fid'] = $fid;
+ $file['display'] = 1;
+ $file['description'] = '';
+ $items[] = $file;
+ }
+ }
+
+ $node->media_gallery_file[LANGUAGE_NONE] = $items;
+ node_save($node);
+
+ drupal_json_output(array('result' => TRUE, 'items' => $items));
+}
+
+/**
+ * Form callback: Display a form for removing a media item from a gallery.
+ *
+ * @param $node
+ * The gallery node object.
+ * @param $file
+ * The file to remove from the gallery.
+ */
+function media_gallery_remove_item_form($form, &$form_state, $node, $file) {
+ // Set the breadcrumb.
+ $node_url_arguments = entity_uri('node', $node);
+ drupal_set_breadcrumb(array(
+ l(t('Home'), NULL),
+ l($node->title, $node_url_arguments['path'], $node_url_arguments['options']),
+ ));
+
+ $form['nid'] = array(
+ '#type' => 'value',
+ '#value' => $node->nid,
+ );
+ $form['fid'] = array(
+ '#type' => 'value',
+ '#value' => $file->fid,
+ );
+ return confirm_form($form,
+ t('Are you sure you want to remove %file from the gallery %gallery?', array('%file' => $file->filename, '%gallery' => $node->title)),
+ "media-gallery/detail/{$node->nid}/{$file->fid}",
+ t('The file will still be available in the media library if used elsewhere on this site. Note: Unused files will be deleted.'),
+ t('Remove file')
+ );
+}
+
+/**
+ * Submit handler for removing a media item from a gallery.
+ */
+function media_gallery_remove_item_form_submit($form, &$form_state) {
+ $node = node_load($form_state['values']['nid']);
+ $file = file_load($form_state['values']['fid']);
+ media_gallery_remove_item_from_gallery($node, $file);
+ drupal_set_message(t('The file %file was removed from the gallery.', array('%file' => $file->filename)));
+ $form_state['redirect'] = "node/{$node->nid}";
+}
+
+/**
+ * Menu callback; presents the Media editing form in the context of a gallery.
+ */
+function media_gallery_media_page_edit($gallery, $file) {
+ // The Media Gallery module defines titles differently than just using the
+ // filename.
+ drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $file->type, '@title' => _media_gallery_get_media_title($file))), PASS_THROUGH);
+
+ // Set the breadcrumb.
+ $node_url_arguments = entity_uri('node', $gallery);
+ drupal_set_breadcrumb(array(
+ l(t('Home'), NULL),
+ l($gallery->title, $node_url_arguments['path'], $node_url_arguments['options']),
+ ));
+
+ // We'll be reusing File entity (fieldable files) module's 'file_entity_edit'
+ // form, so we need to load its include file. In addition to loading it here,
+ // we also set the include file info within $form_state in case there are
+ // AJAX enabled widgets that will submit to system/ajax, bypassing this menu
+ // callback.
+ module_load_include('inc', 'file_entity', 'file_entity.pages');
+ $form_state['build_info']['files']['menu'] = array('type' => 'inc', 'module' => 'file_entity', 'name' => 'file_entity.pages');
+
+ // Provide context information for form building and processing functions.
+ $form_state['media_gallery']['gallery'] = $gallery;
+
+ // Since we have a $form_state, we can't call drupal_get_form(), so pass the
+ // expected arguments to drupal_build_form().
+ $form_state['build_info']['args'] = array($file);
+ return drupal_build_form('file_entity_edit', $form_state);
+}
+
+/**
+ * Menu callback; presents the Media multiedit form in the context of a gallery.
+ */
+function media_gallery_media_page_multiedit($node) {
+ // Determine the media entities to edit.
+ $columns = $node->media_gallery_columns[LANGUAGE_NONE][0]['value'];
+ $rows = $node->media_gallery_rows[LANGUAGE_NONE][0]['value'];
+ $number_per_page = $columns * $rows;
+ $current_page = isset($_GET['page']) ? $_GET['page'] : 0;
+ $items = array_slice($node->media_gallery_file[LANGUAGE_NONE], $current_page*$number_per_page, $number_per_page);
+ $fids = array();
+ foreach ($items as $item) {
+ $fids[] = $item['fid'];
+ }
+ $files = file_load_multiple($fids);
+ $files = media_gallery_filter_media_access($files, 'update');
+
+ // Load the Media module include file containing the form callback.
+ // @todo To support AJAX enabled widgets within the form, we need to also add
+ // media.pages.inc to $form_state['build_info']['files']['menu']. Figure out
+ // how to integrate that properly with the Multiform module.
+ module_load_include('inc', 'media', 'includes/media.pages');
+
+ // Build and process the form.
+ $form = media_bulk_upload_file_operation_edit_multiple($files);
+
+ // Override the page title set by media_file_page_edit_multiple() and return the form.
+ drupal_set_title(t('<em>Edit media for</em> @title', array('@type' => $node->type, '@title' => $node->title)), PASS_THROUGH);
+ return $form;
+}
diff --git a/sites/all/modules/media_gallery/media_gallery.theme.inc b/sites/all/modules/media_gallery/media_gallery.theme.inc
new file mode 100644
index 000000000..56ce24c91
--- /dev/null
+++ b/sites/all/modules/media_gallery/media_gallery.theme.inc
@@ -0,0 +1,594 @@
+<?php
+
+/**
+ * @file
+ * Media Gallery Theming
+ */
+
+/**
+ * Displays a collection of galleries as a grid of teasers.
+ */
+function theme_media_gallery_collection($variables) {
+ $element = $variables['element'];
+ $collection = $element['#term'];
+
+ $columns = $collection->media_gallery_columns[LANGUAGE_NONE][0]['value'];
+ $grid = '<div class="media-gallery-collection mg-collection-' . $collection->vocabulary_machine_name . ' mg-col mg-col-' . $columns . '">';
+ foreach (element_children($element['nodes']) as $nid) {
+ // This invokes node.tpl.php, and where that calls render($content),
+ // theme_media_gallery_teaser() is called.
+
+ // Add the term to the node so that we can use it to configure the meta data
+ $element['nodes'][$nid]['#node']->term = $collection;
+ $teaser = drupal_render($element['nodes'][$nid]);
+
+ // @todo Implement real display needs.
+ $cell = $teaser;
+ $grid .= $cell;
+ }
+ $grid .= '</div>';
+
+ // Replace the 'nodes' element with the rendered grid while preserving its
+ // weight, so that other fields that are part of the collection term get
+ // rendered normally, and in the correct order.
+ $weight = isset($element['nodes']['#weight']) ? $element['nodes']['#weight'] : 0;
+ $element['nodes'] = array('#markup' => $grid, '#weight' => $weight);
+ $output = drupal_render_children($element);
+
+ return $output;
+}
+
+/**
+ * Displays a gallery node as a teaser.
+ *
+ * The Galleries page displays a grid of gallery teasers. Each gallery teaser is
+ * themed as a node using node.tpl.php or one if its overrides. Where that
+ * template file calls @code render($content) @endcode, the output of this
+ * function is returned.
+ */
+function theme_media_gallery_teaser($variables) {
+ $element = $variables['element'];
+ $node = $element['#node'];
+
+ if (isset($element['media_gallery_file'][0])) {
+ $element['media_gallery_file'][0]['#theme'] = 'media_gallery_file_field_inline';
+ $image = drupal_render($element['media_gallery_file'][0]);
+ }
+ else {
+ $image = theme('image', array('path' => drupal_get_path('module', 'media_gallery') . '/images/empty_gallery.png'));
+ }
+
+ $link_vars = array();
+ $link_vars['image'] = $image;
+ $uri = entity_uri('node', $node);
+ $link_vars['link_path'] = $uri['path'];
+ $link_vars['classes'] = array('media-gallery-thumb');
+
+ $output = '<div class="media-collection-item-wrapper"><img class="stack-image" src="' . base_path() . drupal_get_path('module', 'media_gallery') . '/images/stack_bg.png" />' . theme('media_gallery_item', $link_vars) . '</div>';
+
+ // Set the variables to theme the meta data if there is a term on the node
+ if (isset($node->term)) {
+ $term = $node->term;
+ $meta_vars = array();
+ $meta_vars['location'] = $term->media_gallery_image_info_where[LANGUAGE_NONE][0]['value'];
+ $meta_vars['title'] = $node->title;
+ $meta_vars['link_path'] = $link_vars['link_path'];
+ // Organize the file count by type. We only expect images and videos for
+ // now, so we put those first and group the others together into a general
+ // category at the end.
+ $type_count = media_gallery_get_media_type_count($node, 'media_gallery_file_original');
+ $description = array();
+ if (isset($type_count['image'])) {
+ $count = $type_count['image'];
+ $description[] = format_plural($count, '<span>@num image</span>', '<span>@num images</span>', array('@num' => $count));
+ unset($type_count['image']);
+ }
+ if (isset($type_count['video'])) {
+ $count = $type_count['video'];
+ $description[] = format_plural($count, '<span>@num video</span>', '<span>@num videos</span>', array('@num' => $count));
+ unset($type_count['video']);
+ }
+ if (!empty($type_count)) {
+ $count = array_sum($type_count);
+ $description[] = format_plural($count, '<span>@num other item</span>', '<span>@num other items</span>', array('@num' => $count));
+ }
+ $meta_vars['description'] = implode(', ', $description);
+
+ // Add the meta information
+ $output .= theme('media_gallery_meta', $meta_vars);
+ }
+
+ return $output;
+}
+
+/**
+ * Template preprocess function for displaying a media item (entity) as a
+ * thumbnail on the gallery page.
+ */
+function template_preprocess_media_gallery_media_item_thumbnail(&$variables) {
+ $element = $variables['element'];
+
+ // Attach the colorbox javascript if the format calls for it.
+ $format = $element['#media_gallery_entity']->media_gallery_format[LANGUAGE_NONE][0]['value'];
+ $lightbox = is_numeric(strpos($format, 'lightbox')) ? TRUE : FALSE;
+ if ($lightbox) {
+ $element['file']['#attached']['js'][] = drupal_get_path('module', 'media_gallery') . '/colorbox-display.js';
+ $element['file']['#attached']['library'][] = array('media_gallery', 'colorbox');
+ }
+
+ // Get the rendered file without annoying DIV wrappers.
+ $element['file'] = array('#theme' => 'media_gallery_file_field_inline', '0' => $element['file']);
+ $image = drupal_render($element['file']);
+
+ $gallery_id = $element['#media_gallery_entity']->nid;
+ $media_id = $element['#file']->fid;
+
+ // Add a class that is a more targeted version of what template_preprocess()
+ // automatically adds for this theme hook, to enable per-type (e.g., video vs.
+ // image) styling.
+ $variables['classes_array'][] = drupal_html_class('media_gallery_file_item_thumbnail_' . $element['#file']->type);
+
+ // Add a class for the wrapper.
+ $variables['classes_array'][] = 'media-gallery-item-wrapper';
+
+ // Create an array of variables to be added to the main thumbnail link.
+ $link_vars = array();
+ $link_vars['image'] = $image;
+ $link_vars['link_path'] = "media-gallery/detail/$gallery_id/$media_id";
+ $link_vars['classes'] = $lightbox ? array('media-gallery-thumb', 'cbEnabled') : array('media-gallery-thumb');
+ $link_vars['title'] = $element['#bundle'] == 'image' ? t('View larger image') : t('Watch video');
+ // Add the image as a link to the detailed view
+ $variables['media_gallery_item'] = theme('media_gallery_item', $link_vars);
+
+ // Set the variables to theme the metadata.
+ $meta_vars = array();
+ $meta_vars['location'] = $element['#media_gallery_entity']->media_gallery_image_info_where[LANGUAGE_NONE][0]['value'];
+ $meta_vars['title'] = isset($element['media_title']) ? $element['media_title'][0]['#markup'] : '';
+ $meta_vars['link_path'] = $link_vars['link_path'];
+
+ // Theme the metadata.
+ $variables['media_gallery_meta'] = theme('media_gallery_meta', $meta_vars);
+}
+
+/**
+ * Displays a media item (entity) within a lightbox.
+ *
+ * Clicking a thumbnail within the gallery page opens a lightbox if all these
+ * conditions are met:
+ * - The gallery node's media_gallery_format field indicates to open a lightbox.
+ * - The colorbox jQuery plugin is available.
+ * - The user has JavaScript enabled.
+ *
+ * The lightbox contains some navigation functionality (including a "slideshow"
+ * link) and a <div> containing the actual content. This function themes the
+ * contents of that <div>.
+ *
+ * When any of the conditions for opening a lightbox aren't met, the user is
+ * taken to a separate detail page instead, the contents of which are themed by
+ * theme_media_gallery_media_item_detail().
+ */
+function theme_media_gallery_media_item_lightbox($variables) {
+ $element = $variables['element'];
+ $gallery_node = new FieldsRSIPreventor($element['#media_gallery_entity']);
+ $file = $element['#file'];
+
+ // The lightbox JavaScript requires width and height attributes to be set on
+ // the displayed image, but if we're displaying an image derivative, we need
+ // to create it in order to know its width and height.
+ // @todo Improve the JavaScript to not require this.
+ if ($element['file']['#theme'] == 'image_style') {
+ $style_name = $element['file']['#style_name'];
+ $style_path = image_style_path($style_name, $file->uri);
+ if (!file_exists($style_path)) {
+ $style = image_style_load($style_name);
+ image_style_create_derivative($style, $file->uri, $style_path);
+ }
+ $info = image_get_info($style_path);
+ $element['file'] += array('#attributes' => array());
+ $element['file']['#attributes'] += array('width' => $info['width'], 'height' => $info['height']);
+ }
+
+ $image = drupal_render($element['file']);
+
+ $matches = NULL;
+ if (preg_match('@<img .*?/>@', $image, $matches)) {
+ $image = $matches[0];
+ }
+ else {
+
+ }
+
+ $gallery_id = $element['#media_gallery_entity']->nid;
+ $media_id = $element['#file']->fid;
+
+ // Create an array of variables to be added to the main image link.
+ $link_vars = array();
+ $link_vars['image'] = $image;
+ $link_vars['link_path'] = "media-gallery/detail/$gallery_id/$media_id";
+ $link_vars['no_link'] = $element['#bundle'] != 'image' ? TRUE : FALSE;
+
+ if ($gallery_node->getValue('media_gallery_allow_download') == TRUE) {
+ $download_link = $element['#bundle'] == 'image' ? theme('media_gallery_download_link', array('file' => $file)) : l(t('View detail page'), $link_vars['link_path']);
+ }
+
+ else {
+ // Very ugly fix: This prevents the license info from being either hidden
+ // or causing scrollbars (depending on the browser) in cases where a
+ // download link is not being shown. There may be a CSS-only fix for this,
+ // but we haven't found one yet.
+ $download_link = '&nbsp;';
+ }
+
+ $media_gallery_detail =
+ '<div class="lightbox-stack">' .
+ theme('media_gallery_item', $link_vars) .
+ '<div class="media-gallery-detail-info">' .
+ $download_link .
+ theme('media_gallery_license', array('element' => isset($element['field_license']) ? $element['field_license'] : array(), 'color' => 'medium', 'file' => $file)) .
+ '</div></div>';
+ // The license info has been themed already, keep it from being rendered as a child
+ $element['field_license']['#access'] = FALSE;
+
+ $output = 'Error';
+ // If the format is to have the description as well, we add it here
+ if (!empty($gallery_node->media_gallery_lightbox_extras[LANGUAGE_NONE][0]['value'])) {
+ $output =
+ '<div class="mg-lightbox-wrapper clearfix">' .
+ '<div class="lightbox-title">' . drupal_render($element['media_title']) . '</div>' .
+ '<div class="mg-lightbox-detail">' .
+ $media_gallery_detail .
+ '</div><div class="mg-lightbox-description">' .
+ drupal_render_children($element) .
+ '</div>' .
+ '</div>';
+ }
+ else {
+ $output = $media_gallery_detail;
+ }
+
+ return $output;
+}
+
+/**
+ * Displays a media item (entity) as its own page, within gallery context.
+ *
+ * @see theme_media_gallery_media_item_lightbox()
+ */
+function theme_media_gallery_media_item_detail($variables) {
+ $element = $variables['element'];
+ $gallery_node = new FieldsRSIPreventor($element['#media_gallery_entity']);
+ $file = $element['#file'];
+ // Page number for next and previous pages and current page.
+ $i_next = NULL;
+ $i_previous = NULL;
+ $i_current = NULL;
+
+ // Not considering the possibility of this field being translatable, at the
+ // moment. Is there a use-case for a media field (which is just a reference to
+ // a media entity) to be translatable?
+ $media_ids = array();
+ foreach ($gallery_node->media_gallery_file[LANGUAGE_NONE] as $delta => $item) {
+ $media_ids[] = _media_gallery_get_media_fid($item);
+ }
+ $media_ids = array_values(array_unique($media_ids));
+
+ // Get the variables needed for previous and next buttons and "Image X of Y"
+ // text.
+ $num_items = count($media_ids);
+ foreach ($media_ids as $i => $id) {
+ if ($id == $file->fid) {
+ $i_current = $i;
+ break;
+ }
+ }
+
+ $i_previous = $i_current - 1;
+ $i_next = $i_current + 1;
+
+ if ($i_previous < 0) {
+ $i_previous = NULL;
+ }
+
+ $i_next = $i_current + 1;
+ if ($i_next > $num_items - 1) {
+ $i_next = NULL;
+ }
+
+ if ($gallery_node->getValue('media_gallery_allow_download') == TRUE) {
+ $download_link = $element['#bundle'] == 'image' ? theme('media_gallery_download_link', array('file' => $file)) : '&nbsp;';
+ }
+ else {
+ // Very ugly fix: This prevents the license info from being either hidden
+ // or causing scrollbars (depending on the browser) in cases where a
+ // download link is not being shown. There may be a CSS-only fix for this,
+ // but we haven't found one yet.
+ $download_link = '&nbsp;';
+ }
+
+ $previous_link = !is_null($i_previous) ? l(t('« Previous'), "media-gallery/detail/{$gallery_node->nid}/{$media_ids[$i_previous]}", array('html' => TRUE, 'attributes' => array('class' => array('prev')))) : '';
+ $next_link = !is_null($i_next) ? l(t('Next »'), "media-gallery/detail/{$gallery_node->nid}/{$media_ids[$i_next]}", array('html' => TRUE, 'attributes' => array('class' => array('next')))) : '';
+
+ // Render the file out in a wrapper
+ $output =
+ '<div class="media-gallery-detail-wrapper">' .
+ '<div class="media-gallery-detail">' .
+ drupal_render($element['file']) .
+ '<div class="media-gallery-detail-info">' . $download_link .
+ theme('media_gallery_license', array('element' => isset($element['field_license']) ? $element['field_license'] : array('#view_mode' => 'media_gallery_detail'), 'color' => 'dark', 'file' => $file)) .
+ '</div>' .
+ '<div class="media-gallery-detail-info">' .
+ '<span class="media-gallery-back-link">' .
+ l(t('« Back to gallery'), 'node/' . $gallery_node->nid) .
+ '</span>' .
+ '<span class="media-gallery-detail-image-info-wrapper">' .
+ '<span class="media-gallery-image-count">' .
+ t("Item @current of @total", array('@current' => $i_current + 1, '@total' => $num_items)) .
+ '</span>' .
+ '<span class="media-gallery-controls">' .
+ $previous_link .
+ (!empty($previous_link) && !empty($next_link) ? ' | ' : '') .
+ $next_link .
+ '</span>' .
+ '</span>' .
+ '</div>' .
+ '</div>';
+
+ // The license was already output above via a direct theme() call rather
+ // than drupal_render().
+ $element['field_license']['#printed'] = TRUE;
+
+ // The file was rendered above, but due to a drupal_render() bug, might not be
+ // marked as printed.
+ // @todo Remove when http://drupal.org/node/1305220 is fixed.
+ $element['file']['#printed'] = TRUE;
+
+ // Render the remaining fields, but not the title, since that's output as
+ // part of the page title.
+ $element['media_title']['#access'] = FALSE;
+ $output .=
+ '<div class="no-overflow">' .
+ drupal_render_children($element) .
+ '</div></div>';
+
+ return $output;
+}
+
+/**
+ * Displays a media item (entity) as a thumbnail in a block
+ */
+function theme_media_gallery_block_thumbnail($variables) {
+ $element = $variables['element'];
+
+ // Attach the colorbox javascript if the format calls for it.
+ $format = $element['#media_gallery_entity']->media_gallery_format[LANGUAGE_NONE][0]['value'];
+ $lightbox = is_numeric(strpos($format, 'lightbox')) ? TRUE : FALSE;
+ if ($lightbox) {
+ $element['file']['#attached']['js'][] = drupal_get_path('module', 'media_gallery') . '/colorbox-display.js';
+ $element['file']['#attached']['library'][] = array('media_gallery', 'colorbox');
+ }
+
+ // Get the rendered file without annoying DIV wrappers.
+ $element['file'] = array('#theme' => 'media_gallery_file_field_inline', '0' => $element['file']);
+ $image = drupal_render($element['file']);
+
+ // Create some variables to be added to the main image link
+ $classes = $lightbox ? array('media-gallery-thumb', 'cbEnabled') : array('media-gallery-thumb');
+ $gallery_id = $element['#media_gallery_entity']->nid;
+ $media_id = $element['#file']->fid;
+ $link_path = "media-gallery/detail/$gallery_id/$media_id";
+
+ // Create a wrapper
+ $output = '<div class="media-gallery-item-wrapper">';
+ // Style the thumbnail as a link
+ $output .= theme('media_gallery_item', array('image' => $image, 'link_path' => $link_path, 'classes' => $classes));
+ // Close the wrapper
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Displays a media item (entity) as a thumbnail on a Gallery collection page
+ */
+function theme_media_gallery_collection_thumbnail($variables) {
+ // In the default configuration of the module, the collection thumbnail
+ // display format is used for node teasers, and theme_media_gallery_teaser()
+ // bypasses calling this theme function. However, if someone configures their
+ // site differently (to use this display format in a different view mode), we
+ // need some sensible behavior here. For now, we just theme the media item
+ // the same way we theme it when it appears in a block thumbnail.
+ // @todo: Ideally, some of the code in theme_media_gallery_teaser() would
+ // move here instead, so that people could configure their site to
+ // reproduce the default "media gallery node teaser" style in other view
+ // modes as well.
+ return theme('media_gallery_block_thumbnail', $variables);
+}
+
+/**
+ * Theme the thumbnail link for a media gallery item
+ *
+ * @param string $image
+ * Which meta data fields to display
+ * @param string $link_path
+ * The location to place the meta data on the media item
+ * @param string $classes
+ * An array of classes to attach to the link.
+ *
+ */
+function theme_media_gallery_item($variables) {
+ $image = $variables['image'];
+ $link_path = $variables['link_path'];
+ $attributes = array();
+ if (!empty($variables['classes'])) {
+ $attributes['class'] = $variables['classes'];
+ }
+ if (!empty($variables['title'])) {
+ // I'm fairly sure I don't like this solution. But as Alex mentions in
+ // theme_media_gallery_file_field_inline() the File Styles module isn't allowing
+ // us access to the render array pre-rendering, so I'm doing a str_replace()
+ // here specifically to address the title and alt for thumbnails. This had
+ // to be further modified to remove and then add the title and alt attributes
+ // video thumbnails had no title and alt attributes so the string replace was
+ // not triggering for them.
+ $new_image = str_replace(array('title=""', 'alt=""'), array('', ''), $image);
+ $image = str_replace('/>', ' title="' . $variables['title'] . '" alt="' . $variables['title'] . '" />', $new_image);;
+ }
+
+ // Add sliding door top div and wrappers
+ $item = '<div class="media-gallery-item"><div class="top"><div class="top-inset-1"><div class="top-inset-2"></div></div></div><div class="gallery-thumb-outer"><div class="gallery-thumb-inner">';
+ // Create a link around the image
+ $item .= empty($variables['no_link']) ? l($image, $link_path, array('html' => TRUE, 'attributes' => $attributes)) : $image;
+ // Add sliding door bottom div and close wrappers
+ $item .= '</div></div><div class="bottom"><div class="bottom-inset-1"><div class="bottom-inset-2"></div></div></div></div>';
+ return $item;
+}
+
+/**
+ * Theme the meta data for a media gallery item
+ *
+ * @param string $display
+ * Which meta data fields to display
+ * @param string $location
+ * The location to place the meta data on the media item
+ * @param string $title
+ * The title to display (if applicable)
+ * @param string $license
+ * The license information for the media item
+ * @param string $description
+ * A description to display with the title (if applicable).
+ */
+function theme_media_gallery_meta($variables) {
+ $location = $variables['location'];
+ $title = $variables['title'];
+ $description = $variables['description'];
+ $link_path = $variables['link_path'];
+ // Add a top sliding door to the meta info
+ $meta = '<span class="top slider"><span class="top-inner slider"></span></span>';
+
+ // Open the content sliding door for the meta info
+ $meta .= '<span class="meta-outer slider"><span class="meta-inner slider">';
+
+ // If we display nothing, nothing else matters
+ if ($location != 'nothing') {
+ // Add a wrapper around the meta data
+ #$meta .= '<div class="meta-wrapper ' . $location . '">';
+ // Add title
+ $attributes = array('class' => array('meta-wrapper', 'cbEnabled', $location));
+ $meta .= $title ? '<span class="media-title">' . $title . '</span>' : '';
+ if ($description) {
+ $meta .= '<span class="media-description">' . $description . '</span>';
+ }
+ // Close the content sliding door
+ $meta .= '</span></span>';
+
+ // Add a bottom sliding door
+ $meta .= '<span class="bottom slider"><span class="bottom-inner slider"></span></span>';
+
+ // Close the wrapper around the meta data
+ $meta_link = $location == 'hover' ? l($meta, $link_path, array('attributes' => $attributes, 'html' => TRUE)) : '<span class="meta-wrapper">' . $meta . '</span>';
+ }
+
+ return isset($meta_link) ? $meta_link : NULL;
+}
+
+/**
+ * Returns HTML for a link to a file.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - file: A file object to which the link will be created.
+ * - text: (optional) The link text. Defaults to t('Download original image').
+ * - options: (optional) Extra options to pass to l().
+ *
+ * @see theme_file_link()
+ *
+ * @ingroup themeable
+ *
+ * @todo Move to Media module?
+ */
+function theme_media_gallery_download_link($variables) {
+ $file = $variables['file'];
+ $text = $variables['text'];
+ $options = $variables['options'];
+
+ // Link to "media/FID/download", so that Drupal can set the
+ // Content-Disposition header as needed to instruct the browser to download
+ // rather than display the file. Append the file name at the end to work
+ // around Internet Explorer's deficiencies in understanding HTTP headers.
+ $path = 'media/' . $file->fid . '/download/' . $file->filename;
+
+ if (!isset($text)) {
+ $text = t('Download original image');
+ }
+
+ // Set options as per anchor format described at
+ // http://microformats.org/wiki/file-format-examples
+ $options['attributes']['type'] = $file->filemime . '; length=' . $file->filesize;
+ $options['attributes']['class'] = array('gallery-download');
+
+ return l($text, $path, $options);
+}
+
+/*
+ * Turns the license information into the Creative Commons images.
+ */
+function theme_media_gallery_license($variables) {
+ // Don't display license information for externally hosted media. See
+ // media_gallery_field_attach_form().
+ if (isset($variables['file'])) {
+ // @todo Implement a more generic determination for when a license applies
+ // and when it doesn't.
+ if (file_uri_scheme($variables['file']->uri) == 'youtube') {
+ return '';
+ }
+ }
+
+ if (isset($variables['element']['#items'][0]['value'])) {
+ $item = $variables['element']['#items'][0]['value'];
+ }
+ else {
+ $item = 'none';
+ }
+ $color = $variables['color'];
+ $options = explode('_', $item);
+
+ // Open a wrapper around the icons.
+ $output = '<span class="media-license ' . $color . '">';
+
+ if (empty($item) || $item === 'none') {
+ $output .= '<span class="copyright" title="All rights reserved"></span>';
+ }
+
+ $output .= in_array('cc', $options) ? '<span class="attribution" title="Attribution"></span>' : '';
+ $output .= in_array('nc', $options) ? '<span class="non-commercial" title="Non-Commercial"></span>' : '';
+ $output .= in_array('sa', $options) ? '<span class="share-alike" title="Share Alike"></span>' : '';
+ $output .= in_array('nd', $options) ? '<span class="no-deriv" title="No Derivative Works"></span>' : '';
+
+ $output .= '</span>';
+
+ return $output;
+}
+
+/**
+ * Renders a file field for use where block-level HTML tags are not wanted, such as in a link.
+ *
+ * This theme function is called instead of theme_field(), and therefore, we
+ * bypass all the container DIV tags normally added for fields.
+ */
+function theme_media_gallery_file_field_inline($variables) {
+ $element = $variables['element'];
+
+ $output = drupal_render_children($element);
+
+ // @todo Most likely, the image was rendered using a Styles module formatter
+ // (probably a File Styles wrapper to an image style). The Styles module
+ // needs to be improved to integrate properly into Drupal 7 rendering, so
+ // that we can adjust the render array before rendering it. But until the
+ // Styles module is sufficiently improved, we're stuck with getting back a
+ // rendered string containing a DIV wrapper around the image. So here, we
+ // remove all DIV tags, which is totally hacky, and exactly the kind of
+ // stuff that the D7 render system is meant to avoid.
+ $output = trim(preg_replace('@</?div.*?/?\>@', '', $output));
+
+ return $output;
+}
diff --git a/sites/all/modules/plupload/CHANGELOG.txt b/sites/all/modules/plupload/CHANGELOG.txt
new file mode 100644
index 000000000..83b9c0705
--- /dev/null
+++ b/sites/all/modules/plupload/CHANGELOG.txt
@@ -0,0 +1,70 @@
+CHANGELOG for Plupload integration for Drupal 7
+
+Plupload integration 7.x-1.7
+=================================
+- #2370889 by znerol: Fixed Extra margin on file list due to insufficient specificity of CSS selector.
+- #2190007 by slashrsm | hswong3i: Kill Firefox 3.6 check and entire _requirements checks section.
+- #2241523 by K. Tadesse, si.mon: Add some more strings to i18n.js.
+- #2088143 by hswong3i, Chris Charlton | lsolesen: Add make file so dependencies are downloaded automatically.
+
+Plupload integration 7.x-1.6
+=================================
+- #2171575 by slashrsm | kreatIL: Plupload library not found (version 1.5.8).
+- #2098555 by slashrsm: Suggest using .zip instead of .tar.gz.
+
+Plupload integration 7.x-1.5
+=================================
+- by slashrsm: Update README.txt with new download links.
+- #2132373 by OnkelTem: Plupload doesn't support international file names.
+- #2098555 by slashrsm: Document library v2.0.0 incompatibility.
+
+Plupload integration 7.x-1.4
+=================================
+- #2065927 by vladan.me: Added Plupload events support.
+- #2081263 by hgneng: Fixed Check transliteration setting before apply transliteration action.
+
+Plupload integration 7.x-1.3
+=================================
+- #2063161 by mfgering | Amon_Re: Fixed Multi Upload with plupload broken when using Drupal 7.23.
+
+Plupload integration 7.x-1.2
+=================================
+- #1999264 by slashrsm: up_arrow.png and up_arrow_disabled.png are very large.
+- #1935256 by Primsi: Fixed When a form has more than one submit button plupload always submits using the first one.
+
+Plupload integration 7.x-1.1
+=================================
+- #1903818 by quicksketch, slashrsm: Fixed Double dollar signs in hook_requirements().
+- #1506642 by MrHaroldA | succinct: Fixed plupload.js issue with Internet Explorer.
+- #1814744 by slashrsm, Kevin Hankens: File URI not available to hook_file_validate() implementations.
+- #1895328 by larowlan, slashrsm: Fixed Security exploit in plupload external library examples folder.
+- #1230632 by -enzo-, slashrsm: Fixed hard-coded file size limits.
+- by slashrsm: Fix CodeSniffer errors.
+- #1827368 by Kevin Hankens: Fixed Use standard language when validating files.
+- #1565898 by slashrsm: Create docs for Plupload element usage in README.txt.
+- #1111036 by slashrsm: Blacklist script extensions if 'allow_insecure_uploads' isn't set to TRUE.
+- #1426088 by slashrsm: Anonymous User can't upload.
+
+Plupload integration 7.x-1.0
+=================================
+- #1414486 by nbucknor, slashrsm: Improvements of error handling for multiple plupload elements on a single form.
+
+Plupload integration 7.x-1.0-rc1
+==================================
+- by slashrsm: Fix 3 Coder review notices.
+- by slashrsm: Removed unnecessary popup caused by previous commit.
+- #1414486 by nbucknor, annblack: Allow for Multiple Plupload elements in a form.
+- #1461884 by atlea: File uploads larger than php post_max_size broken
+- #1445172 by Moloc: Use drupal_unlink() instead unlink()
+
+Plupload integration 7.x-1.0-beta4
+==================================
+- #1230632 by ksenzee: Hard-coded file size limits.
+- #1219992 by slashrsm, axe312, Dave Reid: Add support for Drupal native translations.
+- #1111054 by George Petsagourakis: Convert plupload_fix_temporary_filename() a private function
+- #1316438 by jcfiala, slashrsm: Plupload in AJAX call causes form.submit handler to be added multiple times
+- #1300620 by jamiecuthill, ksenzee: Update to 1.5.1.1 because uploading is broken in Firefox 7
+- #1121070 by Moloc: Warning: unlink(...) [function.unlink]: Permission denied in plupload_handle_uploads()
+- #1348892 by slashrsm, Moloc: Show library version in status report
+- #1267190 by Moloc: Plupload transliteration support for d7
+- #1240654 by Dave Reid: Use a proper namespace for file_uri_to_object().
diff --git a/sites/all/modules/plupload/LICENSE.txt b/sites/all/modules/plupload/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/plupload/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/plupload/README.txt b/sites/all/modules/plupload/README.txt
new file mode 100644
index 000000000..4e83d0843
--- /dev/null
+++ b/sites/all/modules/plupload/README.txt
@@ -0,0 +1,89 @@
+This module integrates the Plupload library (available from http://plupload.com)
+with Drupal forms. To install the Plupload library:
+
+1. Download it (version 1.5.1.1 or later) from
+ https://github.com/moxiecode/plupload/releases Version 2.0.0
+ is currently unsupported. Latest 1.x.x version is 1.5.8, which can be
+ downloaded from https://github.com/moxiecode/plupload/archive/v1.5.8.zip.
+2. Unzip it into sites/all/libraries, so that there's a
+ sites/all/libraries/plupload/js/plupload.full.js file, in addition to the
+ other files included in the library.
+3. Remove "examples" folder from libraries folder as it could constitute a
+ security risk to your site. See http://drupal.org/node/1895328 and
+ http://drupal.org/node/1189632 for more info.
+
+If you would like to use an alternate library location, you can install the
+http://drupal.org/project/libraries module and/or add
+
+ $conf['plupload_library_path'] = PATH/TO/PLUPLOAD;
+
+to your settings.php file.
+
+At this time, this module only provides a 'plupload' form element type that
+other modules can use for providing multiple file upload capability to their
+forms. It does not provide any end-user functionality on its own. This may
+change, however, as this module evolves. See http://drupal.org/node/880300.
+
+---=== For developers ===---
+
+Plupload from element can be used like this:
+
+$form['my_element'] = array(
+ '#type' => 'plupload',
+ '#title' => t('Upload files'),
+ '#description' => t('This multi-upload widget uses Plupload library.'),
+ '#autoupload' => TRUE,
+ '#autosubmit' => TRUE,
+ '#submit_element' => '#id-of-your-submit-element',
+ '#upload_validators' => array(
+ 'file_validate_extensions' => array('jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'),
+ 'my_custom_file_validator' => array('some validation criteria'),
+ );
+ '#plupload_settings' => array(
+ 'runtimes' => 'html5',
+ 'chunk_size' => '1mb',
+ ),
+ '#event_callbacks' => array(
+ 'FilesAdded' => 'Drupal.mymodule.filesAddedCallback',
+ 'UploadComplete' => 'Drupal.mymodule.uploadCompleteCallback',
+ ),
+);
+
+There are few optional properties of this array that have special meaning:
+
+- #autoupload: set this to TRUE if you want Plupload to start uploading
+ immediately after files are added.
+ Defaults to FALSE.
+
+- #autosubmit: set this to TRUE if you want Plupload to autosubmit
+ your form after automatic upload has finished.
+ Defaults to FALSE.
+ Has to be used in combination with #autoupload.
+
+- #submit_element: specify which submit element Plupload shall use to submit
+ the form. Can also be used in combination with #autoupload and #autosubmit.
+ See: http://drupal.org/node/1935256
+
+- #upload_validators - an array of validation function/validation criteria pairs,
+ that will be passed to file_validate().
+ Defaults to:
+ '#upload_validators' => array(
+ 'file_validate_extensions' => array('jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'),
+ );
+
+- #plupload_settings - array of settings, that will be passed to Plupload library.
+ See: http://www.plupload.com/documentation.php
+ Defaults to:
+ '#plupload_settings' => array(
+ 'runtimes' => 'html5,flash,html4',
+ 'url' => url('plupload-handle-uploads', array('query' => array('plupload_token' => drupal_get_token('plupload-handle-uploads')))),
+ 'max_file_size' => file_upload_max_size() . 'b',
+ 'chunk_size' => '1mb',
+ 'unique_names' => TRUE,
+ 'flash_swf_url' => file_create_url($library_path . '/js/plupload.flash.swf'),
+ 'silverlight_xap_url' => file_create_url($library_path . '/js/plupload.silverlight.xap'),
+ ),
+
+- #event_callbacks - array of callbacks that will be passed to js.
+ See full documentation about events in Plupload library:
+ http://www.plupload.com/example_events.php
diff --git a/sites/all/modules/plupload/images/add.png b/sites/all/modules/plupload/images/add.png
new file mode 100644
index 000000000..1a2faf656
--- /dev/null
+++ b/sites/all/modules/plupload/images/add.png
Binary files differ
diff --git a/sites/all/modules/plupload/images/backgrounds.gif b/sites/all/modules/plupload/images/backgrounds.gif
new file mode 100644
index 000000000..39e33ebc0
--- /dev/null
+++ b/sites/all/modules/plupload/images/backgrounds.gif
Binary files differ
diff --git a/sites/all/modules/plupload/images/buttons-disabled.png b/sites/all/modules/plupload/images/buttons-disabled.png
new file mode 100644
index 000000000..afa11af9b
--- /dev/null
+++ b/sites/all/modules/plupload/images/buttons-disabled.png
Binary files differ
diff --git a/sites/all/modules/plupload/images/buttons.png b/sites/all/modules/plupload/images/buttons.png
new file mode 100644
index 000000000..153e73885
--- /dev/null
+++ b/sites/all/modules/plupload/images/buttons.png
Binary files differ
diff --git a/sites/all/modules/plupload/images/delete.gif b/sites/all/modules/plupload/images/delete.gif
new file mode 100644
index 000000000..78ca8b3b4
--- /dev/null
+++ b/sites/all/modules/plupload/images/delete.gif
Binary files differ
diff --git a/sites/all/modules/plupload/images/done.gif b/sites/all/modules/plupload/images/done.gif
new file mode 100644
index 000000000..29f3ed7c9
--- /dev/null
+++ b/sites/all/modules/plupload/images/done.gif
Binary files differ
diff --git a/sites/all/modules/plupload/images/error.gif b/sites/all/modules/plupload/images/error.gif
new file mode 100644
index 000000000..4682b6300
--- /dev/null
+++ b/sites/all/modules/plupload/images/error.gif
Binary files differ
diff --git a/sites/all/modules/plupload/images/icon-feed.png b/sites/all/modules/plupload/images/icon-feed.png
new file mode 100755
index 000000000..9523eaba6
--- /dev/null
+++ b/sites/all/modules/plupload/images/icon-feed.png
Binary files differ
diff --git a/sites/all/modules/plupload/images/throbber.gif b/sites/all/modules/plupload/images/throbber.gif
new file mode 100644
index 000000000..4ae8b16a5
--- /dev/null
+++ b/sites/all/modules/plupload/images/throbber.gif
Binary files differ
diff --git a/sites/all/modules/plupload/images/transp50.png b/sites/all/modules/plupload/images/transp50.png
new file mode 100644
index 000000000..eb0efe104
--- /dev/null
+++ b/sites/all/modules/plupload/images/transp50.png
Binary files differ
diff --git a/sites/all/modules/plupload/images/up_arrow.png b/sites/all/modules/plupload/images/up_arrow.png
new file mode 100644
index 000000000..36e00c249
--- /dev/null
+++ b/sites/all/modules/plupload/images/up_arrow.png
Binary files differ
diff --git a/sites/all/modules/plupload/images/up_arrow_disabled.png b/sites/all/modules/plupload/images/up_arrow_disabled.png
new file mode 100644
index 000000000..5233594a8
--- /dev/null
+++ b/sites/all/modules/plupload/images/up_arrow_disabled.png
Binary files differ
diff --git a/sites/all/modules/plupload/images/video-indicator.png b/sites/all/modules/plupload/images/video-indicator.png
new file mode 100644
index 000000000..d9eb1361c
--- /dev/null
+++ b/sites/all/modules/plupload/images/video-indicator.png
Binary files differ
diff --git a/sites/all/modules/plupload/js/i18n.js b/sites/all/modules/plupload/js/i18n.js
new file mode 100644
index 000000000..01f858401
--- /dev/null
+++ b/sites/all/modules/plupload/js/i18n.js
@@ -0,0 +1,41 @@
+/**
+ * @file
+ * Localization file for Plupload's strings.
+ */
+
+// Add translations.
+plupload.addI18n({
+ "Select files" : Drupal.t("Select files"),
+ "Add files to the upload queue and click the start button." : Drupal.t("Add files to the upload queue and click the start button."),
+ "Filename" : Drupal.t("Filename"),
+ "Status" : Drupal.t("Status"),
+ "Size" : Drupal.t("Size"),
+ "Add files" : Drupal.t("Add files"),
+ "Stop current upload" : Drupal.t("Stop current upload"),
+ "Start uploading queue" : Drupal.t("Start uploading queue"),
+ "Uploaded %d/%d files": Drupal.t("Uploaded %d/%d files"),
+ "N/A" : Drupal.t("N/A"),
+ "Drag files here." : Drupal.t("Drag files here."),
+ "File extension error.": Drupal.t("File extension error."),
+ "File size error.": Drupal.t("File size error."),
+ "Init error.": Drupal.t("Init error."),
+ "HTTP Error.": Drupal.t("HTTP Error."),
+ "Security error.": Drupal.t("Security error."),
+ "Generic error.": Drupal.t("Generic error."),
+ "IO error.": Drupal.t("IO error."),
+ "Start upload" : Drupal.t("Start upload"),
+ "Stop upload" : Drupal.t("Stop upload"),
+ "%d files queued" : Drupal.t("%d files queued"),
+ "Drag files here." : Drupal.t("Drag files here."),
+ "Start upload" : Drupal.t("Start upload"),
+ "%d files queued" : Drupal.t("%d files queued"),
+ "File: %s" : Drupal.t("File: %s"),
+ "Close" : Drupal.t("Close"),
+ "Using runtime: " : Drupal.t("Using runtime: "),
+ "File: %f, size: %s, max file size: %m" : Drupal.t("File: %f, size: %s, max file size: %m"),
+ "Upload element accepts only %d file(s) at a time. Extra files were stripped." : Drupal.t("Upload element accepts only %d file(s) at a time. Extra files were stripped."),
+ "Upload URL might be wrong or doesn\"t exist" : Drupal.t("Upload URL might be wrong or doesn\"t exist"),
+ "Error: File too large: " : Drupal.t("Error: File too large: "),
+ "Error: Invalid file extension: " : Drupal.t("Error: Invalid file extension: "),
+ "File count error." : Drupal.t("File count error.")
+});
diff --git a/sites/all/modules/plupload/plupload.css b/sites/all/modules/plupload/plupload.css
new file mode 100644
index 000000000..a8aae7a6f
--- /dev/null
+++ b/sites/all/modules/plupload/plupload.css
@@ -0,0 +1,204 @@
+
+.plupload_button {
+ display:inline-block;
+ font:normal 12px sans-serif;
+ text-decoration:none;
+ color:#42454a;
+ margin-right:4px;
+ outline:0;
+ -moz-border-radius:3px;
+ -khtml-border-radius:3px;
+ -webkit-border-radius:3px;
+ border-radius:3px;
+ font-family:"Lucida Grande", Verdana, sans-serif;
+ border:none;
+ background:none;
+ padding:2px 8px 3px 15px;
+}
+
+.plupload_button:hover {
+ color:#000;
+ text-decoration:none;
+}
+
+.plupload_disabled,a.plupload_disabled:hover {
+ cursor:default;
+ color:#737373;
+ background:transparent url(images/up_arrow_disabled.png) no-repeat 0 center;
+ border-color:#c5c5c5;
+}
+
+.plupload_add {
+ background-position:-181px center;
+ background:transparent url(images/add.png) no-repeat 0 center;
+}
+
+.plupload_wrapper {
+ font:normal 11px Verdana,sans-serif;
+ width:100%;
+ font-family:"Lucida Grande", Verdana, sans-serif;
+}
+
+.plupload_container {
+ background:url('images/transp50.png');
+}
+
+.plupload_container input {
+ border:1px solid #DDD;
+ font:normal 11px Verdana,sans-serif;
+ width:98%;
+ font-family:"Lucida Grande", Verdana, sans-serif;
+}
+
+.plupload_scroll .plupload_filelist {
+ height:135px;
+ overflow-y:scroll;
+ background:none;
+ list-style:none;
+ margin:0;
+ padding:0;
+}
+
+.plupload_filelist li {
+ background:#F5F5F5 url('images/backgrounds.gif') repeat-x 0 -156px;
+ border-bottom:1px solid #DDD;
+ padding:10px 8px;
+}
+
+.plupload_filelist_header,.plupload_filelist_footer {
+ background:#DFDFDF;
+ color:#42454A;
+ padding:6px 8px;
+}
+
+.plupload_filelist_header {
+ border-top:1px solid #EEE;
+ border-bottom:1px solid #CDCDCD;
+}
+
+.plupload_filelist_footer {
+ border-top:1px solid #FFF;
+ height:22px;
+ line-height:20px;
+ vertical-align:middle;
+}
+
+.plupload_file_name {
+ float:left;
+ overflow:hidden;
+}
+
+.plupload_file_status {
+ color:#777;
+}
+
+.plupload_file_status span {
+ color:#42454A;
+}
+
+.plupload_file_size,.plupload_file_status,.plupload_progress {
+ float:right;
+ width:80px;
+}
+
+.plupload_file_size,.plupload_file_status,.plupload_file_action {
+ text-align:right;
+}
+
+.plupload_filelist .plupload_file_name {
+ width:205px;
+}
+
+.plupload_file_action {
+ float:right;
+ width:16px;
+ height:16px;
+ margin-left:15px;
+}
+
+.plupload_file_action * {
+ display:none;
+ width:16px;
+ height:16px;
+}
+
+ li.plupload_uploading {
+ background:#ECF3DC url('images/backgrounds.gif') repeat-x 0 -238px;
+}
+
+ li.plupload_done {
+ color:#AAA;
+}
+
+ li.plupload_delete a {
+ background:url('images/delete.gif');
+}
+
+ li.plupload_failed a {
+ background:url('images/error.gif');
+ cursor:default;
+}
+
+ li.plupload_done a {
+ background:url('images/done.gif');
+ cursor:default;
+}
+
+.plupload_progress,.plupload_upload_status {
+ display:none;
+}
+
+.plupload_progress_container {
+ margin-top:3px;
+ border:1px solid #CCC;
+ background:#FFF;
+ padding:1px;
+}
+
+.plupload_progress_bar {
+ width:0;
+ height:7px;
+ background:#CDEB8B;
+}
+
+.plupload_scroll .plupload_filelist_header .plupload_file_action,.plupload_scroll .plupload_filelist_footer .plupload_file_action {
+ margin-right:17px;
+}
+
+.plupload_clear,.plupload_clearer {
+ clear:both;
+}
+
+.plupload_clearer,.plupload_progress_bar {
+ display:block;
+ font-size:0;
+ line-height:0;
+}
+
+li.plupload_droptext {
+ background:transparent;
+ text-align:center;
+ vertical-align:middle;
+ border:0;
+ line-height:115px;
+ list-style-type:none;
+}
+
+.plupload {
+ border:solid 1px #ccc;
+}
+
+.plupload_header {
+ display:none;
+}
+
+.plupload_button,.plupload_button:hover {
+ color:#0074bd;
+ padding-bottom:0;
+ margin-bottom:0;
+}
+
+.plupload_start,a.plupload_start:hover {
+ background:transparent url(images/up_arrow.png) no-repeat 0 center;
+ z-index:100000;
+} \ No newline at end of file
diff --git a/sites/all/modules/plupload/plupload.info b/sites/all/modules/plupload/plupload.info
new file mode 100644
index 000000000..3aae9daa6
--- /dev/null
+++ b/sites/all/modules/plupload/plupload.info
@@ -0,0 +1,12 @@
+name = Plupload integration module
+description = Provides a plupload element.
+files[] = plupload.module
+core = 7.x
+package = Media
+
+; Information added by Drupal.org packaging script on 2014-11-07
+version = "7.x-1.7"
+core = "7.x"
+project = "plupload"
+datestamp = "1415390716"
+
diff --git a/sites/all/modules/plupload/plupload.install b/sites/all/modules/plupload/plupload.install
new file mode 100644
index 000000000..85cddda6a
--- /dev/null
+++ b/sites/all/modules/plupload/plupload.install
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Plupload module.
+ */
+
+/**
+ * Implements hook_requirements().
+ */
+function plupload_requirements($phase) {
+ $requirements = array();
+ $t = get_t();
+
+ if ($phase == 'runtime') {
+ $requirements['plupload'] = array(
+ 'title' => $t('Plupload library'),
+ 'value' => $t('Unknown'),
+ );
+ $requirements['plupload']['severity'] = REQUIREMENT_OK;
+ $libraries = plupload_library();
+ $library = $libraries['plupload'];
+
+ // Check if Plupload library exists. Try to determine it's version
+ // if it does.
+ if (!_plupload_requirements_installed()) {
+ $message = 'The <a href="@url">@title</a> library (version @version or higher) is not installed.';
+ $args = array(
+ '@title' => $library['title'],
+ '@url' => url($library['website']),
+ '@version' => $library['version'],
+ );
+
+ $requirements['plupload']['description'] = $t($message, $args);
+ $requirements['plupload']['severity'] = REQUIREMENT_ERROR;
+ }
+ elseif (($installed_version = _plupload_requirements_version()) === NULL) {
+ $requirements['plupload']['description'] = $t('Plupload version could not be determined.');
+ $requirements['plupload']['severity'] = REQUIREMENT_INFO;
+ }
+ elseif (!version_compare($library['version'], $installed_version, '<=')) {
+ $requirements['plupload']['description'] = $t('Plupload @version or higher is required.', array('@version' => $library['version']));
+ $requirements['plupload']['severity'] = REQUIREMENT_INFO;
+ }
+
+ $requirements['plupload']['value'] = empty($installed_version) ? $t('Not found') : $installed_version;
+
+ if (file_exists(_plupload_library_path() . '/examples/upload.php')) {
+ $requirements['plupload_examples'] = array(
+ 'title' => $t('Plupload example folder'),
+ 'value' => $t('Example folder found'),
+ 'description' => $t('Plupload library contains example files, these could constitute a security risk to your site as per <a href="!url">PSA-2011-02</a>. Please remove the !path folder immediately.', array(
+ '!url' => 'http://drupal.org/node/1189632',
+ '!path' => _plupload_library_path() . '/examples'
+ )),
+ 'severity' => REQUIREMENT_ERROR
+ );
+ }
+ }
+
+ return $requirements;
+}
+
+/**
+ * Checks wether Plupload library exists or not.
+ *
+ * @return boolean
+ * TRUE if plupload library installed, FALSE otherwise.
+ */
+function _plupload_requirements_installed() {
+ $libraries = plupload_library();
+ $library = $libraries['plupload'];
+
+ // We grab the first file and check if it exists.
+ $testfile = key($library['js']);
+ if (!file_exists($testfile)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Returns the version of the installed plupload library.
+ *
+ * @return string
+ * The version of installed Plupload or NULL if unable to detect version.
+ */
+function _plupload_requirements_version() {
+ $library_path = _plupload_library_path();
+ $jspath = $library_path . '/js/plupload.js';
+
+ // Read contents of Plupload's javascript file.
+ $configcontents = @file_get_contents($jspath);
+ if (!$configcontents) {
+ return NULL;
+ }
+
+ // Search for version string using a regular expression.
+ $matches = array();
+ if (preg_match('#VERSION:\"(\d+[\.\d+]*)\"#', $configcontents, $matches)) {
+ return $matches[1];
+ }
+
+ // After 1.5.8 Plupload stopped adding version string to .js file. Let's check
+ // first line of changelog.txt.
+ $library_path = _plupload_library_path();
+ $clpath = $library_path . '/changelog.txt';
+
+ $configcontents = @file_get_contents($clpath);
+ if (!$configcontents) {
+ return NULL;
+ }
+
+ $lines = explode("\n", $configcontents);
+
+ if (preg_match('#^Version (\d+[\.\d+]*)#', $lines[0], $matches)) {
+ return $matches[1];
+ }
+
+ return NULL;
+}
diff --git a/sites/all/modules/plupload/plupload.js b/sites/all/modules/plupload/plupload.js
new file mode 100644
index 000000000..91a5c6b21
--- /dev/null
+++ b/sites/all/modules/plupload/plupload.js
@@ -0,0 +1,178 @@
+(function($) {
+
+Drupal.plupload = Drupal.plupload || {};
+// Add Plupload events for autoupload and autosubmit.
+Drupal.plupload.filesAddedCallback = function (up, files) {
+ setTimeout(function(){up.start()}, 100);
+};
+Drupal.plupload.uploadCompleteCallback = function(up, files) {
+ var $this = $("#"+up.settings.container);
+ // If there is submit_element trigger it.
+ var submit_element = window.Drupal.settings.plupload[$this.attr('id')].submit_element;
+ if (submit_element) {
+ $(submit_element).click();
+ }
+ // Otherwise submit default form.
+ else {
+ var $form = $this.parents('form');
+ $($form[0]).submit();
+ }
+};
+/**
+ * Attaches the Plupload behavior to each Plupload form element.
+ */
+Drupal.behaviors.plupload = {
+ attach: function (context, settings) {
+ $(".plupload-element", context).once('plupload-init', function () {
+ var $this = $(this);
+
+ // Merge the default settings and the element settings to get a full
+ // settings object to pass to the Plupload library for this element.
+ var id = $this.attr('id');
+ var defaultSettings = settings.plupload['_default'] ? settings.plupload['_default'] : {};
+ var elementSettings = (id && settings.plupload[id]) ? settings.plupload[id] : {};
+ var pluploadSettings = $.extend({}, defaultSettings, elementSettings);
+
+ // Process Plupload events.
+ if (elementSettings['init'] || false) {
+ if (!pluploadSettings.init) {
+ pluploadSettings.init = {};
+ }
+ for (var key in elementSettings['init']) {
+ var callback = elementSettings['init'][key].split('.');
+ var fn = window;
+ for (var j = 0; j < callback.length; j++) {
+ fn = fn[callback[j]];
+ }
+ if (typeof fn === 'function') {
+ pluploadSettings.init[key] = fn;
+ }
+ }
+ }
+ // Initialize Plupload for this element.
+ $this.pluploadQueue(pluploadSettings);
+
+ });
+ }
+};
+
+ /**
+ * Attaches the Plupload behavior to each Plupload form element.
+ */
+Drupal.behaviors.pluploadform = {
+ attach: function(context, settings) {
+ $('form', context).once('plupload-form', function() {
+ if (0 < $(this).find('.plupload-element').length) {
+ var $form = $(this);
+ var originalFormAttributes = {
+ 'method': $form.attr('method'),
+ 'enctype': $form.attr('enctype'),
+ 'action': $form.attr('action'),
+ 'target': $form.attr('target')
+ };
+
+ $(this).submit(function(e) {
+ var completedPluploaders = 0;
+ var totalPluploaders = $(this).find('.plupload-element').length;
+ var errors = '';
+
+ $(this).find('.plupload-element').each( function(index){
+ var uploader = $(this).pluploadQueue();
+
+ var id = $(this).attr('id');
+ var defaultSettings = settings.plupload['_default'] ? settings.plupload['_default'] : {};
+ var elementSettings = (id && settings.plupload[id]) ? settings.plupload[id] : {};
+ var pluploadSettings = $.extend({}, defaultSettings, elementSettings);
+
+ //Only allow the submit to proceed if there are files and they've all
+ //completed uploading.
+ //TODO: Implement a setting for whether the field is required, rather
+ //than assuming that all are.
+ if (uploader.state == plupload.STARTED) {
+ errors += Drupal.t("Please wait while your files are being uploaded.");
+ }
+ else if (uploader.files.length == 0 && !pluploadSettings.required) {
+ completedPluploaders++;
+ }
+
+ else if (uploader.files.length == 0) {
+ errors += Drupal.t("@index: You must upload at least one file.\n",{'@index': (index + 1)});
+ }
+
+ else if (uploader.files.length > 0 && uploader.total.uploaded == uploader.files.length) {
+ completedPluploaders++;
+ }
+
+ else {
+ var stateChangedHandler = function() {
+ if (uploader.total.uploaded == uploader.files.length) {
+ uploader.unbind('StateChanged', stateChangedHandler);
+ completedPluploaders++;
+ if (completedPluploaders == totalPluploaders ) {
+ //Plupload's html4 runtime has a bug where it changes the
+ //attributes of the form to handle the file upload, but then
+ //fails to change them back after the upload is finished.
+ for (var attr in originalFormAttributes) {
+ $form.attr(attr, originalFormAttributes[attr]);
+ }
+ // Click a specific element if one is specified.
+ if (settings.plupload[id].submit_element) {
+ $(settings.plupload[id].submit_element).click();
+ }
+ else {
+ $form.submit();
+ }
+ return true;
+ }
+ }
+ };
+ uploader.bind('StateChanged', stateChangedHandler);
+ uploader.start();
+ }
+
+ });
+ if (completedPluploaders == totalPluploaders) {
+ //Plupload's html4 runtime has a bug where it changes the
+ //attributes of the form to handle the file upload, but then
+ //fails to change them back after the upload is finished.
+ for (var attr in originalFormAttributes) {
+ $form.attr(attr, originalFormAttributes[attr]);
+ }
+ return true;
+ }
+ else if (0 < errors.length){
+ alert(errors);
+ }
+
+ return false;
+ });
+ }
+ });
+ }
+};
+
+
+/**
+ * Helper function to compare version strings.
+ *
+ * Returns one of:
+ * - A negative integer if a < b.
+ * - A positive integer if a > b.
+ * - 0 if a == b.
+ */
+Drupal.plupload.compareVersions = function (a, b) {
+ a = a.split('.');
+ b = b.split('.');
+ // Return the most significant difference, if there is one.
+ for (var i=0; i < Math.min(a.length, b.length); i++) {
+ var compare = parseInt(a[i]) - parseInt(b[i]);
+ if (compare != 0) {
+ return compare;
+ }
+ }
+ // If the common parts of the two version strings are equal, the greater
+ // version number is the one with the most sections.
+ return a.length - b.length;
+}
+
+})(jQuery);
diff --git a/sites/all/modules/plupload/plupload.make b/sites/all/modules/plupload/plupload.make
new file mode 100644
index 000000000..1c6463103
--- /dev/null
+++ b/sites/all/modules/plupload/plupload.make
@@ -0,0 +1,9 @@
+api = 2
+core = 7.x
+
+; Libraries
+libraries[plupload][directory_name] = plupload
+libraries[plupload][download][type] = file
+libraries[plupload][download][url] = https://github.com/moxiecode/plupload/archive/v1.5.8.zip
+libraries[plupload][patch][1903850] = https://drupal.org/files/issues/plupload-1_5_8-rm_examples-1903850-16.patch
+libraries[plupload][type] = library
diff --git a/sites/all/modules/plupload/plupload.module b/sites/all/modules/plupload/plupload.module
new file mode 100644
index 000000000..e187f7a57
--- /dev/null
+++ b/sites/all/modules/plupload/plupload.module
@@ -0,0 +1,523 @@
+<?php
+
+/**
+ * @file
+ * Implementation of plupload.module.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function plupload_menu() {
+ $items['plupload-handle-uploads'] = array(
+ 'title' => 'Handles uploads',
+ 'page callback' => 'plupload_handle_uploads',
+ 'type' => MENU_CALLBACK,
+ 'access callback' => 'plupload_upload_access',
+ 'access arguments' => array('access content'),
+ );
+ $items['plupload-test'] = array(
+ 'title' => 'Test Plupload',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('plupload_test'),
+ // @todo: change this to something appropriate, not sure what.
+ 'access arguments' => array('Administer site configuration'),
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+}
+
+/**
+ * Verifies the token for this request.
+ */
+function plupload_upload_access() {
+ foreach (func_get_args() as $permission) {
+ if (!user_access($permission)) {
+ return FALSE;
+ }
+ }
+ return !empty($_REQUEST['plupload_token']) && drupal_valid_token($_REQUEST['plupload_token'], 'plupload-handle-uploads');
+}
+
+/**
+ * Form callback function for test page visible at URL "plupload-test".
+ */
+function plupload_test($form, &$form_state) {
+ $form['pud'] = array(
+ '#type' => 'plupload',
+ '#title' => 'Plupload',
+ // '#validators' => array(...);
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => 'Submit',
+ );
+
+ return $form;
+}
+
+/**
+ * Submit callback for plupload_test form.
+ */
+function plupload_test_submit($form, &$form_state) {
+ $saved_files = array();
+ $scheme = variable_get('file_default_scheme', 'public') . '://';
+ // We can't use file_save_upload() because of
+ // http://www.jacobsingh.name/content/tight-coupling-no-not
+ // file_uri_to_object();
+ foreach ($form_state['values']['pud'] as $uploaded_file) {
+ if ($uploaded_file['status'] == 'done') {
+ $source = $uploaded_file['tmppath'];
+ $destination = file_stream_wrapper_uri_normalize($scheme . $uploaded_file['name']);
+ // Rename it to its original name, and put it in its final home.
+ // Note - not using file_move here because if we call file_get_mime
+ // (in file_uri_to_object) while it has a .tmp extension, it horks.
+ $destination = file_unmanaged_move($source, $destination, FILE_EXISTS_RENAME);
+ $file = plupload_file_uri_to_object($destination);
+ file_save($file);
+ $saved_files[] = $file;
+ }
+ else {
+ // @todo: move this to element validate or something and clean up t().
+ form_set_error('pud', "Upload of {$uploaded_file['name']} failed");
+ }
+ }
+}
+
+/**
+ * Implements hook_element_info().
+ */
+function plupload_element_info() {
+ $types = array();
+ $module_path = drupal_get_path('module', 'plupload');
+ $types['plupload'] = array(
+ '#input' => TRUE,
+ '#attributes' => array('class' => array('plupload-element')),
+ // @todo
+ // '#element_validate' => array('file_managed_file_validate'),
+ '#theme_wrappers' => array('form_element'),
+ '#theme' => 'container',
+ '#value_callback' => 'plupload_element_value',
+ '#attached' => array(
+ 'library' => array(array('plupload', 'plupload')),
+ 'js' => array($module_path . '/plupload.js'),
+ 'css' => array($module_path . '/plupload.css'),
+ ),
+ '#process' => array('plupload_element_process'),
+ '#element_validate' => array('plupload_element_validate'),
+ '#pre_render' => array('plupload_element_pre_render'),
+ );
+ return $types;
+}
+
+/**
+ * Validate callback for plupload form element.
+ */
+function plupload_element_value(&$element, $input = FALSE, $form_state = NULL) {
+ $id = $element['#id'];
+ // If a unique identifier added with '--', we need to exclude it
+ if(preg_match('/(.*)(--[0-9]+)$/', $id, $reg)) {
+ $id = $reg[1];
+ }
+ $files = array();
+ foreach ($form_state['input'] as $key => $value) {
+ if (preg_match('/' . $id . '_([0-9]+)_(.*)/', $key, $reg)) {
+ $i = $reg[1];
+ $key = $reg[2];
+
+ // Only add the keys we expect.
+ if (!in_array($key, array('tmpname', 'name', 'status'))) {
+ continue;
+ }
+
+ // Munge the submitted file names for security.
+ //
+ // Similar munging is normally done by file_save_upload(), but submit
+ // handlers for forms containing plupload elements can't use
+ // file_save_upload(), for reasons discussed in plupload_test_submit().
+ // So we have to do this for them.
+ //
+ // Note that we do the munging here in the value callback function
+ // (rather than during form validation or elsewhere) because we want to
+ // actually modify the submitted values rather than reject them outright;
+ // file names that require munging can be innocent and do not necessarily
+ // indicate an attempted exploit. Actual validation of the file names is
+ // performed later, in plupload_element_validate().
+ if (in_array($key, array('tmpname', 'name'))) {
+ // Find the whitelist of extensions to use when munging. If there are
+ // none, we'll be adding default ones in plupload_element_process(), so
+ // use those here.
+ if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
+ $extensions = $element['#upload_validators']['file_validate_extensions'][0];
+ }
+ else {
+ $validators = _plupload_default_upload_validators();
+ $extensions = $validators['file_validate_extensions'][0];
+ }
+ $value = file_munge_filename($value, $extensions, FALSE);
+ // To prevent directory traversal issues, make sure the file name does
+ // not contain any directory components in it. (This more properly
+ // belongs in the form validation step, but it's simpler to do here so
+ // that we don't have to deal with the temporary file names during form
+ // validation and can just focus on the final file name.)
+ //
+ // This step is necessary since this module allows a large amount of
+ // flexibility in where its files are placed (for example, they could
+ // be intended for public://subdirectory rather than public://, and we
+ // don't want an attacker to be able to get them back into the top
+ // level of public:// in that case).
+ $value = rtrim(drupal_basename($value), '.');
+
+
+ // Based on the same feture from file_save_upload().
+ if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $value) && (substr($value, -4) != '.txt')) {
+ $value .= '.txt';
+
+ // The .txt extension may not be in the allowed list of extensions.
+ // We have to add it here or else the file upload will fail.
+ if (!empty($extensions)) {
+ $element['#upload_validators']['file_validate_extensions'][0] .= ' txt';
+ drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $value)));
+ }
+ }
+ }
+
+ // The temporary file name has to be processed further so it matches what
+ // was used when the file was written; see plupload_handle_uploads().
+ if ($key == 'tmpname') {
+ $value = _plupload_fix_temporary_filename($value);
+ // We also define an extra key 'tmppath' which is useful so that submit
+ // handlers do not need to know which directory plupload stored the
+ // temporary files in before trying to copy them.
+ $files[$i]['tmppath'] = variable_get('plupload_temporary_uri', 'temporary://') . $value;
+ }
+ elseif ($key == 'name') {
+ if (module_exists('transliteration') && variable_get('transliteration_file_uploads', TRUE)) {
+ $value = transliteration_clean_filename($value);
+ }
+ }
+
+ // Store the final value in the array we will return.
+ $files[$i][$key] = $value;
+ }
+ }
+ return $files;
+}
+
+/**
+ * Process callback (#process) for plupload form element.
+ */
+function plupload_element_process($element) {
+ // Start session if not there yet. We need session if we want security
+ // tokens to work properly.
+ if (!drupal_session_started()) {
+ drupal_session_start();
+ }
+
+ if (!isset($element['#upload_validators'])) {
+ $element['#upload_validators'] = array();
+ }
+ $element['#upload_validators'] += _plupload_default_upload_validators();
+ return $element;
+}
+
+/**
+ * Element validation handler for a Plupload element.
+ */
+function plupload_element_validate($element, &$form_state) {
+ foreach ($element['#value'] as $file_info) {
+ // Here we create a $file object for a file that doesn't exist yet,
+ // because saving the file to its destination is done in a submit handler.
+ // Using tmp path will give validators access to the actual file on disk and
+ // filesize information. We manually modify filename and mime to allow
+ // extension checks.
+ $file = plupload_file_uri_to_object($file_info['tmppath']);
+
+ $destination = variable_get('file_default_scheme', 'public') . '://' . $file_info['name'];
+ $destination = file_stream_wrapper_uri_normalize($destination);
+ $file->filename = drupal_basename($destination);
+ $file->filemime = file_get_mimetype($destination);
+
+ foreach (file_validate($file, $element['#upload_validators']) as $error_message) {
+ $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
+ form_error($element, $message . ' ' . $error_message);
+ }
+ }
+}
+
+/**
+ * Pre render (#pre_render) callback to attach JS settings for the element.
+ */
+function plupload_element_pre_render($element) {
+ $settings = isset($element['#plupload_settings']) ? $element['#plupload_settings'] : array();
+
+ // The Plupload library supports client-side validation of file extension, so
+ // pass along the information for it to do that. However, as with all client-
+ // side validation, this is a UI enhancement only, and not a replacement for
+ // server-side validation.
+ if (empty($settings['filters']) && isset($element['#upload_validators']['file_validate_extensions'][0])) {
+ $settings['filters'][] = array(
+ // @todo Some runtimes (e.g., flash) require a non-empty title for each
+ // filter, but I don't know what this title is used for. Seems a shame
+ // to hard-code it, but what's a good way to avoid that?
+ 'title' => t('Allowed files'),
+ 'extensions' => str_replace(' ', ',', $element['#upload_validators']['file_validate_extensions'][0]),
+ );
+ }
+ // Check for autoupload and autosubmit settings and add appropriate callback.
+ if (!empty($element['#autoupload'])) {
+ $settings['init']['FilesAdded'] = 'Drupal.plupload.filesAddedCallback';
+ if (!empty($element['#autosubmit'])) {
+ $settings['init']['UploadComplete'] = 'Drupal.plupload.uploadCompleteCallback';
+ }
+ }
+ // Add a specific submit element that we want to click if one is specified.
+ if (!empty($element['#submit_element'])) {
+ $settings['submit_element'] = $element['#submit_element'];
+ }
+ // Check if there are event callbacks and append them to current ones, if any.
+ if (!empty($element['#event_callbacks'])) {
+ // array_merge() only accepts parameters of type array.
+ if (!isset($settings['init'])) {
+ $settings['init'] = array();
+ }
+ $settings['init'] = array_merge($settings['init'], $element['#event_callbacks']);
+ }
+
+ if (empty($element['#description'])) {
+ $element['#description'] = '';
+ }
+ $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
+
+ $element['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => array('plupload' => array($element['#id'] => $settings)),
+ );
+
+ return $element;
+}
+
+/**
+ * Returns the path to the plupload library.
+ */
+function _plupload_library_path() {
+ return variable_get('plupload_library_path', module_exists('libraries') ? libraries_get_path('plupload') : 'sites/all/libraries/plupload');
+}
+
+/**
+ * Implements hook_library().
+ */
+function plupload_library() {
+ $library_path = _plupload_library_path();
+ $libraries['plupload'] = array(
+ 'title' => 'Plupload',
+ 'website' => 'http://www.plupload.com',
+ 'version' => '1.5.1.1',
+ 'js' => array(
+ // @todo - only add gears JS if gears is an enabled runtime.
+ // $library_path . '/js/gears_init.js' => array(),
+ $library_path . '/js/jquery.plupload.queue/jquery.plupload.queue.js' => array(),
+ $library_path . '/js/plupload.full.js' => array(),
+ 0 => array(
+ 'type' => 'setting',
+ 'data' => array(
+ 'plupload' => array(
+ // Element-specific settings get keyed by the element id (see
+ // plupload_element_pre_render()), so put default settings in
+ // '_default' (Drupal element ids do not have underscores, because
+ // they have hyphens instead).
+ '_default' => array(
+ // @todo Provide a settings page for configuring these.
+ 'runtimes' => 'html5,flash,html4',
+ 'url' => url('plupload-handle-uploads', array('query' => array('plupload_token' => drupal_get_token('plupload-handle-uploads')))),
+ 'max_file_size' => file_upload_max_size() . 'b',
+ 'chunk_size' => parse_size(ini_get('post_max_size')) . 'b',
+ 'unique_names' => TRUE,
+ 'flash_swf_url' => file_create_url($library_path . '/js/plupload.flash.swf'),
+ 'silverlight_xap_url' => file_create_url($library_path . '/js/plupload.silverlight.xap'),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ if (module_exists('locale')) {
+ $module_path = drupal_get_path('module', 'plupload');
+ $libraries['plupload']['js'][$module_path . '/js/i18n.js'] = array('scope' => 'footer');
+ }
+
+ return $libraries;
+}
+
+/**
+ * Callback that handles and saves uploaded files.
+ *
+ * This will respond to the URL on which plupoad library will upload files.
+ */
+function plupload_handle_uploads() {
+ // @todo: Implement file_validate_size();
+ // Added a variable for this because in HA environments, temporary may need
+ // to be a shared location for this to work.
+ $temp_directory = variable_get('plupload_temporary_uri', 'temporary://');
+ $writable = file_prepare_directory($temp_directory, FILE_CREATE_DIRECTORY);
+ if (!$writable) {
+ die('{"jsonrpc" : "2.0", "error" : {"code": 104, "message": "Failed to open temporary directory."}, "id" : "id"}');
+ }
+ // Try to make sure this is private via htaccess.
+ file_create_htaccess($temp_directory, TRUE);
+
+ // Chunk it?
+ $chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0;
+
+ // Get and clean the filename.
+ $file_name = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
+ $file_name = _plupload_fix_temporary_filename($file_name);
+
+ // Check the file name for security reasons; it must contain letters, numbers
+ // and underscores followed by a (single) ".tmp" extension. Since this check
+ // is more stringent than the one performed in plupload_element_value(), we
+ // do not need to run the checks performed in that function here. This is
+ // fortunate, because it would be difficult for us to get the correct list of
+ // allowed extensions to pass in to file_munge_filename() from this point in
+ // the code (outside the form API).
+ if (empty($file_name) || !preg_match('/^\w+\.tmp$/', $file_name)) {
+ die('{"jsonrpc" : "2.0", "error" : {"code": 105, "message": "Invalid temporary file name."}, "id" : "id"}');
+ }
+
+ // Look for the content type header.
+ if (isset($_SERVER["HTTP_CONTENT_TYPE"])) {
+ $content_type = $_SERVER["HTTP_CONTENT_TYPE"];
+ }
+ if (isset($_SERVER["CONTENT_TYPE"])) {
+ $content_type = $_SERVER["CONTENT_TYPE"];
+ }
+
+ // Is this a multipart upload?.
+ if (strpos($content_type, "multipart") !== FALSE) {
+ if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
+ // Open temp file.
+ $out = fopen($temp_directory . $file_name, $chunk == 0 ? "wb" : "ab");
+ if ($out) {
+ // Read binary input stream and append it to temp file.
+ $in = fopen($_FILES['file']['tmp_name'], "rb");
+
+ if ($in) {
+ while ($buff = fread($in, 4096)) {
+ fwrite($out, $buff);
+ }
+ fclose($in);
+ }
+ else {
+ die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
+ }
+
+ fclose($out);
+ drupal_unlink($_FILES['file']['tmp_name']);
+ }
+ else {
+ die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
+ }
+ }
+ else {
+ die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
+ }
+ }
+ else {
+ // Open temp file.
+ $out = fopen($temp_directory . $file_name, $chunk == 0 ? "wb" : "ab");
+ if ($out) {
+ // Read binary input stream and append it to temp file.
+ $in = fopen("php://input", "rb");
+
+ if ($in) {
+ while ($buff = fread($in, 4096)) {
+ fwrite($out, $buff);
+ }
+ fclose($in);
+ }
+ else {
+ die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
+ }
+
+ fclose($out);
+ }
+ else {
+ die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
+ }
+ }
+
+ // Return JSON-RPC response.
+ die('{"jsonrpc" : "2.0", "result" : null, "id" : "id"}');
+}
+
+/**
+ * Returns a file object which can be passed to file_save().
+ *
+ * @param string $uri
+ * A string containing the URI, path, or filename.
+ *
+ * @return boolean
+ * A file object, or FALSE on error.
+ *
+ * @todo Replace with calls to this function with file_uri_to_object() when
+ * http://drupal.org/node/685818 is fixed in core.
+ */
+function plupload_file_uri_to_object($uri) {
+ global $user;
+ $uri = file_stream_wrapper_uri_normalize($uri);
+ $wrapper = file_stream_wrapper_get_instance_by_uri($uri);
+ $file = new StdClass();
+ $file->uid = $user->uid;
+ $file->filename = drupal_basename($uri);
+ $file->uri = $uri;
+ $file->filemime = file_get_mimetype($uri);
+ // This is gagged because some uris will not support it.
+ $file->filesize = @filesize($uri);
+ $file->timestamp = REQUEST_TIME;
+ $file->status = FILE_STATUS_PERMANENT;
+ return $file;
+}
+
+/**
+ * Fix the temporary filename provided by the plupload library.
+ *
+ * Newer versions of the plupload JavaScript library upload temporary files
+ * with names that contain the intended final prefix of the uploaded file
+ * (e.g., ".jpg" or ".png"). Older versions of the plupload library always use
+ * ".tmp" as the temporary file extension.
+ *
+ * We prefer the latter behavior, since although the plupload temporary
+ * directory where these files live is always expected to be private (and we
+ * protect it via .htaccess; see plupload_handle_uploads()), in case it ever
+ * isn't we don't want people to be able to upload files with an arbitrary
+ * extension into that directory.
+ *
+ * This function therefore fixes the plupload temporary filenames so that they
+ * will always use a ".tmp" extension.
+ *
+ * @param string $filename
+ * The original temporary filename provided by the plupload library.
+ *
+ * @return string
+ * The corrected temporary filename, with a ".tmp" extension replacing the
+ * original one.
+ */
+function _plupload_fix_temporary_filename($filename) {
+ $pos = strpos($filename, '.');
+ if ($pos !== FALSE) {
+ $filename = substr_replace($filename, '.tmp', $pos);
+ }
+ return $filename;
+}
+
+/**
+ * Helper function to add defaults to $element['#upload_validators'].
+ */
+function _plupload_default_upload_validators() {
+ return array(
+ // See file_save_upload() for details.
+ 'file_validate_extensions' => array('jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'),
+ );
+}
diff --git a/sites/all/modules/views/D7UPGRADE.txt b/sites/all/modules/views/D7UPGRADE.txt
new file mode 100644
index 000000000..08d3a3b4f
--- /dev/null
+++ b/sites/all/modules/views/D7UPGRADE.txt
@@ -0,0 +1,2 @@
+Information about upgrading existing views from Drupal 6 to Drupal 7 is located
+in the module's advanced help under api upgrading.
diff --git a/sites/all/modules/views/LICENSE.txt b/sites/all/modules/views/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/views/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/views/README.txt b/sites/all/modules/views/README.txt
new file mode 100644
index 000000000..8097eb453
--- /dev/null
+++ b/sites/all/modules/views/README.txt
@@ -0,0 +1,19 @@
+
+Welcome to Views 3. Please see the advanced help for more information.
+
+If you're having trouble installing this module, please ensure that your
+tar program is not flattening the directory tree, truncating filenames
+or losing files.
+
+Installing Views:
+
+Place the entirety of this directory in sites/all/modules/views
+You must also install the CTools module (http://www.drupal.org/project/ctools) to use Views.
+
+Navigate to administer >> build >> modules. Enable Views and Views UI.
+
+If you're new to Views, try the Simple Views module which can create some
+often used Views for you, this might save you some time.
+
+Here you can find many modules extending the functionality of Views:
+ http://drupal.org/taxonomy/term/89
diff --git a/sites/all/modules/views/css/ie/views-admin.ie7.css b/sites/all/modules/views/css/ie/views-admin.ie7.css
new file mode 100644
index 000000000..9a8852a3b
--- /dev/null
+++ b/sites/all/modules/views/css/ie/views-admin.ie7.css
@@ -0,0 +1,91 @@
+/**
+ * The query details collapsible divs are not visible in IE7 because has-layout
+ * is not being triggered. Trigger has-layout with height: 1%;
+ */
+.views-edit-view .collapsible > .fieldset-wrapper {
+ height: 1%;
+}
+
+/**
+ * The column width for the bucket containers in the query details section
+ * is not being calculated to 32% correctly. Give IE7 a slightly smaller
+ * width so that the three columns line up next to each other
+ */
+.views-edit-view .views-display-column {
+ width: 31.95%;
+}
+
+/**
+ * IE7 has no idea how large this container should be and it doesn't
+ * apply has-layout. Expand it's width to 100% and trigger has-layout.
+ */
+.views-edit-view .views-displays {
+ height: 1%;
+ width: 100%;
+}
+
+/**
+ * IE7 isn't positioning the span correctly as a display-inline element
+ */
+.views-edit-view .views-displays .icon {
+ display: block;
+ float: left;
+}
+
+.views-edit-view .views-displays .icon-add {
+ top: 2px;
+}
+
+/**
+ * The add display query dropdown needs a lot of help
+ */
+
+.views-edit-view .views-displays .tabs.secondary {
+ position: relative;
+ z-index: 100;
+}
+
+.views-edit-view .views-displays .secondary .open > a {
+ border-bottom: 1px solid #f1f1f1;
+}
+
+.views-edit-view .views-displays .secondary .action-list {
+ border-bottom: 1px solid #cbcbcb;
+ top: 30px;
+}
+
+.views-edit-view .views-displays .secondary input {
+ text-align: left;
+}
+
+/**
+ * IE7 does not interpret div > * correctly
+ */
+.page-admin-structure-views #content .views-ui-display-tab-bucket {
+ padding-left: 0;
+ padding-right: 0;
+ zoom: 1;
+}
+
+.page-admin-structure-views #content .views-display-column + .views-display-column {
+ margin-top: 0;
+}
+
+/**
+ * IE7 is interpreting a top margin of 18px from somewhere. remove it
+ */
+
+.page-admin-structure-views #content .views-display-setting {
+ margin-top: 0;
+}
+
+/**
+ * IE7 can't handle the + selector that indents form wrappers after a checkbox on the add page
+ * zoom is necessary to trigger has layout. !imporant is necessary because IE7 is precedent
+ * to the theme.css stylesheet, even though it is included before this file.
+ */
+
+.page-admin-structure-views #content .form-type-checkbox + .form-wrapper {
+ margin-left: 27px !important;
+ zoom: 1;
+}
diff --git a/sites/all/modules/views/css/views-admin-rtl.css b/sites/all/modules/views/css/views-admin-rtl.css
new file mode 100644
index 000000000..6d1e03a00
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin-rtl.css
@@ -0,0 +1,98 @@
+/**
+ * The .css file is intended to only contain positioning and size declarations
+ * For example: display, position, float, clear, and overflow.
+ */
+
+/* @group Inline lists */
+
+.horizontal > * {
+ float: right;
+}
+
+.horizontal.right {
+ float: left;
+}
+
+/* @end */
+
+/* @group Attachment details
+ *
+ * The attachment details section, its tabs for each section and the buttons
+ * to add a new section
+ */
+
+.form-actions {
+ float: left;
+}
+
+/* @end */
+
+/* @group Attachment details tabs
+ *
+ * The tabs that switch between sections
+ */
+
+.views-displays .secondary > li {
+ float: right;
+}
+
+/* @end */
+
+/* @group Attachment details new section button */
+
+.views-displays .secondary .action-list {
+ left: auto;
+ right: 0;
+}
+
+/* @end */
+
+/* @group Attachment details collapsible fieldset */
+
+.views-display-tab .fieldset-legend {
+ left: auto;
+ right: -5px;
+}
+
+/* @end */
+
+/* @group Attachment details actions
+ *
+ * Display the "Delete" and "Duplicate" buttons to the right.
+ */
+.views-display-tab .fieldset-wrapper > .views-ui-display-tab-bucket .actions {
+ left: 0;
+ right: auto;
+}
+
+/* @end */
+
+/* @group Attachment configuration columns */
+
+.views-display-columns > * {
+ float: right;
+ margin-left: 0;
+ margin-right: 1%;
+ padding-left: 0;
+ padding-right: 1%;
+}
+
+.views-display-columns > *:first-child {
+ margin-right: 0;
+ padding-right: 0;
+}
+
+/* @end */
+
+/* @group Settings forms */
+
+.views-dependent {
+ margin-right: 1.5em;
+}
+
+.views-display-setting .label,
+.views-display-setting .views-ajax-link {
+ float: right;
+}
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.advanced_help.css b/sites/all/modules/views/css/views-admin.advanced_help.css
new file mode 100644
index 000000000..71d9cb168
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.advanced_help.css
@@ -0,0 +1,24 @@
+/**
+ * The .advanced_help.css file is intended to contain styles that override declarations
+ * in the Advanced Help module.
+ */
+
+/**
+ * Adjust the advanced help icons
+ */
+.views-ui-display-tab-bucket .advanced-help-link {
+ padding: 0;
+ margin: 5px 3px 0px 6px; /* LTR */
+}
+
+.views-ui-display-tab-bucket .icon-text {
+ padding-left: 25px; /* LTR */
+}
+
+.views-ui-display-tab-bucket .icon-linked {
+ background-position: 6px -151px; /* LTR */
+}
+
+.views-ui-display-tab-bucket .icon-unlinked {
+ background-position: 6px -193px; /* LTR */
+}
diff --git a/sites/all/modules/views/css/views-admin.bartik-rtl.css b/sites/all/modules/views/css/views-admin.bartik-rtl.css
new file mode 100644
index 000000000..abf2a3085
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.bartik-rtl.css
@@ -0,0 +1,12 @@
+/**
+ * The .bartik.css file is intended to contain styles that override declarations
+ * in the Bartik theme.
+ */
+
+ /* @group Lists */
+
+.views-display-top .secondary .action-list {
+ padding-right: 0;
+}
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.bartik.css b/sites/all/modules/views/css/views-admin.bartik.css
new file mode 100644
index 000000000..c6f069250
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.bartik.css
@@ -0,0 +1,233 @@
+/**
+ * The .bartik.css file is intended to contain styles that override declarations
+ * in the Bartik theme.
+ */
+
+/* @group Lists */
+
+.views-display-top .secondary .action-list {
+ padding-left: 0; /* LTR */
+}
+
+/* @end */
+
+/* @group Attachment details tabs
+ *
+ * The tabs that switch between sections
+ */
+
+.views-displays .region-content .secondary,
+.views-displays .region-content .secondary {
+ padding-bottom: 0;
+ padding-left: 0;
+}
+
+.views-displays .secondary a {
+ font-size: smaller;
+}
+
+.views-displays .secondary > li a {
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.views-displays .secondary > li.open a {
+ -moz-border-radius: 5px 5px 0 0;
+ -webkit-border-bottom-left-radius: 0;
+ -webkit-border-bottom-right-radius: 0;
+ -webkit-border-top-left-radius: 5px;
+ -webkit-border-top-right-radius: 5px;
+ border-radius: 5px 5px 0 0;
+}
+
+.views-displays .secondary .open > a:hover {
+ color: #0071B3;
+}
+
+.views-displays .secondary input.form-submit {
+ font-size: smaller;
+}
+
+/* @end */
+
+/* @group Modal dialog box
+ *
+ * The contents of the popup dialog on the views edit form.
+ */
+
+.views-filterable-options .even .form-type-checkbox {
+ background-color: #F9F9F9;
+}
+
+.views-ui-dialog .ui-dialog-titlebar-close,
+.views-ui-dialog #views-ajax-title,
+.views-ui-dialog .views-override,
+.views-ui-dialog .form-buttons {
+ background-color: #f6f6f6;
+}
+
+.views-ui-dialog a {
+ color: #0071b3;
+}
+
+.views-ui-dialog a:hover,
+.views-ui-dialog a:focus {
+ color: #018fe2;
+}
+
+/* @end */
+
+/* @group CTools */
+
+/* @group Buttons */
+
+.ctools-button-processed {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(249, 249, 249, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ padding-bottom: 1px;
+ padding-top: 1px;
+}
+
+.ctools-button-processed:hover {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(241, 241, 241, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+}
+
+.ctools-button-processed li a,
+.views-ui-display-tab-actions .ctools-button-processed input {
+ padding-left: 9px;
+ padding-right: 9px;
+}
+
+.ctools-content ul.actions {
+ padding-bottom: 0;
+}
+
+.ctools-dropbutton-processed.open:hover {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(249, 249, 249, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+}
+
+.ctools-dropbutton-processed.open {
+ -moz-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+ -webkit-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+ box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+}
+
+.ctools-twisty {
+ top: 0.6667em;
+}
+
+.ctools-dropbutton-processed.open .ctools-twisty {
+ top: 0.3333em;
+}
+
+.ctools-dropbutton-processed li a,
+.views-ui-display-tab-actions .ctools-dropbutton-processed input {
+ padding-right: 7px;
+}
+
+.views-ui-display-tab-actions .ctools-button-processed input.form-submit {
+ margin-right: 0;
+ margin-top: 0;
+}
+
+/* @end */
+
+/* @group Collapsible */
+
+.ctools-toggle {
+ margin-top: 0.9em;
+}
+
+.ctools-toggle.ctools-toggle-collapsed {
+ margin-top: 0.72em;
+}
+
+.views-display-column > .ctools-toggle {
+ margin-top: 14px;
+}
+
+.views-display-column > .ctools-toggle.ctools-toggle-collapsed {
+ margin-top: 12px;
+}
+
+.views-ui-display-tab-actions .ctools-button input {
+ color: #0071B3;
+}
+
+.views-ui-display-tab-actions .ctools-button input:hover,
+.views-ui-display-tab-actions .ctools-button input:focus {
+ color: #018FE2;
+}
+
+/* @end */
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.contextual.css b/sites/all/modules/views/css/views-admin.contextual.css
new file mode 100644
index 000000000..502e94947
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.contextual.css
@@ -0,0 +1,63 @@
+/**
+ * The .contextual.css file is intended to contain styles that override declarations
+ * in the Contextual module.
+ */
+
+/* @group Wrapper */
+
+#views-live-preview .contextual-links-region-active {
+ outline: medium none;
+}
+
+#views-live-preview div.contextual-links-wrapper {
+ right: auto;
+ top: auto;
+}
+
+html.js #views-live-preview div.contextual-links-wrapper {
+ display: inline;
+}
+
+/* @end */
+
+/* @group Trigger */
+
+#views-live-preview a.contextual-links-trigger {
+ display: block;
+}
+
+/* @end */
+
+/* @group List */
+
+div.contextual-links-wrapper ul.contextual-links {
+ -moz-border-radius: 0 4px 4px 4px;
+ -webkit-border-radius: 0 4px 4px 4px;
+ border-radius: 0 4px 4px 4px;
+ min-width: 10em;
+ padding: 6px 6px 9px 6px;
+ right: auto;
+}
+
+ul.contextual-links li a,
+ul.contextual-links li span {
+ padding-bottom: 0.25em;
+ padding-right: 0.1667em;
+ padding-top: 0.25em;
+}
+
+ul.contextual-links li span {
+ font-weight: bold;
+}
+
+ul.contextual-links li a {
+ color: #666666 !important;
+ margin: 0.25em 0;
+ padding-left: 1em;
+}
+
+ul.contextual-links li a:hover {
+ background-color: #badbec;
+}
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.css b/sites/all/modules/views/css/views-admin.css
new file mode 100644
index 000000000..7ce708820
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.css
@@ -0,0 +1,361 @@
+/**
+ * The .css file is intended to only contain positioning and size declarations
+ * For example: display, position, float, clear, and overflow.
+ */
+
+/* @group Resets */
+
+.views-admin ul,
+.views-admin menu,
+.views-admin dir {
+ padding-left: 0; /* LTR for IE */
+ /* padding-start is used so that RTL works out of the box */
+ -moz-padding-start: 0;
+ -webkit-padding-start: 0;
+ padding-start: 0;
+}
+
+.views-admin pre {
+ margin-bottom: 0;
+ margin-top: 0;
+ white-space: pre-wrap;
+}
+
+/* @end */
+
+/* @group Inline lists */
+
+.horizontal > * {
+ clear: none;
+ float: left; /* LTR */
+}
+
+.horizontal.right {
+ float: right;
+}
+
+.horizontal label {
+ position: absolute;
+}
+
+.horizontal .form-item > [class] {
+ margin-top: 25px;
+}
+
+.horizontal .form-item > [class] + [class] {
+ margin-top: 0;
+}
+
+/* @end */
+
+/* @group Columns */
+
+.views-left-25 {
+ float: left;
+ width: 25%;
+}
+
+.views-left-30 {
+ float: left;
+ width: 30%;
+}
+
+.views-left-40 {
+ float: left;
+ width: 40%;
+}
+
+.views-left-50 {
+ float: left;
+ width: 50%;
+}
+
+.views-left-75 {
+ float: left;
+ width: 75%;
+}
+
+.views-right-50 {
+ float: right;
+ width: 50%;
+}
+
+.views-right-60 {
+ float: right;
+ width: 60%;
+}
+
+.views-right-70 {
+ float: right;
+ width: 70%;
+}
+
+.views-group-box .form-item {
+ margin-left: 3px;
+ margin-right: 3px;
+}
+
+/* @end */
+
+/* @group Attachment details
+ *
+ * The attachment details section, its tabs for each section and the buttons
+ * to add a new section
+ */
+
+.form-edit .form-actions {
+ float: right; /* LTR */
+}
+
+.views-displays {
+ clear: both;
+}
+
+/* @end */
+
+/* @group Attachment details tabs
+ *
+ * The tabs that switch between sections
+ */
+ .views-displays .secondary {
+ border-bottom: 0 none;
+ margin: 0;
+ overflow: visible;
+ padding: 0;
+ }
+
+.views-displays .secondary > li {
+ border-right: 0 none;
+ display: inline-block;
+ float: left; /* LTR */
+ padding: 0;
+}
+
+.views-displays .secondary .open > a {
+ position: relative;
+ z-index: 51;
+}
+
+.views-displays .views-display-deleted-link {
+ text-decoration: line-through;
+}
+
+.views-display-deleted > fieldset > legend,
+.views-display-deleted .fieldset-wrapper > .views-ui-display-tab-bucket > *,
+.views-display-deleted .views-display-columns {
+ opacity: 0.25;
+}
+
+.views-display-disabled > fieldset > legend,
+.views-display-disabled .fieldset-wrapper > .views-ui-display-tab-bucket > *,
+.views-display-disabled .views-display-columns {
+ opacity: 0.5;
+}
+
+.views-display-tab .fieldset-wrapper > .views-ui-display-tab-bucket .actions {
+ opacity: 1.0;
+}
+/* @end */
+
+/* @group Attachment details new section button */
+
+.views-displays .secondary li.add {
+ position: relative;
+}
+
+.views-displays .secondary .action-list {
+ left: 0; /* LTR */
+ margin: 0;
+ position: absolute;
+ top: 23px;
+ z-index: 50;
+}
+
+.views-displays .secondary .action-list li {
+ display: block;
+}
+
+/* @end */
+
+/* @group Attachment details collapsible fieldset */
+
+.views-display-tab .fieldset-legend {
+ left: -5px; /* LTR */
+ position: relative;
+}
+
+.views-display-tab .fieldset-wrapper {
+ position: relative;
+}
+
+/* @end */
+
+/* @group Attachment details actions
+ *
+ * Display the "Delete" and "Duplicate" buttons to the right.
+ */
+.views-display-tab .fieldset-wrapper > .views-ui-display-tab-bucket .actions {
+ position: absolute;
+ right: 0; /* LTR */
+ top: -5px;
+}
+
+/* @end */
+
+/* @group Attachment configuration columns */
+
+.views-display-columns > * {
+ float: left; /* LTR */
+ margin-left: 1%; /* LTR */
+ padding-left: 1%; /* LTR */
+ width: 32%;
+}
+
+.views-display-columns > *:first-child {
+ margin-left: 0; /* LTR */
+ padding-left: 0; /* LTR */
+}
+
+/* @end */
+
+/* @group Modal dialog box */
+
+.views-ui-dialog {
+ /* We need this so the button is visible. */
+ overflow: visible;
+ position: fixed;
+}
+
+.views-ui-dialog .ui-dialog-titlebar-close {
+ border: 1px solid transparent;
+ display: block;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ right: 0;
+ top: 2px;
+ /* Make sure this is in front of the modal backdrop. */
+ z-index: 1002;
+}
+
+.views-ui-dialog .ui-dialog-titlebar {
+ padding: 0;
+ margin: 0;
+}
+
+.views-ui-dialog .ui-dialog-title {
+ display: none;
+}
+
+.views-ui-dialog #views-ajax-popup {
+ padding: 0;
+ overflow: hidden;
+}
+
+.views-ui-dialog #views-ajax-title,
+.views-ui-dialog #views-ajax-body {
+ margin: 0;
+ padding: 0;
+}
+
+.views-ui-dialog #views-ajax-popup {
+ overflow: hidden;
+}
+
+.views-ui-dialog .scroll {
+ max-height: 400px;
+ overflow: auto;
+}
+
+#views-filterable-options-controls {
+ display: none;
+}
+
+.views-ui-dialog #views-filterable-options-controls {
+ display: block;
+}
+
+/* Don't let the messages overwhelm the modal */
+.views-ui-dialog .views-messages {
+ max-height: 200px;
+ overflow: auto;
+}
+
+/* @end */
+
+/* @group Settings forms */
+
+.views-display-setting .label,
+.views-display-setting .views-ajax-link {
+ display: inline-block;
+ float: left; /* LTR */
+}
+
+/* @end */
+
+/* @group Filter Settings form */
+
+div.form-item-options-value-all {
+ display: none;
+}
+/* @end */
+
+/* @group Drupal overrides */
+
+/* The .progress-disabled class added to the form on submit floats the element
+ * left and causes the form width to shrink-wrap to the content. Setting the
+ * width to 100% prevents this.
+ */
+#views-ajax-body form {
+ width: 100%;
+}
+
+/* @end */
+
+
+
+/* @group Javascript dependent styling */
+
+.js-only {
+ display: none;
+}
+
+html.js .js-only {
+ display: inherit;
+}
+
+html.js span.js-only {
+ display: inline;
+}
+
+/* @end */
+
+/* @group AJAX throbber */
+
+/* Base Page */
+#views-ui-list-page .ajax-progress-throbber,
+.views-admin .ajax-progress-throbber {
+ /* Can't do center:50% middle: 50%, so approximate it for a typical window size. */
+ left: 49%;
+ position: fixed;
+ top: 48.5%;
+ z-index: 1000;
+}
+#views-ui-list-page .ajax-progress-throbber .message,
+.views-admin .ajax-progress-throbber .message {
+ display: none;
+}
+
+/* Modal */
+#views-ajax-popup .ajax-progress-throbber {
+ /* Can't do center:50% middle: 50%, so approximate it for a typical window size. */
+ left: 49%;
+ position: fixed;
+ top: 48.5%;
+ z-index: 1000;
+}
+#views-ajax-popup .ajax-progress-throbber .message {
+ display: none;
+}
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.ctools-rtl.css b/sites/all/modules/views/css/views-admin.ctools-rtl.css
new file mode 100644
index 000000000..338b2fb72
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.ctools-rtl.css
@@ -0,0 +1,82 @@
+/* @group Buttons */
+
+.ctools-dropbutton .ctools-content {
+ border-left: 1px solid #e8e8e8;
+}
+
+.ctools-content ul.actions {
+ padding-left: auto;
+ padding-right: 0;
+}
+
+.ctools-dropbutton .ctools-link {
+ border-right: 1px solid #ffffff;
+}
+
+.ctools-dropbutton li {
+ padding-left: 9px;
+ padding-left: auto;
+}
+
+.views-display-top .ctools-button {
+ left: 12px;
+ right: auto;
+}
+
+.views-ui-display-tab-bucket .ctools-button {
+ left: 5px;
+ right: auto;
+}
+
+/* @end */
+
+/* @group Collapsible */
+
+.ctools-toggle {
+ float: right;
+ margin-left: 2px;
+ margin-right: 0;
+}
+
+.ctools-toggle.ctools-toggle-collapsed {
+ border-left: 0;
+ border-right: 4px solid;
+ border-left-color: transparent;
+ border-width: 5px 5px 5px 0;
+ margin-left: 5px;
+ margin-right: 2px;
+}
+
+.ctools-export-ui-row label {
+ float: right;
+}
+
+.views-display-column > .ctools-toggle {
+ margin-left: 3px;
+ margin-right: 6px;
+}
+.views-display-column > .ctools-toggle.ctools-toggle-collapsed {
+ margin-left: 5px;
+ margin-right: 9px;
+}
+
+/* @end */
+
+/* @group Dependent */
+
+.dependent-options {
+ margin-left: 0;
+ margin-right: 18px;
+}
+
+/* @end */
+
+/* @group Export */
+
+/* Override for filter button on the views list screen */
+#ctools-export-ui-list-form .form-submit {
+ margin-left: 0em;
+ margin-right: auto;
+}
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.ctools.css b/sites/all/modules/views/css/views-admin.ctools.css
new file mode 100644
index 000000000..b1f0e299e
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.ctools.css
@@ -0,0 +1,232 @@
+/* @group Buttons */
+
+.ctools-button-processed {
+ background-color: #ffffff;
+ border-color: #cccccc;
+ font-size: 11px;
+ padding-bottom: 2px;
+ padding-top: 2px;
+}
+
+.ctools-button-processed:hover {
+ border-color: #b8b8b8;
+}
+
+.ctools-button-processed:active {
+ border-color: #a0a0a0;
+}
+
+.ctools-button-processed .ctools-content {
+ padding-bottom: 0;
+ padding-top: 0;
+}
+
+.ctools-dropbutton-processed {
+ position: absolute;
+}
+
+.ctools-dropbutton-processed .ctools-content {
+ border-right: 1px solid #e8e8e8;
+}
+
+.ctools-dropbutton-processed .ctools-content ul {
+ margin: 0;
+ padding: 0;
+}
+
+.ctools-content ul.actions {
+ margin-top: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+}
+
+.ctools-button-processed .ctools-content a {
+ background-image: none;
+ border: medium none;
+}
+
+.ctools-dropbutton-processed.open:hover {
+ border-color: #D0D0D0;
+}
+
+.ctools-dropbutton-processed.open {
+ z-index: 100;
+}
+
+.ctools-dropbutton-processed .ctools-link {
+ border-left: 1px solid #ffffff;
+}
+
+.ctools-dropbutton-processed.open .ctools-content {
+ padding-bottom: 4px;
+}
+
+.ctools-dropbutton-processed li a,
+.ctools-dropbutton-processed li input {
+ padding-right: 9px;
+}
+
+.ctools-dropbutton-processed.open li + li {
+ border-top: 1px solid #efefef;
+ margin-top: 4px;
+ padding-bottom: 0;
+ padding-top: 4px;
+}
+
+.ctools-twisty:focus {
+ outline: medium none;
+}
+
+.ctools-no-js .ctools-content ul {
+ margin-bottom: 0;
+ margin-top: 0;
+ padding-left: 0;
+}
+
+.views-display-top .ctools-button-processed {
+ font-size: 12px;
+ position: absolute;
+ right: 12px;
+ top: 7px;
+}
+
+.views-ui-display-tab-bucket .ctools-button-processed {
+ position: absolute;
+ right: 5px;
+ top: 4px;
+}
+
+.views-ui-display-tab-actions .ctools-button-processed li a,
+.views-ui-display-tab-actions .ctools-button-processed input {
+ background: none;
+ border: medium;
+ font-family: inherit;
+ font-size: 12px;
+ padding-bottom: 0;
+ padding-left: 12px;
+ padding-top: 0;
+ margin-bottom: 0;
+}
+
+.views-ui-display-tab-actions .ctools-button-processed input:hover {
+ background: none;
+}
+
+/* @end */
+
+/* @group Collapsible */
+
+.ctools-toggle {
+ border-bottom-color: transparent;
+ border-left-color: transparent;
+ border-right-color: transparent;
+ border-style: solid;
+ border-width: 5px 5px 0;
+ display: inline-block;
+ float: left;
+ height: 0;
+ margin-right: 2px;
+ margin-top: 0.4545em;
+ width: 0;
+}
+
+.ctools-toggle.ctools-toggle-collapsed {
+ border-bottom-color: transparent;
+ border-left: 4px solid;
+ border-right-color: transparent;
+ border-top-color: transparent;
+ border-width: 5px 0 5px 5px;
+ margin-left: 2px;
+ margin-right: 5px;
+ margin-top: 0.3333em;
+}
+
+.ctools-toggle:hover,
+.ctools-collapsible-handle:hover {
+ cursor: pointer;
+}
+
+.ctools-export-ui-row {
+ margin-bottom: 0;
+ padding-top: 0;
+}
+
+.ctools-export-ui-row label {
+ display: block;
+ float: left;
+ width: 55px;
+}
+
+.views-display-settings .ctools-toggle {
+ color: #000000;
+}
+
+.views-display-column > .ctools-toggle {
+ margin-left: 6px;
+ margin-right: 3px;
+ margin-top: 10px;
+}
+.views-display-column > .ctools-toggle.ctools-toggle-collapsed {
+ margin-left: 9px;
+ margin-right: 5px;
+ margin-top: 8px;
+}
+
+.views-display-column > .ctools-collapsible-handle {
+ border-color: #F3F3F3;
+ border-style: solid;
+ border-width: 1px 1px 0;
+ font-size: 13px;
+ font-weight: normal;
+ margin: 0;
+ padding: 6px 3px;
+}
+
+.views-display-column > .ctools-toggle.ctools-toggle-collapsed + .ctools-collapsible-handle {
+ border-width: 1px;
+}
+
+.views-display-column > .ctools-collapsible-content > .views-ui-display-tab-bucket:first-child {
+ border-top: medium none;
+}
+
+h2.ctools-collapsible-handle {
+ display: inline;
+ clear: both;
+}
+
+/* @end */
+
+/* @group Dependent */
+
+.dependent-options {
+ margin-left: 18px; /* LTR */
+}
+
+/* @end */
+
+/* @group Export */
+
+/* Override for filter button on the views list screen */
+#ctools-export-ui-list-form .form-submit {
+ margin-top: 0em !important;
+ margin-right: 0em;
+}
+
+.ctools-export-ui-row + .ctools-export-ui-row {
+ margin-top: 1em;
+}
+
+.ctools-export-ui-fourth-row input {
+ margin-top: 5px !important;
+}
+
+/* @end */
+
+/* @group Jump list */
+
+#views-live-preview .ctools-jump-menu-select{
+ max-width: 450px;
+}
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.garland-rtl.css b/sites/all/modules/views/css/views-admin.garland-rtl.css
new file mode 100644
index 000000000..9a8de6dcc
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.garland-rtl.css
@@ -0,0 +1,13 @@
+/**
+ * The .garland.css file is intended to contain styles that override declarations
+ * in the Garland theme.
+ */
+
+ /* @group Lists */
+
+.views-displays .secondary .action-list {
+ left: auto;
+ right: 1px;
+}
+
+ /* @end */
diff --git a/sites/all/modules/views/css/views-admin.garland.css b/sites/all/modules/views/css/views-admin.garland.css
new file mode 100644
index 000000000..cd9010a53
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.garland.css
@@ -0,0 +1,263 @@
+/**
+ * The .garland.css file is intended to contain styles that override declarations
+ * in the Garland theme.
+ */
+
+/* @group Attachment details tabs
+ *
+ * The tabs that switch between sections
+ */
+
+.views-displays .region-content .secondary,
+.views-displays .region-content .secondary {
+ padding-bottom: 0;
+ padding-left: 0;
+}
+
+.views-displays .secondary .action-list {
+ left: 1px;
+ top: 20px;
+}
+
+.views-displays .secondary .action-list li,
+.views-displays .secondary a {
+ border-color: #e9e9e9;
+}
+
+.views-displays .secondary a {
+ font-size: 12px;
+ padding: 2px 7px;
+}
+
+.views-displays ul.secondary li a:hover,
+.views-displays ul.secondary li.active a {
+ border: 1px solid transparent;
+ padding: 2px 7px;
+}
+
+.views-displays .secondary > li a {
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.views-displays .secondary > li.open a {
+ background-image: none;
+ -moz-border-radius: 5px 5px 0 0;
+ -webkit-border-bottom-left-radius: 0;
+ -webkit-border-bottom-right-radius: 0;
+ -webkit-border-top-left-radius: 5px;
+ -webkit-border-top-right-radius: 5px;
+ border-radius: 5px 5px 0 0;
+}
+
+.views-displays .secondary .open > a:hover {
+ border-color: #e9e9e9 #e9e9e9 #f1f1f1 #e9e9e9;
+ border-width: 1px 1px 1px 1px;
+ color: #0071B3;
+}
+
+.views-displays .secondary input.form-submit {
+ font-size: 11px;
+}
+
+/* @end */
+
+/* @group Attachment buckets
+ *
+ * These are the individual "buckets," or boxes, inside the display settings area
+ */
+
+.views-ui-display-tab-bucket h3 {
+ font-weight: bold;
+}
+
+/* @end */
+
+/* @group Modal dialog box
+ *
+ * The contents of the popup dialog on the views edit form.
+ */
+
+.views-filterable-options .form-type-checkbox {
+ margin: 0;
+}
+
+.views-filterable-options .even .form-type-checkbox {
+ background-color: #F9F9F9;
+}
+
+.views-ui-dialog .ui-dialog-titlebar-close,
+.views-ui-dialog #views-ajax-title,
+.views-ui-dialog .views-override,
+.views-ui-dialog .form-buttons {
+ background-color: #f6f6f6;
+}
+
+/* @end */
+
+/* @group CTools */
+
+/* @group Buttons */
+
+.ctools-button-processed {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(249, 249, 249, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ padding-bottom: 1px;
+ padding-top: 1px;
+}
+
+.ctools-button-processed:hover {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(241, 241, 241, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+}
+
+.ctools-button-processed ol li,
+.ctools-button-processed ul li {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.views-ui-display-tab-actions .ctools-button-processed input {
+ margin-right: 0;
+}
+
+.ctools-content ul.actions {
+ padding-bottom: 0;
+}
+
+.ctools-dropbutton-processed.open:hover {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(249, 249, 249, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+}
+
+.ctools-dropbutton-processed.open {
+ -moz-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+ -webkit-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+ box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+}
+
+.ctools-twisty {
+ top: 0.6667em;
+}
+
+.ctools-dropbutton-processed.open .ctools-twisty {
+ top: 0.3333em;
+}
+
+/* @end */
+
+/* @group Dependent */
+
+.dependent-options,
+.form-checkboxes.dependent-options,
+.form-radios.dependent-options,
+.form-checkboxes .form-item.dependent-options,
+.form-radios .form-item.dependent-options {
+ margin-left: 25px;
+}
+
+/* @end */
+
+/* @group Collapsible */
+
+.ctools-toggle {
+ margin-top: 0.5em;
+}
+
+.ctools-toggle,
+.views-display-settings .ctools-toggle {
+ color: #494949;
+}
+
+.ctools-toggle.ctools-toggle-collapsed {
+ margin-top: 0.25em;
+}
+
+.views-display-column > .ctools-toggle {
+ margin-top: 14px;
+}
+
+.views-display-column > .ctools-toggle.ctools-toggle-collapsed {
+ margin-top: 12px;
+}
+
+.views-ui-display-tab-actions .ctools-button input {
+ color: #027AC6;
+ font-size: 12px;
+}
+
+.views-ui-display-tab-actions .ctools-button input:hover,
+.views-ui-display-tab-actions .ctools-button input:focus {
+ color: #0062A0;
+}
+
+/* @end */
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.seven-rtl.css b/sites/all/modules/views/css/views-admin.seven-rtl.css
new file mode 100644
index 000000000..abdd5aa2d
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.seven-rtl.css
@@ -0,0 +1,43 @@
+/**
+ * The .seven.css file is intended to contain styles that override declarations
+ * in the Seven admin theme.
+ */
+
+/* @group Forms */
+
+.views-admin .form-submit,
+.views-admin a.button {
+ margin-left: 0;
+}
+
+/* @end */
+
+/* @group Lists */
+
+.views-admin .links li {
+ padding-left: 0;
+}
+
+/* @end */
+
+/* @group Attachments */
+
+.views-displays .secondary {
+ text-align: right;
+}
+
+/* @end */
+
+/* @group Attachment details tabs */
+
+.views-display-top ul.secondary {
+ float: right;
+}
+
+.views-displays .secondary .action-list li:first-child {
+ -moz-border-radius: 7px 0 0 0;
+ -webkit-border-radius: 7px 0 0 0;
+ border-radius: 7px 0 0 0;
+}
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.seven.css b/sites/all/modules/views/css/views-admin.seven.css
new file mode 100644
index 000000000..d58bb1be5
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.seven.css
@@ -0,0 +1,552 @@
+/**
+ * The .seven.css file is intended to contain styles that override declarations
+ * in the Seven admin theme.
+ */
+
+/* @group Content */
+
+.views-ui-display-tab-bucket h1,
+.views-ui-display-tab-bucket h2,
+.views-ui-display-tab-bucket h3,
+.views-ui-display-tab-bucket h4,
+.views-ui-display-tab-bucket h5 {
+ margin-bottom: 0;
+ margin-top: 0;
+}
+
+/* @end */
+
+/* @group Forms */
+
+.views-ui-dialog fieldset {
+ padding-top: 2.5em;
+}
+
+fieldset fieldset {
+ border: medium none;
+}
+
+/**
+ * Seven positions the legend absolutely, but does not have a way to ignore
+ * fieldsets without a legend so we make one up.
+ */
+fieldset.fieldset-no-legend {
+ padding-top: 0;
+}
+
+/**
+ * Being extra safe here and scoping this to the add view wizard form (where
+ * a layout problem occurs for the Display format fieldset if we don't fix its
+ * padding), but it's probably safe to just let it apply everywhere.
+ */
+#views-ui-add-form fieldset fieldset .fieldset-wrapper {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.views-display-tab fieldset {
+ padding: 0 12px;
+}
+
+.views-display-tab .fieldset-wrapper {
+ padding: 10px 12px 12px;
+}
+
+.views-display-tab fieldset.box-padding .fieldset-wrapper {
+ padding: 0;
+}
+
+.views-display-tab legend + .fieldset-wrapper {
+ padding-top: 2.5em;
+}
+
+.views-admin .form-item label.option,
+#views-ui-preview-form .form-item label.option {
+ font-size: 1em;
+}
+
+#views-ui-preview-form .form-submit {
+ margin-top: 3px;
+}
+
+.views-admin input.form-submit,
+.views-ui-dialog input.form-submit,
+.views-admin a.button,
+.views-ui-dialog a.button {
+ margin-bottom: 0;
+ margin-right: 0; /* LTR */
+ margin-top: 0;
+}
+
+/* Override for a button on the edit display screen */
+#edit-displays-preview-controls .form-submit {
+ display: inline-block;
+ margin-right: 1em;
+}
+
+/* Override for filter button on the views list screen */
+#ctools-export-ui-list-form .form-submit {
+ margin-bottom: 0;
+}
+
+#ctools-export-ui-list-form .ctools-export-ui-first-row .form-item {
+ margin-top: 3px;
+ margin-right: 5px; /* LTR */
+}
+
+.form-item,
+.form-item .form-item {
+ margin-bottom: 0;
+ margin-top: 9px;
+ padding-bottom: 0;
+ padding-top: 0;
+}
+
+.form-actions {
+ margin-bottom: 0;
+ margin-top: 0;
+}
+
+.form-item .form-item {
+ padding-bottom: 0;
+ padding-top: 0;
+}
+
+.form-radios > .form-item {
+ margin-top: 3px;
+}
+
+/* @group Dependent options
+ *
+ * Dependent options are identified in CTools dependent.js
+ */
+
+/* The .dependent-options.form-item is necessary to supercede the Seven .form-item
+ * reset declaration that sets the margin to zero.
+ */
+.dependent-options,
+.dependent-options.form-item,
+.form-item-options-expose-required,
+.form-item-options-expose-label,
+.form-item-options-expose-description {
+ margin-left: 1.5em;
+}
+
+.views-admin-dependent .form-item .form-item,
+.views-admin-dependent .form-type-checkboxes,
+.views-admin-dependent .form-type-radios,
+.views-admin-dependent .dependent-options,
+.views-admin-dependent .form-item .form-item,
+.views-admin-dependent .dependent-options .form-type-select,
+.views-admin-dependent .dependent-options .form-type-textfield,
+.form-item-options-expose-required,
+.form-item-options-expose-label,
+.form-item-options-expose-description {
+ margin-bottom: 6px;
+ margin-top: 6px;
+}
+
+.views-admin-dependent .form-type-radio,
+.views-admin-dependent .form-radios .form-item {
+ margin-bottom: 2px;
+ margin-top: 2px;
+}
+
+/* @end */
+
+/* @end */
+
+/* @group Lists */
+
+.views-admin ul.secondary,
+.views-admin .item-list ul {
+ margin: 0;
+ padding: 0;
+}
+
+.views-admin ul.secondary {
+ clear: none;
+}
+
+.views-displays ul.secondary li a,
+.views-displays ul.secondary li.active a,
+.views-displays ul.secondary li.active a.active {
+ padding: 2px 7px 3px;
+}
+
+.views-displays ul.secondary li.active a,
+.views-displays ul.secondary li.active a.active {
+ border: 1px solid transparent;
+}
+
+.views-admin .links li {
+ padding-right: 0; /* LTR */
+}
+
+.views-admin .button .links li {
+ padding-right: 12px; /* LTR */
+}
+
+.page-admin-structure-views #content ul.action-links {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.views-display-top ul.secondary {
+ background-color: transparent;
+ float: left
+}
+
+.views-display-top .secondary .action-list li {
+ float: none;
+ margin: 0;
+}
+
+/* @end */
+
+/* @group Buttons */
+
+.ctools-button-processed ul {
+ margin: 0;
+}
+
+/* Override for input elements that are themed like ctools-buttons */
+.ctools-button-processed input.form-submit:hover {
+ background-image: none;
+ color: #0074BD;
+ text-shadow: none;
+}
+
+.ctools-button-processed input.form-submit:active {
+ background: none;
+ border: medium none;
+ color: #0074BD;
+ text-shadow: none;
+}
+
+/* @end */
+
+/* @group Tables */
+
+table td,
+table th {
+ vertical-align: top;
+}
+
+/* @end */
+
+/* @group Attachment details */
+
+#edit-display-settings-title {
+ color: #008BCB;
+}
+
+/* @end */
+
+/* @group Attachment details tabs
+ *
+ * The tabs that switch between sections
+ */
+
+.views-displays .secondary {
+ text-align: left; /* LTR */
+}
+
+.views-displays .secondary > li:first-child {
+ padding-left: 0;
+}
+
+.views-admin .icon.add {
+ background-position: center 3px;
+}
+
+.views-displays .secondary a {
+ background-color: #f1f1f1;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ color: #008BCB;
+}
+
+.views-displays .secondary a:hover > .icon.add {
+ background-position: center -25px;
+}
+
+.views-displays .secondary .open > a {
+ -moz-border-radius: 7px 7px 0 0;
+ -webkit-border-radius: 7px 7px 0 0;
+ border-radius: 7px 7px 0 0;
+}
+
+.views-displays .secondary .open > a:hover {
+ background-color: #f1f1f1;
+ color: #008BCB;
+}
+
+.views-displays .secondary .action-list li:first-child {
+ -moz-border-radius: 0 7px 0 0;
+ -webkit-border-radius: 0 7px 0 0;
+ border-radius: 0 7px 0 0;
+}
+
+.views-displays .secondary .action-list li:last-child {
+ -moz-border-radius: 0 0 7px 7px;
+ -webkit-border-radius: 0 0 7px 7px;
+ border-radius: 0 0 7px 7px;
+}
+
+.views-displays .secondary .action-list input.form-submit {
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ border-radius: 0;
+ color: #008BCB;
+}
+
+/* @end */
+
+/* @group Attachment buckets
+ *
+ * These are the individual "buckets," or boxes, inside the display settings area
+ */
+
+.views-ui-display-tab-bucket h3 {
+ font-size: 12px;
+ text-transform: uppercase;
+}
+
+.views-ui-display-tab-bucket .links {
+ padding: 2px 6px 4px;
+}
+
+.views-ui-display-tab-bucket .links li + li {
+ margin-left: 3px;
+}
+
+/* @end */
+
+/* @group Rearrange filter criteria */
+
+#views-ui-rearrange-filter-form .action-links {
+ margin: 0;
+ padding: 0;
+}
+
+#views-ui-rearrange-filter-form table {
+ border: medium none;
+}
+
+#views-ui-rearrange-filter-form [id^="views-row"] {
+ border: medium none;
+}
+
+#views-ui-rearrange-filter-form tr td:last-child {
+ border-right: medium none;
+}
+
+#views-ui-rearrange-filter-form .filter-group-operator-row {
+ border-left: 1px solid transparent !important;
+ border-right: 1px solid transparent !important;
+}
+
+#views-ui-rearrange-filter-form tr.drag td {
+ background-color: #FFEE77 !important;
+}
+
+#views-ui-rearrange-filter-form tr.drag-previous td {
+ background-color: #FFFFBB !important;
+}
+
+/* @end */
+
+/* @group Live preview elements */
+
+.views-query-info pre {
+ margin-bottom: 0;
+ margin-top: 0;
+}
+
+/* @group Query info table */
+
+.views-query-info table {
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -webkit-border-horizontal-spacing: 1px;
+ -webkit-border-vertical-spacing: 1px;
+}
+
+.views-query-info table tr td:last-child {
+ /* Fixes a Seven style that bleeds down into this table unnecessarily */
+ border-right: 0 none;
+}
+
+/* @end */
+
+/* @end */
+
+/* @group Add view */
+
+.form-item-page-create,
+.form-item-block-create {
+ margin-top: 13px;
+}
+
+/* @end */
+
+/* @group Modal dialog box
+ *
+ * The contents of the popup dialog on the views edit form.
+ */
+
+.views-ui-dialog .ui-dialog-titlebar-close {
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ border-color: #cccccc;
+ right: -27px;
+ top: -1px;
+}
+
+.views-ui-dialog fieldset.collapsible {
+ padding-top: 1.5em;
+}
+
+.views-ui-dialog fieldset.collapsed {
+ padding-top: 2.5em;
+}
+
+.filterable-option .form-item.form-type-checkbox {
+ /* This selector is aggressive because Seven's reset for .form-items is aggressive. */
+ padding-bottom: 4px;
+ padding-left: 4px;
+ padding-top: 4px;
+}
+
+/* @end */
+
+/* @group CTools */
+
+/* @group Buttons */
+
+.ctools-button-processed {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(249, 249, 249, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ -moz-border-radius: 11px 11px 11px 11px;
+ -webkit-border-radius: 11px 11px 11px 11px;
+ border-radius: 11px 11px 11px 11px;
+}
+
+.ctools-button-processed:hover {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(241, 241, 241, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f1f1f1 100%);
+}
+
+.ctools-dropbutton-processed.open:hover {
+ background-image:
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(249, 249, 249, 1.0))
+ );
+ background-image:
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+ background-image:
+ linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #f9f9f9 100%);
+}
+
+.ctools-dropbutton-processed.open {
+ -moz-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+ -webkit-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+ box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+}
+
+/* @end */
+
+/* @group Collapsible */
+
+.ctools-toggle {
+ margin-top: 0.6667em;
+}
+
+.ctools-toggle.ctools-toggle-collapsed {
+ margin-top: 0.5em;
+}
+
+.views-display-settings .ctools-toggle {
+ color: #008BCB;
+}
+
+.views-display-column > .ctools-toggle {
+ margin-top: 14px;
+}
+
+.views-display-column > .ctools-toggle.ctools-toggle-collapsed {
+ margin-top: 12px;
+}
+
+.views-display-column > .ctools-collapsible-handle {
+ color: #008BCB;
+}
+
+.views-ui-display-tab-actions .ctools-button-processed input {
+ color: #0074BD;
+}
+
+/* @end */
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.theme-rtl.css b/sites/all/modules/views/css/views-admin.theme-rtl.css
new file mode 100644
index 000000000..f966432f4
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.theme-rtl.css
@@ -0,0 +1,208 @@
+/**
+ * The .theme.css file is intended to contain presentation declarations including
+ * images, borders, colors, and fonts.
+ */
+
+/* @end */
+
+/* @group Icons */
+
+.actions a,
+.views-admin .icon,
+.views-admin .icon-text {
+ background-position: right top;
+}
+
+/* Targets any element with an icon -> text combo */
+.views-admin .icon-text {
+ padding-right: 19px;
+}
+
+.views-admin .icon-linked {
+ background-position: right -153px;
+}
+
+.views-admin .icon-unlinked {
+ background-position: right -195px;
+}
+
+.actions .views-button-add {
+ background-position: right -39px;
+}
+
+.actions .views-button-rearrange {
+ background-position: right -96px;
+}
+
+.actions .views-button-add:hover {
+ background-position: right -58px;
+}
+
+.actions .views-button-rearrange:hover {
+ background-position: right -115px;
+}
+
+.actions .views-button-add:active {
+ background-position: right -77px;
+}
+
+.actions .views-button-rearrange:active {
+ background-position: right -134px;
+}
+
+.views-displays .icon-add {
+ background-position: right -3px;
+}
+
+.views-displays .secondary a:hover > .icon-add {
+ background-position: right -21px;
+}
+
+.views-displays .secondary .open a:hover > .icon-add {
+ background-position: right -3px;
+}
+
+/* @end */
+
+/* @group Forms */
+
+.form-submit + .form-submit,
+.views-admin a.button + a.button {
+ margin-right: 1em;
+}
+
+.container-inline > * + *,
+.container-inline .fieldset-wrapper > * + * {
+ padding-left: 0;
+ padding-right: 4pt;
+}
+
+.views-admin .form-type-checkbox + .form-wrapper {
+ margin-right: 16px;
+}
+
+/* @end */
+
+/* @group Lists */
+
+.horizontal > * + * {
+ margin-right: 9px;
+ padding-right: 9px;
+}
+
+/* @end */
+
+/* @group Attachments */
+
+.views-displays .secondary {
+ padding: 6px 8px 8px;
+}
+
+.views-displays .views-display-top > ul > li + li {
+ margin-right: 3px;
+}
+
+.views-displays .views-extra-actions {
+ left: 10px;
+}
+
+/* @end */
+
+/* @group Attachment details tabs
+ *
+ * The tabs that switch between sections
+ */
+
+.views-displays .secondary .action-list li:first-child {
+ -moz-border-radius: 7px 0 0 0;
+ -webkit-border-top-left-radius: 7px;
+ -webkit-border-top-right-radius: 0;
+ border-radius: 7px 0 0 0;
+}
+
+/* @end */
+
+/* @group Attachment details collapsible fieldset
+ *
+ * The attachment details section is a collapsible fieldset, but should not
+ * have a border around it.
+ */
+
+.views-display-tab .fieldset-legend {
+ left: auto;
+ right: -5px;
+}
+
+/* @end */
+
+/* @group Auto preview
+ *
+ * The auto-preview checkbox line. This may have more stuff added to it.
+ */
+
+div.form-item-displays-live-preview {
+ text-align: left;
+}
+
+/* @end */
+
+/* @group Attachment buckets
+ *
+ * These are the individual "buckets," or boxes, inside the three columns in the
+ * attachment details section.
+ */
+
+.views-ui-display-tab-bucket .icon-text {
+ padding-right: 25px;
+}
+
+/* @end */
+
+/* @group Attachment bucket rows
+ *
+ * This is each row within one of the "boxes."
+ */
+
+.views-display-setting .label {
+ margin-left: 3pt;
+}
+
+/* @end */
+
+/* @group Modal dialog box
+ *
+ * The contents of the popup dialog on the views edit form.
+ */
+
+#views-filterable-options-controls .form-item {
+ margin-left: 2%;
+}
+
+.views-ui-dialog #views-progress-indicator {
+ left: 10px;
+ right: auto;
+}
+
+/* @end */
+
+/* @group Rearrange filters
+ *
+ * Styling for the form that allows views filters to be rearranged.
+ */
+.views-operator-label {
+ padding-right: 0.5em;
+}
+
+/* @end */
+
+/* @group Live preview elements */
+
+/* @group HTML list */
+
+#views-live-preview .view-content > .item-list > ul {
+ padding-right: 21px;
+}
+
+/* @end */
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-admin.theme.css b/sites/all/modules/views/css/views-admin.theme.css
new file mode 100644
index 000000000..8611783f7
--- /dev/null
+++ b/sites/all/modules/views/css/views-admin.theme.css
@@ -0,0 +1,1083 @@
+/**
+ * The .theme.css file is intended to contain presentation declarations including
+ * images, borders, colors, and fonts.
+ */
+
+/* @group Reset */
+
+.views-admin .links {
+ list-style: none outside none;
+ margin: 0;
+}
+
+.views-admin a:hover {
+ text-decoration: none;
+}
+
+/* @end */
+
+/* @group Layout */
+
+.box-padding {
+ padding-left: 12px;
+ padding-right: 12px;
+}
+
+.box-margin {
+ margin: 12px 12px 0 12px;
+}
+
+/* @end */
+
+/* @group Icons */
+
+.views-admin .icon {
+ height: 16px;
+ width: 16px;
+}
+
+.views-admin .icon,
+.views-admin .icon-text {
+ background-attachment: scroll;
+ background-image: url("../images/sprites.png");
+ background-position: left top; /* LTR */
+ background-repeat: no-repeat;
+}
+
+.views-admin a.icon {
+ background-image:
+ url("../images/sprites.png"),
+ -moz-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #e8e8e8 100%);
+ background-image:
+ url("../images/sprites.png"),
+ -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0.0, rgba(255, 255, 255, 1.0)),
+ color-stop(1.0, rgba(232, 232, 232, 1.0))
+ );
+ background-image:
+ url("../images/sprites.png"),
+ -webkit-linear-gradient(
+ -90deg,
+ #ffffff 0px,
+ #e8e8e8 100%);
+ background-repeat: no-repeat, repeat-y;
+ border: 1px solid #dddddd;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ -moz-box-shadow: 0 0 0 rgba(0,0,0,0.3333) inset;
+ -webkit-box-shadow: 0 0 0 rgba(0,0,0,0.3333) inset;
+ box-shadow: 0 0 0 rgba(0,0,0,0.3333) inset;
+}
+
+.views-admin a.icon:hover {
+ border-color: #d0d0d0;
+ -moz-box-shadow: 0 0 1px rgba(0,0,0,0.3333) inset;
+ -webkit-box-shadow: 0 0 1px rgba(0,0,0,0.3333) inset;
+ box-shadow: 0 0 1px rgba(0,0,0,0.3333) inset;
+}
+
+.views-admin a.icon:active {
+ border-color: #c0c0c0;
+}
+
+/**
+ * Targets a <span> element inside an <a> element.
+ * This assumes no visible text from the span.
+ */
+.views-admin span.icon {
+ display: inline-block;
+ float: left;
+ position: relative;
+}
+
+.views-admin .icon.compact {
+ display: block;
+ overflow: hidden;
+ text-indent: -9999px;
+}
+
+/* Targets any element with an icon -> text combo */
+.views-admin .icon-text {
+ padding-left: 19px; /* LTR */
+}
+
+.views-admin .icon.linked {
+ background-position: center -153px;
+}
+
+.views-admin .icon.unlinked {
+ background-position: center -195px;
+}
+
+.views-admin .icon.add {
+ background-position: center 3px;
+}
+
+.views-admin a.icon.add {
+ background-position: center 3px, left top;
+}
+
+.views-admin .icon.delete {
+ background-position: center -52px;
+}
+
+.views-admin a.icon.delete {
+ background-position: center -52px, left top;
+}
+
+.views-admin .icon.rearrange {
+ background-position: center -111px;
+}
+
+.views-admin a.icon.rearrange {
+ background-position: center -111px, left top;
+}
+
+.views-displays .secondary a:hover > .icon.add {
+ background-position: center -25px;
+}
+
+.views-displays .secondary .open a:hover > .icon.add {
+ background-position: center 3px;
+}
+
+/* @end */
+
+/* @group Forms */
+
+fieldset.box-padding {
+ border: none;
+}
+
+.views-admin fieldset fieldset {
+ margin-bottom: 0;
+}
+
+.form-item {
+ margin-top: 9px;
+ padding-bottom: 0;
+ padding-top: 0;
+}
+
+.form-type-checkbox {
+ margin-top: 6px;
+}
+
+input.form-checkbox,
+input.form-radio {
+ vertical-align: baseline;
+}
+
+.form-submit:not(.js-hide) + .form-submit,
+.views-admin a.button:not(.js-hide) + a.button {
+ margin-left: 1em; /* LTR */
+}
+
+.container-inline {
+ padding-top: 15px;
+}
+
+.container-inline > * + *,
+.container-inline .fieldset-wrapper > * + * {
+ padding-left: 4pt; /* LTR */
+}
+
+.views-admin fieldset fieldset.container-inline {
+ margin-bottom: 1em;
+ margin-top: 1em;
+ padding-top: 0;
+}
+
+.views-admin fieldset fieldset.container-inline > .fieldset-wrapper {
+ padding-bottom: 0;
+}
+
+/* Indent form elements so they're directly underneath the label of the checkbox that reveals them */
+.views-admin .form-type-checkbox + .form-wrapper {
+ margin-left: 16px; /* LTR */
+}
+
+/* Hide 'remove' checkboxes. */
+.views-remove-checkbox {
+ display: none;
+}
+
+/* sizes the labels of checkboxes and radio button to the height of the text */
+.views-admin .form-type-checkbox label,
+.views-admin .form-type-radio label {
+ line-height: 2;
+}
+
+/* @group Dependent options */
+
+.views-admin-dependent .form-item {
+ margin-bottom: 6px;
+ margin-top: 6px;
+}
+
+/* @end */
+
+/* @end */
+
+/* @group Lists */
+
+.horizontal > * + * {
+ margin-left: 9px; /* LTR */
+ padding-left: 9px; /* LTR */
+}
+
+.views-ui-view-title {
+ font-weight: bold;
+}
+
+/* @end */
+
+/* @group Messages */
+
+.view-changed {
+ margin-bottom: 21px;
+}
+
+/* @end */
+
+/* @group Headings */
+
+/* Intentionally targeting h1 */
+.views-admin h1.unit-title {
+ font-size: 15px;
+ line-height: 1.6154;
+ margin-bottom: 0;
+ margin-top: 18px;
+}
+
+/* @end */
+
+/* @group Tables */
+
+table td,
+table th {
+ vertical-align: top;
+}
+
+/* @end */
+
+/* @group List views */
+
+/* These header classes are ambiguous and should be scoped to th elements */
+
+th.views-ui-name {
+ width: 18%;
+}
+
+th.views-ui-description {
+ width: 26%;
+}
+
+th.views-ui-tag {
+ width: 8%;
+}
+
+th.views-ui-path {
+ width: auto;
+}
+
+th.views-ui-operations {
+ width: 24%;
+}
+
+/* @end */
+
+/* @group Add view */
+
+/**
+ * Drupal core forces AJAX triggering elements to float left when they are
+ * disabled due to AJAX processing. On the add view page, we have inline
+ * containers where we don't want that behavior; it causes the select dropdown
+ * which is triggered to jump to the left while the AJAX throbber is active.
+ *
+ * See also http://drupal.org/node/769936 (Drupal core issue); when that is
+ * fixed it may no longer be necessary to do this.
+ */
+.views-admin .container-inline .progress-disabled {
+ float: none;
+}
+
+/**
+ * I wish this didn't have to be so specific
+ */
+.form-item-description-enable + .form-item-description {
+ margin-top: 0;
+}
+
+.form-item-description-enable label {
+ font-weight: bold;
+}
+
+.form-item-page-create,
+.form-item-block-create {
+ margin-top: 13px;
+}
+
+.form-item-page-create label,
+.form-item-block-create label {
+ font-weight: bold;
+}
+
+/* This makes the form elements after the "Display Format" label flow underneath the label */
+.form-item-page-style-style-plugin > label,
+.form-item-block-style-style-plugin > label {
+ display: block;
+}
+
+.views-attachment .options-set label {
+ font-weight: normal;
+}
+
+/* @end */
+
+/* @group Rearrange filters
+ *
+ * Styling for the form that allows views filters to be rearranged.
+ */
+
+.group-populated {
+ display: none;
+}
+
+td.group-title {
+ font-weight: bold;
+}
+
+.views-ui-dialog td.group-title {
+ margin: 0;
+ padding: 0;
+}
+
+.views-ui-dialog td.group-title span {
+ display: block;
+ height: 1px;
+ overflow: hidden;
+}
+
+.group-message .form-submit,
+.views-remove-group-link,
+#views-add-group {
+ float: right;
+ clear: both;
+}
+
+.views-operator-label {
+ font-style: italic;
+ font-weight: bold;
+ padding-left: 0.5em; /* LTR */
+ text-transform: uppercase;
+}
+
+.grouped-description,
+.exposed-description {
+ float: left;
+ padding-top: 3px;
+ padding-right: 10px;
+}
+
+/* This keeps the collapsible fieldsets of options from crashing into the bottom
+ * of the edit option columns. Because the edit option columns are floated, the collapsible
+ * fieldsets need to be floated as well so that the margin above the fieldset interacts with
+ * the float edit option columns.
+ */
+#edit-options .collapsible {
+ float: left;
+ width: 100%;
+}
+
+#edit-options-more {
+ clear: both;
+}
+
+/* @end */
+
+/* @group Attachments */
+
+.views-displays {
+ border: 1px solid #CCC;
+ padding-bottom: 36px;
+}
+
+.views-display-top {
+ background-color: #F9F9F9;
+ border-bottom: 1px solid #CCCCCC;
+ padding: 8px 8px 8px; /* LTR */
+ position: relative;
+}
+
+.views-display-top .secondary {
+ margin-right: 18em;
+}
+
+.views-display-top .secondary > li {
+ margin-right: 6px;
+ padding-left: 0;
+}
+
+.views-display-top .secondary > li:last-child {
+ margin-right: 0;
+}
+
+#views-display-extra-actions li {
+ padding: 3px 9px;
+}
+
+.views-display-top #views-display-top {
+ max-width: 180px;
+}
+
+/* @end */
+
+/* @group Attachment details tabs
+ *
+ * The tabs that switch between sections
+ */
+
+ul#views-display-menu-tabs {
+ margin-right: 200px;
+}
+
+ul#views-display-menu-tabs li {
+ margin-bottom: 5px;
+}
+
+ul#views-display-menu-tabs li.add ul.action-list li{
+ margin: 0;
+}
+
+.views-displays .secondary a {
+ border: 1px solid #cbcbcb;
+ display: inline-block;
+ font-size: small;
+ line-height: 1.3333;
+ padding: 3px 7px;
+}
+
+/**
+ * Display a red border if the display doesn't validate.
+ */
+.views-displays ul.secondary li.active a.active.error,
+.views-displays .secondary a.error {
+ border: 2px solid #ED541D;
+ padding: 1px 6px;
+}
+
+.views-displays .secondary a:focus {
+ outline: none;
+}
+
+.views-displays .secondary a:hover,
+.views-displays .secondary .active a {
+ background-color: #666666;
+ color: #ffffff;
+ border-bottom-width: 1px;
+}
+
+.views-displays .secondary .open > a {
+ background-color: #f1f1f1;
+ border-bottom: 1px solid transparent;
+ position: relative;
+}
+
+.views-displays .secondary .open > a:hover {
+ background-color: #f1f1f1;
+}
+
+.views-displays .secondary .action-list li {
+ background-color: #f1f1f1;
+ border-color: #cbcbcb;
+ border-style: solid;
+ border-width: 0 1px;
+ padding: 2px 9px;
+}
+
+.views-displays .secondary .action-list li:first-child {
+ border-width: 1px 1px 0;
+}
+
+.views-displays .secondary .action-list li.last {
+ border-width: 0 1px 1px;
+}
+
+.views-displays .secondary .action-list li:last-child {
+ border-width: 0 1px 1px;
+}
+
+.views-displays .secondary .action-list input.form-submit {
+ background: none repeat scroll 0 0 transparent;
+ border: medium none;
+ margin: 0;
+ padding: 0;
+}
+
+.views-displays .secondary .action-list li:hover {
+ background-color: #dddddd;
+}
+
+/* @end */
+
+/* @group Attachment details */
+
+#edit-display-settings-title {
+ font-size: 14px;
+ line-height: 1.5;
+ margin: 0;
+}
+
+#edit-display-settings-top {
+ padding-bottom: 4px;
+}
+
+#edit-display-settings-content {
+ margin-top: 12px;
+}
+
+#edit-display-settings-main {
+ margin-top: 15px;
+}
+
+/* @end */
+
+/* @group Attachment columns
+ *
+ * The columns that contain the option buckets e.g. Format and Basic Settings
+ */
+
+.views-display-column + .views-display-column {
+ margin-top: 0;
+ }
+
+ /* @end */
+
+/* @group Auto preview
+ *
+ * The auto-preview checkbox line.
+ */
+
+#views-ui-preview-form > div > div,
+#views-ui-preview-form > div > input {
+ float: left;
+}
+
+#views-ui-preview-form .form-type-checkbox {
+ margin-top: 2px;
+ margin-left: 2px;
+}
+
+#views-ui-preview-form .form-type-textfield {
+ margin-top: 5px;
+}
+
+#views-ui-preview-form .arguments-preview {
+ font-size: 1em;
+}
+
+#views-ui-preview-form .arguments-preview,
+#views-ui-preview-form .form-type-textfield {
+ margin-left: 14px;
+}
+
+#views-ui-preview-form .form-type-textfield label {
+ display: inline-block;
+ float: left;
+ font-weight: normal;
+ height: 6ex;
+ margin-right: 0.75em;
+}
+
+#views-ui-preview-form .form-type-textfield .description {
+ white-space: nowrap;
+}
+
+/* @end */
+
+/* @group Attachment buckets
+ *
+ * These are the individual "buckets," or boxes, inside the display settings area
+ */
+
+.views-ui-display-tab-bucket {
+ border: 1px solid #f3f3f3;
+ line-height: 20px;
+ margin: 0;
+ padding-top: 4px;
+}
+
+.views-ui-display-tab-bucket + .views-ui-display-tab-bucket {
+ border-top: medium none;
+}
+
+.views-ui-display-tab-bucket > h3,
+.views-ui-display-tab-bucket > .views-display-setting {
+ padding: 2px 6px 4px;
+}
+
+.views-ui-display-tab-bucket h3 {
+ font-size: small;
+ margin: 0;
+}
+
+.views-ui-display-tab-bucket .horizontal.actions {
+ margin-right: 6px;
+}
+
+.views-ui-display-tab-bucket .actions.horizontal li + li {
+ margin-left: 3px;
+ padding-left: 3px;
+}
+
+.views-ui-display-tab-bucket.access {
+ padding-top: 0;
+}
+
+.views-ui-display-tab-bucket.page-settings {
+ border-bottom: medium none;
+}
+
+.views-display-setting .views-ajax-link {
+ margin-left: 0.2083em;
+ margin-right: 0.2083em;
+}
+
+/* @end */
+
+/* @group Attachment bucket overridden
+ *
+ * Applies a overriden(italics) font style to overridden buckets.
+ * The better way to implement this would be to add the overridden class
+ * to the bucket header when the bucket is overridden and style it as a
+ * generic icon classed element. For the moment, we'll style the bucket
+ * header specifically with the overriden font style.
+ */
+
+.views-ui-display-tab-setting.overridden,
+.views-ui-display-tab-bucket.overridden > h3 {
+ font-style: italic;
+}
+
+/* @end */
+
+/* @group Attachment bucket drop button */
+
+.views-ui-display-tab-bucket {
+ position: relative;
+}
+
+/* @end */
+
+/* @group Attachment bucket rows
+ *
+ * This is each row within one of the "boxes."
+ */
+
+.views-ui-display-tab-bucket .views-display-setting {
+ color: #666666;
+ font-size: 12px;
+ padding-bottom: 2px;
+}
+
+.views-ui-display-tab-bucket .even {
+ background-color: #f9f9f9;
+}
+
+.views-ui-display-tab-bucket .views-group-text {
+ margin-top: 6px;
+ margin-bottom: 6px;
+}
+
+.views-display-setting .label {
+ margin-right: 3pt; /* LTR */
+}
+
+/* @end */
+
+/* @group Preview
+ *
+ * The preview controls and the preview pane
+ */
+
+#edit-displays-preview-controls .fieldset-wrapper > * {
+ float: left;
+}
+
+#edit-displays-preview-controls .fieldset-wrapper > .form-item {
+ margin-top: 0.3333em;
+}
+
+#edit-displays-preview-controls .form-submit {
+ display: inline-block;
+ margin-right: 1em;
+}
+
+#edit-displays-preview-controls .form-type-textfield {
+ margin-left: 1em;
+ position: relative;
+}
+
+#edit-displays-preview-controls .form-type-textfield label {
+ border-left: 1px solid #999;
+ padding-left: 1em;
+ position: absolute;
+}
+
+#edit-displays-preview-controls .form-type-textfield label:after {
+ content: ":";
+}
+
+#edit-displays-preview-controls .form-type-textfield label ~ * {
+ margin-left: 105px;
+}
+
+/* @end */
+
+/* @group Modal dialog box
+ *
+ * The contents of the popup dialog on the views edit form.
+ */
+
+.views-ui-dialog {
+ font-size: small;
+ padding: 0;
+}
+
+.views-ui-dialog .ui-dialog-titlebar-close {
+ background: url("../images/close.png") no-repeat scroll 6px 3px #F3F4EE;
+ border-color: #aaaaaa;
+ -moz-border-radius: 0 10px 12px 0;
+ -webkit-border-radius: 0 10px 12px 0;
+ border-radius: 0 10px 12px 0;
+ border-style: solid;
+ border-width: 1px 1px 1px 0;
+ -moz-box-shadow: 0 -2px 0 rgba(0, 0, 0, 0.1);
+ -webkit-box-shadow: 0 -2px 0 rgba(0, 0, 0, 0.1);
+ box-shadow: 0 -2px 0 rgba(0, 0, 0, 0.1);
+ height: 22px;
+ right: -28px;
+ top: 0;
+ width: 26px;
+}
+
+.views-ui-dialog .ui-dialog-titlebar-close span {
+ display: none;
+}
+
+.views-filterable-options .form-type-checkbox {
+ border: 1px solid #CCC;
+ padding: 5px 8px;
+ border-top: none;
+}
+
+.views-filterable-options {
+ border-top: 1px solid #CCC;
+}
+
+.views-filterable-options .even .form-type-checkbox {
+ background-color: #F3F4EE;
+}
+
+.filterable-option .form-item {
+ margin-bottom: 0;
+ margin-top: 0;
+}
+
+.views-filterable-options .form-type-checkbox .description {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#views-filterable-options-controls {
+ margin: 1em 0;
+}
+
+#views-filterable-options-controls .form-item {
+ width: 45%;
+ margin-right: 2%; /* LTR */
+}
+
+#views-filterable-options-controls input,
+#views-filterable-options-controls select {
+ width: 200px;
+}
+
+.views-ui-dialog .views-filterable-options {
+ margin-bottom: 10px;
+}
+
+.views-ui-dialog .views-add-form-selected.container-inline {
+ padding-top: 0;
+}
+
+.views-ui-dialog .views-add-form-selected.container-inline > div {
+ display: block;
+}
+
+.views-ui-dialog #edit-selected {
+ margin: 0;
+ padding: 6px 16px;
+}
+
+.views-ui-dialog #views-ajax-title,
+.views-ui-dialog .views-override {
+ background-color: #F3F4EE;
+}
+
+.views-ui-dialog .views-override {
+ padding: 0 13px 8px;
+}
+
+.views-ui-dialog .views-override > * {
+ margin: 0;
+}
+
+.views-ui-dialog #views-ajax-title {
+ font-size: 15px;
+ padding: 8px 13px;
+}
+
+.views-ui-dialog #views-progress-indicator {
+ font-size: 11px;
+ position: absolute;
+ right: 10px; /* LTR */
+ top: 8px;
+}
+
+.views-ui-dialog #views-progress-indicator:before {
+ content: "\003C\00A0";
+}
+
+.views-ui-dialog #views-progress-indicator:after {
+ content: "\00A0\003E";
+}
+
+.views-ui-dialog .scroll {
+ border: 1px solid #CCC;
+ border-width: 1px 0;
+ padding: 8px 13px;
+}
+
+.views-ui-dialog fieldset .item-list {
+ padding-left: 2em;
+}
+
+.views-ui-dialog .form-buttons {
+ background-color: #F3F4EE;
+ padding: 8px 13px;
+}
+.views-ui-dialog .form-buttons input {
+ margin-bottom: 0;
+ margin-right: 0;
+}
+
+/* @end */
+
+/* @group Configure filter criteria */
+
+/* @todo the width and border info could be moved into a more generic class */
+/* @todo Make this a class to be used anywhere there's node types? */
+.form-type-checkboxes #edit-options-value,
+.form-type-checkboxes #edit-options-validate-options-node-types {
+ border-color: #CCCCCC;
+ border-style: solid;
+ border-width: 1px;
+ max-height: 210px;
+ overflow: auto;
+ margin-top: 5px;
+ padding: 0 5px;
+ width: 190px;
+}
+
+/* @end */
+
+/* @group Rearrange filter criteria */
+
+#views-ui-rearrange-filter-form table {
+ border-collapse: collapse;
+}
+
+#views-ui-rearrange-filter-form tr td[rowspan] {
+ border-color: #CDCDCD;
+ border-style: solid;
+ border-width: 0 1px 1px 1px;
+}
+
+#views-ui-rearrange-filter-form tr[id^="views-row"] {
+ border-right: 1px solid #CDCDCD;
+}
+
+#views-ui-rearrange-filter-form tr[id^="views-row"].even td {
+ background-color: #F3F4ED;
+}
+
+#views-ui-rearrange-filter-form .views-group-title {
+ border-top: 1px solid #CDCDCD;
+}
+
+#views-ui-rearrange-filter-form .group-empty {
+ border-bottom: 1px solid #CDCDCD;
+}
+
+/* @end */
+
+/* @group Expose filter form items */
+
+.form-item-options-expose-required,
+.form-item-options-expose-label,
+.form-item-options-expose-description {
+ margin-bottom: 6px;
+ margin-left: 18px;
+ margin-top: 6px;
+}
+
+/* @end */
+
+/* @group Live preview elements */
+
+#views-preview-wrapper {
+ border: 1px solid #CCC;
+ border-top: 2px solid #CCC;
+ padding-bottom: 12px;
+ padding-top: 12px;
+}
+
+#views-ui-preview-form {
+ margin: 12px;
+}
+
+#views-live-preview {
+ margin: 0 12px;
+}
+
+#views-live-preview .views-query-info {
+ overflow: auto;
+}
+
+/* Intentionally targeting h1 */
+#views-live-preview h1.section-title {
+ color: #818181;
+ display: inline-block;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 1.6154;
+ margin-bottom: 0;
+ margin-top: 0;
+}
+
+#views-live-preview .view > * {
+ margin-top: 18px;
+}
+
+#views-live-preview .preview-section {
+ border: 1px dashed #DEDEDE;
+ margin: 0 -5px;
+ padding: 3px 5px;
+}
+
+#views-live-preview li.views-row + li.views-row {
+ margin-top: 18px;
+}
+
+/* The div.views-row is intentional and excludes li.views-row, for example */
+#views-live-preview div.views-row + div.views-row {
+ margin-top: 36px;
+}
+
+/* @group Query info table */
+
+.views-query-info table {
+ border-collapse: separate;
+ border-color: #dddddd;
+ border-spacing: 0;
+ margin: 10px 0;
+}
+
+.views-query-info table tr {
+ background-color: #f9f9f9;
+}
+
+.views-query-info table th,
+.views-query-info table td {
+ color: #666666;
+ padding: 4px 10px;
+}
+
+/* @end */
+
+/* @group Grid */
+
+#views-live-preview .views-view-grid th,
+#views-live-preview .views-view-grid td {
+ vertical-align: top;
+}
+
+/* @end */
+
+/* @group HTML list */
+
+#views-live-preview .view-content > .item-list > ul {
+ list-style-position: outside;
+ padding-left: 21px; /* LTR */
+}
+
+/* @end */
+
+/* @end */
+
+/* @group Add/edit argument form */
+
+#edit-options-default-action {
+ width: 300px;
+ float: left;
+}
+
+#edit-options-exception.collapsible {
+ float: right;
+ width: 250px;
+ margin-top: -2px;
+}
+
+/* @end */
+
+/* @group AJAX */
+
+/* Hide the drupal system throbber image */
+.ajax-progress .throbber {
+ display: none;
+}
+
+.ajax-progress-throbber {
+ background-color: #232323;
+ background-image: url("../images/loading-small.gif");
+ background-position: center center;
+ background-repeat: no-repeat;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ height: 24px;
+ opacity: .9;
+ padding: 4px;
+ width: 24px;
+}
+
+/* @end */
+
+/* @group Drupal
+ *
+ * Overrides to Drupal system CSS
+ */
+div.messages {
+ margin-bottom: 18px;
+}
+
+/* @end */
diff --git a/sites/all/modules/views/css/views-rtl.css b/sites/all/modules/views/css/views-rtl.css
new file mode 100644
index 000000000..f804639f8
--- /dev/null
+++ b/sites/all/modules/views/css/views-rtl.css
@@ -0,0 +1,5 @@
+
+.views-exposed-form .views-exposed-widget {
+ float: right; /* RTL */
+ padding: .5em 1em 0 0; /* RTL */
+}
diff --git a/sites/all/modules/views/css/views.css b/sites/all/modules/views/css/views.css
new file mode 100644
index 000000000..bf96f70ba
--- /dev/null
+++ b/sites/all/modules/views/css/views.css
@@ -0,0 +1,42 @@
+.views-exposed-form .views-exposed-widget {
+ float: left; /* LTR */
+ padding: .5em 1em 0 0; /* LTR */
+}
+
+.views-exposed-form .views-exposed-widget .form-submit {
+ margin-top: 1.6em;
+}
+
+.views-exposed-form .form-item,
+.views-exposed-form .form-submit {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.views-exposed-form label {
+ font-weight: bold;
+}
+
+.views-exposed-widgets {
+ margin-bottom: .5em;
+}
+
+/* table style column align */
+.views-align-left {
+ text-align: left;
+}
+.views-align-right {
+ text-align: right;
+}
+.views-align-center {
+ text-align: center;
+}
+
+/* Remove the border on tbody that system puts in */
+.views-view-grid tbody {
+ border-top: none;
+}
+
+.view .progress-disabled {
+ float: none;
+}
diff --git a/sites/all/modules/views/documentation-standards.txt b/sites/all/modules/views/documentation-standards.txt
new file mode 100644
index 000000000..6a9c5921d
--- /dev/null
+++ b/sites/all/modules/views/documentation-standards.txt
@@ -0,0 +1,5 @@
+- If the interface text is *bolded*, it got strong tags.
+- If it's a button they need to click, that's *bold* too.
+- If the text is not bolded (ex: links to click, options to check), it
+got /italicized/.
+- If it's user-entered text it got 'single quotes'.
diff --git a/sites/all/modules/views/drush/views.drush.inc b/sites/all/modules/views/drush/views.drush.inc
new file mode 100644
index 000000000..eb2ecc2f8
--- /dev/null
+++ b/sites/all/modules/views/drush/views.drush.inc
@@ -0,0 +1,510 @@
+<?php
+
+/**
+ * @file
+ * Drush integration of views.
+ *
+ * drush cache-clear views - Clears the views specific caches.
+ * views-revert - Drush command to revert views overridden in the system.
+ */
+
+/**
+ * Implement hook_drush_help().
+ */
+function views_drush_help($section) {
+ switch ($section) {
+ case 'drush:views-revert':
+ $help = dt('Reverts views in the drupal installation that have been overriden. ');
+ $help .= dt('If no view names are specified, you will be presented with a list of overridden views to choose from. ');
+ $help .= dt('To revert all views, do not specify any view names, and choose the option "All" from the options presented.');
+ return $help;
+ case 'drush:views-list':
+ return dt('Show a list of available views with information about them.');
+ case 'drush:views-enable':
+ return dt('Enable the specified views. Follow the command with a space delimited list of view names');
+ case 'drush:views-disable':
+ return dt('Disable the specified views. Follow the command with a space delimited list of view names');
+ }
+}
+
+/**
+ * Implement hook_drush_command().
+ */
+function views_drush_command() {
+ $items = array();
+
+ $items['views-revert'] = array(
+ 'callback' => 'views_revert_views',
+ 'drupal dependencies' => array('views'),
+ 'description' => 'Revert overridden views to their default state. Make sure to backup first.',
+ 'arguments' => array(
+ 'views' => 'A space delimited list of view names.',
+ ),
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
+ 'aliases' => array('vr'),
+ 'options' => array(
+ 'all' => 'If provided, all views will be reverted.',
+ ),
+ 'examples' => array(
+ 'drush vr archive' => 'Reverts the "archive" view.',
+ 'drush rln archive frontpage' => 'Reverts the "archive" and "frontpage" view.',
+ 'drush vr' => 'Will present you with a list of overridden views to choose from, and an option to revert all overridden views.',
+ 'drush vr --all' => 'Will revert all overridden views.',
+ ),
+ );
+ $items['views-dev'] = array(
+ 'callback' => 'views_development_settings',
+ 'drupal dependencies' => array('views'),
+ 'description' => 'Set the Views settings to more developer-oriented values.',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
+ 'aliases' => array('vd'),
+ );
+
+ $items['views-list'] = array(
+ 'drupal dependencies' => array('views'),
+ 'description' => 'Get a list of all views in the system.',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
+ 'aliases' => array('vl'),
+ 'options' => array(
+ 'name' => 'String contained in view\'s name by which filter the results.',
+ 'tags' => 'A comma-separated list of views tags by which to filter the results.',
+ 'status' => 'Status of the views by which to filter the results. Choices: enabled, disabled.',
+ 'type' => 'Type of the views by which to filter the results. Choices: normal, default or overridden.',
+ ),
+ 'examples' => array(
+ 'drush vl' => 'Show a list of all available views.',
+ 'drush vl --name=blog' => 'Show a list of views which names contain "blog".',
+ 'drush vl --tags=tag1,tag2' => 'Show a list of views tagged with "tag1" or "tag2".',
+ 'drush vl --status=enabled' => 'Show a list of enabled views.',
+ 'drush vl --type=overridden' => 'Show a list of overridden views.',
+ ),
+ );
+ $items['views-analyze'] = array(
+ 'drupal dependencies' => array('views', 'views_ui'),
+ 'description' => 'Get a list of all Views analyze warnings',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
+ 'aliases' => array('va'),
+ );
+ $items['views-enable'] = array(
+ 'drupal dependencies' => array('views'),
+ 'description' => 'Enable the specified views.',
+ 'arguments' => array(
+ 'views' => 'A space delimited list of view names.',
+ ),
+ 'aliases' => array('ven'),
+ 'examples' => array(
+ 'drush ven frontpage taxonomy_term' => 'Enable the frontpage and taxonomy_term views.',
+ ),
+ );
+ $items['views-disable'] = array(
+ 'drupal dependencies' => array('views'),
+ 'description' => 'Disable the specified views.',
+ 'arguments' => array(
+ 'views' => 'A space delimited list of view names.',
+ ),
+ 'aliases' => array('vdis'),
+ 'examples' => array(
+ 'drush vdis frontpage taxonomy_term' => 'Disable the frontpage and taxonomy_term views.',
+ ),
+ );
+
+ return $items;
+}
+
+/**
+ * Callback function for views-revert command.
+ */
+function views_revert_views() {
+ $views = views_get_all_views();
+ $i = 0;
+ // The provided views names specified in the command.
+ $viewnames = _convert_csv_to_array(func_get_args());
+
+ // Find all overridden views.
+ foreach ($views as $view) {
+ if ($view->disabled) {
+ continue;
+ }
+ if ($view->type == dt('Overridden')) {
+ $overridden[$view->name] = $view->name;
+ }
+ }
+
+ // If there are no overridden views in the system, report it.
+ if (empty($overridden)) {
+ drush_log(dt('There are no overridden views in the system.'), 'ok');
+ }
+
+ // If the user provided the "--all" option, revert all views.
+ if (drush_get_option('all')) {
+ $i = views_revert_allviews($views);
+ }
+
+ // If the user specified a list of views on the CLI, revert those.
+ elseif (!empty($viewnames)) {
+ foreach ($viewnames as $key => $viewname) {
+ $is_overridden = key_exists($viewname, $overridden);
+
+ // Check if the provided view name is in the system
+ if ($viewname && !key_exists($viewname, $views)) {
+ drush_set_error(dt("'@viewname' view is not present in the system.", array('@viewname' => $viewname)));
+ }
+ // Check if the provided view is overridden.
+ elseif (!$is_overridden) {
+ drush_set_error(dt("The view specified '@viewname' is not overridden.", array('@viewname' => $viewname)));
+ }
+ // If the view is overriden, revert it.
+ elseif ($is_overridden) {
+ views_revert_view($views[$viewname]);
+ $i++;
+ }
+ // We should never get here but well...
+ else {
+ drush_set_error(dt(
+ "The view specified '@viewname' is not provided in code, and thus cannot be reverted.",
+ array('@viewname' => $viewname)
+ ));
+ }
+ }
+ }
+
+ // The user neither selected the "--all" option, nor provided a list of views to revert.
+ // Prompt the user.
+ else {
+ // list of choices for the user
+ $overridden['all'] = dt('Revert all overridden views'); // add a choice at the end
+ $choice = drush_choice($overridden, 'Enter a number to choose which view to revert.', '!key'); // prompt the user
+
+ if ($choice !== FALSE) {
+ // revert all views option
+ if ($choice == 'all') {
+ $i = views_revert_allviews($views);
+ }
+ // else the user specified a single view
+ else {
+ views_revert_view($views[$choice]);
+ $i++;
+ }
+ }
+
+ }
+
+ // final results output
+ if ($i == 0) {
+ drush_log(dt('No views were reverted.'), 'ok');
+ }
+ else {
+ drush_log(dt('Reverted a total of @count views.', array('@count' => $i)), 'ok');
+ }
+}
+
+/**
+ * Reverts all views
+ * @param $views
+ * All views in the system as provided by views_get_all_views().
+ */
+function views_revert_allviews($views) {
+ $i = 0;
+ foreach ($views as $view) {
+ if ($view->disabled) {
+ continue;
+ }
+
+ if ($view->type == t('Overridden')) {
+ views_revert_view($view);
+ $i++;
+ }
+ }
+ return $i;
+}
+
+/**
+ * Revert a specified view
+ * @param $view
+ * The view object to be reverted
+ *
+ * Checks on wether or not the view is overridden is handled in views_revert_views_revert()
+ * We perform a check here anyway in case someone somehow calls this function on their own...
+ */
+function views_revert_view($view) {
+ // check anyway just in case
+ if ($view->type == t('Overridden')) {
+ // Revert the view.
+ $view->delete();
+ // Clear its cache.
+ ctools_include('object-cache');
+ ctools_object_cache_clear('view', $view->name);
+ // Give feedback.
+ $message = dt("Reverted the view '@viewname'", array('@viewname' => $view->name));
+ drush_log($message, 'success');
+ // Reverted one more view.
+ }
+ else {
+ drush_set_error(dt("The view '@viewname' is not overridden.", array('@viewname' => $view->name)));
+ }
+}
+
+/**
+ * Change the settings to a more developer oriented value.
+ */
+function views_development_settings() {
+ variable_set('views_ui_show_listing_filters', TRUE);
+ variable_set('views_ui_show_master_display', TRUE);
+ variable_set('views_ui_show_advanced_column', TRUE);
+ variable_set('views_ui_always_live_preview', FALSE);
+ variable_set('views_ui_always_live_preview_button', TRUE);
+ variable_set('views_ui_show_preview_information', TRUE);
+ variable_set('views_ui_show_sql_query', TRUE);
+ variable_set('views_ui_show_performance_statistics', TRUE);
+ variable_set('views_show_additional_queries', TRUE);
+ variable_set('views_devel_output', TRUE);
+ variable_set('views_devel_region', 'message');
+ variable_set('views_ui_display_embed', TRUE);
+ $message = dt("Setup the new views settings.");
+ drush_log($message, 'success');
+}
+
+
+/**
+ * Callback function for views-list command.
+ */
+function drush_views_list() {
+ // Initialize stuf
+ $rows = array();
+ $disabled_views = array();
+ $enabled_views = array();
+ $overridden = 0;
+ $indb = 0;
+ $incode = 0;
+ $disabled = 0;
+ $total = 0;
+
+ $views = views_get_all_views();
+
+ // get the --name option
+ // TODO : take into account the case off a comma-separated list of names
+ $name = drush_get_option_list('name');
+ $with_name = !empty($name) ? TRUE : FALSE;
+
+ // get the --tags option
+ $tags = drush_get_option_list('tags');
+ $with_tags = !empty($tags) ? TRUE : FALSE;
+
+ // get the --status option
+ // store user input appart to reuse it after
+ $status_opt = drush_get_option_list('status');
+ // use the same logic than $view->disabled
+ if (in_array('disabled', $status_opt)) {
+ $status = TRUE;
+ $with_status = TRUE;
+ }
+ elseif (in_array('enabled', $status_opt)) {
+ $status = FALSE;
+ $with_status = TRUE;
+ }
+ else {
+ $status = NULL;
+ // wrong or empty --status option
+ $with_status = FALSE;
+ }
+
+ // get the --type option
+ $type = drush_get_option_list('type');
+ // use the same logic than $view->type
+ $with_type = FALSE;
+ if (in_array('normal', $type) || in_array('default', $type)|| in_array('overridden', $type)) {
+ $with_type = TRUE;
+ }
+
+ // set the table headers
+ $header = array(
+ dt('Machine name'),
+ dt('Description'),
+ dt('Type'),
+ dt('Status'),
+ dt('Tag'),
+ );
+
+ // setup a row for each view
+ foreach($views as $id => $view){
+ // if options were specified, check that first
+ // mismatch push the loop to the next view
+ if ($with_tags && !in_array($view->tag, $tags)) {
+ continue;
+ }
+ if ($with_status && !$view->disabled == $status) {
+ continue;
+ }
+ if ($with_type && strtolower($view->type) !== $type[0]) {
+ continue;
+ }
+ if ($with_name && !stristr($view->name, $name[0])) {
+ continue;
+ }
+
+ $row = array();
+ // each row entry should be in the same order as the header
+ $row[] = $view->name;
+ $row[] = $view->description;
+ $row[] = $view->type;
+ $row[] = $view->disabled ? dt('Disabled') : dt('Enabled');
+ $row[] = $view->tag;
+
+ // place the row in the appropiate array,
+ // so we can have disabled views at the bottom
+ if($view->disabled) {
+ $disabled_views[] = $row;
+ }
+ else{
+ $enabled_views[] = $row;
+ }
+ unset($row);
+
+ // gather some statistics
+ switch($view->type) {
+ case dt('Normal'):
+ $indb++;
+ break;
+
+ case dt('Overridden'):
+ $overridden++;
+ break;
+
+ case dt('Default'):
+ $incode++;
+ break;
+ }
+ $total++;
+ }
+
+ $disabled = count($disabled_views);
+
+ // sort alphabeticaly
+ asort($disabled_views);
+ asort($enabled_views);
+
+ // if options were used
+ $summary = "";
+ if ($with_name || $with_tags || $with_status || $with_type) {
+ $summary = "Views";
+
+ if ($with_name) {
+ $summary .= " named $name[0]";
+ }
+
+ if ($with_tags) {
+ $tags = implode(" or ", $tags);
+ $summary .= " tagged $tags";
+ }
+
+ if ($with_status) {
+ $status_opt = implode("", $status_opt);
+ $summary .= " which status is '$status_opt'";
+ }
+
+ if ($with_type) {
+ $type = ucfirst($type[0]);
+ $summary .= " of type '$type'";
+ }
+ }
+
+ if (!empty($summary)) {
+ drush_print($summary . "\n");
+ }
+
+ // print all rows as a table
+ if ($total > 0) {
+ $rows = array_merge($enabled_views, $disabled_views);
+ // put the headers as first row
+ array_unshift($rows, $header);
+
+ drush_print_table($rows, TRUE);
+ }
+
+ // print the statistics messages
+ drush_print(dt("A total of @total views were found in this Drupal installation:", array('@total' => $total)));
+ drush_print(dt(" @indb views reside only in the database", array('@indb' => $indb )));
+ drush_print(dt(" @over views are overridden", array('@over' => $overridden)));
+ drush_print(dt(" @incode views are in their default state", array('@incode' => $incode)));
+ drush_print(dt(" @dis views are disabled\n", array('@dis' => $disabled)));
+}
+
+function drush_views_analyze() {
+ views_include('analyze');
+ $messages_count = 0;
+ $total = 0;
+
+ foreach (views_get_all_views() as $view_name => $view) {
+ $total++;
+ if ($messages = views_analyze_view($view)) {
+ drush_print($view_name);
+ foreach ($messages as $message) {
+ $messages_count++;
+ drush_print($message['type'] .': '. $message['message'], 2);
+ }
+ }
+ }
+ drush_log(dt('A total of @total views were analyzed and @messages problems were found.', array('@total' => $total, '@messages' => $messages_count)), 'ok');
+}
+
+/**
+ * Enables views
+ */
+function drush_views_enable() {
+ $viewnames = _convert_csv_to_array(func_get_args());
+ // Return early if no view names were specified.
+ if (empty($viewnames)) {
+ return drush_set_error(dt('Please specify a space delimited list of view names to enable'));
+ }
+ _views_drush_changestatus($viewnames, FALSE);
+}
+
+/**
+ * Disables views
+ */
+function drush_views_disable() {
+ $viewnames = _convert_csv_to_array(func_get_args());
+ // Return early if no view names were specified.
+ if (empty($viewnames)) {
+ return drush_set_error(dt('Please specify a space delimited list of view names to disable'));
+ }
+ _views_drush_changestatus($viewnames, TRUE);
+}
+
+/**
+ * Helper function to enable / disable views
+ * @param $viewnames: array of viewnames to process
+ * @param $status: TRUE to disable or FALSE to enable the view
+ */
+function _views_drush_changestatus($viewnames = array(), $status = NULL) {
+ if ($status !== NULL && !empty($viewnames)) {
+ $changed = FALSE;
+ $processed = $status ? dt('disabled') : dt('enabled');
+ $views_status = variable_get('views_defaults', array());
+
+ foreach ($viewnames as $key => $viewname) {
+ if ($views_status[$viewname] !== $status) {
+ $views_status[$viewname] = $status;
+ $changed = TRUE;
+ drush_log(dt("The view '!name' has been !processed", array('!name' => $viewname, '!processed' => $processed)), 'success');
+ }
+ else {
+ drush_set_error(dt("The view '!name' is already !processed", array('!name' => $viewname, '!processed' => $processed)));
+ }
+ }
+ // If we made changes to views status, save them and clear caches
+ if ($changed) {
+ variable_set('views_defaults', $views_status);
+ views_invalidate_cache();
+ drush_log(dt("Views cache was cleared"), 'ok');
+ drush_log(dt("Menu cache is set to be rebuilt on the next request."), 'ok');
+ }
+ }
+}
+
+/**
+ * Adds a cache clear option for views.
+ */
+function views_drush_cache_clear(&$types) {
+ $types['views'] = 'views_invalidate_cache';
+}
diff --git a/sites/all/modules/views/handlers/views_handler_area.inc b/sites/all/modules/views/handlers/views_handler_area.inc
new file mode 100644
index 000000000..9fed11c33
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_area.inc
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Views area handlers.
+ */
+
+/**
+ * @defgroup views_area_handlers Views area handlers
+ * @{
+ * Handlers to tell Views what can display in header, footer
+ * and empty text in a view.
+ */
+
+/**
+ * Base class for area handlers.
+ *
+ * @ingroup views_area_handlers
+ */
+class views_handler_area extends views_handler {
+
+ /**
+ * Overrides views_handler::init().
+ *
+ * Make sure that no result area handlers are set to be shown when the result
+ * is empty.
+ */
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ if ($this->handler_type == 'empty') {
+ $this->options['empty'] = TRUE;
+ }
+ }
+
+ /**
+ * Get this field's label.
+ */
+ function label() {
+ if (!isset($this->options['label'])) {
+ return $this->ui_name();
+ }
+ return $this->options['label'];
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $this->definition['field'] = !empty($this->definition['field']) ? $this->definition['field'] : '';
+ $label = !empty($this->definition['label']) ? $this->definition['label'] : $this->definition['field'];
+ $options['label'] = array('default' => $label, 'translatable' => TRUE);
+ $options['empty'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Provide extra data to the administration form
+ */
+ function admin_summary() {
+ return $this->label();
+ }
+
+ /**
+ * Default options form that provides the label widget that all fields
+ * should have.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#default_value' => isset($this->options['label']) ? $this->options['label'] : '',
+ '#description' => t('The label for this area that will be displayed only administratively.'),
+ );
+
+ if ($form_state['type'] != 'empty') {
+ $form['empty'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display even if view has no result'),
+ '#default_value' => isset($this->options['empty']) ? $this->options['empty'] : 0,
+ );
+ }
+ }
+
+ /**
+ * Don't run a query
+ */
+ function query() { }
+
+ /**
+ * Render the area
+ */
+ function render($empty = FALSE) {
+ return '';
+ }
+
+ /**
+ * Area handlers shouldn't have groupby.
+ */
+ function use_group_by() {
+ return FALSE;
+ }
+}
+
+/**
+ * A special handler to take the place of missing or broken handlers.
+ *
+ * @ingroup views_area_handlers
+ */
+class views_handler_area_broken extends views_handler_area {
+ function ui_name($short = FALSE) {
+ return t('Broken/missing handler');
+ }
+
+ function ensure_my_table() { /* No table to ensure! */ }
+ function query($group_by = FALSE) { /* No query to run */ }
+ function render($empty = FALSE) { return ''; }
+ function options_form(&$form, &$form_state) {
+ $form['markup'] = array(
+ '#prefix' => '<div class="form-item description">',
+ '#value' => t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.'),
+ );
+ }
+
+ /**
+ * Determine if the handler is considered 'broken'
+ */
+ function broken() { return TRUE; }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/handlers/views_handler_area_messages.inc b/sites/all/modules/views/handlers/views_handler_area_messages.inc
new file mode 100644
index 000000000..eb1e22ae8
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_area_messages.inc
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains views_handler_area_messages.
+ */
+
+/**
+ * Provides an area for messages.
+ */
+class views_handler_area_messages extends views_handler_area {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function option_definition() {
+ $options = parent::option_definition();
+ // Set the default to TRUE so it shows on empty pages by default.
+ $options['empty']['default'] = TRUE;
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render($empty = FALSE) {
+ $return = '';
+ if (!$empty || !empty($this->options['empty'])) {
+ $return = theme('status_messages');
+ }
+ return $return;
+ }
+
+}
diff --git a/sites/all/modules/views/handlers/views_handler_area_result.inc b/sites/all/modules/views/handlers/views_handler_area_result.inc
new file mode 100644
index 000000000..86b1849b8
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_area_result.inc
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_area_result.
+ */
+
+/**
+ * Views area handler to display some configurable result summary.
+ *
+ * @ingroup views_area_handlers
+ */
+class views_handler_area_result extends views_handler_area {
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['content'] = array(
+ 'default' => 'Displaying @start - @end of @total',
+ 'translatable' => TRUE,
+ );
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $variables = array(
+ 'items' => array(
+ '@start -- the initial record number in the set',
+ '@end -- the last record number in the set',
+ '@total -- the total records in the set',
+ '@name -- the human-readable name of the view',
+ '@per_page -- the number of items per page',
+ '@current_page -- the current page number',
+ '@current_record_count -- the current page record count',
+ '@page_count -- the total page count',
+ ),
+ );
+ $list = theme('item_list', $variables);
+ $form['content'] = array(
+ '#title' => t('Display'),
+ '#type' => 'textarea',
+ '#rows' => 3,
+ '#default_value' => $this->options['content'],
+ '#description' => t('You may use HTML code in this field. The following tokens are supported:') . $list,
+ );
+ }
+
+
+ /**
+ * Find out the information to render.
+ */
+ function render($empty = FALSE) {
+ // Must have options and does not work on summaries.
+ if (!isset($this->options['content']) || $this->view->plugin_name == 'default_summary') {
+ return;
+ }
+ $output = '';
+ $format = $this->options['content'];
+ // Calculate the page totals.
+ $current_page = (int) $this->view->get_current_page() + 1;
+ $per_page = (int) $this->view->get_items_per_page();
+ $count = count($this->view->result);
+ // @TODO: Maybe use a possible is views empty functionality.
+ // Not every view has total_rows set, use view->result instead.
+ $total = isset($this->view->total_rows) ? $this->view->total_rows : count($this->view->result);
+ $name = check_plain($this->view->human_name);
+ if ($per_page === 0) {
+ $page_count = 1;
+ $start = 1;
+ $end = $total;
+ }
+ else {
+ $page_count = (int) ceil($total / $per_page);
+ $total_count = $current_page * $per_page;
+ if ($total_count > $total) {
+ $total_count = $total;
+ }
+ $start = ($current_page - 1) * $per_page + 1;
+ $end = $total_count;
+ }
+ $current_record_count = ($end - $start) + 1;
+ // Get the search information.
+ $items = array('start', 'end', 'total', 'name', 'per_page', 'current_page', 'current_record_count', 'page_count');
+ $replacements = array();
+ foreach ($items as $item) {
+ $replacements["@$item"] = ${$item};
+ }
+ // Send the output.
+ if (!empty($total)) {
+ $output .= filter_xss_admin(str_replace(array_keys($replacements), array_values($replacements), $format));
+ }
+ return $output;
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_area_text.inc b/sites/all/modules/views/handlers/views_handler_area_text.inc
new file mode 100644
index 000000000..edb282f32
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_area_text.inc
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_area_text.
+ */
+
+/**
+ * Views area text handler.
+ *
+ * @ingroup views_area_handlers
+ */
+class views_handler_area_text extends views_handler_area {
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['content'] = array('default' => '', 'translatable' => TRUE, 'format_key' => 'format');
+ $options['format'] = array('default' => NULL);
+ $options['tokenize'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['content'] = array(
+ '#type' => 'text_format',
+ '#default_value' => $this->options['content'],
+ '#rows' => 6,
+ '#format' => isset($this->options['format']) ? $this->options['format'] : filter_default_format(),
+ '#wysiwyg' => FALSE,
+ );
+
+ // @TODO: Refactor token handling into a base class.
+ $form['tokenize'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use replacement tokens from the first row'),
+ '#default_value' => $this->options['tokenize'],
+ );
+
+ // Get a list of the available fields and arguments for token replacement.
+ $options = array();
+ foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
+ $options[t('Fields')]["[$field]"] = $handler->ui_name();
+ }
+
+ $count = 0; // This lets us prepare the key as we want it printed.
+ foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
+ $options[t('Arguments')]['%' . ++$count] = t('@argument title', array('@argument' => $handler->ui_name()));
+ $options[t('Arguments')]['!' . $count] = t('@argument input', array('@argument' => $handler->ui_name()));
+ }
+
+ if (!empty($options)) {
+ $output = '<p>' . t('The following tokens are available. If you would like to have the characters \'[\' and \']\' please use the html entity codes \'%5B\' or \'%5D\' or they will get replaced with empty space.' . '</p>');
+ foreach (array_keys($options) as $type) {
+ if (!empty($options[$type])) {
+ $items = array();
+ foreach ($options[$type] as $key => $value) {
+ $items[] = $key . ' == ' . check_plain($value);
+ }
+ $output .= theme('item_list',
+ array(
+ 'items' => $items,
+ 'type' => $type
+ ));
+ }
+ }
+
+ $form['token_help'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Replacement patterns'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#value' => $output,
+ '#id' => 'edit-options-token-help',
+ '#dependency' => array(
+ 'edit-options-tokenize' => array(1),
+ ),
+ '#prefix' => '<div>',
+ '#suffix' => '</div>',
+ );
+ }
+ }
+
+ function options_submit(&$form, &$form_state) {
+ $form_state['values']['options']['format'] = $form_state['values']['options']['content']['format'];
+ $form_state['values']['options']['content'] = $form_state['values']['options']['content']['value'];
+ parent::options_submit($form, $form_state);
+ }
+
+ function render($empty = FALSE) {
+ $format = isset($this->options['format']) ? $this->options['format'] : filter_default_format();
+ if (!$empty || !empty($this->options['empty'])) {
+ return $this->render_textarea($this->options['content'], $format);
+ }
+ return '';
+ }
+
+ /**
+ * Render a text area, using the proper format.
+ */
+ function render_textarea($value, $format) {
+ if ($value) {
+ if ($this->options['tokenize']) {
+ $value = $this->view->style_plugin->tokenize_value($value, 0);
+ }
+ return check_markup($value, $format, '', FALSE);
+ }
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_area_text_custom.inc b/sites/all/modules/views/handlers/views_handler_area_text_custom.inc
new file mode 100644
index 000000000..3627f0c7f
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_area_text_custom.inc
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_area_text_custom.
+ */
+
+/**
+ * Views area text custom handler.
+ *
+ * @ingroup views_area_handlers
+ */
+class views_handler_area_text_custom extends views_handler_area_text {
+
+ function option_definition() {
+ $options = parent::option_definition();
+ unset($options['format']);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ // Alter the form element, to be a regular text area.
+ $form['content']['#type'] = 'textarea';
+ unset($form['content']['#format']);
+ unset($form['content']['#wysiwyg']);
+
+ // @TODO: Use the token refactored base class.
+ }
+
+ // Empty, so we don't inherit options_submit from the parent.
+ function options_submit(&$form, &$form_state) {
+ }
+
+ function render($empty = FALSE) {
+ if (!$empty || !empty($this->options['empty'])) {
+ return $this->render_textarea_custom($this->options['content']);
+ }
+
+ return '';
+ }
+
+ /**
+ * Render a text area with filter_xss_admin.
+ */
+ function render_textarea_custom($value) {
+ if ($value) {
+ if ($this->options['tokenize']) {
+ $value = $this->view->style_plugin->tokenize_value($value, 0);
+ }
+ return $this->sanitize_value($value, 'xss_admin');
+ }
+ }
+
+}
diff --git a/sites/all/modules/views/handlers/views_handler_area_view.inc b/sites/all/modules/views/handlers/views_handler_area_view.inc
new file mode 100644
index 000000000..3b72bf6bd
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_area_view.inc
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_area_view.
+ */
+
+/**
+ * Views area handlers. Insert a view inside of an area.
+ *
+ * @ingroup views_area_handlers
+ */
+class views_handler_area_view extends views_handler_area {
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['view_to_insert'] = array('default' => '');
+ $options['inherit_arguments'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Default options form that provides the label widget that all fields
+ * should have.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $view_display = $this->view->name . ':' . $this->view->current_display;
+
+ $options = array('' => t('-Select-'));
+ $options += views_get_views_as_options(FALSE, 'all', $view_display, FALSE, TRUE);
+ $form['view_to_insert'] = array(
+ '#type' => 'select',
+ '#title' => t('View to insert'),
+ '#default_value' => $this->options['view_to_insert'],
+ '#description' => t('The view to insert into this area.'),
+ '#options' => $options,
+ );
+
+ $form['inherit_arguments'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Inherit contextual filters'),
+ '#default_value' => $this->options['inherit_arguments'],
+ '#description' => t('If checked, this view will receive the same contextual filters as its parent.'),
+ );
+ }
+
+ /**
+ * Render the area
+ */
+ function render($empty = FALSE) {
+ if ($view = $this->loadView()) {
+ if (!empty($this->options['inherit_arguments']) && !empty($this->view->args)) {
+ return $view->preview(NULL, $this->view->args);
+ }
+ else {
+ return $view->preview(NULL);
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Loads the used view for rendering.
+ *
+ * @return \view|NULL
+ * The loaded view or NULL, in case the view was not loadable / recursion
+ * got detected / access got denied.
+ */
+ protected function loadView() {
+ if (empty($this->options['view_to_insert'])) {
+ return NULL;
+ }
+ list($view_name, $display_id) = explode(':', $this->options['view_to_insert']);
+
+ $view = views_get_view($view_name);
+ if (empty($view) || !$view->access($display_id)) {
+ return NULL;
+ }
+ $view->set_display($display_id);
+
+ // Avoid recursion.
+ $view->parent_views += $this->view->parent_views;
+ $view->parent_views[] = "$view_name:$display_id";
+
+ // Check if the view is part of the parent views of this view.
+ $search = "$view_name:$display_id";
+ if (in_array($search, $this->view->parent_views)) {
+ drupal_set_message(t("Recursion detected in view @view display @display.", array('@view' => $view_name, '@display' => $display_id)), 'error');
+ return NULL;
+ }
+
+ return $view;
+ }
+
+}
diff --git a/sites/all/modules/views/handlers/views_handler_argument.inc b/sites/all/modules/views/handlers/views_handler_argument.inc
new file mode 100644
index 000000000..da62e58de
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_argument.inc
@@ -0,0 +1,1244 @@
+<?php
+
+/**
+ * @file
+ * @todo.
+ */
+
+/**
+ * @defgroup views_argument_handlers Views argument handlers
+ * Handlers to tell Views how to contextually filter queries.
+ * @{
+ */
+
+/**
+ * Base class for arguments.
+ *
+ * The basic argument works for very simple arguments such as nid and uid
+ *
+ * Definition terms for this handler:
+ * - name field: The field to use for the name to use in the summary, which is
+ * the displayed output. For example, for the node: nid argument,
+ * the argument itself is the nid, but node.title is displayed.
+ * - name table: The table to use for the name, should it not be in the same
+ * table as the argument.
+ * - empty field name: For arguments that can have no value, such as taxonomy
+ * which can have "no term", this is the string which
+ * will be displayed for this lack of value. Be sure to use
+ * t().
+ * - validate type: A little used string to allow an argument to restrict
+ * which validator is available to just one. Use the
+ * validator ID. This probably should not be used at all,
+ * and may disappear or change.
+ * - numeric: If set to TRUE this field is numeric and will use %d instead of
+ * %s in queries.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument extends views_handler {
+ var $validator = NULL;
+ var $argument = NULL;
+ var $value = NULL;
+
+ /**
+ * The table to use for the name, should it not be in the same table as the argument.
+ * @var string
+ */
+ var $name_table;
+
+ /**
+ * The field to use for the name to use in the summary, which is
+ * the displayed output. For example, for the node: nid argument,
+ * the argument itself is the nid, but node.title is displayed.
+ * @var string
+ */
+ var $name_field;
+
+ /**
+ * Constructor
+ */
+ function construct() {
+ parent::construct();
+
+ if (!empty($this->definition['name field'])) {
+ $this->name_field = $this->definition['name field'];
+ }
+ if (!empty($this->definition['name table'])) {
+ $this->name_table = $this->definition['name table'];
+ }
+ }
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ // Compatibility: The new UI changed several settings.
+ if (!empty($options['wildcard']) && !isset($options['exception']['value'])) {
+ $this->options['exception']['value'] = $options['wildcard'];
+ }
+ if (!empty($options['wildcard_substitution']) && !isset($options['exception']['title'])) {
+ // Enable the checkbox if the title is filled in.
+ $this->options['exception']['title_enable'] = 1;
+ $this->options['exception']['title'] = $options['wildcard_substitution'];
+ }
+
+ if (!isset($options['summary']['format']) && !empty($options['style_plugin'])) {
+ $this->options['summary']['format'] = $options['style_plugin'];
+ }
+
+ // Setup default value.
+ $options['style_options'] = isset($options['style_options']) ? $options['style_options'] : array();
+
+ if (!isset($options['summary']['sort_order']) && !empty($options['default_action']) && $options['default_action'] == 'summary asc') {
+ $this->options['default_action'] = 'summary';
+ $this->options['summary']['sort_order'] = 'asc';
+ $this->options['summary']['number_of_records'] = 0;
+ $this->options['summary_options'] = $options['style_options'];
+ }
+ elseif (!isset($options['summary']['sort_order']) && !empty($options['default_action']) && $options['default_action'] == 'summary desc') {
+ $this->options['default_action'] = 'summary';
+ $this->options['summary']['sort_order'] = 'desc';
+ $this->options['summary']['number_of_records'] = 0;
+ $this->options['summary_options'] = $options['style_options'];
+ }
+ elseif (!isset($options['summary']['sort_order']) && !empty($options['default_action']) && $options['default_action'] == 'summary asc by count') {
+ $this->options['default_action'] = 'summary';
+ $this->options['summary']['sort_order'] = 'asc';
+ $this->options['summary']['number_of_records'] = 1;
+ $this->options['summary_options'] = $options['style_options'];
+ }
+ elseif (!isset($options['summary']['sort_order']) && !empty($options['default_action']) && $options['default_action'] == 'summary desc by count') {
+ $this->options['default_action'] = 'summary';
+ $this->options['summary']['sort_order'] = 'desc';
+ $this->options['summary']['number_of_records'] = 1;
+ $this->options['summary_options'] = $options['style_options'];
+ }
+
+ if (!empty($options['title']) && !isset($options['title_enable'])) {
+ $this->options['title_enable'] = 1;
+ }
+ if (!empty($options['breadcrumb']) && !isset($options['breadcrumb_enable'])) {
+ $this->options['breadcrumb_enable'] = 1;
+ }
+
+ if (!empty($options['validate_type']) && !isset($options['validate']['type'])) {
+ $this->options['validate']['type'] = $options['validate_type'];
+ $this->options['specify_validation'] = 1;
+ }
+ if (!empty($options['validate_fail']) && !isset($options['validate']['fail'])) {
+ $this->options['validate']['fail'] = $options['validate_fail'];
+ $this->options['specify_validation'] = 1;
+ }
+ }
+
+ /**
+ * Give an argument the opportunity to modify the breadcrumb, if it wants.
+ * This only gets called on displays where a breadcrumb is actually used.
+ *
+ * The breadcrumb will be in the form of an array, with the keys being
+ * the path and the value being the already sanitized title of the path.
+ */
+ function set_breadcrumb(&$breadcrumb) { }
+
+ /**
+ * Determine if the argument can generate a breadcrumb
+ *
+ * @return TRUE/FALSE
+ */
+ function uses_breadcrumb() {
+ $info = $this->default_actions($this->options['default_action']);
+ return !empty($info['breadcrumb']);
+ }
+
+ function is_exception($arg = NULL) {
+ if (!isset($arg)) {
+ $arg = isset($this->argument) ? $this->argument : NULL;
+ }
+ return !empty($this->options['exception']['value']) && $this->options['exception']['value'] === $arg;
+ }
+
+ function exception_title() {
+ // If title overriding is off for the exception, return the normal title.
+ if (empty($this->options['exception']['title_enable'])) {
+ return $this->get_title();
+ }
+ return $this->options['exception']['title'];
+ }
+
+ /**
+ * Determine if the argument needs a style plugin.
+ *
+ * @return TRUE/FALSE
+ */
+ function needs_style_plugin() {
+ $info = $this->default_actions($this->options['default_action']);
+ $validate_info = $this->default_actions($this->options['validate']['fail']);
+ return !empty($info['style plugin']) || !empty($validate_info['style plugin']);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['default_action'] = array('default' => 'ignore');
+ $options['exception'] = array(
+ 'contains' => array(
+ 'value' => array('default' => 'all'),
+ 'title_enable' => array('default' => FALSE, 'bool' => TRUE),
+ 'title' => array('default' => 'All', 'translatable' => TRUE),
+ ),
+ );
+ $options['title_enable'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['title'] = array('default' => '', 'translatable' => TRUE);
+ $options['breadcrumb_enable'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['breadcrumb'] = array('default' => '', 'translatable' => TRUE);
+ $options['default_argument_type'] = array('default' => 'fixed', 'export' => 'export_plugin');
+ $options['default_argument_options'] = array('default' => array(), 'export' => FALSE);
+ $options['default_argument_skip_url'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['summary_options'] = array('default' => array(), 'export' => FALSE);
+ $options['summary'] = array(
+ 'contains' => array(
+ 'sort_order' => array('default' => 'asc'),
+ 'number_of_records' => array('default' => 0),
+ 'format' => array('default' => 'default_summary', 'export' => 'export_summary'),
+ ),
+ );
+ $options['specify_validation'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['validate'] = array(
+ 'contains' => array(
+ 'type' => array('default' => 'none', 'export' => 'export_validation'),
+ 'fail' => array('default' => 'not found'),
+ ),
+ );
+ $options['validate_options'] = array('default' => array(), 'export' => FALSE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $argument_text = $this->view->display_handler->get_argument_text();
+
+ $form['#pre_render'][] = 'views_ui_pre_render_move_argument_options';
+
+ $form['description'] = array(
+ '#markup' => $argument_text['description'],
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('description')),
+ );
+
+ $form['no_argument'] = array(
+ '#type' => 'fieldset',
+ '#title' => $argument_text['filter value not present'],
+ );
+ // Everything in the fieldset is floated, so the last element needs to
+ // clear those floats.
+ $form['no_argument']['clearfix'] = array(
+ '#weight' => 1000,
+ '#markup' => '<div class="clearfix"></div>',
+ );
+ $form['default_action'] = array(
+ '#type' => 'radios',
+ '#process' => array('views_ui_process_container_radios'),
+ '#default_value' => $this->options['default_action'],
+ '#fieldset' => 'no_argument',
+ );
+
+ $form['exception'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Exceptions'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#fieldset' => 'no_argument',
+ );
+ $form['exception']['value'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Exception value'),
+ '#size' => 20,
+ '#default_value' => $this->options['exception']['value'],
+ '#description' => t('If this value is received, the filter will be ignored; i.e, "all values"'),
+ );
+ $form['exception']['title_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Override title'),
+ '#default_value' => $this->options['exception']['title_enable'],
+ );
+ $form['exception']['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Override title'),
+ '#title_display' => 'invisible',
+ '#size' => 20,
+ '#default_value' => $this->options['exception']['title'],
+ '#description' => t('Override the view and other argument titles. Use "%1" for the first argument, "%2" for the second, etc.'),
+ '#dependency' => array(
+ 'edit-options-exception-title-enable' => array('1'),
+ ),
+ );
+
+ $options = array();
+ $defaults = $this->default_actions();
+ $validate_options = array();
+ foreach ($defaults as $id => $info) {
+ $options[$id] = $info['title'];
+ if (empty($info['default only'])) {
+ $validate_options[$id] = $info['title'];
+ }
+ if (!empty($info['form method'])) {
+ $this->{$info['form method']}($form, $form_state);
+ }
+ }
+ $form['default_action']['#options'] = $options;
+
+ $form['argument_present'] = array(
+ '#type' => 'fieldset',
+ '#title' => $argument_text['filter value present'],
+ );
+ $form['title_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Override title'),
+ '#default_value' => $this->options['title_enable'],
+ '#fieldset' => 'argument_present',
+ );
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Provide title'),
+ '#title_display' => 'invisible',
+ '#default_value' => $this->options['title'],
+ '#description' => t('Override the view and other argument titles. Use "%1" for the first argument, "%2" for the second, etc.'),
+ '#dependency' => array(
+ 'edit-options-title-enable' => array('1'),
+ ),
+ '#fieldset' => 'argument_present',
+ );
+
+ $form['breadcrumb_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Override breadcrumb'),
+ '#default_value' => $this->options['breadcrumb_enable'],
+ '#fieldset' => 'argument_present',
+ );
+ $form['breadcrumb'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Provide breadcrumb'),
+ '#title_display' => 'invisible',
+ '#default_value' => $this->options['breadcrumb'],
+ '#description' => t('Enter a breadcrumb name you would like to use. See "Title" for percent substitutions.'),
+ '#dependency' => array(
+ 'edit-options-breadcrumb-enable' => array('1'),
+ ),
+ '#fieldset' => 'argument_present',
+ );
+
+ $form['specify_validation'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Specify validation criteria'),
+ '#default_value' => $this->options['specify_validation'],
+ '#fieldset' => 'argument_present',
+ );
+
+ $form['validate'] = array(
+ '#type' => 'container',
+ '#fieldset' => 'argument_present',
+ );
+ // @todo The mockup wanted to use "Validate using" here, but it doesn't
+ // work well with many options (they'd need to be changed as well)
+ $form['validate']['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Validator'),
+ '#default_value' => $this->options['validate']['type'],
+ '#dependency' => array(
+ 'edit-options-specify-validation' => array('1'),
+ ),
+ );
+
+ $validate_types = array('none' => t('- Basic validation -'));
+ $plugins = views_fetch_plugin_data('argument validator');
+ foreach ($plugins as $id => $info) {
+ if (!empty($info['no ui'])) {
+ continue;
+ }
+
+ $valid = TRUE;
+ if (!empty($info['type'])) {
+ $valid = FALSE;
+ if (empty($this->definition['validate type'])) {
+ continue;
+ }
+ foreach ((array) $info['type'] as $type) {
+ if ($type == $this->definition['validate type']) {
+ $valid = TRUE;
+ break;
+ }
+ }
+ }
+
+ // If we decide this validator is ok, add it to the list.
+ if ($valid) {
+ $plugin = $this->get_plugin('argument validator', $id);
+ if ($plugin) {
+ if ($plugin->access() || $this->options['validate']['type'] == $id) {
+ $form['validate']['options'][$id] = array(
+ '#prefix' => '<div id="edit-options-validate-options-' . $id . '-wrapper">',
+ '#suffix' => '</div>',
+ '#type' => 'item',
+ // Even if the plugin has no options add the key to the form_state.
+ '#input' => TRUE, // trick it into checking input to make #process run
+ '#dependency' => array(
+ 'edit-options-specify-validation' => array('1'),
+ 'edit-options-validate-type' => array($id),
+ ),
+ '#dependency_count' => 2,
+ '#id' => 'edit-options-validate-options-' . $id,
+ );
+ $plugin->options_form($form['validate']['options'][$id], $form_state);
+ $validate_types[$id] = $info['title'];
+ }
+ }
+ }
+ }
+
+ asort($validate_types);
+ $form['validate']['type']['#options'] = $validate_types;
+
+ $form['validate']['fail'] = array(
+ '#type' => 'select',
+ '#title' => t('Action to take if filter value does not validate'),
+ '#default_value' => $this->options['validate']['fail'],
+ '#options' => $validate_options,
+ '#dependency' => array(
+ 'edit-options-specify-validation' => array('1'),
+ ),
+ '#fieldset' => 'argument_present',
+ );
+ }
+
+ function options_validate(&$form, &$form_state) {
+ if (empty($form_state['values']['options'])) {
+ return;
+ }
+
+ // Let the plugins do validation.
+ $default_id = $form_state['values']['options']['default_argument_type'];
+ $plugin = $this->get_plugin('argument default', $default_id);
+ if ($plugin && isset($form['argument_default'][$default_id]) && isset($form_state['values']['options']['argument_default'][$default_id])) {
+ $plugin->options_validate($form['argument_default'][$default_id], $form_state, $form_state['values']['options']['argument_default'][$default_id]);
+ }
+
+ // Validate summary plugin options if one is present.
+ if (isset($form_state['values']['options']['summary']['format'])) {
+ $summary_id = $form_state['values']['options']['summary']['format'];
+ $plugin = $this->get_plugin('style', $summary_id);
+ if ($plugin) {
+ $plugin->options_validate($form['summary']['options'][$summary_id], $form_state, $form_state['values']['options']['summary']['options'][$summary_id]);
+ }
+ }
+
+ $validate_id = $form_state['values']['options']['validate']['type'];
+ $plugin = $this->get_plugin('argument validator', $validate_id);
+ if ($plugin) {
+ $plugin->options_validate($form['validate']['options'][$default_id], $form_state, $form_state['values']['options']['validate']['options'][$validate_id]);
+ }
+
+ }
+
+ function options_submit(&$form, &$form_state) {
+ if (empty($form_state['values']['options'])) {
+ return;
+ }
+
+ // Let the plugins make submit modifications if necessary.
+ $default_id = $form_state['values']['options']['default_argument_type'];
+ $plugin = $this->get_plugin('argument default', $default_id);
+ if ($plugin) {
+ $options = &$form_state['values']['options']['argument_default'][$default_id];
+ $plugin->options_submit($form['argument_default'][$default_id], $form_state, $options);
+ // Copy the now submitted options to their final resting place so they get saved.
+ $form_state['values']['options']['default_argument_options'] = $options;
+ }
+
+ // Handle summary plugin options if one is present.
+ if (isset($form_state['values']['options']['summary']['format'])) {
+ $summary_id = $form_state['values']['options']['summary']['format'];
+ $plugin = $this->get_plugin('style', $summary_id);
+ if ($plugin) {
+ $options = &$form_state['values']['options']['summary']['options'][$summary_id];
+ $plugin->options_submit($form['summary']['options'][$summary_id], $form_state, $options);
+ // Copy the now submitted options to their final resting place so they get saved.
+ $form_state['values']['options']['summary_options'] = $options;
+ }
+ }
+
+ $validate_id = $form_state['values']['options']['validate']['type'];
+ $plugin = $this->get_plugin('argument validator', $validate_id);
+ if ($plugin) {
+ $options = &$form_state['values']['options']['validate']['options'][$validate_id];
+ $plugin->options_submit($form['validate']['options'][$validate_id], $form_state, $options);
+ // Copy the now submitted options to their final resting place so they get saved.
+ $form_state['values']['options']['validate_options'] = $options;
+ }
+
+ // Clear out the content of title if it's not enabled.
+ $options =& $form_state['values']['options'];
+ if (empty($options['title_enable'])) {
+ $options['title'] = '';
+ }
+ }
+
+ /**
+ * Provide a list of default behaviors for this argument if the argument
+ * is not present.
+ *
+ * Override this method to provide additional (or fewer) default behaviors.
+ */
+ function default_actions($which = NULL) {
+ $defaults = array(
+ 'ignore' => array(
+ 'title' => t('Display all results for the specified field'),
+ 'method' => 'default_ignore',
+ 'breadcrumb' => TRUE, // generate a breadcrumb to here
+ ),
+ 'default' => array(
+ 'title' => t('Provide default value'),
+ 'method' => 'default_default',
+ 'form method' => 'default_argument_form',
+ 'has default argument' => TRUE,
+ 'default only' => TRUE, // this can only be used for missing argument, not validation failure
+ 'breadcrumb' => TRUE, // generate a breadcrumb to here
+ ),
+ 'not found' => array(
+ 'title' => t('Hide view'),
+ 'method' => 'default_not_found',
+ 'hard fail' => TRUE, // This is a hard fail condition
+ ),
+ 'summary' => array(
+ 'title' => t('Display a summary'),
+ 'method' => 'default_summary',
+ 'form method' => 'default_summary_form',
+ 'style plugin' => TRUE,
+ 'breadcrumb' => TRUE, // generate a breadcrumb to here
+ ),
+ 'empty' => array(
+ 'title' => t('Display contents of "No results found"'),
+ 'method' => 'default_empty',
+ 'breadcrumb' => TRUE, // generate a breadcrumb to here
+ ),
+ 'access denied' => array(
+ 'title' => t('Display "Access Denied"'),
+ 'method' => 'default_access_denied',
+ 'breadcrumb' => FALSE, // generate a breadcrumb to here
+ ),
+ );
+
+ if ($this->view->display_handler->has_path()) {
+ $defaults['not found']['title'] = t('Show "Page not found"');
+ }
+
+ if ($which) {
+ if (!empty($defaults[$which])) {
+ return $defaults[$which];
+ }
+ }
+ else {
+ return $defaults;
+ }
+ }
+
+ /**
+ * Provide a form for selecting the default argument when the
+ * default action is set to provide default argument.
+ */
+ function default_argument_form(&$form, &$form_state) {
+ $plugins = views_fetch_plugin_data('argument default');
+ $options = array();
+
+ $form['default_argument_skip_url'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Skip default argument for view URL'),
+ '#default_value' => $this->options['default_argument_skip_url'],
+ '#description' => t('Select whether to include this default argument when constructing the URL for this view. Skipping default arguments is useful e.g. in the case of feeds.')
+ );
+
+ $form['default_argument_type'] = array(
+ '#prefix' => '<div id="edit-options-default-argument-type-wrapper">',
+ '#suffix' => '</div>',
+ '#type' => 'select',
+ '#id' => 'edit-options-default-argument-type',
+ '#title' => t('Type'),
+ '#default_value' => $this->options['default_argument_type'],
+
+ '#dependency' => array('radio:options[default_action]' => array('default')),
+ // Views custom key, moves this element to the appropriate container
+ // under the radio button.
+ '#argument_option' => 'default',
+ );
+
+ foreach ($plugins as $id => $info) {
+ if (!empty($info['no ui'])) {
+ continue;
+ }
+ $plugin = $this->get_plugin('argument default', $id);
+ if ($plugin) {
+ if ($plugin->access() || $this->options['default_argument_type'] == $id) {
+ $form['argument_default']['#argument_option'] = 'default';
+ $form['argument_default'][$id] = array(
+ '#prefix' => '<div id="edit-options-argument-default-options-' . $id . '-wrapper">',
+ '#suffix' => '</div>',
+ '#id' => 'edit-options-argument-default-options-' . $id,
+ '#type' => 'item',
+ // Even if the plugin has no options add the key to the form_state.
+ '#input' => TRUE,
+ '#dependency' => array(
+ 'radio:options[default_action]' => array('default'),
+ 'edit-options-default-argument-type' => array($id)
+ ),
+ '#dependency_count' => 2,
+ );
+ $options[$id] = $info['title'];
+ $plugin->options_form($form['argument_default'][$id], $form_state);
+ }
+ }
+ }
+
+ asort($options);
+ $form['default_argument_type']['#options'] = $options;
+ }
+
+ /**
+ * Provide a form for selecting further summary options when the
+ * default action is set to display one.
+ */
+ function default_summary_form(&$form, &$form_state) {
+ $style_plugins = views_fetch_plugin_data('style');
+ $summary_plugins = array();
+ $format_options = array();
+ foreach ($style_plugins as $key => $plugin) {
+ if (isset($plugin['type']) && $plugin['type'] == 'summary') {
+ $summary_plugins[$key] = $plugin;
+ $format_options[$key] = $plugin['title'];
+ }
+ }
+
+ $form['summary'] = array(
+ // Views custom key, moves this element to the appropriate container
+ // under the radio button.
+ '#argument_option' => 'summary',
+ );
+ $form['summary']['sort_order'] = array(
+ '#type' => 'radios',
+ '#title' => t('Sort order'),
+ '#options' => array('asc' => t('Ascending'), 'desc' => t('Descending')),
+ '#default_value' => $this->options['summary']['sort_order'],
+ '#dependency' => array('radio:options[default_action]' => array('summary')),
+ );
+ $form['summary']['number_of_records'] = array(
+ '#type' => 'radios',
+ '#title' => t('Sort by'),
+ '#default_value' => $this->options['summary']['number_of_records'],
+ '#options' => array(
+ 0 => $this->get_sort_name(),
+ 1 => t('Number of records')
+ ),
+ '#dependency' => array('radio:options[default_action]' => array('summary')),
+ );
+
+ $form['summary']['format'] = array(
+ '#type' => 'radios',
+ '#title' => t('Format'),
+ '#options' => $format_options,
+ '#default_value' => $this->options['summary']['format'],
+ '#dependency' => array('radio:options[default_action]' => array('summary')),
+ );
+
+ foreach ($summary_plugins as $id => $info) {
+ if (empty($info['uses options'])) {
+ continue;
+ }
+ $plugin = $this->get_plugin('style', $id);
+ if ($plugin) {
+ $form['summary']['options'][$id] = array(
+ '#prefix' => '<div id="edit-options-summary-options-' . $id . '-wrapper">',
+ '#suffix' => '</div>',
+ '#id' => 'edit-options-summary-options-' . $id,
+ '#type' => 'item',
+ '#input' => TRUE, // trick it into checking input to make #process run
+ '#dependency' => array(
+ 'radio:options[default_action]' => array('summary'),
+ 'radio:options[summary][format]' => array($id),
+ ),
+ '#dependency_count' => 2,
+ );
+ $options[$id] = $info['title'];
+ $plugin->options_form($form['summary']['options'][$id], $form_state);
+ }
+ }
+ }
+
+ /**
+ * Handle the default action, which means our argument wasn't present.
+ *
+ * Override this method only with extreme care.
+ *
+ * @return
+ * A boolean value; if TRUE, continue building this view. If FALSE,
+ * building the view will be aborted here.
+ */
+ function default_action($info = NULL) {
+ if (!isset($info)) {
+ $info = $this->default_actions($this->options['default_action']);
+ }
+
+ if (!$info) {
+ return FALSE;
+ }
+
+ if (!empty($info['method args'])) {
+ return call_user_func_array(array(&$this, $info['method']), $info['method args']);
+ }
+ else {
+ return $this->{$info['method']}();
+ }
+ }
+
+ /**
+ * How to act if validation failes
+ */
+ function validate_fail() {
+ $info = $this->default_actions($this->options['validate']['fail']);
+ return $this->default_action($info);
+ }
+ /**
+ * Default action: ignore.
+ *
+ * If an argument was expected and was not given, in this case, simply
+ * ignore the argument entirely.
+ */
+ function default_ignore() {
+ return TRUE;
+ }
+
+ /**
+ * Default action: not found.
+ *
+ * If an argument was expected and was not given, in this case, report
+ * the view as 'not found' or hide it.
+ */
+ function default_not_found() {
+ // Set a failure condition and let the display manager handle it.
+ $this->view->build_info['fail'] = TRUE;
+ return FALSE;
+ }
+
+ /**
+ * Default action: access denied.
+ *
+ * If an argument was expected and was not given, in this case, report
+ * the view as 'access denied'.
+ */
+ function default_access_denied() {
+ $this->view->build_info['denied'] = TRUE;
+ return FALSE;
+ }
+
+ /**
+ * Default action: empty
+ *
+ * If an argument was expected and was not given, in this case, display
+ * the view's empty text
+ */
+ function default_empty() {
+ // We return with no query; this will force the empty text.
+ $this->view->built = TRUE;
+ $this->view->executed = TRUE;
+ $this->view->result = array();
+ return FALSE;
+ }
+
+ /**
+ * This just returns true. The view argument builder will know where
+ * to find the argument from.
+ */
+ function default_default() {
+ return TRUE;
+ }
+
+ /**
+ * Determine if the argument is set to provide a default argument.
+ */
+ function has_default_argument() {
+ $info = $this->default_actions($this->options['default_action']);
+ return !empty($info['has default argument']);
+ }
+
+ /**
+ * Get a default argument, if available.
+ */
+ function get_default_argument() {
+ $plugin = $this->get_plugin('argument default');
+ if ($plugin) {
+ return $plugin->get_argument();
+ }
+ }
+
+ /**
+ * Process the summary arguments for display.
+ *
+ * For example, the validation plugin may want to alter an argument for use in
+ * the URL.
+ */
+ function process_summary_arguments(&$args) {
+ if ($this->options['validate']['type'] != 'none') {
+ if (isset($this->validator) || $this->validator = $this->get_plugin('argument validator')) {
+ $this->validator->process_summary_arguments($args);
+ }
+ }
+ }
+
+ /**
+ * Default action: summary.
+ *
+ * If an argument was expected and was not given, in this case, display
+ * a summary query.
+ */
+ function default_summary() {
+ $this->view->build_info['summary'] = TRUE;
+ $this->view->build_info['summary_level'] = $this->options['id'];
+
+ // Change the display style to the summary style for this
+ // argument.
+ $this->view->plugin_name = $this->options['summary']['format'];
+ $this->view->style_options = $this->options['summary_options'];
+
+ // Clear out the normal primary field and whatever else may have
+ // been added and let the summary do the work.
+ $this->query->clear_fields();
+ $this->summary_query();
+
+ $by = $this->options['summary']['number_of_records'] ? 'num_records' : NULL;
+ $this->summary_sort($this->options['summary']['sort_order'], $by);
+
+ // Summaries have their own sorting and fields, so tell the View not
+ // to build these.
+ $this->view->build_sort = $this->view->build_fields = FALSE;
+ return TRUE;
+ }
+
+ /**
+ * Build the info for the summary query.
+ *
+ * This must:
+ * - add_groupby: group on this field in order to create summaries.
+ * - add_field: add a 'num_nodes' field for the count. Usually it will
+ * be a count on $view->base_field
+ * - set_count_field: Reset the count field so we get the right paging.
+ *
+ * @return
+ * The alias used to get the number of records (count) for this entry.
+ */
+ function summary_query() {
+ $this->ensure_my_table();
+ // Add the field.
+ $this->base_alias = $this->query->add_field($this->table_alias, $this->real_field);
+
+ $this->summary_name_field();
+ return $this->summary_basics();
+ }
+
+ /**
+ * Add the name field, which is the field displayed in summary queries.
+ * This is often used when the argument is numeric.
+ */
+ function summary_name_field() {
+ // Add the 'name' field. For example, if this is a uid argument, the
+ // name field would be 'name' (i.e, the username).
+
+ if (isset($this->name_table)) {
+ // if the alias is different then we're probably added, not ensured,
+ // so look up the join and add it instead.
+ if ($this->table_alias != $this->name_table) {
+ $j = views_get_table_join($this->name_table, $this->table);
+ if ($j) {
+ $join = clone $j;
+ $join->left_table = $this->table_alias;
+ $this->name_table_alias = $this->query->add_table($this->name_table, $this->relationship, $join);
+ }
+ }
+ else {
+ $this->name_table_alias = $this->query->ensure_table($this->name_table, $this->relationship);
+ }
+ }
+ else {
+ $this->name_table_alias = $this->table_alias;
+ }
+
+ if (isset($this->name_field)) {
+ $this->name_alias = $this->query->add_field($this->name_table_alias, $this->name_field);
+ }
+ else {
+ $this->name_alias = $this->base_alias;
+ }
+ }
+
+ /**
+ * Some basic summary behavior that doesn't need to be repeated as much as
+ * code that goes into summary_query()
+ */
+ function summary_basics($count_field = TRUE) {
+ // Add the number of nodes counter
+ $distinct = ($this->view->display_handler->get_option('distinct') && empty($this->query->no_distinct));
+
+ $count_alias = $this->query->add_field($this->query->base_table, $this->query->base_field, 'num_records',
+ array('count' => TRUE, 'distinct' => $distinct));
+ $this->query->add_groupby($this->name_alias);
+
+ if ($count_field) {
+ $this->query->set_count_field($this->table_alias, $this->real_field);
+ }
+
+ $this->count_alias = $count_alias;
+ }
+
+ /**
+ * Sorts the summary based upon the user's selection. The base variant of
+ * this is usually adequte.
+ *
+ * @param $order
+ * The order selected in the UI.
+ */
+ function summary_sort($order, $by = NULL) {
+ $this->query->add_orderby(NULL, NULL, $order, (!empty($by) ? $by : $this->name_alias));
+ }
+
+ /**
+ * Provide the argument to use to link from the summary to the next level;
+ * this will be called once per row of a summary, and used as part of
+ * $view->get_url().
+ *
+ * @param $data
+ * The query results for the row.
+ */
+ function summary_argument($data) {
+ return $data->{$this->base_alias};
+ }
+
+ /**
+ * Provides the name to use for the summary. By default this is just
+ * the name field.
+ *
+ * @param $data
+ * The query results for the row.
+ */
+ function summary_name($data) {
+ $value = $data->{$this->name_alias};
+ if (empty($value) && !empty($this->definition['empty field name'])) {
+ $value = $this->definition['empty field name'];
+ }
+ return check_plain($value);
+ }
+
+ /**
+ * Set up the query for this argument.
+ *
+ * The argument sent may be found at $this->argument.
+ */
+ function query($group_by = FALSE) {
+ $this->ensure_my_table();
+ $this->query->add_where(0, "$this->table_alias.$this->real_field", $this->argument);
+ }
+
+ /**
+ * Get the title this argument will assign the view, given the argument.
+ *
+ * This usually needs to be overridden to provide a proper title.
+ */
+ function title() {
+ return check_plain($this->argument);
+ }
+
+ /**
+ * Called by the view object to get the title. This may be set by a
+ * validator so we don't necessarily call through to title().
+ */
+ function get_title() {
+ if (isset($this->validated_title)) {
+ return $this->validated_title;
+ }
+ else {
+ return $this->title();
+ }
+ }
+
+ /**
+ * Validate that this argument works. By default, all arguments are valid.
+ */
+ function validate_arg($arg) {
+ // By using % in URLs, arguments could be validated twice; this eases
+ // that pain.
+ if (isset($this->argument_validated)) {
+ return $this->argument_validated;
+ }
+
+ if ($this->is_exception($arg)) {
+ return $this->argument_validated = TRUE;
+ }
+
+ if ($this->options['validate']['type'] == 'none') {
+ return $this->argument_validated = $this->validate_argument_basic($arg);
+ }
+
+ $plugin = $this->get_plugin('argument validator');
+ if ($plugin) {
+ return $this->argument_validated = $plugin->validate_argument($arg);
+ }
+
+ // If the plugin isn't found, fall back to the basic validation path:
+ return $this->argument_validated = $this->validate_argument_basic($arg);
+ }
+
+ /**
+ * Called by the menu system to validate an argument.
+ *
+ * This checks to see if this is a 'soft fail', which means that if the
+ * argument fails to validate, but there is an action to take anyway,
+ * then validation cannot actually fail.
+ */
+ function validate_argument($arg) {
+ $validate_info = $this->default_actions($this->options['validate']['fail']);
+ if (empty($validate_info['hard fail'])) {
+ return TRUE;
+ }
+
+ $rc = $this->validate_arg($arg);
+
+ // If the validator has changed the validate fail condition to a
+ // soft fail, deal with that:
+ $validate_info = $this->default_actions($this->options['validate']['fail']);
+ if (empty($validate_info['hard fail'])) {
+ return TRUE;
+ }
+
+ return $rc;
+ }
+
+ /**
+ * Provide a basic argument validation.
+ *
+ * This can be overridden for more complex types; the basic
+ * validator only checks to see if the argument is not NULL
+ * or is numeric if the definition says it's numeric.
+ */
+ function validate_argument_basic($arg) {
+ if (!isset($arg) || $arg === '') {
+ return FALSE;
+ }
+
+ if (!empty($this->definition['numeric']) && !isset($this->options['break_phrase']) && !is_numeric($arg)) {
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Set the input for this argument
+ *
+ * @return TRUE if it successfully validates; FALSE if it does not.
+ */
+ function set_argument($arg) {
+ $this->argument = $arg;
+ return $this->validate_arg($arg);
+ }
+
+ /**
+ * Get the value of this argument.
+ */
+ function get_value() {
+ // If we already processed this argument, we're done.
+ if (isset($this->argument)) {
+ return $this->argument;
+ }
+
+ // Otherwise, we have to pretend to process ourself to find the value.
+ $value = NULL;
+ // Find the position of this argument within the view.
+ $position = 0;
+ foreach ($this->view->argument as $id => $argument) {
+ if ($id == $this->options['id']) {
+ break;
+ }
+ $position++;
+ }
+
+ $arg = isset($this->view->args[$position]) ? $this->view->args[$position] : NULL;
+ $this->position = $position;
+
+ // Clone ourselves so that we don't break things when we're really
+ // processing the arguments.
+ $argument = clone $this;
+ if (!isset($arg) && $argument->has_default_argument()) {
+ $arg = $argument->get_default_argument();
+
+ // remember that this argument was computed, not passed on the URL.
+ $this->is_default = TRUE;
+ }
+ // Set the argument, which will also validate that the argument can be set.
+ if ($argument->set_argument($arg)) {
+ $value = $argument->argument;
+ }
+ unset($argument);
+ return $value;
+ }
+
+ /**
+ * Export handler for summary export.
+ *
+ * Arguments can have styles for the summary view. This special export
+ * handler makes sure this works properly.
+ */
+ function export_summary($indent, $prefix, $storage, $option, $definition, $parents) {
+ $output = '';
+ $name = $this->options['summary'][$option];
+ $options = $this->options['summary_options'];
+
+ $plugin = views_get_plugin('style', $name);
+ if ($plugin) {
+ $plugin->init($this->view, $this->view->display_handler->display, $options);
+ // Write which plugin to use.
+ $output .= $indent . $prefix . "['summary']['$option'] = '$name';\n";
+
+ // Pass off to the plugin to export itself.
+ $output .= $plugin->export_options($indent, $prefix . "['summary_options']");
+ }
+
+ return $output;
+ }
+
+ /**
+ * Export handler for validation export.
+ *
+ * Arguments use validation plugins. This special export handler makes sure
+ * this works properly.
+ */
+ function export_validation($indent, $prefix, $storage, $option, $definition, $parents) {
+ $output = '';
+ $name = $this->options['validate'][$option];
+ $options = $this->options['validate_options'];
+
+ $plugin = views_get_plugin('argument validator', $name);
+ if ($plugin) {
+ $plugin->init($this->view, $this->display, $options);
+ // Write which plugin to use.
+ $output .= $indent . $prefix . "['validate']['$option'] = '$name';\n";
+
+ // Pass off to the plugin to export itself.
+ $output .= $plugin->export_options($indent, $prefix . "['validate_options']");
+ }
+
+ return $output;
+ }
+
+ /**
+ * Generic plugin export handler.
+ *
+ * Since style and validation plugins have their own export handlers, this
+ * one is currently only used for default argument plugins.
+ */
+ function export_plugin($indent, $prefix, $storage, $option, $definition, $parents) {
+ $output = '';
+ if ($option == 'default_argument_type') {
+ $type = 'argument default';
+ $option_name = 'default_argument_options';
+ }
+
+ $plugin = $this->get_plugin($type);
+ $name = $this->options[$option];
+
+ if ($plugin) {
+ // Write which plugin to use.
+ $output .= $indent . $prefix . "['$option'] = '$name';\n";
+
+ // Pass off to the plugin to export itself.
+ $output .= $plugin->export_options($indent, $prefix . "['$option_name']");
+ }
+
+ return $output;
+ }
+
+ /**
+ * Get the display or row plugin, if it exists.
+ */
+ function get_plugin($type = 'argument default', $name = NULL) {
+ $options = array();
+ switch ($type) {
+ case 'argument default':
+ $plugin_name = $this->options['default_argument_type'];
+ $options_name = 'default_argument_options';
+ break;
+ case 'argument validator':
+ $plugin_name = $this->options['validate']['type'];
+ $options_name = 'validate_options';
+ break;
+ case 'style':
+ $plugin_name = $this->options['summary']['format'];
+ $options_name = 'summary_options';
+ }
+
+ if (!$name) {
+ $name = $plugin_name;
+ }
+
+ // we only fetch the options if we're fetching the plugin actually
+ // in use.
+ if ($name == $plugin_name) {
+ $options = $this->options[$options_name];
+ }
+
+ $plugin = views_get_plugin($type, $name);
+ if ($plugin) {
+ // Style plugins expects different parameters as argument related plugins.
+ if ($type == 'style') {
+ $plugin->init($this->view, $this->view->display_handler->display, $options);
+ }
+ else {
+ $plugin->init($this->view, $this, $options);
+ }
+ return $plugin;
+ }
+ }
+
+ /**
+ * Return a description of how the argument would normally be sorted.
+ *
+ * Subclasses should override this to specify what the default sort order of
+ * their argument is (e.g. alphabetical, numeric, date).
+ */
+ function get_sort_name() {
+ return t('Default sort', array(), array('context' => 'Sort order'));
+ }
+}
+
+/**
+ * A special handler to take the place of missing or broken handlers.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_broken extends views_handler_argument {
+ function ui_name($short = FALSE) {
+ return t('Broken/missing handler');
+ }
+
+ function ensure_my_table() { /* No table to ensure! */ }
+ function query($group_by = FALSE) { /* No query to run */ }
+ function options_form(&$form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => '<div class="form-item description">' . t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.') . '</div>',
+ );
+ }
+
+ /**
+ * Determine if the handler is considered 'broken'
+ */
+ function broken() { return TRUE; }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/handlers/views_handler_argument_date.inc b/sites/all/modules/views/handlers/views_handler_argument_date.inc
new file mode 100644
index 000000000..6803c3606
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_argument_date.inc
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_date.
+ */
+
+/**
+ * Abstract argument handler for dates.
+ *
+ * Adds an option to set a default argument based on the current date.
+ *
+ * @param $arg_format
+ * The format string to use on the current time when
+ * creating a default date argument.
+ *
+ * Definitions terms:
+ * - many to one: If true, the "many to one" helper will be used.
+ * - invalid input: A string to give to the user for obviously invalid input.
+ * This is deprecated in favor of argument validators.
+ *
+ * @see views_many_to_one_helper()
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_date extends views_handler_argument_formula {
+ var $option_name = 'default_argument_date';
+ var $arg_format = 'Y-m-d';
+
+ /**
+ * Add an option to set the default value to the current date.
+ */
+ function default_argument_form(&$form, &$form_state) {
+ parent::default_argument_form($form, $form_state);
+ $form['default_argument_type']['#options'] += array('date' => t('Current date'));
+ $form['default_argument_type']['#options'] += array('node_created' => t("Current node's creation time"));
+ $form['default_argument_type']['#options'] += array('node_changed' => t("Current node's update time")); }
+
+ /**
+ * Set the empty argument value to the current date,
+ * formatted appropriately for this argument.
+ */
+ function get_default_argument($raw = FALSE) {
+ if (!$raw && $this->options['default_argument_type'] == 'date') {
+ return date($this->arg_format, REQUEST_TIME);
+ }
+ else if (!$raw && in_array($this->options['default_argument_type'], array('node_created', 'node_changed'))) {
+ foreach (range(1, 3) as $i) {
+ $node = menu_get_object('node', $i);
+ if (!empty($node)) {
+ continue;
+ }
+ }
+
+ if (arg(0) == 'node' && is_numeric(arg(1))) {
+ $node = node_load(arg(1));
+ }
+
+ if (empty($node)) {
+ return parent::get_default_argument();
+ }
+ elseif ($this->options['default_argument_type'] == 'node_created') {
+ return date($this->arg_format, $node->created);
+ }
+ elseif ($this->options['default_argument_type'] == 'node_changed') {
+ return date($this->arg_format, $node->changed);
+ }
+ }
+
+ return parent::get_default_argument($raw);
+ }
+
+ /**
+ * The date handler provides some default argument types, which aren't argument default plugins,
+ * so addapt the export mechanism.
+ */
+ function export_plugin($indent, $prefix, $storage, $option, $definition, $parents) {
+
+ // Only use a special behaviour for the special argument types, else just
+ // use the default behaviour.
+ if ($option == 'default_argument_type') {
+ $type = 'argument default';
+ $option_name = 'default_argument_options';
+
+ $plugin = $this->get_plugin($type);
+ $name = $this->options[$option];
+ if (in_array($name, array('date', 'node_created', 'node_changed'))) {
+
+ // Write which plugin to use.
+ $output = $indent . $prefix . "['$option'] = '$name';\n";
+ return $output;
+ }
+ }
+ return parent::export_plugin($indent, $prefix, $storage, $option, $definition, $parents);
+ }
+
+
+ function get_sort_name() {
+ return t('Date', array(), array('context' => 'Sort order'));
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_argument_formula.inc b/sites/all/modules/views/handlers/views_handler_argument_formula.inc
new file mode 100644
index 000000000..76f5991ad
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_argument_formula.inc
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_formula.
+ */
+
+/**
+ * Abstract argument handler for simple formulae.
+ *
+ * Child classes of this object should implement summary_argument, at least.
+ *
+ * Definition terms:
+ * - formula: The formula to use for this handler.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_formula extends views_handler_argument {
+ var $formula = NULL;
+ /**
+ * Constructor
+ */
+ function construct() {
+ parent::construct();
+
+ if (!empty($this->definition['formula'])) {
+ $this->formula = $this->definition['formula'];
+ }
+ }
+
+ function get_formula() {
+ return str_replace('***table***', $this->table_alias, $this->formula);
+ }
+
+ /**
+ * Build the summary query based on a formula
+ */
+ function summary_query() {
+ $this->ensure_my_table();
+ // Now that our table is secure, get our formula.
+ $formula = $this->get_formula();
+
+ // Add the field.
+ $this->base_alias = $this->name_alias = $this->query->add_field(NULL, $formula, $this->field);
+ $this->query->set_count_field(NULL, $formula, $this->field);
+
+ return $this->summary_basics(FALSE);
+ }
+
+ /**
+ * Build the query based upon the formula
+ */
+ function query($group_by = FALSE) {
+ $this->ensure_my_table();
+ // Now that our table is secure, get our formula.
+ $placeholder = $this->placeholder();
+ $formula = $this->get_formula() .' = ' . $placeholder;
+ $placeholders = array(
+ $placeholder => $this->argument,
+ );
+ $this->query->add_where(0, $formula, $placeholders, 'formula');
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_argument_group_by_numeric.inc b/sites/all/modules/views/handlers/views_handler_argument_group_by_numeric.inc
new file mode 100644
index 000000000..aa522ea0f
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_argument_group_by_numeric.inc
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_group_by_numeric.
+ */
+
+/**
+ * Simple handler for arguments using group by.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_group_by_numeric extends views_handler_argument {
+ function query($group_by = FALSE) {
+ $this->ensure_my_table();
+ $field = $this->get_field();
+ $placeholder = $this->placeholder();
+
+ $this->query->add_having_expression(0, "$field = $placeholder", array($placeholder => $this->argument));
+ }
+
+ function ui_name($short = FALSE) {
+ return $this->get_field(parent::ui_name($short));
+ }
+
+ function get_sort_name() {
+ return t('Numerical', array(), array('context' => 'Sort order'));
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_argument_many_to_one.inc b/sites/all/modules/views/handlers/views_handler_argument_many_to_one.inc
new file mode 100644
index 000000000..3446760fe
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_argument_many_to_one.inc
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_many_to_one.
+ */
+
+/**
+ * An argument handler for use in fields that have a many to one relationship
+ * with the table(s) to the left. This adds a bunch of options that are
+ * reasonably common with this type of relationship.
+ * Definition terms:
+ * - numeric: If true, the field will be considered numeric. Probably should
+ * always be set TRUE as views_handler_argument_string has many to one
+ * capabilities.
+ * - zero is null: If true, a 0 will be handled as empty, so for example
+ * a default argument can be provided or a summary can be shown.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_many_to_one extends views_handler_argument {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ $this->helper = new views_many_to_one_helper($this);
+
+ // Ensure defaults for these, during summaries and stuff:
+ $this->operator = 'or';
+ $this->value = array();
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ if (!empty($this->definition['numeric'])) {
+ $options['break_phrase'] = array('default' => FALSE, 'bool' => TRUE);
+ }
+
+ $options['add_table'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['require_value'] = array('default' => FALSE, 'bool' => TRUE);
+
+ if (isset($this->helper)) {
+ $this->helper->option_definition($options);
+ }
+ else {
+ $helper = new views_many_to_one_helper($this);
+ $helper->option_definition($options);
+ }
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ // allow + for or, , for and
+ if (!empty($this->definition['numeric'])) {
+ $form['break_phrase'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple values'),
+ '#description' => t('If selected, users can enter multiple values in the form of 1+2+3 (for OR) or 1,2,3 (for AND).'),
+ '#default_value' => !empty($this->options['break_phrase']),
+ '#fieldset' => 'more',
+ );
+ }
+
+ $form['add_table'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple filter values to work together'),
+ '#description' => t('If selected, multiple instances of this filter can work together, as though multiple values were supplied to the same filter. This setting is not compatible with the "Reduce duplicates" setting.'),
+ '#default_value' => !empty($this->options['add_table']),
+ '#fieldset' => 'more',
+ );
+
+ $form['require_value'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Do not display items with no value in summary'),
+ '#default_value' => !empty($this->options['require_value']),
+ '#fieldset' => 'more',
+ );
+
+ $this->helper->options_form($form, $form_state);
+ }
+
+ /**
+ * Override ensure_my_table so we can control how this joins in.
+ * The operator actually has influence over joining.
+ */
+ function ensure_my_table() {
+ $this->helper->ensure_my_table();
+ }
+
+ function query($group_by = FALSE) {
+ $empty = FALSE;
+ if (isset($this->definition['zero is null']) && $this->definition['zero is null']) {
+ if (empty($this->argument)) {
+ $empty = TRUE;
+ }
+ }
+ else {
+ if (!isset($this->argument)) {
+ $empty = TRUE;
+ }
+ }
+ if ($empty) {
+ parent::ensure_my_table();
+ $this->query->add_where(0, "$this->table_alias.$this->real_field", NULL, 'IS NULL');
+ return;
+ }
+
+ if (!empty($this->options['break_phrase'])) {
+ views_break_phrase($this->argument, $this);
+ }
+ else {
+ $this->value = array($this->argument);
+ $this->operator = 'or';
+ }
+
+ $this->helper->add_filter();
+ }
+
+ function title() {
+ if (!$this->argument) {
+ return !empty($this->definition['empty field name']) ? $this->definition['empty field name'] : t('Uncategorized');
+ }
+
+ if (!empty($this->options['break_phrase'])) {
+ views_break_phrase($this->argument, $this);
+ }
+ else {
+ $this->value = array($this->argument);
+ $this->operator = 'or';
+ }
+
+ // @todo -- both of these should check definition for alternate keywords.
+
+ if (empty($this->value)) {
+ return !empty($this->definition['empty field name']) ? $this->definition['empty field name'] : t('Uncategorized');
+ }
+
+ if ($this->value === array(-1)) {
+ return !empty($this->definition['invalid input']) ? $this->definition['invalid input'] : t('Invalid input');
+ }
+
+ return implode($this->operator == 'or' ? ' + ' : ', ', $this->title_query());
+ }
+
+ function summary_query() {
+ $field = $this->table . '.' . $this->field;
+ $join = $this->get_join();
+
+ if (!empty($this->options['require_value'])) {
+ $join->type = 'INNER';
+ }
+
+ if (empty($this->options['add_table']) || empty($this->view->many_to_one_tables[$field])) {
+ $this->table_alias = $this->query->ensure_table($this->table, $this->relationship, $join);
+ }
+ else {
+ $this->table_alias = $this->helper->summary_join();
+ }
+
+ // Add the field.
+ $this->base_alias = $this->query->add_field($this->table_alias, $this->real_field);
+
+ $this->summary_name_field();
+
+ return $this->summary_basics();
+ }
+
+ function summary_argument($data) {
+ $value = $data->{$this->base_alias};
+ if (empty($value)) {
+ $value = 0;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Override for specific title lookups.
+ */
+ function title_query() {
+ return $this->value;
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_argument_null.inc b/sites/all/modules/views/handlers/views_handler_argument_null.inc
new file mode 100644
index 000000000..5b427282d
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_argument_null.inc
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_null.
+ */
+
+/**
+ * Argument handler that ignores the argument.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_null extends views_handler_argument {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['must_not_be'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Override options_form() so that only the relevant options
+ * are displayed to the user.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['must_not_be'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Fail basic validation if any argument is given'),
+ '#default_value' => !empty($this->options['must_not_be']),
+ '#description' => t('By checking this field, you can use this to make sure views with more arguments than necessary fail validation.'),
+ '#fieldset' => 'more',
+ );
+
+ unset($form['exception']);
+ }
+
+ /**
+ * Override default_actions() to remove actions that don't
+ * make sense for a null argument.
+ */
+ function default_actions($which = NULL) {
+ if ($which) {
+ if (in_array($which, array('ignore', 'not found', 'empty', 'default'))) {
+ return parent::default_actions($which);
+ }
+ return;
+ }
+ $actions = parent::default_actions();
+ unset($actions['summary asc']);
+ unset($actions['summary desc']);
+ return $actions;
+ }
+
+ function validate_argument_basic($arg) {
+ if (!empty($this->options['must_not_be'])) {
+ return !isset($arg);
+ }
+
+ return parent::validate_argument_basic($arg);
+ }
+
+ /**
+ * Override the behavior of query() to prevent the query
+ * from being changed in any way.
+ */
+ function query($group_by = FALSE) {}
+}
diff --git a/sites/all/modules/views/handlers/views_handler_argument_numeric.inc b/sites/all/modules/views/handlers/views_handler_argument_numeric.inc
new file mode 100644
index 000000000..8f36b212f
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_argument_numeric.inc
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_numeric.
+ */
+
+/**
+ * Basic argument handler for arguments that are numeric. Incorporates
+ * break_phrase.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_numeric extends views_handler_argument {
+ /**
+ * The operator used for the query: or|and.
+ * @var string
+ */
+ var $operator;
+
+ /**
+ * The actual value which is used for querying.
+ * @var array
+ */
+ var $value;
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['break_phrase'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['not'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ // allow + for or, , for and
+ $form['break_phrase'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple values'),
+ '#description' => t('If selected, users can enter multiple values in the form of 1+2+3 (for OR) or 1,2,3 (for AND).'),
+ '#default_value' => !empty($this->options['break_phrase']),
+ '#fieldset' => 'more',
+ );
+
+ $form['not'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Exclude'),
+ '#description' => t('If selected, the numbers entered for the filter will be excluded rather than limiting the view.'),
+ '#default_value' => !empty($this->options['not']),
+ '#fieldset' => 'more',
+ );
+ }
+
+ function title() {
+ if (!$this->argument) {
+ return !empty($this->definition['empty field name']) ? $this->definition['empty field name'] : t('Uncategorized');
+ }
+
+ if (!empty($this->options['break_phrase'])) {
+ views_break_phrase($this->argument, $this);
+ }
+ else {
+ $this->value = array($this->argument);
+ $this->operator = 'or';
+ }
+
+ if (empty($this->value)) {
+ return !empty($this->definition['empty field name']) ? $this->definition['empty field name'] : t('Uncategorized');
+ }
+
+ if ($this->value === array(-1)) {
+ return !empty($this->definition['invalid input']) ? $this->definition['invalid input'] : t('Invalid input');
+ }
+
+ return implode($this->operator == 'or' ? ' + ' : ', ', $this->title_query());
+ }
+
+ /**
+ * Override for specific title lookups.
+ * @return array
+ * Returns all titles, if it's just one title it's an array with one entry.
+ */
+ function title_query() {
+ return $this->value;
+ }
+
+ function query($group_by = FALSE) {
+ $this->ensure_my_table();
+
+ if (!empty($this->options['break_phrase'])) {
+ views_break_phrase($this->argument, $this);
+ }
+ else {
+ $this->value = array($this->argument);
+ }
+
+ $placeholder = $this->placeholder();
+ $null_check = empty($this->options['not']) ? '' : "OR $this->table_alias.$this->real_field IS NULL";
+
+ if (count($this->value) > 1) {
+ $operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
+ $this->query->add_where_expression(0, "$this->table_alias.$this->real_field $operator($placeholder) $null_check", array($placeholder => $this->value));
+ }
+ else {
+ $operator = empty($this->options['not']) ? '=' : '!=';
+ $this->query->add_where_expression(0, "$this->table_alias.$this->real_field $operator $placeholder $null_check", array($placeholder => $this->argument));
+ }
+ }
+
+ function get_sort_name() {
+ return t('Numerical', array(), array('context' => 'Sort order'));
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_argument_string.inc b/sites/all/modules/views/handlers/views_handler_argument_string.inc
new file mode 100644
index 000000000..dbb98fef4
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_argument_string.inc
@@ -0,0 +1,274 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_string.
+ */
+
+/**
+ * Basic argument handler to implement string arguments that may have length
+ * limits.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_string extends views_handler_argument {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ if (!empty($this->definition['many to one'])) {
+ $this->helper = new views_many_to_one_helper($this);
+
+ // Ensure defaults for these, during summaries and stuff:
+ $this->operator = 'or';
+ $this->value = array();
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['glossary'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['limit'] = array('default' => 0);
+ $options['case'] = array('default' => 'none');
+ $options['path_case'] = array('default' => 'none');
+ $options['transform_dash'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['break_phrase'] = array('default' => FALSE, 'bool' => TRUE);
+
+ if (!empty($this->definition['many to one'])) {
+ $options['add_table'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['require_value'] = array('default' => FALSE, 'bool' => TRUE);
+ }
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['glossary'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Glossary mode'),
+ '#description' => t('Glossary mode applies a limit to the number of characters used in the filter value, which allows the summary view to act as a glossary.'),
+ '#default_value' => $this->options['glossary'],
+ '#fieldset' => 'more',
+ );
+
+ $form['limit'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Character limit'),
+ '#description' => t('How many characters of the filter value to filter against. If set to 1, all fields starting with the first letter in the filter value would be matched.'),
+ '#default_value' => $this->options['limit'],
+ '#dependency' => array('edit-options-glossary' => array(TRUE)),
+ '#fieldset' => 'more',
+ );
+
+ $form['case'] = array(
+ '#type' => 'select',
+ '#title' => t('Case'),
+ '#description' => t('When printing the title and summary, how to transform the case of the filter value.'),
+ '#options' => array(
+ 'none' => t('No transform'),
+ 'upper' => t('Upper case'),
+ 'lower' => t('Lower case'),
+ 'ucfirst' => t('Capitalize first letter'),
+ 'ucwords' => t('Capitalize each word'),
+ ),
+ '#default_value' => $this->options['case'],
+ '#fieldset' => 'more',
+ );
+
+ $form['path_case'] = array(
+ '#type' => 'select',
+ '#title' => t('Case in path'),
+ '#description' => t('When printing url paths, how to transform the case of the filter value. Do not use this unless with Postgres as it uses case sensitive comparisons.'),
+ '#options' => array(
+ 'none' => t('No transform'),
+ 'upper' => t('Upper case'),
+ 'lower' => t('Lower case'),
+ 'ucfirst' => t('Capitalize first letter'),
+ 'ucwords' => t('Capitalize each word'),
+ ),
+ '#default_value' => $this->options['path_case'],
+ '#fieldset' => 'more',
+ );
+
+ $form['transform_dash'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Transform spaces to dashes in URL'),
+ '#default_value' => $this->options['transform_dash'],
+ '#fieldset' => 'more',
+ );
+
+ if (!empty($this->definition['many to one'])) {
+ $form['add_table'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple filter values to work together'),
+ '#description' => t('If selected, multiple instances of this filter can work together, as though multiple values were supplied to the same filter. This setting is not compatible with the "Reduce duplicates" setting.'),
+ '#default_value' => !empty($this->options['add_table']),
+ '#fieldset' => 'more',
+ );
+
+ $form['require_value'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Do not display items with no value in summary'),
+ '#default_value' => !empty($this->options['require_value']),
+ '#fieldset' => 'more',
+ );
+ }
+
+ // allow + for or, , for and
+ $form['break_phrase'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple values'),
+ '#description' => t('If selected, users can enter multiple values in the form of 1+2+3 (for OR) or 1,2,3 (for AND).'),
+ '#default_value' => !empty($this->options['break_phrase']),
+ '#fieldset' => 'more',
+ );
+ }
+
+ /**
+ * Build the summary query based on a string
+ */
+ function summary_query() {
+ if (empty($this->definition['many to one'])) {
+ $this->ensure_my_table();
+ }
+ else {
+ $this->table_alias = $this->helper->summary_join();
+ }
+
+ if (empty($this->options['glossary'])) {
+ // Add the field.
+ $this->base_alias = $this->query->add_field($this->table_alias, $this->real_field);
+ $this->query->set_count_field($this->table_alias, $this->real_field);
+ }
+ else {
+ // Add the field.
+ $formula = $this->get_formula();
+ $this->base_alias = $this->query->add_field(NULL, $formula, $this->field . '_truncated');
+ $this->query->set_count_field(NULL, $formula, $this->field, $this->field . '_truncated');
+ }
+
+ $this->summary_name_field();
+ return $this->summary_basics(FALSE);
+ }
+
+ /**
+ * Get the formula for this argument.
+ *
+ * $this->ensure_my_table() MUST have been called prior to this.
+ */
+ function get_formula() {
+ return "SUBSTRING($this->table_alias.$this->real_field, 1, " . intval($this->options['limit']) . ")";
+ }
+
+ /**
+ * Build the query based upon the formula
+ */
+ function query($group_by = FALSE) {
+ $argument = $this->argument;
+ if (!empty($this->options['transform_dash'])) {
+ $argument = strtr($argument, '-', ' ');
+ }
+
+ if (!empty($this->options['break_phrase'])) {
+ views_break_phrase_string($argument, $this);
+ }
+ else {
+ $this->value = array($argument);
+ $this->operator = 'or';
+ }
+
+ if (!empty($this->definition['many to one'])) {
+ if (!empty($this->options['glossary'])) {
+ $this->helper->formula = TRUE;
+ }
+ $this->helper->ensure_my_table();
+ $this->helper->add_filter();
+ return;
+ }
+
+ $this->ensure_my_table();
+ $formula = FALSE;
+ if (empty($this->options['glossary'])) {
+ $field = "$this->table_alias.$this->real_field";
+ }
+ else {
+ $formula = TRUE;
+ $field = $this->get_formula();
+ }
+
+ if (count($this->value) > 1) {
+ $operator = 'IN';
+ $argument = $this->value;
+ }
+ else {
+ $operator = '=';
+ }
+
+ if ($formula) {
+ $placeholder = $this->placeholder();
+ if ($operator == 'IN') {
+ $field .= " IN($placeholder)";
+ }
+ else {
+ $field .= ' = ' . $placeholder;
+ }
+ $placeholders = array(
+ $placeholder => $argument,
+ );
+ $this->query->add_where_expression(0, $field, $placeholders);
+ }
+ else {
+ $this->query->add_where(0, $field, $argument, $operator);
+ }
+ }
+
+ function summary_argument($data) {
+ $value = $this->case_transform($data->{$this->base_alias}, $this->options['path_case']);
+ if (!empty($this->options['transform_dash'])) {
+ $value = strtr($value, ' ', '-');
+ }
+ return $value;
+ }
+
+ function get_sort_name() {
+ return t('Alphabetical', array(), array('context' => 'Sort order'));
+ }
+
+ function title() {
+ $this->argument = $this->case_transform($this->argument, $this->options['case']);
+ if (!empty($this->options['transform_dash'])) {
+ $this->argument = strtr($this->argument, '-', ' ');
+ }
+
+ if (!empty($this->options['break_phrase'])) {
+ views_break_phrase_string($this->argument, $this);
+ }
+ else {
+ $this->value = array($this->argument);
+ $this->operator = 'or';
+ }
+
+ if (empty($this->value)) {
+ return !empty($this->definition['empty field name']) ? $this->definition['empty field name'] : t('Uncategorized');
+ }
+
+ if ($this->value === array(-1)) {
+ return !empty($this->definition['invalid input']) ? $this->definition['invalid input'] : t('Invalid input');
+ }
+
+ return implode($this->operator == 'or' ? ' + ' : ', ', $this->title_query());
+ }
+
+ /**
+ * Override for specific title lookups.
+ */
+ function title_query() {
+ return drupal_map_assoc($this->value, 'check_plain');
+ }
+
+ function summary_name($data) {
+ return $this->case_transform(parent::summary_name($data), $this->options['case']);
+ }
+
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field.inc b/sites/all/modules/views/handlers/views_handler_field.inc
new file mode 100644
index 000000000..65210d9f6
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field.inc
@@ -0,0 +1,1630 @@
+<?php
+
+/**
+ * @file
+ * @todo.
+ */
+
+/**
+ * @defgroup views_field_handlers Views field handlers
+ * @{
+ * Handlers to tell Views how to build and display fields.
+ *
+ */
+
+/**
+ * Indicator of the render_text() method for rendering a single item.
+ * (If no render_item() is present).
+ */
+define('VIEWS_HANDLER_RENDER_TEXT_PHASE_SINGLE_ITEM', 0);
+
+/**
+ * Indicator of the render_text() method for rendering the whole element.
+ * (if no render_item() method is available).
+ */
+define('VIEWS_HANDLER_RENDER_TEXT_PHASE_COMPLETELY', 1);
+
+/**
+ * Indicator of the render_text() method for rendering the empty text.
+ */
+define('VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY', 2);
+
+/**
+ * Base field handler that has no options and renders an unformatted field.
+ *
+ * Definition terms:
+ * - additional fields: An array of fields that should be added to the query
+ * for some purpose. The array is in the form of:
+ * array('identifier' => array('table' => tablename,
+ * 'field' => fieldname); as many fields as are necessary
+ * may be in this array.
+ * - click sortable: If TRUE, this field may be click sorted.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field extends views_handler {
+ var $field_alias = 'unknown';
+ var $aliases = array();
+
+ /**
+ * The field value prior to any rewriting.
+ *
+ * @var mixed
+ */
+ public $original_value = NULL;
+
+ /**
+ * @var array
+ * Stores additional fields which get's added to the query.
+ * The generated aliases are stored in $aliases.
+ */
+ var $additional_fields = array();
+
+ /**
+ * Construct a new field handler.
+ */
+ function construct() {
+ parent::construct();
+
+ $this->additional_fields = array();
+ if (!empty($this->definition['additional fields'])) {
+ $this->additional_fields = $this->definition['additional fields'];
+ }
+
+ if (!isset($this->options['exclude'])) {
+ $this->options['exclude'] = '';
+ }
+ }
+
+ /**
+ * Determine if this field can allow advanced rendering.
+ *
+ * Fields can set this to FALSE if they do not wish to allow
+ * token based rewriting or link-making.
+ */
+ function allow_advanced_render() {
+ return TRUE;
+ }
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ }
+
+ /**
+ * Called to add the field to a query.
+ */
+ function query() {
+ $this->ensure_my_table();
+ // Add the field.
+ $params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array();
+ $this->field_alias = $this->query->add_field($this->table_alias, $this->real_field, NULL, $params);
+
+ $this->add_additional_fields();
+ }
+
+ /**
+ * Add 'additional' fields to the query.
+ *
+ * @param $fields
+ * An array of fields. The key is an identifier used to later find the
+ * field alias used. The value is either a string in which case it's
+ * assumed to be a field on this handler's table; or it's an array in the
+ * form of
+ * @code array('table' => $tablename, 'field' => $fieldname) @endcode
+ */
+ function add_additional_fields($fields = NULL) {
+ if (!isset($fields)) {
+ // notice check
+ if (empty($this->additional_fields)) {
+ return;
+ }
+ $fields = $this->additional_fields;
+ }
+
+ $group_params = array();
+ if ($this->options['group_type'] != 'group') {
+ $group_params = array(
+ 'function' => $this->options['group_type'],
+ );
+ }
+
+ if (!empty($fields) && is_array($fields)) {
+ foreach ($fields as $identifier => $info) {
+ if (is_array($info)) {
+ if (isset($info['table'])) {
+ $table_alias = $this->query->ensure_table($info['table'], $this->relationship);
+ }
+ else {
+ $table_alias = $this->table_alias;
+ }
+
+ if (empty($table_alias)) {
+ debug(t('Handler @handler tried to add additional_field @identifier but @table could not be added!', array('@handler' => $this->definition['handler'], '@identifier' => $identifier, '@table' => $info['table'])));
+ $this->aliases[$identifier] = 'broken';
+ continue;
+ }
+
+ $params = array();
+ if (!empty($info['params'])) {
+ $params = $info['params'];
+ }
+
+ $params += $group_params;
+ $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field'], NULL, $params);
+ }
+ else {
+ $this->aliases[$info] = $this->query->add_field($this->table_alias, $info, NULL, $group_params);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called to determine what to tell the clicksorter.
+ */
+ function click_sort($order) {
+ if (isset($this->field_alias)) {
+ // Since fields should always have themselves already added, just
+ // add a sort on the field.
+ $params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array();
+ $this->query->add_orderby(NULL, NULL, $order, $this->field_alias, $params);
+ }
+ }
+
+ /**
+ * Determine if this field is click sortable.
+ */
+ function click_sortable() {
+ return !empty($this->definition['click sortable']);
+ }
+
+ /**
+ * Get this field's label.
+ */
+ function label() {
+ if (!isset($this->options['label'])) {
+ return '';
+ }
+ return $this->options['label'];
+ }
+
+ /**
+ * Return an HTML element based upon the field's element type.
+ */
+ function element_type($none_supported = FALSE, $default_empty = FALSE, $inline = FALSE) {
+ if ($none_supported) {
+ if ($this->options['element_type'] === '0') {
+ return '';
+ }
+ }
+ if ($this->options['element_type']) {
+ return check_plain($this->options['element_type']);
+ }
+
+ if ($default_empty) {
+ return '';
+ }
+
+ if ($inline) {
+ return 'span';
+ }
+
+ if (isset($this->definition['element type'])) {
+ return $this->definition['element type'];
+ }
+
+ return 'span';
+ }
+
+ /**
+ * Return an HTML element for the label based upon the field's element type.
+ */
+ function element_label_type($none_supported = FALSE, $default_empty = FALSE) {
+ if ($none_supported) {
+ if ($this->options['element_label_type'] === '0') {
+ return '';
+ }
+ }
+ if ($this->options['element_label_type']) {
+ return check_plain($this->options['element_label_type']);
+ }
+
+ if ($default_empty) {
+ return '';
+ }
+
+ return 'span';
+ }
+
+ /**
+ * Return an HTML element for the wrapper based upon the field's element type.
+ */
+ function element_wrapper_type($none_supported = FALSE, $default_empty = FALSE) {
+ if ($none_supported) {
+ if ($this->options['element_wrapper_type'] === '0') {
+ return 0;
+ }
+ }
+ if ($this->options['element_wrapper_type']) {
+ return check_plain($this->options['element_wrapper_type']);
+ }
+
+ if ($default_empty) {
+ return '';
+ }
+
+ return 'div';
+ }
+
+ /**
+ * Provide a list of elements valid for field HTML.
+ *
+ * This function can be overridden by fields that want more or fewer
+ * elements available, though this seems like it would be an incredibly
+ * rare occurence.
+ */
+ function get_elements() {
+ static $elements = NULL;
+ if (!isset($elements)) {
+ $elements = variable_get('views_field_rewrite_elements', array(
+ '' => t('- Use default -'),
+ '0' => t('- None -'),
+ 'div' => 'DIV',
+ 'span' => 'SPAN',
+ 'h1' => 'H1',
+ 'h2' => 'H2',
+ 'h3' => 'H3',
+ 'h4' => 'H4',
+ 'h5' => 'H5',
+ 'h6' => 'H6',
+ 'p' => 'P',
+ 'strong' => 'STRONG',
+ 'em' => 'EM',
+ ));
+ }
+
+ return $elements;
+ }
+
+ /**
+ * Return the class of the field.
+ */
+ function element_classes($row_index = NULL) {
+ $classes = explode(' ', $this->options['element_class']);
+ foreach ($classes as &$class) {
+ $class = $this->tokenize_value($class, $row_index);
+ $class = views_clean_css_identifier($class);
+ }
+ return implode(' ', $classes);
+ }
+
+ /**
+ * Replace a value with tokens from the last field.
+ *
+ * This function actually figures out which field was last and uses its
+ * tokens so they will all be available.
+ */
+ function tokenize_value($value, $row_index = NULL) {
+ if (strpos($value, '[') !== FALSE || strpos($value, '!') !== FALSE || strpos($value, '%') !== FALSE) {
+ $fake_item = array(
+ 'alter_text' => TRUE,
+ 'text' => $value,
+ );
+
+ // Use isset() because empty() will trigger on 0 and 0 is
+ // the first row.
+ if (isset($row_index) && isset($this->view->style_plugin->render_tokens[$row_index])) {
+ $tokens = $this->view->style_plugin->render_tokens[$row_index];
+ }
+ else {
+ // Get tokens from the last field.
+ $last_field = end($this->view->field);
+ if (isset($last_field->last_tokens)) {
+ $tokens = $last_field->last_tokens;
+ }
+ else {
+ $tokens = $last_field->get_render_tokens($fake_item);
+ }
+ }
+
+ $value = strip_tags($this->render_altered($fake_item, $tokens));
+ if (!empty($this->options['alter']['trim_whitespace'])) {
+ $value = trim($value);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Return the class of the field's label.
+ */
+ function element_label_classes($row_index = NULL) {
+ $classes = explode(' ', $this->options['element_label_class']);
+ foreach ($classes as &$class) {
+ $class = $this->tokenize_value($class, $row_index);
+ $class = views_clean_css_identifier($class);
+ }
+ return implode(' ', $classes);
+ }
+
+ /**
+ * Return the class of the field's wrapper.
+ */
+ function element_wrapper_classes($row_index = NULL) {
+ $classes = explode(' ', $this->options['element_wrapper_class']);
+ foreach ($classes as &$class) {
+ $class = $this->tokenize_value($class, $row_index);
+ $class = views_clean_css_identifier($class);
+ }
+ return implode(' ', $classes);
+ }
+
+ /**
+ * Get the value that's supposed to be rendered.
+ *
+ * This api exists so that other modules can easy set the values of the field
+ * without having the need to change the render method as well.
+ *
+ * @param $values
+ * An object containing all retrieved values.
+ * @param $field
+ * Optional name of the field where the value is stored.
+ */
+ function get_value($values, $field = NULL) {
+ $alias = isset($field) ? $this->aliases[$field] : $this->field_alias;
+ if (isset($values->{$alias})) {
+ return $values->{$alias};
+ }
+ }
+
+ /**
+ * Determines if this field will be available as an option to group the result
+ * by in the style settings.
+ *
+ * @return bool
+ * TRUE if this field handler is groupable, otherwise FALSE.
+ */
+ function use_string_group_by() {
+ return TRUE;
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['label'] = array('default' => $this->definition['title'], 'translatable' => TRUE);
+ $options['exclude'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['alter'] = array(
+ 'contains' => array(
+ 'alter_text' => array('default' => FALSE, 'bool' => TRUE),
+ 'text' => array('default' => '', 'translatable' => TRUE),
+ 'make_link' => array('default' => FALSE, 'bool' => TRUE),
+ 'path' => array('default' => ''),
+ 'absolute' => array('default' => FALSE, 'bool' => TRUE),
+ 'external' => array('default' => FALSE, 'bool' => TRUE),
+ 'replace_spaces' => array('default' => FALSE, 'bool' => TRUE),
+ 'path_case' => array('default' => 'none', 'translatable' => FALSE),
+ 'trim_whitespace' => array('default' => FALSE, 'bool' => TRUE),
+ 'alt' => array('default' => '', 'translatable' => TRUE),
+ 'rel' => array('default' => ''),
+ 'link_class' => array('default' => ''),
+ 'prefix' => array('default' => '', 'translatable' => TRUE),
+ 'suffix' => array('default' => '', 'translatable' => TRUE),
+ 'target' => array('default' => ''),
+ 'nl2br' => array('default' => FALSE, 'bool' => TRUE),
+ 'max_length' => array('default' => ''),
+ 'word_boundary' => array('default' => TRUE, 'bool' => TRUE),
+ 'ellipsis' => array('default' => TRUE, 'bool' => TRUE),
+ 'more_link' => array('default' => FALSE, 'bool' => TRUE),
+ 'more_link_text' => array('default' => '', 'translatable' => TRUE),
+ 'more_link_path' => array('default' => ''),
+ 'strip_tags' => array('default' => FALSE, 'bool' => TRUE),
+ 'trim' => array('default' => FALSE, 'bool' => TRUE),
+ 'preserve_tags' => array('default' => ''),
+ 'html' => array('default' => FALSE, 'bool' => TRUE),
+ ),
+ );
+ $options['element_type'] = array('default' => '');
+ $options['element_class'] = array('default' => '');
+
+ $options['element_label_type'] = array('default' => '');
+ $options['element_label_class'] = array('default' => '');
+ $options['element_label_colon'] = array('default' => TRUE, 'bool' => TRUE);
+
+ $options['element_wrapper_type'] = array('default' => '');
+ $options['element_wrapper_class'] = array('default' => '');
+
+ $options['element_default_classes'] = array('default' => TRUE, 'bool' => TRUE);
+
+ $options['empty'] = array('default' => '', 'translatable' => TRUE);
+ $options['hide_empty'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['empty_zero'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['hide_alter_empty'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Performs some cleanup tasks on the options array before saving it.
+ */
+ function options_submit(&$form, &$form_state) {
+ $options = &$form_state['values']['options'];
+ $types = array('element_type', 'element_label_type', 'element_wrapper_type');
+ $classes = array_combine(array('element_class', 'element_label_class', 'element_wrapper_class'), $types);
+
+ foreach ($types as $type) {
+ if (!$options[$type . '_enable']) {
+ $options[$type] = '';
+ }
+ }
+
+ foreach ($classes as $class => $type) {
+ if (!$options[$class . '_enable'] || !$options[$type . '_enable']) {
+ $options[$class] = '';
+ }
+ }
+
+ if (empty($options['custom_label'])) {
+ $options['label'] = '';
+ $options['element_label_colon'] = FALSE;
+ }
+ }
+
+ /**
+ * Default options form that provides the label widget that all fields
+ * should have.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $label = $this->label();
+ $form['custom_label'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create a label'),
+ '#description' => t('Enable to create a label for this field.'),
+ '#default_value' => $label !== '',
+ '#weight' => -103,
+ );
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#default_value' => $label,
+ '#dependency' => array(
+ 'edit-options-custom-label' => array(1),
+ ),
+ '#weight' => -102,
+ );
+ $form['element_label_colon'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Place a colon after the label'),
+ '#default_value' => $this->options['element_label_colon'],
+ '#dependency' => array(
+ 'edit-options-custom-label' => array(1),
+ ),
+ '#weight' => -101,
+ );
+
+ $form['exclude'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Exclude from display'),
+ '#default_value' => $this->options['exclude'],
+ '#description' => t('Enable to load this field as hidden. Often used to group fields, or to use as token in another field.'),
+ '#weight' => -100,
+ );
+
+ $form['style_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Style settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#weight' => 99,
+ );
+
+ $form['element_type_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Customize field HTML'),
+ '#default_value' => !empty($this->options['element_type']) || (string) $this->options['element_type'] == '0' || !empty($this->options['element_class']) || (string) $this->options['element_class'] == '0',
+ '#fieldset' => 'style_settings',
+ );
+ $form['element_type'] = array(
+ '#title' => t('HTML element'),
+ '#options' => $this->get_elements(),
+ '#type' => 'select',
+ '#default_value' => $this->options['element_type'],
+ '#description' => t('Choose the HTML element to wrap around this field, e.g. H1, H2, etc.'),
+ '#dependency' => array(
+ 'edit-options-element-type-enable' => array(1),
+ ),
+ '#fieldset' => 'style_settings',
+ );
+
+ $form['element_class_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create a CSS class'),
+ '#dependency' => array(
+ 'edit-options-element-type-enable' => array(1),
+ ),
+ '#default_value' => !empty($this->options['element_class']) || (string) $this->options['element_class'] == '0',
+ '#fieldset' => 'style_settings',
+ );
+ $form['element_class'] = array(
+ '#title' => t('CSS class'),
+ '#description' => t('You may use token substitutions from the rewriting section in this class.'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['element_class'],
+ '#dependency' => array(
+ 'edit-options-element-class-enable' => array(1),
+ 'edit-options-element-type-enable' => array(1),
+ ),
+ '#dependency_count' => 2,
+ '#fieldset' => 'style_settings',
+ );
+
+ $form['element_label_type_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Customize label HTML'),
+ '#default_value' => !empty($this->options['element_label_type']) || (string) $this->options['element_label_type'] == '0' || !empty($this->options['element_label_class']) || (string) $this->options['element_label_class'] == '0',
+ '#fieldset' => 'style_settings',
+ );
+ $form['element_label_type'] = array(
+ '#title' => t('Label HTML element'),
+ '#options' => $this->get_elements(FALSE),
+ '#type' => 'select',
+ '#default_value' => $this->options['element_label_type'],
+ '#description' => t('Choose the HTML element to wrap around this label, e.g. H1, H2, etc.'),
+ '#dependency' => array(
+ 'edit-options-element-label-type-enable' => array(1),
+ ),
+ '#fieldset' => 'style_settings',
+ );
+ $form['element_label_class_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create a CSS class'),
+ '#dependency' => array(
+ 'edit-options-element-label-type-enable' => array(1)
+ ),
+ '#default_value' => !empty($this->options['element_label_class']) || (string) $this->options['element_label_class'] == '0',
+ '#fieldset' => 'style_settings',
+ );
+ $form['element_label_class'] = array(
+ '#title' => t('CSS class'),
+ '#description' => t('You may use token substitutions from the rewriting section in this class.'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['element_label_class'],
+ '#dependency' => array(
+ 'edit-options-element-label-class-enable' => array(1),
+ 'edit-options-element-label-type-enable' => array(1),
+ ),
+ '#dependency_count' => 2,
+ '#fieldset' => 'style_settings',
+ );
+
+ $form['element_wrapper_type_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Customize field and label wrapper HTML'),
+ '#default_value' => !empty($this->options['element_wrapper_type']) || (string) $this->options['element_wrapper_type'] == '0' || !empty($this->options['element_wrapper_class']) || (string) $this->options['element_wrapper_class'] == '0',
+ '#fieldset' => 'style_settings',
+ );
+ $form['element_wrapper_type'] = array(
+ '#title' => t('Wrapper HTML element'),
+ '#options' => $this->get_elements(FALSE),
+ '#type' => 'select',
+ '#default_value' => $this->options['element_wrapper_type'],
+ '#description' => t('Choose the HTML element to wrap around this field and label, e.g. H1, H2, etc. This may not be used if the field and label are not rendered together, such as with a table.'),
+ '#dependency' => array(
+ 'edit-options-element-wrapper-type-enable' => array(1),
+ ),
+ '#fieldset' => 'style_settings',
+ );
+
+ $form['element_wrapper_class_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create a CSS class'),
+ '#dependency' => array(
+ 'edit-options-element-wrapper-type-enable' => array(1),
+ ),
+ '#default_value' => !empty($this->options['element_wrapper_class']) || (string) $this->options['element_wrapper_class'] == '0',
+ '#fieldset' => 'style_settings',
+ );
+ $form['element_wrapper_class'] = array(
+ '#title' => t('CSS class'),
+ '#description' => t('You may use token substitutions from the rewriting section in this class.'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['element_wrapper_class'],
+ '#dependency' => array(
+ 'edit-options-element-wrapper-class-enable' => array(1),
+ 'edit-options-element-wrapper-type-enable' => array(1),
+ ),
+ '#dependency_count' => 2,
+ '#fieldset' => 'style_settings',
+ );
+
+ $form['element_default_classes'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add default classes'),
+ '#default_value' => $this->options['element_default_classes'],
+ '#description' => t('Use default Views classes to identify the field, field label and field content.'),
+ '#fieldset' => 'style_settings',
+ );
+
+ $form['alter'] = array(
+ '#title' => t('Rewrite results'),
+ '#type' => 'fieldset',
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#weight' => 100,
+ );
+
+ if ($this->allow_advanced_render()) {
+ $form['alter']['#tree'] = TRUE;
+ $form['alter']['alter_text'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Rewrite the output of this field'),
+ '#description' => t('Enable to override the output of this field with custom text or replacement tokens.'),
+ '#default_value' => $this->options['alter']['alter_text'],
+ );
+
+ $form['alter']['text'] = array(
+ '#title' => t('Text'),
+ '#type' => 'textarea',
+ '#default_value' => $this->options['alter']['text'],
+ '#description' => t('The text to display for this field. You may include HTML. You may enter data from this view as per the "Replacement patterns" below.'),
+ '#dependency' => array(
+ 'edit-options-alter-alter-text' => array(1),
+ ),
+ );
+
+ $form['alter']['make_link'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Output this field as a link'),
+ '#description' => t('If checked, this field will be made into a link. The destination must be given below.'),
+ '#default_value' => $this->options['alter']['make_link'],
+ );
+ $form['alter']['path'] = array(
+ '#title' => t('Link path'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['alter']['path'],
+ '#description' => t('The Drupal path or absolute URL for this link. You may enter data from this view as per the "Replacement patterns" below.'),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ '#maxlength' => 255,
+ );
+ $form['alter']['absolute'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use absolute path'),
+ '#default_value' => $this->options['alter']['absolute'],
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ );
+ $form['alter']['replace_spaces'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Replace spaces with dashes'),
+ '#default_value' => $this->options['alter']['replace_spaces'],
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1)
+ ),
+ );
+ $form['alter']['external'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('External server URL'),
+ '#default_value' => $this->options['alter']['external'],
+ '#description' => t("Links to an external server using a full URL: e.g. 'http://www.example.com' or 'www.example.com'."),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ );
+ $form['alter']['path_case'] = array(
+ '#type' => 'select',
+ '#title' => t('Transform the case'),
+ '#description' => t('When printing url paths, how to transform the case of the filter value.'),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ '#options' => array(
+ 'none' => t('No transform'),
+ 'upper' => t('Upper case'),
+ 'lower' => t('Lower case'),
+ 'ucfirst' => t('Capitalize first letter'),
+ 'ucwords' => t('Capitalize each word'),
+ ),
+ '#default_value' => $this->options['alter']['path_case'],
+ );
+ $form['alter']['link_class'] = array(
+ '#title' => t('Link class'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['alter']['link_class'],
+ '#description' => t('The CSS class to apply to the link.'),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ );
+ $form['alter']['alt'] = array(
+ '#title' => t('Title text'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['alter']['alt'],
+ '#description' => t('Text to place as "title" text which most browsers display as a tooltip when hovering over the link.'),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ );
+ $form['alter']['rel'] = array(
+ '#title' => t('Rel Text'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['alter']['rel'],
+ '#description' => t('Include Rel attribute for use in lightbox2 or other javascript utility.'),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ );
+ $form['alter']['prefix'] = array(
+ '#title' => t('Prefix text'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['alter']['prefix'],
+ '#description' => t('Any text to display before this link. You may include HTML.'),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ );
+ $form['alter']['suffix'] = array(
+ '#title' => t('Suffix text'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['alter']['suffix'],
+ '#description' => t('Any text to display after this link. You may include HTML.'),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ );
+ $form['alter']['target'] = array(
+ '#title' => t('Target'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['alter']['target'],
+ '#description' => t("Target of the link, such as _blank, _parent or an iframe's name. This field is rarely used."),
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ ),
+ );
+
+
+ // Get a list of the available fields and arguments for token replacement.
+ $options = array();
+ foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
+ $options[t('Fields')]["[$field]"] = $handler->ui_name();
+ // We only use fields up to (and including) this one.
+ if ($field == $this->options['id']) {
+ break;
+ }
+ }
+ $count = 0; // This lets us prepare the key as we want it printed.
+ foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
+ $options[t('Arguments')]['%' . ++$count] = t('@argument title', array('@argument' => $handler->ui_name()));
+ $options[t('Arguments')]['!' . $count] = t('@argument input', array('@argument' => $handler->ui_name()));
+ }
+
+ $this->document_self_tokens($options[t('Fields')]);
+
+ // Default text.
+ $output = t('<p>You must add some additional fields to this display before using this field. These fields may be marked as <em>Exclude from display</em> if you prefer. Note that due to rendering order, you cannot use fields that come after this field; if you need a field not listed here, rearrange your fields.</p>');
+ // We have some options, so make a list.
+ if (!empty($options)) {
+ $output = t('<p>The following tokens are available for this field. Note that due to rendering order, you cannot use fields that come after this field; if you need a field not listed here, rearrange your fields.
+If you would like to have the characters \'[\' and \']\' please use the html entity codes \'%5B\' or \'%5D\' or they will get replaced with empty space.</p>');
+ foreach (array_keys($options) as $type) {
+ if (!empty($options[$type])) {
+ $items = array();
+ foreach ($options[$type] as $key => $value) {
+ $items[] = $key . ' == ' . check_plain($value);
+ }
+ $output .= theme('item_list',
+ array(
+ 'items' => $items,
+ 'type' => $type
+ ));
+ }
+ }
+ }
+ // This construct uses 'hidden' and not markup because process doesn't
+ // run. It also has an extra div because the dependency wants to hide
+ // the parent in situations like this, so we need a second div to
+ // make this work.
+ $form['alter']['help'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Replacement patterns'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#value' => $output,
+ '#dependency' => array(
+ 'edit-options-alter-make-link' => array(1),
+ 'edit-options-alter-alter-text' => array(1),
+ 'edit-options-alter-more-link' => array(1),
+ ),
+ );
+
+ $form['alter']['trim'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Trim this field to a maximum length'),
+ '#description' => t('Enable to trim the field to a maximum length of characters'),
+ '#default_value' => $this->options['alter']['trim'],
+ );
+
+ $form['alter']['max_length'] = array(
+ '#title' => t('Maximum length'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['alter']['max_length'],
+ '#description' => t('The maximum number of characters this field can be.'),
+ '#dependency' => array(
+ 'edit-options-alter-trim' => array(1),
+ ),
+ );
+
+ $form['alter']['word_boundary'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Trim only on a word boundary'),
+ '#description' => t('If checked, this field be trimmed only on a word boundary. This is guaranteed to be the maximum characters stated or less. If there are no word boundaries this could trim a field to nothing.'),
+ '#default_value' => $this->options['alter']['word_boundary'],
+ '#dependency' => array(
+ 'edit-options-alter-trim' => array(1),
+ ),
+ );
+
+ $form['alter']['ellipsis'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add an ellipsis'),
+ '#description' => t('If checked, a "..." will be added if a field was trimmed.'),
+ '#default_value' => $this->options['alter']['ellipsis'],
+ '#dependency' => array(
+ 'edit-options-alter-trim' => array(1),
+ ),
+ );
+
+ $form['alter']['more_link'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add a read-more link if output is trimmed.'),
+ '#description' => t('If checked, a read-more link will be added at the end of the trimmed output'),
+ '#default_value' => $this->options['alter']['more_link'],
+ '#dependency' => array(
+ 'edit-options-alter-trim' => array(1),
+ ),
+ );
+
+ $form['alter']['more_link_text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('More link text'),
+ '#default_value' => $this->options['alter']['more_link_text'],
+ '#description' => t('The text which will be displayed on the more link. You may enter data from this view as per the "Replacement patterns" above.'),
+ '#dependency_count' => 2,
+ '#dependency' => array(
+ 'edit-options-alter-trim' => array(1),
+ 'edit-options-alter-more-link' => array(1),
+ ),
+ );
+ $form['alter']['more_link_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('More link path'),
+ '#default_value' => $this->options['alter']['more_link_path'],
+ '#description' => t('The path which is used for the more link. You may enter data from this view as per the "Replacement patterns" above.'),
+ '#dependency_count' => 2,
+ '#dependency' => array(
+ 'edit-options-alter-trim' => array(1),
+ 'edit-options-alter-more-link' => array(1),
+ ),
+ );
+
+ $form['alter']['html'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Field can contain HTML'),
+ '#description' => t('If checked, HTML corrector will be run to ensure tags are properly closed after trimming.'),
+ '#default_value' => $this->options['alter']['html'],
+ '#dependency' => array(
+ 'edit-options-alter-trim' => array(1),
+ ),
+ );
+
+ $form['alter']['strip_tags'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Strip HTML tags'),
+ '#description' => t('If checked, all HTML tags will be stripped.'),
+ '#default_value' => $this->options['alter']['strip_tags'],
+ );
+
+ $form['alter']['preserve_tags'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Preserve certain tags'),
+ '#description' => t('List the tags that need to be preserved during the stripping process. example &quot;&lt;p&gt; &lt;br&gt;&quot; which will preserve all p and br elements'),
+ '#default_value' => $this->options['alter']['preserve_tags'],
+ '#dependency' => array(
+ 'edit-options-alter-strip-tags' => array(1),
+ ),
+ );
+
+ $form['alter']['trim_whitespace'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remove whitespace'),
+ '#description' => t('If checked, all whitespaces at the beginning and the end of the output will be removed.'),
+ '#default_value' => $this->options['alter']['trim_whitespace'],
+ );
+
+ $form['alter']['nl2br'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Convert newlines to HTML &lt;br&gt; tags'),
+ '#description' => t('If checked, all newlines chars (e.g. \n) are converted into HTML &lt;br&gt; tags.'),
+ '#default_value' => $this->options['alter']['nl2br'],
+ );
+ }
+
+ $form['empty_field_behavior'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('No results behavior'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#weight' => 100,
+ );
+
+ $form['empty'] = array(
+ '#type' => 'textarea',
+ '#title' => t('No results text'),
+ '#default_value' => $this->options['empty'],
+ '#description' => t('Provide text to display if this field contains an empty result. You may include HTML. You may enter data from this view as per the "Replacement patterns" in the "Rewrite Results" section below.'),
+ '#fieldset' => 'empty_field_behavior',
+ );
+
+ $form['empty_zero'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Count the number 0 as empty'),
+ '#default_value' => $this->options['empty_zero'],
+ '#description' => t('Enable to display the "no results text" if the field contains the number 0.'),
+ '#fieldset' => 'empty_field_behavior',
+ );
+
+ $form['hide_empty'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide if empty'),
+ '#default_value' => $this->options['hide_empty'],
+ '#description' => t('Enable to hide this field if it is empty. Note that the field label or rewritten output may still be displayed. To hide labels, check the style or row style settings for empty fields. To hide rewritten content, check the "Hide rewriting if empty" checkbox.'),
+ '#fieldset' => 'empty_field_behavior',
+ );
+
+ $form['hide_alter_empty'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide rewriting if empty'),
+ '#default_value' => $this->options['hide_alter_empty'],
+ '#description' => t('Do not display rewritten content if this field is empty.'),
+ '#fieldset' => 'empty_field_behavior',
+ );
+ }
+
+ /**
+ * Provide extra data to the administration form
+ */
+ function admin_summary() {
+ return $this->label();
+ }
+
+ /**
+ * Run before any fields are rendered.
+ *
+ * This gives the handlers some time to set up before any handler has
+ * been rendered.
+ *
+ * @param $values
+ * An array of all objects returned from the query.
+ */
+ function pre_render(&$values) { }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->sanitize_value($value);
+ }
+
+ /**
+ * Render a field using advanced settings.
+ *
+ * This renders a field normally, then decides if render-as-link and
+ * text-replacement rendering is necessary.
+ */
+ function advanced_render($values) {
+ if ($this->allow_advanced_render() && method_exists($this, 'render_item')) {
+ $raw_items = $this->get_items($values);
+ // If there are no items, set the original value to NULL.
+ if (empty($raw_items)) {
+ $this->original_value = NULL;
+ }
+ }
+ else {
+ $value = $this->render($values);
+ if (is_array($value)) {
+ $value = drupal_render($value);
+ }
+ $this->last_render = $value;
+ $this->original_value = $value;
+ }
+
+ if ($this->allow_advanced_render()) {
+ $tokens = NULL;
+ if (method_exists($this, 'render_item')) {
+ $items = array();
+ foreach ($raw_items as $count => $item) {
+ $value = $this->render_item($count, $item);
+ if (is_array($value)) {
+ $value = drupal_render($value);
+ }
+ $this->last_render = $value;
+ $this->original_value = $this->last_render;
+
+ $alter = $item + $this->options['alter'];
+ $alter['phase'] = VIEWS_HANDLER_RENDER_TEXT_PHASE_SINGLE_ITEM;
+ $items[] = $this->render_text($alter);
+ }
+
+ $value = $this->render_items($items);
+ }
+ else {
+ $alter = array('phase' => VIEWS_HANDLER_RENDER_TEXT_PHASE_COMPLETELY) + $this->options['alter'];
+ $value = $this->render_text($alter);
+ }
+
+ if (is_array($value)) {
+ $value = drupal_render($value);
+ }
+ // This happens here so that render_as_link can get the unaltered value of
+ // this field as a token rather than the altered value.
+ $this->last_render = $value;
+ }
+
+ if (empty($this->last_render)) {
+ if ($this->is_value_empty($this->last_render, $this->options['empty_zero'], FALSE)) {
+ $alter = $this->options['alter'];
+ $alter['alter_text'] = 1;
+ $alter['text'] = $this->options['empty'];
+ $alter['phase'] = VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY;
+ $this->last_render = $this->render_text($alter);
+ }
+ }
+
+ return $this->last_render;
+ }
+
+ /**
+ * Checks if a field value is empty.
+ *
+ * @param $value
+ * The field value.
+ * @param bool $empty_zero
+ * Whether or not this field is configured to consider 0 as empty.
+ * @param bool $no_skip_empty
+ * Whether or not to use empty() to check the value.
+ *
+ * @return bool
+ * TRUE if the value is considered empty, FALSE otherwise.
+ */
+ function is_value_empty($value, $empty_zero, $no_skip_empty = TRUE) {
+ if (!isset($value)) {
+ $empty = TRUE;
+ }
+ else {
+ $empty = ($empty_zero || ($value !== 0 && $value !== '0'));
+ }
+
+ if ($no_skip_empty) {
+ $empty = empty($value) && $empty;
+ }
+ return $empty;
+ }
+
+ /**
+ * Perform an advanced text render for the item.
+ *
+ * This is separated out as some fields may render lists, and this allows
+ * each item to be handled individually.
+ */
+ function render_text($alter) {
+ $value = $this->last_render;
+
+ if (!empty($alter['alter_text']) && $alter['text'] !== '') {
+ $tokens = $this->get_render_tokens($alter);
+ $value = $this->render_altered($alter, $tokens);
+ }
+
+ if (!empty($this->options['alter']['trim_whitespace'])) {
+ $value = trim($value);
+ }
+
+ // Check if there should be no further rewrite for empty values.
+ $no_rewrite_for_empty = $this->options['hide_alter_empty'] && $this->is_value_empty($this->original_value, $this->options['empty_zero']);
+
+ // Check whether the value is empty and return nothing, so the field isn't rendered.
+ // First check whether the field should be hidden if the value(hide_alter_empty = TRUE) /the rewrite is empty (hide_alter_empty = FALSE).
+ // For numeric values you can specify whether "0"/0 should be empty.
+ if ((($this->options['hide_empty'] && empty($value))
+ || ($alter['phase'] != VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty))
+ && $this->is_value_empty($value, $this->options['empty_zero'], FALSE)) {
+ return '';
+ }
+ // Only in empty phase.
+ if ($alter['phase'] == VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty) {
+ // If we got here then $alter contains the value of "No results text"
+ // and so there is nothing left to do.
+ return $value;
+ }
+
+ if (!empty($alter['strip_tags'])) {
+ $value = strip_tags($value, $alter['preserve_tags']);
+ }
+
+ $suffix = '';
+ if (!empty($alter['trim']) && !empty($alter['max_length'])) {
+ $length = strlen($value);
+ $value = $this->render_trim_text($alter, $value);
+ if ($this->options['alter']['more_link'] && strlen($value) < $length) {
+ $tokens = $this->get_render_tokens($alter);
+ $more_link_text = $this->options['alter']['more_link_text'] ? $this->options['alter']['more_link_text'] : t('more');
+ $more_link_text = strtr(filter_xss_admin($more_link_text), $tokens);
+ $more_link_path = $this->options['alter']['more_link_path'];
+ $more_link_path = strip_tags(decode_entities(strtr($more_link_path, $tokens)));
+
+ // Take sure that paths which was runned through url() does work as well.
+ $base_path = base_path();
+ // Checks whether the path starts with the base_path.
+ if (strpos($more_link_path, $base_path) === 0) {
+ $more_link_path = drupal_substr($more_link_path, drupal_strlen($base_path));
+ }
+
+ $more_link = l($more_link_text, $more_link_path, array('attributes' => array('class' => array('views-more-link'))));
+
+ $suffix .= " " . $more_link;
+ }
+ }
+
+ if (!empty($alter['nl2br'])) {
+ $value = nl2br($value);
+ }
+ $this->last_render_text = $value;
+
+ if (!empty($alter['make_link']) && !empty($alter['path'])) {
+ if (!isset($tokens)) {
+ $tokens = $this->get_render_tokens($alter);
+ }
+ $value = $this->render_as_link($alter, $value, $tokens);
+ }
+
+ return $value . $suffix;
+ }
+
+ /**
+ * Render this field as altered text, from a fieldset set by the user.
+ */
+ function render_altered($alter, $tokens) {
+ // Filter this right away as our substitutions are already sanitized.
+ $value = filter_xss_admin($alter['text']);
+ $value = strtr($value, $tokens);
+
+ return $value;
+ }
+
+ /**
+ * Trim the field down to the specified length.
+ */
+ function render_trim_text($alter, $value) {
+ if (!empty($alter['strip_tags'])) {
+ // NOTE: It's possible that some external fields might override the
+ // element type so if someone from, say, CCK runs into a bug here,
+ // this may be why =)
+ $this->definition['element type'] = 'span';
+ }
+ return views_trim_text($alter, $value);
+ }
+
+ /**
+ * Render this field as a link, with the info from a fieldset set by
+ * the user.
+ */
+ function render_as_link($alter, $text, $tokens) {
+ $value = '';
+
+ if (!empty($alter['prefix'])) {
+ $value .= filter_xss_admin(strtr($alter['prefix'], $tokens));
+ }
+
+ $options = array(
+ 'html' => TRUE,
+ 'absolute' => !empty($alter['absolute']) ? TRUE : FALSE,
+ );
+
+ // $path will be run through check_url() by l() so we do not need to
+ // sanitize it ourselves.
+ $path = $alter['path'];
+
+ // strip_tags() removes <front>, so check whether its different to front.
+ if ($path != '<front>') {
+ // Use strip tags as there should never be HTML in the path.
+ // However, we need to preserve special characters like " that
+ // were removed by check_plain().
+ $path = strip_tags(decode_entities(strtr($path, $tokens)));
+
+ if (!empty($alter['path_case']) && $alter['path_case'] != 'none') {
+ $path = $this->case_transform($path, $this->options['alter']['path_case']);
+ }
+
+ if (!empty($alter['replace_spaces'])) {
+ $path = str_replace(' ', '-', $path);
+ }
+ }
+
+ // Parse the URL and move any query and fragment parameters out of the path.
+ $url = parse_url($path);
+
+ // Seriously malformed URLs may return FALSE or empty arrays.
+ if (empty($url)) {
+ return $text;
+ }
+
+ // If the path is empty do not build a link around the given text and return
+ // it as is.
+ // http://www.example.com URLs will not have a $url['path'], so check host as well.
+ if (empty($url['path']) && empty($url['host']) && empty($url['fragment'])) {
+ return $text;
+ }
+
+ // If no scheme is provided in the $path, assign the default 'http://'.
+ // This allows a url of 'www.example.com' to be converted to 'http://www.example.com'.
+ // Only do this on for external URLs.
+ if ($alter['external']){
+ if (!isset($url['scheme'])) {
+ // There is no scheme, add the default 'http://' to the $path.
+ $path = "http://$path";
+ // Reset the $url array to include the new scheme.
+ $url = parse_url($path);
+ }
+ }
+
+ if (isset($url['query'])) {
+ $path = strtr($path, array('?' . $url['query'] => ''));
+ $query = drupal_get_query_array($url['query']);
+ // Remove query parameters that were assigned a query string replacement
+ // token for which there is no value available.
+ foreach ($query as $param => $val) {
+ if ($val == '%' . $param) {
+ unset($query[$param]);
+ }
+ }
+ $options['query'] = $query;
+ }
+ if (isset($url['fragment'])) {
+ $path = strtr($path, array('#' . $url['fragment'] => ''));
+ // If the path is empty we want to have a fragment for the current site.
+ if ($path == '') {
+ $options['external'] = TRUE;
+ }
+ $options['fragment'] = $url['fragment'];
+ }
+
+ $alt = strtr($alter['alt'], $tokens);
+ // Set the title attribute of the link only if it improves accessibility
+ if ($alt && $alt != $text) {
+ $options['attributes']['title'] = decode_entities($alt);
+ }
+
+ $class = strtr($alter['link_class'], $tokens);
+ if ($class) {
+ $options['attributes']['class'] = array($class);
+ }
+
+ if (!empty($alter['rel']) && $rel = strtr($alter['rel'], $tokens)) {
+ $options['attributes']['rel'] = $rel;
+ }
+
+ $target = check_plain(trim(strtr($alter['target'],$tokens)));
+ if (!empty($target)) {
+ $options['attributes']['target'] = $target;
+ }
+
+ // Allow the addition of arbitrary attributes to links. Additional attributes
+ // currently can only be altered in preprocessors and not within the UI.
+ if (isset($alter['link_attributes']) && is_array($alter['link_attributes'])) {
+ foreach ($alter['link_attributes'] as $key => $attribute) {
+ if (!isset($options['attributes'][$key])) {
+ $options['attributes'][$key] = strtr($attribute, $tokens);
+ }
+ }
+ }
+
+ // If the query and fragment were programatically assigned overwrite any
+ // parsed values.
+ if (isset($alter['query'])) {
+ // Convert the query to a string, perform token replacement, and then
+ // convert back to an array form for l().
+ $options['query'] = drupal_http_build_query($alter['query']);
+ $options['query'] = strtr($options['query'], $tokens);
+ $options['query'] = drupal_get_query_array($options['query']);
+ }
+ if (isset($alter['alias'])) {
+ // Alias is a boolean field, so no token.
+ $options['alias'] = $alter['alias'];
+ }
+ if (isset($alter['fragment'])) {
+ $options['fragment'] = strtr($alter['fragment'], $tokens);
+ }
+ if (isset($alter['language'])) {
+ $options['language'] = $alter['language'];
+ }
+
+ // If the url came from entity_uri(), pass along the required options.
+ if (isset($alter['entity'])) {
+ $options['entity'] = $alter['entity'];
+ }
+ if (isset($alter['entity_type'])) {
+ $options['entity_type'] = $alter['entity_type'];
+ }
+
+ $value .= l($text, $path, $options);
+
+ if (!empty($alter['suffix'])) {
+ $value .= filter_xss_admin(strtr($alter['suffix'], $tokens));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Get the 'render' tokens to use for advanced rendering.
+ *
+ * This runs through all of the fields and arguments that
+ * are available and gets their values. This will then be
+ * used in one giant str_replace().
+ */
+ function get_render_tokens($item) {
+ $tokens = array();
+ if (!empty($this->view->build_info['substitutions'])) {
+ $tokens = $this->view->build_info['substitutions'];
+ }
+ $count = 0;
+ foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
+ $token = '%' . ++$count;
+ if (!isset($tokens[$token])) {
+ $tokens[$token] = '';
+ }
+
+ // Use strip tags as there should never be HTML in the path.
+ // However, we need to preserve special characters like " that
+ // were removed by check_plain().
+ $tokens['!' . $count] = isset($this->view->args[$count - 1]) ? strip_tags(decode_entities($this->view->args[$count - 1])) : '';
+ }
+
+ // Get flattened set of tokens for any array depth in $_GET parameters.
+ $tokens += $this->get_token_values_recursive($_GET);
+
+ // Now add replacements for our fields.
+ foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
+ if (isset($handler->last_render)) {
+ $tokens["[$field]"] = $handler->last_render;
+ }
+ else {
+ $tokens["[$field]"] = '';
+ }
+ if (!empty($item)) {
+ $this->add_self_tokens($tokens, $item);
+ }
+
+ // We only use fields up to (and including) this one.
+ if ($field == $this->options['id']) {
+ break;
+ }
+ }
+
+ // Store the tokens for the row so we can reference them later if necessary.
+ $this->view->style_plugin->render_tokens[$this->view->row_index] = $tokens;
+ $this->last_tokens = $tokens;
+
+ return $tokens;
+ }
+
+ /**
+ * Recursive function to add replacements for nested query string parameters.
+ *
+ * E.g. if you pass in the following array:
+ * array(
+ * 'foo' => array(
+ * 'a' => 'value',
+ * 'b' => 'value',
+ * ),
+ * 'bar' => array(
+ * 'a' => 'value',
+ * 'b' => array(
+ * 'c' => value,
+ * ),
+ * ),
+ * );
+ *
+ * Would yield the following array of tokens:
+ * array(
+ * '%foo_a' => 'value'
+ * '%foo_b' => 'value'
+ * '%bar_a' => 'value'
+ * '%bar_b_c' => 'value'
+ * );
+ *
+ * @param $array
+ * An array of values.
+ *
+ * @param $parent_keys
+ * An array of parent keys. This will represent the array depth.
+ *
+ * @return
+ * An array of available tokens, with nested keys representative of the array structure.
+ */
+ function get_token_values_recursive(array $array, array $parent_keys = array()) {
+ $tokens = array();
+
+ foreach ($array as $param => $val) {
+ if (is_array($val)) {
+ // Copy parent_keys array, so we don't afect other elements of this iteration.
+ $child_parent_keys = $parent_keys;
+ $child_parent_keys[] = $param;
+ // Get the child tokens.
+ $child_tokens = $this->get_token_values_recursive($val, $child_parent_keys);
+ // Add them to the current tokens array.
+ $tokens += $child_tokens;
+ }
+ else {
+ // Create a token key based on array element structure.
+ $token_string = !empty($parent_keys) ? implode('_', $parent_keys) . '_' . $param : $param;
+ $tokens['%' . $token_string] = strip_tags(decode_entities($val));
+ }
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Add any special tokens this field might use for itself.
+ *
+ * This method is intended to be overridden by items that generate
+ * fields as a list. For example, the field that displays all terms
+ * on a node might have tokens for the tid and the term.
+ *
+ * By convention, tokens should follow the format of [token-subtoken]
+ * where token is the field ID and subtoken is the field. If the
+ * field ID is terms, then the tokens might be [terms-tid] and [terms-name].
+ */
+ function add_self_tokens(&$tokens, $item) { }
+
+ /**
+ * Document any special tokens this field might use for itself.
+ *
+ * @see add_self_tokens()
+ */
+ function document_self_tokens(&$tokens) { }
+
+ /**
+ * Call out to the theme() function, which probably just calls render() but
+ * allows sites to override output fairly easily.
+ */
+ function theme($values) {
+ return theme($this->theme_functions(),
+ array(
+ 'view' => $this->view,
+ 'field' => $this,
+ 'row' => $values
+ ));
+ }
+
+ function theme_functions() {
+ $themes = array();
+ $hook = 'views_view_field';
+
+ $display = $this->view->display[$this->view->current_display];
+
+ if (!empty($display)) {
+ $themes[] = $hook . '__' . $this->view->name . '__' . $display->id . '__' . $this->options['id'];
+ $themes[] = $hook . '__' . $this->view->name . '__' . $display->id;
+ $themes[] = $hook . '__' . $display->id . '__' . $this->options['id'];
+ $themes[] = $hook . '__' . $display->id;
+ if ($display->id != $display->display_plugin) {
+ $themes[] = $hook . '__' . $this->view->name . '__' . $display->display_plugin . '__' . $this->options['id'];
+ $themes[] = $hook . '__' . $this->view->name . '__' . $display->display_plugin;
+ $themes[] = $hook . '__' . $display->display_plugin . '__' . $this->options['id'];
+ $themes[] = $hook . '__' . $display->display_plugin;
+ }
+ }
+ $themes[] = $hook . '__' . $this->view->name . '__' . $this->options['id'];
+ $themes[] = $hook . '__' . $this->view->name;
+ $themes[] = $hook . '__' . $this->options['id'];
+ $themes[] = $hook;
+
+ return $themes;
+ }
+
+ function ui_name($short = FALSE) {
+ return $this->get_field(parent::ui_name($short));
+ }
+}
+
+/**
+ * A special handler to take the place of missing or broken handlers.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_broken extends views_handler_field {
+ function ui_name($short = FALSE) {
+ return t('Broken/missing handler');
+ }
+
+ function ensure_my_table() { /* No table to ensure! */ }
+ function query($group_by = FALSE) { /* No query to run */ }
+ function options_form(&$form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => '<div class="form-item description">' . t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.') . '</div>',
+ );
+ }
+
+ /**
+ * Determine if the handler is considered 'broken'
+ */
+ function broken() { return TRUE; }
+}
+
+/**
+ * Render a numeric value as a size.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_size extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['file_size_display'] = array('default' => 'formatted');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['file_size_display'] = array(
+ '#title' => t('File size display'),
+ '#type' => 'select',
+ '#options' => array(
+ 'formatted' => t('Formatted (in KB or MB)'),
+ 'bytes' => t('Raw bytes'),
+ ),
+ );
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ if ($value) {
+ switch ($this->options['file_size_display']) {
+ case 'bytes':
+ return $value;
+ case 'formatted':
+ default:
+ return format_size($value);
+ }
+ }
+ else {
+ return '';
+ }
+ }
+}
+
+/**
+ * A handler to run a field through simple XSS filtering.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_xss extends views_handler_field {
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->sanitize_value($value, 'xss');
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/handlers/views_handler_field_boolean.inc b/sites/all/modules/views/handlers/views_handler_field_boolean.inc
new file mode 100644
index 000000000..8acfb32cc
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_boolean.inc
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_boolean.
+ */
+
+/**
+ * A handler to provide proper displays for booleans.
+ *
+ * Allows for display of true/false, yes/no, on/off, enabled/disabled.
+ *
+ * Definition terms:
+ * - output formats: An array where the first entry is displayed on boolean true
+ * and the second is displayed on boolean false. An example for sticky is:
+ * @code
+ * 'output formats' => array(
+ * 'sticky' => array(t('Sticky'), ''),
+ * ),
+ * @endcode
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_boolean extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['type'] = array('default' => 'yes-no');
+ $options['type_custom_true'] = array('default' => '', 'translatable' => TRUE);
+ $options['type_custom_false'] = array('default' => '', 'translatable' => TRUE);
+ $options['not'] = array('definition bool' => 'reverse');
+
+ return $options;
+ }
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ $default_formats = array(
+ 'yes-no' => array(t('Yes'), t('No')),
+ 'true-false' => array(t('True'), t('False')),
+ 'on-off' => array(t('On'), t('Off')),
+ 'enabled-disabled' => array(t('Enabled'), t('Disabled')),
+ 'boolean' => array(1, 0),
+ 'unicode-yes-no' => array('✔', '✖'),
+ );
+ $output_formats = isset($this->definition['output formats']) ? $this->definition['output formats'] : array();
+ $custom_format = array('custom' => array(t('Custom')));
+ $this->formats = array_merge($default_formats, $output_formats, $custom_format);
+ }
+
+ function options_form(&$form, &$form_state) {
+ foreach ($this->formats as $key => $item) {
+ $options[$key] = implode('/', $item);
+ }
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Output format'),
+ '#options' => $options,
+ '#default_value' => $this->options['type'],
+ );
+
+ $form['type_custom_true'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Custom output for TRUE'),
+ '#default_value' => $this->options['type_custom_true'],
+ '#states' => array(
+ 'visible' => array(
+ 'select[name="options[type]"]' => array('value' => 'custom'),
+ ),
+ ),
+ );
+
+ $form['type_custom_false'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Custom output for FALSE'),
+ '#default_value' => $this->options['type_custom_false'],
+ '#states' => array(
+ 'visible' => array(
+ 'select[name="options[type]"]' => array('value' => 'custom'),
+ ),
+ ),
+ );
+
+ $form['not'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Reverse'),
+ '#description' => t('If checked, true will be displayed as false.'),
+ '#default_value' => $this->options['not'],
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ if (!empty($this->options['not'])) {
+ $value = !$value;
+ }
+
+ if ($this->options['type'] == 'custom') {
+ return $value ? filter_xss_admin($this->options['type_custom_true']) : filter_xss_admin($this->options['type_custom_false']);
+ }
+ else if (isset($this->formats[$this->options['type']])) {
+ return $value ? $this->formats[$this->options['type']][0] : $this->formats[$this->options['type']][1];
+ }
+ else {
+ return $value ? $this->formats['yes-no'][0] : $this->formats['yes-no'][1];
+ }
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_contextual_links.inc b/sites/all/modules/views/handlers/views_handler_field_contextual_links.inc
new file mode 100644
index 000000000..faeeedc21
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_contextual_links.inc
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_contextual_links.
+ */
+
+/**
+ * Provides a handler that adds contextual links.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_contextual_links extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['fields'] = array('default' => array());
+ $options['destination'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $all_fields = $this->view->display_handler->get_field_labels();
+ // Offer to include only those fields that follow this one.
+ $field_options = array_slice($all_fields, 0, array_search($this->options['id'], array_keys($all_fields)));
+ $form['fields'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Fields'),
+ '#description' => t('Fields to be included as contextual links.'),
+ '#options' => $field_options,
+ '#default_value' => $this->options['fields'],
+ );
+ $form['destination'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include destination'),
+ '#description' => t('Include a "destination" parameter in the link to return the user to the original view upon completing the contextual action.'),
+ '#default_value' => $this->options['destination'],
+ );
+ }
+
+ function pre_render(&$values) {
+ // Add a row plugin css class for the contextual link.
+ $class = 'contextual-links-region';
+ if (!empty($this->view->style_plugin->options['row_class'])) {
+ $this->view->style_plugin->options['row_class'] .= " $class";
+ }
+ else {
+ $this->view->style_plugin->options['row_class'] = $class;
+ }
+ }
+
+ /**
+ * Render the contextual fields.
+ */
+ function render($values) {
+ $links = array();
+ foreach ($this->options['fields'] as $field) {
+ if (empty($this->view->style_plugin->rendered_fields[$this->view->row_index][$field])) {
+ continue;
+ }
+ $title = $this->view->field[$field]->last_render_text;
+ $path = '';
+ if (!empty($this->view->field[$field]->options['alter']['path'])) {
+ $path = $this->view->field[$field]->options['alter']['path'];
+ }
+ if (!empty($title) && !empty($path)) {
+ // Make sure that tokens are replaced for this paths as well.
+ $tokens = $this->get_render_tokens(array());
+ $path = strip_tags(decode_entities(strtr($path, $tokens)));
+
+ $links[$field] = array(
+ 'href' => $path,
+ 'title' => $title,
+ );
+ if (!empty($this->options['destination'])) {
+ $links[$field]['query'] = drupal_get_destination();
+ }
+ }
+ }
+
+ if (!empty($links)) {
+ $build = array(
+ '#prefix' => '<div class="contextual-links-wrapper">',
+ '#suffix' => '</div>',
+ '#theme' => 'links__contextual',
+ '#links' => $links,
+ '#attributes' => array('class' => array('contextual-links')),
+ '#attached' => array(
+ 'library' => array(array('contextual', 'contextual-links')),
+ ),
+ '#access' => user_access('access contextual links'),
+ );
+ return drupal_render($build);
+ }
+ else {
+ return '';
+ }
+ }
+
+ function query() { }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_counter.inc b/sites/all/modules/views/handlers/views_handler_field_counter.inc
new file mode 100644
index 000000000..e4a0ebffd
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_counter.inc
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_counter.
+ */
+
+/**
+ * Field handler to show a counter of the current row.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_counter extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['counter_start'] = array('default' => 1);
+ $options['reverse'] = array('default' => FALSE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['counter_start'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Starting value'),
+ '#default_value' => $this->options['counter_start'],
+ '#description' => t('Specify the number the counter should start at.'),
+ '#size' => 2,
+ );
+
+ $form['reverse'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Reverse'),
+ '#default_value' => $this->options['reverse'],
+ '#description' => t('Reverse the counter.'),
+ );
+
+ parent::options_form($form, $form_state);
+ }
+
+ function query() {
+ // do nothing -- to override the parent query.
+ }
+
+ function render($values) {
+ $reverse = empty($this->options['reverse']) ? 1 : -1;
+
+ // Note: 1 is subtracted from the counter start value below because the
+ // counter value is incremented by 1 at the end of this function.
+ $counter_start = is_numeric($this->options['counter_start']) ? $this->options['counter_start'] : 0;
+ $count = ($reverse == -1) ? count($this->view->result) + $counter_start : $counter_start -1;
+ $pager = $this->view->query->pager;
+
+ // Get the base count of the pager.
+ if ($pager->use_pager()) {
+ if ($reverse == -1) {
+ $count = ($pager->total_items + $counter_start - ($pager->get_current_page() * $pager->get_items_per_page()) + $pager->get_offset());
+ } else {
+ $count += (($pager->get_items_per_page() * $pager->get_current_page() + $pager->get_offset())) * $reverse;
+ }
+ }
+ // Add the counter for the current site.
+ $count += ($this->view->row_index + 1) * $reverse;
+
+ return $count;
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_custom.inc b/sites/all/modules/views/handlers/views_handler_field_custom.inc
new file mode 100644
index 000000000..66586de23
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_custom.inc
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_custom.
+ */
+
+/**
+ * A handler to provide a field that is completely custom by the administrator.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_custom extends views_handler_field {
+ function query() {
+ // do nothing -- to override the parent query.
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ // Override the alter text option to always alter the text.
+ $options['alter']['contains']['alter_text'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['hide_alter_empty'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ // Remove the checkbox
+ unset($form['alter']['alter_text']);
+ unset($form['alter']['text']['#dependency']);
+ unset($form['alter']['text']['#process']);
+ unset($form['alter']['help']['#dependency']);
+ unset($form['alter']['help']['#process']);
+ $form['#pre_render'][] = 'views_handler_field_custom_pre_render_move_text';
+ }
+
+ function render($values) {
+ // Return the text, so the code never thinks the value is empty.
+ return $this->options['alter']['text'];
+ }
+}
+
+/**
+ * Prerender function to move the textarea to the top.
+ */
+function views_handler_field_custom_pre_render_move_text($form) {
+ $form['text'] = $form['alter']['text'];
+ $form['help'] = $form['alter']['help'];
+ unset($form['alter']['text']);
+ unset($form['alter']['help']);
+
+ return $form;
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_date.inc b/sites/all/modules/views/handlers/views_handler_field_date.inc
new file mode 100644
index 000000000..f2c637108
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_date.inc
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_date.
+ */
+
+/**
+ * A handler to provide proper displays for dates.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_date extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['date_format'] = array('default' => 'small');
+ $options['custom_date_format'] = array('default' => '');
+ $options['second_date_format_custom'] = array('default' => '');
+ $options['second_date_format'] = array('default' => 'small');
+ $options['timezone'] = array('default' => '');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+
+ $date_formats = array();
+ $date_types = system_get_date_types();
+ foreach ($date_types as $key => $value) {
+ $date_formats[$value['type']] = t('@date_format format', array('@date_format' => $value['title'])) . ': ' . format_date(REQUEST_TIME, $value['type']);
+ }
+
+ $form['date_format'] = array(
+ '#type' => 'select',
+ '#title' => t('Date format'),
+ '#options' => $date_formats + array(
+ 'custom' => t('Custom'),
+ 'raw time ago' => t('Time ago'),
+ 'time ago' => t('Time ago (with "ago" appended)'),
+ 'today time ago' => t('Time ago (with "ago" appended) for today\'s date, but not for other dates'),
+ 'raw time hence' => t('Time hence'),
+ 'time hence' => t('Time hence (with "hence" appended)'),
+ 'raw time span' => t('Time span (future dates have "-" prepended)'),
+ 'inverse time span' => t('Time span (past dates have "-" prepended)'),
+ 'time span' => t('Time span (with "ago/hence" appended)'),
+ ),
+ '#default_value' => isset($this->options['date_format']) ? $this->options['date_format'] : 'small',
+ );
+ $form['custom_date_format'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Custom date format'),
+ '#description' => t('If "Custom", see the <a href="@url" target="_blank">PHP manual</a> for date formats. Otherwise, enter the number of different time units to display, which defaults to 2.', array('@url' => 'http://php.net/manual/function.date.php')),
+ '#default_value' => isset($this->options['custom_date_format']) ? $this->options['custom_date_format'] : '',
+ '#dependency' => array('edit-options-date-format' => array('custom', 'raw time ago', 'time ago', 'today time ago', 'raw time hence', 'time hence', 'raw time span', 'time span', 'raw time span', 'inverse time span', 'time span')),
+ );
+ $form['second_date_format'] = array(
+ '#type' => 'select',
+ '#title' => t('Second date format'),
+ '#options' => $date_formats + array(
+ 'custom' => t('Custom'),
+ ),
+ '#description' => t('The date format which will be used for rendering dates other than today.'),
+ '#default_value' => isset($this->options['second_date_format']) ? $this->options['second_date_format'] : 'small',
+ '#dependency' => array('edit-options-date-format' => array('today time ago')),
+ );
+ $form['second_date_format_custom'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Custom date format of second date'),
+ '#description' => t('If "Custom" is selected in "Second date format", see the <a href="@url" target="_blank">PHP manual</a> for date formats. Otherwise, enter the number of different time units to display, which defaults to 2.', array('@url' => 'http://php.net/manual/function.date.php')),
+ '#default_value' => isset($this->options['second_date_format_custom']) ? $this->options['second_date_format_custom'] : '',
+ // We have to use states instead of ctools dependency because dependency
+ // doesn't handle multiple conditions.
+ '#states' => array(
+ 'visible' => array(
+ '#edit-options-date-format' => array('value' => 'today time ago'),
+ '#edit-options-second-date-format' => array('value' => 'custom'),
+ ),
+ ),
+ // We have to use ctools dependency too because states doesn't add the
+ // correct left margin to the element's wrapper.
+ '#dependency' => array(
+ // This condition is handled by form API's states.
+// 'edit-options-date-format' => array('today time ago'),
+ 'edit-options-second-date-format' => array('custom'),
+ ),
+ );
+ $form['timezone'] = array(
+ '#type' => 'select',
+ '#title' => t('Timezone'),
+ '#description' => t('Timezone to be used for date output.'),
+ '#options' => array('' => t('- Default site/user timezone -')) + system_time_zones(FALSE),
+ '#default_value' => $this->options['timezone'],
+ '#dependency' => array('edit-options-date-format' => array_merge(array('custom'), array_keys($date_formats))),
+ );
+
+ parent::options_form($form, $form_state);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ $format = $this->options['date_format'];
+ if (in_array($format, array('custom', 'raw time ago', 'time ago', 'today time ago', 'raw time hence', 'time hence', 'raw time span', 'time span', 'raw time span', 'inverse time span', 'time span'))) {
+ $custom_format = $this->options['custom_date_format'];
+ }
+
+ if ($value) {
+ $timezone = !empty($this->options['timezone']) ? $this->options['timezone'] : NULL;
+ $time_diff = REQUEST_TIME - $value; // will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence)
+ switch ($format) {
+ case 'raw time ago':
+ return format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2);
+ case 'time ago':
+ return t('%time ago', array('%time' => format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2)));
+ case 'today time ago':
+ $second_format = $this->options['second_date_format'];
+ $second_custom_format = $this->options['second_date_format_custom'];
+ if (format_date(REQUEST_TIME, 'custom', 'Y-m-d', $timezone) == format_date($value, 'custom', 'Y-m-d', $timezone)) {
+ return t('%time ago', array('%time' => format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2)));
+ }
+ elseif ($second_format == 'custom') {
+ if ($second_custom_format == 'r') {
+ return format_date($value, $second_format, $second_custom_format, $timezone, 'en');
+ }
+ return format_date($value, $second_format, $second_custom_format, $timezone);
+ }
+ else {
+ return format_date($value, $this->options['second_date_format'], '', $timezone);
+ }
+ case 'raw time hence':
+ return format_interval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2);
+ case 'time hence':
+ return t('%time hence', array('%time' => format_interval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2)));
+ case 'raw time span':
+ return ($time_diff < 0 ? '-' : '') . format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2);
+ case 'inverse time span':
+ return ($time_diff > 0 ? '-' : '') . format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2);
+ case 'time span':
+ return t(($time_diff < 0 ? '%time hence' : '%time ago'), array('%time' => format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2)));
+ case 'custom':
+ if ($custom_format == 'r') {
+ return format_date($value, $format, $custom_format, $timezone, 'en');
+ }
+ return format_date($value, $format, $custom_format, $timezone);
+ default:
+ return format_date($value, $format, '', $timezone);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_entity.inc b/sites/all/modules/views/handlers/views_handler_field_entity.inc
new file mode 100644
index 000000000..d8aaba499
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_entity.inc
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_entity.
+ */
+
+/**
+ * A handler to display data from entity objects.
+ *
+ * Fields based upon this handler work with all query-backends if the tables
+ * used by the query backend have an 'entity type' specified. In order to
+ * make fields based upon this handler automatically available to all compatible
+ * query backends, the views field can be defined in the table
+ * @code views_entity_{ENTITY_TYPE} @endcode.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_entity extends views_handler_field {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores all entites which are in the result.
+ */
+ public $entities;
+
+ /**
+ * The base field of the entity type assosiated with this field.
+ */
+ public $base_field;
+
+ /**
+ * Initialize the entity type.
+ */
+ public function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ // Initialize the entity-type used.
+ $table_data = views_fetch_data($this->table);
+ $this->entity_type = $table_data['table']['entity type'];
+ }
+
+ /**
+ * Overriden to add the field for the entity id.
+ */
+ function query() {
+ $this->table_alias = $base_table = $this->view->base_table;
+ $this->base_field = $this->view->base_field;
+
+ if (!empty($this->relationship)) {
+ foreach ($this->view->relationship as $relationship) {
+ if ($relationship->alias == $this->relationship) {
+ $base_table = $relationship->definition['base'];
+ $this->table_alias = $relationship->alias;
+
+ $table_data = views_fetch_data($base_table);
+ $this->base_field = empty($relationship->definition['base field']) ? $table_data['table']['base']['field'] : $relationship->definition['base field'];
+ }
+ }
+ }
+
+ // Add the field if the query back-end implements an add_field() method,
+ // just like the default back-end.
+ if (method_exists($this->query, 'add_field')) {
+ $this->field_alias = $this->query->add_field($this->table_alias, $this->base_field, '');
+ }
+
+ $this->add_additional_fields();
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ function pre_render(&$values) {
+ if (!empty($values)) {
+ list($this->entity_type, $this->entities) = $this->query->get_result_entities($values, !empty($this->relationship) ? $this->relationship : NULL, $this->field_alias);
+ }
+ }
+
+ /**
+ * Overridden to return the entity object, or a certain property of the entity.
+ */
+ function get_value($values, $field = NULL) {
+ if (isset($this->entities[$this->view->row_index])) {
+ $entity = $this->entities[$this->view->row_index];
+ // Support to get a certain part of the entity.
+ if (isset($field) && isset($entity->{$field})) {
+ return $entity->{$field};
+ }
+ // Support to get a part of the values as the normal get_value.
+ elseif (isset($field) && isset($values->{$this->aliases[$field]})) {
+ return $values->{$this->aliases[$field]};
+ }
+ else {
+ return $entity;
+ }
+ }
+ return FALSE;
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_machine_name.inc b/sites/all/modules/views/handlers/views_handler_field_machine_name.inc
new file mode 100644
index 000000000..9f3587fe1
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_machine_name.inc
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_machine_name.
+ */
+
+/**
+ * Field handler whichs allows to show machine name content as human name.
+ * @ingroup views_field_handlers
+ *
+ * Definition items:
+ * - options callback: The function to call in order to generate the value options. If omitted, the options 'Yes' and 'No' will be used.
+ * - options arguments: An array of arguments to pass to the options callback.
+ */
+class views_handler_field_machine_name extends views_handler_field {
+ /**
+ * @var array Stores the available options.
+ */
+ var $value_options;
+
+ function get_value_options() {
+ if (isset($this->value_options)) {
+ return;
+ }
+
+ if (isset($this->definition['options callback']) && is_callable($this->definition['options callback'])) {
+ if (isset($this->definition['options arguments']) && is_array($this->definition['options arguments'])) {
+ $this->value_options = call_user_func_array($this->definition['options callback'], $this->definition['options arguments']);
+ }
+ else {
+ $this->value_options = call_user_func($this->definition['options callback']);
+ }
+ }
+ else {
+ $this->value_options = array();
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['machine_name'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['machine_name'] = array(
+ '#title' => t('Output machine name'),
+ '#description' => t('Display field as machine name.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['machine_name']),
+ );
+ }
+
+ function pre_render(&$values) {
+ $this->get_value_options();
+ }
+
+ function render($values) {
+ $value = $values->{$this->field_alias};
+ if (!empty($this->options['machine_name']) || !isset($this->value_options[$value])) {
+ $result = check_plain($value);
+ }
+ else {
+ $result = $this->value_options[$value];
+ }
+
+ return $result;
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_markup.inc b/sites/all/modules/views/handlers/views_handler_field_markup.inc
new file mode 100644
index 000000000..b0f1cea9c
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_markup.inc
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_markup.
+ */
+
+/**
+ * A handler to run a field through check_markup, using a companion
+ * format field.
+ *
+ * - format: (REQUIRED) Either a string format id to use for this field or an
+ * array('field' => {$field}) where $field is the field in this table
+ * used to control the format such as the 'format' field in the node,
+ * which goes with the 'body' field.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_markup extends views_handler_field {
+ /**
+ * Constructor; calls to base object constructor.
+ */
+ function construct() {
+ parent::construct();
+
+ $this->format = $this->definition['format'];
+
+ $this->additional_fields = array();
+ if (is_array($this->format)) {
+ $this->additional_fields['format'] = $this->format;
+ }
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ if (is_array($this->format)) {
+ $format = $this->get_value($values, 'format');
+ }
+ else {
+ $format = $this->format;
+ }
+ if ($value) {
+ $value = str_replace('<!--break-->', '', $value);
+ return check_markup($value, $format, '');
+ }
+ }
+
+ function element_type($none_supported = FALSE, $default_empty = FALSE, $inline = FALSE) {
+ if ($inline) {
+ return 'span';
+ }
+
+ if (isset($this->definition['element type'])) {
+ return $this->definition['element type'];
+ }
+
+ return 'div';
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_math.inc b/sites/all/modules/views/handlers/views_handler_field_math.inc
new file mode 100644
index 000000000..50a948ae5
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_math.inc
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_math.
+ */
+
+/**
+ * Render a mathematical expression as a numeric value
+ *
+ * Definition terms:
+ * - float: If true this field contains a decimal value. If unset this field
+ * will be assumed to be integer.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_math extends views_handler_field_numeric {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['expression'] = array('default' => '');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['expression'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Expression'),
+ '#description' => t("Enter mathematical expressions such as 2 + 2 or sqrt(5). You may assign variables and create mathematical functions and evaluate them. Use the ; to separate these. For example: f(x) = x + 2; f(2). The result of the previous row's mathematical expression can be accessed by using the [expression] token itself."),
+ '#default_value' => $this->options['expression'],
+ );
+
+ // Create a place for the help
+ $form['expression_help'] = array();
+ parent::options_form($form, $form_state);
+
+ // Then move the existing help:
+ $form['expression_help'] = $form['alter']['help'];
+ unset($form['expression_help']['#dependency']);
+ unset($form['alter']['help']);
+ }
+
+ function render($values) {
+ ctools_include('math-expr');
+ $tokens = array_map('floatval', $this->get_render_tokens(array()));
+ $value = strtr($this->options['expression'], $tokens);
+ $expressions = explode(';', $value);
+ $math = new ctools_math_expr;
+ foreach ($expressions as $expression) {
+ if ($expression !== '') {
+ $value = $math->evaluate($expression);
+ }
+ }
+
+ // The rest is directly from views_handler_field_numeric but because it
+ // does not allow the value to be passed in, it is copied.
+ if (!empty($this->options['set_precision'])) {
+ $value = number_format($value, $this->options['precision'], $this->options['decimal'], $this->options['separator']);
+ }
+ else {
+ $remainder = abs($value) - intval(abs($value));
+ $value = $value > 0 ? floor($value) : ceil($value);
+ $value = number_format($value, 0, '', $this->options['separator']);
+ if ($remainder) {
+ // The substr may not be locale safe.
+ $value .= $this->options['decimal'] . substr($remainder, 2);
+ }
+ }
+
+ // Check to see if hiding should happen before adding prefix and suffix.
+ if ($this->options['hide_empty'] && empty($value) && ($value !== 0 || $this->options['empty_zero'])) {
+ return '';
+ }
+
+ // Should we format as a plural.
+ if (!empty($this->options['format_plural']) && ($value != 0 || !$this->options['empty_zero'])) {
+ $value = format_plural($value, $this->options['format_plural_singular'], $this->options['format_plural_plural']);
+ }
+
+ return $this->sanitize_value($this->options['prefix'] . $value . $this->options['suffix']);
+ }
+
+ function query() { }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_numeric.inc b/sites/all/modules/views/handlers/views_handler_field_numeric.inc
new file mode 100644
index 000000000..d10d3d0b3
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_numeric.inc
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_numeric.
+ */
+
+/**
+ * Render a field as a numeric value
+ *
+ * Definition terms:
+ * - float: If true this field contains a decimal value. If unset this field
+ * will be assumed to be integer.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_numeric extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['set_precision'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['precision'] = array('default' => 0);
+ $options['decimal'] = array('default' => '.', 'translatable' => TRUE);
+ $options['separator'] = array('default' => ',', 'translatable' => TRUE);
+ $options['format_plural'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['format_plural_singular'] = array('default' => '1');
+ $options['format_plural_plural'] = array('default' => '@count');
+ $options['prefix'] = array('default' => '', 'translatable' => TRUE);
+ $options['suffix'] = array('default' => '', 'translatable' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ if (!empty($this->definition['float'])) {
+ $form['set_precision'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Round'),
+ '#description' => t('If checked, the number will be rounded.'),
+ '#default_value' => $this->options['set_precision'],
+ );
+ $form['precision'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Precision'),
+ '#default_value' => $this->options['precision'],
+ '#description' => t('Specify how many digits to print after the decimal point.'),
+ '#dependency' => array('edit-options-set-precision' => array(TRUE)),
+ '#size' => 2,
+ );
+ $form['decimal'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Decimal point'),
+ '#default_value' => $this->options['decimal'],
+ '#description' => t('What single character to use as a decimal point.'),
+ '#size' => 2,
+ );
+ }
+ $form['separator'] = array(
+ '#type' => 'select',
+ '#title' => t('Thousands marker'),
+ '#options' => array(
+ '' => t('- None -'),
+ ',' => t('Comma'),
+ ' ' => t('Space'),
+ '.' => t('Decimal'),
+ '\'' => t('Apostrophe'),
+ ),
+ '#default_value' => $this->options['separator'],
+ '#description' => t('What single character to use as the thousands separator.'),
+ '#size' => 2,
+ );
+ $form['format_plural'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Format plural'),
+ '#description' => t('If checked, special handling will be used for plurality.'),
+ '#default_value' => $this->options['format_plural'],
+ );
+ $form['format_plural_singular'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Singular form'),
+ '#default_value' => $this->options['format_plural_singular'],
+ '#description' => t('Text to use for the singular form.'),
+ '#dependency' => array('edit-options-format-plural' => array(TRUE)),
+ );
+ $form['format_plural_plural'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Plural form'),
+ '#default_value' => $this->options['format_plural_plural'],
+ '#description' => t('Text to use for the plural form, @count will be replaced with the value.'),
+ '#dependency' => array('edit-options-format-plural' => array(TRUE)),
+ );
+ $form['prefix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Prefix'),
+ '#default_value' => $this->options['prefix'],
+ '#description' => t('Text to put before the number, such as currency symbol.'),
+ );
+ $form['suffix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Suffix'),
+ '#default_value' => $this->options['suffix'],
+ '#description' => t('Text to put after the number, such as currency symbol.'),
+ );
+
+ parent::options_form($form, $form_state);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ if (!empty($this->options['set_precision'])) {
+ $value = number_format($value, $this->options['precision'], $this->options['decimal'], $this->options['separator']);
+ }
+ else {
+ $remainder = abs($value) - intval(abs($value));
+ $value = $value > 0 ? floor($value) : ceil($value);
+ $value = number_format($value, 0, '', $this->options['separator']);
+ if ($remainder) {
+ // The substr may not be locale safe.
+ $value .= $this->options['decimal'] . substr($remainder, 2);
+ }
+ }
+
+ // Check to see if hiding should happen before adding prefix and suffix.
+ if ($this->options['hide_empty'] && empty($value) && ($value !== 0 || $this->options['empty_zero'])) {
+ return '';
+ }
+
+ // Should we format as a plural.
+ if (!empty($this->options['format_plural'])) {
+ $value = format_plural($value, $this->options['format_plural_singular'], $this->options['format_plural_plural']);
+ }
+
+ return $this->sanitize_value($this->options['prefix'], 'xss')
+ . $this->sanitize_value($value)
+ . $this->sanitize_value($this->options['suffix'], 'xss');
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_prerender_list.inc b/sites/all/modules/views/handlers/views_handler_field_prerender_list.inc
new file mode 100644
index 000000000..00a571aa6
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_prerender_list.inc
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_prerender_list.
+ */
+
+/**
+ * Field handler to provide a list of items.
+ *
+ * The items are expected to be loaded by a child object during pre_render,
+ * and 'my field' is expected to be the pointer to the items in the list.
+ *
+ * Items to render should be in a list in $this->items
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_prerender_list extends views_handler_field {
+ /**
+ * Stores all items which are used to render the items.
+ * It should be keyed first by the id of the base table, for example nid.
+ * The second key is the id of the thing which is displayed multiple times
+ * per row, for example the tid.
+ *
+ * @var array
+ */
+ var $items = array();
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['type'] = array('default' => 'separator');
+ $options['separator'] = array('default' => ', ');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Display type'),
+ '#options' => array(
+ 'ul' => t('Unordered list'),
+ 'ol' => t('Ordered list'),
+ 'separator' => t('Simple separator'),
+ ),
+ '#default_value' => $this->options['type'],
+ );
+
+ $form['separator'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Separator'),
+ '#default_value' => $this->options['separator'],
+ '#dependency' => array('radio:options[type]' => array('separator')),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * This function is deprecated, but left in for older systems that have not
+ * yet or won't update their prerender list fields. If a render_item method
+ * exists, this will not get used by advanced_render.
+ */
+ function render($values) {
+ $field = $this->get_value($values);
+ if (!empty($this->items[$field])) {
+ if ($this->options['type'] == 'separator') {
+ return implode($this->sanitize_value($this->options['separator']), $this->items[$field]);
+ }
+ else {
+ return theme('item_list',
+ array(
+ 'items' => $this->items[$field],
+ 'title' => NULL,
+ 'type' => $this->options['type']
+ ));
+ }
+ }
+ }
+
+ /**
+ * Render all items in this field together.
+ *
+ * When using advanced render, each possible item in the list is rendered
+ * individually. Then the items are all pasted together.
+ */
+ function render_items($items) {
+ if (!empty($items)) {
+ if ($this->options['type'] == 'separator') {
+ return implode($this->sanitize_value($this->options['separator'], 'xss_admin'), $items);
+ }
+ else {
+ return theme('item_list',
+ array(
+ 'items' => $items,
+ 'title' => NULL,
+ 'type' => $this->options['type']
+ ));
+ }
+ }
+ }
+
+ /**
+ * Return an array of items for the field.
+ *
+ * Items should be stored in the result array, if possible, as an array
+ * with 'value' as the actual displayable value of the item, plus
+ * any items that might be found in the 'alter' options array for
+ * creating links, such as 'path', 'fragment', 'query' etc, such a thing
+ * is to be made. Additionally, items that might be turned into tokens
+ * should also be in this array.
+ */
+ function get_items($values) {
+ // Only the parent get_value returns a single field.
+ $field = parent::get_value($values);
+ if (!empty($this->items[$field])) {
+ return $this->items[$field];
+ }
+
+ return array();
+ }
+
+ /**
+ * Get the value that's supposed to be rendered.
+ *
+ * @param $values
+ * An object containing all retrieved values.
+ * @param $field
+ * Optional name of the field where the value is stored.
+ * @param $raw
+ * Use the raw data and not the data defined in pre_render
+ */
+ function get_value($values, $field = NULL, $raw = FALSE) {
+ if ($raw) {
+ return parent::get_value($values, $field);
+ }
+ $item = $this->get_items($values);
+ $item = (array) $item;
+ if (isset($field) && isset($item[$field])) {
+ return $item[$field];
+ }
+ return $item;
+ }
+
+ /**
+ * Determine if advanced rendering is allowed.
+ *
+ * By default, advanced rendering will NOT be allowed if the class
+ * inheriting from this does not implement a 'render_items' method.
+ */
+ function allow_advanced_render() {
+ // Note that the advanced render bits also use the presence of
+ // this method to determine if it needs to render items as a list.
+ return method_exists($this, 'render_item');
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_serialized.inc b/sites/all/modules/views/handlers/views_handler_field_serialized.inc
new file mode 100644
index 000000000..1579fce71
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_serialized.inc
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_serialized.
+ */
+
+/**
+ * Field handler to show data of serialized fields.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_serialized extends views_handler_field {
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['format'] = array('default' => 'unserialized');
+ $options['key'] = array('default' => '');
+ return $options;
+ }
+
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['format'] = array(
+ '#type' => 'select',
+ '#title' => t('Display format'),
+ '#description' => t('How should the serialized data be displayed. You can choose a custom array/object key or a print_r on the full output.'),
+ '#options' => array(
+ 'unserialized' => t('Full data (unserialized)'),
+ 'serialized' => t('Full data (serialized)'),
+ 'key' => t('A certain key'),
+ ),
+ '#default_value' => $this->options['format'],
+ );
+ $form['key'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Which key should be displayed'),
+ '#default_value' => $this->options['key'],
+ '#dependency' => array('edit-options-format' => array('key')),
+ );
+ }
+
+ function options_validate(&$form, &$form_state) {
+ // Require a key if the format is key.
+ if ($form_state['values']['options']['format'] == 'key' && $form_state['values']['options']['key'] == '') {
+ form_error($form['key'], t('You have to enter a key if you want to display a key of the data.'));
+ }
+ }
+
+ function render($values) {
+ $value = $values->{$this->field_alias};
+
+ if ($this->options['format'] == 'unserialized') {
+ return check_plain(print_r(unserialize($value), TRUE));
+ }
+ elseif ($this->options['format'] == 'key' && !empty($this->options['key'])) {
+ $value = (array) unserialize($value);
+ return check_plain($value[$this->options['key']]);
+ }
+
+ return $value;
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_time_interval.inc b/sites/all/modules/views/handlers/views_handler_field_time_interval.inc
new file mode 100644
index 000000000..e6063af17
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_time_interval.inc
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_time_interval.
+ */
+
+/**
+ * A handler to provide proper displays for time intervals.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_time_interval extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['granularity'] = array('default' => 2);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['granularity'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Granularity'),
+ '#description' => t('How many different units to display in the string.'),
+ '#default_value' => $this->options['granularity'],
+ );
+ }
+
+ function render($values) {
+ $value = $values->{$this->field_alias};
+ return format_interval($value, isset($this->options['granularity']) ? $this->options['granularity'] : 2);
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_field_url.inc b/sites/all/modules/views/handlers/views_handler_field_url.inc
new file mode 100644
index 000000000..4a76548e9
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_field_url.inc
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_url.
+ */
+
+/**
+ * Field handler to provide simple renderer that turns a URL into a clickable link.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_url extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['display_as_link'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Provide link to the page being visited.
+ */
+ function options_form(&$form, &$form_state) {
+ $form['display_as_link'] = array(
+ '#title' => t('Display as link'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['display_as_link']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ if (!empty($this->options['display_as_link'])) {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = $value;
+ $text = !empty($this->options['text']) ? $this->sanitize_value($this->options['text']) : $this->sanitize_value($value, 'url');
+ return $text;
+ }
+ else {
+ return $this->sanitize_value($value, 'url');
+ }
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter.inc b/sites/all/modules/views/handlers/views_handler_filter.inc
new file mode 100644
index 000000000..8583605bc
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter.inc
@@ -0,0 +1,1423 @@
+<?php
+
+/**
+ * @file
+ * @todo.
+ */
+
+/**
+ * @defgroup views_filter_handlers Views filter handlers
+ * @{
+ * Handlers to tell Views how to filter queries.
+ *
+ * Definition items:
+ * - allow empty: If true, the 'IS NULL' and 'IS NOT NULL' operators become
+ * available as standard operators.
+ *
+ * Object flags:
+ * You can set some specific behavior by setting up the following flags on
+ * your custom class.
+ *
+ * - always_multiple:
+ * Disable the possibility to force a single value.
+ * - no_operator:
+ * Disable the possibility to use operators.
+ * - always_required:
+ * Disable the possibility to allow a exposed input to be optional.
+ */
+
+/**
+ * Base class for filters.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter extends views_handler {
+ /**
+ * Contains the actual value of the field,either configured in the views ui
+ * or entered in the exposed filters.
+ */
+ var $value = NULL;
+
+ /**
+ * Contains the operator which is used on the query.
+ */
+ var $operator = '=';
+
+ /**
+ * Contains the information of the selected item in a gruped filter.
+ */
+ var $group_info = NULL;
+
+ /**
+ * @var bool
+ * Disable the possibility to force a single value.
+ */
+ var $always_multiple = FALSE;
+
+ /**
+ * @var bool
+ * Disable the possibility to use operators.
+ */
+ var $no_operator = FALSE;
+
+ /**
+ * @var bool
+ * Disable the possibility to allow a exposed input to be optional.
+ */
+ var $always_required = FALSE;
+
+ /**
+ * Provide some extra help to get the operator/value easier to use.
+ *
+ * This likely has to be overridden by filters which are more complex
+ * than simple operator/value.
+ */
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ $this->operator = $this->options['operator'];
+ $this->value = $this->options['value'];
+ $this->group_info = $this->options['group_info']['default_group'];
+
+ // Compatibility: The new UI changed several settings.
+ if (!empty($options['exposed']) && !empty($options['expose']['optional']) && !isset($options['expose']['required'])) {
+ $this->options['expose']['required'] = !$options['expose']['optional'];
+ }
+ if (!empty($options['exposed']) && !empty($options['expose']['single']) && !isset($options['expose']['multiple'])) {
+ $this->options['expose']['multiple'] = !$options['expose']['single'];
+ }
+ if (!empty($options['exposed']) && !empty($options['expose']['operator']) && !isset($options['expose']['operator_id'])) {
+ $this->options['expose']['operator_id'] = $options['expose']['operator_id'] = $options['expose']['operator'];
+ }
+
+ if ($this->multiple_exposed_input()) {
+ $this->group_info = NULL;
+ if (!empty($options['group_info']['default_group_multiple'])) {
+ $this->group_info = array_filter($options['group_info']['default_group_multiple']);
+ }
+
+ $this->options['expose']['multiple'] = TRUE;
+ }
+
+ // If there are relationships in the view, allow empty should be true
+ // so that we can do IS NULL checks on items. Not all filters respect
+ // allow empty, but string and numeric do and that covers enough.
+ if ($this->view->display_handler->get_option('relationships')) {
+ $this->definition['allow empty'] = TRUE;
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['operator'] = array('default' => '=');
+ $options['value'] = array('default' => '');
+ $options['group'] = array('default' => '1');
+ $options['exposed'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['expose'] = array(
+ 'contains' => array(
+ 'operator_id' => array('default' => FALSE),
+ 'label' => array('default' => '', 'translatable' => TRUE),
+ 'description' => array('default' => '', 'translatable' => TRUE),
+ 'use_operator' => array('default' => FALSE, 'bool' => TRUE),
+ 'operator_label' => array('default' => '', 'translatable' => TRUE),
+ 'operator' => array('default' => ''),
+ 'identifier' => array('default' => ''),
+ 'required' => array('default' => FALSE, 'bool' => TRUE),
+ 'remember' => array('default' => FALSE, 'bool' => TRUE),
+ 'multiple' => array('default' => FALSE, 'bool' => TRUE),
+ 'remember_roles' => array('default' => array(
+ DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID,
+ )),
+ ),
+ );
+
+ // A group is a combination of a filter, an operator and a value
+ // operating like a single filter.
+ // Users can choose from a select box which group they want to apply.
+ // Views will filter the view according to the defined values.
+ // Because it acts as a standard filter, we have to define
+ // an identifier and other settings like the widget and the label.
+ // This settings are saved in another array to allow users to switch
+ // between a normal filter and a group of filters with a single click.
+ $options['is_grouped'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['group_info'] = array(
+ 'contains' => array(
+ 'label' => array('default' => '', 'translatable' => TRUE),
+ 'description' => array('default' => '', 'translatable' => TRUE),
+ 'identifier' => array('default' => ''),
+ 'optional' => array('default' => TRUE, 'bool' => TRUE),
+ 'widget' => array('default' => 'select'),
+ 'multiple' => array('default' => FALSE, 'bool' => TRUE),
+ 'remember' => array('default' => 0),
+ 'default_group' => array('default' => 'All'),
+ 'default_group_multiple' => array('default' => array()),
+ 'group_items' => array('default' => array()),
+ ),
+ );
+
+ return $options;
+ }
+
+ /**
+ * Display the filter on the administrative summary
+ */
+ function admin_summary() {
+ return check_plain((string) $this->operator) . ' ' . check_plain((string) $this->value);
+ }
+
+ /**
+ * Determine if a filter can be exposed.
+ */
+ function can_expose() { return TRUE; }
+
+ /**
+ * Determine if a filter can be converted into a group.
+ * Only exposed filters with operators available can be converted into groups.
+ */
+ function can_build_group() {
+ return $this->is_exposed() && (count($this->operator_options()) > 0);
+ }
+
+ /**
+ * Returns TRUE if the exposed filter works like a grouped filter.
+ */
+ function is_a_group() {
+ return $this->is_exposed() && !empty($this->options['is_grouped']);
+ }
+
+ /**
+ * Provide the basic form which calls through to subforms.
+ * If overridden, it is best to call through to the parent,
+ * or to at least make sure all of the functions in this form
+ * are called.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ if ($this->can_expose()) {
+ $this->show_expose_button($form, $form_state);
+ }
+ if ($this->can_build_group()) {
+ $this->show_build_group_button($form, $form_state);
+ }
+ $form['clear_markup_start'] = array(
+ '#markup' => '<div class="clearfix">',
+ );
+ if ($this->is_a_group()) {
+ if ($this->can_build_group()) {
+ $form['clear_markup_start'] = array(
+ '#markup' => '<div class="clearfix">',
+ );
+ // Render the build group form.
+ $this->show_build_group_form($form, $form_state);
+ $form['clear_markup_end'] = array(
+ '#markup' => '</div>',
+ );
+ }
+ }
+ else {
+ // Add the subform from operator_form().
+ $this->show_operator_form($form, $form_state);
+ // Add the subform from value_form().
+ $this->show_value_form($form, $form_state);
+ $form['clear_markup_end'] = array(
+ '#markup' => '</div>',
+ );
+ if ($this->can_expose()) {
+ // Add the subform from expose_form().
+ $this->show_expose_form($form, $form_state);
+ }
+ }
+ }
+
+ /**
+ * Simple validate handler
+ */
+ function options_validate(&$form, &$form_state) {
+ $this->operator_validate($form, $form_state);
+ $this->value_validate($form, $form_state);
+ if (!empty($this->options['exposed']) && !$this->is_a_group()) {
+ $this->expose_validate($form, $form_state);
+ }
+ if ($this->is_a_group()) {
+ $this->build_group_validate($form, $form_state);
+ }
+ }
+
+ /**
+ * Simple submit handler
+ */
+ function options_submit(&$form, &$form_state) {
+ unset($form_state['values']['expose_button']); // don't store this.
+ unset($form_state['values']['group_button']); // don't store this.
+ if (!$this->is_a_group()) {
+ $this->operator_submit($form, $form_state);
+ $this->value_submit($form, $form_state);
+ }
+ if (!empty($this->options['exposed'])) {
+ $this->expose_submit($form, $form_state);
+ }
+ if ($this->is_a_group()) {
+ $this->build_group_submit($form, $form_state);
+ }
+ }
+
+ /**
+ * Shortcut to display the operator form.
+ */
+ function show_operator_form(&$form, &$form_state) {
+ $this->operator_form($form, $form_state);
+ $form['operator']['#prefix'] = '<div class="views-group-box views-left-30">';
+ $form['operator']['#suffix'] = '</div>';
+ }
+
+ /**
+ * Options form subform for setting the operator.
+ *
+ * This may be overridden by child classes, and it must
+ * define $form['operator'];
+ *
+ * @see options_form()
+ */
+ function operator_form(&$form, &$form_state) {
+ $options = $this->operator_options();
+ if (!empty($options)) {
+ $form['operator'] = array(
+ '#type' => count($options) < 10 ? 'radios' : 'select',
+ '#title' => t('Operator'),
+ '#default_value' => $this->operator,
+ '#options' => $options,
+ );
+ }
+ }
+
+ /**
+ * Provide a list of options for the default operator form.
+ * Should be overridden by classes that don't override operator_form
+ */
+ function operator_options() { return array(); }
+
+ /**
+ * Validate the operator form.
+ */
+ function operator_validate($form, &$form_state) { }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function operator_submit($form, &$form_state) { }
+
+ /**
+ * Shortcut to display the value form.
+ */
+ function show_value_form(&$form, &$form_state) {
+ $this->value_form($form, $form_state);
+ if (empty($this->no_operator)) {
+ $form['value']['#prefix'] = '<div class="views-group-box views-right-70">' . (isset($form['value']['#prefix']) ? $form['value']['#prefix'] : '');
+ $form['value']['#suffix'] = (isset($form['value']['#suffix']) ? $form['value']['#suffix'] : '') . '</div>';
+ }
+ }
+
+ /**
+ * Options form subform for setting options.
+ *
+ * This should be overridden by all child classes and it must
+ * define $form['value']
+ *
+ * @see options_form()
+ */
+ function value_form(&$form, &$form_state) { $form['value'] = array(); }
+
+ /**
+ * Validate the options form.
+ */
+ function value_validate($form, &$form_state) { }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function value_submit($form, &$form_state) { }
+
+ /**
+ * Shortcut to display the exposed options form.
+ */
+ function show_build_group_form(&$form, &$form_state) {
+ if (empty($this->options['is_grouped'])) {
+ return;
+ }
+
+ $this->build_group_form($form, $form_state);
+
+ // When we click the expose button, we add new gadgets to the form but they
+ // have no data in $_POST so their defaults get wiped out. This prevents
+ // these defaults from getting wiped out. This setting will only be TRUE
+ // during a 2nd pass rerender.
+ if (!empty($form_state['force_build_group_options'])) {
+ foreach (element_children($form['group_info']) as $id) {
+ if (isset($form['group_info'][$id]['#default_value']) && !isset($form['group_info'][$id]['#value'])) {
+ $form['group_info'][$id]['#value'] = $form['group_info'][$id]['#default_value'];
+ }
+ }
+ }
+ }
+
+ /**
+ * Shortcut to display the build_group/hide button.
+ */
+ function show_build_group_button(&$form, &$form_state) {
+
+ $form['group_button'] = array(
+ '#prefix' => '<div class="views-grouped clearfix">',
+ '#suffix' => '</div>',
+ // Should always come after the description and the relationship.
+ '#weight' => -190,
+ );
+
+ $grouped_description = t('Grouped filters allow a choice between predefined operator|value pairs.');
+ $form['group_button']['radios'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('js-only')),
+ );
+ $form['group_button']['radios']['radios'] = array(
+ '#title' => t('Filter type to expose'),
+ '#description' => $grouped_description,
+ '#type' => 'radios',
+ '#options' => array(
+ t('Single filter'),
+ t('Grouped filters'),
+ ),
+ );
+
+ if (empty($this->options['is_grouped'])) {
+ $form['group_button']['markup'] = array(
+ '#markup' => '<div class="description grouped-description">' . $grouped_description . '</div>',
+ );
+ $form['group_button']['button'] = array(
+ '#limit_validation_errors' => array(),
+ '#type' => 'submit',
+ '#value' => t('Grouped filters'),
+ '#submit' => array('views_ui_config_item_form_build_group'),
+ );
+ $form['group_button']['radios']['radios']['#default_value'] = 0;
+ }
+ else {
+ $form['group_button']['button'] = array(
+ '#limit_validation_errors' => array(),
+ '#type' => 'submit',
+ '#value' => t('Single filter'),
+ '#submit' => array('views_ui_config_item_form_build_group'),
+ );
+ $form['group_button']['radios']['radios']['#default_value'] = 1;
+ }
+ }
+ /**
+ * Shortcut to display the expose/hide button.
+ */
+ function show_expose_button(&$form, &$form_state) {
+ $form['expose_button'] = array(
+ '#prefix' => '<div class="views-expose clearfix">',
+ '#suffix' => '</div>',
+ // Should always come after the description and the relationship.
+ '#weight' => -200,
+ );
+
+ // Add a checkbox for JS users, which will have behavior attached to it
+ // so it can replace the button.
+ $form['expose_button']['checkbox'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('js-only')),
+ );
+ $form['expose_button']['checkbox']['checkbox'] = array(
+ '#title' => t('Expose this filter to visitors, to allow them to change it'),
+ '#type' => 'checkbox',
+ );
+
+ // Then add the button itself.
+ if (empty($this->options['exposed'])) {
+ $form['expose_button']['markup'] = array(
+ '#markup' => '<div class="description exposed-description">' . t('This filter is not exposed. Expose it to allow the users to change it.') . '</div>',
+ );
+ $form['expose_button']['button'] = array(
+ '#limit_validation_errors' => array(),
+ '#type' => 'submit',
+ '#value' => t('Expose filter'),
+ '#submit' => array('views_ui_config_item_form_expose'),
+ );
+ $form['expose_button']['checkbox']['checkbox']['#default_value'] = 0;
+ }
+ else {
+ $form['expose_button']['markup'] = array(
+ '#markup' => '<div class="description exposed-description">' . t('This filter is exposed. If you hide it, users will not be able to change it.') . '</div>',
+ );
+ $form['expose_button']['button'] = array(
+ '#limit_validation_errors' => array(),
+ '#type' => 'submit',
+ '#value' => t('Hide filter'),
+ '#submit' => array('views_ui_config_item_form_expose'),
+ );
+ $form['expose_button']['checkbox']['checkbox']['#default_value'] = 1;
+ }
+ }
+
+ /**
+ * Options form subform for exposed filter options.
+ *
+ * @see options_form()
+ */
+ function expose_form(&$form, &$form_state) {
+ $form['#theme'] = 'views_ui_expose_filter_form';
+ // #flatten will move everything from $form['expose'][$key] to $form[$key]
+ // prior to rendering. That's why the pre_render for it needs to run first,
+ // so that when the next pre_render (the one for fieldsets) runs, it gets
+ // the flattened data.
+ array_unshift($form['#pre_render'], 'views_ui_pre_render_flatten_data');
+ $form['expose']['#flatten'] = TRUE;
+
+ if (empty($this->always_required)) {
+ $form['expose']['required'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Required'),
+ '#default_value' => $this->options['expose']['required'],
+ );
+ }
+ else {
+ $form['expose']['required'] = array(
+ '#type' => 'value',
+ '#value' => TRUE,
+ );
+ }
+ $form['expose']['label'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['expose']['label'],
+ '#title' => t('Label'),
+ '#size' => 40,
+ );
+
+ $form['expose']['description'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['expose']['description'],
+ '#title' => t('Description'),
+ '#size' => 60,
+ );
+
+ if (!empty($form['operator']['#type'])) {
+ // Increase the width of the left (operator) column.
+ $form['operator']['#prefix'] = '<div class="views-group-box views-left-40">';
+ $form['operator']['#suffix'] = '</div>';
+ $form['value']['#prefix'] = '<div class="views-group-box views-right-60">';
+ $form['value']['#suffix'] = '</div>';
+
+ $form['expose']['use_operator'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Expose operator'),
+ '#description' => t('Allow the user to choose the operator.'),
+ '#default_value' => !empty($this->options['expose']['use_operator']),
+ );
+ $form['expose']['operator_label'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['expose']['operator_label'],
+ '#title' => t('Operator label'),
+ '#size' => 40,
+ '#description' => t('This will appear before your operator select field.'),
+ '#dependency' => array(
+ 'edit-options-expose-use-operator' => array(1)
+ ),
+ );
+ $form['expose']['operator_id'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['expose']['operator_id'],
+ '#title' => t('Operator identifier'),
+ '#size' => 40,
+ '#description' => t('This will appear in the URL after the ? to identify this operator.'),
+ '#dependency' => array(
+ 'edit-options-expose-use-operator' => array(1)
+ ),
+ '#fieldset' => 'more',
+ );
+ }
+ else {
+ $form['expose']['operator_id'] = array(
+ '#type' => 'value',
+ '#value' => '',
+ );
+ }
+
+ if (empty($this->always_multiple)) {
+ $form['expose']['multiple'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple selections'),
+ '#description' => t('Enable to allow users to select multiple items.'),
+ '#default_value' => $this->options['expose']['multiple'],
+ );
+ }
+ $form['expose']['remember'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remember the last selection'),
+ '#description' => t('Enable to remember the last selection made by the user.'),
+ '#default_value' => $this->options['expose']['remember'],
+ );
+
+ $role_options = array_map('check_plain', user_roles());
+ $form['expose']['remember_roles'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('User roles'),
+ '#description' => t('Remember exposed selection only for the selected user role(s). If you select no roles, the exposed data will never be stored.'),
+ '#default_value' => $this->options['expose']['remember_roles'],
+ '#options' => $role_options,
+ '#dependency' => array(
+ 'edit-options-expose-remember' => array(1),
+ ),
+ );
+
+ $form['expose']['identifier'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['expose']['identifier'],
+ '#title' => t('Filter identifier'),
+ '#size' => 40,
+ '#description' => t('This will appear in the URL after the ? to identify this filter. Cannot be blank.'),
+ '#fieldset' => 'more',
+ );
+ }
+
+ /**
+ * Validate the options form.
+ */
+ function expose_validate($form, &$form_state) {
+ if (empty($form_state['values']['options']['expose']['identifier'])) {
+ form_error($form['expose']['identifier'], t('The identifier is required if the filter is exposed.'));
+ }
+
+ if (!empty($form_state['values']['options']['expose']['identifier']) && $form_state['values']['options']['expose']['identifier'] == 'value') {
+ form_error($form['expose']['identifier'], t('This identifier is not allowed.'));
+ }
+
+ if (!$this->view->display_handler->is_identifier_unique($form_state['id'], $form_state['values']['options']['expose']['identifier'])) {
+ form_error($form['expose']['identifier'], t('This identifier is used by another handler.'));
+ }
+ }
+
+ /**
+ * Validate the build group options form.
+ */
+ function build_group_validate($form, &$form_state) {
+ if (!empty($form_state['values']['options']['group_info'])) {
+ if (empty($form_state['values']['options']['group_info']['identifier'])) {
+ form_error($form['group_info']['identifier'], t('The identifier is required if the filter is exposed.'));
+ }
+
+ if (!empty($form_state['values']['options']['group_info']['identifier']) && $form_state['values']['options']['group_info']['identifier'] == 'value') {
+ form_error($form['group_info']['identifier'], t('This identifier is not allowed.'));
+ }
+
+ if (!$this->view->display_handler->is_identifier_unique($form_state['id'], $form_state['values']['options']['group_info']['identifier'])) {
+ form_error($form['group_info']['identifier'], t('This identifier is used by another handler.'));
+ }
+ }
+
+ if (!empty($form_state['values']['options']['group_info']['group_items'])) {
+ foreach ($form_state['values']['options']['group_info']['group_items'] as $id => $group) {
+ if (empty($group['remove'])) {
+
+ // Check if the title is defined but value wasn't defined.
+ if (!empty($group['title'])) {
+ if ((!is_array($group['value']) && trim($group['value']) == "") ||
+ (is_array($group['value']) && count(array_filter($group['value'], '_views_array_filter_zero')) == 0)) {
+ form_error($form['group_info']['group_items'][$id]['value'],
+ t('The value is required if title for this item is defined.'));
+ }
+ }
+
+ // Check if the value is defined but title wasn't defined.
+ if ((!is_array($group['value']) && trim($group['value']) != "") ||
+ (is_array($group['value']) && count(array_filter($group['value'], '_views_array_filter_zero')) > 0)) {
+ if (empty($group['title'])) {
+ form_error($form['group_info']['group_items'][$id]['title'],
+ t('The title is required if value for this item is defined.'));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Save new group items, re-enumerates and remove groups marked to delete.
+ */
+ function build_group_submit($form, &$form_state) {
+ $groups = array();
+ uasort($form_state['values']['options']['group_info']['group_items'], 'drupal_sort_weight');
+ // Filter out removed items.
+
+ // Start from 1 to avoid problems with #default_value in the widget.
+ $new_id = 1;
+ $new_default = 'All';
+ foreach ($form_state['values']['options']['group_info']['group_items'] as $id => $group) {
+ if (empty($group['remove'])) {
+ // Don't store this.
+ unset($group['remove']);
+ unset($group['weight']);
+ $groups[$new_id] = $group;
+
+ if ($form_state['values']['options']['group_info']['default_group'] === $id) {
+ $new_default = $new_id;
+ }
+ }
+ $new_id++;
+ }
+ if ($new_default != 'All') {
+ $form_state['values']['options']['group_info']['default_group'] = $new_default;
+ }
+ $filter_default_multiple = array_filter($form_state['values']['options']['group_info']['default_group_multiple']);
+ $form_state['values']['options']['group_info']['default_group_multiple'] = $filter_default_multiple;
+
+ $form_state['values']['options']['group_info']['group_items'] = $groups;
+ }
+
+ /**
+ * Provide default options for exposed filters.
+ */
+ function expose_options() {
+ $this->options['expose'] = array(
+ 'use_operator' => FALSE,
+ 'operator' => $this->options['id'] . '_op',
+ 'identifier' => $this->options['id'],
+ 'label' => $this->definition['title'],
+ 'description' => NULL,
+ 'remember' => FALSE,
+ 'multiple' => FALSE,
+ 'required' => FALSE,
+ );
+ }
+
+ /**
+ * Provide default options for exposed filters.
+ */
+ function build_group_options() {
+ $this->options['group_info'] = array(
+ 'label' => $this->definition['title'],
+ 'description' => NULL,
+ 'identifier' => $this->options['id'],
+ 'optional' => TRUE,
+ 'widget' => 'select',
+ 'multiple' => FALSE,
+ 'remember' => FALSE,
+ 'default_group' => 'All',
+ 'default_group_multiple' => array(),
+ 'group_items' => array(),
+ );
+ }
+
+ /**
+ * Build a form containing a group of operator | values to apply as a
+ * single filter.
+ */
+ function group_form(&$form, &$form_state) {
+ if (!empty($this->options['group_info']['optional']) && !$this->multiple_exposed_input()) {
+
+ $old_any = $this->options['group_info']['widget'] == 'select' ? '<Any>' : '&lt;Any&gt;';
+ $any_label = variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? $old_any : t('- Any -');
+ $groups = array('All' => $any_label);
+ }
+ foreach ($this->options['group_info']['group_items'] as $id => $group) {
+ if (!empty($group['title'])) {
+ $groups[$id] = $id != 'All' ? t($group['title']) : $group['title'];
+ }
+ }
+
+ if (count($groups)) {
+ $value = $this->options['group_info']['identifier'];
+
+ $form[$value] = array(
+ '#type' => $this->options['group_info']['widget'],
+ '#default_value' => $this->group_info,
+ '#options' => $groups,
+ );
+ if (!empty($this->options['group_info']['multiple'])) {
+ if (count($groups) < 5) {
+ $form[$value]['#type'] = 'checkboxes';
+ }
+ else {
+ $form[$value]['#type'] = 'select';
+ $form[$value]['#size'] = 5;
+ $form[$value]['#multiple'] = TRUE;
+ }
+ unset($form[$value]['#default_value']);
+ if (empty($form_state['input'])) {
+ $form_state['input'][$value] = $this->group_info;
+ }
+ }
+
+ $this->options['expose']['label'] = '';
+ }
+ }
+
+
+ /**
+ * Render our chunk of the exposed filter form when selecting
+ *
+ * You can override this if it doesn't do what you expect.
+ */
+ function exposed_form(&$form, &$form_state) {
+ if (empty($this->options['exposed'])) {
+ return;
+ }
+
+ // Build the exposed form, when its based on an operator.
+ if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id'])) {
+ $operator = $this->options['expose']['operator_id'];
+ $this->operator_form($form, $form_state);
+ $form[$operator] = $form['operator'];
+ $form[$operator]['#title'] = $this->options['expose']['operator_label'];
+ $form[$operator]['#title_display'] = 'invisible';
+
+ $this->exposed_translate($form[$operator], 'operator');
+
+ unset($form['operator']);
+ }
+
+ // Build the form and set the value based on the identifier.
+ if (!empty($this->options['expose']['identifier'])) {
+ $value = $this->options['expose']['identifier'];
+ $this->value_form($form, $form_state);
+ $form[$value] = $form['value'];
+
+ if (isset($form[$value]['#title']) && !empty($form[$value]['#type']) && $form[$value]['#type'] != 'checkbox') {
+ unset($form[$value]['#title']);
+ }
+
+ $this->exposed_translate($form[$value], 'value');
+
+ if (!empty($form['#type']) && ($form['#type'] == 'checkboxes' || ($form['#type'] == 'select' && !empty($form['#multiple'])))) {
+ unset($form[$value]['#default_value']);
+ }
+
+ if (!empty($form['#type']) && $form['#type'] == 'select' && empty($form['#multiple'])) {
+ $form[$value]['#default_value'] = 'All';
+ }
+
+ if ($value != 'value') {
+ unset($form['value']);
+ }
+ }
+ }
+
+ /**
+ * Build the form to let users create the group of exposed filters.
+ * This form is displayed when users click on button 'Build group'
+ */
+ function build_group_form(&$form, &$form_state) {
+ if (empty($this->options['exposed']) || empty($this->options['is_grouped'])) {
+ return;
+ }
+ $form['#theme'] = 'views_ui_build_group_filter_form';
+
+ // #flatten will move everything from $form['group_info'][$key] to $form[$key]
+ // prior to rendering. That's why the pre_render for it needs to run first,
+ // so that when the next pre_render (the one for fieldsets) runs, it gets
+ // the flattened data.
+ array_unshift($form['#pre_render'], 'views_ui_pre_render_flatten_data');
+ $form['group_info']['#flatten'] = TRUE;
+
+ if (!empty($this->options['group_info']['identifier'])) {
+ $identifier = $this->options['group_info']['identifier'];
+ }
+ else {
+ $identifier = 'group_' . $this->options['expose']['identifier'];
+ }
+ $form['group_info']['identifier'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $identifier,
+ '#title' => t('Filter identifier'),
+ '#size' => 40,
+ '#description' => t('This will appear in the URL after the ? to identify this filter. Cannot be blank.'),
+ '#fieldset' => 'more',
+ );
+ $form['group_info']['label'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['group_info']['label'],
+ '#title' => t('Label'),
+ '#size' => 40,
+ );
+
+ $form['group_info']['optional'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Optional'),
+ '#description' => t('This exposed filter is optional and will have added options to allow it not to be set.'),
+ '#default_value' => $this->options['group_info']['optional'],
+ );
+ $form['group_info']['multiple'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple selections'),
+ '#description' => t('Enable to allow users to select multiple items.'),
+ '#default_value' => $this->options['group_info']['multiple'],
+ );
+ $form['group_info']['widget'] = array(
+ '#type' => 'radios',
+ '#default_value' => $this->options['group_info']['widget'],
+ '#title' => t('Widget type'),
+ '#options' => array(
+ 'radios' => t('Radios'),
+ 'select' => t('Select'),
+ ),
+ '#description' => t('Select which kind of widget will be used to render the group of filters'),
+ );
+ $form['group_info']['remember'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remember'),
+ '#description' => t('Remember the last setting the user gave this filter.'),
+ '#default_value' => $this->options['group_info']['remember'],
+ );
+
+ if (!empty($this->options['group_info']['identifier'])) {
+ $identifier = $this->options['group_info']['identifier'];
+ }
+ else {
+ $identifier = 'group_' . $this->options['expose']['identifier'];
+ }
+ $form['group_info']['identifier'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $identifier,
+ '#title' => t('Filter identifier'),
+ '#size' => 40,
+ '#description' => t('This will appear in the URL after the ? to identify this filter. Cannot be blank.'),
+ '#fieldset' => 'more',
+ );
+ $form['group_info']['label'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['group_info']['label'],
+ '#title' => t('Label'),
+ '#size' => 40,
+ );
+ $form['group_info']['description'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['group_info']['description'],
+ '#title' => t('Description'),
+ '#size' => 60,
+ );
+ $form['group_info']['optional'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Optional'),
+ '#description' => t('This exposed filter is optional and will have added options to allow it not to be set.'),
+ '#default_value' => $this->options['group_info']['optional'],
+ );
+ $form['group_info']['widget'] = array(
+ '#type' => 'radios',
+ '#default_value' => $this->options['group_info']['widget'],
+ '#title' => t('Widget type'),
+ '#options' => array(
+ 'radios' => t('Radios'),
+ 'select' => t('Select'),
+ ),
+ '#description' => t('Select which kind of widget will be used to render the group of filters'),
+ );
+ $form['group_info']['remember'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remember'),
+ '#description' => t('Remember the last setting the user gave this filter.'),
+ '#default_value' => $this->options['group_info']['remember'],
+ );
+
+ $groups = array('All' => '- Any -'); // The string '- Any -' will not be rendered see @theme_views_ui_build_group_filter_form
+
+ // Provide 3 options to start when we are in a new group.
+ if (count($this->options['group_info']['group_items']) == 0) {
+ $this->options['group_info']['group_items'] = array_fill(1, 3, array());
+ }
+
+ // After the general settings, comes a table with all the existent groups.
+ $default_weight = 0;
+ foreach ($this->options['group_info']['group_items'] as $item_id => $item) {
+ if (!empty($form_state['values']['options']['group_info']['group_items'][$item_id]['remove'])) {
+ continue;
+ }
+ // Each rows contains three widgets:
+ // a) The title, where users define how they identify a pair of operator | value
+ // b) The operator
+ // c) The value (or values) to use in the filter with the selected operator
+
+ // In each row, we have to display the operator form and the value from
+ // $row acts as a fake form to render each widget in a row.
+ $row = array();
+ $groups[$item_id] = '';
+ $this->operator_form($row, $form_state);
+ // Force the operator form to be a select box. Some handlers uses
+ // radios and they occupy a lot of space in a table row.
+ $row['operator']['#type'] = 'select';
+ $row['operator']['#title'] = '';
+ $this->value_form($row, $form_state);
+
+ // Fix the dependencies to update value forms when operators
+ // changes. This is needed because forms are inside a new form and
+ // their ids changes. Dependencies are used when operator changes
+ // from to 'Between', 'Not Between', etc, and two or more widgets
+ // are displayed.
+ $without_children = TRUE;
+ foreach (element_children($row['value']) as $children) {
+ if (isset($row['value'][$children]['#dependency']['edit-options-operator'])) {
+ $row['value'][$children]['#dependency']["edit-options-group-info-group-items-$item_id-operator"] = $row['value'][$children]['#dependency']['edit-options-operator'];
+ unset($row['value'][$children]['#dependency']['edit-options-operator']);
+ $row['value'][$children]['#title'] = '';
+
+ if (!empty($this->options['group_info']['group_items'][$item_id]['value'][$children])) {
+ $row['value'][$children]['#default_value'] = $this->options['group_info']['group_items'][$item_id]['value'][$children];
+ }
+ }
+ $without_children = FALSE;
+ }
+
+ if ($without_children) {
+ if (!empty($this->options['group_info']['group_items'][$item_id]['value'])) {
+ $row['value']['#default_value'] = $this->options['group_info']['group_items'][$item_id]['value'];
+ }
+ }
+
+ if (!empty($this->options['group_info']['group_items'][$item_id]['operator'])) {
+ $row['operator']['#default_value'] = $this->options['group_info']['group_items'][$item_id]['operator'];
+ }
+
+ $default_title = '';
+ if (!empty($this->options['group_info']['group_items'][$item_id]['title'])) {
+ $default_title = $this->options['group_info']['group_items'][$item_id]['title'];
+ }
+
+ // Per item group, we have a title that identifies it.
+ $form['group_info']['group_items'][$item_id] = array(
+ 'title' => array(
+ '#type' => 'textfield',
+ '#size' => 20,
+ '#default_value' => $default_title,
+ ),
+ 'operator' => $row['operator'],
+ 'value' => $row['value'],
+ 'remove' => array(
+ '#type' => 'checkbox',
+ '#id' => 'views-removed-' . $item_id,
+ '#attributes' => array('class' => array('views-remove-checkbox')),
+ '#default_value' => 0,
+ ),
+ 'weight' => array(
+ '#type' => 'weight',
+ '#delta' => 10,
+ '#default_value' => $default_weight++,
+ '#attributes' => array('class' => array('weight')),
+ ),
+ );
+ }
+ // From all groups, let chose which is the default.
+ $form['group_info']['default_group'] = array(
+ '#type' => 'radios',
+ '#options' => $groups,
+ '#default_value' => $this->options['group_info']['default_group'],
+ '#required' => TRUE,
+ '#attributes' => array(
+ 'class' => array('default-radios'),
+ )
+ );
+ // From all groups, let chose which is the default.
+ $form['group_info']['default_group_multiple'] = array(
+ '#type' => 'checkboxes',
+ '#options' => $groups,
+ '#default_value' => $this->options['group_info']['default_group_multiple'],
+ '#attributes' => array(
+ 'class' => array('default-checkboxes'),
+ )
+ );
+
+ $form['group_info']['add_group'] = array(
+ '#prefix' => '<div class="views-build-group clear-block">',
+ '#suffix' => '</div>',
+ '#type' => 'submit',
+ '#value' => t('Add another item'),
+ '#submit' => array('views_ui_config_item_form_add_group'),
+ );
+
+ $js = array();
+ $js['tableDrag']['views-filter-groups']['weight'][0] = array(
+ 'target' => 'weight',
+ 'source' => NULL,
+ 'relationship' => 'sibling',
+ 'action' => 'order',
+ 'hidden' => TRUE,
+ 'limit' => 0,
+ );
+ if (!empty($form_state['js settings']) && is_array($js)) {
+ $form_state['js settings'] = array_merge($form_state['js settings'], $js);
+ }
+ else {
+ $form_state['js settings'] = $js;
+ }
+ }
+
+
+ /**
+ * Make some translations to a form item to make it more suitable to
+ * exposing.
+ */
+ function exposed_translate(&$form, $type) {
+ if (!isset($form['#type'])) {
+ return;
+ }
+
+ if ($form['#type'] == 'radios') {
+ $form['#type'] = 'select';
+ }
+ // Checkboxes don't work so well in exposed forms due to GET conversions.
+ if ($form['#type'] == 'checkboxes') {
+ if (empty($form['#no_convert']) || empty($this->options['expose']['multiple'])) {
+ $form['#type'] = 'select';
+ }
+ if (!empty($this->options['expose']['multiple'])) {
+ $form['#multiple'] = TRUE;
+ }
+ }
+ if (empty($this->options['expose']['multiple']) && isset($form['#multiple'])) {
+ unset($form['#multiple']);
+ $form['#size'] = NULL;
+ }
+
+ // Cleanup in case the translated element's (radios or checkboxes) display value contains html.
+ if ($form['#type'] == 'select') {
+ $this->prepare_filter_select_options($form['#options']);
+ }
+
+ if ($type == 'value' && empty($this->always_required) && empty($this->options['expose']['required']) && $form['#type'] == 'select' && empty($form['#multiple'])) {
+ $any_label = variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? t('<Any>') : t('- Any -');
+ $form['#options'] = array('All' => $any_label) + $form['#options'];
+ $form['#default_value'] = 'All';
+ }
+
+ if (!empty($this->options['expose']['required'])) {
+ $form['#required'] = TRUE;
+ }
+ }
+
+
+
+ /**
+ * Sanitizes the HTML select element's options.
+ *
+ * The function is recursive to support optgroups.
+ */
+ function prepare_filter_select_options(&$options) {
+ foreach ($options as $value => $label) {
+ // Recurse for optgroups.
+ if (is_array($label)) {
+ $this->prepare_filter_select_options($options[$value]);
+ }
+ // FAPI has some special value to allow hierarchy.
+ // @see _form_options_flatten
+ elseif (is_object($label)) {
+ $this->prepare_filter_select_options($options[$value]->option);
+ }
+ else {
+ $options[$value] = strip_tags(decode_entities($label));
+ }
+ }
+ }
+
+ /**
+ * Tell the renderer about our exposed form. This only needs to be
+ * overridden for particularly complex forms. And maybe not even then.
+ *
+ * @return array|null
+ * For standard exposed filters. An array with the following keys:
+ * - operator: The $form key of the operator. Set to NULL if no operator.
+ * - value: The $form key of the value. Set to NULL if no value.
+ * - label: The label to use for this piece.
+ * For grouped exposed filters. An array with the following keys:
+ * - value: The $form key of the value. Set to NULL if no value.
+ * - label: The label to use for this piece.
+ */
+ function exposed_info() {
+ if (empty($this->options['exposed'])) {
+ return;
+ }
+
+ if ($this->is_a_group()) {
+ return array(
+ 'value' => $this->options['group_info']['identifier'],
+ 'label' => $this->options['group_info']['label'],
+ 'description' => $this->options['group_info']['description'],
+ );
+ }
+
+ return array(
+ 'operator' => $this->options['expose']['operator_id'],
+ 'value' => $this->options['expose']['identifier'],
+ 'label' => $this->options['expose']['label'],
+ 'description' => $this->options['expose']['description'],
+ );
+ }
+
+ /*
+ * Transform the input from a grouped filter into a standard filter.
+ *
+ * When a filter is a group, find the set of operator and values
+ * that the choosed item represents, and inform views that a normal
+ * filter was submitted by telling the operator and the value selected.
+ *
+ * The param $selected_group_id is only passed when the filter uses the
+ * checkboxes widget, and this function will be called for each item
+ * choosed in the checkboxes.
+ */
+ function convert_exposed_input(&$input, $selected_group_id = NULL) {
+ if ($this->is_a_group()) {
+ // If it is already defined the selected group, use it. Only valid
+ // when the filter uses checkboxes for widget.
+ if (!empty($selected_group_id)) {
+ $selected_group = $selected_group_id;
+ }
+ else {
+ $selected_group = $input[$this->options['group_info']['identifier']];
+ }
+ if ($selected_group == 'All' && !empty($this->options['group_info']['optional'])) {
+ return NULL;
+ }
+ if ($selected_group != 'All' && empty($this->options['group_info']['group_items'][$selected_group])) {
+ return FALSE;
+ }
+ if (isset($selected_group) && isset($this->options['group_info']['group_items'][$selected_group])) {
+ $input[$this->options['expose']['operator']] = $this->options['group_info']['group_items'][$selected_group]['operator'];
+
+ // Value can be optional, For example for 'empty' and 'not empty' filters.
+ if (!empty($this->options['group_info']['group_items'][$selected_group]['value'])) {
+ $input[$this->options['expose']['identifier']] = $this->options['group_info']['group_items'][$selected_group]['value'];
+ }
+ $this->options['expose']['use_operator'] = TRUE;
+
+ $this->group_info = $input[$this->options['group_info']['identifier']];
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+ }
+ }
+
+ /**
+ * Returns the options available for a grouped filter that users checkboxes
+ * as widget, and therefore has to be applied several times, one per
+ * item selected.
+ */
+ function group_multiple_exposed_input(&$input) {
+ if (!empty($input[$this->options['group_info']['identifier']])) {
+ return array_filter($input[$this->options['group_info']['identifier']]);
+ }
+ return array();
+ }
+
+ /**
+ * Returns TRUE if users can select multiple groups items of a
+ * grouped exposed filter.
+ */
+ function multiple_exposed_input() {
+ return $this->is_a_group() && !empty($this->options['group_info']['multiple']);
+ }
+
+ /**
+ * If set to remember exposed input in the session, store it there.
+ * This function is similar to store_exposed_input but modified to
+ * work properly when the filter is a group.
+ */
+ function store_group_input($input, $status) {
+ if (!$this->is_a_group() || empty($this->options['group_info']['identifier'])) {
+ return TRUE;
+ }
+
+ if (empty($this->options['group_info']['remember'])) {
+ return;
+ }
+
+ // Figure out which display id is responsible for the filters, so we
+ // know where to look for session stored values.
+ $display_id = ($this->view->display_handler->is_defaulted('filters')) ? 'default' : $this->view->current_display;
+
+ // false means that we got a setting that means to recuse ourselves,
+ // so we should erase whatever happened to be there.
+ if ($status === FALSE && isset($_SESSION['views'][$this->view->name][$display_id])) {
+ $session = &$_SESSION['views'][$this->view->name][$display_id];
+
+ if (isset($session[$this->options['group_info']['identifier']])) {
+ unset($session[$this->options['group_info']['identifier']]);
+ }
+ }
+
+ if ($status !== FALSE) {
+ if (!isset($_SESSION['views'][$this->view->name][$display_id])) {
+ $_SESSION['views'][$this->view->name][$display_id] = array();
+ }
+
+ $session = &$_SESSION['views'][$this->view->name][$display_id];
+
+ $session[$this->options['group_info']['identifier']] = $input[$this->options['group_info']['identifier']];
+ }
+ }
+
+ /**
+ * Check to see if input from the exposed filters should change
+ * the behavior of this filter.
+ */
+ function accept_exposed_input($input) {
+ if (empty($this->options['exposed'])) {
+ return TRUE;
+ }
+
+
+ if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
+ $this->operator = $input[$this->options['expose']['operator_id']];
+ }
+
+ if (!empty($this->options['expose']['identifier'])) {
+ $value = $input[$this->options['expose']['identifier']];
+
+ // Various ways to check for the absence of non-required input.
+ if (empty($this->options['expose']['required'])) {
+ if (($this->operator == 'empty' || $this->operator == 'not empty') && $value === '') {
+ $value = ' ';
+ }
+
+ if ($this->operator != 'empty' && $this->operator != 'not empty') {
+ if ($value == 'All' || $value === array()) {
+ return FALSE;
+ }
+ }
+
+ if (!empty($this->always_multiple) && $value === '') {
+ return FALSE;
+ }
+ }
+
+
+ if (isset($value)) {
+ $this->value = $value;
+ if (empty($this->always_multiple) && empty($this->options['expose']['multiple'])) {
+ $this->value = array($value);
+ }
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ function store_exposed_input($input, $status) {
+ if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
+ return TRUE;
+ }
+
+ if (empty($this->options['expose']['remember'])) {
+ return;
+ }
+
+ // Check if we store exposed value for current user.
+ global $user;
+ $allowed_rids = empty($this->options['expose']['remember_roles']) ? array() : array_filter($this->options['expose']['remember_roles']);
+ $intersect_rids = array_intersect_key($allowed_rids, $user->roles);
+ if (empty($intersect_rids)) {
+ return;
+ }
+
+ // Figure out which display id is responsible for the filters, so we
+ // know where to look for session stored values.
+ $display_id = ($this->view->display_handler->is_defaulted('filters')) ? 'default' : $this->view->current_display;
+
+ // shortcut test.
+ $operator = !empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']);
+
+ // false means that we got a setting that means to recuse ourselves,
+ // so we should erase whatever happened to be there.
+ if (!$status && isset($_SESSION['views'][$this->view->name][$display_id])) {
+ $session = &$_SESSION['views'][$this->view->name][$display_id];
+ if ($operator && isset($session[$this->options['expose']['operator_id']])) {
+ unset($session[$this->options['expose']['operator_id']]);
+ }
+
+ if (isset($session[$this->options['expose']['identifier']])) {
+ unset($session[$this->options['expose']['identifier']]);
+ }
+ }
+
+ if ($status) {
+ if (!isset($_SESSION['views'][$this->view->name][$display_id])) {
+ $_SESSION['views'][$this->view->name][$display_id] = array();
+ }
+
+ $session = &$_SESSION['views'][$this->view->name][$display_id];
+
+ if ($operator && isset($input[$this->options['expose']['operator_id']])) {
+ $session[$this->options['expose']['operator_id']] = $input[$this->options['expose']['operator_id']];
+ }
+
+ $session[$this->options['expose']['identifier']] = $input[$this->options['expose']['identifier']];
+ }
+ }
+
+ /**
+ * Add this filter to the query.
+ *
+ * Due to the nature of fapi, the value and the operator have an unintended
+ * level of indirection. You will find them in $this->operator
+ * and $this->value respectively.
+ */
+ function query() {
+ $this->ensure_my_table();
+ $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", $this->value, $this->operator);
+ }
+
+ /**
+ * Can this filter be used in OR groups?
+ *
+ * Some filters have complicated where clauses that cannot be easily used
+ * with OR groups. Some filters must also use HAVING which also makes
+ * them not groupable. These filters will end up in a special group
+ * if OR grouping is in use.
+ *
+ * @return bool
+ */
+ function can_group() {
+ return TRUE;
+ }
+}
+
+
+/**
+ * A special handler to take the place of missing or broken handlers.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_broken extends views_handler_filter {
+ function ui_name($short = FALSE) {
+ return t('Broken/missing handler');
+ }
+
+ function ensure_my_table() { /* No table to ensure! */ }
+ function query($group_by = FALSE) { /* No query to run */ }
+ function options_form(&$form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => '<div class="form-item description">' . t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.') . '</div>',
+ );
+ }
+
+ /**
+ * Determine if the handler is considered 'broken'
+ */
+ function broken() { return TRUE; }
+}
+
+/**
+ * Filter by no empty values, though allow to use "0".
+ * @param $var
+ * @return bool
+ */
+function _views_array_filter_zero($var) {
+ return trim($var) != "";
+}
+
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/handlers/views_handler_filter_boolean_operator.inc b/sites/all/modules/views/handlers/views_handler_filter_boolean_operator.inc
new file mode 100644
index 000000000..56365e19d
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_boolean_operator.inc
@@ -0,0 +1,179 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_boolean_operator.
+ */
+
+/**
+ * Simple filter to handle matching of boolean values
+ *
+ * Definition items:
+ * - label: (REQUIRED) The label for the checkbox.
+ * - type: For basic 'true false' types, an item can specify the following:
+ * - true-false: True/false (this is the default)
+ * - yes-no: Yes/No
+ * - on-off: On/Off
+ * - enabled-disabled: Enabled/Disabled
+ * - accept null: Treat a NULL value as false.
+ * - use equal: If you use this flag the query will use = 1 instead of <> 0.
+ * This might be helpful for performance reasons.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_boolean_operator extends views_handler_filter {
+ // exposed filter options
+ var $always_multiple = TRUE;
+ // Don't display empty space where the operator would be.
+ var $no_operator = TRUE;
+ // Whether to accept NULL as a false value or not
+ var $accept_null = FALSE;
+
+ function construct() {
+ $this->value_value = t('True');
+ if (isset($this->definition['label'])) {
+ $this->value_value = $this->definition['label'];
+ }
+ if (isset($this->definition['accept null'])) {
+ $this->accept_null = (bool) $this->definition['accept null'];
+ }
+ else if (isset($this->definition['accept_null'])) {
+ $this->accept_null = (bool) $this->definition['accept_null'];
+ }
+ $this->value_options = NULL;
+ parent::construct();
+ }
+
+ /**
+ * Return the possible options for this filter.
+ *
+ * Child classes should override this function to set the possible values
+ * for the filter. Since this is a boolean filter, the array should have
+ * two possible keys: 1 for "True" and 0 for "False", although the labels
+ * can be whatever makes sense for the filter. These values are used for
+ * configuring the filter, when the filter is exposed, and in the admin
+ * summary of the filter. Normally, this should be static data, but if it's
+ * dynamic for some reason, child classes should use a guard to reduce
+ * database hits as much as possible.
+ */
+ function get_value_options() {
+ if (isset($this->definition['type'])) {
+ if ($this->definition['type'] == 'yes-no') {
+ $this->value_options = array(1 => t('Yes'), 0 => t('No'));
+ }
+ if ($this->definition['type'] == 'on-off') {
+ $this->value_options = array(1 => t('On'), 0 => t('Off'));
+ }
+ if ($this->definition['type'] == 'enabled-disabled') {
+ $this->value_options = array(1 => t('Enabled'), 0 => t('Disabled'));
+ }
+ }
+
+ // Provide a fallback if the above didn't set anything.
+ if (!isset($this->value_options)) {
+ $this->value_options = array(1 => t('True'), 0 => t('False'));
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['value']['default'] = FALSE;
+
+ return $options;
+ }
+
+ function operator_form(&$form, &$form_state) {
+ $form['operator'] = array();
+ }
+
+ function value_form(&$form, &$form_state) {
+ if (empty($this->value_options)) {
+ // Initialize the array of possible values for this filter.
+ $this->get_value_options();
+ }
+ if (!empty($form_state['exposed'])) {
+ // Exposed filter: use a select box to save space.
+ $filter_form_type = 'select';
+ }
+ else {
+ // Configuring a filter: use radios for clarity.
+ $filter_form_type = 'radios';
+ }
+ $form['value'] = array(
+ '#type' => $filter_form_type,
+ '#title' => $this->value_value,
+ '#options' => $this->value_options,
+ '#default_value' => $this->value,
+ );
+ if (!empty($this->options['exposed'])) {
+ $identifier = $this->options['expose']['identifier'];
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) {
+ $form_state['input'][$identifier] = $this->value;
+ }
+ // If we're configuring an exposed filter, add an <Any> option.
+ if (empty($form_state['exposed']) || empty($this->options['expose']['required'])) {
+ $any_label = variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? '<Any>' : t('- Any -');
+ if ($form['value']['#type'] != 'select') {
+ $any_label = check_plain($any_label);
+ }
+ $form['value']['#options'] = array('All' => $any_label) + $form['value']['#options'];
+ }
+ }
+ }
+
+ function value_validate($form, &$form_state) {
+ if ($form_state['values']['options']['value'] == 'All' && !empty($form_state['values']['options']['expose']['required'])) {
+ form_set_error('value', t('You must select a value unless this is an non-required exposed filter.'));
+ }
+ }
+
+ function admin_summary() {
+ if ($this->is_a_group()) {
+ return t('grouped');
+ }
+ if (!empty($this->options['exposed'])) {
+ return t('exposed');
+ }
+ if (empty($this->value_options)) {
+ $this->get_value_options();
+ }
+ // Now that we have the valid options for this filter, just return the
+ // human-readable label based on the current value. The value_options
+ // array is keyed with either 0 or 1, so if the current value is not
+ // empty, use the label for 1, and if it's empty, use the label for 0.
+ return $this->value_options[!empty($this->value)];
+ }
+
+ function expose_options() {
+ parent::expose_options();
+ $this->options['expose']['operator_id'] = '';
+ $this->options['expose']['label'] = $this->value_value;
+ $this->options['expose']['required'] = TRUE;
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $field = "$this->table_alias.$this->real_field";
+
+ if (empty($this->value)) {
+ if ($this->accept_null) {
+ $or = db_or()
+ ->condition($field, 0, '=')
+ ->condition($field, NULL, 'IS NULL');
+ $this->query->add_where($this->options['group'], $or);
+ }
+ else {
+ $this->query->add_where($this->options['group'], $field, 0, '=');
+ }
+ }
+ else {
+ if (!empty($this->definition['use equal'])) {
+ $this->query->add_where($this->options['group'], $field, 1, '=');
+ }
+ else {
+ $this->query->add_where($this->options['group'], $field, 0, '<>');
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_boolean_operator_string.inc b/sites/all/modules/views/handlers/views_handler_filter_boolean_operator_string.inc
new file mode 100644
index 000000000..080ac50a2
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_boolean_operator_string.inc
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_boolean_operator_string.
+ */
+
+/**
+ * Simple filter to handle matching of boolean values.
+ *
+ * This handler checks to see if a string field is empty (equal to '') or not.
+ * It is otherwise identical to the parent operator.
+ *
+ * Definition items:
+ * - label: (REQUIRED) The label for the checkbox.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_boolean_operator_string extends views_handler_filter_boolean_operator {
+ function query() {
+ $this->ensure_my_table();
+ $where = "$this->table_alias.$this->real_field ";
+
+ if (empty($this->value)) {
+ $where .= "= ''";
+ if ($this->accept_null) {
+ $where = '(' . $where . " OR $this->table_alias.$this->real_field IS NULL)";
+ }
+ }
+ else {
+ $where .= "<> ''";
+ }
+ $this->query->add_where_expression($this->options['group'], $where);
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_combine.inc b/sites/all/modules/views/handlers/views_handler_filter_combine.inc
new file mode 100644
index 000000000..c1afcf5c9
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_combine.inc
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_combine.
+ */
+
+/**
+ * Filter handler which allows to search on multiple fields.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_filter_combine extends views_handler_filter_string {
+ /**
+ * @var views_plugin_query_default
+ */
+ public $query;
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['fields'] = array('default' => array());
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $this->view->init_style();
+
+ // Allow to choose all fields as possible.
+ if ($this->view->style_plugin->uses_fields()) {
+ $options = array();
+ foreach ($this->view->display_handler->get_handlers('field') as $name => $field) {
+ $options[$name] = $field->ui_name(TRUE);
+ }
+ if ($options) {
+ $form['fields'] = array(
+ '#type' => 'select',
+ '#title' => t('Choose fields to combine for filtering'),
+ '#description' => t("This filter doesn't work for very special field handlers."),
+ '#multiple' => TRUE,
+ '#options' => $options,
+ '#default_value' => $this->options['fields'],
+ );
+ }
+ else {
+ form_set_error('', t('You have to add some fields to be able to use this filter.'));
+ }
+ }
+ }
+
+ function query() {
+ $this->view->_build('field');
+ $fields = array();
+ // Only add the fields if they have a proper field and table alias.
+ foreach ($this->options['fields'] as $id) {
+ // Field access checks may have removed this handler.
+ if (!isset($this->view->field[$id])) {
+ continue;
+ }
+
+ $field = $this->view->field[$id];
+ // Always add the table of the selected fields to be sure a table alias
+ // exists.
+ $field->ensure_my_table();
+ if (!empty($field->field_alias) && !empty($field->field_alias)) {
+ $fields[] = "$field->table_alias.$field->real_field";
+ }
+ }
+ if ($fields) {
+ $count = count($fields);
+ $separated_fields = array();
+ foreach ($fields as $key => $field) {
+ $separated_fields[] = $field;
+ if ($key < $count - 1) {
+ $separated_fields[] = "' '";
+ }
+ }
+ $expression = implode(', ', $separated_fields);
+ $expression = "CONCAT_WS(' ', $expression)";
+
+ $info = $this->operators();
+ if (!empty($info[$this->operator]['method'])) {
+ $this->{$info[$this->operator]['method']}($expression);
+ }
+ }
+ }
+
+ // By default things like op_equal uses add_where, that doesn't support
+ // complex expressions, so override all operators.
+ function op_equal($field) {
+ $placeholder = $this->placeholder();
+ $operator = $this->operator();
+ $this->query->add_where_expression($this->options['group'], "$field $operator $placeholder", array($placeholder => $this->value));
+ }
+
+ function op_contains($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "$field LIKE $placeholder", array($placeholder => '%' . db_like($this->value) . '%'));
+ }
+
+ function op_word($field) {
+ $where = $this->operator == 'word' ? db_or() : db_and();
+
+ // Don't filter on empty strings.
+ if (empty($this->value)) {
+ return;
+ }
+
+ preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $this->value, $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ $phrase = FALSE;
+ // Strip off phrase quotes.
+ if ($match[2]{0} == '"') {
+ $match[2] = substr($match[2], 1, -1);
+ $phrase = TRUE;
+ }
+ $words = trim($match[2], ',?!();:-');
+ $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
+ $placeholder = $this->placeholder();
+ foreach ($words as $word) {
+ $where->where($field . " LIKE $placeholder", array($placeholder => '%' . db_like(trim($word, " ,!?")) . '%'));
+ }
+ }
+
+ if (!$where) {
+ return;
+ }
+
+ // Previously this was a call_user_func_array() but that's unnecessary
+ // as views will unpack an array that is a single arg.
+ $this->query->add_where($this->options['group'], $where);
+ }
+
+ function op_starts($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "$field LIKE $placeholder", array($placeholder => db_like($this->value) . '%'));
+ }
+
+ function op_not_starts($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "$field NOT LIKE $placeholder", array($placeholder => db_like($this->value) . '%'));
+ }
+
+ function op_ends($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "$field LIKE $placeholder", array($placeholder => '%' . db_like($this->value)));
+ }
+
+ function op_not_ends($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "$field NOT LIKE $placeholder", array($placeholder => '%' . db_like($this->value)));
+ }
+
+ function op_not($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "$field NOT LIKE $placeholder", array($placeholder => '%' . db_like($this->value) . '%'));
+ }
+
+ function op_regex($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "$field RLIKE $placeholder", array($placeholder => $this->value));
+ }
+
+ function op_empty($field) {
+ if ($this->operator == 'empty') {
+ $operator = "IS NULL";
+ }
+ else {
+ $operator = "IS NOT NULL";
+ }
+
+ $this->query->add_where_expression($this->options['group'], "$field $operator");
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_date.inc b/sites/all/modules/views/handlers/views_handler_filter_date.inc
new file mode 100644
index 000000000..383132743
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_date.inc
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_date.
+ */
+
+/**
+ * Filter to handle dates stored as a timestamp.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_date extends views_handler_filter_numeric {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ // value is already set up properly, we're just adding our new field to it.
+ $options['value']['contains']['type']['default'] = 'date';
+
+ return $options;
+ }
+
+ /**
+ * Add a type selector to the value form
+ */
+ function value_form(&$form, &$form_state) {
+ if (empty($form_state['exposed'])) {
+ $form['value']['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Value type'),
+ '#options' => array(
+ 'date' => t('A date in any machine readable format. CCYY-MM-DD HH:MM:SS is preferred.'),
+ 'offset' => t('An offset from the current time such as "!example1" or "!example2"', array('!example1' => '+1 day', '!example2' => '-2 hours -30 minutes')),
+ ),
+ '#default_value' => !empty($this->value['type']) ? $this->value['type'] : 'date',
+ );
+ }
+ parent::value_form($form, $form_state);
+ }
+
+ function options_validate(&$form, &$form_state) {
+ parent::options_validate($form, $form_state);
+
+ if (!empty($this->options['exposed']) && empty($form_state['values']['options']['expose']['required'])) {
+ // Who cares what the value is if it's exposed and non-required.
+ return;
+ }
+
+ $this->validate_valid_time($form['value'], $form_state['values']['options']['operator'], $form_state['values']['options']['value']);
+ }
+
+ function exposed_validate(&$form, &$form_state) {
+ if (empty($this->options['exposed'])) {
+ return;
+ }
+
+ if (empty($this->options['expose']['required'])) {
+ // Who cares what the value is if it's exposed and non-required.
+ return;
+ }
+
+ $value = &$form_state['values'][$this->options['expose']['identifier']];
+ if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id'])) {
+ $operator = $form_state['values'][$this->options['expose']['operator_id']];
+ }
+ else {
+ $operator = $this->operator;
+ }
+
+ $this->validate_valid_time($this->options['expose']['identifier'], $operator, $value);
+
+ }
+
+ /**
+ * Validate that the time values convert to something usable.
+ */
+ function validate_valid_time(&$form, $operator, $value) {
+ $operators = $this->operators();
+
+ if ($operators[$operator]['values'] == 1) {
+ $convert = strtotime($value['value']);
+ if (!empty($form['value']) && ($convert == -1 || $convert === FALSE)) {
+ form_error($form['value'], t('Invalid date format.'));
+ }
+ }
+ elseif ($operators[$operator]['values'] == 2) {
+ $min = strtotime($value['min']);
+ if ($min == -1 || $min === FALSE) {
+ form_error($form['min'], t('Invalid date format.'));
+ }
+ $max = strtotime($value['max']);
+ if ($max == -1 || $max === FALSE) {
+ form_error($form['max'], t('Invalid date format.'));
+ }
+ }
+ }
+
+ /**
+ * Validate the build group options form.
+ */
+ function build_group_validate($form, &$form_state) {
+ // Special case to validate grouped date filters, this is because the
+ // $group['value'] array contains the type of filter (date or offset)
+ // and therefore the number of items the comparission has to be done
+ // against 'one' instead of 'zero'.
+ foreach ($form_state['values']['options']['group_info']['group_items'] as $id => $group) {
+ if (empty($group['remove'])) {
+ // Check if the title is defined but value wasn't defined.
+ if (!empty($group['title'])) {
+ if ((!is_array($group['value']) && empty($group['value'])) || (is_array($group['value']) && count(array_filter($group['value'])) == 1)) {
+ form_error($form['group_info']['group_items'][$id]['value'], t('The value is required if title for this item is defined.'));
+ }
+ }
+
+ // Check if the value is defined but title wasn't defined.
+ if ((!is_array($group['value']) && !empty($group['value'])) || (is_array($group['value']) && count(array_filter($group['value'])) > 1)) {
+ if (empty($group['title'])) {
+ form_error($form['group_info']['group_items'][$id]['title'], t('The title is required if value for this item is defined.'));
+ }
+ }
+ }
+ }
+ }
+
+
+ function accept_exposed_input($input) {
+ if (empty($this->options['exposed'])) {
+ return TRUE;
+ }
+
+ // Store this because it will get overwritten.
+ $type = $this->value['type'];
+ $rc = parent::accept_exposed_input($input);
+
+ // Don't filter if value(s) are empty.
+ $operators = $this->operators();
+ if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id'])) {
+ $operator = $input[$this->options['expose']['operator_id']];
+ }
+ else {
+ $operator = $this->operator;
+ }
+
+ if ($operators[$operator]['values'] == 1) {
+ if ($this->value['value'] == '') {
+ return FALSE;
+ }
+ }
+ else {
+ if ($this->value['min'] == '' || $this->value['max'] == '') {
+ return FALSE;
+ }
+ }
+
+ // restore what got overwritten by the parent.
+ $this->value['type'] = $type;
+ return $rc;
+ }
+
+ function op_between($field) {
+ // Use the substitutions to ensure a consistent timestamp.
+ $query_substitutions = views_views_query_substitutions($this->view);
+ $a = intval(strtotime($this->value['min'], $query_substitutions['***CURRENT_TIME***']));
+ $b = intval(strtotime($this->value['max'], $query_substitutions['***CURRENT_TIME***']));
+
+ // This is safe because we are manually scrubbing the values.
+ // It is necessary to do it this way because $a and $b are formulas when using an offset.
+ $operator = strtoupper($this->operator);
+ $this->query->add_where_expression($this->options['group'], "$field $operator $a AND $b");
+ }
+
+ function op_simple($field) {
+ // Use the substitutions to ensure a consistent timestamp.
+ $query_substitutions = views_views_query_substitutions($this->view);
+ $value = intval(strtotime($this->value['value'], $query_substitutions['***CURRENT_TIME***']));
+
+ // This is safe because we are manually scrubbing the value.
+ // It is necessary to do it this way because $value is a formula when using an offset.
+ $this->query->add_where_expression($this->options['group'], "$field $this->operator $value");
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_entity_bundle.inc b/sites/all/modules/views/handlers/views_handler_filter_entity_bundle.inc
new file mode 100644
index 000000000..c46cd0b8c
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_entity_bundle.inc
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_entity_bundle
+ */
+
+/**
+ * Filter class which allows to filter by certain bundles of an entity.
+ *
+ * This class provides workarounds for taxonomy and comment.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_entity_bundle extends views_handler_filter_in_operator {
+ /**
+ * Stores the entity type on which the filter filters.
+ *
+ * @var string
+ */
+ public $entity_type;
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ $this->get_entity_type();
+ }
+
+ /**
+ * Set and returns the entity_type.
+ *
+ * @return string
+ * The entity type on the filter.
+ */
+ function get_entity_type() {
+ if (!isset($this->entity_type)) {
+ $data = views_fetch_data($this->table);
+ if (isset($data['table']['entity type'])) {
+ $this->entity_type = $data['table']['entity type'];
+ }
+
+ // If the current filter is under a relationship you can't be sure that the
+ // entity type of the view is the entity type of the current filter
+ // For example a filter from a node author on a node view does have users as entity type.
+ if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
+ $relationships = $this->view->display_handler->get_option('relationships');
+ if (!empty($relationships[$this->options['relationship']])) {
+ $options = $relationships[$this->options['relationship']];
+ $data = views_fetch_data($options['table']);
+ $this->entity_type = $data['table']['entity type'];
+ }
+ }
+ }
+
+ return $this->entity_type;
+ }
+
+
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $info = entity_get_info($this->entity_type);
+ $types = $info['bundles'];
+ $this->value_title = t('@entity types', array('@entity' => $info['label']));
+
+ $options = array();
+ foreach ($types as $type => $info) {
+ $options[$type] = t($info['label']);
+ }
+ asort($options);
+ $this->value_options = $options;
+ }
+ }
+
+ /**
+ * All entity types beside comment and taxonomy terms have a proper implement
+ * bundle, though these two need an additional join to node/vocab table
+ * to work as required.
+ */
+ function query() {
+ $this->ensure_my_table();
+
+ // Adjust the join for the comment case.
+ if ($this->entity_type == 'comment') {
+ $join = new views_join();
+ $def = array(
+ 'table' => 'node',
+ 'field' => 'nid',
+ 'left_table' => $this->table_alias,
+ 'left_field' => 'nid',
+ );
+ $join->definition = $def;
+ $join->construct();
+ $join->adjusted = TRUE;
+ $this->table_alias = $this->query->add_table('node', $this->relationship, $join);
+ $this->real_field = 'type';
+
+ // Replace the value to match the node type column.
+ foreach ($this->value as &$value) {
+ $value = str_replace('comment_node_', '', $value);
+ }
+ }
+ elseif ($this->entity_type == 'taxonomy_term') {
+ $join = new views_join();
+ $def = array(
+ 'table' => 'taxonomy_vocabulary',
+ 'field' => 'vid',
+ 'left_table' => $this->table_alias,
+ 'left_field' => 'vid',
+ );
+ $join->definition = $def;
+ $join->construct();
+ $join->adjusted = TRUE;
+ $this->table_alias = $this->query->add_table('taxonomy_vocabulary', $this->relationship, $join);
+ $this->real_field = 'machine_name';
+ }
+ else {
+ $entity_info = entity_get_info($this->entity_type);
+ $this->real_field = $entity_info['bundle keys']['bundle'];
+ }
+ parent::query();
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_equality.inc b/sites/all/modules/views/handlers/views_handler_filter_equality.inc
new file mode 100644
index 000000000..e045c7e34
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_equality.inc
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_equality.
+ */
+
+/**
+ * Simple filter to handle equal to / not equal to filters
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_equality extends views_handler_filter {
+ // exposed filter options
+ var $always_multiple = TRUE;
+
+ /**
+ * Provide simple equality operator
+ */
+ function operator_options() {
+ return array(
+ '=' => t('Is equal to'),
+ '!=' => t('Is not equal to'),
+ );
+ }
+
+ /**
+ * Provide a simple textfield for equality
+ */
+ function value_form(&$form, &$form_state) {
+ $form['value'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Value'),
+ '#size' => 30,
+ '#default_value' => $this->value,
+ );
+
+ if (!empty($form_state['exposed'])) {
+ $identifier = $this->options['expose']['identifier'];
+ if (!isset($form_state['input'][$identifier])) {
+ $form_state['input'][$identifier] = $this->value;
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_fields_compare.inc b/sites/all/modules/views/handlers/views_handler_filter_fields_compare.inc
new file mode 100644
index 000000000..fe94ce767
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_fields_compare.inc
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_fields_compare.
+ */
+
+/**
+ * A handler to filter a view using fields comparison.
+ *
+ * @ingroup views_filter_handlers
+ */
+
+class views_handler_filter_fields_compare extends views_handler_filter {
+
+ function can_expose() {
+ return FALSE;
+ }
+
+ /**
+ * Overrides views_handler_filter#option_definition().
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['left_field'] = $options['right_field'] = array('default' => '');
+
+ return $options;
+ }
+
+ /**
+ * Provide a list of all operators.
+ */
+ function fields_operator_options() {
+ return array(
+ '<' => t('Is less than'),
+ '<=' => t('Is less than or equal to'),
+ '=' => t('Is equal to'),
+ '<>' => t('Is not equal to'),
+ '>=' => t('Is greater than or equal to'),
+ '>' => t('Is greater than')
+ );
+ }
+
+ /**
+ * Provide a list of available fields.
+ */
+ function field_options() {
+ $options = array();
+
+ $field_handlers = $this->view->display_handler->get_handlers('field');
+ foreach ($field_handlers as $field => $handler) {
+ if ($handler->table != 'views') {
+ $options[$field] = $handler->ui_name();
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Overrides views_handler_filter#options_form().
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $field_options = $this->field_options();
+
+ $form['left_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Left field'),
+ '#default_value' => $this->options['left_field'],
+ '#options' => $field_options,
+ '#weight' => -3,
+ );
+
+ $form['operator'] = array(
+ '#type' => 'select',
+ '#title' => t('Operator'),
+ '#default_value' => $this->options['operator'],
+ '#options' => $this->fields_operator_options(),
+ '#weight' => -2,
+ );
+
+ $form['right_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Right field'),
+ '#default_value' => $this->options['right_field'],
+ '#options' => $field_options,
+ '#weight' => -1,
+ );
+
+ }
+
+ /**
+ * Overrides views_handler_filter#query().
+ *
+ * Build extra condition from existing fields (from existing joins).
+ */
+ function query() {
+ $left = $this->options['left_field'];
+ $right = $this->options['right_field'];
+
+ // Get all existing field handlers.
+ $field_handlers = $this->view->display_handler->get_handlers('field');
+
+ // Make sure the selected fields still exist.
+ if (!isset($field_handlers[$left], $field_handlers[$right])) {
+ return;
+ }
+
+ // Get the left table and field.
+ $left_handler = $field_handlers[$left];
+ $left_handler->set_relationship();
+ $left_table_alias = $this->query->ensure_table($left_handler->table, $left_handler->relationship);
+
+ // Get the left table and field.
+ $right_handler = $field_handlers[$right];
+ $right_handler->set_relationship();
+ $right_table_alias = $this->query->ensure_table($right_handler->table, $right_handler->relationship);
+
+ // Build piece of SQL.
+ $snippet =
+ $left_table_alias . '.' . $left_handler->real_field .
+ ' ' . $this->options['operator'] . ' ' .
+ $right_table_alias . '.' . $right_handler->real_field;
+
+ $this->query->add_where_expression($this->options['group'], $snippet);
+ }
+
+ /**
+ * Overrides views_handler_filter#admin_summary().
+ */
+ function admin_summary() {
+ return check_plain(
+ $this->options['left_field'] . ' ' .
+ $this->options['operator'] . ' ' .
+ $this->options['right_field']
+ );
+ }
+
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_group_by_numeric.inc b/sites/all/modules/views/handlers/views_handler_filter_group_by_numeric.inc
new file mode 100644
index 000000000..2b265beff
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_group_by_numeric.inc
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_group_by_numeric.
+ */
+
+/**
+ * Simple filter to handle greater than/less than filters
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_group_by_numeric extends views_handler_filter_numeric {
+ function query() {
+ $this->ensure_my_table();
+ $field = $this->get_field();
+
+ $info = $this->operators();
+ if (!empty($info[$this->operator]['method'])) {
+ $this->{$info[$this->operator]['method']}($field);
+ }
+ }
+ function op_between($field) {
+ $placeholder_min = $this->placeholder();
+ $placeholder_max = $this->placeholder();
+ if ($this->operator == 'between') {
+ $this->query->add_having_expression($this->options['group'], "$field >= $placeholder_min", array($placeholder_min => $this->value['min']));
+ $this->query->add_having_expression($this->options['group'], "$field <= $placeholder_max", array($placeholder_max => $this->value['max']));
+ }
+ else {
+ $this->query->add_having_expression($this->options['group'], "$field <= $placeholder_min OR $field >= $placeholder_max", array($placeholder_min => $this->value['min'], $placeholder_max => $this->value['max']));
+ }
+ }
+
+ function op_simple($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_having_expression($this->options['group'], "$field $this->operator $placeholder", array($placeholder => $this->value['value']));
+ }
+
+ function op_empty($field) {
+ if ($this->operator == 'empty') {
+ $operator = "IS NULL";
+ }
+ else {
+ $operator = "IS NOT NULL";
+ }
+
+ $this->query->add_having_expression($this->options['group'], "$field $operator");
+ }
+
+ function ui_name($short = FALSE) {
+ return $this->get_field(parent::ui_name($short));
+ }
+
+ function can_group() { return FALSE; }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_in_operator.inc b/sites/all/modules/views/handlers/views_handler_filter_in_operator.inc
new file mode 100644
index 000000000..fc2700bd1
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_in_operator.inc
@@ -0,0 +1,426 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_in_operator.
+ */
+
+/**
+ * Simple filter to handle matching of multiple options selectable via checkboxes
+ *
+ * Definition items:
+ * - options callback: The function to call in order to generate the value options. If omitted, the options 'Yes' and 'No' will be used.
+ * - options arguments: An array of arguments to pass to the options callback.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_in_operator extends views_handler_filter {
+ var $value_form_type = 'checkboxes';
+
+ /**
+ * @var array
+ * Stores all operations which are available on the form.
+ */
+ var $value_options = NULL;
+
+ function construct() {
+ parent::construct();
+ $this->value_title = t('Options');
+ $this->value_options = NULL;
+ }
+
+ /**
+ * Child classes should be used to override this function and set the
+ * 'value options', unless 'options callback' is defined as a valid function
+ * or static public method to generate these values.
+ *
+ * This can use a guard to be used to reduce database hits as much as
+ * possible.
+ *
+ * @return
+ * Return the stored values in $this->value_options if someone expects it.
+ */
+ function get_value_options() {
+ if (isset($this->value_options)) {
+ return;
+ }
+
+ if (isset($this->definition['options callback']) && is_callable($this->definition['options callback'])) {
+ if (isset($this->definition['options arguments']) && is_array($this->definition['options arguments'])) {
+ $this->value_options = call_user_func_array($this->definition['options callback'], $this->definition['options arguments']);
+ }
+ else {
+ $this->value_options = call_user_func($this->definition['options callback']);
+ }
+ }
+ else {
+ $this->value_options = array(t('Yes'), t('No'));
+ }
+
+ return $this->value_options;
+ }
+
+ function expose_options() {
+ parent::expose_options();
+ $this->options['expose']['reduce'] = FALSE;
+ }
+
+ function expose_form(&$form, &$form_state) {
+ parent::expose_form($form, $form_state);
+ $form['expose']['reduce'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Limit list to selected items'),
+ '#description' => t('If checked, the only items presented to the user will be the ones selected here.'),
+ '#default_value' => !empty($this->options['expose']['reduce']), // safety
+ );
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['operator']['default'] = 'in';
+ $options['value']['default'] = array();
+ $options['expose']['contains']['reduce'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * This kind of construct makes it relatively easy for a child class
+ * to add or remove functionality by overriding this function and
+ * adding/removing items from this array.
+ */
+ function operators() {
+ $operators = array(
+ 'in' => array(
+ 'title' => t('Is one of'),
+ 'short' => t('in'),
+ 'short_single' => t('='),
+ 'method' => 'op_simple',
+ 'values' => 1,
+ ),
+ 'not in' => array(
+ 'title' => t('Is not one of'),
+ 'short' => t('not in'),
+ 'short_single' => t('<>'),
+ 'method' => 'op_simple',
+ 'values' => 1,
+ ),
+ );
+ // if the definition allows for the empty operator, add it.
+ if (!empty($this->definition['allow empty'])) {
+ $operators += array(
+ 'empty' => array(
+ 'title' => t('Is empty (NULL)'),
+ 'method' => 'op_empty',
+ 'short' => t('empty'),
+ 'values' => 0,
+ ),
+ 'not empty' => array(
+ 'title' => t('Is not empty (NOT NULL)'),
+ 'method' => 'op_empty',
+ 'short' => t('not empty'),
+ 'values' => 0,
+ ),
+ );
+ }
+
+ return $operators;
+ }
+
+ /**
+ * Build strings from the operators() for 'select' options
+ */
+ function operator_options($which = 'title') {
+ $options = array();
+ foreach ($this->operators() as $id => $info) {
+ $options[$id] = $info[$which];
+ }
+
+ return $options;
+ }
+
+ function operator_values($values = 1) {
+ $options = array();
+ foreach ($this->operators() as $id => $info) {
+ if (isset($info['values']) && $info['values'] == $values) {
+ $options[] = $id;
+ }
+ }
+
+ return $options;
+ }
+
+ function value_form(&$form, &$form_state) {
+ $form['value'] = array();
+ $options = array();
+
+ if (empty($form_state['exposed'])) {
+ // Add a select all option to the value form.
+ $options = array('all' => t('Select all'));
+ }
+
+ $this->get_value_options();
+ $options += $this->value_options;
+ $default_value = (array) $this->value;
+
+ $which = 'all';
+ if (!empty($form['operator'])) {
+ $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
+ }
+ if (!empty($form_state['exposed'])) {
+ $identifier = $this->options['expose']['identifier'];
+
+ if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
+ // exposed and locked.
+ $which = in_array($this->operator, $this->operator_values(1)) ? 'value' : 'none';
+ }
+ else {
+ $source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
+ }
+
+ if (!empty($this->options['expose']['reduce'])) {
+ $options = $this->reduce_value_options();
+
+ if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) {
+ $default_value = array();
+ }
+ }
+
+ if (empty($this->options['expose']['multiple'])) {
+ if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) {
+ $default_value = 'All';
+ }
+ elseif (empty($default_value)) {
+ $keys = array_keys($options);
+ $default_value = array_shift($keys);
+ }
+ else {
+ $copy = $default_value;
+ $default_value = array_shift($copy);
+ }
+ }
+ }
+
+ if ($which == 'all' || $which == 'value') {
+ $form['value'] = array(
+ '#type' => $this->value_form_type,
+ '#title' => $this->value_title,
+ '#options' => $options,
+ '#default_value' => $default_value,
+ // These are only valid for 'select' type, but do no harm to checkboxes.
+ '#multiple' => TRUE,
+ '#size' => count($options) > 8 ? 8 : count($options),
+ );
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) {
+ $form_state['input'][$identifier] = $default_value;
+ }
+
+ if ($which == 'all') {
+ if (empty($form_state['exposed']) && (in_array($this->value_form_type, array('checkbox', 'checkboxes', 'radios', 'select')))) {
+ $form['value']['#prefix'] = '<div id="edit-options-value-wrapper">';
+ $form['value']['#suffix'] = '</div>';
+ }
+ $form['value']['#dependency'] = array($source => $this->operator_values(1));
+ }
+ }
+ }
+
+ /**
+ * When using exposed filters, we may be required to reduce the set.
+ */
+ function reduce_value_options($input = NULL) {
+ if (!isset($input)) {
+ $input = $this->value_options;
+ }
+
+ // Because options may be an array of strings, or an array of mixed arrays
+ // and strings (optgroups) or an array of objects, we have to
+ // step through and handle each one individually.
+ $options = array();
+ foreach ($input as $id => $option) {
+ if (is_array($option)) {
+ $options[$id] = $this->reduce_value_options($option);
+ continue;
+ }
+ elseif (is_object($option)) {
+ $keys = array_keys($option->option);
+ $key = array_shift($keys);
+ if (isset($this->options['value'][$key])) {
+ $options[$id] = $option;
+ }
+ }
+ elseif (isset($this->options['value'][$id])) {
+ $options[$id] = $option;
+ }
+ }
+ return $options;
+ }
+
+ function accept_exposed_input($input) {
+ // A very special override because the All state for this type of
+ // filter could have a default:
+ if (empty($this->options['exposed'])) {
+ return TRUE;
+ }
+
+ // If this is non-multiple and non-required, then this filter will
+ // participate, but using the default settings, *if* 'limit is true.
+ if (empty($this->options['expose']['multiple']) && empty($this->options['expose']['required']) && !empty($this->options['expose']['limit'])) {
+ $identifier = $this->options['expose']['identifier'];
+ if ($input[$identifier] == 'All') {
+ return TRUE;
+ }
+ }
+
+ return parent::accept_exposed_input($input);
+ }
+
+ function value_submit($form, &$form_state) {
+ // Drupal's FAPI system automatically puts '0' in for any checkbox that
+ // was not set, and the key to the checkbox if it is set.
+ // Unfortunately, this means that if the key to that checkbox is 0,
+ // we are unable to tell if that checkbox was set or not.
+
+ // Luckily, the '#value' on the checkboxes form actually contains
+ // *only* a list of checkboxes that were set, and we can use that
+ // instead.
+
+ $form_state['values']['options']['value'] = $form['value']['#value'];
+ }
+
+ function admin_summary() {
+ if ($this->is_a_group()) {
+ return t('grouped');
+ }
+ if (!empty($this->options['exposed'])) {
+ return t('exposed');
+ }
+ $info = $this->operators();
+
+ $this->get_value_options();
+
+ if (!is_array($this->value)) {
+ return;
+ }
+
+ $operator = check_plain($info[$this->operator]['short']);
+ $values = '';
+ if (in_array($this->operator, $this->operator_values(1))) {
+ // Remove every element which is not known.
+ foreach ($this->value as $value) {
+ if (!isset($this->value_options[$value])) {
+ unset($this->value[$value]);
+ }
+ }
+ // Choose different kind of ouput for 0, a single and multiple values.
+ if (count($this->value) == 0) {
+ $values = t('Unknown');
+ }
+ else if (count($this->value) == 1) {
+ // If any, use the 'single' short name of the operator instead.
+ if (isset($info[$this->operator]['short_single'])) {
+ $operator = check_plain($info[$this->operator]['short_single']);
+ }
+
+ $keys = $this->value;
+ $value = array_shift($keys);
+ if (isset($this->value_options[$value])) {
+ $values = check_plain($this->value_options[$value]);
+ }
+ else {
+ $values = '';
+ }
+ }
+ else {
+ foreach ($this->value as $value) {
+ if ($values !== '') {
+ $values .= ', ';
+ }
+ if (drupal_strlen($values) > 8) {
+ $values .= '...';
+ break;
+ }
+ if (isset($this->value_options[$value])) {
+ $values .= check_plain($this->value_options[$value]);
+ }
+ }
+ }
+ }
+
+ return $operator . (($values !== '') ? ' ' . $values : '');
+ }
+
+ function query() {
+ $info = $this->operators();
+ if (!empty($info[$this->operator]['method'])) {
+ $this->{$info[$this->operator]['method']}();
+ }
+ }
+
+ function op_simple() {
+ if (empty($this->value)) {
+ return;
+ }
+ $this->ensure_my_table();
+
+ // We use array_values() because the checkboxes keep keys and that can cause
+ // array addition problems.
+ $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", array_values($this->value), $this->operator);
+ }
+
+ function op_empty() {
+ $this->ensure_my_table();
+ if ($this->operator == 'empty') {
+ $operator = "IS NULL";
+ }
+ else {
+ $operator = "IS NOT NULL";
+ }
+
+ $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", NULL, $operator);
+ }
+
+ function validate() {
+ $this->get_value_options();
+ $errors = array();
+
+ // If the operator is an operator which doesn't require a value, there is
+ // no need for additional validation.
+ if (in_array($this->operator, $this->operator_values(0))) {
+ return array();
+ }
+
+ if (!in_array($this->operator, $this->operator_values(1))) {
+ $errors[] = t('The operator is invalid on filter: @filter.', array('@filter' => $this->ui_name(TRUE)));
+ }
+ if (is_array($this->value)) {
+ if (!isset($this->value_options)) {
+ // Don't validate if there are none value options provided, for example for special handlers.
+ return $errors;
+ }
+ if ($this->options['exposed'] && !$this->options['expose']['required'] && empty($this->value)) {
+ // Don't validate if the field is exposed and no default value is provided.
+ return $errors;
+ }
+
+ // Some filter_in_operator usage uses optgroups forms, so flatten it.
+ $flat_options = form_options_flatten($this->value_options, TRUE);
+
+ // Remove every element which is not known.
+ foreach ($this->value as $value) {
+ if (!isset($flat_options[$value])) {
+ unset($this->value[$value]);
+ }
+ }
+ // Choose different kind of ouput for 0, a single and multiple values.
+ if (count($this->value) == 0) {
+ $errors[] = t('No valid values found on filter: @filter.', array('@filter' => $this->ui_name(TRUE)));
+ }
+ }
+ elseif (!empty($this->value) && ($this->operator == 'in' || $this->operator == 'not in')) {
+ $errors[] = t('The value @value is not an array for @operator on filter: @filter', array('@value' => views_var_export($this->value), '@operator' => $this->operator, '@filter' => $this->ui_name(TRUE)));
+ }
+ return $errors;
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_many_to_one.inc b/sites/all/modules/views/handlers/views_handler_filter_many_to_one.inc
new file mode 100644
index 000000000..f38479616
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_many_to_one.inc
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_many_to_one.
+ */
+
+/**
+ * Complex filter to handle filtering for many to one relationships,
+ * such as terms (many terms per node) or roles (many roles per user).
+ *
+ * The construct method needs to be overridden to provide a list of options;
+ * alternately, the value_form and admin_summary methods need to be overriden
+ * to provide something that isn't just a select list.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_many_to_one extends views_handler_filter_in_operator {
+ /**
+ * @var views_many_to_one_helper
+ *
+ * Stores the Helper object which handles the many_to_one complexity.
+ */
+ var $helper = NULL;
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ $this->helper = new views_many_to_one_helper($this);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['operator']['default'] = 'or';
+ $options['value']['default'] = array();
+
+ if (isset($this->helper)) {
+ $this->helper->option_definition($options);
+ }
+ else {
+ $helper = new views_many_to_one_helper($this);
+ $helper->option_definition($options);
+ }
+
+ return $options;
+ }
+
+ function operators() {
+ $operators = array(
+ 'or' => array(
+ 'title' => t('Is one of'),
+ 'short' => t('or'),
+ 'short_single' => t('='),
+ 'method' => 'op_helper',
+ 'values' => 1,
+ 'ensure_my_table' => 'helper',
+ ),
+ 'and' => array(
+ 'title' => t('Is all of'),
+ 'short' => t('and'),
+ 'short_single' => t('='),
+ 'method' => 'op_helper',
+ 'values' => 1,
+ 'ensure_my_table' => 'helper',
+ ),
+ 'not' => array(
+ 'title' => t('Is none of'),
+ 'short' => t('not'),
+ 'short_single' => t('<>'),
+ 'method' => 'op_helper',
+ 'values' => 1,
+ 'ensure_my_table' => 'helper',
+ ),
+ );
+ // if the definition allows for the empty operator, add it.
+ if (!empty($this->definition['allow empty'])) {
+ $operators += array(
+ 'empty' => array(
+ 'title' => t('Is empty (NULL)'),
+ 'method' => 'op_empty',
+ 'short' => t('empty'),
+ 'values' => 0,
+ ),
+ 'not empty' => array(
+ 'title' => t('Is not empty (NOT NULL)'),
+ 'method' => 'op_empty',
+ 'short' => t('not empty'),
+ 'values' => 0,
+ ),
+ );
+ }
+
+ return $operators;
+ }
+
+ var $value_form_type = 'select';
+ function value_form(&$form, &$form_state) {
+ parent::value_form($form, $form_state);
+
+ if (empty($form_state['exposed'])) {
+ $this->helper->options_form($form, $form_state);
+ }
+ }
+
+ /**
+ * Override ensure_my_table so we can control how this joins in.
+ * The operator actually has influence over joining.
+ */
+ function ensure_my_table() {
+ // Defer to helper if the operator specifies it.
+ $info = $this->operators();
+ if (isset($info[$this->operator]['ensure_my_table']) && $info[$this->operator]['ensure_my_table'] == 'helper') {
+ return $this->helper->ensure_my_table();
+ }
+
+ return parent::ensure_my_table();
+ }
+
+ function op_helper() {
+ if (empty($this->value)) {
+ return;
+ }
+ $this->helper->add_filter();
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_numeric.inc b/sites/all/modules/views/handlers/views_handler_filter_numeric.inc
new file mode 100644
index 000000000..03384f685
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_numeric.inc
@@ -0,0 +1,325 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_numeric.
+ */
+
+/**
+ * Simple filter to handle greater than/less than filters
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_numeric extends views_handler_filter {
+ var $always_multiple = TRUE;
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['value'] = array(
+ 'contains' => array(
+ 'min' => array('default' => ''),
+ 'max' => array('default' => ''),
+ 'value' => array('default' => ''),
+ ),
+ );
+
+ return $options;
+ }
+
+ function operators() {
+ $operators = array(
+ '<' => array(
+ 'title' => t('Is less than'),
+ 'method' => 'op_simple',
+ 'short' => t('<'),
+ 'values' => 1,
+ ),
+ '<=' => array(
+ 'title' => t('Is less than or equal to'),
+ 'method' => 'op_simple',
+ 'short' => t('<='),
+ 'values' => 1,
+ ),
+ '=' => array(
+ 'title' => t('Is equal to'),
+ 'method' => 'op_simple',
+ 'short' => t('='),
+ 'values' => 1,
+ ),
+ '!=' => array(
+ 'title' => t('Is not equal to'),
+ 'method' => 'op_simple',
+ 'short' => t('!='),
+ 'values' => 1,
+ ),
+ '>=' => array(
+ 'title' => t('Is greater than or equal to'),
+ 'method' => 'op_simple',
+ 'short' => t('>='),
+ 'values' => 1,
+ ),
+ '>' => array(
+ 'title' => t('Is greater than'),
+ 'method' => 'op_simple',
+ 'short' => t('>'),
+ 'values' => 1,
+ ),
+ 'between' => array(
+ 'title' => t('Is between'),
+ 'method' => 'op_between',
+ 'short' => t('between'),
+ 'values' => 2,
+ ),
+ 'not between' => array(
+ 'title' => t('Is not between'),
+ 'method' => 'op_between',
+ 'short' => t('not between'),
+ 'values' => 2,
+ ),
+ );
+
+ // if the definition allows for the empty operator, add it.
+ if (!empty($this->definition['allow empty'])) {
+ $operators += array(
+ 'empty' => array(
+ 'title' => t('Is empty (NULL)'),
+ 'method' => 'op_empty',
+ 'short' => t('empty'),
+ 'values' => 0,
+ ),
+ 'not empty' => array(
+ 'title' => t('Is not empty (NOT NULL)'),
+ 'method' => 'op_empty',
+ 'short' => t('not empty'),
+ 'values' => 0,
+ ),
+ );
+ }
+
+ // Add regexp support for MySQL.
+ if (Database::getConnection()->databaseType() == 'mysql') {
+ $operators += array(
+ 'regular_expression' => array(
+ 'title' => t('Regular expression'),
+ 'short' => t('regex'),
+ 'method' => 'op_regex',
+ 'values' => 1,
+ ),
+ );
+ }
+
+ return $operators;
+ }
+
+ /**
+ * Provide a list of all the numeric operators
+ */
+ function operator_options($which = 'title') {
+ $options = array();
+ foreach ($this->operators() as $id => $info) {
+ $options[$id] = $info[$which];
+ }
+
+ return $options;
+ }
+
+ function operator_values($values = 1) {
+ $options = array();
+ foreach ($this->operators() as $id => $info) {
+ if ($info['values'] == $values) {
+ $options[] = $id;
+ }
+ }
+
+ return $options;
+ }
+ /**
+ * Provide a simple textfield for equality
+ */
+ function value_form(&$form, &$form_state) {
+ $form['value']['#tree'] = TRUE;
+
+ // We have to make some choices when creating this as an exposed
+ // filter form. For example, if the operator is locked and thus
+ // not rendered, we can't render dependencies; instead we only
+ // render the form items we need.
+ $which = 'all';
+ if (!empty($form['operator'])) {
+ $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
+ }
+
+ if (!empty($form_state['exposed'])) {
+ $identifier = $this->options['expose']['identifier'];
+
+ if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
+ // exposed and locked.
+ $which = in_array($this->operator, $this->operator_values(2)) ? 'minmax' : 'value';
+ }
+ else {
+ $source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
+ }
+ }
+
+ if ($which == 'all') {
+ $form['value']['value'] = array(
+ '#type' => 'textfield',
+ '#title' => empty($form_state['exposed']) ? t('Value') : '',
+ '#size' => 30,
+ '#default_value' => $this->value['value'],
+ '#dependency' => array($source => $this->operator_values(1)),
+ );
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['value'])) {
+ $form_state['input'][$identifier]['value'] = $this->value['value'];
+ }
+ }
+ elseif ($which == 'value') {
+ // When exposed we drop the value-value and just do value if
+ // the operator is locked.
+ $form['value'] = array(
+ '#type' => 'textfield',
+ '#title' => empty($form_state['exposed']) ? t('Value') : '',
+ '#size' => 30,
+ '#default_value' => $this->value['value'],
+ );
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) {
+ $form_state['input'][$identifier] = $this->value['value'];
+ }
+ }
+
+ if ($which == 'all' || $which == 'minmax') {
+ $form['value']['min'] = array(
+ '#type' => 'textfield',
+ '#title' => empty($form_state['exposed']) ? t('Min') : '',
+ '#size' => 30,
+ '#default_value' => $this->value['min'],
+ );
+ $form['value']['max'] = array(
+ '#type' => 'textfield',
+ '#title' => empty($form_state['exposed']) ? t('And max') : t('And'),
+ '#size' => 30,
+ '#default_value' => $this->value['max'],
+ );
+ if ($which == 'all') {
+ $dependency = array(
+ '#dependency' => array($source => $this->operator_values(2)),
+ );
+ $form['value']['min'] += $dependency;
+ $form['value']['max'] += $dependency;
+ }
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['min'])) {
+ $form_state['input'][$identifier]['min'] = $this->value['min'];
+ }
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['max'])) {
+ $form_state['input'][$identifier]['max'] = $this->value['max'];
+ }
+
+ if (!isset($form['value'])) {
+ // Ensure there is something in the 'value'.
+ $form['value'] = array(
+ '#type' => 'value',
+ '#value' => NULL
+ );
+ }
+ }
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $field = "$this->table_alias.$this->real_field";
+
+ $info = $this->operators();
+ if (!empty($info[$this->operator]['method'])) {
+ $this->{$info[$this->operator]['method']}($field);
+ }
+ }
+
+ function op_between($field) {
+ if ($this->operator == 'between') {
+ $this->query->add_where($this->options['group'], $field, array($this->value['min'], $this->value['max']), 'BETWEEN');
+ }
+ else {
+ $this->query->add_where($this->options['group'], db_or()->condition($field, $this->value['min'], '<=')->condition($field, $this->value['max'], '>='));
+ }
+ }
+
+ function op_simple($field) {
+ $this->query->add_where($this->options['group'], $field, $this->value['value'], $this->operator);
+ }
+
+ function op_empty($field) {
+ if ($this->operator == 'empty') {
+ $operator = "IS NULL";
+ }
+ else {
+ $operator = "IS NOT NULL";
+ }
+
+ $this->query->add_where($this->options['group'], $field, NULL, $operator);
+ }
+
+ function op_regex($field) {
+ $this->query->add_where($this->options['group'], $field, $this->value['value'], 'RLIKE');
+ }
+
+ function admin_summary() {
+ if ($this->is_a_group()) {
+ return t('grouped');
+ }
+ if (!empty($this->options['exposed'])) {
+ return t('exposed');
+ }
+
+ $options = $this->operator_options('short');
+ $output = check_plain($options[$this->operator]);
+ if (in_array($this->operator, $this->operator_values(2))) {
+ $output .= ' ' . t('@min and @max', array('@min' => $this->value['min'], '@max' => $this->value['max']));
+ }
+ elseif (in_array($this->operator, $this->operator_values(1))) {
+ $output .= ' ' . check_plain($this->value['value']);
+ }
+ return $output;
+ }
+
+ /**
+ * Do some minor translation of the exposed input
+ */
+ function accept_exposed_input($input) {
+ if (empty($this->options['exposed'])) {
+ return TRUE;
+ }
+
+ // rewrite the input value so that it's in the correct format so that
+ // the parent gets the right data.
+ if (!empty($this->options['expose']['identifier'])) {
+ $value = &$input[$this->options['expose']['identifier']];
+ if (!is_array($value)) {
+ $value = array(
+ 'value' => $value,
+ );
+ }
+ }
+
+ $rc = parent::accept_exposed_input($input);
+
+ if (empty($this->options['expose']['required'])) {
+ // We have to do some of our own checking for non-required filters.
+ $info = $this->operators();
+ if (!empty($info[$this->operator]['values'])) {
+ switch ($info[$this->operator]['values']) {
+ case 1:
+ if ($value['value'] === '') {
+ return FALSE;
+ }
+ break;
+ case 2:
+ if ($value['min'] === '' && $value['max'] === '') {
+ return FALSE;
+ }
+ break;
+ }
+ }
+ }
+
+ return $rc;
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_filter_string.inc b/sites/all/modules/views/handlers/views_handler_filter_string.inc
new file mode 100644
index 000000000..c50eff448
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_filter_string.inc
@@ -0,0 +1,338 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_string.
+ */
+
+/**
+ * Basic textfield filter to handle string filtering commands
+ * including equality, like, not like, etc.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_string extends views_handler_filter {
+ // exposed filter options
+ var $always_multiple = TRUE;
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['expose']['contains']['required'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * This kind of construct makes it relatively easy for a child class
+ * to add or remove functionality by overriding this function and
+ * adding/removing items from this array.
+ */
+ function operators() {
+ $operators = array(
+ '=' => array(
+ 'title' => t('Is equal to'),
+ 'short' => t('='),
+ 'method' => 'op_equal',
+ 'values' => 1,
+ ),
+ '!=' => array(
+ 'title' => t('Is not equal to'),
+ 'short' => t('!='),
+ 'method' => 'op_equal',
+ 'values' => 1,
+ ),
+ 'contains' => array(
+ 'title' => t('Contains'),
+ 'short' => t('contains'),
+ 'method' => 'op_contains',
+ 'values' => 1,
+ ),
+ 'word' => array(
+ 'title' => t('Contains any word'),
+ 'short' => t('has word'),
+ 'method' => 'op_word',
+ 'values' => 1,
+ ),
+ 'allwords' => array(
+ 'title' => t('Contains all words'),
+ 'short' => t('has all'),
+ 'method' => 'op_word',
+ 'values' => 1,
+ ),
+ 'starts' => array(
+ 'title' => t('Starts with'),
+ 'short' => t('begins'),
+ 'method' => 'op_starts',
+ 'values' => 1,
+ ),
+ 'not_starts' => array(
+ 'title' => t('Does not start with'),
+ 'short' => t('not_begins'),
+ 'method' => 'op_not_starts',
+ 'values' => 1,
+ ),
+ 'ends' => array(
+ 'title' => t('Ends with'),
+ 'short' => t('ends'),
+ 'method' => 'op_ends',
+ 'values' => 1,
+ ),
+ 'not_ends' => array(
+ 'title' => t('Does not end with'),
+ 'short' => t('not_ends'),
+ 'method' => 'op_not_ends',
+ 'values' => 1,
+ ),
+ 'not' => array(
+ 'title' => t('Does not contain'),
+ 'short' => t('!has'),
+ 'method' => 'op_not',
+ 'values' => 1,
+ ),
+ 'shorterthan' => array(
+ 'title' => t('Length is shorter than'),
+ 'short' => t('shorter than'),
+ 'method' => 'op_shorter',
+ 'values' => 1,
+ ),
+ 'longerthan' => array(
+ 'title' => t('Length is longer than'),
+ 'short' => t('longer than'),
+ 'method' => 'op_longer',
+ 'values' => 1,
+ ),
+ );
+ // if the definition allows for the empty operator, add it.
+ if (!empty($this->definition['allow empty'])) {
+ $operators += array(
+ 'empty' => array(
+ 'title' => t('Is empty (NULL)'),
+ 'method' => 'op_empty',
+ 'short' => t('empty'),
+ 'values' => 0,
+ ),
+ 'not empty' => array(
+ 'title' => t('Is not empty (NOT NULL)'),
+ 'method' => 'op_empty',
+ 'short' => t('not empty'),
+ 'values' => 0,
+ ),
+ );
+ }
+ // Add regexp support for MySQL.
+ if (Database::getConnection()->databaseType() == 'mysql') {
+ $operators += array(
+ 'regular_expression' => array(
+ 'title' => t('Regular expression'),
+ 'short' => t('regex'),
+ 'method' => 'op_regex',
+ 'values' => 1,
+ ),
+ );
+ }
+
+ return $operators;
+ }
+
+ /**
+ * Build strings from the operators() for 'select' options
+ */
+ function operator_options($which = 'title') {
+ $options = array();
+ foreach ($this->operators() as $id => $info) {
+ $options[$id] = $info[$which];
+ }
+
+ return $options;
+ }
+
+ function admin_summary() {
+ if ($this->is_a_group()) {
+ return t('grouped');
+ }
+ if (!empty($this->options['exposed'])) {
+ return t('exposed');
+ }
+
+ $options = $this->operator_options('short');
+ $output = '';
+ if(!empty($options[$this->operator])) {
+ $output = check_plain($options[$this->operator]);
+ }
+ if (in_array($this->operator, $this->operator_values(1))) {
+ $output .= ' ' . check_plain($this->value);
+ }
+ return $output;
+ }
+
+ function operator_values($values = 1) {
+ $options = array();
+ foreach ($this->operators() as $id => $info) {
+ if (isset($info['values']) && $info['values'] == $values) {
+ $options[] = $id;
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Provide a simple textfield for equality
+ */
+ function value_form(&$form, &$form_state) {
+ // We have to make some choices when creating this as an exposed
+ // filter form. For example, if the operator is locked and thus
+ // not rendered, we can't render dependencies; instead we only
+ // render the form items we need.
+ $which = 'all';
+ if (!empty($form['operator'])) {
+ $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
+ }
+ if (!empty($form_state['exposed'])) {
+ $identifier = $this->options['expose']['identifier'];
+
+ if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
+ // exposed and locked.
+ $which = in_array($this->operator, $this->operator_values(1)) ? 'value' : 'none';
+ }
+ else {
+ $source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
+ }
+ }
+
+ if ($which == 'all' || $which == 'value') {
+ $form['value'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Value'),
+ '#size' => 30,
+ '#default_value' => $this->value,
+ );
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) {
+ $form_state['input'][$identifier] = $this->value;
+ }
+
+ if ($which == 'all') {
+ $form['value'] += array(
+ '#dependency' => array($source => $this->operator_values(1)),
+ );
+ }
+ }
+
+ if (!isset($form['value'])) {
+ // Ensure there is something in the 'value'.
+ $form['value'] = array(
+ '#type' => 'value',
+ '#value' => NULL
+ );
+ }
+ }
+
+ function operator() {
+ return $this->operator == '=' ? 'LIKE' : 'NOT LIKE';
+ }
+
+ /**
+ * Add this filter to the query.
+ *
+ * Due to the nature of fapi, the value and the operator have an unintended
+ * level of indirection. You will find them in $this->operator
+ * and $this->value respectively.
+ */
+ function query() {
+ $this->ensure_my_table();
+ $field = "$this->table_alias.$this->real_field";
+
+ $info = $this->operators();
+ if (!empty($info[$this->operator]['method'])) {
+ $this->{$info[$this->operator]['method']}($field);
+ }
+ }
+
+ function op_equal($field) {
+ $this->query->add_where($this->options['group'], $field, $this->value, $this->operator());
+ }
+
+ function op_contains($field) {
+ $this->query->add_where($this->options['group'], $field, '%' . db_like($this->value) . '%', 'LIKE');
+ }
+
+ function op_word($field) {
+ $where = $this->operator == 'word' ? db_or() : db_and();
+
+ // Don't filter on empty strings.
+ if (empty($this->value)) {
+ return;
+ }
+
+ preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $this->value, $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ $phrase = false;
+ // Strip off phrase quotes
+ if ($match[2]{0} == '"') {
+ $match[2] = substr($match[2], 1, -1);
+ $phrase = true;
+ }
+ $words = trim($match[2], ',?!();:-');
+ $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($words as $word) {
+ $placeholder = $this->placeholder();
+ $where->condition($field, '%' . db_like(trim($word, " ,!?")) . '%', 'LIKE');
+ }
+ }
+
+ if (!$where) {
+ return;
+ }
+
+ // previously this was a call_user_func_array but that's unnecessary
+ // as views will unpack an array that is a single arg.
+ $this->query->add_where($this->options['group'], $where);
+ }
+
+ function op_starts($field) {
+ $this->query->add_where($this->options['group'], $field, db_like($this->value) . '%', 'LIKE');
+ }
+
+ function op_not_starts($field) {
+ $this->query->add_where($this->options['group'], $field, db_like($this->value) . '%', 'NOT LIKE');
+ }
+
+ function op_ends($field) {
+ $this->query->add_where($this->options['group'], $field, '%' . db_like($this->value), 'LIKE');
+ }
+
+ function op_not_ends($field) {
+ $this->query->add_where($this->options['group'], $field, '%' . db_like($this->value), 'NOT LIKE');
+ }
+
+ function op_not($field) {
+ $this->query->add_where($this->options['group'], $field, '%' . db_like($this->value) . '%', 'NOT LIKE');
+ }
+
+ function op_shorter($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "LENGTH($field) < $placeholder", array($placeholder => $this->value));
+ }
+
+ function op_longer($field) {
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression($this->options['group'], "LENGTH($field) > $placeholder", array($placeholder => $this->value));
+ }
+
+ function op_regex($field) {
+ $this->query->add_where($this->options['group'], $field, $this->value, 'RLIKE');
+ }
+
+ function op_empty($field) {
+ if ($this->operator == 'empty') {
+ $operator = "IS NULL";
+ }
+ else {
+ $operator = "IS NOT NULL";
+ }
+
+ $this->query->add_where($this->options['group'], $field, NULL, $operator);
+ }
+
+}
diff --git a/sites/all/modules/views/handlers/views_handler_relationship.inc b/sites/all/modules/views/handlers/views_handler_relationship.inc
new file mode 100644
index 000000000..695082b69
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_relationship.inc
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * @file
+ * Views' relationship handlers.
+ */
+
+/**
+ * @defgroup views_relationship_handlers Views relationship handlers
+ * @{
+ * Handlers to tell Views how to create alternate relationships.
+ */
+
+/**
+ * Simple relationship handler that allows a new version of the primary table
+ * to be linked in.
+ *
+ * The base relationship handler can only handle a single join. Some relationships
+ * are more complex and might require chains of joins; for those, you must
+ * utilize a custom relationship handler.
+ *
+ * Definition items:
+ * - base: The new base table this relationship will be adding. This does not
+ * have to be a declared base table, but if there are no tables that
+ * utilize this base table, it won't be very effective.
+ * - base field: The field to use in the relationship; if left out this will be
+ * assumed to be the primary field.
+ * - relationship table: The actual table this relationship operates against.
+ * This is analogous to using a 'table' override.
+ * - relationship field: The actual field this relationship operates against.
+ * This is analogous to using a 'real field' override.
+ * - label: The default label to provide for this relationship, which is
+ * shown in parentheses next to any field/sort/filter/argument that uses
+ * the relationship.
+ *
+ * @ingroup views_relationship_handlers
+ */
+class views_handler_relationship extends views_handler {
+ /**
+ * Init handler to let relationships live on tables other than
+ * the table they operate on.
+ */
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ if (isset($this->definition['relationship table'])) {
+ $this->table = $this->definition['relationship table'];
+ }
+ if (isset($this->definition['relationship field'])) {
+ // Set both real_field and field so custom handler
+ // can rely on the old field value.
+ $this->real_field = $this->field = $this->definition['relationship field'];
+ }
+ }
+
+ /**
+ * Get this field's label.
+ */
+ function label() {
+ if (!isset($this->options['label'])) {
+ return $this->ui_name();
+ }
+ return $this->options['label'];
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+
+ // Relationships definitions should define a default label, but if they aren't get another default value.
+ if (!empty($this->definition['label'])) {
+ $label = $this->definition['label'];
+ }
+ else {
+ $label = !empty($this->definition['field']) ? $this->definition['field'] : $this->definition['base field'];
+ }
+
+ $options['label'] = array('default' => $label, 'translatable' => TRUE);
+ $options['required'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Default options form that provides the label widget that all fields
+ * should have.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Identifier'),
+ '#default_value' => isset($this->options['label']) ? $this->options['label'] : '',
+ '#description' => t('Edit the administrative label displayed when referencing this relationship from filters, etc.'),
+ '#required' => TRUE,
+ );
+
+ $form['required'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Require this relationship'),
+ '#description' => t('Enable to hide items that do not contain this relationship'),
+ '#default_value' => !empty($this->options['required']),
+ );
+ }
+
+ /**
+ * Called to implement a relationship in a query.
+ */
+ function query() {
+ // Figure out what base table this relationship brings to the party.
+ $table_data = views_fetch_data($this->definition['base']);
+ $base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field'];
+
+ $this->ensure_my_table();
+
+ $def = $this->definition;
+ $def['table'] = $this->definition['base'];
+ $def['field'] = $base_field;
+ $def['left_table'] = $this->table_alias;
+ $def['left_field'] = $this->real_field;
+ if (!empty($this->options['required'])) {
+ $def['type'] = 'INNER';
+ }
+
+ if (!empty($this->definition['extra'])) {
+ $def['extra'] = $this->definition['extra'];
+ }
+
+ if (!empty($def['join_handler']) && class_exists($def['join_handler'])) {
+ $join = new $def['join_handler'];
+ }
+ else {
+ $join = new views_join();
+ }
+
+ $join->definition = $def;
+ $join->options = $this->options;
+ $join->construct();
+ $join->adjusted = TRUE;
+
+ // use a short alias for this:
+ $alias = $def['table'] . '_' . $this->table;
+
+ $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship);
+
+ // Add access tags if the base table provide it.
+ if (empty($this->query->options['disable_sql_rewrite']) && isset($table_data['table']['base']['access query tag'])) {
+ $access_tag = $table_data['table']['base']['access query tag'];
+ $this->query->add_tag($access_tag);
+ }
+ }
+
+ /**
+ * You can't groupby a relationship.
+ */
+ function use_group_by() {
+ return FALSE;
+ }
+}
+
+/**
+ * A special handler to take the place of missing or broken handlers.
+ *
+ * @ingroup views_relationship_handlers
+ */
+class views_handler_relationship_broken extends views_handler_relationship {
+ function ui_name($short = FALSE) {
+ return t('Broken/missing handler');
+ }
+
+ function ensure_my_table() { /* No table to ensure! */ }
+ function query() { /* No query to run */ }
+ function options_form(&$form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => '<div class="form-item description">' . t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.') . '</div>',
+ );
+ }
+
+ /**
+ * Determine if the handler is considered 'broken'
+ */
+ function broken() { return TRUE; }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/handlers/views_handler_relationship_groupwise_max.inc b/sites/all/modules/views/handlers/views_handler_relationship_groupwise_max.inc
new file mode 100644
index 000000000..23198c603
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_relationship_groupwise_max.inc
@@ -0,0 +1,382 @@
+<?php
+
+/**
+ * @file
+ * Relationship for groupwise maximum handler.
+ */
+
+/**
+ * Relationship handler that allows a groupwise maximum of the linked in table.
+ * For a definition, see:
+ * http://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html
+ * In lay terms, instead of joining to get all matching records in the linked
+ * table, we get only one record, a 'representative record' picked according
+ * to a given criteria.
+ *
+ * Example:
+ * Suppose we have a term view that gives us the terms: Horse, Cat, Aardvark.
+ * We wish to show for each term the most recent node of that term.
+ * What we want is some kind of relationship from term to node.
+ * But a regular relationship will give us all the nodes for each term,
+ * giving the view multiple rows per term. What we want is just one
+ * representative node per term, the node that is the 'best' in some way:
+ * eg, the most recent, the most commented on, the first in alphabetical order.
+ *
+ * This handler gives us that kind of relationship from term to node.
+ * The method of choosing the 'best' implemented with a sort
+ * that the user selects in the relationship settings.
+ *
+ * So if we want our term view to show the most commented node for each term,
+ * add the relationship and in its options, pick the 'Comment count' sort.
+ *
+ * Relationship definition
+ * - 'outer field': The outer field to substitute into the correlated subquery.
+ * This must be the full field name, not the alias.
+ * Eg: 'term_data.tid'.
+ * - 'argument table',
+ * 'argument field': These options define a views argument that the subquery
+ * must add to itself to filter by the main view.
+ * Example: the main view shows terms, this handler is being used to get to
+ * the nodes base table. Your argument must be 'term_node', 'tid', as this
+ * is the argument that should be added to a node view to filter on terms.
+ *
+ * A note on performance:
+ * This relationship uses a correlated subquery, which is expensive.
+ * Subsequent versions of this handler could also implement the alternative way
+ * of doing this, with a join -- though this looks like it could be pretty messy
+ * to implement. This is also an expensive method, so providing both methods and
+ * allowing the user to choose which one works fastest for their data might be
+ * the best way.
+ * If your use of this relationship handler is likely to result in large
+ * data sets, you might want to consider storing statistics in a separate table,
+ * in the same way as node_comment_statistics.
+ *
+ * @ingroup views_relationship_handlers
+ */
+class views_handler_relationship_groupwise_max extends views_handler_relationship {
+
+ /**
+ * Defines default values for options.
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['subquery_sort'] = array('default' => NULL);
+ // Descending more useful.
+ $options['subquery_order'] = array('default' => 'DESC');
+ $options['subquery_regenerate'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['subquery_view'] = array('default' => FALSE);
+ $options['subquery_namespace'] = array('default' => FALSE);
+
+ return $options;
+ }
+
+ /**
+ * Extends the relationship's basic options, allowing the user to pick
+ * a sort and an order for it.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ // Get the sorts that apply to our base.
+ $sorts = views_fetch_fields($this->definition['base'], 'sort');
+ foreach ($sorts as $sort_id => $sort) {
+ $sort_options[$sort_id] = "$sort[group]: $sort[title]";
+ }
+ $base_table_data = views_fetch_data($this->definition['base']);
+
+ $form['subquery_sort'] = array(
+ '#type' => 'select',
+ '#title' => t('Representative sort criteria'),
+ // Provide the base field as sane default sort option.
+ '#default_value' => !empty($this->options['subquery_sort']) ? $this->options['subquery_sort'] : $this->definition['base'] . '.' . $base_table_data['table']['base']['field'],
+ '#options' => $sort_options,
+ '#description' => theme('advanced_help_topic', array('module' => 'views', 'topic' => 'relationship-representative')) .
+ t("The sort criteria is applied to the data brought in by the relationship to determine how a representative item is obtained for each row. For example, to show the most recent node for each user, pick 'Content: Updated date'."),
+ );
+
+ $form['subquery_order'] = array(
+ '#type' => 'radios',
+ '#title' => t('Representative sort order'),
+ '#description' => t("The ordering to use for the sort criteria selected above."),
+ '#options' => array('ASC' => t('Ascending'), 'DESC' => t('Descending')),
+ '#default_value' => $this->options['subquery_order'],
+ );
+
+ $form['subquery_namespace'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Subquery namespace'),
+ '#description' => t('Advanced. Enter a namespace for the subquery used by this relationship.'),
+ '#default_value' => $this->options['subquery_namespace'],
+ );
+
+
+ // WIP: This stuff doens't work yet: namespacing issues.
+ // A list of suitable views to pick one as the subview.
+ $views = array('' => '<none>');
+ $all_views = views_get_all_views();
+ foreach ($all_views as $view) {
+ // Only get views that are suitable:
+ // - base must the base that our relationship joins towards
+ // - must have fields.
+ if ($view->base_table == $this->definition['base'] && !empty($view->display['default']->display_options['fields'])) {
+ // TODO: check the field is the correct sort?
+ // or let users hang themselves at this stage and check later?
+ if ($view->type == 'Default') {
+ $views[t('Default Views')][$view->name] = $view->name;
+ }
+ else {
+ $views[t('Existing Views')][$view->name] = $view->name;
+ }
+ }
+ }
+
+ $form['subquery_view'] = array(
+ '#type' => 'select',
+ '#title' => t('Representative view'),
+ '#default_value' => $this->options['subquery_view'],
+ '#options' => $views,
+ '#description' => t('Advanced. Use another view to generate the relationship subquery. This allows you to use filtering and more than one sort. If you pick a view here, the sort options above are ignored. Your view must have the ID of its base as its only field, and should have some kind of sorting.'),
+ );
+
+ $form['subquery_regenerate'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Generate subquery each time view is run.'),
+ '#default_value' => $this->options['subquery_regenerate'],
+ '#description' => t('Will re-generate the subquery for this relationship every time the view is run, instead of only when these options are saved. Use for testing if you are making changes elsewhere. WARNING: seriously impairs performance.'),
+ );
+ }
+
+ /**
+ * Helper function to create a pseudo view.
+ *
+ * We use this to obtain our subquery SQL.
+ */
+ function get_temporary_view() {
+ views_include('view');
+ $view = new view();
+ $view->vid = 'new'; // @todo: what's this?
+ $view->base_table = $this->definition['base'];
+ $view->add_display('default');
+ return $view;
+ }
+
+ /**
+ * When the form is submitted, take sure to clear the subquery string cache.
+ */
+ function options_form_submit(&$form, &$form_state) {
+ $cid = 'views_relationship_groupwise_max:' . $this->view->name . ':' . $this->view->current_display . ':' . $this->options['id'];
+ cache_clear_all($cid, 'cache_views_data');
+ }
+
+ /**
+ * Generate a subquery given the user options, as set in the options.
+ * These are passed in rather than picked up from the object because we
+ * generate the subquery when the options are saved, rather than when the view
+ * is run. This saves considerable time.
+ *
+ * @param $options
+ * An array of options:
+ * - subquery_sort: the id of a views sort.
+ * - subquery_order: either ASC or DESC.
+ * @return
+ * The subquery SQL string, ready for use in the main query.
+ */
+ function left_query($options) {
+ // Either load another view, or create one on the fly.
+ if ($options['subquery_view']) {
+ $temp_view = views_get_view($options['subquery_view']);
+ // Remove all fields from default display
+ unset($temp_view->display['default']->display_options['fields']);
+ }
+ else {
+ // Create a new view object on the fly, which we use to generate a query
+ // object and then get the SQL we need for the subquery.
+ $temp_view = $this->get_temporary_view();
+
+ // Add the sort from the options to the default display.
+ // This is broken, in that the sort order field also gets added as a
+ // select field. See http://drupal.org/node/844910.
+ // We work around this further down.
+ $sort = $options['subquery_sort'];
+ list($sort_table, $sort_field) = explode('.', $sort);
+ $sort_options = array('order' => $options['subquery_order']);
+ $temp_view->add_item('default', 'sort', $sort_table, $sort_field, $sort_options);
+ }
+
+ // Get the namespace string.
+ $temp_view->namespace = (!empty($options['subquery_namespace'])) ? '_'. $options['subquery_namespace'] : '_INNER';
+ $this->subquery_namespace = (!empty($options['subquery_namespace'])) ? '_'. $options['subquery_namespace'] : 'INNER';
+
+ // The value we add here does nothing, but doing this adds the right tables
+ // and puts in a WHERE clause with a placeholder we can grab later.
+ $temp_view->args[] = '**CORRELATED**';
+
+ // Add the base table ID field.
+ $views_data = views_fetch_data($this->definition['base']);
+ $base_field = $views_data['table']['base']['field'];
+ $temp_view->add_item('default', 'field', $this->definition['base'], $this->definition['field']);
+
+ // Add the correct argument for our relationship's base
+ // ie the 'how to get back to base' argument.
+ // The relationship definition tells us which one to use.
+ $temp_view->add_item(
+ 'default',
+ 'argument',
+ $this->definition['argument table'], // eg 'term_node',
+ $this->definition['argument field'] // eg 'tid'
+ );
+
+ // Build the view. The creates the query object and produces the query
+ // string but does not run any queries.
+ $temp_view->build();
+
+ // Now take the SelectQuery object the View has built and massage it
+ // somewhat so we can get the SQL query from it.
+ $subquery = $temp_view->build_info['query'];
+
+ // Workaround until http://drupal.org/node/844910 is fixed:
+ // Remove all fields from the SELECT except the base id.
+ $fields =& $subquery->getFields();
+ foreach (array_keys($fields) as $field_name) {
+ // The base id for this subquery is stored in our definition.
+ if ($field_name != $this->definition['field']) {
+ unset($fields[$field_name]);
+ }
+ }
+
+ // Make every alias in the subquery safe within the outer query by
+ // appending a namespace to it, '_inner' by default.
+ $tables =& $subquery->getTables();
+ foreach (array_keys($tables) as $table_name) {
+ $tables[$table_name]['alias'] .= $this->subquery_namespace;
+ // Namespace the join on every table.
+ if (isset($tables[$table_name]['condition'])) {
+ $tables[$table_name]['condition'] = $this->condition_namespace($tables[$table_name]['condition']);
+ }
+ }
+ // Namespace fields.
+ foreach (array_keys($fields) as $field_name) {
+ $fields[$field_name]['table'] .= $this->subquery_namespace;
+ $fields[$field_name]['alias'] .= $this->subquery_namespace;
+ }
+ // Namespace conditions.
+ $where =& $subquery->conditions();
+ $this->alter_subquery_condition($subquery, $where);
+ // Not sure why, but our sort order clause doesn't have a table.
+ // TODO: the call to add_item() above to add the sort handler is probably
+ // wrong -- needs attention from someone who understands it.
+ // In the meantime, this works, but with a leap of faith...
+ $orders =& $subquery->getOrderBy();
+ foreach ($orders as $order_key => $order) {
+ // But if we're using a whole view, we don't know what we have!
+ if ($options['subquery_view']) {
+ list($sort_table, $sort_field) = explode('.', $order_key);
+ }
+ $orders[$sort_table . $this->subquery_namespace . '.' . $sort_field] = $order;
+ unset($orders[$order_key]);
+ }
+
+ // The query we get doesn't include the LIMIT, so add it here.
+ $subquery->range(0, 1);
+
+ // Extract the SQL the temporary view built.
+ $subquery_sql = $subquery->__toString();
+
+ // Replace the placeholder with the outer, correlated field.
+ // Eg, change the placeholder ':users_uid' into the outer field 'users.uid'.
+ // We have to work directly with the SQL, because putting a name of a field
+ // into a SelectQuery that it does not recognize (because it's outer) just
+ // makes it treat it as a string.
+ $outer_placeholder = ':' . str_replace('.', '_', $this->definition['outer field']);
+ $subquery_sql = str_replace($outer_placeholder, $this->definition['outer field'], $subquery_sql);
+
+ return $subquery_sql;
+ }
+
+ /**
+ * Recursive helper to add a namespace to conditions.
+ *
+ * Similar to _views_query_tag_alter_condition().
+ *
+ * (Though why is the condition we get in a simple query 3 levels deep???)
+ */
+ function alter_subquery_condition(QueryAlterableInterface $query, &$conditions) {
+ foreach ($conditions as $condition_id => &$condition) {
+ // Skip the #conjunction element.
+ if (is_numeric($condition_id)) {
+ if (is_string($condition['field'])) {
+ $condition['field'] = $this->condition_namespace($condition['field']);
+ }
+ elseif (is_object($condition['field'])) {
+ $sub_conditions =& $condition['field']->conditions();
+ $this->alter_subquery_condition($query, $sub_conditions);
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function to namespace query pieces.
+ *
+ * Turns 'foo.bar' into 'foo_NAMESPACE.bar'.
+ */
+ function condition_namespace($string) {
+ return str_replace('.', $this->subquery_namespace . '.', $string);
+ }
+
+ /**
+ * Called to implement a relationship in a query.
+ * This is mostly a copy of our parent's query() except for this bit with
+ * the join class.
+ */
+ function query() {
+ // Figure out what base table this relationship brings to the party.
+ $table_data = views_fetch_data($this->definition['base']);
+ $base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field'];
+
+ $this->ensure_my_table();
+
+ $def = $this->definition;
+ $def['table'] = $this->definition['base'];
+ $def['field'] = $base_field;
+ $def['left_table'] = $this->table_alias;
+ $def['left_field'] = $this->field;
+ if (!empty($this->options['required'])) {
+ $def['type'] = 'INNER';
+ }
+
+ if ($this->options['subquery_regenerate']) {
+ // For testing only, regenerate the subquery each time.
+ $def['left_query'] = $this->left_query($this->options);
+ }
+ else {
+ // Get the stored subquery SQL string.
+ $cid = 'views_relationship_groupwise_max:' . $this->view->name . ':' . $this->view->current_display . ':' . $this->options['id'];
+ $cache = cache_get($cid, 'cache_views_data');
+ if (isset($cache->data)) {
+ $def['left_query'] = $cache->data;
+ }
+ else {
+ $def['left_query'] = $this->left_query($this->options);
+ cache_set($cid, $def['left_query'], 'cache_views_data');
+ }
+ }
+
+ if (!empty($def['join_handler']) && class_exists($def['join_handler'])) {
+ $join = new $def['join_handler'];
+ }
+ else {
+ $join = new views_join_subquery();
+ }
+
+ $join->definition = $def;
+ $join->construct();
+ $join->adjusted = TRUE;
+
+ // use a short alias for this:
+ $alias = $def['table'] . '_' . $this->table;
+
+ $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship);
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_sort.inc b/sites/all/modules/views/handlers/views_handler_sort.inc
new file mode 100644
index 000000000..99574e4dd
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_sort.inc
@@ -0,0 +1,240 @@
+<?php
+
+/**
+ * @file
+ * @todo.
+ */
+
+/**
+ * @defgroup views_sort_handlers Views sort handlers
+ * @{
+ * Handlers to tell Views how to sort queries.
+ */
+
+/**
+ * Base sort handler that has no options and performs a simple sort.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort extends views_handler {
+
+ /**
+ * Determine if a sort can be exposed.
+ */
+ function can_expose() { return TRUE; }
+
+ /**
+ * Called to add the sort to a query.
+ */
+ function query() {
+ $this->ensure_my_table();
+ // Add the field.
+ $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['order'] = array('default' => 'ASC');
+ $options['exposed'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['expose'] = array(
+ 'contains' => array(
+ 'label' => array('default' => '', 'translatable' => TRUE),
+ ),
+ );
+ return $options;
+ }
+
+ /**
+ * Display whether or not the sort order is ascending or descending
+ */
+ function admin_summary() {
+ if (!empty($this->options['exposed'])) {
+ return t('Exposed');
+ }
+ switch ($this->options['order']) {
+ case 'ASC':
+ case 'asc':
+ default:
+ return t('asc');
+ break;
+ case 'DESC';
+ case 'desc';
+ return t('desc');
+ break;
+ }
+ }
+
+ /**
+ * Basic options for all sort criteria
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ if ($this->can_expose()) {
+ $this->show_expose_button($form, $form_state);
+ }
+ $form['op_val_start'] = array('#value' => '<div class="clearfix">');
+ $this->show_sort_form($form, $form_state);
+ $form['op_val_end'] = array('#value' => '</div>');
+ if ($this->can_expose()) {
+ $this->show_expose_form($form, $form_state);
+ }
+ }
+
+ /**
+ * Shortcut to display the expose/hide button.
+ */
+ function show_expose_button(&$form, &$form_state) {
+ $form['expose_button'] = array(
+ '#prefix' => '<div class="views-expose clearfix">',
+ '#suffix' => '</div>',
+ // Should always come first
+ '#weight' => -1000,
+ );
+
+ // Add a checkbox for JS users, which will have behavior attached to it
+ // so it can replace the button.
+ $form['expose_button']['checkbox'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('js-only')),
+ );
+ $form['expose_button']['checkbox']['checkbox'] = array(
+ '#title' => t('Expose this sort to visitors, to allow them to change it'),
+ '#type' => 'checkbox',
+ );
+
+ // Then add the button itself.
+ if (empty($this->options['exposed'])) {
+ $form['expose_button']['markup'] = array(
+ '#markup' => '<div class="description exposed-description" style="float: left; margin-right:10px">' . t('This sort is not exposed. Expose it to allow the users to change it.') . '</div>',
+ );
+ $form['expose_button']['button'] = array(
+ '#limit_validation_errors' => array(),
+ '#type' => 'submit',
+ '#value' => t('Expose sort'),
+ '#submit' => array('views_ui_config_item_form_expose'),
+ );
+ $form['expose_button']['checkbox']['checkbox']['#default_value'] = 0;
+ }
+ else {
+ $form['expose_button']['markup'] = array(
+ '#markup' => '<div class="description exposed-description">' . t('This sort is exposed. If you hide it, users will not be able to change it.') . '</div>',
+ );
+ $form['expose_button']['button'] = array(
+ '#limit_validation_errors' => array(),
+ '#type' => 'submit',
+ '#value' => t('Hide sort'),
+ '#submit' => array('views_ui_config_item_form_expose'),
+ );
+ $form['expose_button']['checkbox']['checkbox']['#default_value'] = 1;
+ }
+ }
+
+ /**
+ * Simple validate handler
+ */
+ function options_validate(&$form, &$form_state) {
+ $this->sort_validate($form, $form_state);
+ if (!empty($this->options['exposed'])) {
+ $this->expose_validate($form, $form_state);
+ }
+
+ }
+
+ /**
+ * Simple submit handler
+ */
+ function options_submit(&$form, &$form_state) {
+ unset($form_state['values']['expose_button']); // don't store this.
+ $this->sort_submit($form, $form_state);
+ if (!empty($this->options['exposed'])) {
+ $this->expose_submit($form, $form_state);
+ }
+ }
+
+ /**
+ * Shortcut to display the value form.
+ */
+ function show_sort_form(&$form, &$form_state) {
+ $options = $this->sort_options();
+ if (!empty($options)) {
+ $form['order'] = array(
+ '#type' => 'radios',
+ '#options' => $options,
+ '#default_value' => $this->options['order'],
+ );
+ }
+ }
+
+ function sort_validate(&$form, &$form_state) { }
+
+ function sort_submit(&$form, &$form_state) { }
+
+ /**
+ * Provide a list of options for the default sort form.
+ * Should be overridden by classes that don't override sort_form
+ */
+ function sort_options() {
+ return array(
+ 'ASC' => t('Sort ascending'),
+ 'DESC' => t('Sort descending'),
+ );
+ }
+
+ function expose_form(&$form, &$form_state) {
+ // #flatten will move everything from $form['expose'][$key] to $form[$key]
+ // prior to rendering. That's why the pre_render for it needs to run first,
+ // so that when the next pre_render (the one for fieldsets) runs, it gets
+ // the flattened data.
+ array_unshift($form['#pre_render'], 'views_ui_pre_render_flatten_data');
+ $form['expose']['#flatten'] = TRUE;
+
+ $form['expose']['label'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $this->options['expose']['label'],
+ '#title' => t('Label'),
+ '#required' => TRUE,
+ '#size' => 40,
+ '#weight' => -1,
+ );
+ }
+
+ /**
+ * Provide default options for exposed sorts.
+ */
+ function expose_options() {
+ $this->options['expose'] = array(
+ 'order' => $this->options['order'],
+ 'label' => $this->definition['title'],
+ );
+ }
+}
+
+/**
+ * A special handler to take the place of missing or broken handlers.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_broken extends views_handler_sort {
+ function ui_name($short = FALSE) {
+ return t('Broken/missing handler');
+ }
+
+ function ensure_my_table() { /* No table to ensure! */ }
+ function query($group_by = FALSE) { /* No query to run */ }
+ function options_form(&$form, &$form_state) {
+ $form['markup'] = array(
+ '#markup' => '<div class="form-item description">' . t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.') . '</div>',
+ );
+ }
+
+ /**
+ * Determine if the handler is considered 'broken'
+ */
+ function broken() { return TRUE; }
+}
+
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/handlers/views_handler_sort_date.inc b/sites/all/modules/views/handlers/views_handler_sort_date.inc
new file mode 100644
index 000000000..b37cc41c2
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_sort_date.inc
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_sort_date.
+ */
+
+/**
+ * Basic sort handler for dates.
+ *
+ * This handler enables granularity, which is the ability to make dates
+ * equivalent based upon nearness.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_date extends views_handler_sort {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['granularity'] = array('default' => 'second');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['granularity'] = array(
+ '#type' => 'radios',
+ '#title' => t('Granularity'),
+ '#options' => array(
+ 'second' => t('Second'),
+ 'minute' => t('Minute'),
+ 'hour' => t('Hour'),
+ 'day' => t('Day'),
+ 'month' => t('Month'),
+ 'year' => t('Year'),
+ ),
+ '#description' => t('The granularity is the smallest unit to use when determining whether two dates are the same; for example, if the granularity is "Year" then all dates in 1999, regardless of when they fall in 1999, will be considered the same date.'),
+ '#default_value' => $this->options['granularity'],
+ );
+ }
+
+ /**
+ * Called to add the sort to a query.
+ */
+ function query() {
+ $this->ensure_my_table();
+ switch ($this->options['granularity']) {
+ case 'second':
+ default:
+ $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']);
+ return;
+ case 'minute':
+ $formula = views_date_sql_format('YmdHi', "$this->table_alias.$this->real_field");
+ break;
+ case 'hour':
+ $formula = views_date_sql_format('YmdH', "$this->table_alias.$this->real_field");
+ break;
+ case 'day':
+ $formula = views_date_sql_format('Ymd', "$this->table_alias.$this->real_field");
+ break;
+ case 'month':
+ $formula = views_date_sql_format('Ym', "$this->table_alias.$this->real_field");
+ break;
+ case 'year':
+ $formula = views_date_sql_format('Y', "$this->table_alias.$this->real_field");
+ break;
+ }
+
+ // Add the field.
+ $this->query->add_orderby(NULL, $formula, $this->options['order'], $this->table_alias . '_' . $this->field . '_' . $this->options['granularity']);
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_sort_group_by_numeric.inc b/sites/all/modules/views/handlers/views_handler_sort_group_by_numeric.inc
new file mode 100644
index 000000000..4a521c158
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_sort_group_by_numeric.inc
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_sort_group_by_numeric.
+ */
+
+/**
+ * Handler for GROUP BY on simple numeric fields.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_group_by_numeric extends views_handler_sort {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ // Initialize the original handler.
+ $this->handler = views_get_handler($options['table'], $options['field'], 'sort');
+ $this->handler->init($view, $options);
+ }
+
+ /**
+ * Called to add the field to a query.
+ */
+ function query() {
+ $this->ensure_my_table();
+
+ $params = array(
+ 'function' => $this->options['group_type'],
+ );
+
+ $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order'], NULL, $params);
+ }
+
+ function ui_name($short = FALSE) {
+ return $this->get_field(parent::ui_name($short));
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_sort_menu_hierarchy.inc b/sites/all/modules/views/handlers/views_handler_sort_menu_hierarchy.inc
new file mode 100644
index 000000000..dacb1feec
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_sort_menu_hierarchy.inc
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_sort_menu_hierarchy.
+ */
+
+/**
+ * Sort in menu hierarchy order.
+ *
+ * Given a field name of 'p' this produces an ORDER BY on p1, p2, ..., p9;
+ * and optionally injects multiple joins to {menu_links} to sort by weight
+ * and title as well.
+ *
+ * This is only really useful for the {menu_links} table.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_menu_hierarchy extends views_handler_sort {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['sort_within_level'] = array('default' => FALSE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['sort_within_level'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Sort within each hierarchy level'),
+ '#description' => t('Enable this to sort the items within each level of the hierarchy by weight and title. Warning: this may produce a slow query.'),
+ '#default_value' => $this->options['sort_within_level'],
+ );
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $max_depth = isset($this->definition['max depth']) ? $this->definition['max depth'] : MENU_MAX_DEPTH;
+ for ($i = 1; $i <= $max_depth; ++$i) {
+ if ($this->options['sort_within_level']) {
+ $join = new views_join();
+ $join->construct('menu_links', $this->table_alias, $this->field . $i, 'mlid');
+ $menu_links = $this->query->add_table('menu_links', NULL, $join);
+ $this->query->add_orderby($menu_links, 'weight', $this->options['order']);
+ $this->query->add_orderby($menu_links, 'link_title', $this->options['order']);
+ }
+
+ // We need this even when also sorting by weight and title, to make sure
+ // that children of two parents with the same weight and title are
+ // correctly separated.
+ $this->query->add_orderby($this->table_alias, $this->field . $i, $this->options['order']);
+ }
+ }
+}
diff --git a/sites/all/modules/views/handlers/views_handler_sort_random.inc b/sites/all/modules/views/handlers/views_handler_sort_random.inc
new file mode 100644
index 000000000..eaaaf790f
--- /dev/null
+++ b/sites/all/modules/views/handlers/views_handler_sort_random.inc
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_sort_random.
+ */
+
+/**
+ * Handle a random sort.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_random extends views_handler_sort {
+ function query() {
+ $this->query->add_orderby('rand');
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['order']['#access'] = FALSE;
+ }
+}
diff --git a/sites/all/modules/views/help/about.html b/sites/all/modules/views/help/about.html
new file mode 100644
index 000000000..8ba77f86d
--- /dev/null
+++ b/sites/all/modules/views/help/about.html
@@ -0,0 +1,62 @@
+The views module allows administrators and site designers to create, manage, and display lists of content. Each list managed by the views module is known as a "view", and the output of a view is known as a "display". Displays are provided in either block or page form, and a single view may have multiple displays. Optional navigation aids, including a system path and menu item, can be set for each page-based display of a view. By default, views may be created that list content (a <em>Node</em> view type), content revisions (a <em>Node revisions</em> view type) or users (a <em>User</em> view type). A view may be restricted to members of specific user roles, and may be added, edited or deleted at the <a href="base_url:admin/structure/views">views administration page</a>.
+
+For more technical users, views can be understood as a user interface to compose SQL-queries and to pull information (Content, Users, etc.) from the database and show it on a screen as desired.
+
+The "building block" design of the views system provides power and flexibility, allowing parameters to be specified only when needed. While an advanced view may use all of available parameters to create complex and highly interactive applications, a simple content listing may specify only a few options. All views rely on a conceptual framework that includes:
+
+<ul>
+ <li><a href="topic:views/field">Fields</a>, or the individual pieces of data being displayed. Adding the fields <em>Node: Title</em>, <em>Node: Type</em>, and <em>Node: Post date</em> to a node view, for example, includes the title, content type and creation date in the displayed results </li>
+
+ <li><a href="topic:views/relationship">Relationships</a>, or information about how data elements relate to one another. If relationship data is available, like that provided by a CCK <em>nodereference</em> field, items from a related node may be included in the view </li>
+
+ <li><a href="topic:views/argument">Arguments</a>, or additional parameters that dynamically refine the view results, passed as part of the path. Adding an argument of <em>Node: Type</em> to a node view with a path of "content", for example, dynamically filters the displayed items by content type. In this example (shown with Clean URLs enabled), accessing the view through the path "<em>http://www.example.com/content/page</em>" displays all posts of the type "page", the path "<em>http://www.example.com/content/story</em>" displays all posts of the type "story", and "<em>http://www.example.com/content</em>" displays all posts regardless of type) </li>
+
+ <li><a href="topic:views/sort">Sort criteria</a>, which determine the order of items displayed in the view results. Adding the sort criteria <em>Node: Post date</em> (in descending order) to a node <em>view</em>, for example, sorts the displayed posts in descending order by creation date </li>
+
+ <li><a href="topic:views/filter">Filters</a>, which limit items displayed in the results. Adding the filter <em>Node: Published</em> (and setting it equal to "Published") to a node view, for example, prevents unpublished items from being displayed</li>
+
+ <li><a href="topic:views/display">Displays</a>, which control where the output will be seen. Every view has a default display, which doesn't actually display the view anywhere, but is used to hold the default settings for the view, and is used when the view is called programmatically if another display is not specified. Much more useful to users are the <a href="topic:views/display-page">page</a> display, which gives a view a path and allows it to be the primary content of a page, or the <a href="topic:views/display-block">block</a> display which allows it to appear as secondary content on other pages.</li>
+
+<li><a href="topic:views/header">Header</a>, which allow you to add by default one or more text area above the views output. </li>
+
+ <li><a href="topic:views/footer">Footer</a>, which allow you to add by default one or more text area beneath the views output. </li>
+
+ <li>The <a href="topic:views/footer">Emtpy Text</a> content will be displayed, when you choose in the Arguments Section "Action to take if argument is not present" the option "Display empty text".</li>
+
+
+</ul>
+
+Parallels between the components in the Views interface and an sql query:
+
+<table>
+ <thead>
+ <tr>
+ <th>Sql Query</th>
+ <th>Views Component</th>
+ <tr>
+ </thead>
+ <tr>
+ <td>SELECT n.title, u.name</td>
+ <td>fields</td>
+ </tr>
+ <tr>
+ <td>FROM {node} n base table</td>
+ <td>view type</td>
+ </tr>
+ <tr>
+ <td>INNER JOIN {users} u ON n.uid = u.uid</td>
+ <td>relationship</td>
+ </tr>
+ <tr>
+ <td>WHERE n.status = 1</td>
+ <td>filter</td>
+ </tr>
+ <tr>
+ <td>AND u.uid = arg(1)</td>
+ <td>argument</td>
+ </tr>
+ <tr>
+ <td>ORDER BY n.changed DESC</td>
+ <td>sort</td>
+ </tr>
+</table>
diff --git a/sites/all/modules/views/help/advanced-settings.html b/sites/all/modules/views/help/advanced-settings.html
new file mode 100644
index 000000000..6a1685900
--- /dev/null
+++ b/sites/all/modules/views/help/advanced-settings.html
@@ -0,0 +1,43 @@
+In the category <strong>Other</strong> you have different options to set Advanced configurations in your View.
+
+<strong>Machine Name:</strong>
+You can change the default machine name of the view.
+
+<strong>Comment: No comment</strong>
+You can Use the comment option to write comments for your Views, the comments are only shown in the Views UI. Comment your Display for other Maintainer
+
+<strong>Use AJAX: No</strong>
+If set, this view will use an AJAX mechanism for paging, table sorting and exposed filters. This prevents the entire page from refreshing. It is not recommended that you use this if this view is the main content of the page as it will prevent deep linking to specific pages, but it is very useful for side content. Block displays require this setting to be ON if exposed filters are being used.
+
+<strong>Hide attachments in summary: No</strong>
+
+<strong>Use aggregation: No</strong>
+All fields that are selected for grouping will be collapsed to one record per distinct value. Other fields which are selected for aggregation will have the function run on them. For example, you can group nodes on title and count the number of nids in order to get a list of duplicate titles.
+For more Information how aggregation work see the "Use Aggregation" Help Page
+
+<strong>Query settings: Settings</strong>
+
+Here can you set advanced Settings for the SQL Settings
+<ul>
+ <li><strong>Disable SQL rewriting</strong></li>
+
+ <li><strong>Distinct: No</strong></li>
+ This will make the view display only distinct items. If there are multiple identical items, each will be displayed only once. You can use this to try and remove duplicates from a view, though it does not always work. Note that this can slow queries down, so use it with caution.
+
+ <li><strong>Use Slave Server</strong></li>
+
+ <li><strong>Query Comment</strong></li>
+</ul>
+<strong>Field Language: Current user's language</strong>
+
+<strong>Caching: None</strong>
+You can choose a "Time-based" Caching if you want. With it you get the option to choose the length of time raw query results should be cached and "The length of time rendered HTML output should be cached."
+
+<strong>Link display: Page</strong>
+
+<strong>CSS class: None</strong>
+You can define some own CSS Classes for your View
+
+<strong>Theme: Information</strong>
+Clicking on the "Theme: Information" link provides you with a listing of all posiible theming files. The highlighted files are the ones Views is currently using. All other filenames are suggested templates.
+For more Information see the <a href="/help/views/analyze-theme" title="Theme Information" >"Theme information"</a> Page
diff --git a/sites/all/modules/views/help/advanced-style-settings.html b/sites/all/modules/views/help/advanced-style-settings.html
new file mode 100644
index 000000000..9c90c0f71
--- /dev/null
+++ b/sites/all/modules/views/help/advanced-style-settings.html
@@ -0,0 +1,30 @@
+In Views 3 you can set Advanced Style Settings, they help you to insert markup of your own from the Views UI, so that you can fairly easily override the default markup without having to restyle via templates.
+
+
+<ul>
+<li> Customize field HTML</li>
+With Customize field HTML you can generate html tags around the field.
+
+<li>Customize label HTML</li>
+Here you can generate html tags around the label of a field.
+
+<li>Customize field and label wrapper HTML</li>
+Here you can generate html tags around the wrapper of the label and field
+</ul>
+
+<a href="path:images/views3-semanticviews.png"><img src="path:images/views3-semanticviews.png" /></a>
+
+Usage example
+
+In a view with a field:
+<ol>
+<li>Configure the field. (Click on the field.)</li>
+
+<li>In the modal that opens, scroll down to <strong>Style Settings</strong>.</li>
+
+<li>Choose one or more of the three <i>Customize</i> options. This will reveal a dropdown menu where you can choose from one or more HTML tags to use on that field and allow you to add a CSS class specific to that field should you desire.</li>
+
+<li>Decide if you want to keep the Views default classes. Unchecking this box means your markup is *it*.</li>
+</ol>
+
+In Views 2 you needed for Style Settings the <a href="http://drupal.org/project/semanticviews">Semantic Views</a> Modul, now the Semantic Views module has been mostly incorporated into Views 3.x. Semantic Views is still around for people who need it, though. For some details on how the original module is different from the Views implementation, please see <a href="http://drupal.org/node/1013876">this issue</a>.
diff --git a/sites/all/modules/views/help/aggregation.html b/sites/all/modules/views/help/aggregation.html
new file mode 100644
index 000000000..83ad880d8
--- /dev/null
+++ b/sites/all/modules/views/help/aggregation.html
@@ -0,0 +1 @@
+See: <a href="topic:views/group-by">Group by</a>
diff --git a/sites/all/modules/views/help/alter-exposed-filter.html b/sites/all/modules/views/help/alter-exposed-filter.html
new file mode 100644
index 000000000..454994f71
--- /dev/null
+++ b/sites/all/modules/views/help/alter-exposed-filter.html
@@ -0,0 +1,31 @@
+Modifying default values of a views exposed form is tricky, because FAPI was not designed to work with GET forms. One consequence is that it often can't tell if textfields (there are others) were submitted or not.
+
+As a consequence, it *always* thinks the value was submitted, even if it was not. To fix that, Views modifies $form_state['input'][$identifier] with the default value if $form_state['input'][$identifier] was not set. In order to modify the default value in an alter, you need to do this:
+
+<pre>
+&lt;?php
+if (empty($form_state['view']->exposed_input[$identifier])) .
+ { $form_state['input'][$identifier] = $default_value; }
+?&gt;
+</pre>
+
+where $identifier is the particular filter for which you want to change the default value, and $default_value is the new default value you want to set.
+
+If you use a hook_form_FORM_ID_alter or hook_form_alter, you can modify exposed filters on the fly based on information that is external to Views. For example, I modified the exposed filter of a form to set a taxonomy term ID based on the user's GeoIP.
+
+To do this, I used the following function, where geoip_redirect_get_tid() loads the relevant term id based on the user's current ip_address():
+
+<pre>
+&lt;?php
+function MODULENAME_form_views_exposed_form_alter(&$form, $form_state) {
+ if(strpos($form['#id'], 'volunteer-directory') !== FALSE) {
+ $city_tid = geoip_redirect_get_tid();
+ if(is_numeric($city_tid) && $city_tid != 7660) {
+ if (empty($form_state['view']->exposed_input['tid'])) {
+ $form_state['input']['tid'] = $city_tid;
+ }
+ }
+ }
+}
+?&gt;
+</pre>
diff --git a/sites/all/modules/views/help/analyze-theme.html b/sites/all/modules/views/help/analyze-theme.html
new file mode 100644
index 000000000..290dc0749
--- /dev/null
+++ b/sites/all/modules/views/help/analyze-theme.html
@@ -0,0 +1,24 @@
+<p>Clicking on the "Theme: Information" link provides you with a listing of all posiible theming files. The highlighted files are the ones Views is currently using. All other filenames are suggested templates.</p>
+<p>You may use any of the following possible theme files to modify individual parts of your view. In total, there are four parts to theming a view.</p>
+<ul>
+ <li> The <strong>display</strong> theme is usually views-view.tpl.php and it largely controls the decorations around a view; where the header, footer, pager, more link, feed icon, etc, will be placed. </li>
+
+ <li> The <strong>style</strong> will control how all of the results of the display are put together. It may be as simple as just displaying all of the rows, or it may be a complex table generator or something in between. </li>
+
+ <li> The <strong>row</strong> style controls each individual row; not all styles utilize the row style (notably the table), but most others do.
+
+ <li> Finally, <strong>field</strong> themes allow you to override the look and even the data of each individual field, if the style uses fields. The actual template the system will use should be hilighted in <strong>bold</strong>.</li>
+</ul>
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/style-breakdown-large.png"><img src="path:images/style-breakdown.png" /></a>
+<em>A breakdown of View output</em>
+</div>
+
+<p>The link to the left of each type will give you information about the default template used for that type. You may cut and paste this and place it in your theme with the appropriate template, or you may copy the base file from the views/theme directory (or, if provided by a module, from the module's directory). <strong>It is important that you clear the theme registry cache every time you add a new template, or the new template will not be picked up.</strong></p>
+
+<p><strong>Important note:</strong> You place your custom template files in your theme directory, <strong>not views/theme</strong>. This is always true of theming with Drupal.
+
+<p>In addition to this tool, the very useful <a href="http://drupal.org/project/devel">devel</a> module contains a tool called the "Theme developer" which does a good job of visually showing you which areas of your site use which themes. Be careful with it, though, as the theme developer causes the Views edit page to break.</p>
+
+<p>Also, this feature will only work properly with Drupal 6.3 and later; prior to Drupal 6.3 <a href="http://drupal.org/node/241570">this patch</a> will be required.</p>
diff --git a/sites/all/modules/views/help/api-default-views.html b/sites/all/modules/views/help/api-default-views.html
new file mode 100644
index 000000000..f676ed86f
--- /dev/null
+++ b/sites/all/modules/views/help/api-default-views.html
@@ -0,0 +1,103 @@
+Views can be stored in the database, which is typical of smaller sites and hobby sites. However, Views may also be stored directly in the code as "default" views, (which simply means they're available by default). Modules often come with views that are specific to the module data, but it's also possible -- and <b>highly</b> recommended -- that sites which have separate "development" and "production" sites export their views into default views in a site-specific module. This makes it very easy to transfer views from dev to production without making database changes.
+
+<h3>Creating a module</h3>
+First, create a directory in <em>sites/all/modules</em> for your new module. Call it whatever you like, but for this example we will call it <em>mymodule</em>.
+
+In this directory, create a <em>mymodule.module</em> file. It can be empty for now, but it should at least contain an opening PHP tag:
+<pre>&lt;?php
+</pre>
+
+It should not contain a closing ?&gt; tag, as the closing ?&gt; tag is not required and anything AFTER the closing tag, such as a space or a linefeed, will be displayed directly to the browser and can potentially cause problems.
+
+The .module file will contain functions and drupal hooks. Hooks are specially named functions that Drupal will call in order to get your module's response at certain times while generating pages. The only function you will need for this exercise is the 'views_api' hook that tells Views that this module supports the Views API and what version:
+
+<pre>function mymodule_views_api() {
+ return array('api' => 2.0);
+}
+</pre>
+
+For other uses you may well add additional functions.
+
+Second, you need to create a <em>mymodule.info</em> file:
+
+<pre>name = My module
+description = My site specific module.
+core = 6.x
+</pre>
+
+Once you have these two files set up, you should be able to activate your new module at the <em>Administer >> Modules</em> page.
+<h3>Exporting your views</h3>
+
+The easiest way to do this is to activate the 'views_export' module, and navigate to <em>Administer >> Structure >> Views >> Tools >> Bulk export</em> Place a check next to each view that you want in your module, type the module name into the text field, and click export. This will create the entire <em>hook_views_default_views()</em> function for you.
+
+You can also export individual views. If you do this, keep in mind that this export does not include the line that adds the exported $view into the larger $views array:
+
+<pre>$views[$view->name] = $view</pre>
+
+To place this into your <em>hook_views_default_views()</em> you will need to place that after the view, and make sure the function returns $views at the end.
+
+<h3>Placing your exported views into your module</h3>
+Cut and paste the entire output of the bulk export tool into mymodule.views_default.inc -- and be sure to put a &lt;?php at the top of the file so that the webserver knows that it's PHP code! Then visit the Views tools page and clear the Views cache. Your views should now be listed as <b>Overridden</b> on the view list page. If you <b>revert</b> these views, they will be removed from the database, but will remain in code.
+
+<h3>Theming your views in your module</h3>
+You can theme these views in the module and not need to rely on the theme to do this at all; and in fact, the theme can continue to override these just like it ordinarily would, even if your module provides a theme. This is very useful for distributing a module where the view needs to look "just so."
+
+To do this, you need to implement <em>hook_theme()</em> in your module:
+<pre>function mymodule_theme($existing) {
+ return array(
+ 'views_view__viewname__displayid' => array (
+ 'arguments' => array('view' => NULL),
+ 'template' => 'views-view--viewname--displayid',
+ 'base hook' => 'views_view',
+ 'path' => drupal_get_path('module', 'mymodule'),
+ ),
+ );
+}
+</pre>
+
+There are a small number of gotchas in doing this that you must be aware of.
+
+<ol>
+<li>When referring to a template filename, you always use dashes in the name. i.e, <em>views-view--viewname--displayid.tpl.php</em>. However, when referring to the hook or function names, you use underscores instead of dashes. i.e, <em>views_view</em> and <em>views_view__viewname__displayid</em></li>
+
+<li>The 'arguments' change based upon which of the 3 types you're overriding. There's the 'display', the 'style' and the 'row' style. The above code is assuming the display, which is usually just <em>views_view</em>. Here are the possibilities:
+
+<pre>display: array('view_array' => array(), 'view' => NULL),
+style: array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
+row: array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
+field: array('view' => NULL, 'field' => NULL, 'row' => NULL),
+</pre>
+
+Be sure to use the right arguments line or the theme system will not properly translate.
+</li>
+<li>The 'template' line should never include the extension, so drop the .tpl.php from it.</li>
+
+<li>You need to make sure that the Views preprocess functions get registered. The 'base hook' line in the definition does that, but it can only do it if it comes after the Views registration, which actually happens very late in theme building. 99% of the time, your module will come before Views. You have two choices to deal with this:
+<ol>
+ <li>Set your module's weight to 11 or higher in the database. Views' weight is 10. You can make this happen automatically when the module is first installed by creating a mymodule.install file and using this code:
+ <pre>function mymodule_install() {
+ db_query("UPDATE {system} SET weight = 11 WHERE name = 'mymodule'");
+}
+</pre>
+ If you use this method, the <em>base hook</em> should be set to the name of the original template being used. i.e, if this is a variate of views-view-list.tpl.php, this should be 'views_view_list'.
+ </li>
+ <li>You can also just force it to list the preprocessors without actually having to detect them. This doesn't require modifying your module's weight, which is not always possible, you can insert this code into the array:
+ <pre> 'preprocess functions' => array(
+ 'template_preprocess',
+ 'template_preprocess_views_view',
+ 'mymodule_preprocess_views_view__viewname_displayid',
+ ),
+</pre>
+
+ The first one is the global 'template_preprocess' function which all templates utilize. It does some basic things such as setting up $zebra and a few other items. See <a href="http://api.drupal.org/api/function/template_preprocess/6">api.drupal.org</a> for specifics.
+
+ The second one is the plugin specific preprocess. Like 'base hook' it should conform to the name used by the original template. i.e, if the original template was <em>views-view-list.tpl.php</em> then that preprocess function would be named <em>template_preprocess_views_view_list</em>.
+
+ The third one is your module's preprocess function, if it needs one. In general, you probably will not need one, and you should only attempt to use one if you are reasonably familiar with the concept of preprocess functions and Drupal's theme system in general. See Drupal's theme documentation for more information.
+ </li>
+</ol>
+</li>
+<li>
+ If you leave the path blank the template file will be searched for in "./" which is the Drupal install base path.
+</li>
+</ol>
diff --git a/sites/all/modules/views/help/api-example.html b/sites/all/modules/views/help/api-example.html
new file mode 100644
index 000000000..682fe87f0
--- /dev/null
+++ b/sites/all/modules/views/help/api-example.html
@@ -0,0 +1,179 @@
+
+For the new table defined by the Node example module to be understood by the views module you need to create a node_example.views.inc file that describes the table and its relationships to the rest of the database. In order for views to know that this file is to be loaded you need to implement hook_views_api. This is done by adding the following function into your node_example.module file
+
+<pre>
+&lt;?php
+/**
+ * Implements hook_views_api().
+ *
+ * This tells drupal that there is Views integration file named
+ * module-name.views.inc
+ */
+function node_example_views_api() {
+ // Note that you can include 'path' in this array so that your views.inc
+ // file can be stored in a different location.
+ return array(
+ 'api' => 2.0
+ );
+}
+?&gt;
+</pre>
+
+Below is the contents of a simple node_example.views.inc file that allows you to create views that include the new color and quantity information.
+
+<pre>
+&lt;?php
+
+/**
+ * This file is used to tell the views module about the new node_example table.
+ *
+ * Database definition:
+ * @code
+ * CREATE TABLE node_example (
+ * vid int(10) unsigned NOT NULL default '0',
+ * nid int(10) unsigned NOT NULL default '0',
+ * color varchar(255) NOT NULL default '',
+ * quantity int(10) unsigned NOT NULL default '0',
+ * PRIMARY KEY (vid, nid),
+ * KEY `node_example_nid` (nid)
+ * )
+ * @endcode
+ */
+
+function node_example_views_data() {
+ // Basic table information.
+
+ // ----------------------------------------------------------------
+ // node_example table
+ // New group within Views called 'Example'
+ // The group will appear in the UI in the dropdown tha allows you
+ // to narrow down which fields and filters are available.
+
+ $data = array();
+ $data['node_example']['table']['group'] = t('Example');
+
+ // Let Views know that our example table joins to the 'node'
+ // base table. This means it will be available when listing
+ // nodes and automatically make its fields appear.
+ //
+ // We also show up for node revisions.
+ $data['node_example']['table']['join'] = array(
+ 'node_revisions' => array(
+ 'left_field' => 'vid',
+ 'field' => 'vid',
+ ),
+ 'node' => array(
+ 'left_field' => 'vid',
+ 'field' => 'vid',
+ ),
+ );
+
+ // quantity
+ $data['node_example']['quantity'] = array(
+ 'title' => t('Quantity'),
+ 'help' => t('Quantity of items.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // Color
+ $data['node_example']['color'] = array(
+ 'title' => t('Color'),
+ 'help' => t('Color of item.'),
+
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ return $data;
+}
+
+?&gt;
+</pre>
+
+Some notes on usage:
+
+Within Views, click on the Add tab. You have a number of type options here. Normally you would select either 'Node' (if you only want to display information on current nodes) or 'Node revision' (if you want to display information on all revisions of the nodes)
+
+With this configuration you always pull out of the database, data for every single node, whether or not it has color and quantity information. To display information on just those nodes that have color and quantity information you can use a filter so that only nodes which don't have a NULL color or a NULL quantity are displayed.
+
+<h3>Type/relationship extension</h3>
+
+When your tables have first class data, you will often need to have own View types and View relationships defined. With the current node_example table this isn't required although I try to justify it below on an efficiency basis. See [[http://groups.drupal.org/node/17236#comment-58980|this discussion]] as to why it isn't justified.
+
+Pulling data out of the database for every node when you only want data for the new Example node type is inefficient. To reduce the initial data extraction to just that relating to the new Example nodes requires that you make the node_example table the base table. This can be done by adding the following code into the node_example.views.inc file just before the 'return $data;'
+
+<pre>
+&lt;?php
+
+// **** Begin optional extra for type and relationships ****
+
+ // Use node_example as a new base table
+ // by creating a new views type called 'Node example'
+ // This allows it to be selected as the 'view type'
+ // when you initially add a new view.
+ $data['node_example']['table']['base'] = array(
+ 'field' => 'vid',
+ 'title' => t('Node example'),
+ 'help' => t("Node example type with color and quantity information."),
+ 'weight' => -9,
+ );
+
+ // When using the new 'Node example' type you need to use relationships
+ // to access fields in other tables.
+
+ // Relationship to the 'Node revision' table
+ $data['node_example']['vid'] = array(
+ 'title' => t('Node revision'),
+ 'help' => t('The particular node revision the color and quantity is attached to'),
+ 'relationship' => array(
+ 'label' => t('Node revision'),
+ 'base' => 'node_revisions',
+ 'base field' => 'vid',
+ // This allows us to not show this relationship if the base is already
+ // node_revisions so users won't create circular relationships.
+ 'skip base' => array('node', 'node_revisions'),
+ ),
+ );
+
+ // Relationship to the 'Node' table
+ $data['node_example']['nid'] = array(
+ 'title' => t('Node'),
+ 'help' => t('The particular node the color and quantity is attached to'),
+ 'relationship' => array(
+ 'label' => t('Node'),
+ 'base' => 'node',
+ 'base field' => 'nid',
+ // This allows us to not show this relationship if the base is already
+ // node so users won't create circular relationships.
+ 'skip base' => array('node', 'node_revisions'),
+ ),
+ );
+
+// **** End optional extra for type and relationships ****
+
+?&gt;
+</pre>
+
+The above code adds a new 'Node example' to the view types that can be selected within the Add tab window of views. Selecting this sets the node_example table to be the base table.
+
+If you select 'Node example' as view type, when you initially go into the edit window of views you will find the only fields available are the color and quantity fields. To get fields from other tables you need to add a relationship. Relationships may be found at the top in the same column as the fields.
diff --git a/sites/all/modules/views/help/api-forms.html b/sites/all/modules/views/help/api-forms.html
new file mode 100644
index 000000000..27729091d
--- /dev/null
+++ b/sites/all/modules/views/help/api-forms.html
@@ -0,0 +1,88 @@
+Views allows handlers to output form elements, wrapping them automatically in a form, and handling validation / submission.
+The form is multistep by default, allowing other modules to add additional steps, such as confirmation screens.
+
+<h2>Implementation</h2>
+A views handler outputs a special placeholder in render(), while the real form with matching structure gets added in views_form().
+When the View is being preprocessed for the theme file, all placeholders get replaced with the rendered form elements.
+
+The views handler can also implement views_form_validate() and views_form_submit().
+<pre>
+ function render($values) {
+ return '&lt;!--form-item-' . $this-&gt;options['id'] . '--' . $this-&gt;view-&gt;row_index . '--&gt;';
+ }
+
+ function form_element_name() {
+ // Make sure this value is unique for all the view fields
+ return $this-&gt;options['id'];
+ }
+
+ function form_element_row_id($row_id) {
+ // You could use values from $this-&gt;view-&gt;result[$row_id]
+ // to provide complex row ids.
+ return $row_id;
+ }
+
+ function views_form(&$form, &$form_state) {
+ // The view is empty, abort.
+ if (empty($this-&gt;view-&gt;result)) {
+ return;
+ }
+
+ $field_name = $this-&gt;form_element_name();
+ $form[$field_name] = array(
+ '#tree' => TRUE,
+ );
+ // At this point, the query has already been run, so we can access the results
+ foreach ($this-&gt;view-&gt;result as $row_id => $row) {
+ $form_element_row_id = $this-&gt;form_element_row_id($row_id);
+ $form[$field_name][$form_element_row_id] = array(
+ '#type' => 'textfield',
+ '#title' => t('Your name'),
+ '#default_value' => '',
+ );
+ }
+ }
+
+ // Optional validate function.
+ function views_form_validate($form, &$form_state) {
+ $field_name = $this->form_element_name();
+ foreach ($form_state['values'][$field_name] as $row_id => $value) {
+ if ($value == 'Drupal') {
+ form_set_error($field_name . '][' . $row_id, "You can't be named Drupal. That's my name.");
+ }
+ }
+ }
+
+ // Optional submit function.
+ function views_form_submit($form, &$form_state) {
+ // Do something here
+ }
+</pre>
+
+The form is multistep by default, with one step: 'views_form_views_form'.
+A "form_example" module could add a confirmation step by setting:
+<pre>
+ $form_state['step'] = 'form_example_confirmation';
+</pre>
+in form_example_views_form_submit().
+Then, views_form would call form_example_confirmation($form, $form_state, $view, $output) to get that step.
+
+<b>Important:</b> You can fetch the Views object in form_alter and validate / submit hooks from the form state:
+<pre>
+ $view = $form_state['build_info']['args'][0];
+</pre>
+
+<h2>Relevant Views functions</h2>
+<ul>
+<li>template_preprocess_views_view()</li>
+<li>views_form()</li>
+<li>views_form_views_form()</li>
+<li>views_form_views_form_validate()</li>
+<li>views_form_views_form_submit()</li>
+<li>theme_views_form_views_form()</li>
+</ul>
+
+<h2>Hooks</h2>
+<ul>
+<li>hook_views_form_substitutions()</li>
+</ul>
diff --git a/sites/all/modules/views/help/api-handler-area.html b/sites/all/modules/views/help/api-handler-area.html
new file mode 100644
index 000000000..ad8ec5636
--- /dev/null
+++ b/sites/all/modules/views/help/api-handler-area.html
@@ -0,0 +1,45 @@
+In Views areas (header, footer, empty-text) are pluggable, this means you can write your own php logic to place whatever you want.
+
+<h3>Requirements</h3>
+You should have read <a href="topic:views/api">API</a> and <a href="topic:views/api-tables">Tables API</a> to get a basic knowledge
+how to extend views.
+
+<h3>Create your own area handler</h3>
+
+The first step is to tell views: Hey i want to add a new area handler.
+Therefore you have to implement hook_views_data and add a new one. For example:
+
+<pre>
+function yourmodule_views_data() {
+ $data['views']['collapsible_area'] = array(
+ 'title' =&gt; t('Collabsible Text area'),
+ 'help' =&gt; t('Provide collabsible markup text for the area.'),
+ 'area' =&gt; array(
+ 'handler' =&gt; 'yourmodule_handler_collapsible_area_text',
+ ),
+ );
+}
+</pre>
+
+The second step is to write this handler. Therefore create a file called yourmodule_handler_collapsible_area_text.inc and
+add it to the .info file of your module.
+
+Then add content to your area file like this:
+<pre>
+class yourmodule_handler_collapsible_area_text extends views_handler_area_text {
+ function render($empty = FALSE) {
+ // Here you just return a string of your content you want.
+ if ($render = parent::render($empty)) {
+ $element = array(
+ '#type' =&gt; 'fieldset',
+ '#title' =&gt; t('Title'),
+ '#value' =&gt; $render,
+ );
+ $output = theme('fieldset', $element);
+ return $output;
+ }
+ }
+}
+</pre>
+
+As on every handler you can add options so you can configure the behavior. If the area isn't shown yet in the views interface, please clear the cache :)
diff --git a/sites/all/modules/views/help/api-tables.html b/sites/all/modules/views/help/api-tables.html
new file mode 100644
index 000000000..cafbbab4a
--- /dev/null
+++ b/sites/all/modules/views/help/api-tables.html
@@ -0,0 +1,262 @@
+Tables are described to Views by implementing hook_views_data(). This should be placed in MODULENAME.views.inc and it will be autoloaded (see <a href="topic:views/api">Views' API</a>). The hook implementation should return an array of table information, keyed by the name of the table. For example, if your module is describing three tables, 'foo', 'bar' and 'baz', your hook will look like this:
+<pre> /**
+ * Implements hook_views_data().
+ */
+ function MODULENAME_views_data() {
+ $data = array(
+ 'foo' =&gt; array(
+ // ...info about the table named 'foo'...
+ ),
+ 'bar' =&gt; array(
+ // ...info about the table named 'bar'...
+ ),
+ 'baz' =&gt; array(
+ // ...info about the table named 'baz'...
+ ),
+ );
+ return $data;
+ }
+</pre>
+
+The key should be the actual database name of the table (not including prefix), but it can be an alias as long as the join information (explained later) contains the real name of the table.
+
+Each item in the array should be a field in the table, with the exception of a special information section called 'table'. Example:
+
+<pre>$data['foo'] = array(
+ 'table' =&gt; array(
+ // ... info about the table, described later ...
+ ),
+ 'bar' =&gt; array(
+ // ... info about the field named 'bar', i.e, foo.bar,
+ ),
+ 'baz' =&gt; array(
+ // ... info about the field named 'baz', i.e, foo.baz,
+ ),
+);
+</pre>
+
+Once you get down to an array that contains actual data, that piece of the array will often be referred to as the definition.
+
+<h2>The 'table' section</h2>
+Each table should have a 'table' section in it, which is used to set default information for the table, such as the group, as well as the very important joins and whether or not this is a base table.
+
+First, there are several items that are actually for fields but can be placed here so that all fields within the table inherit them:
+<dl>
+<dt>group</dt>
+<dd>The name of the group this item will be with. In the UI, this is displayed as Group: Title. For example, "Node: Node ID", "Taxonomy: Term description", etc. It is important to be consistent with groups, because the UI sorts by group, and allows filtering by group to find fields as well.</dd>
+<dt>title</dt>
+<dd>The actual name of the field; it should be concise and descriptive.</dd>
+<dt>help</dt>
+<dd>A longer description to help describe what the field is or does. It should try to be only a line or two so as not to clutter the UI.</dd>
+</dl>
+
+In general, having 'title' and 'help' at the table level doesn't make a lot of sense, but usually every item in a table is in the same group. Thus it is very common to define the 'group':
+
+<pre>
+ $data['foo']['table']['group'] = t('Foo');
+</pre>
+
+The other items in the 'table' section are described in the following sections.
+
+<h3>'base': Base table</h3>
+If your table is a base table -- meaning it can be the primary, central table for a View to use, you can declare it to be a base table. This primarily provides UI information so that it can be selected.
+For example:
+<pre>
+ // Advertise this table as a possible base table
+ $data['node']['table']['base'] = array(
+ 'field' =&gt; 'nid',
+ 'title' =&gt; t('Node'),
+ 'help' =&gt; t("Nodes are a Drupal site's primary content."),
+ 'weight' =&gt; -10,
+ );
+</pre>
+
+The following items are available in the base section:
+<dl>
+<dt>field</dt>
+<dd>The primary key field for this table. For Views to treat any table as a base table, it <b>must</b> have a primary field. For node this is the 'nid', for users this is the 'uid', etc. <strong>Without a single primary key field (i.e. not a composite key), Views will not be able to utilize the table as a base table.</strong> If your table does not have a primary key field, it is not too difficult to just add a serial field to it, usually.</dd>
+<dt>title</dt>
+<dd>The title of this table in the UI. It should be singular and describe the object that this table contains from the perspective of the user.</dd>
+<dt>help</dt>
+<dd>A short piece of text to describe what object this table contains.</dd>
+<dt>database</dt>
+<dd>If this table is held in a different database from your Drupal database, specify it as a string in the exact same format as the settings.php file. This is a special purpose variable that will probably be only used in site specific code, and <b>it must be the same database type as your Drupal database</b>. Also, don't try to join it to any table that isn't in the same database. That'll just create all kinds of silly errors. For example:
+<pre>
+ // In settings.php for your site
+ // Your drupal (site) database needs to be called 'default'
+ $db_url['default'] = 'mysqli://user:pass@host/drupal_db';
+ $db_url['budget'] = 'mysqli://user:pass@host/other_db';
+</pre>
+Then when you are describing the external database in your base table you would write something like this:
+<pre>
+ $data[$table]['table']['base'] = array(
+ 'field' => 'Primary key',
+ 'title' => t('Field name'),
+ 'help' => t('Field description'),
+ 'database' => 'budget',
+ 'weight' => -10,
+ );
+</pre>
+</dd>
+</dl>
+
+<h3>'join': Linking your table to existing base tables</h3>
+For Views to use your table, it has to either be a base table, or know how to link to an existing base table. Or sometimes both. Views uses this information to create a path to the base table; when the table is added to the query, Views will walk along this path, adding all tables required into the query.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/node-term_node-term_data-large.png"><img src="path:images/node-term_node-term_data.png" /></a>
+<em>How taxonomy_term_data joins to node</em>
+</div>
+
+In the above example, to use these with 'node' as the base table, both 'taxonomy_term_data' and 'term_node' need to be defined, and they each need a join handler for node:
+
+<pre>
+$data['taxonomy_term_data']['table']['join']['node'] = array(
+ 'left_table' =&gt; 'term_node',
+ 'left_field' =&gt; 'tid',
+ 'field' =&gt; 'tid',
+);
+</pre>
+
+The above can be read as "In order to join to the node table, the taxonomy_term_data table must first link to the term_node table, and they join on the 'tid' field.". When adding this table to the query for a node view, Views will look at this and then look for the term_node table.
+
+<pre>
+$data['term_node']['table']['join']['node'] = array(
+ 'left_field' =&gt; 'nid',
+ 'field' =&gt; 'nid',
+);
+</pre>
+
+Above, the fact that 'left_table' is left out lets us know that term_node links directly to the node table, using the 'nid' field on both sides of the join.
+
+Quite a few more fields are available in this definition:
+<dl>
+ <dt>handler</dt>
+ <dd>The name of the handler object to use. Defaults to 'views_join'. You may create custom join handlers that may or may not use any of the data below, as they see fit.</dd>
+ <dt>table</dt>
+ <dd>Table to join. This is optional, and should only be used if the table being referenced is an alias.</dd>
+ <dt>field</dt>
+ <dd>Field to join on. This is required.</dd>
+ <dt>left_table</dt>
+ <dd>The next step toward the final destination. If this is the final destination it may be omitted.</dd>
+ <dt>left_field</dt>
+ <dd>The field to join to on the left side. This is required.</dd>
+ <dt>type</dt>
+ <dd>Either LEFT (default) or INNER.</dd>
+ <dt>extra</dt>
+ <dd>Either a string that's directly added, or an array of items. Each item is, itself, an array:
+ <dl>
+ <dt>table</dt>
+ <dd>table should not be set in most cases, as it would be filled with the right table alias. Set it to NULL if you want to use a formular in "field"</dd>
+ <dt>field</dt>
+ <dd>Field or formula, therein "%alias" can be used to reference the right table.</dd>
+ <dt>operator</dt>
+ <dd>Similar to filters, this is the operator, such as &gt;, &lt;, =, etc. Defaults to = or IN.</dd>
+ <dt>value</dt>
+ <dd>Must be set. If an array, operator will be defaulted to IN.</dd>
+ <dt>numeric</dt>
+ <dd>If true, the value will not be surrounded in quotes, and %d will be used for its placeholder.</dd>
+ </dl>
+ </dd>
+ <dt>extra type</dt>
+ <dd> How all the extras will be combined. Either AND or OR. Defaults to AND.</dd>
+</dl>
+
+<h2>Describing fields on tables</h2>
+Aside from the special table tag, each table can also have an unlimited number of field designations; these correspond roughly to fields on the table, though it is very common to use non-fields to display data that isn't directly in a field, such as data arrived from formulae, or special links related to the object the table is part of.
+
+Each field is described in the view data with an array, keyed to the database name of the field. This array may contain some information fields, plus an entry in each of the five types of items Views has per field: argument, field, filter, relationship, sort. For example:
+
+<pre>
+$data['node']['nid'] = array(
+ 'title' =&gt; t('Nid'),
+ 'help' =&gt; t('The node ID of the node.'), // The help that appears on the UI,
+ // Information for displaying the nid
+ 'field' =&gt; array(
+ 'handler' =&gt; 'views_handler_field_node',
+ 'click sortable' =&gt; TRUE,
+ ),
+ // Information for accepting a nid as an argument
+ 'argument' =&gt; array(
+ 'handler' =&gt; 'views_handler_argument_node_nid',
+ 'name field' =&gt; 'title', // the field to display in the summary.
+ 'numeric' =&gt; TRUE,
+ 'validate type' =&gt; 'nid',
+ ),
+ // Information for accepting a nid as a filter
+ 'filter' =&gt; array(
+ 'handler' =&gt; 'views_handler_filter_numeric',
+ ),
+ // Information for sorting on a nid.
+ 'sort' =&gt; array(
+ 'handler' =&gt; 'views_handler_sort',
+ ),
+);
+</pre>
+
+The above example describes the 'nid' field on the 'node' table, providing 4 of the 5 handlers. Note that while field is normally expected to be the database name of the field, it doesn't have to be; you can use an alias (which is how you get multiple handlers per field) or something completely made up for items that aren't tied to the database. For example:
+
+<pre>
+$data['node']['edit_node'] = array(
+ 'field' =&gt; array(
+ 'title' =&gt; t('Edit link'),
+ 'help' =&gt; t('Provide a simple link to edit the node.'),
+ 'handler' =&gt; 'views_handler_field_node_link_edit',
+ ),
+);
+</pre>
+
+The above handler definition an edit link to a node, but this isn't a field in and of itself. For aliased fields, here is another example:
+
+<pre>
+$data['users']['uid_current'] = array(
+ 'real field' =&gt; 'uid',
+ 'title' =&gt; t('Current'),
+ 'help' =&gt; t('Filter the view to the currently logged in user.'),
+ 'filter' =&gt; array(
+ 'handler' =&gt; 'views_handler_filter_user_current',
+ ),
+);
+</pre>
+
+The above definition provides an alternate filter handler on the uid field for the current user.
+
+The following items are allowed in the field definition:
+
+<dl>
+<dt>group, title, help</dt>
+<dd>As above, these fields are for the UI. If placed here, any of these fields will override a setting on the base table.</dd>
+<dt>real field</dt>
+<dd>If this field is an alias, the "real field" may be placed here, and the handler will never know the difference.</dd>
+
+<dt>field</dt>
+<dd>A handler definition for the "Field" section, which is a field that may be displayed in a view. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_field'.</dd>
+<dt>filter</dt>
+<dd>A handler definition for the "Filters" section, which will be used to apply WHERE clauses to the view. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_filter'.</dd>
+<dt>sort</dt>
+<dd>A handler definition for the "Sort criteria" section, which will be used to add an ORDER BY clause to the view. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_sort'.</dd>
+<dt>relationship</dt>
+<dd>A handler definition for the "Field" section, which is a way to bring in new or alternative base tables in the view. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_relationship'. All relationships need the 'base' to be set. The basic relationship handler also requires 'base field' to be set. 'base' and 'base field' represent the "right" half of the join that will use this field as the left side.</dd>
+<dt>argument</dt>
+<dd>A handler definition for the "Field" section, which is method of accepting user input from the URL or some other source. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_argument'.</dd>
+</dl>
+
+For more information about what handlers need/use what data, visit <a href="http://views.doc.logrus.com">the Views API site</a> and check out the available handlers.
+
+<h3>Support old tabledata</h3>
+If you need to rename some tables/fields you can create some references in the views data to be able to continue to work.
+Therefore you create the whole table structure of the current views data.
+
+If you have to rename a single table you need to specify
+<pre>
+$data['oldtable']['moved to'] = 'newtable';
+</pre>
+
+If you have to rename/move a single a field to another table you specify
+<pre>
+$data['oldtable']['oldfield']['field']['moved to'] = array('newtable', 'newfield');
+</pre>
+or
+<pre>
+$data['oldtable']['oldfield']['moved to'] = array('newtable', 'newfield');
+</pre>
diff --git a/sites/all/modules/views/help/api-upgrading.html b/sites/all/modules/views/help/api-upgrading.html
new file mode 100644
index 000000000..158f80916
--- /dev/null
+++ b/sites/all/modules/views/help/api-upgrading.html
@@ -0,0 +1,224 @@
+In order to take advantage of the changes in Drupal 7, Views has gone through several API changes.
+Here's what you should know.
+
+<h3>Handler registry</h3>
+
+Views now uses Drupal's dynamic-loading code registry.
+You can safely remove your implementations of hook_views_handlers(), since they are no longer used.
+
+Please remember to specify the handlers in your module's .info file. For example:
+<pre>
+name = Example module
+description = "Gives an example of a module."
+core = 7.x
+files[] = example.module
+files[] = example.install
+
+; Views handlers
+files[] = includes/views/handlers/example_handler_argument_string.inc
+</pre>
+
+<h3>Removed handlers</h3>
+
+Note that views_handler_filter_float has been removed.
+This functionality is now handled by views_handler_filter_numeric.
+There's no need for having a special handler any more, thanks to the new DB layer in Drupal 7.
+
+views_handler_sort_formula has been removed.
+Everyone who used it can extend from views_handler_sort, too.
+
+<h3>Ctools dependency</h3>
+Views requires ctools now, so it can use the dependency system of ctools.
+The only thing you have to do is to remove views_process_dependency.
+
+<h3>Changed add_where api</h3>
+If your field is a plain sql field:
+<pre>
+$this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . $this->operator . " '%s'", $this->value);
+</pre>
+has to be converted to
+<pre>
+$this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", $this->value, $this->operator);
+</pre>
+If your field is a complex where condition:
+<pre>
+$this->query->add_where($this->options['group'], "$upper($field) NOT LIKE $upper('%%%s')", $this->value);
+</pre>
+has to be converted to
+<pre>
+$placeholder = $this->placeholder();
+$this->query->add_where_expression($this->options['group'], "$field LIKE $placeholder", array($placeholder => '%' . db_like($this->value)));
+</pre>
+placeholder() generates a automatic unique placeholder for you.
+
+add_where with operator 'formula' can be converted to add_where_expression.
+add_having with operator 'formula' can be converted to add_having_expression.
+
+<h3>Changed place for display specific settings</h3>
+In the new ui the new place for display settings is at the top of the second column.
+Therefore use something like this code in your display plugin:
+<pre>
+$categories['block'] = array(
+ 'title' => t('Block settings'),
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -10,
+ ),
+);
+</pre>
+
+<h3>Changed filter settings and associated class variables</h3>
+'optional' and 'single' are now 'required' and 'multiple', the logic is now opposite.
+Also, the 'no_single' and 'no_optional' class variables (known as "object flags" in the API docs)
+are now 'always_multiple' and 'always_required'.
+
+<h3>Changed argument settings</h3>
+See the init() function in views_handler_argument for an overview of everything
+that changed.
+
+1. The default actions 'summary asc', 'summary desc', 'summary asc by count', 'summary asc by count'
+have been replaced by a single 'summary' action (which takes the sort order and type as options).
+2. Wildcards are now called exceptions.
+<pre>
+$this->options['exception']['value'] = $options['wildcard'];
+$this->options['exception']['title'] = $options['wildcard_substitution'];
+</pre>
+3. Summary plugin options are now stored in 'summary_options' instead of 'style_options'
+<pre>
+$this->options['summary_options'] = $options['style_options'];
+</pre>
+4. The selected summary plugin is no longer stored in 'style_plugin'.
+<pre>
+$this->options['summary']['format'] = $options['style_plugin'];
+</pre>
+5. The validator options have been moved.
+<pre>
+$options['validate']['type'] = $options['validate_type'];
+$options['validate']['fail'] = $options['validate_fail'];
+</pre>
+6. The validator settings have been moved from $form['argument_validate'] to ['validate_options']
+This means that dependent code in validate plugins needs to change.
+Example change for views_plugin_argument_validate_user:
+<pre>
+ $form['roles'] = array(
+ '#dependency' => array(
+- 'edit-options-argument-validate-user-restrict-roles' => array(1),
++ 'edit-options-validate-options-user-restrict-roles' => array(1),
+ ),
+</pre>
+
+<h3>The introduction of get_value() and sanitize_value()</h3>
+The views_handler class got two new functions:
+<pre>
+/**
+ * Get the value that's supposed to be rendered.
+ *
+ * @param $values
+ * An object containing all retrieved values.
+ * @param $field
+ * Optional name of the field where the value is stored.
+ */
+function get_value($values, $field = NULL) {
+ $alias = isset($field) ? $this->aliases[$field] : $this->field_alias;
+ if (isset($values->{$alias})) {
+ return $values->{$alias};
+ }
+}
+
+/**
+ * Sanitize the value for output.
+ *
+ * @param $value
+ * The value being rendered.
+ * @param $type
+ * The type of sanitization needed. If not provided, check_plain() is used.
+ */
+function sanitize_value($value, $type = NULL) {
+ switch ($type) {
+ case 'xss':
+ $value = filter_xss($value);
+ break;
+ case 'url':
+ $value = check_url($value);
+ break;
+ default:
+ $value = check_plain($value);
+ break;
+ }
+ return $value;
+}
+</pre>
+These functions are meant to be used in the render() functions of field handlers,
+for fetching data (usually by alias) from the $values object, and for sanitizing values.
+
+The abstraction of fetching data from rendering data is important because
+different query backends have different ways of storing data in $values, and the field alias
+is a SQL specific thing. So instead of overriding the whole render() function and copying
+all of the logic there (as well as having to constantly keep up with upstream Views changes),
+the backend can just override get_values(), which is significantly less code.
+
+Of course, different ways of fetching and displaying data might require different
+ways of sanitizing it, hence the usage of the sanitize_value() function.
+
+Examples of converting render() field handler implementations:
+<pre>
+// This
+$value = $values->{$this->field_alias};
+// Becomes this
+$value = $this->get_value($values);
+
+// And this
+$format = $values->{$this->aliases['format']};
+// Becomes this
+$format = $this->get_values($values, 'format');
+
+// Instead of this:
+return check_plain($value);
+// We write:
+return $this->sanitize_value($value);
+
+// Since sanitize_value() supports different sanitization functions, this:
+return filter_xss($value);
+// Can become:
+return $this->sanitize_value($value, 'xss');
+</pre>
+
+
+<h3>Changed views_get_page_view</h3>
+In contrast to 6.x views_get_page_view now does stores the current view, not the current page display.
+
+<h3>Removed views-view-row-node</h3>
+Due to changes in comment.module there is no extra views-view-row-node template needed to display the comments. If you do some custom stuff there you should now be able to do everything in your node.tpl.php.
+
+<h3>Entity type Key on Base tables</h3>
+During the development of the drupal7 version of views the entity type associated with a table got added to $data['name']['table']['base']['entity type']. It should be moved to $data['name']['table']['entity type'].
+
+<h3>Changed views_plugin_style::render_grouping()</h3>
+The parameters as well as the structure of the methods return have changed.
+The method now accepts a third optional parameter called "$group_rendered".
+This parameter defines whether to use the rendered or the raw field value for grouping.
+Intention for adding the parameter was that the grouping could have been acted
+unexpected if the rendered field contained unique values e.g. by using drupal_html_id().
+<dl>
+<dt>New return structure</dt>
+<dd>
+{grouping value} is the value affected by the new parameter.
+<pre>
+ array (
+ {grouping value} => array(
+ 'group' => {rendered_value of the grouping field},
+ 'rows' => array({group rows}),
+ ),
+ );
+</pre>
+</dd>
+<dt>Old return structure</dt>
+<dd>
+<strong>If the new parameter isn't explicitly set or its value is NULL the structure of the return will be the same as in D6!</strong>
+<pre>
+ array (
+ {rendered_value of the grouping field} => array({group rows}),
+ );
+</pre>
+</dd>
+</dl>
diff --git a/sites/all/modules/views/help/api.html b/sites/all/modules/views/help/api.html
new file mode 100644
index 000000000..2b743a757
--- /dev/null
+++ b/sites/all/modules/views/help/api.html
@@ -0,0 +1,24 @@
+Views allows modules to describe their tables relationships to each other, as well as fields, filters, sort criteria and arguments via <strong>hook_views_data()</strong>. Whenever Views deems it necessary, this hook is called, the data aggregated together and cached. <strong>hook_views_data_alter()</strong> may also be used to modify existing data, changing other module's handlers or adding handlers to other module's tables.
+
+Views also allows modules to create new display types, style types, row styles, argument default handlers and argument validators via <strong>hook_views_handlers()</strong> and <strong>hook_views_plugins()</strong>.
+
+These hooks are kept in a file named MODULENAME.views.inc. This file is automatically included upon need, so there is no need to try and include this in hook_init or any other method of including .inc files. This file should store hook_views_data, hook_views_data_alter(), hook_views_plugins(), hook_views_handlers(), as well as any other hooks and subsidiary data that will only be used by your module when Views is active. All handlers and plugins provided by your module should be in separate .inc files, and must be referenced in your module's .info file with the files[] directive.
+
+There are two similar files, MODULENAME.views_default.inc and MODULENAME.views_convert.inc which contain default views and views 1 to views 2 convert helpers, respectively.
+
+<h3>hook_views_api()</h3>
+<strong>In order for your files to be included, your module must first implement hook_views_api()</strong> in the main .module file. This module should return array of information. The following items may be returned:
+
+<dl>
+<dt><strong>api</strong></dt>
+<dd>This must appear; it should be the oldest API version that your module can work with. If Views is currently running an older version of the API, it will ignore your module's views integration. This is a good thing, as it will prevent code crashes, at the expense of your module's functionality disappearing.
+<br />
+You may find the current Views API version by calling <strong>views_api_version()</strong> which is implemented at the top of views.module. This version numbering starts at 2.0. Every time changes are made to the Views handlers and plugins or other aspects of the Views API, the number will tick up (by either .001, .01 .1 or 1 depending upon how major the changes are). Note that <strong>views_api_version()</strong> was introduced in Views 2.0-rc2 and may not exist prior to that version. You may use drupal_function_exists() to test to see if this function is there.
+<br />
+Often these versions are basically compatible with each other and Views won't care if your module implements 2.000, 2.001, 2.002, etc. Your module can request that it won't work with any version older than a given version, however. Views will determine, itself, if a newer version will work.
+</dd>
+<dt><strong>path</strong></dt>
+<dd>If your *.views*.inc files are not in the same directory as the .module file, then return the full path here. You should probably use something like drupal_get_path('module', 'yourmodulename') . '/includepath'.</dd>
+<dt><strong>template path</strong></dt>
+<dd>A path where the module has stored it's views template files. When you have specificed this key views automatically uses the template files for the views. You can use the same naming conventions like for normal views template files.</dd>
+</dl>
diff --git a/sites/all/modules/views/help/argument.html b/sites/all/modules/views/help/argument.html
new file mode 100644
index 000000000..da67e353d
--- /dev/null
+++ b/sites/all/modules/views/help/argument.html
@@ -0,0 +1,106 @@
+Contextual Filters (known in previous versions of Views as "Arguments") are input. While they often come from the URL, they don't always. Don't be shocked when they don't. Each display type may have its own source for contextual filters. Block displays have no source of contextual filters at all; they cannot pull contextual filters from the URL, and often require use of PHP code as a default contextual filter. Various default values can be applied against contextual filters to use filters in a block view. See "Provide default value," under "When the filter value is NOT in the URL" below.
+
+In general, contextual filters are used to filter the view, and in that sense they have a very close relationship to filters. However, this isn't necessarily true for every contextual filter. Contextual filters can be used for any purpose, really; the extent of what the contextual filter does is up to the developer of the contextual filter.
+
+A typical use of an contextual filter might be to reduce a view to a single node, a single user, or nodes with a given taxonomy term.
+
+<h3>When the filter value is NOT in the URL</h3>
+
+<dl>
+<dt>Display all results for the specified field</dt>
+<dd>If this option is selected, the contextual filter is removed from the view as though it weren't there and all results will be displayed.</dd>
+<dt>Provide default value</dt>
+<dd>If no contextual filter is given, a default contextual filter can be selected. You may choose from a number of different default filter options. See "Default contextual filters" below.</dd>
+<dt>Hide view</dt>
+<dd>The view will remove itself entirely if the contextual filter is not present; for a block this means it simply won't appear. For page views the view will return a 404 and present a "Page not found" error. </dd>
+<dt>Display a summary</dt>
+<dd>The view will attempt to display a summary of contextual filters available, based upon the view, and provide links to the view with those contextual filters. Summaries have their own style handlers as well as options. The default summary style simply displays a list of contextual filters with a count of entries found per contextual filter. This special mode is a very powerful part of Views.</dd>
+<dt>Display contents of "No results found"</dt>
+<dd>If this option is selected, the value specified under "No results behavior" on the main view page will be displayed when there is no filter value in the URL.</dd>
+</dl>
+
+An exception value can be set under the "Exceptions" menu. If this exception value is received, any filter value specified under "When the filter value is NOT in the URL" will be ignored. This is a literal value: if you enter the word "everything" here, the exception will apply only if the value "everything" is received.
+
+<h3>Default contextual filters</h3>
+
+The Default contextual filter selection is available <strong>only if the action to take is "Provide default value"</strong> under "When the filter value is NOT in the URL." When that option is selected, a new fieldset will appear that will allow you to choose default contextual filters. Views provides the following default selectors by default (but other modules may add more):
+
+<dl>
+<dt>Content ID from URL</dt>
+<dd>This will attempt to find a Node ID from the URL, and is primarily useful for the Node: ID contextual filter (though other contextual filters that use Node IDs, such as the CCK Node Reference contextual filter, will find it useful, too). For example, when visiting the path "node/1" or "node/1/edit" it will know that the "1" in that path is a node ID and use it.</dd>
+<dt>Fixed value</dt>
+<dd>You may directly enter what the contextual filter will be. This is not a variable, it will always be the same contextual filter. </dd>
+<dt>PHP Code</dt>
+<dd>Arbitrary php code inserted here will run whenever a contextual filter is not present in the URL.</dd>
+<dt>Taxonomy term ID from URL</dt>
+<dd>This default filter will attempt to return a taxonomy term from the current path. Selecting this option gives you the choice to also load default filters from terms.</dd>
+<dt>User ID from URL</dt>
+<dd>Like Node ID and Taxonomy term ID from URL, this will attempt to find a user ID from the path. It also includes an option to look for a node and use the node author as the user ID.</dd>
+<dt>User ID from logged in user</dt>
+<dd>If a user is currently logged in and accessing this view, their user ID will be returned as the contextual filter.</dd>
+<dt>Current date</dt>
+<dd>This option simply returns today's date.</dd>
+<dt>Current node's creation time</dt>
+<dd>Select this to return a node's creation time as a contextual filter.</dd>
+<dt>Current node's update time</dt>
+<dd>Not surprisingly, this filter returns the current node's update time</dd>
+</dl>
+
+Please bear in mind that the selection of a default contextual filter happens only if a contextual filter is not provided. If you are using a display that has no contextual filter source, such as a block, the contextual filter value selected here will always be used. However, if using a display that reads contextual filters from the URL, the options selected here will only be applied if the URL did not contain an contextual filter.
+
+The "Skip default argument for view URL" option gives you the choice of ignoring the default argument totally. This is useful for certain applications, like the creation of feeds.
+
+<h3>When the filter value IS in the URL or a default is provided</h3>
+
+<dl>
+<dt>Override title</dt>
+<dd>Selecting this option will allow you to replace the default view title with a piece of arbitrary text. Argument substitutions in the form of %1, %2, etc. may be used here.</dd>
+<dt>Override breadcrumb</dt>
+<dd>This option will allow you to overwrite the view name in breadcrumbs. The same substitution values as in "Override title" may be used.</dd>
+<dt>Specify validation criteria</dt>
+<dd>Contextual filters can have validators, which are pluggable systems used to ensure that contextual filters fit within certain parameters. When a validator is chosen, it may provide some specific settings, including the action to take if an contextual filter is presented, but it fails to validate. These actions are generally the same as the default actions above, excluding the ability to provide another default filter. See "Contextual filter validation" below for a detailed description.</dd>
+</dl>
+
+<h3>Contextual filter validation</h3>
+
+Note: If a view provides a menu option, such as a tab, the tab will not appear if the contextual filter does not validate.
+
+This sytem can have other validators plugged in; by default, Views provides:
+
+<dl>
+<dt>Basic validation</dt>
+<dd>This validator ensures that the contextual filter is present. A PHP NULL value (from eg. PHP default contextual filter code) will invalidate.</dd>
+<dt>Content</dt>
+<dd>This validator ensures that the contextual filter is a valid Node ID. You may select which node types the validator will accept.</dd>
+<dt>Numeric</dt>
+<dd>This validator ensures that the contextual filter is a valid number.</dd>
+<dt>PHP Code</dt>
+<dd>You may enter arbitrary PHP code here, similar to the "PHP code" option under "Provide default value" in "When the filter value is NOT in the URL" above, to determine if the contextual filter is valid or not.</dd>
+<dt>Taxonomy term</dt>
+<dd>Ensures that the contextual filter is a valid taxonomy term. This includes options to limit to specific vocabularies and can transform the contextual filter to the right type depending upon the actual contextual filter. Set the contextual filter value type option to the actual type of data that the contextual filter being used is expecting.</dd>
+<dt>User</dt>
+<dd>Ensures that the contextual filter refers to a valid user. If you select this validator, additional options are available to accept numeric user IDs, usernames or both, as well as to consider a user's role when filtering the view.</dd>
+</dl>
+
+If you select the "Specify validation criteria" option to select a specific validator, you will have the option to select an action to take if the filter value does not validate.
+
+<dl>
+<dt>Display all results for the specified field</dt>
+<dd>Selecting this option will return all values if the filter value does not validate, similar to "Display all results for the specified field" under "When the filter value is not in the URL" above.</dd>
+<dt>Hide View</dt>
+<dd>Similar to "Hide view" under "When the filter value is NOT in the URL" above, if this option is selected and the selector does not validate, the view will hide itself. If the view is a block, nothing will appear. If it is a page, it will throw a 404 and present a "Page not found" error.</dd>
+<dt>Return Summary</dt>
+<dd>If you select this option and the filter does not validate, a summary of all values that are valid will be returned, as in the option of the same name under "When the filter value is NOT in the URL above."</dd>
+<dt>Display contents of "No results found"</dt>
+<dd>If this option is selected and the selector does not validate, the value specified under "No results behavior" on the main view page will be displayed.</dd>
+</dl>
+
+<h3>Adminstrative title</h3>
+
+Located under the "More" group at the bottom of the "Contextual filters" menu, this option allows you to specify a custom name for the contextual filter. This may be particularly useful if you use the same contextual filter twice and you'd like to distinguish between the two filters.
+
+<h3>Grouping of contextual filters</h3>
+
+Even though contextual filters do not appear in the "<em>and/or</em>" user interface for sorting and grouping regular filters, contextual filters are always added to the <em>first</em> group of filters. Thus the order of the groups can cause the contextual filter to have entirely different effects on the results of a view that has contextual filters. Even though differences might not be apparent through the user interface.
+
+Multiple contextual filters are therefore always in the same "and/or" group of filters, and can not be placed in different groups. There is <a href="http://drupal.org/node/357082">an effort to add this feature</a>.
diff --git a/sites/all/modules/views/help/basic-settings.html b/sites/all/modules/views/help/basic-settings.html
new file mode 100644
index 000000000..be2cadc1e
--- /dev/null
+++ b/sites/all/modules/views/help/basic-settings.html
@@ -0,0 +1,20 @@
+You choose the <strong>name</strong> of the current display.
+This <strong>title</strong> will be displayed with the view, wherever titles are normally displayed; i.e, as the page title, block title, etc.
+
+When you use have many items to display, you have the choice to display them in different variants.
+<dl>
+<dt>Display a specified number of items</dt>
+<dd>Specify the number of items to display per page and an offset. The offset is the number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed.
+<dt>Display all items</dt>
+<dd>All items will display, but you can choose an offset. The number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed.</dd>
+<dt>Paged output, full pager</dt>
+<dd>A Pager can be used to display items, with the possibility to select the next page and also the first and last page. When you have many items the query is very <i>expensive</i>. To avoid this, you can choose the mini pager.
+You can also choose the number of items per page. If you enter 0, then there is no limit. Pagers also will respect an offset, if present. If multiple pagers are present on one page you may need to set this number to a higher value so as not to conflict within the ?page= array. Large values will add commas to your URLs, so avoid this if possible. Unless you're experiencing problems with pagers related to this view, you should leave this at 0. Enter the total number of pages to limit the number of values. When you leave the field empty all pages will show.
+The <strong>Exposed options</strong> allow users to define their values in a exposed form when view is displayed.
+You can choose "Expose items per page". With this option the user can determine how many items per page show in a view. Options for which label should display and what numberic options are also available.
+Furthermore, you can choose "Expose Offset". When checked, users can determine how many items should be skipped at the beginning. You can define a label.
+</dd>
+<dt>Paged output, mini pager</dt>
+<dd>The pager optiona are the same as for the "Paged output, full pager" but you have no possibility to jump to the last or first items. </dd>
+</dl>
+Normally, all views are created with <strong>unrestricted access</strong>. This means any site visitor can see the views. Please consider this when you make a view with a menu link and private data as output. You have by default two options: "Permission" and "Role". If you choose permission, you get a list of all permissions. Only users with the selected permission flag will be able to access this display. If you choose role, you get all roles as checkboxes. Only the checked roles will be able to access this display. Note that users with "access all views" can see any view, regardless of role.
diff --git a/sites/all/modules/views/help/demo-video.html b/sites/all/modules/views/help/demo-video.html
new file mode 100644
index 000000000..43c024033
--- /dev/null
+++ b/sites/all/modules/views/help/demo-video.html
@@ -0,0 +1,5 @@
+<p>Here are some links to demonstration videos. It will get you started working with Views.</p>
+
+<p><a href="http://drupal.org/node/248766">Views 2 (Drupal 6) Demonstration Video - Drupal Handbook</a></p>
+
+<p><a href="http://nodeone.se/blogg/taming-the-beast-learn-views-with-nodeone">NodeOne's initial screencasts on the Views 3 UI.</a>
diff --git a/sites/all/modules/views/help/display-attachment.html b/sites/all/modules/views/help/display-attachment.html
new file mode 100644
index 000000000..3dbdf88fa
--- /dev/null
+++ b/sites/all/modules/views/help/display-attachment.html
@@ -0,0 +1 @@
+Attachment displays are 'attached' to another display in the same view. When the display is visited, the attached display will also be rendered and may be placed before, after or both before and after the original display. Attachment displays are often useful for displaying an argument summary view along with a page display that accepts arguments. This can be used to provide a kind of glossary.
diff --git a/sites/all/modules/views/help/display-block.html b/sites/all/modules/views/help/display-block.html
new file mode 100644
index 000000000..9ac6d315a
--- /dev/null
+++ b/sites/all/modules/views/help/display-block.html
@@ -0,0 +1,11 @@
+Block displays will show up on your blocks administration page. Once a block display is created and saved, it can be enabled and positioned in your theme by visiting <strong>administer &gt;&gt; site building &gt;&gt; blocks</strong> and selecting it from the list.
+
+Blocks <strong>do not</strong> accept arguments from any source; the only way to get arguments to a block is to provide defaults to it, possibly via the PHP Code default setting.
+
+<ul>
+<li>Edit the argument in question; you may want to override this argument if you have multiple displays using it.</li>
+<li>Change the "Action to take if argument is not present" to "Provide default argument". This will bring up a new box called "Provide default argument options".</li>
+<li>The most common default argument type used for blocks is Node from URL, where it attempts to determine if the URL refers to a node, for example if visiting 'node/1' or 'node/1/edit'. User ID from URL is also very common.</li>
+<li>If you change the default argument type to 'PHP Code' (note: You must have permission to use PHP code on your site) you can enter PHP to define the argument needed. Simply return the argument.</li>
+<li>If you are using a <strong>more link</strong> with a block, you must have a page display for that more link to attach to. The more link will not print if there is no place (no display) for it to link to.</li>
+</ul>
diff --git a/sites/all/modules/views/help/display-default.html b/sites/all/modules/views/help/display-default.html
new file mode 100644
index 000000000..b619981d5
--- /dev/null
+++ b/sites/all/modules/views/help/display-default.html
@@ -0,0 +1,3 @@
+The default display is primarily a display to store settings, and isn't actually used anywhere within the Views system. It is possible for external programs to use the default display, but if they do they will (hopefully) tell you which display they will be working with. The default display is also a convenient display to use to embed into your site using PHP snippets; this is useful, for example, in node content, but this is something that should generally only be done by administrators.
+
+In general, you probably want to add either a <a href="topic:views/display-page">page display</a> or a <a href="topic:views/display-block">block display</a>.
diff --git a/sites/all/modules/views/help/display-feed.html b/sites/all/modules/views/help/display-feed.html
new file mode 100644
index 000000000..beddd0085
--- /dev/null
+++ b/sites/all/modules/views/help/display-feed.html
@@ -0,0 +1 @@
+A feed display allows you to attach an RSS feed to a view.
diff --git a/sites/all/modules/views/help/display-page.html b/sites/all/modules/views/help/display-page.html
new file mode 100644
index 000000000..9ab849956
--- /dev/null
+++ b/sites/all/modules/views/help/display-page.html
@@ -0,0 +1,7 @@
+Page displays have a <a href="topic:views/path">path</a> and an optional <a href="topic:views/menu">menu</a> component. Page displays will be the primary content for the page, meaning they will be displayed in the main content area when you visit the URL that corresponds to the path.
+
+Page displays take their arguments from the URL. You can embed arguments into the URL using %; in previous versions of Views, this was '$arg'. For example, 'node/%/foo' will accept URLs such as 'node/1/foo'.
+
+Please remember that using a % placeholder makes the argument required. If you wish to have an optional argument, simply omit the % from the path. I.e. using "page/%" as the path requires an argument and visiting 'http://www.example.com/page' will not trigger the view.
+
+If you intend to embed a view manually into another view, it is recommended that the page display not be used for embedding. Select a different display type to embed.
diff --git a/sites/all/modules/views/help/display.html b/sites/all/modules/views/help/display.html
new file mode 100644
index 000000000..48b2475c9
--- /dev/null
+++ b/sites/all/modules/views/help/display.html
@@ -0,0 +1,13 @@
+Displays tell Views where the output should go. By adding a display to a View, you can have your view appear as a page, or as a block, or even as an attachment to a different display on the view.
+
+Displays tell Views where the output should go. By adding a display to a View, you can have your view appear as a page, or as a block, or even as an attachment to a different display on the view. You must create at least one display in order to place a View on your site.
+
+Each display can have its own settings, but when created, a display will take all of its <em>basic settings</em> from the <strong>default display</strong> which all Views must have. For most settings, there is an <a href="topic:views/overrides">override</a> button that will override that single setting for the current display. Overridden settings will have a mark in the summary for that display. All 'default display settings' are shown in the other displays in '<i>italic</i>'. When you override a setting, then it is shown 'normal'.
+
+Please keep in mind that when you are editing a setting on a display that is not overridden, then by default you are editing that for all displays.
+
+Overriding <strong>fields</strong>, <strong>arguments</strong>, <strong>sorts</strong>, <strong>filters</strong> and <strong>relationships</strong>, can only be done by overriding the entire group or none of them. To do this, click on the header for the filters or the rearrange button. Once you override, the display will then have its own copies of the fields/filters/etc and changes to the defaults will not be reflected on your display.
+
+With the <strong>Reorder</strong> button you can organize the order of your displays.
+With the <strong>Analysis</strong> button the system checks the view and may give you suggestions if something is wrong.
+You can <strong>clone a display</strong> by using the link in the header of the display.
diff --git a/sites/all/modules/views/help/drush.html b/sites/all/modules/views/help/drush.html
new file mode 100644
index 000000000..689ff3391
--- /dev/null
+++ b/sites/all/modules/views/help/drush.html
@@ -0,0 +1,13 @@
+There are some Drush commands available for Views, initially added in <a href="http://drupal.org/node/1079178">Drush command to revert views</a>:
+
+<ul>
+<li>views-dev (vd) - Setup the views settings to a more developer oriented value</li>
+<li>views-revert (vr) - Revert overridden views to their default state. Make backups first!</li>
+</ul>
+
+Examples:
+drush views-revert
+[prompts the user with a list of overridden views to choose from, or to revert all]
+
+drush views-revert archive myview2
+[reverts the two specified views]
diff --git a/sites/all/modules/views/help/embed.html b/sites/all/modules/views/help/embed.html
new file mode 100644
index 000000000..e39fbf5cd
--- /dev/null
+++ b/sites/all/modules/views/help/embed.html
@@ -0,0 +1,24 @@
+You can easily embed the results of a view into other parts of your site;
+either with code as a module, or in nodes or blocks as snippets. The
+easiest way is to use the function <strong>views_embed_view()</strong>:
+
+<code>/**
+ * Embed a view using a PHP snippet.
+ *
+ * This function is meant to be called from PHP snippets, should one wish to
+ * embed a view in a node or something. It's meant to provide the simplest
+ * solution and doesn't really offer a lot of options, but breaking the function
+ * apart is pretty easy, and this provides a worthwhile guide to doing so.
+ *
+ * @param $name
+ * The name of the view to embed.
+ * @param $display_id
+ * The display id to embed. If unsure, use 'default', as it will always be
+ * valid. But things like 'page' or 'block' should work here.
+ * @param ...
+ * Any additional parameters will be passed as arguments.
+ */
+function views_embed_view($name, $display_id = 'default') {
+</code>
+
+To figure out the id of a display, hover your mouse over the tab to select that display. Everything after the '#views-tab-' is the id of that display. This ID is guaranteed never to change unless you delete the display and create a new one.
diff --git a/sites/all/modules/views/help/empty-text.html b/sites/all/modules/views/help/empty-text.html
new file mode 100644
index 000000000..0a085938c
--- /dev/null
+++ b/sites/all/modules/views/help/empty-text.html
@@ -0,0 +1,3 @@
+The Emtpy Text content will be displayed when you choose the option <em>Display empty text</em> under the <strong>Arguments</strong> labelled <em>Action to take if argument is not present</em>.
+
+By default you can choose one or more text areas.
diff --git a/sites/all/modules/views/help/example-author-block.html b/sites/all/modules/views/help/example-author-block.html
new file mode 100644
index 000000000..41ff071dd
--- /dev/null
+++ b/sites/all/modules/views/help/example-author-block.html
@@ -0,0 +1,77 @@
+<p>In this example you will create a context-sensitive block that shows the titles of recent blog entries by an author when viewing one of their posts. This will demonstrate using Views <em>arguments</em> to dynamically filter a view's contents at display time.</p>
+
+<p>Before working through this example, enable the <strong>Blog</strong> module and populate some entries from a few different users.</p>
+
+<h3>Creating the View</h3>
+<p>The first step is creating a view for our recent blog entries block. Because the block will show the titles of blog entries, this view is considered a "Node" type. Go to <a target="_blank" href="base_url:admin/structure/views/add">add new view</a>, enter the following properties, and click <strong>Next</strong>:</p>
+
+<dl>
+ <dt>View name</dt>
+ <dd>recent_blog_entries</dd>
+ <dt>View description</dt>
+ <dd>List of recent blog entries for a given author.</dd>
+ <dt>View tag</dt>
+ <dd>blog</dd>
+ <dt>View type</dt>
+ <dd>Node</dd>
+</dl>
+
+<h3>Generating a list of blog entries</h3>
+<p>It will be much easier to see the view progress if we can see it doing something. In this section, we will create a basic view displaying blog entry titles.</p>
+
+<ol>
+<li>In the third column, locate the <strong>Fields</strong> area. Generally speaking, fields are the pieces of information that you want to display in the view (in this case, node title). Click the <strong>+</strong> icon to add a field.</li>
+<li>Scroll down to <strong>Defaults: Add fields</strong>, below the settings table. A large selection of fields will be available.</li>
+<li>In the <strong>Groups</strong> drop-down menu, select <em>Node</em>. This will limit the list to only the default fields exposed by Node module.</li>
+<li>Scroll down the list, select the <em>Node: Title</em> field, and click <strong>Add</strong>.</li>
+<li>You will now be presented with settings for the <em>Node: Title</em> field. Delete the label from the <strong>Label</strong> field, so that each individual node title is not prefixed with the word "Title." Additionally, check the <em>Link this field to its node</em> box so that visitors who see an interesting title can click directly on it to read the blog entry to which it belongs.</li>
+<li>When finished, click <strong>Update</strong>. If you scroll down to the <strong>Live Preview</strong> section, you should now see a list of several node titles; however both blog entries and other node types will be in the list. Let's fix that.</li>
+<li>In the fourth column, locate the <strong>Filters</strong> area. Filters limit the results displayed in the view, and we can use this to our advantage here by showing node titles only from blog entries and not every type of node. Click the <strong>+</strong> icon to add a filter.</li>
+<li>As before, scroll down to the <strong>Defaults: Add filters</strong> section, select <em>Node</em> from the <strong>Groups</strong> select box to limit the list of options to only those exposed by Node module.</li>
+<li>Scroll down and select the <em>Node: Type</em> field and click <strong>Add</strong>. In the settings page that appears, leave <strong>Operator</strong> as <em>Is one of</em> and select <em>Blog entry</em> under <strong>Node type</strong>. Click <strong>Update</strong> when finished.</li>
+<li>Now, by scrolling down to <strong>Live preview</strong>, you'll see that the list only shows blog entries.</li>
+</ol>
+
+<h3>Adding context with arguments</h3>
+<p>While filters are very useful for limiting the results of a view when the condition is always consistent (for example, a view of blog entry nodes should <em>always</em> be filtered by the blog entry type), something filters can't do is smart decision-making based on the page context. In our case, we want the view to display a different list of blog entries when looking at a post by user 'admin' than we do when looking at a post by user 'member', and filters won't be able to help.</p>
+
+<p>Luckily, there's another way to filter a view's content: <em>arguments</em>. Through arguments, Views are able to obtain additional context (typically via dynamic URLs with IDs in them) and can take this context into consideration when displaying the view.</p>
+
+<p>Let's walk through adding and configuring an argument to our view so that we can change its contents based on post author.</p>
+
+<ol>
+<li>In the third column, locate the <strong>Arguments</strong> area. Click the <strong>+</strong> icon to add an argument.</li>
+<li>Because we are basing the view around content <em>authors</em>, this time under <strong>Groups</strong> select <em>User</em>. Check <em>User: Uid</em> and click <strong>Add</strong>.</li>
+<li>The <strong>Defaults: Configure Argument User: Uid</strong> settings page has a lot going on, but only a few things that need our attention.</li>
+<li>The <strong>Title</strong> field here, unlike the Title field under <em>Basic Settings</em>, can be based upon the context that the view is being displayed in. Change the title to 'Recent entries by %1.' %1 will later be expanded to the user's name (based on the User: Uid argument) when the view is displayed.</li>
+<li>Under <strong>Action to take if argument is not present</strong>, there are a variety of options, ranging from displaying a 404 or a 403 page to simply displaying all values in the view. In our case, if an argument isn't specified (which it won't be, since this view will be displayed in a sidebar block, not as a page with its own URL), we want to give it a default one to act on. Select <em>Provide default argument</em>.</li>
+<li>Assuming JavaScript is enabled in your browser, you should now get another selection for <strong>Default argument type</strong>. Select <em>User ID from URL</em>, which will then provide a new option, <em>Also look for a node and use the node author</em>. Select it. This will cause Views to first see if it can figure out a user ID from the current URL (for example, user/1). If it can't, it will instead check to see if the current page is a node page (such as node/42) and, if so, take the user ID from the node's author field instead.</li>
+<li><strong>Validator options</strong> provide a useful way to control what kind of arguments your view will accept. Select <em>User</em> as the <strong>Validator</strong>. By default, changing this setting will check the incoming argument and ensure it's a valid user ID; if not, the view will be hidden from the page.</li>
+<li>Once you have changed the argument's title, default argument, and validator options, click <strong>Update</strong> to save your changes.</li>
+<li>You'll notice that now the Live preview no longer shows anything. Did we just break the view? Fortunately, no. It's merely abiding by our wishes to hide itself if there is no valid user ID given to it. Try entering a '1' in the <strong>Arguments</strong> box and clicking <strong>Preview</strong>. You should now see a list of only user 1's blog entries.</li>
+</ol>
+
+<h3>Creating the block</h3>
+<p>So the live preview is now showing basically what we want. There's just one problem: we have no way to stick what we've done so far into a sidebar block! Let's fix that by adding a new <strong>Display</strong>.</p>
+
+<ol>
+<li>In the first column, under <strong>Defaults</strong>, there is a select box containing entries such as <em>Page</em>, <em>Feed</em>, and, yes, <em>Block</em>! Select <em>Block</em> and click <strong>Add display</strong>.</li>
+<li>There's not much else to do here as far as Views is concerned. Under <strong>Block settings</strong>, click the <em>None</em> link next to <strong>Admin</strong> and fill in a description for the block in the administrative interface, such as: 'Recent blog entries by author.' and click <strong>Update</strong>.</li>
+<li>Save your work by clicking the <strong>Save</strong> button at the bottom of the Views interface. You should receive a message that the view has been saved.</li>
+<li>Next, navigate to the <a target="_blank" href="base_url:admin/structure/block">blocks interface</a> and drag the 'Recent blog entries by author' block to the right sidebar region (or similar) and click <strong>Save blocks</strong>.</li>
+<li>You'll notice this appeared to do nothing. No block shows in the sidebar. But remember, we are looking at an adminitrative page; we are not looking at a page that would provide a user ID context. Navigate to the <a target="_blank" href="base_url:blog">main blog listing</a> and click on an entry there. You should now see a sidebar block, titled something like "Recent entries by admin," with a list of blog entries beneath it.</li>
+</ol>
+
+<h3>Finishing touches</h3>
+<p>There are still a few remaining things to do before our view is complete. For example, we said that the block was to show <em>recent</em> blog entries, but instead it's showing them in the order they were entered, with oldest on top. Additionally, even unpublished entries are showing in the list currently.</p>
+
+<ol>
+<li>Return to the <a target="_blank" href="base_url:admin/structure/views/view/recent_blog_entries">recent_blog_entries view edit page</a>.</li>
+<li>Add an additional filter by clicking the <strong>+</strong> icon in the <strong>Filters</strong> section in the fourth column.</li>
+<li>Change <strong>Groups</strong> to <em>Node</em> and select <em>Node: Published</em>. Click <strong>Add</strong>.</li>
+<li>Under the <strong>Published</strong> selection, choose <em>Yes</em> and click <strong>Update</strong>.</li>
+<li>To handle sorting, locate the <strong>Sort criteria</strong> area, just above filters, and click the <strong>+</strong> icon there.</li>
+<li>Under <strong>Groups</strong>, again select <em>Node</em>. From the list of options, check <em>Node: Post date</em> and click <strong>Add</strong>.</li>
+<li>In the settings page, change <strong>Sort order</strong> to <em>Descending</em>. This will place the newer posts on top of the older ones. Click <strong>Update</strong> when finished.</li>
+<li>Finally, <strong>Save</strong> the view for your new settings to take effect.</li>
+</ol>
diff --git a/sites/all/modules/views/help/example-filter-by-current-user.html b/sites/all/modules/views/help/example-filter-by-current-user.html
new file mode 100644
index 000000000..b7fdd05d0
--- /dev/null
+++ b/sites/all/modules/views/help/example-filter-by-current-user.html
@@ -0,0 +1,46 @@
+<p>In this example you will create a page that displays a list of the content authored by the current logged-in user. This will demonstrate using Views <em>filters</em> and <em>relationships</em> to dynamically filter the view's contents at display time.</p>
+
+<p>For this example, we are assuming you have a content type "Blog Post".</p>
+
+<h3>Creating the View</h3>
+<p>The first step is creating a view for our content list page. Because the page will show the titles of content, this view is considered a "Content" type. Go to <a target="_blank" href="base_url:admin/structure/views/add">add new view</a>, enter the following properties, and click <strong>Next</strong>:</p>
+
+<dl>
+ <dt>View name</dt>
+ <dd>content_by_current_user</dd>
+ <dt>Description</dt>
+ <dd>List of content authored by the current user.</dd>
+</dl>
+
+<p>Choose <strong>Show</strong> <em>Content</em> <strong>of type</strong> <em>Blog Post</em>. You can choose any way you wish to sort the content.</p>
+
+<h3>Creating the page</h3>
+<p>Tick the box next to <strong>Create a page</strong>. Enter a page title and a path. For our purposes here, the default settings for the rest of this page are sufficient.</p>
+
+<p>Click on <strong>Continue &amp; edit</strong>.</p>
+
+<h3>Creating the relationship</h3>
+<p>In order to have access to the author of the content, it is important to create a relationship between the current content type, and users.</p>
+
+<p>Under <strong>Advanced</strong> in the right culumn, select <strong>add</strong> next to <strong>Relationships</strong>.</p>
+<p>Select <em>Content: Author</em> and click on <strong>Add and configure relationships</strong>. Leave the settings as they are and click on <strong>Apply (all displays)</strong>.</p>
+
+<p>You now have access to the user data related to the content you are viewing.</p>
+
+<h3>Filtering the view</h3>
+<p>Now you need to filter the view to display only content authored by the current user. This data is now available for the content because you have created the relationship in the step above.</p>
+
+<p>Next to <strong>Filter criteria</strong> click on <strong>add</strong> to add a new filter to your view.</p>
+
+<p>Filter the list of fields by selecting <em>User</em> next to <strong>Filter</strong> at the top. You now have more fields than before due to the relationship you created.</p>
+
+<p>Select <em>User: Current</em> from the list and click on <strong>Add and configure filter criteria</strong>.</p>
+
+<p>Since this field is only visible due to the relationship you created, <em>author</em> will already be selected under <strong>Relationship</strong>. This shows that the relationship you created is being used for the filter field.</p>
+<p>Select <em>Yes</em> under <strong>Is the logged in user</strong>, and click on <strong>Apply (all displays)</strong>.</p>
+
+<p>If you have authored content of the type <em>Blog Post</em>, you should now see a list of those posts under the preview section at the bottom.</p>
+
+<h3>Saving &amp; testing the view</h3>
+<p>Click on <strong>Save</strong> to save the view.</p>
+<p>You can test the view by going to the path you entered in the first part of this example.</p>
diff --git a/sites/all/modules/views/help/example-recent-stories.html b/sites/all/modules/views/help/example-recent-stories.html
new file mode 100644
index 000000000..7e21324d3
--- /dev/null
+++ b/sites/all/modules/views/help/example-recent-stories.html
@@ -0,0 +1,57 @@
+In this example you will create a list of nodes of the content type "story", to be shown in a block. Through this step-by-step process, you will become familiar with some basic steps in creating a view, and familiarize yourself with the Views User Interface.
+
+<ol>
+<li><h3>Creating a new view</h3>
+<p>Go to <a target="_blank" href="base_url:admin/structure/views/add">add new view</a>. Give your new view the name 'recent_stories', description 'Recent Stories', tag 'story', type 'Node' and click <strong>Next</strong>.</p></li>
+<li><h3>About the interface</h3>
+<p>You have been brought to Views User Interface. As you start, you are editing the "Default" options for the view. In the 1st column on the left you can see the drop-down menu offers 'block', for example, to select settings specific only to block views. In the remaining columns, you will be able to add or change options by clicking on links or icons. These options will then appear below this main area. Most likely, you will need to scroll a bit to see the options appear.</p></li>
+<li><h3>Selecting the fields to display</h3>
+ <ol>
+ <li>In 3rd column locate the <strong>Fields</strong> options. Click the <strong>+</strong> icon to add fields.</li>
+ <li>Scroll down to <strong>Defaults: Add fields</strong>. In the <strong>Groups</strong> drop-down menu select 'Node', then check the following two fields: <em>Node: Post date</em>, <em>Node: Title</em>. Then click <strong>Add</strong>.</li>
+ <li>You will be taken through the fields you added one at a time. Make the changes specified below.
+ <ul>
+ <li>For the <em>Post date</em> field: Delete the 'Post date' label. Change the <strong>Date format</strong> to <em>Custom</em>, and the <strong>Custom date format</strong> to 'F j, Y, g:i a' (do not type the single quotes; for the meaning of these letter codes, click on <em>the PHP docs</em> link under that box to arrive at the explanation). Click <strong>Update</strong>.</li>
+ <li>For the <em>Title</em> field: Delete the 'Title' label. Select <em>Link this field to its node.</em> Click <strong>Update</strong>.</li>
+ </ul>
+ </li>
+ <li>Scroll back up to <strong>Fields</strong> and click the <strong>&uarr;&darr;</strong> icon to rearrange fields.</li>
+ <li>Drag the four-sided arrow next to <em>Node: Title</em> so that it appears above <em>Node: Post date</em>. Click <strong>Update</strong> to save the new field order.</li>
+ </ol>
+</li>
+<li><h3>Filtering to <em>story</em> nodes only</h3>
+ <ol>
+ <li>Click the <strong>+</strong> icon next to <strong>Filters</strong>.</li>
+ <li>In the <strong>Groups</strong> drop-down menu select 'Node', then check the <em>Node: Published</em> and <em>Node: Type</em> filters, and click <strong>Add</strong>.</li>
+ <li>Select the <em>Published</em> checkbox. Click <strong>Update</strong></li>
+ <li>Select <em>Is one of</em> and check <em>Story</em> in the <em>Node Type</em> field. Click <strong>Update</strong>.</li>
+ </ol>
+</li>
+<li><h3>Sorting to show most recent first</h3>
+ <ol>
+ <li>Scroll up to <strong>Sort criteria</strong> and click the <strong>+</strong> icon.</li>
+ <li>In the <strong>Groups</strong> drop-down menu below, select 'Node', then check <em>Node: Post date</em>, and click <strong>Add</strong>. Alternatively, you may instead check <em>Node: Last comment time</em>, or <em>Node: Updated/commented date</em>, or <em>Node: Updated date</em>.</li>
+ <li>Select <em>Descending</em> <strong>Sort order</strong>. Click <strong>Update</strong>.</li>
+ </ol>
+</li>
+<li><h3>Refining the basic settings</h3>
+ <ul>
+ <li>In 1st column under <strong>Basic settings</strong> locate these options:
+ <ul>
+ <li><em>Items to Display</em> setting, click <em>10</em>. Change the '10' to '4'. Click <strong>Update</strong></li>
+ <li><em>Style</em> setting, click <em>Unformatted</em>. Change to <em>List</em>. Click <strong>Update</strong>.</li>
+ </ul>
+ </li>
+ </ul>
+</li>
+<li><h3>Adding a block display for custom options</h3>
+ <ol>
+ <li>In the dropdown on the left, ensure that <em>Block</em> is selected, and click <strong>Add Display</strong>.</li>
+ <li>Under <strong>Block settings</strong>, click the <em>None</em> link next to the <em>Admin</em> setting. Change <strong>Block: Block admin description</strong> to 'Recent Stories'.</li>
+ </ol>
+</li>
+<li><h3>Saving the view</h3>
+<p>Click <strong>Save</strong> to save your work.</p></li>
+<li><h3>Instructing Drupal to show the block</h3>
+<p>Finally, you should tell Drupal to show this block. Configure your block by going to <a target="_blank" href="base_url:admin/structure/block">admin/structure/block</a>. Locate the block in the list: it is labeled <em>Recent Stories</em>. Place this block in a region and click <strong>Save</strong>. You may click <em>Configure</em> to set a different title, to determine which roles can view the block, and on which pages it appears; If you want your block on the front page only, enter '&lt;front&gt;'.</p></li>
+</ol>
diff --git a/sites/all/modules/views/help/example-slideshow-thumb-pager.html b/sites/all/modules/views/help/example-slideshow-thumb-pager.html
new file mode 100644
index 000000000..1c83984d9
--- /dev/null
+++ b/sites/all/modules/views/help/example-slideshow-thumb-pager.html
@@ -0,0 +1,54 @@
+<p>In this example you will create a views block that displays images in a slideshow using thumbnails of the images as a pager underneath the slideshow. This will demonstrate using <em>Views Slideshow </em>and <em>Image Styles</em> to display images.</p>
+
+<p>For this example, we are going to display a single image from each content item of the type &quot;Photos&quot;, which we assume you have already set up with an image field. We are also assuming that <em>Views Slideshow</em> and at least one plugin is installed and activated.</p>
+
+<h3>Creating the image styles</h3>
+
+<p>The first step is creating the right image styles to display the images from the node. We will create one for the slideshow image, and one for the pagers. Go to <a target="_blank" href="base_url:admin/structure/views/add">Image Styles</a> and create the following two styles:</p>
+
+<dl>
+ <dt>Style name</dt>
+ <dd>slideshow_image</dd>
+ <dt>Effects</dt>
+ <dd>Scale and crop: 600px wide, 400px high</dd>
+</dl>
+<dl>
+ <dt>Style name</dt>
+ <dd>slideshow_thumbnail</dd>
+ <dt>Effects</dt>
+ <dd>Scale and crop: 30px wide, 20px high</dd>
+</dl>
+
+<h3>Creating the View and block</h3>
+
+<p>The next step is creating a view for the slideshow. Because the block will show the images in content, this view is considered a "Content" view. Go to <a target="_blank" href="base_url:admin/structure/views/add">add new view</a>, enter the following properties, and click <strong>Next</strong>:</p>
+<dl>
+ <dt>View name</dt>
+ <dd>Photo Slideshow</dd>
+ <dt>Description</dt>
+ <dd>Slideshow of images from Photos.</dd>
+</dl>
+
+<p>Choose <strong>Show</strong> <em>Content</em> <strong>of type</strong> <em>Photos</em>. You can choose any way you wish to sort the content.</p>
+<p>Untick the box next to <strong>Create a page</strong> and tick the box next to <strong>Create a block</strong>. Enter a block title and choose Slideshow from the <strong>Display format</strong> select box. Select <em>fields</em> from the other select box. Leave the remaining settings as they are.</p>
+<p>Click on <strong>Continue &amp; edit</strong>.</p>
+
+<h3>Editing the view settings</h3>
+
+<p>Turn off the pager by clicking on <strong>Display a specified number of items</strong> in the middle column and selecting <strong>Display all items</strong> in the next screen, and applying the settings.</p>
+<p>Enter a Block name by clicking on <strong>None</strong> at the top of the middle column.</p>
+<p>Next, remove the <em>Content: Title</em> field from the fields list in the left column by blicking on <strong>rearrage</strong> under the arrow.</p>
+<p>Next we have to add the thumbnail image field. Click on <strong>Add</strong> under the fields section and select your image field from the list. In the next screen, turn off the label, select <em>Exclude from display</em> and select <em>slideshow_thumbnail</em> from the Image Style select box. Under MORE, enter <em>Thumbnail</em> under Administrative Title.</p>
+<p>Click on <strong>Apply (all displays)</strong>.</p>
+<p>Now we have to add the image field to display in the slideshow. Clcik on <strong>Add</strong> under the fields section and select your image field from the list. In the next screen, turn off the label and select <em>slideshow_image</em> from the Image Style select box. Under MORE, enter <em>Display Image</em> under Administrative Title.</p>
+<p>Click on <strong>Apply (all displays)</strong>.</p>
+
+<h3>Editing the slideshow settings</h3>
+
+<p>Click on <strong>Settings</strong> next to Format: Slideshow in the first column. In the screen that opens we can choose the options for our slideshow.</p>
+<p>For the purpose of this example, we will only add the thumbnails as a pager, and leave the remaining slideshow settings as they are. Select the tick box next to <em>Pager</em> under Bottom Widgets. Select <em>Fields</em> from the Pager Type select box. Select the tick box next to <em>Thumbnail</em>.</p>
+<p>Click on <strong>Apply (all displays)</strong>.</p>
+
+<h3>Saving &amp; testing the view</h3>
+<p>Click on <strong>Save</strong> to save the view.</p>
+<p>You can test the view by adding the block you have created to your theme.</p>
diff --git a/sites/all/modules/views/help/example-user-feed.html b/sites/all/modules/views/help/example-user-feed.html
new file mode 100644
index 000000000..64b2b42ac
--- /dev/null
+++ b/sites/all/modules/views/help/example-user-feed.html
@@ -0,0 +1,73 @@
+<p>In this example you will create a <em>Feed display</em> to show nodes by individual users, dynamically selected through the URL. You will become familiar with the Views 2 interface, as well as learn how to use an argument to pull in a user name and use it in a dynamically created path.</p>
+<p>A <em>feed</em> is a data format that places your site's content into a file that can be read and displayed by news reader programs. When visiting a site, you may notice a small <a href="http://drupal.org/misc/feed.png">RSS transmission icon</a>, whereby clicking on it, you can subscribe to the site's most recent content. This makes it easier for your visitors to keep up to date with your website. You can also use this format to aggregate information into other sites. For more information, please watch a video from Common Craft about <a href="http://www.commoncraft.com/rss_plain_english">RSS in plain English</a>.</p>
+<p>Note, Drupal automatically creates a feed for your website, but you may want to create feeds with specific information. In this case, a list per user. </p>
+<ol>
+ <li>
+ <h3>Creating a new view </h3>
+ <ol>
+ <li>Go to <a target="_blank" href="base_url:admin/structure/views/add">add new view</a>. Give it the name 'user_feed', description 'A feed of user nodes.', tag 'users', type 'Node' and click Next.</li>
+ </ol>
+ </li>
+ <li><strong>About the Interface.</strong> You have been brought to the Views User Interface. As you start, you are editing the &quot;Default&quot; options for the view. In the 1st column on the left- you can see the pull-down menu offers 'Feed', for example, to select settings specific only to RSS views. In the remaining columns, you will be able to add or change options by clicking on links or icons. These options appear below this main area. Most likely, you will need to scroll to see the options appear. As you make changes, these options will appear in bold until you save your view.</li>
+ <li>
+ <h3>Change default display</h3>
+ <ol>
+ <li>Under <strong>Basic Settings</strong> in the 2nd column, click <em>Row style: Fields</em></li>
+ <li>A menu loads below, <em>Defaults: How should each row in this view be styled</em>, check the <em>Node</em> option, and click <strong>Update</strong>.</li>
+ <li>This loads another options menu, <em>Defaults: Row style options</em> click <strong>Update</strong>.</li>
+ </ol>
+ </li>
+ <li>
+ <h3>Create the RSS view </h3>
+ <ol>
+ <li>In the 1st column, select 'Feed' in the drop-down menu, and click <strong>Add Display</strong>.</li>
+ <li>Under <strong>Basic Settings </strong>in the 2nd column, click<em> Row style:Missing style plugin</em></li>
+ <li>Note, options appear below the Views Interface, you may need to scroll to see <em>Feed: How should each row in this view be styled</em><br />
+ tick <strong>Node</strong>, then <strong>Update</strong></li>
+ <li>This loads the next options menu- <em>Display type: </em>select &quot;Use default RSS settings&quot;, click <strong>Update</strong>.</li>
+ </ol>
+ </li>
+ <li>
+ <h3>Set the path for accessing your feed</h3>
+ <ol>
+ <li> In the 2nd column under <strong>Feed settings</strong>, click <em>Path: None </em></li>
+ <li>In options below <em>Feed: The menu path or URL of this view</em> enter in the path with an argument feeds/%/rss.xml</li>
+ <li>Click <strong>Update</strong></li>
+ </ol>
+ </li>
+ <li>
+ <h3>Set up your arguments to say which user's nodes to display</h3>
+ <ol>
+ <li>To the right of <strong>Arguments</strong>, click the + sign to add and argument</li>
+ <li>In the Feed: Add arguments menu that loads below, select User in the pull-down menu</li>
+ <li>Check the box <em>User: Name</em>, click <strong>Add</strong></li>
+ <li>Scroll down to options to find <strong>Case in path:</strong> select <em>Lower case</em></li>
+ <li>Check the box <em>Transform spaces to dashes in URL</em></li>
+ <li>Click <strong>Update default display</strong></li>
+ </ol>
+ </li>
+ <li>
+ <h3>Sort to show most recent at top of feed</h3>
+ <ol>
+ <li>Scroll up to <strong>Sort criteria</strong> in the right most column and click the + icon.</li>
+ <li>In the <strong>Groups</strong> drop-down menu below, select 'Node', then check <em>Node: Post date</em>, and click <strong>Add</strong>. </li>
+ <li>Select <em>Descending</em> <strong>Sort order</strong>. Click <strong>Update</strong>.</li>
+ </ol>
+ </li>
+ <li>
+ <h3>Set filters to hide unpublished entries</h3>
+ <ol>
+ <li>Click the + icon next to <strong>Filters</strong>. In the options below, select <em>Node</em> under <strong>Groups</strong> drop-down menu, choose the <em>Node: Published</em> filter, and click <strong>Add</strong>.</li>
+ <li>Check the box <em>Published</em>. Click <strong>Update default display</strong></li>
+ </ol>
+ </li>
+ <li>
+ <h3>Test</h3>
+ <ol>
+ <li>Click <strong>Save</strong></li>
+ <li>Under <strong>Live preview</strong> type in the name of a user, in lowercase, replacing spaces with dashes, click <strong>Preview</strong>.</li>
+ <li>You should test and find your feeds at URLs like http://yoursite.com/feeds/user-name/rss.xml</li>
+ <li>You can use this path for aggregating on another site. You can also attach the RSS feed to another display of view to make the feed link appear on that display.</li>
+ </ol>
+ </li>
+</ol>
diff --git a/sites/all/modules/views/help/example-users-by-role.html b/sites/all/modules/views/help/example-users-by-role.html
new file mode 100644
index 000000000..f5228be3b
--- /dev/null
+++ b/sites/all/modules/views/help/example-users-by-role.html
@@ -0,0 +1,47 @@
+In this example you will create a page view listing users on your site. Through this step-by-step process, you will become familiar with some basic steps in creating a view, and familiarize yourself with the Views User Interface.
+
+<ol>
+<li><h3>Creating a new view</h3>
+<p>Go to <a target="_blank" href="base_url:admin/structure/views/add">add new view</a>. Give your new view the name 'user_list', description 'A simple user listing.', tag 'users', type 'User' and click <strong>Next</strong>.</p></li>
+<li><h3>About the Interface</h3>
+<p>You have been brought to the Views User Interface. As you start, you are editing the "Default" options for the view. In the 1st column on the left you can see the drop-down menu offers 'block', for example, to select settings specific only to block views. In the remaining columns, you will be able to add or change options by clicking on links or icons. These options will then appear below this main area. Most likely, you will need to scroll to see the options appear. As you make changes, these options will appear in bold until you save your view.</p></li>
+<li><h3>Creating a page display; choosing a URL and creating a menu link</h3>
+ <ol>
+ <li>In the 1st column, ensure that 'Page' is selected in the drop-down menu, and click <strong>Add Display</strong>.</li>
+ <li>Next we'll define the path for this page. A page must have a path, and we define it early so that Views doesn't warn us "Display Page uses path but path is undefined." Locate the <strong>Page settings</strong> in the 2nd column, and click the <em>None</em> link next to the <em>Path</em> setting. In the options editing area that appears below, set the path to 'user_list' (or something Implement you prefer) and click <strong>Update</strong>.</li>
+ <li>Next to <em>Menu</em> setting, Click the <em>No menu</em> link. In the options which appear below, select <em>Normal menu entry</em>, and set the title to 'User list' and click <strong>Update</strong>.</li>
+ <li>Scroll up to <strong>Basic settings</strong>, in that same 2nd column, and click the <em>No</em> link next to <em>Use pager</em>. Below, in the options, select <em>Full pager</em> and click <strong>Update default display</strong>.</li>
+ </ol>
+</li>
+<li><h3>Selecting the fields to display</h3>
+ <ol>
+ <li>In 3rd column locate the <strong>Fields</strong> options. Click the <strong>+</strong> icon to add fields.</li>
+ <li>Scroll down to <strong>Defaults: Add fields</strong>. In the <strong>Groups</strong> drop-down menu select 'User', then check the following fields: <em>User: Created date</em>, <em>User: Delete link</em>, <em>User: Edit link</em>, <em>User: Last access</em>, <em>User: Name</em> and <em>User: Picture</em>. Then click <strong>Add</strong>.</li>
+ <li>You will be taken through the fields you added one at a time. Click <strong>Update default display</strong> to go to each next field. Leave the default options on all fields except <em>Delete link</em>; change that field's label to 'Operations'.</li>
+ <li>Scroll back up to <strong>Fields</strong> and click the <strong>&uarr;&darr;</strong> icon to rearrange fields. Down below, drag the <em>Name</em> field, by dragging its four-sided arrow, to the top. Drag the <em>Delete link (Operations)</em> field to the bottom, and the <em>Edit link</em> field just above it. Then click <strong>Update</strong>.</li>
+ </ol>
+</li>
+<li><h3>Seeing what we've done so far</h3>
+<p>At this point, you have done enough to create a valid view. If you scroll down, you will see a preview of your view. If it doesn't show already, click the <strong>Preview</strong> button; but generally this display updates automatically whenever you finish working in one of the mini forms.</p></li>
+<li><h3>Styling the view as a table; combining related fields into columns</h3>
+ <ol>
+ <li>Under <strong>Basic settings</strong>, in the 1st column, click the <em>Unformatted</em> link next to the <em>Style</em> setting. In the options below, under <strong>Page: How should this view be styled</strong>, choose <em>Table</em> and click <strong>Update default display</strong>.</li>
+ <li>You will be taken to a <strong>Page: Style options</strong> form to edit the table settings. Locate our <em>Edit link</em> field in this mini form, and notice the <strong>Column</strong> drop-down. Change this drop-down to show <em>Operations</em>. In the <strong>Separator</strong> column next to the <em>Operations</em> field, type ' | ' (note the spaces around the <strong>|</strong> symbol). Check all of the <strong>Sortable</strong> checkboxes, and set <strong>Default sort</strong> to <em>Name</em>. When finished, click <strong>Update default display</strong>.</li>
+ </ol>
+</li>
+<li><h3>Filtering the user list to exclude unwanted entries</h3>
+ <ol>
+ <li>Click the <strong>+</strong> icon next to <strong>Filters</strong>.</li>
+ <li>In the <strong>Groups</strong> drop-down menu select 'User', then check the <em>User: Name</em> filter, and click <strong>Add</strong>.</li>
+ <li>Select <em>Is not one of</em> and enter 'Anonymous' in the <strong>Usernames</strong> box. Click <strong>Update default display</strong>.</li>
+ </ol>
+</li>
+<li><h3>Adding an argument to list users by role dynamically</h3>
+ <ol>
+ <li>Scroll up to <strong>Arguments</strong>, and click its <strong>+</strong> icon.</li>
+ <li>Check the <em>User: Roles</em> argument, and click <strong>Add</strong>. Set the title to '%1' (don't type the quotes), and under <strong>Action to take if argument is not present</strong> select <em>Summary, sorted ascending</em>. Leave the other settings as they are. Click <strong>Update default display</strong>, and click <strong>Update</strong> through the prompts that follow to accept their default values.</li>
+ </ol>
+</li>
+<li><h3>Saving the view</h3>
+<p>Finally, click the <strong>Save</strong> button to save your work. At the very top, click <strong>View "Page"</strong> to go to your new view!</p></li>
+</ol>
diff --git a/sites/all/modules/views/help/exposed-form.html b/sites/all/modules/views/help/exposed-form.html
new file mode 100644
index 000000000..ca06e9fba
--- /dev/null
+++ b/sites/all/modules/views/help/exposed-form.html
@@ -0,0 +1,24 @@
+This is used when you want to position the exposed form in the sidebar or anywhere else, but not with the view. Instead, a block will be made available to the Drupal block administration system, and the exposed form will appear there. Note that this block must be enabled manually, Views will not enable it for you.
+To do this select "Exposed form in block: Yes" and choose one option from "Exposed form style".
+<dl>
+<dt>Basic</dt>
+<dd>When you expose a form and the view is loaded, no filter is selected and all items will displayed.</dd>
+<dt>Input required</dt>
+<dd>When you expose a form and the view is loaded, only the filter settings are shown. After you select one filter and hit the apply button, the items willi be shown.</dd>
+</dl>
+
+You have several options to customize the appearance of the exposed forms:
+<dl>
+<dt>Submit button text</dt>
+<dd>Text to display in the submit button of the exposed form.</dd>
+<dt>Include reset button</dt>
+<dd>If checked the exposed form will provide a button to reset all the applied exposed filters</dd>
+<dt>Reset button label</dt>
+<dd>Text to display in the reset button of the exposed form.</dd>
+<dt>Exposed sorts label</dt>
+<dd>Text to display as the label of the exposed sort select box.</dd>
+<dt>Ascending</dt>
+<dd>Text to use when exposed sort is ordered ascending.</dd>
+<dt>Descending</dt>
+<dd>Text to use when exposed sort is ordered descending.</dd>
+</dl>
diff --git a/sites/all/modules/views/help/field.html b/sites/all/modules/views/help/field.html
new file mode 100644
index 000000000..e9d9e609c
--- /dev/null
+++ b/sites/all/modules/views/help/field.html
@@ -0,0 +1,27 @@
+Fields are the individual pieces of data being displayed. Adding the fields <em>Node: Title</em>, <em>Node: Type</em>, and <em>Node: Post date</em> to a node view, for example, includes the title, content type and creation date in the displayed results).
+
+Fields may not appear on every display, because not all style plugins actually use <a href="topic:views/style-fields">fields</a>. For example, the '<a href="topic:views/style-node">node</a>' <a href="topic:views/style-row">row plugin</a> simply displays the node through Drupal's normal mechanisms, and fields are not involved.
+For the most part, the field settings should be self explanatory. Fields will appear in the order that they are arranged in, and they will usually appear with the label they are given.
+
+If you add new cck fields you will find them under the Group "Content". Search for the field name. With new modules the list of groups will grow. Modules can add new items with the <em>hook_views_data()</em> hook.
+
+If you do not find a field, consider whether or not you need a <a href="topic:views/relationship">Relationship</a>.
+
+You can override the entire field section - see <a href="topic:views/overrides">here</a> for more information.
+
+When a field is added, the "Configure field" modal opens. It has a dropdown at the top that lets you choose what display this field configuration is valid for (ie, this display, or all displays.)
+
+You start by configuring three checkboxes:
+<ul>
+<li>Create a label: when checked, this opens a textbox that can be filled out to label the field. </li>
+<li>Exclude from display: Loads the field, but hides it from general view. This is useful for grouping fields by hiding the group from the user's view.</li>
+<li>Link this field to the original piece of content: overrides any default link set up.</li>
+</ul>
+
+Style settings give you several options for wrapping an HTML element around a field; title, for example, can be wrapped in an H1-H6, a SPAN, DIV, etc. It can also be given a particular identifying CSS class of its own here. You can do the same with the label or do them both at the same time. This is part of the Semantic Views integration. Alternately, you can leave the default Views classes to identify the field and content.
+
+If you have No Results, you can customize that in this modal also. You have the options to count 0 as empty, or to hide the entire field if it's empty.
+
+Next up is the Rewrite results: all the options you need to rewrite the output of the field with tokens, custom text, a link, etc. If your field is output as a link using the <strong>Output this field as a link</strong> option, you can include the "Alt" text for a link hover in a Title attribute. This is important for accessibility.
+
+The last section is the administrative title, which just gives you a place to give the field a special name on the admin screen in case you have more than one copy.
diff --git a/sites/all/modules/views/help/filter.html b/sites/all/modules/views/help/filter.html
new file mode 100644
index 000000000..cb84f8a8a
--- /dev/null
+++ b/sites/all/modules/views/help/filter.html
@@ -0,0 +1,35 @@
+Filters are used to reduce the data set that Views provides. That is to say, without any filters applied, Views will return all of your content. You don't want that, so at least some filters must be used.
+
+Some very commonly used filters:
+<ul>
+<li> The 'Node: Published' filter is used to restrict a node View to only nodes that are are have the 'published' box checked. This can be very important to prevent users from viewing content they should not have access to.</li>
+<li> The 'Node: Promoted to front page' filter can be used to show only nodes that have the 'promote to front page' turned on. </li>
+<li> The 'Node: Type' filter is useful for showing only certain types of nodes. Let's say you wanted users to see only nodes that were 'book' nodes, or a combination of 'book' nodes and 'staff-blog' nodes. This filter allows you to select exactly that.</li>
+<li> The 'User: Current' filter will show only nodes that the logged in user has authored.</li>
+<li> The 'Node: Post date' filter can be used to show only nodes posted before, after, or between a range of dates.</li>
+</ul>
+
+The above list is only a tiny fraction of the filters available in Views, referenced here to give an idea of the kinds of tasks filters can accomplish. When you do not find a filter type, you may need to choose a <a href="topic:views/relationship">Relationship</a> before the expected filter will show, or to install a new module that contains the requested filter.
+
+When you click the Rearrange Icon you can first rearrange your filters, easily delete filters and select an operator: "AND" or "Or". By default the "AND" operator is selcted. At the lower right of the window is the new button "Add new group". When you click on it, you can drag and drop an individual filter to the new group "Group 1". For this new group and the default group you can select the "Group operator": "And" or "Or". To remove a group, remove all filters and click the "Remove group 1" button.
+
+When you want that the user to select their own filter, you can expose the filter. A selection box will show for the user and they will be able to select one item. After that the view will reload and only the selected item will be displayed. You can also choose to expose the selection to a block, see <a href="topic:views/exposed-form">here</a>.
+
+For exposed filters, you can create a grouped filter. When filters are in a group, each item of the group represents a set of operators and values. The following table illustrates how this feature works. The values of the first column of the table are displayed as options of a single select box:
+
+<table>
+<thead>
+<tr><th>What the user see</th><th>What views does</th></tr>
+</thead>
+<tbody>
+<tr><td>Is lower than 10</td><td><strong>Operator:</strong> Is Lower than. <strong>Value:</strong> 10</td></tr>
+<tr><td>Is between 10 and 20</td><td><strong>Operator:</strong> Is between. <strong>Value:</strong> 10 and 20</td></tr>
+<tr><td>Is greater than 20</td><td><strong>Operator:</strong> Is Greater. <strong>Value:</strong> 20</td></tr>
+</tbody>
+</table>
+
+<strong>Please note:</strong> When using grouped filters with the option: 'Enable to allow users to select multiple items' checked, you probably may want to to place the filter in a separated group and define the operator of the group as 'OR'. This may be neccesary because in order to use multiple times the same filter, all options have to be applied using the OR operator, if not, probably you will get nothing listed since usually items in a group are mutually exclusive.
+
+Taxonomy filters have been significantly altered in Views 7.x-3.x. D7 significantly re-organized taxonomy, there was a lot of duplicate taxonomy related fields and filters. Some of them were removed to try and reduce confusion between them. Implicit relationships to taxonomy have been removed, in favor of explicit relationships. If the filters you can find don't do what you need, try adding either the related taxonomy terms relationship, or a relationship on the specific taxonomy field. That will give you the term specific filters.
+
+You can override the complete filter section - see <a href="topic:views/overrides">here</a> for more information.
diff --git a/sites/all/modules/views/help/get-total-rows.html b/sites/all/modules/views/help/get-total-rows.html
new file mode 100644
index 000000000..623b35fe7
--- /dev/null
+++ b/sites/all/modules/views/help/get-total-rows.html
@@ -0,0 +1,16 @@
+The flag $view->get_total_rows is used to force the query of the view to calculate the total number of results of the set.
+
+This parameter is TRUE by default in views that get all the results (no limit) or those which have a pager, so you always have the $view->total_rows variable populated in those cases.
+But when you have a view that gets only a given number of results and no pager, the count query is not executed by default so you have to force it, i.e. in the hook_views_pre_execute so you have $view->total_rows populated for later use.
+
+This code will help you do that.
+
+<pre>
+&lt;?php
+function my_module_views_pre_execute(&$view) {
+ if ($view->name == 'my_view' && $view->current_display == 'my_display') {
+ $view->get_total_rows = TRUE;
+ }
+}
+?&gt;
+</pre>
diff --git a/sites/all/modules/views/help/getting-started.html b/sites/all/modules/views/help/getting-started.html
new file mode 100644
index 000000000..e05af40b9
--- /dev/null
+++ b/sites/all/modules/views/help/getting-started.html
@@ -0,0 +1,23 @@
+For those new to Views, it can be a complex system that appears totally overwhelming. The good news is that the UI is designed to compartmentalize everything; this means that for the most part, you can ignore the parts you're not interested in. Start small and build your way up.
+
+Because of this, the edit UI can seem overwhelming at first, but there are really just a few things you <strong>have</strong> to know. The rest you can find through exploration. The Views Edit UI image, below, gives an overview of what you'll find on the edit UI.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/overview-ui-large.png"><img src="path:images/overview-ui-small.png" /></a>
+<em>The Views Edit UI</em>
+</div>
+
+Notes:
+1) Every view has a number of <a href="topic:views/display">displays</a> which represent where output will be placed. If you're familiar with the original Views 1, you could set a view to be a 'page', with a URL (path), or a block that could show up in a sidebar. With Views 2, you can add as many displays as you like. In addition, you have the <em>default</em> display which contains the basic settings, but doesn't actually show up anywhere.
+
+2) When you click on the link for an item, a form will open up. For browsers with smaller resolutions, you may have to scroll down a little to see this form. If a form is open, the item its attached to will be highlighted.
+
+3) <a href="topic:views/overrides">Overrides</a> mean that a particular display is <strong>not</strong> using default settings. When you create a new display, many of its settings will start off using default values. This will be indicated by italics and a lighter color. <strong>If you change these values without first overriding them, you will change the default value for all displays that use them.</strong>
+
+4) Some items, particularly styles, have additional settings. Ordinarily when you <em>update</em> a style, if it has additional settings you will automatically see that form next. Often, you will need to go directly to those settings.
+
+5) You can safely leave a view page to go and do other things. If you come back, the view will still be there, stored in a cache. Keep in mind, however, that while you do this, that view is <em>locked</em>, meaning another user cannot edit this view without breaking the lock. Breaking the lock will discard your changes.
+
+6) Don't forget permissions. Views installs with two default permissions. Users with access all views permissions will have access to all views. Users with administer views permissions will be able to edit and change views. If you are trying to restrict access based on role, make sure that the role does not have access all views checked.
+
+It helps to have something particular in mind that you want to accomplish when using Views. Here are a couple of ideas and a brief sketch of how to accomplish what you want.
diff --git a/sites/all/modules/views/help/group-by.html b/sites/all/modules/views/help/group-by.html
new file mode 100644
index 000000000..871fb5368
--- /dev/null
+++ b/sites/all/modules/views/help/group-by.html
@@ -0,0 +1,17 @@
+This is another major new feature for Views that has been long requested. It incorporates another module that has seen a relatively wide amount of use: views_groupby. This feature provides multiple new options for manipulating data. First, it includes the important “group” SQL functionality, and then enables aggregation functions for Views, such as SUM and COUNT.
+<div class="help-box" style="text-align:center">
+<a href="path:images/views3-group-aggregation.png"><img src="path:images/views3-group-aggregation.png" /></a>
+<em>Where to set aggregation to get group settings</em>
+</div>
+
+Grouping is available for sorts and filters. To use grouping, “Use grouping” must be enabled on a per-view basis in the Views UI. This is toggled in the Advanced settings box by clicking the link next to “Use grouping”. Once you activate this checkbox (be sure to read the notes in the UI!), functions for aggregating particular fields will become available. The gear icon that should be familiar to users with any amount of Views experience will now appear next to any sorted or filtered field with aggregation capabilities. Choosing that icon will open up the configuration for the aggregation functions. As an example, this could be used to count things like number of posts in a day, or number of published posts. This could also be used to sum the values of a row, instead of everything in the view.
+
+Setting only the "Use aggregation" turned on itself does nothing. It only gives the possibility to set Aggregation types.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views3-group-aggregation-types.png"><img src="path:images/views3-group-aggregation-types.png" /></a>
+<em>Different aggregation possibilities</em>
+</div>
+
+
+It should be noted that modules that are providing data to Views are responsible for noting whether a field supports aggregation or not; modules that do not provide this information may not have all of their fields available to Views if this data is not in place.
diff --git a/sites/all/modules/views/help/header.html b/sites/all/modules/views/help/header.html
new file mode 100644
index 000000000..bc31b490e
--- /dev/null
+++ b/sites/all/modules/views/help/header.html
@@ -0,0 +1,3 @@
+In this section you can choose one or more areas, by default a text area, which will display above the view output.
+
+You can override the complete header section - see <a href="topic:views/overrides">here</a> for more information.
diff --git a/sites/all/modules/views/help/images/node-term_node-term_data-large.png b/sites/all/modules/views/help/images/node-term_node-term_data-large.png
new file mode 100644
index 000000000..4fcd19146
--- /dev/null
+++ b/sites/all/modules/views/help/images/node-term_node-term_data-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/node-term_node-term_data.png b/sites/all/modules/views/help/images/node-term_node-term_data.png
new file mode 100644
index 000000000..de1273c98
--- /dev/null
+++ b/sites/all/modules/views/help/images/node-term_node-term_data.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/overview-ui-large.png b/sites/all/modules/views/help/images/overview-ui-large.png
new file mode 100644
index 000000000..04fbe90d1
--- /dev/null
+++ b/sites/all/modules/views/help/images/overview-ui-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/overview-ui-small.png b/sites/all/modules/views/help/images/overview-ui-small.png
new file mode 100644
index 000000000..ed7595f26
--- /dev/null
+++ b/sites/all/modules/views/help/images/overview-ui-small.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/style-breakdown-large.png b/sites/all/modules/views/help/images/style-breakdown-large.png
new file mode 100644
index 000000000..698b8cc32
--- /dev/null
+++ b/sites/all/modules/views/help/images/style-breakdown-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/style-breakdown.png b/sites/all/modules/views/help/images/style-breakdown.png
new file mode 100644
index 000000000..d7513a89b
--- /dev/null
+++ b/sites/all/modules/views/help/images/style-breakdown.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views1-admin-large.png b/sites/all/modules/views/help/images/views1-admin-large.png
new file mode 100644
index 000000000..06744bddc
--- /dev/null
+++ b/sites/all/modules/views/help/images/views1-admin-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views1-admin.png b/sites/all/modules/views/help/images/views1-admin.png
new file mode 100644
index 000000000..398c145b9
--- /dev/null
+++ b/sites/all/modules/views/help/images/views1-admin.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views1-changeviewtype-large.png b/sites/all/modules/views/help/images/views1-changeviewtype-large.png
new file mode 100644
index 000000000..5c58d8137
--- /dev/null
+++ b/sites/all/modules/views/help/images/views1-changeviewtype-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views1-changeviewtype.png b/sites/all/modules/views/help/images/views1-changeviewtype.png
new file mode 100644
index 000000000..6b1798a33
--- /dev/null
+++ b/sites/all/modules/views/help/images/views1-changeviewtype.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-addaview-large.png b/sites/all/modules/views/help/images/views2-addaview-large.png
new file mode 100644
index 000000000..bbad1b18f
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-addaview-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-addaview.png b/sites/all/modules/views/help/images/views2-addaview.png
new file mode 100644
index 000000000..546ea169c
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-addaview.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-adddisplay-large.png b/sites/all/modules/views/help/images/views2-adddisplay-large.png
new file mode 100644
index 000000000..51c367551
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-adddisplay-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-adddisplay.png b/sites/all/modules/views/help/images/views2-adddisplay.png
new file mode 100644
index 000000000..dff143e56
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-adddisplay.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-addfields-large.png b/sites/all/modules/views/help/images/views2-addfields-large.png
new file mode 100644
index 000000000..b7c1ba58c
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-addfields-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-addfields.png b/sites/all/modules/views/help/images/views2-addfields.png
new file mode 100644
index 000000000..e70c75817
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-addfields.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-addfieldsajax-large.png b/sites/all/modules/views/help/images/views2-addfieldsajax-large.png
new file mode 100644
index 000000000..a9308a031
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-addfieldsajax-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-addfieldsajax.png b/sites/all/modules/views/help/images/views2-addfieldsajax.png
new file mode 100644
index 000000000..3043d04a5
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-addfieldsajax.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-admin-large.png b/sites/all/modules/views/help/images/views2-admin-large.png
new file mode 100644
index 000000000..d262bc55e
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-admin-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-admin.png b/sites/all/modules/views/help/images/views2-admin.png
new file mode 100644
index 000000000..c27336381
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-admin.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-changedisplaystyle-large.png b/sites/all/modules/views/help/images/views2-changedisplaystyle-large.png
new file mode 100644
index 000000000..09925df87
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-changedisplaystyle-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-changedisplaystyle.png b/sites/all/modules/views/help/images/views2-changedisplaystyle.png
new file mode 100644
index 000000000..5a82ea561
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-changedisplaystyle.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-fieldspreview-large.png b/sites/all/modules/views/help/images/views2-fieldspreview-large.png
new file mode 100644
index 000000000..e2730b414
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-fieldspreview-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-fieldspreview.png b/sites/all/modules/views/help/images/views2-fieldspreview.png
new file mode 100644
index 000000000..5a41ab29d
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-fieldspreview.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-newview-large.png b/sites/all/modules/views/help/images/views2-newview-large.png
new file mode 100644
index 000000000..498627a90
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-newview-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-newview.png b/sites/all/modules/views/help/images/views2-newview.png
new file mode 100644
index 000000000..b522d2ce3
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-newview.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-rearrangefields-large.png b/sites/all/modules/views/help/images/views2-rearrangefields-large.png
new file mode 100644
index 000000000..acfed4cdf
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-rearrangefields-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-rearrangefields.png b/sites/all/modules/views/help/images/views2-rearrangefields.png
new file mode 100644
index 000000000..562df08d6
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-rearrangefields.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-tablestyle-large.png b/sites/all/modules/views/help/images/views2-tablestyle-large.png
new file mode 100644
index 000000000..67e9e6b9c
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-tablestyle-large.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views2-tablestyle.png b/sites/all/modules/views/help/images/views2-tablestyle.png
new file mode 100644
index 000000000..f8997403a
--- /dev/null
+++ b/sites/all/modules/views/help/images/views2-tablestyle.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views3-group-aggregation-types.png b/sites/all/modules/views/help/images/views3-group-aggregation-types.png
new file mode 100644
index 000000000..ac6f32fe5
--- /dev/null
+++ b/sites/all/modules/views/help/images/views3-group-aggregation-types.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views3-group-aggregation.png b/sites/all/modules/views/help/images/views3-group-aggregation.png
new file mode 100644
index 000000000..ef93bba66
--- /dev/null
+++ b/sites/all/modules/views/help/images/views3-group-aggregation.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views3-jump-style-menu.png b/sites/all/modules/views/help/images/views3-jump-style-menu.png
new file mode 100644
index 000000000..6945b94ef
--- /dev/null
+++ b/sites/all/modules/views/help/images/views3-jump-style-menu.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views3-semanticviews.png b/sites/all/modules/views/help/images/views3-semanticviews.png
new file mode 100644
index 000000000..e01163a79
--- /dev/null
+++ b/sites/all/modules/views/help/images/views3-semanticviews.png
Binary files differ
diff --git a/sites/all/modules/views/help/images/views3-views-all.png b/sites/all/modules/views/help/images/views3-views-all.png
new file mode 100644
index 000000000..27ce88ed4
--- /dev/null
+++ b/sites/all/modules/views/help/images/views3-views-all.png
Binary files differ
diff --git a/sites/all/modules/views/help/menu.html b/sites/all/modules/views/help/menu.html
new file mode 100644
index 000000000..bb818a39b
--- /dev/null
+++ b/sites/all/modules/views/help/menu.html
@@ -0,0 +1,21 @@
+Page displays can hook into the Drupal menu system and provide <strong>menu links</strong> that will appear in the Navigation system as well as <strong>tabs</strong> that can be used to keep Views next to each other.
+
+For simple <strong>menu links</strong>, there is very little you need to do; simply select 'Normal menu entry' and fill in the text for the title. This will appear in the Navigation menu by default; you will need to visit the menu administration page to move this to another menu.
+
+Tabs are not quite so simple; there are some complex rules for using tabs in Drupal.
+<ol>
+<li> All tabs must have a parent, which is the next level up in the path hierarchy. For example, if the view path is 'example/taba' then the parent must be 'example'.
+<li> All tabs must have one and only one default tab; this is usually the same content as the parent.
+<li> If a parent does not exist, when creating the 'default' tab, Views will allow you to also create a parent item. It will automatically set up the URL for you when it does this.
+<li> Tab weight is used to control what order the tabs are displayed in. Lower numbers will display more to the left. For tabs whose numbers are the same, they will be displayed alphabetically.
+<li> Drupal only supports 2 levels of tabs, so be careful about trying to put tabs within tabs within tabs. That won't work.
+</ol>
+
+For example, if you have two views that you want to be tabs, you could set it up like this:
+<ul>
+<li> In the first view, set the path to 'tabs/tab1'. Set it to be the 'default tab', set the title to 'Tab 1' and the weight to 0.
+<li> Click update and you will be taken to a form that lets you define the parent. Since 'tabs' doesn't already exist in the system, select 'Normal menu item', and set the title to 'Tabs'.
+<li> On the second view, set the path to 'tabs/tab2'; set it to be a 'Menu tab', and set the title to 'Tab 2'.
+</ul>
+
+With this done, you will now have a Navigation link named 'Tabs' and when you click on it, you will go to the tabs, with 'Tab 1' being the default tab that appears. You can then click between Tab 1 and Tab 2.
diff --git a/sites/all/modules/views/help/misc-notes.html b/sites/all/modules/views/help/misc-notes.html
new file mode 100644
index 000000000..8efc1f9e3
--- /dev/null
+++ b/sites/all/modules/views/help/misc-notes.html
@@ -0,0 +1,11 @@
+<h3>Image Assist &amp; ImageField Assist</h3>
+Under certain conditions these modules can block access to the header input section of the admin.
+They need to be disabled on certain views paths. To do this go to
+Image assist ->Access settings
+check "NOT on specific paths":
+and enter
+
+Drupal 7.x has different paths
+admin/structure/views/ajax/display/*
+admin/structure/views/ajax/*
+For futher reference please see <a href="http://drupal.org/node/415990">this issue for more information</a>
diff --git a/sites/all/modules/views/help/new.html b/sites/all/modules/views/help/new.html
new file mode 100644
index 000000000..bee52e9f0
--- /dev/null
+++ b/sites/all/modules/views/help/new.html
@@ -0,0 +1,131 @@
+Views 3 is the newest major release of Views and is coded for Drupal 6. Views 3 retains all of the core functionality of previous releases, together with a completely revamped user interface and a large set of new features.
+
+Major new features (these need help files):
+<ol>
+<li>Views Or has been incorporated.</li>
+<li><a href="topic:views/semantic-views">Semantic views</a> have been incorporated.</li>
+<li><a href="topic:views/group-by">Group by</a> possibility.</li>
+<li>The Jump Menu style has been added.</li>
+<li>The Panel Fields style has been added.</li>
+<li>Pluggable back ends</li>
+<li>Pluggable text areas, pagers, and forms</li>
+<li>Translation plugin</li>
+<li>Exposed sorts</li>
+<li>Reliance on CTools</li>
+</ol>
+
+Everything below this point is subject to change based on the new Views wizard and UI in 7.x.
+
+<p>Note: Only UID 1 or users with the "Use PHP code" permission from the phpfilter module will be able to import views.</p>
+
+<h2>Admin interface</h2>
+The first thing that pops out after you install Views 2 is the radically different admin interface:
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-admin-large.png"><img src="path:images/views2-admin.png" /></a>
+<em>Views 2 admin interface</em>
+</div>
+
+compared to the old comfy Views 1 interface:
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views1-admin-large.png"><img src="path:images/views1-admin.png" /></a>
+<em>Views 1 admin interface</em>
+</div>
+
+The new admin interface performs the same functions as the old -- listing all the views in the system, providing links to add or import views and a link to Views Tools -- but has been compacted, with each view displayed as a paragraph style-row compared to the table of Views 1 and set of filters on top to ease locating views among a large list.
+
+Context-help is available by clicking the small blue question-mark icon. Context-help in Views 2 is provided by the <a href="http://drupal.org/project/advanced_help">Advanced Help</a> module, so make sure to install that together with installing Views 2. The small blue help icons will be available in various parts of the Views UI. In particular, look for them as part of the description of a <strong>display</strong>, when setting <strong>style</strong> options, and in various editing sections such as <strong>path</strong>, <strong>menu</strong> and the like.
+
+Several new attributes of each view are visible in the filter header:
+<ol>
+<li><strong>Tag</strong> - This is just another label for organizing and sorting views. Tags can be any text. Views that are provided by modules will often be tagged together to make it easy to find them. Tags are also added to your template suggestions, so take care what you set here. For example setting the tag <i>Page</i> will give all your views the Page template.</li>
+<li><strong>Display</strong> - In Views 1 each view query was tied to its display; in other words your fields, sorts, filters, and arguments could only be displayed in the single page or block display provided in the view definition. In Views 2, view displays have been decoupled from view queries - it is now possible to have multiple page, block, and feed displays from a single view. More on view displays later. </li>
+<li><strong>Type</strong> - Views 2 view types are radically different from Views 1 types. Views 1 types basically defined how the node list displays were <i>styled</i> - you had Full Nodes, Teaser List, Table View, and so on. In Views 2 view display styles have been broken out into the separate <i>Style</i> attribute. View types now refer to the primary table on which information for the query will be retrieved which controls what arguments, fields, sort criteria and filters are available. Views 2 view types are discussed later.
+</li>
+</ol>
+
+<h2>Adding a view</h2>
+So let's jump in and add a view. For this example, we're going to create a <strong>user</strong> view, which will display a list of users.
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-addaview-large.png"><img src="path:images/views2-addaview.png" /></a>
+<em>Adding a view</em>
+</div>
+
+The first step in adding a view is simply entering a name (only alphanumeric characters, no spaces) a description, tag, and the view type. To get the user view, we selected the <strong>User</strong> radio button.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-newview-large.png"><img src="path:images/views2-newview.png" /></a>
+<em>Configuring the new view</em>
+</div>
+
+This might be the 2nd whoa moment as the interface here is also completely revamped from Views 1.x. The best way to summarize is to say all the pieces from the Views 1.x interface are still there...just in different places. Fields, arguments, sort critera and filters are all still there there, just in new AJAXY-flavours.
+
+Let's start by adding some fields:
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-addfields-large.png"><img src="path:images/views2-addfields.png" /></a>
+<em>Adding fields</em>
+</div>
+
+Clicking on the [+] icon next to the word Fields unfurls a section beneath the view information with all the available fields grouped by Comment, File, Node, Node revision, Taxonomy and User, and probably a few others. This is a general paradigm for the Views 2 interface -- clicking on a widget or link unfurls a section beneath the view information with the relevant interface. Usually, what is being edited will be hilited in yellow, as well.
+
+When adding items, you can use the Groups drop-down box to show only a subset of the fields available according to the above groups, or select All to see all fields available, which is what was selected when the section unfurled. For our example, we're selecting the 'User' group and adding the <strong>User: E-mail</strong>, <strong>User: Name</strong> and <strong>User: Picture</strong> fields.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-addfieldsajax-large.png"><img src="path:images/views2-addfieldsajax.png" /></a>
+<em>Adding fields</em>
+</div>
+
+Once we add our fields they show up in the Fields section of the interface. We will be walked through each field we added, so keep clicking <strong>update</strong>, even if you don't make changes to the field and you will see the next one.
+
+The fields we added can be rearranged by clicking the up/down icon, right next to the add icon we used earlier. We can also remove a field using the same interface.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-rearrangefields-large.png"><img src="path:images/views2-rearrangefields.png" /></a>
+<em>Rearranging fields</em>
+</div>
+
+From here, the fields can be dragged up and down by grabbing the little drag handle on the left and moving them where you like. Making a change to any part of the view by clicking update usually triggers a refresh of the view preview which is conveniently located right below the main interface.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-fieldspreview-large.png"><img src="path:images/views2-fieldspreview.png" /></a>
+<em>Views preview</em>
+</div>
+
+Now that we have some fields set up we can turn our attention to Basic Settings for the view.
+
+It's important to note that all the interface elements pertain to the current <i>Display</i> selected for the view. As mentioned before a view can have multiple displays. The first time you create a view you'll be manipulating the <i>Default</i> display. You can add displays using the Add Display button, whose Basic Settings are completely different from each other; this lets you have as many displays of a view as you would like all sharing items such as Sort Criteria, Filters and Arguments but different display settings like Title, Style, Fields, and Pager settings. Also, any display you add automatically inherits display settings from the default display initially, so you can keep a core of common settings in your default display and add additional settings for every other display.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-adddisplay-large.png"><img src="path:images/views2-adddisplay.png" /></a>
+<em>Adding a <strong>Page</strong> display </em>
+</div>
+Let's stick with the Default display and twiddle some settings. We can set the <i>Title</i> to "User View 1" and the <i>Style</i> to Table. As mentioned earlier, view styles in Views 2 correspond more to view types in Views 1 (remember, List, Table, Teasers, Full nodes).
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views1-changeviewtype-large.png"><img src="path:images/views1-changeviewtype.png" /></a>
+<em>Selecting a Views 1 View Type</em>
+</div>
+
+In Views 2, view <i>styles</i> control how a view display looks. These styles are significantly different from the Types in Views 1; in particular, types have been 'broken up'; there is now the <em>style</em> as well as the <em>row style</em> which focus on different parts of the output.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/style-breakdown-large.png"><img src="path:images/style-breakdown.png" /></a>
+<em>A breakdown of View output</em>
+</div>
+
+We change the style by clicking on the current style on the left hand side of the View information area.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-changedisplaystyle-large.png"><img src="path:images/views2-changedisplaystyle.png" /></a>
+<em>Selecting a Views 2 Display Style</em>
+</div>
+
+We're given the style options of <strong>Grid</strong>, <strong>List</strong>, <strong>Table</strong> and <strong>Unformatted</strong>. Additional display styles can be added by modules which have Views <i>style plugins</i>. Choosing a style reveals a "settings" button which you can click to configure the style you've chosen. In the shot below we've selected and are configuring the Table style, which we're using to produce a more compact output than we had earlier.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views2-tablestyle-large.png"><img src="path:images/views2-tablestyle.png" /></a>
+<em>Selecting and configuring the table style</em>
+</div>
+
+... TODO: Finish this document ...
diff --git a/sites/all/modules/views/help/other-help.html b/sites/all/modules/views/help/other-help.html
new file mode 100644
index 000000000..a2100f398
--- /dev/null
+++ b/sites/all/modules/views/help/other-help.html
@@ -0,0 +1,9 @@
+There are many tutorials, podcasts, and a few books on Views that you can turn to for further help.
+
+Books:
+<a href="http://www.drupal-building-blocks.com">Drupal Building Blocks</a> is the Views' author book; check <a href="http://www.drupal.org/books">the Drupal.org books page</a> for a fairly comprehensive list.
+
+Videos:
+See <a href="topic:views/demo-video">this page.</a>
+
+Google. Many Drupal shops put together helpful tutorials and publish them to Drupal Planet, not to mention the countless users of Views who do it for the community.
diff --git a/sites/all/modules/views/help/overrides.html b/sites/all/modules/views/help/overrides.html
new file mode 100644
index 000000000..08fb356bf
--- /dev/null
+++ b/sites/all/modules/views/help/overrides.html
@@ -0,0 +1,6 @@
+
+If an item is <strong>using defaults</strong> then it is using values from the <strong>default <a href="topic:views/display">display</a></strong>. <em>IMPORTANT NOTE:</em> If you modify this value, you are modifying the <strong>default <a href="topic:views/display">display</a></strong> and thus modifying for all displays that are using default values.
+
+If that is not what you intend, you must click the <strong>override</strong> button. Once overridden, that display now has its own version of the value; modifying it will not modify it for other displays. You can override in the settings of the non-default display when you are clicking on the header of the section or on the rearrange button.
+
+For <a href="topic:views/relationship">relationships</a>, <a href="topic:views/argument">arguments</a>, <a href="topic:views/field">fields</a>, <a href="topic:views/sort">sort criterias</a>, and <a href="topic:views/filter">filters</a>, each of these must be overridden as a group! In other words, you cannot override a single filter, but instead must override all filters. A message will appear on the item to let you know what its status is, but you can only change the status by clicking on the header or the rearrange button for that item.
diff --git a/sites/all/modules/views/help/path.html b/sites/all/modules/views/help/path.html
new file mode 100644
index 000000000..eb44de791
--- /dev/null
+++ b/sites/all/modules/views/help/path.html
@@ -0,0 +1,7 @@
+If a display has a path that means that it can be retrieved directly by calling a URL as a first class page on your Drupal site. Any items after the path will be passed into the view as arguments. For example, if the path is <strong>foo/bar</strong> and a user visits <strong>http://www.example.com/foo/bar/baz/beta</strong>, 'baz' and 'beta' will be given as arguments to the view. These can be handled by adding items to the <a href="topic:views/arguments">arguments</a> section.
+
+You may also use placeholders in your path to represent arguments that come in the middle. For example, the path <strong>node/%/someview</strong> would expect the first argument to be the second part of the path. For example, <strong>node/21/someview</strong> would have an argument of '21'.
+
+<em>Note:</em> Views 1 used <strong>$arg</strong> for this kind of thing. $arg is no longer allowed as part of the path. You must use % instead.
+
+If multiple displays <strong>within the same view</strong> have the same path, the user will get the first display they have access to. This means you can create successfuly less restricted displays in order to give administrators and privileged users different content at the same path.
diff --git a/sites/all/modules/views/help/performance-views-vs-displays.html b/sites/all/modules/views/help/performance-views-vs-displays.html
new file mode 100644
index 000000000..9f02a5a90
--- /dev/null
+++ b/sites/all/modules/views/help/performance-views-vs-displays.html
@@ -0,0 +1,5 @@
+<p>Multiple displays in one view is <strong>appropriate</strong> when they are <strong>fundamentally similar</strong> and are <strong>sharing quite a fair amount of data</strong>. For example, showing upcoming events in either blocks or page and sorting it according to either event name or date of the event will be appropriate to be implemented by four displays (two blocks and two pages) in one view.</p>
+
+<p>In the other hand, having multiple displays in one view that contains <strong>mostly overrides</strong> will be a burden to the system and will be hard to maintain. The effect of having only couple of such displays has negligible performance difference. It will be magnified when the number of displays in one view is large.</p>
+
+<p>Another consideration is the use of Views, especially when using multiple views in conjunction with other modules such as Panels. It is entirely possible to have many views on a page due to Panels' ability to contain a view in each pane. Some people have mistaken this as a problem on Panels' or Views' part, but realistically it is likely to be the sheer number of queries that are being run during page render. </p>
diff --git a/sites/all/modules/views/help/performance.html b/sites/all/modules/views/help/performance.html
new file mode 100644
index 000000000..e393452e1
--- /dev/null
+++ b/sites/all/modules/views/help/performance.html
@@ -0,0 +1 @@
+<p>Views module is optimized for certain practices. To ensure Views has its best performance, please follow these best practices for Views.</p>
diff --git a/sites/all/modules/views/help/relationship-representative.html b/sites/all/modules/views/help/relationship-representative.html
new file mode 100644
index 000000000..cf7feb5b7
--- /dev/null
+++ b/sites/all/modules/views/help/relationship-representative.html
@@ -0,0 +1,14 @@
+A representative relationship obtains just one object from the linked in objects.
+
+This is best explained with an example. Suppose you have a term view that shows you the terms in your vocabulary: Horse, Cat, Aardvark.
+In addition to the term names, you want to show the title of the most recent node in that term. This would give you a view that shows: Horse - latest horse node title, Cat - latest cat node title, Aardvark - latest aardvark node title.
+Each of these is the title of the <em>representative node</em> for that term, chosen by creation time.
+
+Any sort criterion can be used to choose the representative node. You might instead want to show the node with the most comments for each term, the first node in alphabetical order, or if you have a voting module installed, the most popular node.
+
+The options for a representative relationship let you choose a sort criterion and a sort order. This determines how the representative object is chosen: the first object returned by the sort is shown. For example, choose 'Node: title' and 'Ascending' to get the first node by title as the representative; or 'Node: comment count' and 'Descending' to get the node with the most comments.
+
+<h2>Performance</h2>
+These relationships require a correlated subquery. This can be slow to run on large amounts of data, as the subquery must be run for every row of the main query.
+
+For more on this topic, see the <a href="http://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html">MySQL tutorial on group-wise maximum queries</a>.
diff --git a/sites/all/modules/views/help/relationship.html b/sites/all/modules/views/help/relationship.html
new file mode 100644
index 000000000..2e3c09ca0
--- /dev/null
+++ b/sites/all/modules/views/help/relationship.html
@@ -0,0 +1,17 @@
+Relationships allow you to expand the query to include objects other than the base query. This is actually made more difficult to understand by the fact that Views actually includes a few relationships by default, and doesn't tell you they're there. For historical reasons, it would be inconvenient to remove these default relationships. When relationships are present, all fields (including relationships) will gain a new form item to let you select which relationship they will use. They will default to using no relationship at all.
+
+The main example of the relationship that is there by default is the node --&gt; user relationship; every node has an author, and if a node is in the query, the user who wrote that node is automatically made available. [Note: the author considers it an error that this relationship is automatic, but by the time it was realized this was in error, it was too late to change it.]
+
+A similar relationship that is <b>not</b> automatically made available is for node revisions. Each revision has its own author, which is the user who made the revision. By adding the "Node revision: User" relationship, all of the 'user' fields, sorts, filters and arguments available to a user will now be available for the revision author.
+
+When a relationship is added to the view, all applicable items will gain a "Relationship" select box, where you can choose which version of that particular item you wish to use. This can be illustrated with an example:
+
+A 'comment' view contains the relationships 'Comment: node' and 'Comment: user'. This means that all the fields for the node that a comment is attached to are available, and all the user fields for that node author also become available. The other relationship makes fields for the author of the comment available -- very often not the author of the node!
+
+When you add the "User: name" field, you will be presented with a select box. Either the node relationship or the user relationship must be selected, because there are two possible user names in the view to choose from.
+
+Another example of relationships involves the <strong>Files</strong> table. In Drupal, files are related to users, but files are not necessarily related to nodes. However, the upload.module allows some files to be attached to nodes. The only way for Views to deal with this discrepancy is with relationships. When creating a 'node' view, it's possible to add an uploaded files relationship to get file data for nodes that were attached with the upload module. It is also possible to go the other way; from a files view you may add a relationship via the Upload table to view information about the node.
+
+Drupal 7 made significant changes to Taxonomy. Because of this, many taxonomy functions Views can perform are now part of relationships. If you can't find the filter you need, add either the related taxonomy terms relationship, or a relationship on the specific taxonomy field.
+
+You can override the complete relationship section - see <a href="topic:views/overrides">overrides</a> for more information.
diff --git a/sites/all/modules/views/help/reports.html b/sites/all/modules/views/help/reports.html
new file mode 100644
index 000000000..2a6087355
--- /dev/null
+++ b/sites/all/modules/views/help/reports.html
@@ -0,0 +1,3 @@
+Visit <a href="/admin/reports/views-fields">admin/reports/views-fields</a> to see an overview of fields usage across all the views.
+
+It's useful to check for unused or misused fields.
diff --git a/sites/all/modules/views/help/select-multple-nids-contextual-filters.html b/sites/all/modules/views/help/select-multple-nids-contextual-filters.html
new file mode 100644
index 000000000..433d751ce
--- /dev/null
+++ b/sites/all/modules/views/help/select-multple-nids-contextual-filters.html
@@ -0,0 +1,28 @@
+We assume that you have a properly configured view at this point. You should have a page or block display with fields/node to be displayed in it.
+
+<ol>
+<li>In the views administration screen add a new 'contextual filter' using 'add'. You will see a configuration screen appear as a modal.</li>
+
+<li>In the 'Filter' jump menu that you now see select 'Content'. A list will appear below the jump menu.</li>
+
+<li>In the list look for 'Content: Nid', and check it. </li>
+
+<li>Press the 'Add and configure contextual filters' button. You will now get the configuration screen for 'Content: Nid'.</li>
+
+<li>Under 'When the filter value is NOT in the URL' select 'Provide default argument', and leave it set at 'Fixed value'.</li>
+
+<li>In the field 'Fixed value' add the Nids of the nodes separated by a '+' that you want to show by default.</li>
+
+<li>In 'When the filter value IS in the URL or a default is provided' choose 'Specify validation criteria'. Now select the content types you want, or leave blank for all nodes.</li>
+
+<li>Check the 'Validate user has access to the node'. This will check if a user has permission to view nodes, that are going to be displayed</li>
+
+<li>For the 'Filter value format' select 'Node IDs separated by , or +'.</li>
+
+<li>Select the MORE link.</li>
+
+<li>Check 'Allow Multiple Terms per Argument' and press 'Apply (all displays
+)' (or choose a particular display in the dropdown at the top and Apply (this display)). </li>
+</ol>
+
+Preview should now show nodes, that you selected as "default argument". You can now pass arguments to your view, to select custom node IDs. If you provide no argument, default will be used. Save the view when ready.
diff --git a/sites/all/modules/views/help/semantic-views.html b/sites/all/modules/views/help/semantic-views.html
new file mode 100644
index 000000000..ef645c6d1
--- /dev/null
+++ b/sites/all/modules/views/help/semantic-views.html
@@ -0,0 +1,18 @@
+The <a href="http://drupal.org/project/semanticviews">Semantic Views</a> module has been mostly incorporated into Views 3.x. Semantic Views is still around for people who need it, though. For some details on how the original module is different from the Views implementation, please see <a href="http://drupal.org/node/1013876">this issue</a>.
+
+Semantic views help you insert markup of your own from the Views UI, so that you can fairly easily override the default markup without having to restyle via templates.
+
+As a usage example,
+
+In a view with a field:
+<ol>
+<li>Configure the field. (Click on the field.)</li>
+
+<li>In the modal that opens, scroll down to <strong>Style Settings</strong>.</li>
+
+<li>Choose one or more of the three <i>Customize</i> options. This will reveal a dropdown menu where you can choose from one or more HTML tags to use on that field and allow you to add a CSS class specific to that field should you desire.</li>
+
+<li>Decide if you want to keep the Views default classes. Unchecking this box means your markup is *it*.</li>
+</ol>
+
+<a href="path:images/views3-semanticviews.png"><img src="path:images/views3-semanticviews.png" />
diff --git a/sites/all/modules/views/help/sort.html b/sites/all/modules/views/help/sort.html
new file mode 100644
index 000000000..322537ed3
--- /dev/null
+++ b/sites/all/modules/views/help/sort.html
@@ -0,0 +1,28 @@
+Sort criteria determine what order the records are retrieved from the database and displayed in; generally, all you need to do is pick a field and choose ascending (1, 2, 3, 4) or descending (4, 3, 2, 1) and it will be done. If you have multiple sort criteria, the second (and later) items only come into play if the first item is the same.
+
+In Views 3.x, sorts may be exposed just as filters are. Note that if only one item is exposed for sorting, the dropdown menu created by the exposed sort will only have one item in it. Some users may find this odd.
+
+Different data types sort just a little bit differently from others:
+<dl>
+<dt>Number fields</dt>
+<dd>Number fields sort like you would expect. 1 comes before 2 which comes before 10 which comes before 100 which comes before 200, etc.</dd>
+<dt>Text fields</dt>
+<dd>Text fields always sort alphabetically, even if the text contains numbers. This can have some odd effects if you have numbers stored in text, because the values 1, 3, 7, 10, 12, 20, 100, 120 will sort like this:
+<ul>
+<li> 1 </li>
+<li> 10 </li>
+<li> 100 </li>
+<li> 12 </li>
+<li> 120 </li>
+<li> 200 </li>
+<li> 3 </li>
+<li> 7 </li>
+</ul>
+
+This is because these fields sort purely by characters, and not numeric value. i.e, comparing 200 and 3, the '2' comes before the '3', therefore, '200' is "smaller" than '3'.
+</dd>
+<dt>Date fields</dt>
+<dd>Date fields often can have a 'granularity', which is a way of making similar dates actually be the same date. Take two dates that are close to each other: <strong>May 1, 2007 5:30 am</strong> and <strong>May 1, 2007 9:45am</strong>. Without granularity, the two dates are compared and the first date comes before the second date. However, if the granularity is set to 'day' it only looks at the parts of the date up to the day: <strong>May 1, 2007</strong> and <strong>May 1, 2007</strong>. At that point, they are the same, and the sort would move on to the next sort criterion.</dd>
+</dl>
+
+You can override the complete sort criteria section - see <a href="topic:views/overrides">here</a> for more information.
diff --git a/sites/all/modules/views/help/style-comment-rss.html b/sites/all/modules/views/help/style-comment-rss.html
new file mode 100644
index 000000000..5ce1dcd0d
--- /dev/null
+++ b/sites/all/modules/views/help/style-comment-rss.html
@@ -0,0 +1 @@
+This row style is only available to RSS styles. It produces XML necessary for an RSS feed for the comment.
diff --git a/sites/all/modules/views/help/style-fields.html b/sites/all/modules/views/help/style-fields.html
new file mode 100644
index 000000000..4d8d077b0
--- /dev/null
+++ b/sites/all/modules/views/help/style-fields.html
@@ -0,0 +1,16 @@
+The <strong>fields</strong> row style displays each field defined in the view, one after another. Each field defines its own output.
+
+By default, each field is put in a &lt;div&gt; unless it is selected to be <em>inline</em>. If it is inline, it is put in a &lt;span&gt;. Two items in &lt;div&gt;s will be displayed one after another, with the second one below the first. Two items in &lt;span&gt;s will be displayed on the same line. One item in a &lt;span&gt; next to &lt;div&gt;s is the same as two items in &lt;div&gt;s. This means that for the <em>inline</em> setting to do anything, at least two consecutive items must be set inline.
+
+You may define a <i>separator</i> which will be placed between each item. This separator may be html. You can use &amp;nbsp; to print blank space.
+
+If the view's row style is set to "fields", fields must be added to the View. If there are no fields, you may receive validation errors such as:
+
+<i>* Display "Defaults" uses fields but there are none defined for it or all are excluded.
+* Display "Page" uses fields but there are none defined for it or all are excluded.</i>
+
+This is because the row style "fields" expects at least one field for display.
+
+There is also an option to hide empty fields, so empty fields, along with their labels or markup will not be displayed.
+
+There is also an option to hide empty fields, so empty fields, along with their labels or markup will not be displayed.
diff --git a/sites/all/modules/views/help/style-grid.html b/sites/all/modules/views/help/style-grid.html
new file mode 100644
index 000000000..463e2bdc0
--- /dev/null
+++ b/sites/all/modules/views/help/style-grid.html
@@ -0,0 +1,22 @@
+The <strong>grid</strong> style will display each row of your view within a grid. You may customize the <strong>number of columns</strong>, though it defaults to 4. A grid looks like this:
+
+<table width="100%">
+<tr><td>row 1</td><td>row 2</td><td>row 3</td><td>row 4</td></tr>
+<tr><td>row 5</td><td>row 6</td><td>row 7</td><td>row 8</td></tr>
+<tr><td>row 9</td><td>row 10</td><td>row 11</td><td>row 12</td></tr>
+<tr><td>row 13</td><td>row 14</td><td>row 15</td><td>row 16</td></tr>
+</table>
+
+The above uses the <strong>horizontal</strong> alignment, where rows are added into the grid from left to right.
+
+With a <strong>vertical</strong> alignment, rows will be placed from top to bottom, like this (your <strong>row</strong> results will appear as columns):
+<table width="100%">
+<tr><td>row 1</td><td>row 5</td><td>row 9</td><td>row 13</td></tr>
+<tr><td>row 2</td><td>row 6</td><td>row 10</td><td>row 14</td></tr>
+<tr><td>row 3</td><td>row 7</td><td>row 11</td><td>row 15</td></tr>
+<tr><td>row 4</td><td>row 8</td><td>row 12</td><td>row 16</td></tr>
+</table>
+
+You can also choose to <strong>group a field</strong> from the Fields Section. This grouping field will be displayed as a header, and all rows will be displayed beneath it.
+
+This style uses a <a href="topic:views/style-row">row style</a> to determine what each row will look like.
diff --git a/sites/all/modules/views/help/style-grouping.html b/sites/all/modules/views/help/style-grouping.html
new file mode 100644
index 000000000..8b0c9a7a4
--- /dev/null
+++ b/sites/all/modules/views/help/style-grouping.html
@@ -0,0 +1,7 @@
+Many styles can be <strong>grouped</strong>. For styles that can, there will be a 'grouping field' option; pick one of the fields to group by. This grouping field will be displayed as a header, and all rows will be displayed beneath it.
+
+If the style is not <strong>grouped</strong> then the corresponding style output template will by invoked once when displaying the view. If the style is grouped, then the style output template will be invoked once per group. The text if the grouping filed is passed to the template in the <strong>$title</strong> variable.
+
+Views is very naive about <strong>grouping</strong>, and it can only group on one field. It doesn't understand anything about which object (node, comment, etc.) the field belongs to, and doesn't make any smart grouping choices based on the parent object of the grouping field. The grouping field does not modify the views SQL query - grouping is handed after records are retrieved from the database.
+
+The devel_themer module is known to break grouping. If grouping is not working, please check and make sure the devel modules are disabled.
diff --git a/sites/all/modules/views/help/style-jump.html b/sites/all/modules/views/help/style-jump.html
new file mode 100644
index 000000000..dc8532f8f
--- /dev/null
+++ b/sites/all/modules/views/help/style-jump.html
@@ -0,0 +1,48 @@
+With the <strong>jump menu</strong> style can you create selectbox menus for the content of your site.
+
+<a href="path:images/views3-jump-style-menu.png"><img src="path:images/views3-jump-style-menu.png" /></a>
+
+The <strong>jump menu</strong> style will display each row of your view within a jump menu. This style requires that your view's <a href="topic:views/style-row">row style</a> is set to <a href="topic:views/style-fields">fields</a>.
+
+To properly configure a jump menu, you must select one field that will represent the path to utilize. In most cases, you will need to rewrite the output of this field. You should set that field to exclude from display. All other displayed fields will be part of the menu. Please note that all HTML will be stripped from this output as select boxes cannot show HTML.
+
+Some examples of how this might be useful:
+
+
+<h3>Jump to a node</h3>
+<ol>
+<li>Create a new <strong>Node</strong> view</li>
+<li>Select the <strong>Node: Path</strong> and <strong>Node: Title</strong> fields</li>
+<li>Configure the <strong>Node: Path</strong> field to "exclude from display" and check "Use absolute link"</li>
+<li>Configure the <strong>Node: Title</strong> field by removing the "Label" and unchecking "Link this field to its node"</li>
+<li>Set the view style to <strong>jump menu</strong></li>
+<li>In the style settings, set the "Path field" to <strong>Node: Path</strong></li>
+</ol>
+
+Your view will now display with a select list and a <strong>Go</strong> button. If you select an item in the list and hit the <strong>Go</strong> button you will see the selected node's page in the browser.
+
+<h3>Jump to a node's edit page</h3>
+<ol>
+<li>Create a new <strong>Node</strong> view</li>
+<li>Select the <strong>Node: Nid</strong> and <strong>Node: Title</strong> fields</li>
+<li>Configure the <strong>Node: Nid</strong> field to "exclude from display" and check "Rewrite the output of this field"</li>
+<li>In the text field that appears for rewriting the output of this field, enter <strong>node/[nid]/edit</strong></li>
+<li>Configure the <strong>Node: Title</strong> field by removing the "Label" and unchecking "Link this field to its node"</li>
+<li>Set the view style to <strong>jump menu</strong></li>
+<li>In the style settings, set the "Path field" to <strong>Node: Nid</strong></li>
+</ol>
+
+Your view will now display with a select list and a <strong>Go</strong> button. If you select an item in the list and hit the <strong>Go</strong> button you will see the selected node's edit page in the browser. Please note that users without rights to the node's edit page will see an access denied message.
+
+<h3>Jump to a user profile</h3>
+<ol>
+<li>Create a new <strong>User</strong> view</li>
+<li>Select the <strong>User: Uid</strong> and <strong>User: Name</strong> fields</li>
+<li>Configure the <strong>User: Uid</strong> field to "exclude from display" and check "Rewrite the output of this field"</li>
+<li>In the text field that appears for rewriting the output of this field, enter <strong>user/[uid]</strong></li>
+<li>Configure the <strong>User: Name</strong> field by removing the "Label" and unchecking "Link this field to its user"</li>
+<li>Set the view style to <strong>jump menu</strong></li>
+<li>In the style settings, set the "Path field" to <strong>User: Uid</strong></li>
+</ol>
+
+Your view will now display with a select list and a <strong>Go</strong> button. If you select an item in the list and hit the <strong>Go</strong> button you will see the selected user's profile page in the browser.
diff --git a/sites/all/modules/views/help/style-list.html b/sites/all/modules/views/help/style-list.html
new file mode 100644
index 000000000..86355a51a
--- /dev/null
+++ b/sites/all/modules/views/help/style-list.html
@@ -0,0 +1,20 @@
+The <strong>List</strong> view style will display every row of the view as part of an HTML list construct. For example:
+<ul>
+<li> Row 1 </li>
+<li> Row 2 </li>
+<li> Row 3 </li>
+<li> Row 4 </li>
+</ul>
+
+You may select whether or not the list is <em>ordered</em> which just means whether or not it uses numbers instead of the bullet:
+
+<ol>
+<li> Row 1 </li>
+<li> Row 2 </li>
+<li> Row 3 </li>
+<li> Row 4 </li>
+</ol>
+
+The list style also uses a <em>row style</em> which means that it doesn't care what the actual output for each row of the view is.
+
+If you need information about using CSS to style list views, you may find this <a href="http://www.alistapart.com/stories/taminglists/">A list apart guide to styling lists</a> useful.
diff --git a/sites/all/modules/views/help/style-node-rss.html b/sites/all/modules/views/help/style-node-rss.html
new file mode 100644
index 000000000..7a97bdd56
--- /dev/null
+++ b/sites/all/modules/views/help/style-node-rss.html
@@ -0,0 +1 @@
+This row style is only available to RSS styles. It produces XML necessary for an RSS feed for the node record.
diff --git a/sites/all/modules/views/help/style-node.html b/sites/all/modules/views/help/style-node.html
new file mode 100644
index 000000000..928980f20
--- /dev/null
+++ b/sites/all/modules/views/help/style-node.html
@@ -0,0 +1,11 @@
+The <strong>node</strong> row style will display each item of the view through Drupal's standard <em>node_view()</em> function. Views has very little control over this output, except for the options you see. You can choose from different <strong>Build modes</strong>. By default there are "Teaser" and "Full node". You can also decide if you want to "Display links" and/or "Display node comments".
+
+Because the output is run through the standard node template mechanism (typically <strong>node.tpl.php</strong> or a variant thereof), any decisions about what is output may be done there.
+
+Views does add an extra 'suggestion' to the list of possible node templates: <strong>node--view--VIEWNAME.tpl.php</strong> -- you may use this to theme a node specifically for the view. This can be handy for creating very small teasers and the like.
+
+You may opt to display the full node body or the node teaser, and you may add the node links (such as the 'comment' links that appear after a node) or not.
+
+Because of this behavior, <strong>the node row style does not utilize fields</strong> and the Fields section will not be displayed.
+
+<strong>Please note that this row style performs a node_load() for every row, and as such can produce a lot of extra queries.</strong> Sometimes this is necessary, but it can have a negative impact on your site's performance!
diff --git a/sites/all/modules/views/help/style-row.html b/sites/all/modules/views/help/style-row.html
new file mode 100644
index 000000000..a8406d358
--- /dev/null
+++ b/sites/all/modules/views/help/style-row.html
@@ -0,0 +1,11 @@
+A row style is an individual style to display only an individual record within a view. You can choose between <a href="topic:views/style-node">node</a> or <a href="topic:views/style-fields">fields</a>. For example, a node type view would display one node per row; a user type view would display one user per row.
+
+When you choose <strong>fields</strong>, you must have entries in the <a href="topic:views/field">Fields</a> section to produce an output. To set some options for fields, read <a href="topic:views/style-fields">here</a>.
+
+Some row styles use <em>fields</em> which means you may select from the available fields to display. Others row styles do not - they are able to use the base type and create a display. Usually, row styles that do not use fields produce less efficient (slower) views, so bear this in mind when contemplating the performance of your site.
+
+When you choose <strong>node</strong> the complete node will display through Drupal's standard <em>node_view()</em> function. You can choose some <a href="topic:views/style-node">options</a>.
+
+When styling views, it's important to realize that the unformatted styles will take the majority of styling from more specific templates. In an unformatted style, the row style will provide theming. For instance, in a view that uses fields as its primary data, the views-view-fields.tpl.php will provide the theming.
+
+Mustardseed created a videocast on row theming that can be viewed <a href="http://mustardseedmedia.com/podcast/episode30">here</a>.
diff --git a/sites/all/modules/views/help/style-rss.html b/sites/all/modules/views/help/style-rss.html
new file mode 100644
index 000000000..e67bd3092
--- /dev/null
+++ b/sites/all/modules/views/help/style-rss.html
@@ -0,0 +1,5 @@
+The <strong>RSS</strong> output style is only available for <em>Feed</em> display types. It will display the view as an RSS feed, which is a specialized XML output. This output is not user visible, but can be parsed by feed readers for aggregation.
+
+You may supply a description for the RSS feed; most feed readers will display this description along with the contents of the feed. You may also select to use the site's mission statement for the description.
+
+Please note that when using RSS views only comes with the one RSS style. There's no style override for RSS. Modules can add more.
diff --git a/sites/all/modules/views/help/style-settings.html b/sites/all/modules/views/help/style-settings.html
new file mode 100644
index 000000000..63133d811
--- /dev/null
+++ b/sites/all/modules/views/help/style-settings.html
@@ -0,0 +1,3 @@
+Views includes many options for theming or styling.
+
+TODO: add more here.
diff --git a/sites/all/modules/views/help/style-summary-unformatted.html b/sites/all/modules/views/help/style-summary-unformatted.html
new file mode 100644
index 000000000..bd14f249d
--- /dev/null
+++ b/sites/all/modules/views/help/style-summary-unformatted.html
@@ -0,0 +1,3 @@
+The <strong>unformatted summary</strong> style is only available for <em>summary</em> styles, which are when an argument has been set to provide a summary if it was not provided with a value. This summary provides the possible candidates for the argument one after another with no special formatting. If <em>inline</em> is selected, the summary items will be enclosed within &lt;span&gt; tags. Otherwise the items will be in &lt;div&gt; tags.
+
+You can also elect to display the number of matching records for the argument, plus change the number of items per page for the summary. This is often useful because summary views are often quite small, but other views quite space intensive. It is very common to have far more records available in the summary view than in the more normal view.
diff --git a/sites/all/modules/views/help/style-summary.html b/sites/all/modules/views/help/style-summary.html
new file mode 100644
index 000000000..0c3845fae
--- /dev/null
+++ b/sites/all/modules/views/help/style-summary.html
@@ -0,0 +1,3 @@
+The <strong>list summary</strong> style is only available for <em>summary</em> styles, which are when an argument has been set to provide a summary if it was not provided with a value. This summary provides a list of possible candidates for the argument in a standard HTML list. Like the normal list style, you may set this list to be ordered or not.
+
+You can also elect to display the number of matching records for the argument, plus change the number of items per page for the summary. This is often useful because summary views are often quite small, but other views quite space intensive. It is very common to have far more records available in the summary view than in the more normal view.
diff --git a/sites/all/modules/views/help/style-table.html b/sites/all/modules/views/help/style-table.html
new file mode 100644
index 000000000..9fc8175e6
--- /dev/null
+++ b/sites/all/modules/views/help/style-table.html
@@ -0,0 +1,13 @@
+The <strong>table</strong> style will display the View results as a table; each row of the table will correspond to a row from the view result.
+
+When setting the table options, each field in the view will be presented with some information next to each field:
+<dl>
+<dt> <strong>Column</strong> </dt>
+<dd> By default, each field is its own column. However, you can place multiple fields in the same column. To do this, pick which field you want to represent the column, then pick another field and set the 'column' value to that field. You can place as many fields as you like in a single column, but only the main field in a column can be click-sorted.</dd>
+<dt> <strong>Separator</strong> </dt>
+<dd> If you have multiple fields in the same column, the separator will be placed between each one. At the very least, &amp;nbsp; should be used, as without the separator the fields will be placed very close to each other. Common separators are a bullet, the | symbol, and a comma. If there are no other fields in the column, the separator will have no effect.</dd>
+<dt> <strong>Sortable</strong> </dt>
+<dd> If checked, the header for the column will be clickable, and the user may re-sort the table by clicking on this to sort by that field. At this time Views does not support click-sorting to sort by multiple columns at the same time.</dd>
+<dt> <strong>Default sort</strong> </dt>
+<dd> You may select a column which will be sorted by default when the table is first viewed. This column will be highlighted to the user. You may also select whether the default sort is ascending or descending.</dd>
+</dl>
diff --git a/sites/all/modules/views/help/style-unformatted.html b/sites/all/modules/views/help/style-unformatted.html
new file mode 100644
index 000000000..99fa3de7f
--- /dev/null
+++ b/sites/all/modules/views/help/style-unformatted.html
@@ -0,0 +1 @@
+The unformatted output style simply places each row of the view, one after another, with no additional formatting.
diff --git a/sites/all/modules/views/help/style.html b/sites/all/modules/views/help/style.html
new file mode 100644
index 000000000..d32a2edad
--- /dev/null
+++ b/sites/all/modules/views/help/style.html
@@ -0,0 +1,15 @@
+The Views' <strong>style</strong> system is how you customize the output produced by your view. A view style is basically a smart theme template that processes the view data and then outputs it. All styles in Views can be <a href="topic:views/using-theme">overridden</a> by placing copies of the templates in your theme directory and then modifying them. See the <a href="topic:views/analyze-theme">theme: information</a> link available on all views to get hints for which templates a given view is using.
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/style-breakdown-large.png"><img src="path:images/style-breakdown.png" /></a>
+<em>A breakdown of View output</em>
+</div>
+By default, the style is <em>unformatted</em>, which means that there is very little style actually used; the records are simply displayed one after another, enclosed in a &lt;div&gt; tag so that you can use <a href="topic:views/theme-css">CSS to manipulate the view</a>.
+
+Some styles use a separate <a href="topic:views/style-row">row style</a> to determine how each row of the View looks. This is useful for mixing and matching styles to more readily produce exactly the kind of output you need.
+
+Many styles can be <strong>grouped</strong>. For styles that can, there will be a 'grouping field' option; pick one of the fields to group by. Please see <a href="topic:views/style-grouping">Grouping in styles</a> for more information.
+
+Each style is its own entity.
+
+If you want your fields to be templatable, you need to use values that are acceptable in function names. That's actually more strict, since that basically limits you to alphanumeric and _. Even though Views may be able to query datastores that use special characters, naming is still important for use in templates.
diff --git a/sites/all/modules/views/help/taxonomy-page-override.html b/sites/all/modules/views/help/taxonomy-page-override.html
new file mode 100644
index 000000000..f67623477
--- /dev/null
+++ b/sites/all/modules/views/help/taxonomy-page-override.html
@@ -0,0 +1,41 @@
+NOTE: This page has not been updated for 7.x-3.x
+Views 2 provides a way to override the taxonomy pages of any term. The view is included by default but is disabled. This page covers three minor aspects that some users could use to control the aspect of their taxonomy pages.
+
+<h3>Background</h3>
+The Taxonomy module provides a way to organize content through the site. It also presents a page with nodes that belong to some term. The style can't be changed easily and taxonomy only displays node teasers.
+
+<h3>Force All</h3>
+When using vocabularies with multiple term levels, a top level won't include the nodes that belongs to the levels inside. Let's clarify this with an example, let's assume the site have this vocabulary:
+
+<code>
+Vocab
+ - Term 1
+ -- Term 2
+ -- Term 3
+ - Term 4
+ -- Term 5
+ -- Term 6
+</code>
+
+If you go to taxonomy/term/1, then you see all nodes in Term 1 listed, but not nodes in Term 2 and Term 3. If you want to see all nodes in Term 1, Term 2 and Term 3 in a single page, you have to go to
+
+<code>
+taxonomy/term/1/all
+</code>
+
+Views 2 allows you to show all terms inside another term.
+
+<h3>Step 1. Enabling taxonomy_term</h3>
+The first step we need to do is to enable the "taxonomy term" view that views provides for us. After we've done that we need to edit it.
+
+<h3>Step 2. Changing minor aspects</h3>
+On the Display: Page, we can change the Style to any of the styles that views provides for us, it could be a table or a list. If we do this it's necessary to add some fields to the view.
+
+The most important thing we're going to change it's the argument <strong>Taxonomy: Term ID: (with depth)</strong>. At the bottom of the screen, you'll have 2 options, the first one to change it's the <strong>depth</strong>, if we want to do what the <a href="http://drupal.org/project/taxonomy_forceall" target="_blank">Taxonomy Force All</a> modules does, you'll have to set it up for at least 1.
+
+The next thing you have to do it's check the <strong>Set the breadcrumb for the term parents</strong> option. This will allow your view to show the breadcrumb with all the term levels in your taxonomy.
+
+Save the changes and try. Next you could do whatever you want, like adding fields, theming, exposing filters, sorting or adding feeds.
+
+<h3>Theme every vocabulary/term independently</h3>
+Using <a href="http://drupal.org/project/tvi">TVI: Taxonomy Views Integrator</a> it's possible to theme every vocabulary or term with it's own view. All you have to do is clone the view in Step 1 and set it as the TVI active view in the vocab or term edit pages.
diff --git a/sites/all/modules/views/help/theme-css.html b/sites/all/modules/views/help/theme-css.html
new file mode 100644
index 000000000..a23d9e556
--- /dev/null
+++ b/sites/all/modules/views/help/theme-css.html
@@ -0,0 +1,76 @@
+Views uses a wide array of CSS classes on all of its content to ensure that you can easily and accurately select exactly the content you need in order to manipulate it with CSS.
+
+It is possible to enter a custom css class under <a href="topic:views/style">Style</a> settings. The <strong>CSS class</strong> names will be added to the view. This enables you to use specific CSS code for each view. You may define multiples classes separated by spaces.
+
+Typically, every view is wrapped in a div with the name of the view as part of its class (for all these examples, we will assume the name of the view is <strong>myview</strong>), as well as the generic class 'view':
+
+<pre>
+&lt;div class="view view-myview"&gt;
+...
+&lt;/div&gt;
+</pre>
+
+In your CSS, you can modify all views:
+
+<pre>
+div.view {
+ border: 1px solid black;
+}
+</pre>
+
+Or just your view:
+
+<pre>
+div.view-myview {
+ background: yellow;
+}
+</pre>
+
+By default, the general view template also provides the following classes to easily style other areas of the view:
+<ul>
+<li> .view-header </li>
+<li> .view-filters </li>
+<li> .view-content </li>
+<li> .view-empty (if an "empty" text is used when the view has no results) </li>
+<li> .view-footer </li>
+<li> .feed-icon </li>
+<li> .attachment-before (if using an "attachment" display)</li>
+<li> .attachment-after (if using an "attachment" display)</li>
+</ul>
+
+So for example:
+<pre>
+div.view-myview div.view-header {
+ /* make the header stand out */
+ font-size: 120%;
+ font-weight: bold;
+}
+
+div.view-myview div.view-footer {
+ /* Make the footer less important */
+ font-size: 80%;
+ font-style: italic;
+ color: #CCC;
+}
+</pre>
+
+In the above example, we whimsically made the header bold and in a bigger font, and the footer smaller, italicized, and greyish.
+
+<h3>Views with fields</h3>
+If your view has fields, each field is uniquely tagged with its ID. A field's ID may be gleaned from the Theme: Information page. Note that due to CSS rules, any _ in the id will be converted to - automatically, so if you have a field whose id is 'edit_node' (this is the field used to provide an "edit" link to a node), it will be 'edit-node'. Additionally, to make sure that the view IDs don't conflict with other css classes in the system, they will be pretended with 'views-field-'; thus, the final CSS class for the field with the id 'edit_node' will be <strong>views-field-edit-node</strong>.
+
+Exactly how this appears is going to depend upon the style you're using. For example, the 'unformatted' style uses <strong>div.views-field-edit-node</strong> and <strong>div.views-label-edit-node</strong> to access that particular field, but a table would use <strong>td.views-field-edit-node</strong> and <strong>th.views-field-edit-node</strong> to access the table header; or just <strong>.views-field-edit-node</strong> to affect both.
+
+<pre>
+.view-myview th {
+ color: red; /* make all headers red */
+}
+
+.view-myview .views-field-title {
+ font-weight: bold; /* Make the 'title' field bold */
+}
+
+.view-myview td.views-field-body {
+ font-size: 60%; /* Make the text in the body field small */
+}
+</pre>
diff --git a/sites/all/modules/views/help/top-pager.html b/sites/all/modules/views/help/top-pager.html
new file mode 100644
index 000000000..68247af12
--- /dev/null
+++ b/sites/all/modules/views/help/top-pager.html
@@ -0,0 +1,18 @@
+Copy the views-view.tpl.php from the /views/theme directory to themes/yourtheme/theme directory. Find the following code...
+
+
+<pre>
+ &lt;?php if ($attachment_before): ?&gt;
+ &lt;div class="attachment-before"&gt;
+ &lt;?php print $attachment_before; ?&gt;
+ &lt;/div&gt;
+ &lt;?php endif; ?&gt;
+</pre>
+
+Insert the following code after it (this is copied directly from the same code at the bottom of the tpl):
+
+<pre>
+ &lt;?php if ($pager): ?&gt;
+ &lt;?php print $pager; ?&gt;
+ &lt;?php endif; ?&gt;
+ </pre>
diff --git a/sites/all/modules/views/help/ui-crashes.html b/sites/all/modules/views/help/ui-crashes.html
new file mode 100644
index 000000000..4e8a3893a
--- /dev/null
+++ b/sites/all/modules/views/help/ui-crashes.html
@@ -0,0 +1,25 @@
+<h2>Troubleshooting UI crashes</h2>
+
+There are a number of reasons why the Views UI may crash; the most common state or result of a crash is either a white screen (everyone's favorite WSOD), or a screen of what looks like garbage text. This is generally a javascript crash of some fashion.
+
+To get the most timely and accurate help in the issue queue, please try to gather this information:
+
+Check your javascript console. In Firefox, you can hit ctrl-shift-j or use firebug. Copy this information into the issue, or attach it as a text file. Really - this is the single biggest thing you can do to help figure out where the crash is coming from.
+
+
+<h3>JSON prepends data with jQuery, causing editing and preview problems.</h3>
+This section originally stems from <a href="http://drupal.org/node/346662">this issue.</a>
+
+Some modules may add PHP improperly, disrupting normal jQuery operation. Errors may look like
+
+<code>
+<? session_module_name("files"); ?>{ "default": "default" }
+</code>
+
+This can also be a server configuration issue. In one case, this was solved by commenting out
+
+auto_prepend_file = c:\wamp\www\php.ini.prepend
+
+in the php.ini.
+
+Another page to look at is the drupal.org page on <a href="http://drupal.org/node/158043">White screens</a>
diff --git a/sites/all/modules/views/help/updating-view3.html b/sites/all/modules/views/help/updating-view3.html
new file mode 100644
index 000000000..aa2d9770d
--- /dev/null
+++ b/sites/all/modules/views/help/updating-view3.html
@@ -0,0 +1 @@
+Most views should automatically convert without issue. It's always a good idea and highly recommended that all views be exported for backup purposes before upgrading to Views 3.x.
diff --git a/sites/all/modules/views/help/updating.html b/sites/all/modules/views/help/updating.html
new file mode 100644
index 000000000..34a1d253f
--- /dev/null
+++ b/sites/all/modules/views/help/updating.html
@@ -0,0 +1,7 @@
+Views 1 views stored in the database may not convert properly to Views 2. In order to aid with the conversion, a tool is available. In the main Views page, choose Tools -> Convert. Any views you choose will attempt to convert.
+
+Note that converted Views may not match perfectly! Adjustments may be necessary to retain the integrity of your previous views.
+
+Views 1 views may also be exported before upgrade, and then imported through the standard Import tab. Those views will attempt to convert during import. Exporting your views before upgrade has the added benefit of ensuring a backup.
+
+There are no known issues converting views from Views 2 to Views 3 at this time.
diff --git a/sites/all/modules/views/help/upgrading.html b/sites/all/modules/views/help/upgrading.html
new file mode 100644
index 000000000..b31b8f931
--- /dev/null
+++ b/sites/all/modules/views/help/upgrading.html
@@ -0,0 +1,8 @@
+
+<h3>Updating templates</h3>
+If you have theme files for node-view-$viewname of comment-view-$viewname you have
+to convert them to node--view--$viewname or comment--view--$viewname.
+
+<h4>Updating table templates</h4>
+The class variable was renamed to $classes. Additional if you want to add
+classes, you should add it to $classes_array and drupal will automatically convert it to the $classes string.
diff --git a/sites/all/modules/views/help/using-theme.html b/sites/all/modules/views/help/using-theme.html
new file mode 100644
index 000000000..c4164eb47
--- /dev/null
+++ b/sites/all/modules/views/help/using-theme.html
@@ -0,0 +1,50 @@
+Views theme templates are straightforward to use with the Drupal theming system. If you are unfamiliar with the theming system at all, you should probably at least read <a href="http://drupal.org/node/173880">drupal.org theming documentation</a>. That said, these are the important things you need to know:
+
+<ol>
+<li> Copy a base Views template to one of the names provided from the Theme: Information section of the View. Copy this template right into your theme directory. </li>
+<li> Clear the theme registry. See the <a href="http://drupal.org/node/173880#theme-registry">instructions</a> for how to do this. </li>
+<li> Your new template should now operate; assuming you picked a nicely named template that includes the view name, that template should now be active for your view. A good example is <strong>views-view-list--foobar.tpl.php</strong> which would work for a view named 'foobar'.</li>
+<li> You can now modify this template all you like.</li>
+</ol>
+
+For any template that uses fields, the fields will be in array. In order to use this effectively, you will need to understand enough PHP to fetch data from an array. This is a place where the <a href="http://drupal.org/project/devel">devel module</a> can really help you, because you can use its dsm() function right in your template to see what variables it uses. There is an alternative to dsm() that works without devel module, but it's a bit longer to use.
+
+For example, I placed the following code inside a loop in views-view-table.php.php:
+<code> &lt;?php drupal_set_message('&lt;pre&gt;' . var_export($row, true) . '&lt;/pre&gt;'); ?&gt;
+</code>
+
+And it produced this output:
+<code> array (
+ 'nid' =&gt; '97',
+ 'title' =&gt; 'Scisco Ideo Vicis Feugiat Qui',
+ 'name' =&gt; 'luwrepuslan',
+ )
+</code>
+
+My view had three fields:
+<code>Node: Nid
+Node: Title
+User: Name
+</code>
+
+The contents of the $row variable included these fields, in precisely the order that I had arranged them to using the Views rearrange link. Also worth noting, though, is that each field also has an identifier so it can easily be pulled out of the row should I want to display it differently. Using
+<code>&lt;?php print $row['title']; ?&gt;
+</code>
+
+Would print just the title for that row. Please remember that I'm doing this inside the loop, so this will get repeated for every row of the view.
+
+The IDs used to fetch items from the array, id <strong>$row['title']</strong> can be quickly and easily looked up on the Theme: Information page. Once a field has been added to the view, its ID will not change, but note that if there are two "title" fields in a view, one will be 'title' and the other will be 'title_1', in order to prevent namespace collisions.
+
+The important thing here is that Views does provide IDs. Views doesn't tell you what these IDs are, but it's easy to get them by dumping the row data and doing a simple visual inspection. Views does guarantee that these IDs will not change, unless you actually add a new field and remove the existing one (in which case 'title', above, would become 'title_1').
+
+<h2>The basic fields template</h2>
+
+The most common template people will need to rewrite is the "simple" views-view-fields.tpl.php, which is the template used by the <i>Fields</i> row style and all it does is display a simple list of fields. However, it is not that simple to the user. Because the template can't inherently know what the fields are, it has to go through an array in a loop.
+
+This loop isn't very handy when you really want to have fine control over the template by placing your fields precisely where and how you want. Relax, though; if you know what your fields are, you can rewrite this. If you end up writing your own HTML, the only part that is really important is the content for each field. We know from above that you can get the ID for each field on the Theme: Information page from the view. In the header for the template, we can see that the fields are all in the $fields array, and each field is an object. That leads us to this:
+
+<code>&lt;?php print $fields['some_id']-&gt;content; ?&gt;</code>
+
+Assuming you replace some_id with an id found on the theme: information page, this code will print the content for that field. You can also get the label and some other data about the field, as well as the raw information. Complete details for what is available are documented directly in views-view-fields.tpl.php.
+
+Keep in mind that if you rewrite your templates using this, you'll need to maintain this template whenever changes are made to the fields of the view; while this isn't generally recommend, sometimes it's necessary to get the kind of control you might ultimately need.
diff --git a/sites/all/modules/views/help/view-add.html b/sites/all/modules/views/help/view-add.html
new file mode 100644
index 000000000..619d1b667
--- /dev/null
+++ b/sites/all/modules/views/help/view-add.html
@@ -0,0 +1,25 @@
+<p>When you want to create a new view navigate to <strong> Structure > Views </strong>. There you can see all the views specified. Fresh install has a few examples ready and you can choose "edit" to investigate how the is made. To create a new view press "Add new view".</p>
+
+<div class="help-box" style="text-align:center">
+<a href="path:images/views3-views-all.png"><img src="path:images/views3-views-all.png" /></a>
+<em>All the created Views</em>
+</div>
+
+<p>You can enter the following information about it.</p>
+<p> First, you must enter a <strong>view name</strong>. This is the unique name of the view. It must contain only alphanumeric characters and underscores; it is used to identify the view internally and to generate unique theming template names for this view. If you are overriding a module provided view, the name must not be changed or a new view will be created instead of overriding.</p>
+<p> You can enter a <strong>description of the view</strong>. This description will appear on the Views administrative UI to tell you what the view is about. You can enter a <strong>view tag</strong>. It will be used help sort views on the administrative page.</p>
+You must choose a view type.
+<dl>
+<dt>Node</dt>
+<dd>Nodes are a Drupal site's primary content.</dd>
+<dt>Comment</dt>
+<dd>When you want to handle comments and information related to the and information related to them.</dd>
+<dt>File</dt>
+<dd>When you want to handle files and file information.</dd>
+<dt>Node revision</dt>
+<dd>When you want to handle node revision information, choose this.</dd>
+<dt>Term</dt>
+<dd>When you want to handle taxonomy, choose this one.</dd>
+<dt>User</dt>
+<dd>When you want to handle users and user information, choose this one.</dd>
+</dl>
diff --git a/sites/all/modules/views/help/view-settings.html b/sites/all/modules/views/help/view-settings.html
new file mode 100644
index 000000000..906656e19
--- /dev/null
+++ b/sites/all/modules/views/help/view-settings.html
@@ -0,0 +1,5 @@
+With the Description and Tag you can set the "View description" and the "View tag". The tag help you to organize your views. Only one tag may be entered for a given view. Note that this tag can also be used in themeing, so do not use words already reserved by Drupal for use, such as "Page".
+
+You can <strong>clone</strong> a view. When you click on the clone link on the upper right, all the options in the current view will copy to a new view, which can be given a new name and edited further.
+
+You can <strong>export</strong> a view. You can the copy and paste the code from a view into a module or something else. It is a array with all options for that view.
diff --git a/sites/all/modules/views/help/view-type.html b/sites/all/modules/views/help/view-type.html
new file mode 100644
index 000000000..bdb80fc0c
--- /dev/null
+++ b/sites/all/modules/views/help/view-type.html
@@ -0,0 +1,21 @@
+<p>The <strong>view type</strong> describes how this view is stored; Views is capable of having Views entirely in code that are not in the database. This allows modules to easily ship with Views built in, and it allows you to create a module to store your views for easy deployment between development and production servers.</p>
+
+<dl>
+<dt><strong>Normal</strong></dt>
+<dd>Normal views are stored in your database and are completely local to your system.</dd>
+
+<dt><strong>Default</strong></dt>
+<dd>Default views are stored only in code and are not anywhere in your database. They may be <strong>enabled</strong> or <strong>disabled</strong> but you may not completely remove them from your system. You can <strong>override</strong> the view which will create a local copy of your view. If you do this, future updates to the version in code will not affect your view.</dd>
+
+<dt><strong>Overridden</strong></dt>
+<dd>Overridden views are stored both in code and in the database; while overridden, the version that is in code is completely dormant. If you <strong>revert</strong> the view, the version in the database will be deleted, and the version that is in code will once again be used.</dd>
+</dl>
+
+You may store your views in code with the following procedure:
+<ol>
+<li> Create a module to store the views. </li>
+<li> Add the function <em>MODULENAME_views_default_views()</em> to this module. </li>
+<li> Export the view you wish to store in your module in code. Cut and paste that into the abovenamed function. Make sure the last line of the view is: <em>$views[$view-&gt;name] = $view;</em></li>
+<li> Make sure the last line of the function is <em>return $views;</em></li>
+<li> After you make any changes, be sure to <strong>clear the Views' cache</strong>. You may do this from the <strong>Tools</strong> menu.</li>
+</ol>
diff --git a/sites/all/modules/views/help/views.help.ini b/sites/all/modules/views/help/views.help.ini
new file mode 100644
index 000000000..bd2d4577a
--- /dev/null
+++ b/sites/all/modules/views/help/views.help.ini
@@ -0,0 +1,359 @@
+[advanced help settings]
+line break = TRUE
+
+[about]
+title = "What is Views?"
+weight = -50
+
+[getting-started]
+title = "Getting started"
+weight = -40
+
+[new]
+title = "What's new in Views 3"
+weight = -30
+
+[example-users-by-role]
+title = "Create a page to list users by role"
+parent = getting-started
+weight = -20
+
+[example-recent-stories]
+title = "Create a block of recent stories"
+parent = getting-started
+weight = 0
+
+[example-user-feed]
+title = "Create an RSS feed of user posts"
+parent = getting-started
+weight = 30
+
+[example-author-block]
+title = "Create a block of author's recent blog posts"
+parent = getting-started
+weight = 40
+
+
+[view-add]
+title = "Add a View"
+weight = -10
+
+[view-settings]
+title = "View settings"
+weight = 0
+
+[basic-settings]
+title = "Basic settings"
+weight = 10
+
+[advanced-settings]
+title= "Advanced settings"
+weight = 20
+
+[display]
+title = "Displays"
+weight = -10
+
+[display-default]
+title = "Default display"
+parent = display
+weight = -20
+
+[display-page]
+title = "Page display"
+parent = display
+weight = -15
+
+[display-block]
+title = "Block display"
+parent = display
+weight = -10
+
+[display-attachment]
+title = "Attachment display"
+parent = display
+
+[display-feed]
+title = "Feed display"
+parent = display
+
+[style-settings]
+title = "Style settings"
+weight = 30
+
+[style]
+title = "Output styles (View styles)"
+parent = style-settings
+weight = -20
+
+[style-grid]
+title = "Grid (output style)"
+parent = style
+weight = 0
+
+[style-list]
+title = "HTML List (output style)"
+parent = style
+weight = 10
+
+[style-table]
+title = "Table (output style)"
+parent = style
+weight = 20
+
+[style-unformatted]
+title = "Unformatted (output style)"
+parent = style
+weight = 30
+
+[style-rss]
+title = "RSS output style"
+parent = style
+weight = 50
+
+[style-grouping]
+title = "Grouping in styles"
+parent = style
+weight = -7
+
+[style-row]
+title = "Row styles"
+weight = -10
+parent = style-settings
+
+[style-jump]
+title = "Jump menu output style"
+parent = style
+weight = -4
+
+[style-fields]
+title = "Fields"
+parent = style-row
+weight = -10
+
+[style-node]
+title = "Node"
+parent = style-row
+weight = 0
+
+[style-node-rss]
+title = "Node RSS item row style"
+parent = style-row
+weight = 10
+
+[style-comment-rss]
+title = "Comment RSS item row style"
+parent = style-row
+weight = 20
+
+[performance]
+title = "Performance"
+
+[performance-views-vs-displays]
+title = "Multiple Views vs Multiple Displays"
+parent = performance
+
+[analyze-theme]
+title = "Theme information"
+parent = style-settings
+weight = 30
+
+[using-theme]
+title = "Using Views templates"
+parent = analyze-theme
+weight = 40
+
+[theme-css]
+title = "Using CSS with Views"
+parent = style-settings
+weight = 20
+
+[advanced-style-settings]
+title = "Advanced Style Settings"
+parent = style-settings
+
+[group-by]
+title = "Group by"
+parent = field
+
+[menu]
+title = "Menu options (page display)"
+parent = display-page
+
+[path]
+title = "Path options (page display)"
+parent = display-page
+
+[exposed-form]
+title = "Exposed Form"
+weight = 45
+
+[header]
+title = "Header"
+weight = 50
+
+[footer]
+title = "Footer"
+weight = 60
+
+[empty-text]
+title = "Empty Text"
+weight = 70
+
+[field]
+title = "Fields"
+weight = 80
+
+[relationship]
+title = "Relationships"
+weight = 90
+
+[aggregation]
+title = "Aggregation"
+weight = 90
+
+[argument]
+title = "Arguments/Contextual Filters"
+weight = 100
+
+[style-summary-unformatted]
+title = "Summary Style: Unformatted (output style)"
+parent = argument
+
+[style-summary]
+title = "Summary Style: List (output style)"
+parent = argument
+
+[sort]
+title = "Sort criteria"
+weight = 110
+
+[filter]
+title = "Filters"
+weight = 120
+
+[overrides]
+title = "What are overrides?"
+parent = display
+
+[embed]
+title = "Embedding a view into other parts of your site"
+weight = 140
+
+[upgrading]
+title = "Upgrading your Views from Drupal 6 to Drupal 7"
+
+[updating-view3]
+title = "Updating your views from Views 2 to Views 3"
+weight = 160
+
+[misc-notes]
+title = "Known Issues and Workarounds"
+
+[reports]
+title = "Reports"
+
+
+; API related
+[api]
+title = "Views' API"
+weight = 170
+
+[api-tables]
+title = "Describing tables to Views"
+weight = -100
+parent = api
+
+[api-default-views]
+title = "Using default views in your module"
+weight = -90
+parent = api
+
+[api-handlers]
+title = "How Views handlers work"
+weight = -50
+parent = api
+
+[api-handler-area]
+title = "How to write an area handler"
+weight = -40
+parent = api
+
+[api-plugins]
+title = "How Views plugins work"
+weight = -40
+parent = api
+
+[api-forms]
+title = "Outputting form elements from handlers"
+weight = -30
+parent = api
+
+[api-upgrading]
+title = "Upgrading to Drupal 7 (API)"
+parent = api
+
+[api-example]
+title = "Integrating the Node Example module"
+parent = api
+weight = 100
+
+[alter-exposed-filter]
+title = "Altering the default value of an exposed filter"
+parent = api
+weight = 101
+
+[get-total-rows]
+title = "How to get a total number of rows for a View with a filter and no pager"]
+parent = "api"
+weight = 102
+
+[drush]
+title = "Drush commands for Views"
+parent = api
+weight = 103
+
+;Troubleshooting
+[troubleshooting]
+title = "Troubleshooting tips and gotchas"
+weight = 110
+
+[ui-crashes]
+parent = troubleshooting
+title = "UI crashes and whitescreens"
+weight = 115
+
+;Other places to get help
+[other-help]
+title = "Other places to get help"
+weight = 200
+
+[demo-video]
+title = "Video demos for Views"
+weight = 210
+parent = other-help
+
+;More examples
+[top-pager]
+title = "Adding a pager to the top and bottom of a view"
+weight = 215
+
+[select-multple-nids-contextual-filters]
+title = "Selecting multiple nids with contextual filters (arguments)"
+weight = 216
+
+[taxonomy-page-override]
+title = "Overriding the default taxonomy pages with the Taxonomy term view"
+weight = 217
+
+[only-link-title-for-published-nodes]
+title = "A Views field template that creates title links only for published nodes"
+weight = 218
+
+[example-filter-by-current-user]
+title = "Example to filter content by the current logged-in user"
+weight = 219
+
+[example-slideshow-thumb-pager]
+title = "Example to create a slideshow with thumbnails as a pager underneath"
+weight = 220
diff --git a/sites/all/modules/views/images/arrow-active.png b/sites/all/modules/views/images/arrow-active.png
new file mode 100644
index 000000000..3bbd3c27f
--- /dev/null
+++ b/sites/all/modules/views/images/arrow-active.png
Binary files differ
diff --git a/sites/all/modules/views/images/close.png b/sites/all/modules/views/images/close.png
new file mode 100644
index 000000000..2af0da5b4
--- /dev/null
+++ b/sites/all/modules/views/images/close.png
Binary files differ
diff --git a/sites/all/modules/views/images/expanded-options.png b/sites/all/modules/views/images/expanded-options.png
new file mode 100644
index 000000000..b7b755c0a
--- /dev/null
+++ b/sites/all/modules/views/images/expanded-options.png
Binary files differ
diff --git a/sites/all/modules/views/images/loading-small.gif b/sites/all/modules/views/images/loading-small.gif
new file mode 100644
index 000000000..5cbf6e7b7
--- /dev/null
+++ b/sites/all/modules/views/images/loading-small.gif
Binary files differ
diff --git a/sites/all/modules/views/images/loading.gif b/sites/all/modules/views/images/loading.gif
new file mode 100644
index 000000000..2dbcd624a
--- /dev/null
+++ b/sites/all/modules/views/images/loading.gif
Binary files differ
diff --git a/sites/all/modules/views/images/overridden.gif b/sites/all/modules/views/images/overridden.gif
new file mode 100644
index 000000000..b7811910e
--- /dev/null
+++ b/sites/all/modules/views/images/overridden.gif
Binary files differ
diff --git a/sites/all/modules/views/images/sprites.png b/sites/all/modules/views/images/sprites.png
new file mode 100644
index 000000000..9fcb94022
--- /dev/null
+++ b/sites/all/modules/views/images/sprites.png
Binary files differ
diff --git a/sites/all/modules/views/images/status-active.gif b/sites/all/modules/views/images/status-active.gif
new file mode 100644
index 000000000..207e95c3f
--- /dev/null
+++ b/sites/all/modules/views/images/status-active.gif
Binary files differ
diff --git a/sites/all/modules/views/includes/admin.inc b/sites/all/modules/views/includes/admin.inc
new file mode 100644
index 000000000..237a05d56
--- /dev/null
+++ b/sites/all/modules/views/includes/admin.inc
@@ -0,0 +1,5465 @@
+<?php
+
+/**
+ * @file
+ * Provides the Views' administrative interface.
+ */
+
+/**
+ * Create an array of Views admin CSS for adding or attaching.
+ *
+ * This returns an array of arrays. Each array represents a single
+ * file. The array format is:
+ * - file: The fully qualified name of the file to send to drupal_add_css
+ * - options: An array of options to pass to drupal_add_css.
+ */
+function views_ui_get_admin_css() {
+ $module_path = drupal_get_path('module', 'views_ui');
+ $list = array();
+ $list[$module_path . '/css/views-admin.css'] = array();
+
+ $list[$module_path . '/css/ie/views-admin.ie7.css'] = array(
+ 'browsers' => array(
+ 'IE' => 'lte IE 7',
+ '!IE' => FALSE
+ ),
+ 'preprocess' => FALSE,
+ );
+
+ $list[$module_path . '/css/views-admin.theme.css'] = array();
+
+ // Add in any theme specific CSS files we have
+ $themes = list_themes();
+ $theme_key = $GLOBALS['theme'];
+ while ($theme_key) {
+ // Try to find the admin css file for non-core themes.
+ if (!in_array($theme_key, array('garland', 'seven', 'bartik'))) {
+ $theme_path = drupal_get_path('theme', $theme_key);
+ // First search in the css directory, then in the root folder of the theme.
+ if (file_exists($theme_path . "/css/views-admin.$theme_key.css")) {
+ $list[$theme_path . "/css/views-admin.$theme_key.css"] = array(
+ 'group' => CSS_THEME,
+ );
+ }
+ else if (file_exists($theme_path . "/views-admin.$theme_key.css")) {
+ $list[$theme_path . "/views-admin.$theme_key.css"] = array(
+ 'group' => CSS_THEME,
+ );
+ }
+ }
+ else {
+ $list[$module_path . "/css/views-admin.$theme_key.css"] = array(
+ 'group' => CSS_THEME,
+ );
+ }
+ $theme_key = isset($themes[$theme_key]->base_theme) ? $themes[$theme_key]->base_theme : '';
+ }
+ // Views contains style overrides for the following modules
+ $module_list = array('contextual', 'advanced_help', 'ctools');
+ foreach ($module_list as $module) {
+ if (module_exists($module)) {
+ $list[$module_path . '/css/views-admin.' . $module . '.css'] = array();
+ }
+ }
+
+
+ return $list;
+}
+
+/**
+ * Adds standard Views administration CSS to the current page.
+ */
+function views_ui_add_admin_css() {
+ foreach (views_ui_get_admin_css() as $file => $options) {
+ drupal_add_css($file, $options);
+ }
+}
+
+/**
+ * Check to see if the advanced help module is installed, and if not put up
+ * a message.
+ *
+ * Only call this function if the user is already in a position for this to
+ * be useful.
+ */
+function views_ui_check_advanced_help() {
+ if (!variable_get('views_ui_show_advanced_help_warning', TRUE)) {
+ return;
+ }
+
+ if (!module_exists('advanced_help')) {
+ $filename = db_query_range("SELECT filename FROM {system} WHERE type = 'module' AND name = 'advanced_help'", 0, 1)
+ ->fetchField();
+ if ($filename && file_exists($filename)) {
+ drupal_set_message(t('If you <a href="@modules">enable the advanced help module</a>, Views will provide more and better help. <a href="@hide">You can disable this message at the Views settings page.</a>', array('@modules' => url('admin/modules'),'@hide' => url('admin/structure/views/settings'))));
+ }
+ else {
+ drupal_set_message(t('If you install the advanced help module from !href, Views will provide more and better help. <a href="@hide">You can disable this message at the Views settings page.</a>', array('!href' => l('http://drupal.org/project/advanced_help', 'http://drupal.org/project/advanced_help'), '@hide' => url('admin/structure/views/settings'))));
+ }
+ }
+}
+
+/**
+ * Returns the results of the live preview.
+ */
+function views_ui_preview($view, $display_id, $args = array()) {
+ // When this function is invoked as a page callback, each Views argument is
+ // passed separately.
+ if (!is_array($args)) {
+ $args = array_slice(func_get_args(), 2);
+ }
+
+ // Save $_GET['q'] so it can be restored before returning from this function.
+ $q = $_GET['q'];
+
+ // Determine where the query and performance statistics should be output.
+ $show_query = variable_get('views_ui_show_sql_query', FALSE);
+ $show_info = variable_get('views_ui_show_preview_information', FALSE);
+ $show_location = variable_get('views_ui_show_sql_query_where', 'above');
+
+ $show_stats = variable_get('views_ui_show_performance_statistics', FALSE);
+ if ($show_stats) {
+ $show_stats = variable_get('views_ui_show_sql_query_where', 'above');
+ }
+
+ $combined = $show_query && $show_stats;
+
+ $rows = array('query' => array(), 'statistics' => array());
+ $output = '';
+
+ $errors = $view->validate();
+ if ($errors === TRUE) {
+ $view->ajax = TRUE;
+ $view->live_preview = TRUE;
+ $view->views_ui_context = TRUE;
+
+ // AJAX happens via $_POST but everything expects exposed data to
+ // be in GET. Copy stuff but remove ajax-framework specific keys.
+ // If we're clicking on links in a preview, though, we could actually
+ // still have some in $_GET, so we use $_REQUEST to ensure we get it all.
+ $exposed_input = $_REQUEST;
+ foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', 'ajax_html_ids', 'ajax_page_state', 'form_id', 'form_build_id', 'form_token') as $key) {
+ if (isset($exposed_input[$key])) {
+ unset($exposed_input[$key]);
+ }
+ }
+
+ $view->set_exposed_input($exposed_input);
+
+
+ if (!$view->set_display($display_id)) {
+ return t('Invalid display id @display', array('@display' => $display_id));
+ }
+
+ $view->set_arguments($args);
+
+ // Store the current view URL for later use:
+ if ($view->display_handler->get_option('path')) {
+ $path = $view->get_url();
+ }
+
+ // Make view links come back to preview.
+ $view->override_path = 'admin/structure/views/nojs/preview/' . $view->name . '/' . $display_id;
+
+ // Also override $_GET['q'] so we get the pager.
+ $original_path = current_path();
+ $_GET['q'] = $view->override_path;
+ if ($args) {
+ $_GET['q'] .= '/' . implode('/', $args);
+ }
+
+ // Suppress contextual links of entities within the result set during a
+ // Preview.
+ // @todo We'll want to add contextual links specific to editing the View, so
+ // the suppression may need to be moved deeper into the Preview pipeline.
+ views_ui_contextual_links_suppress_push();
+ $preview = $view->preview($display_id, $args);
+ views_ui_contextual_links_suppress_pop();
+
+ // Reset variables.
+ unset($view->override_path);
+ $_GET['q'] = $original_path;
+
+ // Prepare the query information and statistics to show either above or
+ // below the view preview.
+ if ($show_info || $show_query || $show_stats) {
+ // Get information from the preview for display.
+ if (!empty($view->build_info['query'])) {
+ if ($show_query) {
+ $query = $view->build_info['query'];
+ // Only the sql default class has a method getArguments.
+ $quoted = array();
+
+ if (get_class($view->query) == 'views_plugin_query_default') {
+ $quoted = $query->getArguments();
+ $connection = Database::getConnection();
+ foreach ($quoted as $key => $val) {
+ if (is_array($val)) {
+ $quoted[$key] = implode(', ', array_map(array($connection, 'quote'), $val));
+ }
+ else {
+ $quoted[$key] = $connection->quote($val);
+ }
+ }
+ }
+ $rows['query'][] = array('<strong>' . t('Query') . '</strong>', '<pre>' . check_plain(strtr($query, $quoted)) . '</pre>');
+ if (!empty($view->additional_queries)) {
+ $queries = '<strong>' . t('These queries were run during view rendering:') . '</strong>';
+ foreach ($view->additional_queries as $query) {
+ if ($queries) {
+ $queries .= "\n";
+ }
+ $queries .= t('[@time ms]', array('@time' => intval($query[1] * 100000) / 100)) . ' ' . $query[0];
+ }
+
+ $rows['query'][] = array('<strong>' . t('Other queries') . '</strong>', '<pre>' . $queries . '</pre>');
+ }
+ }
+ if ($show_info) {
+ $rows['query'][] = array('<strong>' . t('Title') . '</strong>', filter_xss_admin($view->get_title()));
+ if (isset($path)) {
+ $path = l($path, $path);
+ }
+ else {
+ $path = t('This display has no path.');
+ }
+ $rows['query'][] = array('<strong>' . t('Path') . '</strong>', $path);
+ }
+
+ if ($show_stats) {
+ $rows['statistics'][] = array('<strong>' . t('Query build time') . '</strong>', t('@time ms', array('@time' => intval($view->build_time * 100000) / 100)));
+ $rows['statistics'][] = array('<strong>' . t('Query execute time') . '</strong>', t('@time ms', array('@time' => intval($view->execute_time * 100000) / 100)));
+ $rows['statistics'][] = array('<strong>' . t('View render time') . '</strong>', t('@time ms', array('@time' => intval($view->render_time * 100000) / 100)));
+
+ }
+ drupal_alter('views_preview_info', $rows, $view);
+ }
+ else {
+ // No query was run. Display that information in place of either the
+ // query or the performance statistics, whichever comes first.
+ if ($combined || ($show_location === 'above')) {
+ $rows['query'] = array(array('<strong>' . t('Query') . '</strong>', t('No query was run')));
+ }
+ else {
+ $rows['statistics'] = array(array('<strong>' . t('Query') . '</strong>', t('No query was run')));
+ }
+ }
+ }
+ }
+ else {
+ foreach ($errors as $error) {
+ drupal_set_message($error, 'error');
+ }
+ $preview = t('Unable to preview due to validation errors.');
+ }
+
+ // Assemble the preview, the query info, and the query statistics in the
+ // requested order.
+ if ($show_location === 'above') {
+ if ($combined) {
+ $output .= '<div class="views-query-info">' . theme('table', array('rows' => array_merge($rows['query'], $rows['statistics']))) . '</div>';
+ }
+ else {
+ $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['query'])) . '</div>';
+ }
+ }
+ elseif ($show_stats === 'above') {
+ $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['statistics'])) . '</div>';
+ }
+
+ $output .= $preview;
+
+ if ($show_location === 'below') {
+ if ($combined) {
+ $output .= '<div class="views-query-info">' . theme('table', array('rows' => array_merge($rows['query'], $rows['statistics']))) . '</div>';
+ }
+ else {
+ $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['query'])) . '</div>';
+ }
+ }
+ elseif ($show_stats === 'below') {
+ $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['statistics'])) . '</div>';
+ }
+
+ $_GET['q'] = $q;
+ return $output;
+}
+
+/**
+ * Page callback to add a new view.
+ */
+function views_ui_add_page() {
+ views_ui_add_admin_css();
+ drupal_set_title(t('Add new view'));
+ return drupal_get_form('views_ui_add_form');
+}
+
+/**
+ * Form builder for the "add new view" page.
+ */
+function views_ui_add_form($form, &$form_state) {
+ ctools_include('dependent');
+ $form['#attached']['js'][] = drupal_get_path('module', 'views_ui') . '/js/views-admin.js';
+ $form['#attributes']['class'] = array('views-admin');
+
+ $form['human_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('View name'),
+ '#required' => TRUE,
+ '#size' => 32,
+ '#default_value' => !empty($form_state['view']) ? $form_state['view']->human_name : '',
+ '#maxlength' => 255,
+ );
+ $form['name'] = array(
+ '#type' => 'machine_name',
+ '#maxlength' => 128,
+ '#machine_name' => array(
+ 'exists' => 'views_get_view',
+ 'source' => array('human_name'),
+ ),
+ '#description' => t('A unique machine-readable name for this View. It must only contain lowercase letters, numbers, and underscores.'),
+ );
+
+ $form['description_enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Description'),
+ );
+ $form['description'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Provide description'),
+ '#title_display' => 'invisible',
+ '#size' => 64,
+ '#default_value' => !empty($form_state['view']) ? $form_state['view']->description : '',
+ '#dependency' => array(
+ 'edit-description-enable' => array(1),
+ ),
+ );
+
+ // Create a wrapper for the entire dynamic portion of the form. Everything
+ // that can be updated by AJAX goes somewhere inside here. For example, this
+ // is needed by "Show" dropdown (below); it changes the base table of the
+ // view and therefore potentially requires all options on the form to be
+ // dynamically updated.
+ $form['displays'] = array();
+
+ // Create the part of the form that allows the user to select the basic
+ // properties of what the view will display.
+ $form['displays']['show'] = array(
+ '#type' => 'fieldset',
+ '#tree' => TRUE,
+ '#attributes' => array('class' => array('container-inline')),
+ );
+
+ // Create the "Show" dropdown, which allows the base table of the view to be
+ // selected.
+ $wizard_plugins = views_ui_get_wizards();
+ $options = array();
+ foreach ($wizard_plugins as $key => $wizard) {
+ $options[$key] = $wizard['title'];
+ }
+ $form['displays']['show']['wizard_key'] = array(
+ '#type' => 'select',
+ '#title' => t('Show'),
+ '#options' => $options,
+ );
+ $show_form = &$form['displays']['show'];
+ $show_form['wizard_key']['#default_value'] = views_ui_get_selected($form_state, array('show', 'wizard_key'), 'node', $show_form['wizard_key']);
+ // Changing this dropdown updates the entire content of $form['displays'] via
+ // AJAX.
+ views_ui_add_ajax_trigger($show_form, 'wizard_key', array('displays'));
+
+ // Build the rest of the form based on the currently selected wizard plugin.
+ $wizard_key = $show_form['wizard_key']['#default_value'];
+ $get_instance = $wizard_plugins[$wizard_key]['get_instance'];
+ $wizard_instance = $get_instance($wizard_plugins[$wizard_key]);
+ $form = $wizard_instance->build_form($form, $form_state);
+
+ $form['save'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save & exit'),
+ '#validate' => array('views_ui_wizard_form_validate'),
+ '#submit' => array('views_ui_add_form_save_submit'),
+ );
+ $form['continue'] = array(
+ '#type' => 'submit',
+ '#value' => t('Continue & edit'),
+ '#validate' => array('views_ui_wizard_form_validate'),
+ '#submit' => array('views_ui_add_form_store_edit_submit'),
+ '#process' => array_merge(array('views_ui_default_button'), element_info_property('submit', '#process', array())),
+ );
+ $form['cancel'] = array(
+ '#type' => 'submit',
+ '#value' => t('Cancel'),
+ '#submit' => array('views_ui_add_form_cancel_submit'),
+ '#limit_validation_errors' => array(),
+ );
+
+ return $form;
+}
+
+/**
+ * Helper form element validator: integer.
+ *
+ * The problem with this is that the function is private so it's not guaranteed
+ * that it might not be renamed/changed. In the future field.module or something else
+ * should provide a public validate function.
+ *
+ * @see _element_validate_integer_positive()
+ */
+function views_element_validate_integer($element, &$form_state) {
+ $value = $element['#value'];
+ if ($value !== '' && (!is_numeric($value) || intval($value) != $value || abs($value) != $value)) {
+ form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title'])));
+ }
+}
+
+/**
+ * Gets the current value of a #select element, from within a form constructor function.
+ *
+ * This function is intended for use in highly dynamic forms (in particular the
+ * add view wizard) which are rebuilt in different ways depending on which
+ * triggering element (AJAX or otherwise) was most recently fired. For example,
+ * sometimes it is necessary to decide how to build one dynamic form element
+ * based on the value of a different dynamic form element that may not have
+ * even been present on the form the last time it was submitted. This function
+ * takes care of resolving those conflicts and gives you the proper current
+ * value of the requested #select element.
+ *
+ * By necessity, this function sometimes uses non-validated user input from
+ * $form_state['input'] in making its determination. Although it performs some
+ * minor validation of its own, it is not complete. The intention is that the
+ * return value of this function should only be used to help decide how to
+ * build the current form the next time it is reloaded, not to be saved as if
+ * it had gone through the normal, final form validation process. Do NOT use
+ * the results of this function for any other purpose besides deciding how to
+ * build the next version of the form.
+ *
+ * @param $form_state
+ * The standard associative array containing the current state of the form.
+ * @param $parents
+ * An array of parent keys that point to the part of the submitted form
+ * values that are expected to contain the element's value (in the case where
+ * this form element was actually submitted). In a simple case (assuming
+ * #tree is TRUE throughout the form), if the select element is located in
+ * $form['wrapper']['select'], so that the submitted form values would
+ * normally be found in $form_state['values']['wrapper']['select'], you would
+ * pass array('wrapper', 'select') for this parameter.
+ * @param $default_value
+ * The default value to return if the #select element does not currently have
+ * a proper value set based on the submitted input.
+ * @param $element
+ * An array representing the current version of the #select element within
+ * the form.
+ *
+ * @return
+ * The current value of the #select element. A common use for this is to feed
+ * it back into $element['#default_value'] so that the form will be rendered
+ * with the correct value selected.
+ */
+function views_ui_get_selected($form_state, $parents, $default_value, $element) {
+ // For now, don't trust this to work on anything but a #select element.
+ if (!isset($element['#type']) || $element['#type'] != 'select' || !isset($element['#options'])) {
+ return $default_value;
+ }
+
+ // If there is a user-submitted value for this element that matches one of
+ // the currently available options attached to it, use that. We need to check
+ // $form_state['input'] rather than $form_state['values'] here because the
+ // triggering element often has the #limit_validation_errors property set to
+ // prevent unwanted errors elsewhere on the form. This means that the
+ // $form_state['values'] array won't be complete. We could make it complete
+ // by adding each required part of the form to the #limit_validation_errors
+ // property individually as the form is being built, but this is difficult to
+ // do for a highly dynamic and extensible form. This method is much simpler.
+ if (!empty($form_state['input'])) {
+ $key_exists = NULL;
+ $submitted = drupal_array_get_nested_value($form_state['input'], $parents, $key_exists);
+ // Check that the user-submitted value is one of the allowed options before
+ // returning it. This is not a substitute for actual form validation;
+ // rather it is necessary because, for example, the same select element
+ // might have #options A, B, and C under one set of conditions but #options
+ // D, E, F under a different set of conditions. So the form submission
+ // might have occurred with option A selected, but when the form is rebuilt
+ // option A is no longer one of the choices. In that case, we don't want to
+ // use the value that was submitted anymore but rather fall back to the
+ // default value.
+ if ($key_exists && in_array($submitted, array_keys($element['#options']))) {
+ return $submitted;
+ }
+ }
+
+ // Fall back on returning the default value if nothing was returned above.
+ return $default_value;
+}
+
+/**
+ * Converts a form element in the add view wizard to be AJAX-enabled.
+ *
+ * This function takes a form element and adds AJAX behaviors to it such that
+ * changing it triggers another part of the form to update automatically. It
+ * also adds a submit button to the form that appears next to the triggering
+ * element and that duplicates its functionality for users who do not have
+ * JavaScript enabled (the button is automatically hidden for users who do have
+ * JavaScript).
+ *
+ * To use this function, call it directly from your form builder function
+ * immediately after you have defined the form element that will serve as the
+ * JavaScript trigger. Calling it elsewhere (such as in hook_form_alter()) may
+ * mean that the non-JavaScript fallback button does not appear in the correct
+ * place in the form.
+ *
+ * @param $wrapping_element
+ * The element whose child will server as the AJAX trigger. For example, if
+ * $form['some_wrapper']['triggering_element'] represents the element which
+ * will trigger the AJAX behavior, you would pass $form['some_wrapper'] for
+ * this parameter.
+ * @param $trigger_key
+ * The key within the wrapping element that identifies which of its children
+ * serves as the AJAX trigger. In the above example, you would pass
+ * 'triggering_element' for this parameter.
+ * @param $refresh_parents
+ * An array of parent keys that point to the part of the form that will be
+ * refreshed by AJAX. For example, if triggering the AJAX behavior should
+ * cause $form['dynamic_content']['section'] to be refreshed, you would pass
+ * array('dynamic_content', 'section') for this parameter.
+ */
+function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_parents) {
+ $seen_ids = &drupal_static(__FUNCTION__ . ':seen_ids', array());
+ $seen_buttons = &drupal_static(__FUNCTION__ . ':seen_buttons', array());
+
+ // Add the AJAX behavior to the triggering element.
+ $triggering_element = &$wrapping_element[$trigger_key];
+ $triggering_element['#ajax']['callback'] = 'views_ui_ajax_update_form';
+ // We do not use drupal_html_id() to get an ID for the AJAX wrapper, because
+ // it remembers IDs across AJAX requests (and won't reuse them), but in our
+ // case we need to use the same ID from request to request so that the
+ // wrapper can be recognized by the AJAX system and its content can be
+ // dynamically updated. So instead, we will keep track of duplicate IDs
+ // (within a single request) on our own, later in this function.
+ $triggering_element['#ajax']['wrapper'] = 'edit-view-' . implode('-', $refresh_parents) . '-wrapper';
+
+ // Add a submit button for users who do not have JavaScript enabled. It
+ // should be displayed next to the triggering element on the form.
+ $button_key = $trigger_key . '_trigger_update';
+ $wrapping_element[$button_key] = array(
+ '#type' => 'submit',
+ // Hide this button when JavaScript is enabled.
+ '#attributes' => array('class' => array('js-hide')),
+ '#submit' => array('views_ui_nojs_submit'),
+ // Add a process function to limit this button's validation errors to the
+ // triggering element only. We have to do this in #process since until the
+ // form API has added the #parents property to the triggering element for
+ // us, we don't have any (easy) way to find out where its submitted values
+ // will eventually appear in $form_state['values'].
+ '#process' => array_merge(array('views_ui_add_limited_validation'), element_info_property('submit', '#process', array())),
+ // Add an after-build function that inserts a wrapper around the region of
+ // the form that needs to be refreshed by AJAX (so that the AJAX system can
+ // detect and dynamically update it). This is done in #after_build because
+ // it's a convenient place where we have automatic access to the complete
+ // form array, but also to minimize the chance that the HTML we add will
+ // get clobbered by code that runs after we have added it.
+ '#after_build' => array_merge(element_info_property('submit', '#after_build', array()), array('views_ui_add_ajax_wrapper')),
+ );
+ // Copy #weight and #access from the triggering element to the button, so
+ // that the two elements will be displayed together.
+ foreach (array('#weight', '#access') as $property) {
+ if (isset($triggering_element[$property])) {
+ $wrapping_element[$button_key][$property] = $triggering_element[$property];
+ }
+ }
+ // For easiest integration with the form API and the testing framework, we
+ // always give the button a unique #value, rather than playing around with
+ // #name.
+ $button_title = !empty($triggering_element['#title']) ? $triggering_element['#title'] : $trigger_key;
+ if (empty($seen_buttons[$button_title])) {
+ $wrapping_element[$button_key]['#value'] = t('Update "@title" choice', array(
+ '@title' => $button_title,
+ ));
+ $seen_buttons[$button_title] = 1;
+ }
+ else {
+ $wrapping_element[$button_key]['#value'] = t('Update "@title" choice (@number)', array(
+ '@title' => $button_title,
+ '@number' => ++$seen_buttons[$button_title],
+ ));
+ }
+
+ // Attach custom data to the triggering element and submit button, so we can
+ // use it in both the process function and AJAX callback.
+ $ajax_data = array(
+ 'wrapper' => $triggering_element['#ajax']['wrapper'],
+ 'trigger_key' => $trigger_key,
+ 'refresh_parents' => $refresh_parents,
+ // Keep track of duplicate wrappers so we don't add the same wrapper to the
+ // page more than once.
+ 'duplicate_wrapper' => !empty($seen_ids[$triggering_element['#ajax']['wrapper']]),
+ );
+ $seen_ids[$triggering_element['#ajax']['wrapper']] = TRUE;
+ $triggering_element['#views_ui_ajax_data'] = $ajax_data;
+ $wrapping_element[$button_key]['#views_ui_ajax_data'] = $ajax_data;
+}
+
+/**
+ * Processes a non-JavaScript fallback submit button to limit its validation errors.
+ */
+function views_ui_add_limited_validation($element, &$form_state) {
+ // Retrieve the AJAX triggering element so we can determine its parents. (We
+ // know it's at the same level of the complete form array as the submit
+ // button, so all we have to do to find it is swap out the submit button's
+ // last array parent.)
+ $array_parents = $element['#array_parents'];
+ array_pop($array_parents);
+ $array_parents[] = $element['#views_ui_ajax_data']['trigger_key'];
+ $ajax_triggering_element = drupal_array_get_nested_value($form_state['complete form'], $array_parents);
+
+ // Limit this button's validation to the AJAX triggering element, so it can
+ // update the form for that change without requiring that the rest of the
+ // form be filled out properly yet.
+ $element['#limit_validation_errors'] = array($ajax_triggering_element['#parents']);
+
+ // If we are in the process of a form submission and this is the button that
+ // was clicked, the form API workflow in form_builder() will have already
+ // copied it to $form_state['triggering_element'] before our #process
+ // function is run. So we need to make the same modifications in $form_state
+ // as we did to the element itself, to ensure that #limit_validation_errors
+ // will actually be set in the correct place.
+ if (!empty($form_state['triggering_element'])) {
+ $clicked_button = &$form_state['triggering_element'];
+ if ($clicked_button['#name'] == $element['#name'] && $clicked_button['#value'] == $element['#value']) {
+ $clicked_button['#limit_validation_errors'] = $element['#limit_validation_errors'];
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * After-build function that adds a wrapper to a form region (for AJAX refreshes).
+ *
+ * This function inserts a wrapper around the region of the form that needs to
+ * be refreshed by AJAX, based on information stored in the corresponding
+ * submit button form element.
+ */
+function views_ui_add_ajax_wrapper($element, &$form_state) {
+ // Don't add the wrapper <div> if the same one was already inserted on this
+ // form.
+ if (empty($element['#views_ui_ajax_data']['duplicate_wrapper'])) {
+ // Find the region of the complete form that needs to be refreshed by AJAX.
+ // This was earlier stored in a property on the element.
+ $complete_form = &$form_state['complete form'];
+ $refresh_parents = $element['#views_ui_ajax_data']['refresh_parents'];
+ $refresh_element = drupal_array_get_nested_value($complete_form, $refresh_parents);
+
+ // The HTML ID that AJAX expects was also stored in a property on the
+ // element, so use that information to insert the wrapper <div> here.
+ $id = $element['#views_ui_ajax_data']['wrapper'];
+ $refresh_element += array(
+ '#prefix' => '',
+ '#suffix' => '',
+ );
+ $refresh_element['#prefix'] = '<div id="' . $id . '" class="views-ui-ajax-wrapper">' . $refresh_element['#prefix'];
+ $refresh_element['#suffix'] .= '</div>';
+
+ // Copy the element that needs to be refreshed back into the form, with our
+ // modifications to it.
+ drupal_array_set_nested_value($complete_form, $refresh_parents, $refresh_element);
+ }
+
+ return $element;
+}
+
+/**
+ * Updates a part of the add view form via AJAX.
+ *
+ * @return
+ * The part of the form that has changed.
+ */
+function views_ui_ajax_update_form($form, $form_state) {
+ // The region that needs to be updated was stored in a property of the
+ // triggering element by views_ui_add_ajax_trigger(), so all we have to do is
+ // retrieve that here.
+ return drupal_array_get_nested_value($form, $form_state['triggering_element']['#views_ui_ajax_data']['refresh_parents']);
+}
+
+/**
+ * Non-Javascript fallback for updating the add view form.
+ */
+function views_ui_nojs_submit($form, &$form_state) {
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Validate the add view form.
+ */
+function views_ui_wizard_form_validate($form, &$form_state) {
+ $wizard = views_ui_get_wizard($form_state['values']['show']['wizard_key']);
+ $form_state['wizard'] = $wizard;
+ $get_instance = $wizard['get_instance'];
+ $form_state['wizard_instance'] = $get_instance($wizard);
+ $errors = $form_state['wizard_instance']->validate($form, $form_state);
+ foreach ($errors as $name => $message) {
+ form_set_error($name, $message);
+ }
+}
+
+/**
+ * Process the add view form, 'save'.
+ */
+function views_ui_add_form_save_submit($form, &$form_state) {
+ try {
+ $view = $form_state['wizard_instance']->create_view($form, $form_state);
+ }
+ catch (ViewsWizardException $e) {
+ drupal_set_message($e->getMessage(), 'error');
+ $form_state['redirect'] = 'admin/structure/views';
+ }
+ $view->save();
+
+ $form_state['redirect'] = 'admin/structure/views';
+ if (!empty($view->display['page'])) {
+ $display = $view->display['page'];
+ if ($display->handler->has_path()) {
+ $one_path = $display->handler->get_option('path');
+ if (strpos($one_path, '%') === FALSE) {
+ $form_state['redirect'] = $one_path; // PATH TO THE VIEW IF IT HAS ONE
+ return;
+ }
+ }
+ }
+ drupal_set_message(t('Your view was saved. You may edit it from the list below.'));
+}
+
+/**
+ * Process the add view form, 'continue'.
+ */
+function views_ui_add_form_store_edit_submit($form, &$form_state) {
+ try {
+ $view = $form_state['wizard_instance']->create_view($form, $form_state);
+ }
+ catch (ViewsWizardException $e) {
+ drupal_set_message($e->getMessage(), 'error');
+ $form_state['redirect'] = 'admin/structure/views';
+ }
+ // Just cache it temporarily to edit it.
+ views_ui_cache_set($view);
+
+ // If there is a destination query, ensure we still redirect the user to the
+ // edit view page, and then redirect the user to the destination.
+ $destination = array();
+ if (isset($_GET['destination'])) {
+ $destination = drupal_get_destination();
+ unset($_GET['destination']);
+ }
+ $form_state['redirect'] = array('admin/structure/views/view/' . $view->name, array('query' => $destination));
+}
+
+/**
+ * Cancel the add view form.
+ */
+function views_ui_add_form_cancel_submit($form, &$form_state) {
+ $form_state['redirect'] = 'admin/structure/views';
+}
+
+/**
+ * Form element validation handler for a taxonomy autocomplete field.
+ *
+ * This allows a taxonomy autocomplete field to be validated outside the
+ * standard Field API workflow, without passing in a complete field widget.
+ * Instead, all that is required is that $element['#field_name'] contain the
+ * name of the taxonomy autocomplete field that is being validated.
+ *
+ * This function is currently not used for validation directly, although it
+ * could be. Instead, it is only used to store the term IDs and vocabulary name
+ * in the element value, based on the tags that the user typed in.
+ *
+ * @see taxonomy_autocomplete_validate()
+ */
+function views_ui_taxonomy_autocomplete_validate($element, &$form_state) {
+ $value = array();
+ if ($tags = $element['#value']) {
+ // Get the machine names of the vocabularies we will search, keyed by the
+ // vocabulary IDs.
+ $field = field_info_field($element['#field_name']);
+ $vocabularies = array();
+ if (!empty($field['settings']['allowed_values'])) {
+ foreach ($field['settings']['allowed_values'] as $tree) {
+ if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
+ $vocabularies[$vocabulary->vid] = $tree['vocabulary'];
+ }
+ }
+ }
+ // Store the term ID of each (valid) tag that the user typed.
+ $typed_terms = drupal_explode_tags($tags);
+ foreach ($typed_terms as $typed_term) {
+ if ($terms = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => array_keys($vocabularies)))) {
+ $term = array_pop($terms);
+ $value['tids'][] = $term->tid;
+ }
+ }
+ // Store the term IDs along with the name of the vocabulary. Currently
+ // Views (as well as the Field UI) assumes that there will only be one
+ // vocabulary, although technically the API allows there to be more than
+ // one.
+ if (!empty($value['tids'])) {
+ $value['tids'] = array_unique($value['tids']);
+ $value['vocabulary'] = array_pop($vocabularies);
+ }
+ }
+ form_set_value($element, $value, $form_state);
+}
+
+/**
+ * Theme function; returns basic administrative information about a view.
+ *
+ * TODO: template + preprocess
+ */
+function theme_views_ui_view_info($variables) {
+ $view = $variables['view'];
+ $title = $view->get_human_name();
+
+ $displays = _views_ui_get_displays_list($view);
+ $displays = empty($displays) ? t('None') : format_plural(count($displays), 'Display', 'Displays') . ': ' . '<em>' . implode(', ', $displays) . '</em>';
+
+ switch ($view->type) {
+ case t('Default'):
+ default:
+ $type = t('In code');
+ break;
+
+ case t('Normal'):
+ $type = t('In database');
+ break;
+
+ case t('Overridden'):
+ $type = t('Database overriding code');
+ }
+
+ $output = '';
+ $output .= '<div class="views-ui-view-title">' . check_plain($title) . "</div>\n";
+ $output .= '<div class="views-ui-view-displays">' . $displays . "</div>\n";
+ $output .= '<div class="views-ui-view-storage">' . $type . "</div>\n";
+ $output .= '<div class="views-ui-view-base">' . t('Type') . ': ' . check_plain($variables['base']). "</div>\n";
+ return $output;
+}
+
+/**
+ * Page to delete a view.
+ */
+function views_ui_break_lock_confirm($form, &$form_state, $view) {
+ $form_state['view'] = &$view;
+ $form = array();
+
+ if (empty($view->locked)) {
+ $form['message']['#markup'] = t('There is no lock on view %name to break.', array('%name' => $view->name));
+ return $form;
+ }
+
+ $cancel = 'admin/structure/views/view/' . $view->name . '/edit';
+
+ $account = user_load($view->locked->uid);
+ return confirm_form($form,
+ t('Are you sure you want to break the lock on view %name?',
+ array('%name' => $view->name)),
+ $cancel,
+ t('By breaking this lock, any unsaved changes made by !user will be lost!', array('!user' => theme('username', array('account' => $account)))),
+ t('Break lock'),
+ t('Cancel'));
+}
+
+/**
+ * Submit handler to break_lock a view.
+ */
+function views_ui_break_lock_confirm_submit(&$form, &$form_state) {
+ ctools_object_cache_clear_all('view', $form_state['view']->name);
+ $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
+ drupal_set_message(t('The lock has been broken and you may now edit this view.'));
+}
+
+/**
+ * Helper function to return the used display_id for the edit page
+ *
+ * This function handles access to the display.
+ */
+function views_ui_edit_page_display($view, $display_id) {
+ // Determine the displays available for editing.
+ if ($tabs = views_ui_edit_page_display_tabs($view, $display_id)) {
+ // If a display isn't specified, use the first one.
+ if (empty($display_id)) {
+ foreach ($tabs as $id => $tab) {
+ if (!isset($tab['#access']) || $tab['#access']) {
+ $display_id = $id;
+ break;
+ }
+ }
+ }
+ // If a display is specified, but we don't have access to it, return
+ // an access denied page.
+ if ($display_id && (!isset($tabs[$display_id]) || (isset($tabs[$display_id]['#access']) && !$tabs[$display_id]['#access']))) {
+ return MENU_ACCESS_DENIED;
+ }
+
+ return $display_id;
+ }
+ elseif ($display_id) {
+ return MENU_ACCESS_DENIED;
+ }
+ else {
+ $display_id = NULL;
+ }
+
+ return $display_id;
+}
+
+/**
+ * Page callback for the Edit View page.
+ */
+function views_ui_edit_page($view, $display_id = NULL) {
+ $display_id = views_ui_edit_page_display($view, $display_id);
+ if (!in_array($display_id, array(MENU_ACCESS_DENIED, MENU_NOT_FOUND))) {
+ $build = array();
+ $build['edit_form'] = drupal_get_form('views_ui_edit_form', $view, $display_id);
+ $build['preview'] = views_ui_build_preview($view, $display_id, FALSE);
+ }
+ else {
+ $build = $display_id;
+ }
+
+ return $build;
+}
+
+function views_ui_build_preview($view, $display_id, $render = TRUE) {
+ if (isset($_POST['ajax_html_ids'])) {
+ unset($_POST['ajax_html_ids']);
+ }
+
+ $build = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('id' => 'views-preview-wrapper', 'class' => 'views-admin clearfix'),
+ );
+
+ $form_state = array('build_info' => array('args' => array($view, $display_id)));
+ $build['controls'] = drupal_build_form('views_ui_preview_form', $form_state);
+
+ $args = array();
+ if (!empty($form_state['values']['view_args'])) {
+ $args = explode('/', $form_state['values']['view_args']);
+ }
+
+ $build['preview'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('id' => 'views-live-preview'),
+ '#markup' => $render ? views_ui_preview($view->clone_view(), $display_id, $args) : '',
+ );
+
+ return $build;
+}
+
+/**
+ * Form builder callback for editing a View.
+ *
+ * @todo Remove as many #prefix/#suffix lines as possible. Use #theme_wrappers
+ * instead.
+ *
+ * @todo Rename to views_ui_edit_view_form(). See that function for the "old"
+ * version.
+ *
+ * @see views_ui_ajax_get_form()
+ */
+function views_ui_edit_form($form, &$form_state, $view, $display_id = NULL) {
+ // Do not allow the form to be cached, because $form_state['view'] can become
+ // stale between page requests.
+ // See views_ui_ajax_get_form() for how this affects #ajax.
+ // @todo To remove this and allow the form to be cacheable:
+ // - Change $form_state['view'] to $form_state['temporary']['view'].
+ // - Add a #process function to initialize $form_state['temporary']['view']
+ // on cached form submissions.
+ // - Update ctools_include() to support cached forms, or else use
+ // form_load_include().
+ $form_state['no_cache'] = TRUE;
+
+ if ($display_id) {
+ if (!$view->set_display($display_id)) {
+ $form['#markup'] = t('Invalid display id @display', array('@display' => $display_id));
+ return $form;
+ }
+
+ $view->fix_missing_relationships();
+ }
+
+ ctools_include('dependent');
+ $form['#attached']['js'][] = ctools_attach_js('dependent');
+ $form['#attached']['js'][] = ctools_attach_js('collapsible-div');
+
+ $form['#tree'] = TRUE;
+ // @todo When more functionality is added to this form, cloning here may be
+ // too soon. But some of what we do with $view later in this function
+ // results in making it unserializable due to PDO limitations.
+ $form_state['view'] = clone($view);
+
+ $form['#attached']['library'][] = array('system', 'ui.tabs');
+ $form['#attached']['library'][] = array('system', 'ui.dialog');
+ $form['#attached']['library'][] = array('system', 'drupal.ajax');
+ $form['#attached']['library'][] = array('system', 'jquery.form');
+ // TODO: This should be getting added to the page when an ajax popup calls
+ // for it, instead of having to add it manually here.
+ $form['#attached']['js'][] = 'misc/tabledrag.js';
+
+ $form['#attached']['css'] = views_ui_get_admin_css();
+ $module_path = drupal_get_path('module', 'views_ui');
+
+ $form['#attached']['js'][] = $module_path . '/js/views-admin.js';
+ $form['#attached']['js'][] = array(
+ 'data' => array('views' => array('ajax' => array(
+ 'id' => '#views-ajax-body',
+ 'title' => '#views-ajax-title',
+ 'popup' => '#views-ajax-popup',
+ 'defaultForm' => views_ui_get_default_ajax_message(),
+ ))),
+ 'type' => 'setting',
+ );
+
+ $form += array(
+ '#prefix' => '',
+ '#suffix' => '',
+ );
+ $form['#prefix'] = $form['#prefix'] . '<div class="views-edit-view views-admin clearfix">';
+ $form['#suffix'] = '</div>' . $form['#suffix'];
+
+ $form['#attributes']['class'] = array('form-edit');
+
+ if (isset($view->locked) && is_object($view->locked)) {
+ $form['locked'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('view-locked', 'messages', 'warning')),
+ '#markup' => t('This view is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => theme('username', array('account' => user_load($view->locked->uid))), '!age' => format_interval(REQUEST_TIME - $view->locked->updated), '!break' => url('admin/structure/views/view/' . $view->name . '/break-lock'))),
+ );
+ }
+ if (isset($view->vid) && $view->vid == 'new') {
+ $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard the view.');
+ }
+ else {
+ $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard your changes.');
+ }
+
+ $form['changed'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('view-changed', 'messages', 'warning')),
+ '#markup' => $message,
+ );
+ if (empty($view->changed)) {
+ $form['changed']['#attributes']['class'][] = 'js-hide';
+ }
+
+ $form['help_text'] = array(
+ '#prefix' => '<div>',
+ '#suffix' => '</div>',
+ '#markup' => t('Modify the display(s) of your view below or add new displays.'),
+ );
+
+ $form['actions'] = array(
+ '#type' => 'actions',
+ '#weight' => 0,
+ );
+
+ if (empty($view->changed)) {
+ $form['actions']['#attributes'] = array(
+ 'class' => array(
+ 'js-hide',
+ ),
+ );
+ }
+
+ $form['actions']['save'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ // Taken from the "old" UI. @TODO: Review and rename.
+ '#validate' => array('views_ui_edit_view_form_validate'),
+ '#submit' => array('views_ui_edit_view_form_submit'),
+ );
+ $form['actions']['cancel'] = array(
+ '#type' => 'submit',
+ '#value' => t('Cancel'),
+ '#submit' => array('views_ui_edit_view_form_cancel'),
+ );
+
+ $form['displays'] = array(
+ '#prefix' => '<h1 class="unit-title clearfix">' . t('Displays') . '</h1>' . "\n" . '<div class="views-displays">',
+ '#suffix' => '</div>',
+ );
+
+ $form['displays']['top'] = views_ui_render_display_top($view, $display_id);
+
+ // The rest requires a display to be selected.
+ if ($display_id) {
+ $form_state['display_id'] = $display_id;
+
+ // The part of the page where editing will take place.
+ // This element is the ctools collapsible-div container for the display edit elements.
+ $form['displays']['settings'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array(
+ 'class' => array(
+ 'views-display-settings',
+ 'box-margin',
+ 'ctools-collapsible-container',
+ ),
+ ),
+ '#id' => 'edit-display-settings',
+ );
+ $display_title = views_ui_get_display_label($view, $display_id, FALSE);
+ // Add a handle for the ctools collapsible-div. The handle is the title of the display
+ $form['displays']['settings']['tab_title']['#markup'] = '<h2 id="edit-display-settings-title" class="ctools-collapsible-handle">' . t('@display_title details', array('@display_title' => ucwords($display_title))) . '</h2>';
+ // Add a text that the display is disabled.
+ if (!empty($view->display[$display_id]->handler)) {
+ $enabled = $view->display[$display_id]->handler->get_option('enabled');
+ if (empty($enabled)) {
+ $form['displays']['settings']['disabled']['#markup'] = t('This display is disabled.');
+ }
+ }
+ // The ctools collapsible-div content
+ $form['displays']['settings']['settings_content']= array(
+ '#theme_wrappers' => array('container'),
+ '#id' => 'edit-display-settings-content',
+ '#attributes' => array(
+ 'class' => array(
+ 'ctools-collapsible-content',
+ ),
+ ),
+ );
+ // Add the edit display content
+ $form['displays']['settings']['settings_content']['tab_content'] = views_ui_get_display_tab($view, $display_id);
+ $form['displays']['settings']['settings_content']['tab_content']['#theme_wrappers'] = array('container');
+ $form['displays']['settings']['settings_content']['tab_content']['#attributes'] = array('class' => array('views-display-tab'));
+ $form['displays']['settings']['settings_content']['tab_content']['#id'] = 'views-tab-' . $display_id;
+ // Mark deleted displays as such.
+ if (!empty($view->display[$display_id]->deleted)) {
+ $form['displays']['settings']['settings_content']['tab_content']['#attributes']['class'][] = 'views-display-deleted';
+ }
+ // Mark disabled displays as such.
+ if (empty($enabled)) {
+ $form['displays']['settings']['settings_content']['tab_content']['#attributes']['class'][] = 'views-display-disabled';
+ }
+
+ // The content of the popup dialog.
+ $form['ajax-area'] = array(
+ '#theme_wrappers' => array('container'),
+ '#id' => 'views-ajax-popup',
+ );
+ $form['ajax-area']['ajax-title'] = array(
+ '#markup' => '<h2 id="views-ajax-title"></h2>',
+ );
+ $form['ajax-area']['ajax-body'] = array(
+ '#theme_wrappers' => array('container'),
+ '#id' => 'views-ajax-body',
+ '#markup' => views_ui_get_default_ajax_message(),
+ );
+ }
+
+ // If relationships had to be fixed, we want to get that into the cache
+ // so that edits work properly, and to try to get the user to save it
+ // so that it's not using weird fixed up relationships.
+ if (!empty($view->relationships_changed) && empty($_POST)) {
+ drupal_set_message(t('This view has been automatically updated to fix missing relationships. While this View should continue to work, you should verify that the automatic updates are correct and save this view.'));
+ views_ui_cache_set($view);
+ }
+ return $form;
+}
+
+/**
+ * Provide the preview formulas and the preview output, too.
+ */
+function views_ui_preview_form($form, &$form_state, $view, $display_id = 'default') {
+ $form_state['no_cache'] = TRUE;
+ $form_state['view'] = $view;
+
+ $form['#attributes'] = array('class' => array('clearfix',));
+
+ // Add a checkbox controlling whether or not this display auto-previews.
+ $form['live_preview'] = array(
+ '#type' => 'checkbox',
+ '#id' => 'edit-displays-live-preview',
+ '#title' => t('Auto preview'),
+ '#default_value' => variable_get('views_ui_always_live_preview', TRUE),
+ );
+
+ // Add the arguments textfield
+ $form['view_args'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Preview with contextual filters:'),
+ '#description' => t('Separate contextual filter values with a "/". For example, %example.', array('%example' => '40/12/10')),
+ '#id' => 'preview-args',
+// '#attributes' => array('class' => array('ctools-auto-submit')),
+ );
+
+ // Add the preview button
+ $form['button'] = array(
+ '#type' => 'submit',
+ '#value' => t('Update preview'),
+ '#attributes' => array('class' => array('arguments-preview', 'ctools-auto-submit-click')),
+ '#pre_render' => array('ctools_dependent_pre_render'),
+ '#prefix' => '<div id="preview-submit-wrapper">',
+ '#suffix' => '</div>',
+ '#id' => 'preview-submit',
+ '#submit' => array('views_ui_edit_form_submit_preview'),
+ '#ajax' => array(
+ 'path' => 'admin/structure/views/view/' . $view->name . '/preview/' . $display_id . '/ajax',
+ 'wrapper' => 'views-preview-wrapper',
+ 'event' => 'click',
+ 'progress' => array('type' => 'throbber'),
+ 'method' => 'replace',
+ ),
+ // Make ENTER in arguments textfield (and other controls) submit the form
+ // as this button, not the Save button.
+ // @todo This only works for JS users. To make this work for nojs users,
+ // we may need to split Preview into a separate form.
+ '#process' => array_merge(array('views_ui_default_button'), element_info_property('submit', '#process', array())),
+ );
+ $form['#action'] = url('admin/structure/views/view/' . $view->name .'/preview/' . $display_id);
+
+ return $form;
+}
+
+/**
+ * Render the top of the display so it can be updated during ajax operations.
+ */
+function views_ui_render_display_top($view, $display_id) {
+ $element['#theme_wrappers'] = array('views_container');
+ $element['#attributes']['class'] = array('views-display-top', 'clearfix');
+ $element['#attributes']['id'] = array('views-display-top');
+
+ // Extra actions for the display
+ $element['extra_actions'] = array(
+ '#theme' => 'links__ctools_dropbutton',
+ '#attributes' => array(
+ 'id' => 'views-display-extra-actions',
+ 'class' => array(
+ 'horizontal', 'right', 'links', 'actions',
+ ),
+ ),
+ '#links' => array(
+ 'edit-details' => array(
+ 'title' => t('edit view name/description'),
+ 'href' => "admin/structure/views/nojs/edit-details/$view->name",
+ 'attributes' => array('class' => array('views-ajax-link')),
+ ),
+ 'analyze' => array(
+ 'title' => t('analyze view'),
+ 'href' => "admin/structure/views/nojs/analyze/$view->name/$display_id",
+ 'attributes' => array('class' => array('views-ajax-link')),
+ ),
+ 'clone' => array(
+ 'title' => t('clone view'),
+ 'href' => "admin/structure/views/view/$view->name/clone",
+ ),
+ 'export' => array(
+ 'title' => t('export view'),
+ 'href' => "admin/structure/views/view/$view->name/export",
+ ),
+ 'reorder' => array(
+ 'title' => t('reorder displays'),
+ 'href' => "admin/structure/views/nojs/reorder-displays/$view->name/$display_id",
+ 'attributes' => array('class' => array('views-ajax-link')),
+ ),
+ ),
+ );
+
+ // Let other modules add additional links here.
+ drupal_alter('views_ui_display_top_links', $element['extra_actions']['#links'], $view, $display_id);
+
+ if (isset($view->type) && $view->type != t('Default')) {
+ if ($view->type == t('Overridden')) {
+ $element['extra_actions']['#links']['revert'] = array(
+ 'title' => t('revert view'),
+ 'href' => "admin/structure/views/view/$view->name/revert",
+ 'query' => array('destination' => "admin/structure/views/view/$view->name"),
+ );
+ }
+ else {
+ $element['extra_actions']['#links']['delete'] = array(
+ 'title' => t('delete view'),
+ 'href' => "admin/structure/views/view/$view->name/delete",
+ );
+ }
+ }
+
+ // Determine the displays available for editing.
+ if ($tabs = views_ui_edit_page_display_tabs($view, $display_id)) {
+ if ($display_id) {
+ $tabs[$display_id]['#active'] = TRUE;
+ }
+ $tabs['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2><ul id = "views-display-menu-tabs" class="tabs secondary">';
+ $tabs['#suffix'] = '</ul>';
+ $element['tabs'] = $tabs;
+ }
+
+ // Buttons for adding a new display.
+ foreach (views_fetch_plugin_names('display', NULL, array($view->base_table)) as $type => $label) {
+ $element['add_display'][$type] = array(
+ '#type' => 'submit',
+ '#value' => t('Add !display', array('!display' => $label)),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('views_ui_edit_form_submit_add_display', 'views_ui_edit_form_submit_delay_destination'),
+ '#attributes' => array('class' => array('add-display')),
+ // Allow JavaScript to remove the 'Add ' prefix from the button label when
+ // placing the button in a "Add" dropdown menu.
+ '#process' => array_merge(array('views_ui_form_button_was_clicked'), element_info_property('submit', '#process', array())),
+ '#values' => array(t('Add !display', array('!display' => $label)), $label),
+ );
+ }
+
+ return $element;
+}
+
+function views_ui_get_default_ajax_message() {
+ return '<div class="message">' . t("Click on an item to edit that item's details.") . '</div>';
+}
+
+/**
+ * Submit handler to add a display to a view.
+ */
+function views_ui_edit_form_submit_add_display($form, &$form_state) {
+ $view = $form_state['view'];
+
+ // Create the new display.
+ $parents = $form_state['triggering_element']['#parents'];
+ $display_type = array_pop($parents);
+ $display_id = $view->add_display($display_type);
+ views_ui_cache_set($view);
+
+ // Redirect to the new display's edit page.
+ $form_state['redirect'] = 'admin/structure/views/view/' . $view->name . '/edit/' . $display_id;
+}
+
+/**
+ * Submit handler to duplicate a display for a view.
+ */
+function views_ui_edit_form_submit_duplicate_display($form, &$form_state) {
+ $view = $form_state['view'];
+ $display_id = $form_state['display_id'];
+
+ // Create the new display.
+ $display = $view->display[$display_id];
+ $new_display_id = $view->add_display($display->display_plugin);
+ $view->display[$new_display_id] = clone $display;
+ $view->display[$new_display_id]->id = $new_display_id;
+
+ // By setting the current display the changed marker will appear on the new
+ // display.
+ $view->current_display = $new_display_id;
+ views_ui_cache_set($view);
+
+ // Redirect to the new display's edit page.
+ $form_state['redirect'] = 'admin/structure/views/view/' . $view->name . '/edit/' . $new_display_id;
+}
+
+/**
+ * Submit handler to delete a display from a view.
+ */
+function views_ui_edit_form_submit_delete_display($form, &$form_state) {
+ $view = $form_state['view'];
+ $display_id = $form_state['display_id'];
+
+ // Mark the display for deletion.
+ $view->display[$display_id]->deleted = TRUE;
+ views_ui_cache_set($view);
+
+ // Redirect to the top-level edit page. The first remaining display will
+ // become the active display.
+ $form_state['redirect'] = 'admin/structure/views/view/' . $view->name;
+}
+
+/**
+ * Submit handler to add a restore a removed display to a view.
+ */
+function views_ui_edit_form_submit_undo_delete_display($form, &$form_state) {
+ // Create the new display
+ $id = $form_state['display_id'];
+ $form_state['view']->display[$id]->deleted = FALSE;
+
+ // Store in cache
+ views_ui_cache_set($form_state['view']);
+
+ // Redirect to the top-level edit page.
+ $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
+}
+
+/**
+ * Submit handler to enable a disabled display.
+ */
+function views_ui_edit_form_submit_enable_display($form, &$form_state) {
+ $id = $form_state['display_id'];
+ // set_option doesn't work because this would might affect upper displays
+ $form_state['view']->display[$id]->handler->set_option('enabled', TRUE);
+
+ // Store in cache
+ views_ui_cache_set($form_state['view']);
+
+ // Redirect to the top-level edit page.
+ $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
+}
+
+/**
+ * Submit handler to disable display.
+ */
+function views_ui_edit_form_submit_disable_display($form, &$form_state) {
+ $id = $form_state['display_id'];
+ $form_state['view']->display[$id]->handler->set_option('enabled', FALSE);
+
+ // Store in cache
+ views_ui_cache_set($form_state['view']);
+
+ // Redirect to the top-level edit page.
+ $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
+}
+
+/**
+ * Submit handler when Preview button is clicked.
+ */
+function views_ui_edit_form_submit_preview($form, &$form_state) {
+ // Rebuild the form with a pristine $view object.
+ $form_state['build_info']['args'][0] = views_ui_cache_load($form_state['view']->name);
+ $form_state['show_preview'] = TRUE;
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Submit handler for form buttons that do not complete a form workflow.
+ *
+ * The Edit View form is a multistep form workflow, but with state managed by
+ * the CTools object cache rather than $form_state['rebuild']. Without this
+ * submit handler, buttons that add or remove displays would redirect to the
+ * destination parameter (e.g., when the Edit View form is linked to from a
+ * contextual link). This handler can be added to buttons whose form submission
+ * should not yet redirect to the destination.
+ */
+function views_ui_edit_form_submit_delay_destination($form, &$form_state) {
+ if (isset($_GET['destination']) && $form_state['redirect'] !== FALSE) {
+ if (!isset($form_state['redirect'])) {
+ $form_state['redirect'] = $_GET['q'];
+ }
+ if (is_string($form_state['redirect'])) {
+ $form_state['redirect'] = array($form_state['redirect']);
+ }
+ $options = isset($form_state['redirect'][1]) ? $form_state['redirect'][1] : array();
+ if (!isset($options['query']['destination'])) {
+ $options['query']['destination'] = $_GET['destination'];
+ }
+ $form_state['redirect'][1] = $options;
+ unset($_GET['destination']);
+ }
+}
+
+/**
+ * Adds tabs for navigating across Displays when editing a View.
+ *
+ * This function can be called from hook_menu_local_tasks_alter() to implement
+ * these tabs as secondary local tasks, or it can be called from elsewhere if
+ * having them as secondary local tasks isn't desired. The caller is responsible
+ * for setting the active tab's #active property to TRUE.
+ *
+ * @param view $view
+ * The view which will be edited.
+ * @param $display_id
+ * The display_id which is edited on the current request.
+ */
+function views_ui_edit_page_display_tabs($view, $display_id = NULL) {
+ $tabs = array();
+
+ // Create a tab for each display.
+ foreach ($view->display as $id => $display) {
+ $tabs[$id] = array(
+ '#theme' => 'menu_local_task',
+ '#link' => array(
+ 'title' => views_ui_get_display_label($view, $id),
+ 'href' => 'admin/structure/views/view/' . $view->name . '/edit/' . $id,
+ 'localized_options' => array(),
+ ),
+ );
+ if (!empty($display->deleted)) {
+ $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-deleted-link';
+ }
+ if (isset($display->display_options['enabled']) && !$display->display_options['enabled']) {
+ $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-disabled-link';
+ }
+ }
+
+ // If the default display isn't supposed to be shown, don't display its tab, unless it's the only display.
+ if ((!views_ui_show_default_display($view) && $display_id != 'default') && count($tabs) > 1) {
+ $tabs['default']['#access'] = FALSE;
+ }
+
+ // Mark the display tab as red to show validation errors.
+ $view->validate();
+ foreach ($view->display as $id => $display) {
+ if (!empty($view->display_errors[$id])) {
+ // Always show the tab.
+ $tabs[$id]['#access'] = TRUE;
+ // Add a class to mark the error and a title to make a hover tip.
+ $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'error';
+ $tabs[$id]['#link']['localized_options']['attributes']['title'] = t('This display has one or more validation errors; please review it.');
+ }
+ }
+
+ return $tabs;
+}
+
+/**
+ * Controls whether or not the default display should have its own tab on edit.
+ */
+function views_ui_show_default_display($view) {
+ // Always show the default display for advanced users who prefer that mode.
+ $advanced_mode = variable_get('views_ui_show_master_display', FALSE);
+ // For other users, show the default display only if there are no others, and
+ // hide it if there's at least one "real" display.
+ $additional_displays = (count($view->display) == 1);
+
+ return $advanced_mode || $additional_displays;
+}
+
+/**
+ * Returns a renderable array representing the edit page for one display.
+ */
+function views_ui_get_display_tab($view, $display_id) {
+ $build = array();
+ $display = $view->display[$display_id];
+ // If the plugin doesn't exist, display an error message instead of an edit
+ // page.
+ if (empty($display->handler)) {
+ $title = isset($display->display_title) ? $display->display_title : t('Invalid');
+ // @TODO: Improved UX for the case where a plugin is missing.
+ $build['#markup'] = t("Error: Display @display refers to a plugin named '@plugin', but that plugin is not available.", array('@display' => $display->id, '@plugin' => $display->display_plugin));
+ }
+ // Build the content of the edit page.
+ else {
+ $build['details'] = views_ui_get_display_tab_details($view, $display);
+ }
+ // In AJAX context, views_ui_regenerate_tab() returns this outside of form
+ // context, so hook_form_views_ui_edit_form_alter() is insufficient.
+ drupal_alter('views_ui_display_tab', $build, $view, $display_id);
+ return $build;
+}
+
+/**
+ * Helper function to get the display details section of the edit UI.
+ *
+ * @param $view
+ * @param $display
+ *
+ * @return array
+ * A renderable page build array.
+ */
+function views_ui_get_display_tab_details($view, $display) {
+ $display_title = views_ui_get_display_label($view, $display->id, FALSE);
+ $build = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('id' => 'edit-display-settings-details',),
+ );
+
+ $plugin = views_fetch_plugin_data('display', $view->display[$display->id]->display_plugin);
+ // The following is for display purposes only. We need to determine if there is more than one button and wrap
+ // the buttons in a .ctools-dropbutton class if more than one is present. Otherwise, we'll just wrap the
+ // actions in the .ctools-button class.
+ $isDisplayDeleted = !empty($display->deleted);
+ $isDeletable = empty($plugin['no remove']);
+ // The master display cannot be cloned.
+ $isDefault = $display->id == 'default';
+ // @todo: Figure out why get_option doesn't work here.
+ $isEnabled = $display->handler->get_option('enabled');
+
+ if (!$isDisplayDeleted && $isDeletable && !$isDefault) {
+ $prefix = '<div class="ctools-no-js ctools-button ctools-dropbutton"><div class="ctools-link"><a href="#" class="ctools-twisty ctools-text">open</a></div><div class="ctools-content"><ul class="horizontal right actions">';
+ $suffix = '</ul></div></div>';
+ $itemElement = 'li';
+ }
+ else {
+ $prefix = '<div class="ctools-button"><div class="ctools-content"><ul class="horizontal right actions">';
+ $suffix = '</ul></div></div>';
+ $itemElement = 'li';
+ }
+
+ if ($display->id != 'default') {
+ $build['top']['#theme_wrappers'] = array('container');
+ $build['top']['#attributes']['id'] = 'edit-display-settings-top';
+ $build['top']['#attributes']['class'] = array('views-ui-display-tab-actions', 'views-ui-display-tab-bucket', 'clearfix');
+
+ // The Delete, Duplicate and Undo Delete buttons.
+ $build['top']['actions'] = array(
+ '#prefix' => $prefix,
+ '#suffix' => $suffix,
+ );
+
+ if (!$isDisplayDeleted) {
+ if (!$isEnabled) {
+ $build['top']['actions']['enable'] = array(
+ '#type' => 'submit',
+ '#value' => t('enable @display_title', array('@display_title' => $display_title)),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('views_ui_edit_form_submit_enable_display', 'views_ui_edit_form_submit_delay_destination'),
+ '#prefix' => '<' . $itemElement . ' class="enable">',
+ "#suffix" => '</' . $itemElement . '>',
+ );
+ }
+ // Add a link to view the page.
+ elseif ($display->handler->has_path()) {
+ $path = $display->handler->get_path();
+ if (strpos($path, '%') === FALSE) {
+ $build['top']['actions']['path'] = array(
+ '#type' => 'link',
+ '#title' => t('view @display', array('@display' => $display->display_title)),
+ '#options' => array('alt' => array(t("Go to the real page for this display"))),
+ '#href' => $path,
+ '#prefix' => '<' . $itemElement . ' class="view">',
+ "#suffix" => '</' . $itemElement . '>',
+ );
+ }
+ }
+ if (!$isDefault) {
+ $build['top']['actions']['duplicate'] = array(
+ '#type' => 'submit',
+ '#value' => t('clone @display_title', array('@display_title' => $display_title)),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('views_ui_edit_form_submit_duplicate_display', 'views_ui_edit_form_submit_delay_destination'),
+ '#prefix' => '<' . $itemElement . ' class="duplicate">',
+ "#suffix" => '</' . $itemElement . '>',
+ );
+ }
+ if ($isDeletable) {
+ $build['top']['actions']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('delete @display_title', array('@display_title' => $display_title)),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('views_ui_edit_form_submit_delete_display', 'views_ui_edit_form_submit_delay_destination'),
+ '#prefix' => '<' . $itemElement . ' class="delete">',
+ "#suffix" => '</' . $itemElement . '>',
+ );
+ }
+ if ($isEnabled) {
+ $build['top']['actions']['disable'] = array(
+ '#type' => 'submit',
+ '#value' => t('disable @display_title', array('@display_title' => $display_title)),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('views_ui_edit_form_submit_disable_display', 'views_ui_edit_form_submit_delay_destination'),
+ '#prefix' => '<' . $itemElement . ' class="disable">',
+ "#suffix" => '</' . $itemElement . '>',
+ );
+ }
+ }
+ else {
+ $build['top']['actions']['undo_delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('undo delete of @display_title', array('@display_title' => $display_title)),
+ '#limit_validation_errors' => array(),
+ '#submit' => array('views_ui_edit_form_submit_undo_delete_display', 'views_ui_edit_form_submit_delay_destination'),
+ '#prefix' => '<' . $itemElement . ' class="undo-delete">',
+ "#suffix" => '</' . $itemElement . '>',
+ );
+ }
+
+ // The area above the three columns.
+ $build['top']['display_title'] = array(
+ '#theme' => 'views_ui_display_tab_setting',
+ '#description' => t('Display name'),
+ '#link' => $display->handler->option_link(check_plain($display_title), 'display_title'),
+ );
+ }
+
+ $build['columns'] = array();
+ $build['columns']['#theme_wrappers'] = array('container');
+ $build['columns']['#attributes'] = array('id' => 'edit-display-settings-main', 'class' => array('clearfix', 'views-display-columns'),);
+
+ $build['columns']['first']['#theme_wrappers'] = array('container');
+ $build['columns']['first']['#attributes'] = array('class' => array('views-display-column', 'first'));
+
+ $build['columns']['second']['#theme_wrappers'] = array('container');
+ $build['columns']['second']['#attributes'] = array('class' => array('views-display-column', 'second'));
+
+ $build['columns']['second']['settings'] = array();
+ $build['columns']['second']['header'] = array();
+ $build['columns']['second']['footer'] = array();
+ $build['columns']['second']['pager'] = array();
+
+ // The third column buckets are wrapped in a CTools collapsible div
+ $build['columns']['third']['#theme_wrappers'] = array('container');
+ $build['columns']['third']['#attributes'] = array('class' => array('views-display-column', 'third', 'ctools-collapsible-container', 'ctools-collapsible-remember'));
+ // Specify an id that won't change after AJAX requests, so ctools can keep
+ // track of the user's preferred collapsible state. Use the same id across
+ // different displays of the same view, so changing displays doesn't
+ // recollapse the column.
+ $build['columns']['third']['#attributes']['id'] = 'views-ui-advanced-column-' . $view->name;
+ // Collapse the div by default.
+ if (!variable_get('views_ui_show_advanced_column', FALSE)) {
+ $build['columns']['third']['#attributes']['class'][] = 'ctools-collapsed';
+ }
+ $build['columns']['third']['advanced'] = array('#markup' => '<h3 class="ctools-collapsible-handle"><a href="">' . t('Advanced') . '</a></h3>',);
+ $build['columns']['third']['collapse']['#theme_wrappers'] = array('container');
+ $build['columns']['third']['collapse']['#attributes'] = array('class' => array('ctools-collapsible-content',),);
+
+ // Each option (e.g. title, access, display as grid/table/list) fits into one
+ // of several "buckets," or boxes (Format, Fields, Sort, and so on).
+ $buckets = array();
+
+ // Fetch options from the display plugin, with a list of buckets they go into.
+ $options = array();
+ $display->handler->options_summary($buckets, $options);
+
+ // Place each option into its bucket.
+ foreach ($options as $id => $option) {
+ // Each option self-identifies as belonging in a particular bucket.
+ $buckets[$option['category']]['build'][$id] = views_ui_edit_form_get_build_from_option($id, $option, $view, $display);
+ }
+
+ // Place each bucket into the proper column.
+ foreach ($buckets as $id => $bucket) {
+ // Let buckets identify themselves as belonging in a column.
+ if (isset($bucket['column']) && isset($build['columns'][$bucket['column']])) {
+ $column = $bucket['column'];
+ }
+ // If a bucket doesn't pick one of our predefined columns to belong to, put
+ // it in the last one.
+ else {
+ $column = 'third';
+ }
+ if (isset($bucket['build']) && is_array($bucket['build'])) {
+ // The third column is a CTools collapsible div, so
+ // the structure of the form is a little different for this column
+ if ($column === 'third') {
+ $build['columns'][$column]['collapse'][$id] = $bucket['build'];
+ $build['columns'][$column]['collapse'][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
+ $build['columns'][$column]['collapse'][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
+ $build['columns'][$column]['collapse'][$id]['#name'] = !empty($bucket['title']) ? $bucket['title'] : $id;
+ }
+ else {
+ $build['columns'][$column][$id] = $bucket['build'];
+ $build['columns'][$column][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
+ $build['columns'][$column][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
+ $build['columns'][$column][$id]['#name'] = !empty($bucket['title']) ? $bucket['title'] : $id;
+ }
+ }
+ }
+
+ $build['columns']['first']['fields'] = views_ui_edit_form_get_bucket('field', $view, $display);
+ $build['columns']['first']['filters'] = views_ui_edit_form_get_bucket('filter', $view, $display);
+ $build['columns']['first']['sorts'] = views_ui_edit_form_get_bucket('sort', $view, $display);
+ $build['columns']['second']['header'] = views_ui_edit_form_get_bucket('header', $view, $display);
+ $build['columns']['second']['footer'] = views_ui_edit_form_get_bucket('footer', $view, $display);
+ $build['columns']['third']['collapse']['arguments'] = views_ui_edit_form_get_bucket('argument', $view, $display);
+ $build['columns']['third']['collapse']['relationships'] = views_ui_edit_form_get_bucket('relationship', $view, $display);
+ $build['columns']['third']['collapse']['empty'] = views_ui_edit_form_get_bucket('empty', $view, $display);
+
+ return $build;
+}
+
+/**
+ * Build a renderable array representing one option on the edit form.
+ *
+ * This function might be more logical as a method on an object, if a suitable
+ * object emerges out of refactoring.
+ */
+function views_ui_edit_form_get_build_from_option($id, $option, $view, $display) {
+ $option_build = array();
+ $option_build['#theme'] = 'views_ui_display_tab_setting';
+
+ $option_build['#description'] = $option['title'];
+
+ $option_build['#link'] = $display->handler->option_link($option['value'], $id, '', empty($option['desc']) ? '' : $option['desc']);
+
+ $option_build['#links'] = array();
+ if (!empty($option['links']) && is_array($option['links'])) {
+ foreach ($option['links'] as $link_id => $link_value) {
+ $option_build['#settings_links'][] = $display->handler->option_link($option['setting'], $link_id, 'views-button-configure', $link_value);
+ }
+ }
+
+ if (!empty($display->handler->options['defaults'][$id])) {
+ $display_id = 'default';
+ $option_build['#defaulted'] = TRUE;
+ }
+ else {
+ $display_id = $display->id;
+ if (!$display->handler->is_default_display()) {
+ if ($display->handler->defaultable_sections($id)) {
+ $option_build['#overridden'] = TRUE;
+ }
+ }
+ }
+ $option_build['#attributes']['class'][] = drupal_clean_css_identifier($display_id . '-' . $id);
+ if (!empty($view->changed_sections[$display_id . '-' . $id])) {
+ $option_build['#changed'] = TRUE;
+ }
+ return $option_build;
+}
+
+function template_preprocess_views_ui_display_tab_setting(&$variables) {
+ static $zebra = 0;
+ $variables['zebra'] = ($zebra % 2 === 0 ? 'odd' : 'even');
+ $zebra++;
+
+ // Put the main link to the left side
+ array_unshift($variables['settings_links'], $variables['link']);
+ $variables['settings_links'] = implode('<span class="label">&nbsp;|&nbsp;</span>', $variables['settings_links']);
+
+ // Add classes associated with this display tab to the overall list.
+ $variables['classes_array'] = array_merge($variables['classes_array'], $variables['class']);
+
+ if (!empty($variables['defaulted'])) {
+ $variables['classes_array'][] = 'defaulted';
+ }
+ if (!empty($variables['overridden'])) {
+ $variables['classes_array'][] = 'overridden';
+ $variables['attributes_array']['title'][] = t('Overridden');
+ }
+
+ // Append a colon to the description, if requested.
+ if ($variables['description'] && $variables['description_separator']) {
+ $variables['description'] .= t(':');
+ }
+}
+
+function template_preprocess_views_ui_display_tab_bucket(&$variables) {
+ $element = $variables['element'];
+
+ $variables['item_help_icon'] = '';
+ if (!empty($element['#item_help_icon'])) {
+ $variables['item_help_icon'] = render($element['#item_help_icon']);
+ }
+ if (!empty($element['#name'])) {
+ $variables['classes_array'][] = drupal_clean_css_identifier(strtolower($element['#name']));
+ }
+ if (!empty($element['#overridden'])) {
+ $variables['classes_array'][] = 'overridden';
+ $variables['attributes_array']['title'][] = t('Overridden');
+ }
+
+ $variables['content'] = $element['#children'];
+ $variables['title'] = $element['#title'];
+ $variables['actions'] = !empty($element['#actions']) ? $element['#actions'] : '';
+}
+
+function template_preprocess_views_ui_display_tab_column(&$variables) {
+ $element = $variables['element'];
+
+ $variables['content'] = $element['#children'];
+ $variables['column'] = $element['#column'];
+}
+
+/**
+ * Move form elements into fieldsets for presentation purposes.
+ *
+ * Many views forms use #tree = TRUE to keep their values in a hierarchy for
+ * easier storage. Moving the form elements into fieldsets during form building
+ * would break up that hierarchy. Therefore, we wait until the pre_render stage,
+ * where any changes we make affect presentation only and aren't reflected in
+ * $form_state['values'].
+ */
+function views_ui_pre_render_add_fieldset_markup($form) {
+ foreach (element_children($form) as $key) {
+ $element = $form[$key];
+ // In our form builder functions, we added an arbitrary #fieldset property
+ // to any element that belongs in a fieldset. If this form element has that
+ // property, move it into its fieldset.
+ if (isset($element['#fieldset']) && isset($form[$element['#fieldset']])) {
+ $form[$element['#fieldset']][$key] = $element;
+ // Remove the original element this duplicates.
+ unset($form[$key]);
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Flattens the structure of an element containing the #flatten property.
+ *
+ * If a form element has #flatten = TRUE, then all of it's children
+ * get moved to the same level as the element itself.
+ * So $form['to_be_flattened'][$key] becomes $form[$key], and
+ * $form['to_be_flattened'] gets unset.
+ */
+function views_ui_pre_render_flatten_data($form) {
+ foreach (element_children($form) as $key) {
+ $element = $form[$key];
+ if (!empty($element['#flatten'])) {
+ foreach (element_children($element) as $child_key) {
+ $form[$child_key] = $form[$key][$child_key];
+ }
+ // All done, remove the now-empty parent.
+ unset($form[$key]);
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Moves argument options into their place.
+ *
+ * When configuring the default argument behavior, almost each of the radio
+ * buttons has its own fieldset shown bellow it when the radio button is
+ * clicked. That fieldset is created through a custom form process callback.
+ * Each element that has #argument_option defined and pointing to a default
+ * behavior gets moved to the appropriate fieldset.
+ * So if #argument_option is specified as 'default', the element is moved
+ * to the 'default_options' fieldset.
+ */
+function views_ui_pre_render_move_argument_options($form) {
+ foreach (element_children($form) as $key) {
+ $element = $form[$key];
+ if (!empty($element['#argument_option'])) {
+ $container_name = $element['#argument_option'] . '_options';
+ if (isset($form['no_argument']['default_action'][$container_name])) {
+ $form['no_argument']['default_action'][$container_name][$key] = $element;
+ }
+ // Remove the original element this duplicates.
+ unset($form[$key]);
+ }
+ }
+ return $form;
+}
+
+/**
+ * Custom form radios process function.
+ *
+ * Roll out a single radios element to a list of radios,
+ * using the options array as index.
+ * While doing that, create a container element underneath each option, which
+ * contains the settings related to that option.
+ *
+ * @see form_process_radios()
+ */
+function views_ui_process_container_radios($element) {
+ if (count($element['#options']) > 0) {
+ foreach ($element['#options'] as $key => $choice) {
+ $element += array($key => array());
+ // Generate the parents as the autogenerator does, so we will have a
+ // unique id for each radio button.
+ $parents_for_id = array_merge($element['#parents'], array($key));
+
+ $element[$key] += array(
+ '#type' => 'radio',
+ '#title' => $choice,
+ // The key is sanitized in drupal_attributes() during output from the
+ // theme function.
+ '#return_value' => $key,
+ '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
+ '#attributes' => $element['#attributes'],
+ '#parents' => $element['#parents'],
+ '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
+ '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+ );
+ $element[$key . '_options'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('views-admin-dependent')),
+ );
+ }
+ }
+ return $element;
+}
+
+/**
+ * Import a view from cut & paste.
+ */
+function views_ui_import_page($form, &$form_state) {
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('View name'),
+ '#description' => t('Enter the name to use for this view if it is different from the source view. Leave blank to use the name of the view.'),
+ );
+
+ $form['name_override'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Replace an existing view if one exists with the same name'),
+ );
+
+ $form['bypass_validation'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Bypass view validation'),
+ '#description' => t('Bypass the validation of plugins and handlers when importing this view.'),
+ );
+
+ $form['view'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Paste view code here'),
+ '#required' => TRUE,
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import'),
+ '#submit' => array('views_ui_import_submit'),
+ '#validate' => array('views_ui_import_validate'),
+ );
+ return $form;
+}
+
+/**
+ * Validate handler to import a view.
+ */
+function views_ui_import_validate($form, &$form_state) {
+ $view = '';
+ views_include('view');
+ // Be forgiving if someone pastes views code that starts with '<?php'.
+ if (substr($form_state['values']['view'], 0, 5) == '<?php') {
+ $form_state['values']['view'] = substr($form_state['values']['view'], 5);
+ }
+ ob_start();
+ eval($form_state['values']['view']);
+ ob_end_clean();
+
+ if (!is_object($view)) {
+ return form_error($form['view'], t('Unable to interpret view code.'));
+ }
+
+ if (empty($view->api_version) || $view->api_version < 2) {
+ form_error($form['view'], t('That view is not compatible with this version of Views.
+ If you have a view from views1 you have to go to a drupal6 installation and import it there.'));
+ }
+ elseif (version_compare($view->api_version, views_api_version(), '>')) {
+ form_error($form['view'], t('That view is created for the version @import_version of views, but you only have @api_version', array(
+ '@import_version' => $view->api_version,
+ '@api_version' => views_api_version())));
+ }
+
+ // View name must be alphanumeric or underscores, no other punctuation.
+ if (!empty($form_state['values']['name']) && preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
+ form_error($form['name'], t('View name must be alphanumeric or underscores only.'));
+ }
+
+ if ($form_state['values']['name']) {
+ $view->name = $form_state['values']['name'];
+ }
+
+ $test = views_get_view($view->name);
+ if (!$form_state['values']['name_override']) {
+ if ($test && $test->type != t('Default')) {
+ form_set_error('', t('A view by that name already exists; please choose a different name'));
+ }
+ }
+ else {
+ if ($test->vid) {
+ $view->vid = $test->vid;
+ }
+ }
+
+ // Make sure base table gets set properly if it got moved.
+ $view->update();
+
+ $view->init_display();
+
+ $broken = FALSE;
+
+ // Bypass the validation of view pluigns/handlers if option is checked.
+ if (!$form_state['values']['bypass_validation']) {
+ // Make sure that all plugins and handlers needed by this view actually exist.
+ foreach ($view->display as $id => $display) {
+ if (empty($display->handler) || !empty($display->handler->broken)) {
+ drupal_set_message(t('Display plugin @plugin is not available.', array('@plugin' => $display->display_plugin)), 'error');
+ $broken = TRUE;
+ continue;
+ }
+
+ $plugin = views_get_plugin('style', $display->handler->get_option('style_plugin'));
+ if (!$plugin) {
+ drupal_set_message(t('Style plugin @plugin is not available.', array('@plugin' => $display->handler->get_option('style_plugin'))), 'error');
+ $broken = TRUE;
+ }
+ elseif ($plugin->uses_row_plugin()) {
+ $plugin = views_get_plugin('row', $display->handler->get_option('row_plugin'));
+ if (!$plugin) {
+ drupal_set_message(t('Row plugin @plugin is not available.', array('@plugin' => $display->handler->get_option('row_plugin'))), 'error');
+ $broken = TRUE;
+ }
+ }
+
+ foreach (views_object_types() as $type => $info) {
+ $handlers = $display->handler->get_handlers($type);
+ if ($handlers) {
+ foreach ($handlers as $id => $handler) {
+ if ($handler->broken()) {
+ drupal_set_message(t('@type handler @table.@field is not available.', array(
+ '@type' => $info['stitle'],
+ '@table' => $handler->table,
+ '@field' => $handler->field,
+ )), 'error');
+ $broken = TRUE;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ($broken) {
+ form_set_error('', t('Unable to import view.'));
+ }
+
+ $form_state['view'] = &$view;
+}
+
+/**
+ * Submit handler for view import.
+ */
+function views_ui_import_submit($form, &$form_state) {
+ // Store in cache and then go to edit.
+ views_ui_cache_set($form_state['view']);
+ $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
+}
+
+/**
+ * Validate that a view is complete and whole.
+ */
+function views_ui_edit_view_form_validate($form, &$form_state) {
+ // Do not validate cancel or delete or revert.
+ if (empty($form_state['clicked_button']['#value']) || $form_state['clicked_button']['#value'] != t('Save')) {
+ return;
+ }
+
+ $errors = $form_state['view']->validate();
+ if ($errors !== TRUE) {
+ foreach ($errors as $error) {
+ form_set_error('', $error);
+ }
+ }
+}
+
+/**
+ * Submit handler for the edit view form.
+ */
+function views_ui_edit_view_form_submit($form, &$form_state) {
+ // Go through and remove displayed scheduled for removal.
+ foreach ($form_state['view']->display as $id => $display) {
+ if (!empty($display->deleted)) {
+ unset($form_state['view']->display[$id]);
+ }
+ }
+ // Rename display ids if needed.
+ foreach ($form_state['view']->display as $id => $display) {
+ if (!empty($display->new_id)) {
+ $form_state['view']->display[$id]->id = $display->new_id;
+ // Redirect the user to the renamed display to be sure that the page itself exists and doesn't throw errors.
+ $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $display->new_id;
+ }
+ }
+
+ // Direct the user to the right url, if the path of the display has changed.
+ if (!empty($_GET['destination'])) {
+ $destination = $_GET['destination'];
+ // Find out the first display which has a changed path and redirect to this url.
+ $old_view = views_get_view($form_state['view']->name);
+ foreach ($old_view->display as $id => $display) {
+ // Only check for displays with a path.
+ if (!isset($display->display_options['path'])) {
+ continue;
+ }
+ $old_path = $display->display_options['path'];
+ if (($display->display_plugin == 'page') && ($old_path == $destination) && ($old_path != $form_state['view']->display[$id]->display_options['path'])) {
+ $destination = $form_state['view']->display[$id]->display_options['path'];
+ unset($_GET['destination']);
+ }
+ }
+ $form_state['redirect'] = $destination;
+ }
+
+ $form_state['view']->save();
+ drupal_set_message(t('The view %name has been saved.', array('%name' => $form_state['view']->get_human_name())));
+
+ // Remove this view from cache so we can edit it properly.
+ ctools_object_cache_clear('view', $form_state['view']->name);
+}
+
+/**
+ * Submit handler for the edit view form.
+ */
+function views_ui_edit_view_form_cancel($form, &$form_state) {
+ // Remove this view from cache so edits will be lost.
+ ctools_object_cache_clear('view', $form_state['view']->name);
+ if (empty($form['view']->vid)) {
+ // I seem to have to drupal_goto here because I can't get fapi to
+ // honor the redirect target. Not sure what I screwed up here.
+ drupal_goto('admin/structure/views');
+ }
+}
+
+function views_ui_edit_view_form_delete($form, &$form_state) {
+ unset($_REQUEST['destination']);
+ // Redirect to the delete confirm page
+ $form_state['redirect'] = array('admin/structure/views/view/' . $form_state['view']->name . '/delete', array('query' => drupal_get_destination() + array('cancel' => 'admin/structure/views/view/' . $form_state['view']->name . '/edit')));
+}
+
+/**
+ * Add information about a section to a display.
+ */
+function views_ui_edit_form_get_bucket($type, $view, $display) {
+ $build = array(
+ '#theme_wrappers' => array('views_ui_display_tab_bucket'),
+ );
+ $types = views_object_types();
+
+ $build['#overridden'] = FALSE;
+ $build['#defaulted'] = FALSE;
+ if (module_exists('advanced_help')) {
+ $build['#item_help_icon'] = array(
+ '#theme' => 'advanced_help_topic',
+ '#module' => 'views',
+ '#topic' => $type,
+ );
+ }
+
+ $build['#name'] = $build['#title'] = $types[$type]['title'];
+
+ // Different types now have different rearrange forms, so we use this switch
+ // to get the right one.
+ switch ($type) {
+ case 'filter':
+ $rearrange_url = "admin/structure/views/nojs/rearrange-$type/$view->name/$display->id/$type";
+ $rearrange_text = t('And/Or, Rearrange');
+ // TODO: Add another class to have another symbol for filter rearrange.
+ $class = 'icon compact rearrange';
+ break;
+ case 'field':
+ // Fetch the style plugin info so we know whether to list fields or not.
+ $style_plugin = $display->handler->get_plugin();
+ $uses_fields = $style_plugin && $style_plugin->uses_fields();
+ if (!$uses_fields) {
+ $build['fields'][] = array(
+ '#markup' => t('The selected style or row format does not utilize fields.'),
+ '#theme_wrappers' => array('views_container'),
+ '#attributes' => array('class' => array('views-display-setting')),
+ );
+ return $build;
+ }
+
+ default:
+ $rearrange_url = "admin/structure/views/nojs/rearrange/$view->name/$display->id/$type";
+ $rearrange_text = t('Rearrange');
+ $class = 'icon compact rearrange';
+ }
+
+ // Create an array of actions to pass to theme_links
+ $actions = array();
+ $count_handlers = count($display->handler->get_handlers($type));
+ $actions['add'] = array(
+ 'title' => t('Add'),
+ 'href' => "admin/structure/views/nojs/add-item/$view->name/$display->id/$type",
+ 'attributes'=> array('class' => array('icon compact add', 'views-ajax-link'), 'title' => t('Add'), 'id' => 'views-add-' . $type),
+ 'html' => TRUE,
+ );
+ if ($count_handlers > 0) {
+ $actions['rearrange'] = array(
+ 'title' => $rearrange_text,
+ 'href' => $rearrange_url,
+ 'attributes' => array('class' => array($class, 'views-ajax-link'), 'title' => t('Rearrange'), 'id' => 'views-rearrange-' . $type),
+ 'html' => TRUE,
+ );
+ }
+
+ // Render the array of links
+ $build['#actions'] = theme('links__ctools_dropbutton',
+ array(
+ 'links' => $actions,
+ 'attributes' => array(
+ 'class' => array('inline', 'links', 'actions', 'horizontal', 'right')
+ ),
+ 'class' => array('views-ui-settings-bucket-operations'),
+ )
+ );
+
+ if (!$display->handler->is_default_display()) {
+ if (!$display->handler->is_defaulted($types[$type]['plural'])) {
+ $build['#overridden'] = TRUE;
+ }
+ else {
+ $build['#defaulted'] = TRUE;
+ }
+ }
+
+ // If there's an options form for the bucket, link to it.
+ if (!empty($types[$type]['options'])) {
+ $build['#title'] = l($build['#title'], "admin/structure/views/nojs/config-type/$view->name/$display->id/$type", array('attributes' => array('class' => array('views-ajax-link'), 'id' => 'views-title-' . $type)));
+ }
+
+ static $relationships = NULL;
+ if (!isset($relationships)) {
+ // Get relationship labels
+ $relationships = array();
+ // @todo: get_handlers()
+ $handlers = $display->handler->get_option('relationships');
+ if ($handlers) {
+ foreach ($handlers as $id => $info) {
+ $handler = $display->handler->get_handler('relationship', $id);
+ $relationships[$id] = $handler->label();
+ }
+ }
+ }
+
+ // Filters can now be grouped so we do a little bit extra:
+ $groups = array();
+ $grouping = FALSE;
+ if ($type == 'filter') {
+ $group_info = $view->display_handler->get_option('filter_groups');
+ // If there is only one group but it is using the "OR" filter, we still
+ // treat it as a group for display purposes, since we want to display the
+ // "OR" label next to items within the group.
+ if (!empty($group_info['groups']) && (count($group_info['groups']) > 1 || current($group_info['groups']) == 'OR')) {
+ $grouping = TRUE;
+ $groups = array(0 => array());
+ }
+ }
+
+ $build['fields'] = array();
+
+ foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
+ // Build the option link for this handler ("Node: ID = article").
+ $build['fields'][$id] = array();
+ $build['fields'][$id]['#theme'] = 'views_ui_display_tab_setting';
+
+ $handler = $display->handler->get_handler($type, $id);
+ if (empty($handler)) {
+ $build['fields'][$id]['#class'][] = 'broken';
+ $field_name = t('Broken/missing handler: @table > @field', array('@table' => $field['table'], '@field' => $field['field']));
+ $build['fields'][$id]['#link'] = l($field_name, "admin/structure/views/nojs/config-item/$view->name/$display->id/$type/$id", array('attributes' => array('class' => array('views-ajax-link')), 'html' => TRUE));
+ continue;
+ }
+
+ $field_name = check_plain($handler->ui_name(TRUE));
+ if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
+ $field_name = '(' . $relationships[$field['relationship']] . ') ' . $field_name;
+ }
+
+ $description = filter_xss_admin($handler->admin_summary());
+ $link_text = $field_name . (empty($description) ? '' : " ($description)");
+ $link_attributes = array('class' => array('views-ajax-link'));
+ if (!empty($field['exclude'])) {
+ $link_attributes['class'][] = 'views-field-excluded';
+ }
+ $build['fields'][$id]['#link'] = l($link_text, "admin/structure/views/nojs/config-item/$view->name/$display->id/$type/$id", array('attributes' => $link_attributes, 'html' => TRUE));
+ $build['fields'][$id]['#class'][] = drupal_clean_css_identifier($display->id . '-' . $type . '-' . $id);
+ if (!empty($view->changed_sections[$display->id . '-' . $type . '-' . $id])) {
+ // @TODO: #changed is no longer being used?
+ $build['fields'][$id]['#changed'] = TRUE;
+ }
+
+ if ($display->handler->use_group_by() && $handler->use_group_by()) {
+ $build['fields'][$id]['#settings_links'][] = l('<span class="label">' . t('Aggregation settings') . '</span>', "admin/structure/views/nojs/config-item-group/$view->name/$display->id/$type/$id", array('attributes' => array('class' => 'views-button-configure views-ajax-link', 'title' => t('Aggregation settings')), 'html' => true));
+ }
+
+ if ($handler->has_extra_options()) {
+ $build['fields'][$id]['#settings_links'][] = l('<span class="label">' . t('Settings') . '</span>', "admin/structure/views/nojs/config-item-extra/$view->name/$display->id/$type/$id", array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => t('Settings')), 'html' => true));
+ }
+
+ if ($grouping) {
+ $gid = $handler->options['group'];
+
+ // Show in default group if the group does not exist.
+ if (empty($group_info['groups'][$gid])) {
+ $gid = 0;
+ }
+ $groups[$gid][] = $id;
+ }
+ }
+
+ // If using grouping, re-order fields so that they show up properly in the list.
+ if ($type == 'filter' && $grouping) {
+ $store = $build['fields'];
+ $build['fields'] = array();
+ foreach ($groups as $gid => $contents) {
+ // Display an operator between each group.
+ if (!empty($build['fields'])) {
+ $build['fields'][] = array(
+ '#theme' => 'views_ui_display_tab_setting',
+ '#class' => array('views-group-text'),
+ '#link' => ($group_info['operator'] == 'OR' ? t('OR') : t('AND')),
+ );
+ }
+ // Display an operator between each pair of filters within the group.
+ $keys = array_keys($contents);
+ $last = end($keys);
+ foreach ($contents as $key => $pid) {
+ if ($key != $last) {
+ $store[$pid]['#link'] .= '&nbsp;&nbsp;' . ($group_info['groups'][$gid] == 'OR' ? t('OR') : t('AND'));
+ }
+ $build['fields'][$pid] = $store[$pid];
+ }
+ }
+ }
+
+ return $build;
+}
+
+/**
+ * Regenerate the current tab for AJAX updates.
+ */
+function views_ui_regenerate_tab(&$view, &$output, $display_id) {
+ if (!$view->set_display('default')) {
+ return;
+ }
+
+ // Regenerate the main display area.
+ $build = views_ui_get_display_tab($view, $display_id);
+ views_ui_add_microweights($build);
+ $output[] = ajax_command_html('#views-tab-' . $display_id, drupal_render($build));
+
+ // Regenerate the top area so changes to display names and order will appear.
+ $build = views_ui_render_display_top($view, $display_id);
+ views_ui_add_microweights($build);
+ $output[] = ajax_command_replace('#views-display-top', drupal_render($build));
+}
+
+/**
+ * Recursively adds microweights to a render array, similar to what form_builder() does for forms.
+ *
+ * @todo Submit a core patch to fix drupal_render() to do this, so that all
+ * render arrays automatically preserve array insertion order, as forms do.
+ */
+function views_ui_add_microweights(&$build) {
+ $count = 0;
+ foreach (element_children($build) as $key) {
+ if (!isset($build[$key]['#weight'])) {
+ $build[$key]['#weight'] = $count/1000;
+ }
+ views_ui_add_microweights($build[$key]);
+ $count++;
+ }
+}
+
+/**
+ * Provide a standard set of Apply/Cancel/OK buttons for the forms. Also provide
+ * a hidden op operator because the forms plugin doesn't seem to properly
+ * provide which button was clicked.
+ *
+ * TODO: Is the hidden op operator still here somewhere, or is that part of the
+ * docblock outdated?
+ */
+function views_ui_standard_form_buttons(&$form, &$form_state, $form_id, $name = NULL, $third = NULL, $submit = NULL) {
+ $form['buttons'] = array(
+ '#prefix' => '<div class="clearfix"><div class="form-buttons">',
+ '#suffix' => '</div></div>',
+ );
+
+ if (empty($name)) {
+ $name = t('Apply');
+ $view = $form_state['view'];
+ if (!empty($view->stack) && count($view->stack) > 1) {
+ $name = t('Apply and continue');
+ }
+ $names = array(t('Apply'), t('Apply and continue'));
+ }
+
+ // Forms that are purely informational set an ok_button flag, so we know not
+ // to create an "Apply" button for them.
+ if (empty($form_state['ok_button'])) {
+ $form['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => $name,
+ // The regular submit handler ($form_id . '_submit') does not apply if
+ // we're updating the default display. It does apply if we're updating
+ // the current display. Since we have no way of knowing at this point
+ // which display the user wants to update, views_ui_standard_submit will
+ // take care of running the regular submit handler as appropriate.
+ '#submit' => array('views_ui_standard_submit'),
+ );
+ // Form API button click detection requires the button's #value to be the
+ // same between the form build of the initial page request, and the initial
+ // form build of the request processing the form submission. Ideally, the
+ // button's #value shouldn't change until the form rebuild step. However,
+ // views_ui_ajax_form() implements a different multistep form workflow than
+ // the Form API does, and adjusts $view->stack prior to form processing, so
+ // we compensate by extending button click detection code to support any of
+ // the possible button labels.
+ if (isset($names)) {
+ $form['buttons']['submit']['#values'] = $names;
+ $form['buttons']['submit']['#process'] = array_merge(array('views_ui_form_button_was_clicked'), element_info_property($form['buttons']['submit']['#type'], '#process', array()));
+ }
+ // If a validation handler exists for the form, assign it to this button.
+ if (function_exists($form_id . '_validate')) {
+ $form['buttons']['submit']['#validate'][] = $form_id . '_validate';
+ }
+ }
+
+ // Create a "Cancel" button. For purely informational forms, label it "OK".
+ $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : 'views_ui_standard_cancel';
+ $form['buttons']['cancel'] = array(
+ '#type' => 'submit',
+ '#value' => empty($form_state['ok_button']) ? t('Cancel') : t('Ok'),
+ '#submit' => array($cancel_submit),
+ '#validate' => array(),
+ );
+
+ // Some forms specify a third button, with a name and submit handler.
+ if ($third) {
+ if (empty($submit)) {
+ $submit = 'third';
+ }
+ $third_submit = function_exists($form_id . '_' . $submit) ? $form_id . '_' . $submit : 'views_ui_standard_cancel';
+
+ $form['buttons'][$submit] = array(
+ '#type' => 'submit',
+ '#value' => $third,
+ '#validate' => array(),
+ '#submit' => array($third_submit),
+ );
+ }
+
+ // Compatibility, to be removed later: // TODO: When is "later"?
+ // We used to set these items on the form, but now we want them on the $form_state:
+ if (isset($form['#title'])) {
+ $form_state['title'] = $form['#title'];
+ }
+ if (isset($form['#help_topic'])) {
+ $form_state['help_topic'] = $form['#help_topic'];
+ }
+ if (isset($form['#help_module'])) {
+ $form_state['help_module'] = $form['#help_module'];
+ }
+ if (isset($form['#url'])) {
+ $form_state['url'] = $form['#url'];
+ }
+ if (isset($form['#section'])) {
+ $form_state['#section'] = $form['#section'];
+ }
+ // Finally, we never want these cached -- our object cache does that for us.
+ $form['#no_cache'] = TRUE;
+
+ // If this isn't an ajaxy form, then we want to set the title.
+ if (!empty($form['#title'])) {
+ drupal_set_title($form['#title']);
+ }
+}
+
+/**
+ * Basic submit handler applicable to all 'standard' forms.
+ *
+ * This submit handler determines whether the user wants the submitted changes
+ * to apply to the default display or to the current display, and dispatches
+ * control appropriately.
+ */
+function views_ui_standard_submit($form, &$form_state) {
+ // Determine whether the values the user entered are intended to apply to
+ // the current display or the default display.
+
+ list($was_defaulted, $is_defaulted, $revert) = views_ui_standard_override_values($form, $form_state);
+
+ // Mark the changed section of the view as changed.
+ // TODO: Document why we are doing this, and see if we still need it.
+ if (!empty($form['#section'])) {
+ $form_state['view']->changed_sections[$form['#section']] = TRUE;
+ }
+
+ // Based on the user's choice in the display dropdown, determine which display
+ // these changes apply to.
+ if ($revert) {
+ // If it's revert just change the override and return.
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ $display->handler->options_override($form, $form_state);
+
+ // Don't execute the normal submit handling but still store the changed view into cache.
+ views_ui_cache_set($form_state['view']);
+ return;
+ }
+ elseif ($was_defaulted === $is_defaulted) {
+ // We're not changing which display these form values apply to.
+ // Run the regular submit handler for this form.
+ }
+ elseif ($was_defaulted && !$is_defaulted) {
+ // We were using the default display's values, but we're now overriding
+ // the default display and saving values specific to this display.
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ // options_override toggles the override of this section.
+ $display->handler->options_override($form, $form_state);
+ $display->handler->options_submit($form, $form_state);
+ }
+ elseif (!$was_defaulted && $is_defaulted) {
+ // We used to have an override for this display, but the user now wants
+ // to go back to the default display.
+ // Overwrite the default display with the current form values, and make
+ // the current display use the new default values.
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ // options_override toggles the override of this section.
+ $display->handler->options_override($form, $form_state);
+ $display->handler->options_submit($form, $form_state);
+ }
+
+ $submit_handler = $form['#form_id'] . '_submit';
+ if (function_exists($submit_handler)) {
+ $submit_handler($form, $form_state);
+ }
+}
+
+/**
+ * Return the was_defaulted, is_defaulted and revert state of a form.
+ */
+function views_ui_standard_override_values($form, $form_state) {
+ // Make sure the dropdown exists in the first place.
+ if (isset($form_state['values']['override']['dropdown'])) {
+ // #default_value is used to determine whether it was the default value or not.
+ // So the available options are: $display, 'default' and 'default_revert', not 'defaults'.
+ $was_defaulted = ($form['override']['dropdown']['#default_value'] === 'defaults');
+ $is_defaulted = ($form_state['values']['override']['dropdown'] === 'default');
+ $revert = ($form_state['values']['override']['dropdown'] === 'default_revert');
+
+ if ($was_defaulted !== $is_defaulted && isset($form['#section'])) {
+ // We're changing which display these values apply to.
+ // Update the #section so it knows what to mark changed.
+ $form['#section'] = str_replace('default-', $form_state['display_id'] . '-', $form['#section']);
+ }
+ }
+ else {
+ // The user didn't get the dropdown for overriding the default display.
+ $was_defaulted = FALSE;
+ $is_defaulted = FALSE;
+ $revert = FALSE;
+ }
+
+ return array($was_defaulted, $is_defaulted, $revert);
+}
+
+/**
+ * Submit handler for cancel button
+ */
+function views_ui_standard_cancel($form, &$form_state) {
+ if (!empty($form_state['view']->changed) && isset($form_state['view']->form_cache)) {
+ unset($form_state['view']->form_cache);
+ views_ui_cache_set($form_state['view']);
+ }
+
+ $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
+}
+
+/**
+ * Add a <select> dropdown for a given section, allowing the user to
+ * change whether this info is stored on the default display or on
+ * the current display.
+ */
+function views_ui_standard_display_dropdown(&$form, &$form_state, $section) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $displays = $view->display;
+ $current_display = $view->display[$display_id];
+
+ // Add the "2 of 3" progress indicator.
+ // @TODO: Move this to a separate function if it's needed on any forms that
+ // don't have the display dropdown.
+ if ($form_progress = views_ui_get_form_progress($view)) {
+ $form['progress']['#markup'] = '<div id="views-progress-indicator">' . t('@current of @total', array('@current' => $form_progress['current'], '@total' => $form_progress['total'])) . '</div>';
+ $form['progress']['#weight'] = -1001;
+ }
+
+ if ($current_display->handler->is_default_display()) {
+ return;
+ }
+
+ // Determine whether any other displays have overrides for this section.
+ $section_overrides = FALSE;
+ $section_defaulted = $current_display->handler->is_defaulted($section);
+ foreach ($displays as $id => $display) {
+ if ($id === 'default' || $id === $display_id) {
+ continue;
+ }
+ if ($display->handler && !$display->handler->is_defaulted($section)) {
+ $section_overrides = TRUE;
+ }
+ }
+
+ $display_dropdown['default'] = ($section_overrides ? t('All displays (except overridden)') : t('All displays'));
+ $display_dropdown[$display_id] = t('This @display_type (override)', array('@display_type' => $current_display->display_plugin));
+ // Only display the revert option if we are in a overridden section.
+ if (!$section_defaulted) {
+ $display_dropdown['default_revert'] = t('Revert to default');
+ }
+
+ $form['override'] = array(
+ '#prefix' => '<div class="views-override clearfix container-inline">',
+ '#suffix' => '</div>',
+ '#weight' => -1000,
+ '#tree' => TRUE,
+ );
+ $form['override']['dropdown'] = array(
+ '#type' => 'select',
+ '#title' => t('For'), // @TODO: Translators may need more context than this.
+ '#options' => $display_dropdown,
+ );
+ if ($current_display->handler->is_defaulted($section)) {
+ $form['override']['dropdown']['#default_value'] = 'defaults';
+ }
+ else {
+ $form['override']['dropdown']['#default_value'] = $display_id;
+ }
+
+}
+
+/**
+ * Get the user's current progress through the form stack.
+ *
+ * @param $view
+ * The current view.
+ *
+ * @return
+ * FALSE if the user is not currently in a multiple-form stack. Otherwise,
+ * an associative array with the following keys:
+ * - current: The number of the current form on the stack.
+ * - total: The total number of forms originally on the stack.
+ */
+function views_ui_get_form_progress($view) {
+ $progress = FALSE;
+ if (!empty($view->stack)) {
+ $stack = $view->stack;
+ // The forms on the stack have integer keys that don't change as the forms
+ // are completed, so we can see which ones are still left.
+ $keys = array_keys($view->stack);
+ // Add 1 to the array keys for the benefit of humans, who start counting
+ // from 1 and not 0.
+ $current = reset($keys) + 1;
+ $total = end($keys) + 1;
+ if ($total > 1) {
+ $progress = array();
+ $progress['current'] = $current;
+ $progress['total'] = $total;
+ }
+ }
+ return $progress;
+}
+
+
+// --------------------------------------------------------------------------
+// Various subforms for editing the pieces of a view.
+
+function views_ui_ajax_forms($key = NULL) {
+ $forms = array(
+ 'display' => array(
+ 'form_id' => 'views_ui_edit_display_form',
+ 'args' => array('section'),
+ ),
+ 'remove-display' => array(
+ 'form_id' => 'views_ui_remove_display_form',
+ 'args' => array(),
+ ),
+ 'config-type' => array(
+ 'form_id' => 'views_ui_config_type_form',
+ 'args' => array('type'),
+ ),
+ 'rearrange' => array(
+ 'form_id' => 'views_ui_rearrange_form',
+ 'args' => array('type'),
+ ),
+ 'rearrange-filter' => array(
+ 'form_id' => 'views_ui_rearrange_filter_form',
+ 'args' => array('type'),
+ ),
+ 'reorder-displays' => array(
+ 'form_id' => 'views_ui_reorder_displays_form',
+ 'args' => array(),
+ ),
+ 'add-item' => array(
+ 'form_id' => 'views_ui_add_item_form',
+ 'args' => array('type'),
+ ),
+ 'config-item' => array(
+ 'form_id' => 'views_ui_config_item_form',
+ 'args' => array('type', 'id'),
+ ),
+ 'config-item-extra' => array(
+ 'form_id' => 'views_ui_config_item_extra_form',
+ 'args' => array('type', 'id'),
+ ),
+ 'config-item-group' => array(
+ 'form_id' => 'views_ui_config_item_group_form',
+ 'args' => array('type', 'id'),
+ ),
+ 'config-style' => array(
+ 'form_id' => 'views_ui_config_style_form',
+ 'args' => array('type', 'id'),
+ ),
+ 'edit-details' => array(
+ 'form_id' => 'views_ui_edit_details_form',
+ 'args' => array(),
+ ),
+ 'analyze' => array(
+ 'form_id' => 'views_ui_analyze_view_form',
+ 'args' => array(),
+ ),
+ );
+
+ if ($key) {
+ return !empty($forms[$key]) ? $forms[$key] : NULL;
+ }
+
+ return $forms;
+}
+
+/**
+ * Build a form identifier that we can use to see if one form
+ * is the same as another. Since the arguments differ slightly
+ * we do a lot of spiffy concatenation here.
+ */
+function views_ui_build_identifier($key, $view, $display_id, $args) {
+ $form = views_ui_ajax_forms($key);
+ // Automatically remove the single-form cache if it exists and
+ // does not match the key.
+ $identifier = implode('-', array($key, $view->name, $display_id));
+
+ foreach ($form['args'] as $id) {
+ $arg = (!empty($args)) ? array_shift($args) : NULL;
+ $identifier .= '-' . $arg;
+ }
+ return $identifier;
+}
+
+/**
+ * Build up a $form_state object suitable for use with drupal_build_form
+ * based on known information about a form.
+ */
+function views_ui_build_form_state($js, $key, &$view, $display_id, $args) {
+ $form = views_ui_ajax_forms($key);
+ // Build up form state
+ $form_state = array(
+ 'form_key' => $key,
+ 'form_id' => $form['form_id'],
+ 'view' => &$view,
+ 'ajax' => $js,
+ 'display_id' => $display_id,
+ 'no_redirect' => TRUE,
+ );
+
+ foreach ($form['args'] as $id) {
+ $form_state[$id] = (!empty($args)) ? array_shift($args) : NULL;
+ }
+
+ return $form_state;
+}
+
+/**
+ * Create the URL for one of our standard AJAX forms based upon known
+ * information about the form.
+ */
+function views_ui_build_form_url($form_state) {
+ $form = views_ui_ajax_forms($form_state['form_key']);
+ $ajax = empty($form_state['ajax']) ? 'nojs' : 'ajax';
+ $name = $form_state['view']->name;
+ $url = "admin/structure/views/$ajax/$form_state[form_key]/$name/$form_state[display_id]";
+ foreach ($form['args'] as $arg) {
+ $url .= '/' . $form_state[$arg];
+ }
+ return $url;
+}
+
+/**
+ * Add another form to the stack; clicking 'apply' will go to this form
+ * rather than closing the ajax popup.
+ */
+function views_ui_add_form_to_stack($key, &$view, $display_id, $args, $top = FALSE, $rebuild_keys = FALSE) {
+ if (empty($view->stack)) {
+ $view->stack = array();
+ }
+
+ $stack = array(views_ui_build_identifier($key, $view, $display_id, $args), $key, &$view, $display_id, $args);
+ // If we're being asked to add this form to the bottom of the stack, no
+ // special logic is required. Our work is equally easy if we were asked to add
+ // to the top of the stack, but there's nothing in it yet.
+ if (!$top || empty($view->stack)) {
+ $view->stack[] = $stack;
+ }
+ // If we're adding to the top of an existing stack, we have to maintain the
+ // existing integer keys, so they can be used for the "2 of 3" progress
+ // indicator (which will now read "2 of 4").
+ else {
+ $keys = array_keys($view->stack);
+ $first = current($keys);
+ $last = end($keys);
+ for ($i = $last; $i >= $first; $i--) {
+ if (!isset($view->stack[$i])) {
+ continue;
+ }
+ // Move form number $i to the next position in the stack.
+ $view->stack[$i + 1] = $view->stack[$i];
+ unset($view->stack[$i]);
+ }
+ // Now that the previously $first slot is free, move the new form into it.
+ $view->stack[$first] = $stack;
+ ksort($view->stack);
+
+ // Start the keys from 0 again, if requested.
+ if ($rebuild_keys) {
+ $view->stack = array_values($view->stack);
+ }
+ }
+}
+
+/**
+ * Generic entry point to handle forms.
+ *
+ * We do this for consistency and to make it easy to chain forms
+ * together.
+ */
+function views_ui_ajax_form($js, $key, $view, $display_id = '') {
+ // Reset the cache of IDs. Drupal rather aggressively prevents id duplication
+ // but this causes it to remember IDs that are no longer even being used.
+ if (isset($_POST['ajax_html_ids'])) {
+ unset($_POST['ajax_html_ids']);
+ }
+
+ $form = views_ui_ajax_forms($key);
+ if (empty($form)) {
+ return MENU_NOT_FOUND;
+ }
+
+ views_include('ajax');
+ $args = func_get_args();
+ // Remove the known args
+ array_splice($args, 0, 4);
+
+ $form_state = views_ui_build_form_state($js, $key, $view, $display_id, $args);
+ // check to see if this is the top form of the stack. If it is, pop
+ // it off; if it isn't, the user clicked somewhere else and the stack is
+ // now irrelevant.
+ if (!empty($view->stack)) {
+ $identifier = views_ui_build_identifier($key, $view, $display_id, $args);
+ // Retrieve the first form from the stack without changing the integer keys,
+ // as they're being used for the "2 of 3" progress indicator.
+ reset($view->stack);
+ list($key, $top) = each($view->stack);
+ unset($view->stack[$key]);
+
+ if (array_shift($top) != $identifier) {
+ $view->stack = array();
+ }
+ }
+
+ // Automatically remove the form cache if it is set and the key does
+ // not match. This way navigating away from the form without hitting
+ // update will work.
+ if (isset($view->form_cache) && $view->form_cache['key'] != $key) {
+ unset($view->form_cache);
+ }
+
+ // With the below logic, we may end up rendering a form twice (or two forms
+ // each sharing the same element ids), potentially resulting in
+ // drupal_add_js() being called twice to add the same setting. drupal_get_js()
+ // is ok with that, but until ajax_render() is (http://drupal.org/node/208611),
+ // reset the drupal_add_js() static before rendering the second time.
+ $drupal_add_js_original = drupal_add_js();
+ $drupal_add_js = &drupal_static('drupal_add_js');
+ $output = views_ajax_form_wrapper($form_state['form_id'], $form_state);
+ if ($form_state['submitted'] && empty($form_state['rerender'])) {
+ // Sometimes we need to re-generate the form for multi-step type operations.
+ $object = NULL;
+ if (!empty($view->stack)) {
+ $drupal_add_js = $drupal_add_js_original;
+ $stack = $view->stack;
+ $top = array_shift($stack);
+ $top[0] = $js;
+ $form_state = call_user_func_array('views_ui_build_form_state', $top);
+ $form_state['input'] = array();
+ $form_state['url'] = url(views_ui_build_form_url($form_state));
+ if (!$js) {
+ return drupal_goto(views_ui_build_form_url($form_state));
+ }
+ $output = views_ajax_form_wrapper($form_state['form_id'], $form_state);
+ }
+ elseif (!$js) {
+ // if nothing on the stack, non-js forms just go back to the main view editor.
+ return drupal_goto("admin/structure/views/view/$view->name/edit");
+ }
+ else {
+ $output = array();
+ $output[] = views_ajax_command_dismiss_form();
+ $output[] = views_ajax_command_show_buttons(!empty($view->changed));
+ $output[] = views_ajax_command_trigger_preview();
+ if (!empty($form_state['#page_title'])) {
+ $output[] = views_ajax_command_replace_title($form_state['#page_title']);
+ }
+ }
+ // If this form was for view-wide changes, there's no need to regenerate
+ // the display section of the form.
+ if ($display_id !== '') {
+ views_ui_regenerate_tab($view, $output, $display_id);
+ }
+ }
+
+ return $js ? array('#type' => 'ajax', '#commands' => $output) : $output;
+}
+
+/**
+ * Submit handler to add a restore a removed display to a view.
+ */
+function views_ui_remove_display_form_restore($form, &$form_state) {
+ // Create the new display
+ $id = $form_state['display_id'];
+ $form_state['view']->display[$id]->deleted = FALSE;
+
+ // Store in cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Form constructor callback to display analysis information on a view
+ */
+function views_ui_analyze_view_form($form, &$form_state) {
+ $view = &$form_state['view'];
+
+ $form['#title'] = t('View analysis');
+ $form['#section'] = 'analyze';
+
+ views_include('analyze');
+ $messages = views_analyze_view($view);
+
+ $form['analysis'] = array(
+ '#prefix' => '<div class="form-item">',
+ '#suffix' => '</div>',
+ '#markup' => views_analyze_format_result($view, $messages),
+ );
+
+ // Inform the standard button function that we want an OK button.
+ $form_state['ok_button'] = TRUE;
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_analyze_view_form');
+ return $form;
+}
+
+/**
+ * Submit handler for views_ui_analyze_view_form
+ */
+function views_ui_analyze_view_form_submit($form, &$form_state) {
+ $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
+}
+
+/**
+ * Form constructor callback to reorder displays on a view
+ */
+function views_ui_reorder_displays_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+
+ $form['view'] = array('#type' => 'value', '#value' => $view);
+
+ $form['#tree'] = TRUE;
+
+ $last_display = end($view->display);
+
+ foreach ($view->display as $display) {
+ $form[$display->id] = array(
+ 'title' => array('#markup' => check_plain($display->display_title)),
+ 'weight' => array(
+ '#type' => 'weight',
+ '#value' => $display->position,
+ '#delta' => $last_display->position,
+ '#title' => t('Weight for @display', array('@display' => $display->display_title)),
+ '#title_display' => 'invisible',
+ ),
+ '#tree' => TRUE,
+ '#display' => $display,
+ 'removed' => array(
+ '#type' => 'checkbox',
+ '#id' => 'display-removed-' . $display->id,
+ '#attributes' => array('class' => array('views-remove-checkbox')),
+ '#default_value' => isset($display->deleted),
+ ),
+ );
+
+ if (isset($display->deleted) && $display->deleted) {
+ $form[$display->id]['deleted'] = array('#type' => 'value', '#value' => TRUE);
+ }
+ if ($display->id === 'default') {
+ unset($form[$display->id]['weight']);
+ unset($form[$display->id]['removed']);
+ }
+
+ }
+
+ $form['#title'] = t('Displays Reorder');
+ $form['#section'] = 'reorder';
+
+ // Add javascript settings that will be added via $.extend for tabledragging
+ $form['#js']['tableDrag']['reorder-displays']['weight'][0] = array(
+ 'target' => 'weight',
+ 'source' => NULL,
+ 'relationship' => 'sibling',
+ 'action' => 'order',
+ 'hidden' => TRUE,
+ 'limit' => 0,
+ );
+
+ $form['#action'] = url('admin/structure/views/nojs/reorder-displays/' . $view->name . '/' . $display_id);
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_reorder_displays_form');
+
+ return $form;
+}
+
+/**
+ * Display position sorting function
+ */
+function _views_position_sort($display1, $display2) {
+ if ($display1->position != $display2->position) {
+ return $display1->position < $display2->position ? -1 : 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Submit handler for rearranging display form
+ */
+function views_ui_reorder_displays_form_submit($form, &$form_state) {
+ foreach($form_state['input'] as $display => $info) {
+ // add each value that is a field with a weight to our list, but only if
+ // it has had its 'removed' checkbox checked.
+ if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
+ $order[$display] = $info['weight'];
+ }
+ }
+
+ // Sort the order array
+ asort($order);
+
+ // Fixing up positions
+ $position = 2;
+
+ foreach(array_keys($order) as $display) {
+ $order[$display] = $position++;
+ }
+
+ // Setting up position and removing deleted displays
+ $displays = $form_state['view']->display;
+ foreach($displays as $display_id => $display) {
+ // Don't touch the default !!!
+ if ($display_id === 'default') {
+ continue;
+ }
+ if (isset($order[$display_id])) {
+ $form_state['view']->display[$display_id]->position = $order[$display_id];
+ }
+ else {
+ $form_state['view']->display[$display_id]->deleted = TRUE;
+ }
+ }
+
+ // Sorting back the display array as the position is not enough
+ uasort($form_state['view']->display, '_views_position_sort');
+
+ // Store in cache
+ views_ui_cache_set($form_state['view']);
+ $form_state['redirect'] = array('admin/structure/views/view/' . $form_state['view']->name . '/edit', array('fragment' => 'views-tab-default'));
+}
+
+/**
+ * Turn the reorder form into a proper table
+ */
+function theme_views_ui_reorder_displays_form($vars) {
+ $form = $vars['form'];
+ $rows = array();
+ foreach (element_children($form) as $key) {
+ if (isset($form[$key]['#display'])) {
+ $display = &$form[$key];
+
+ $row = array();
+ $row[] = drupal_render($display['title']);
+ $form[$key]['weight']['#attributes']['class'] = array('weight');
+ $row[] = drupal_render($form[$key]['weight']);
+ if (isset($display['removed'])) {
+ $row[] = drupal_render($form[$key]['removed']) .
+ l('<span>' . t('Remove') . '</span>',
+ 'javascript:void()',
+ array(
+ 'attributes' => array(
+ 'id' => 'display-remove-link-' . $key,
+ 'class' => array('views-button-remove display-remove-link'),
+ 'alt' => t('Remove this display'),
+ 'title' => t('Remove this display')),
+ 'html' => TRUE));
+ }
+ else {
+ $row[] = '';
+ }
+ $class = array();
+ $styles = array();
+ if (isset($form[$key]['weight']['#type'])) {
+ $class[] = 'draggable';
+ }
+ if (isset($form[$key]['deleted']['#value']) && $form[$key]['deleted']['#value']) {
+ $styles[] = 'display: none;';
+ }
+ $rows[] = array('data' => $row, 'class' => $class, 'id' => 'display-row-' . $key, 'style' => $styles);
+ }
+ }
+
+ $header = array(t('Display'), t('Weight'), t('Remove'));
+ $output = '';
+ drupal_add_tabledrag('reorder-displays', 'order', 'sibling', 'weight');
+
+ $output = drupal_render($form['override']);
+ $output .= '<div class="scroll">';
+ $output .= theme('table',
+ array('header' => $header,
+ 'rows' => $rows,
+ 'attributes' => array('id' => 'reorder-displays'),
+ ));
+ $output .= '</div>';
+ $output .= drupal_render_children($form);
+
+ return $output;
+}
+
+/**
+ * Form builder to edit details of a view.
+ */
+function views_ui_edit_details_form($form, &$form_state) {
+ $view = &$form_state['view'];
+
+ $form['#title'] = t('View name and description');
+ $form['#section'] = 'details';
+
+ $form['details'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('scroll')),
+ );
+ $form['details']['human_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Human-readable name'),
+ '#description' => t('A descriptive human-readable name for this view. Spaces are allowed'),
+ '#default_value' => $view->get_human_name(),
+ );
+ $form['details']['tag'] = array(
+ '#type' => 'textfield',
+ '#title' => t('View tag'),
+ '#description' => t('Optionally, enter a comma delimited list of tags for this view to use in filtering and sorting views on the administrative page.'),
+ '#default_value' => $view->tag,
+ '#autocomplete_path' => 'admin/views/ajax/autocomplete/tag',
+ );
+ $form['details']['description'] = array(
+ '#type' => 'textfield',
+ '#title' => t('View description'),
+ '#description' => t('This description will appear on the Views administrative UI to tell you what the view is about.'),
+ '#default_value' => $view->description,
+ );
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_details_form');
+ return $form;
+}
+
+/**
+ * Submit handler for views_ui_edit_details_form.
+ */
+function views_ui_edit_details_form_submit($form, &$form_state) {
+ $view = $form_state['view'];
+ foreach ($form_state['values'] as $key => $value) {
+ // Only save values onto the view if they're actual view properties
+ // (as opposed to 'op' or 'form_build_id').
+ if (isset($form['details'][$key])) {
+ $view->$key = $value;
+ }
+ }
+ $form_state['#page_title'] = views_ui_edit_page_title($view);
+ views_ui_cache_set($view);
+}
+
+/**
+ * Form constructor callback to edit display of a view
+ */
+function views_ui_edit_display_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $section = $form_state['section'];
+
+ if (!$view->set_display($display_id)) {
+ views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ $display = &$view->display[$display_id];
+
+ // Get form from the handler.
+ $form['options'] = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('scroll')),
+ );
+ $display->handler->options_form($form['options'], $form_state);
+
+ // The handler options form sets $form['#title'], which we need on the entire
+ // $form instead of just the ['options'] section.
+ $form['#title'] = $form['options']['#title'];
+ unset($form['options']['#title']);
+
+ // Move the override dropdown out of the scrollable section of the form.
+ if (isset($form['options']['override'])) {
+ $form['override'] = $form['options']['override'];
+ unset($form['options']['override']);
+ }
+
+ $name = NULL;
+ if (isset($form_state['update_name'])) {
+ $name = $form_state['update_name'];
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_display_form', $name);
+ return $form;
+}
+
+/**
+ * Validate handler for views_ui_edit_display_form
+ */
+function views_ui_edit_display_form_validate($form, &$form_state) {
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ $display->handler->options_validate($form['options'], $form_state);
+
+ if (form_get_errors()) {
+ $form_state['rerender'] = TRUE;
+ }
+}
+
+/**
+ * Submit handler for views_ui_edit_display_form
+ */
+function views_ui_edit_display_form_submit($form, &$form_state) {
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ $display->handler->options_submit($form, $form_state);
+
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Override handler for views_ui_edit_display_form
+ *
+ * @TODO: Not currently used. Remove unless we implement an override toggle.
+ */
+function views_ui_edit_display_form_override($form, &$form_state) {
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ $display->handler->options_override($form, $form_state);
+
+ views_ui_cache_set($form_state['view']);
+ $form_state['rerender'] = TRUE;
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Form to config items in the views UI.
+ */
+function views_ui_config_type_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+
+ $types = views_object_types();
+ if (!$view->set_display($display_id)) {
+ views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ $display = &$view->display[$display_id];
+ $form['#title'] = t('Configure @type', array('@type' => $types[$type]['ltitle']));
+ $form['#section'] = $display_id . 'config-item';
+
+ if ($display->handler->defaultable_sections($types[$type]['plural'])) {
+ $form_state['section'] = $types[$type]['plural'];
+ views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+ }
+
+ if (!empty($types[$type]['options']) && function_exists($types[$type]['options'])) {
+ $options = $type . '_options';
+ $form[$options] = array('#tree' => TRUE);
+ $types[$type]['options']($form, $form_state);
+ }
+
+ $name = NULL;
+ if (isset($form_state['update_name'])) {
+ $name = $form_state['update_name'];
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_type_form', $name);
+ return $form;
+}
+
+/**
+ * Submit handler for type configuration form
+ */
+function views_ui_config_type_form_submit($form, &$form_state) {
+ $types = views_object_types();
+ $display = &$form_state['view']->display[$form_state['display_id']];
+
+ // Store in cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Form to rearrange items in the views UI.
+ */
+function views_ui_rearrange_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+
+ $types = views_object_types();
+ if (!$view->set_display($display_id)) {
+ views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ $display = &$view->display[$display_id];
+ $form['#title'] = t('Rearrange @type', array('@type' => $types[$type]['ltitle']));
+ $form['#section'] = $display_id . 'rearrange-item';
+
+ if ($display->handler->defaultable_sections($types[$type]['plural'])) {
+ $form_state['section'] = $types[$type]['plural'];
+ views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+ }
+
+ $count = 0;
+
+ // Get relationship labels
+ $relationships = array();
+ foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
+ $relationships[$id] = $handler->label();
+ $handlers = $display->handler->get_option('relationships');
+ if ($handlers) {
+ foreach ($handlers as $id => $info) {
+ $handler = $display->handler->get_handler('relationship', $id);
+ $relationships[$id] = $handler->label();
+ }
+ }
+ }
+
+ // Filters can now be grouped so we do a little bit extra:
+ $groups = array();
+ $grouping = FALSE;
+ if ($type == 'filter') {
+ $group_info = $view->display_handler->get_option('filter_groups');
+ if (!empty($group_info['groups']) && count($group_info['groups']) > 1) {
+ $grouping = TRUE;
+ $groups = array(0 => array());
+ }
+ }
+
+ foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
+ $form['fields'][$id] = array('#tree' => TRUE);
+ $form['fields'][$id]['weight'] = array(
+ '#type' => 'textfield',
+ '#default_value' => ++$count,
+ );
+ $handler = $display->handler->get_handler($type, $id);
+ if ($handler) {
+ $name = $handler->ui_name() . ' ' . $handler->admin_summary();
+ if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
+ $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
+ }
+
+ $form['fields'][$id]['name'] = array(
+ '#markup' => $name,
+ );
+ }
+ else {
+ $form['fields'][$id]['name'] = array('#markup' => t('Broken field @id', array('@id' => $id)));
+ }
+ $form['fields'][$id]['removed'] = array(
+ '#type' => 'checkbox',
+ '#id' => 'views-removed-' . $id,
+ '#attributes' => array('class' => array('views-remove-checkbox')),
+ '#default_value' => 0,
+ );
+ }
+
+ // Add javascript settings that will be added via $.extend for tabledragging
+ $form['#js']['tableDrag']['arrange']['weight'][0] = array(
+ 'target' => 'weight',
+ 'source' => NULL,
+ 'relationship' => 'sibling',
+ 'action' => 'order',
+ 'hidden' => TRUE,
+ 'limit' => 0,
+ );
+
+ $name = NULL;
+ if (isset($form_state['update_name'])) {
+ $name = $form_state['update_name'];
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_form');
+ return $form;
+}
+
+/**
+ * Turn the rearrange form into a proper table
+ */
+function theme_views_ui_rearrange_form($variables) {
+ $form = $variables['form'];
+
+ $rows = array();
+ foreach (element_children($form['fields']) as $id) {
+ if (isset($form['fields'][$id]['name'])) {
+ $row = array();
+ $row[] = drupal_render($form['fields'][$id]['name']);
+ $form['fields'][$id]['weight']['#attributes']['class'] = array('weight');
+ $row[] = drupal_render($form['fields'][$id]['weight']);
+ $row[] = drupal_render($form['fields'][$id]['removed']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $id, 'class' => array('views-hidden', 'views-button-remove', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => TRUE));
+ $rows[] = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
+ }
+ }
+ if (empty($rows)) {
+ $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
+ }
+
+ $header = array('', t('Weight'), t('Remove'));
+ $output = drupal_render($form['override']);
+ $output .= '<div class="scroll">';
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'arrange')));
+ $output .= '</div>';
+ $output .= drupal_render_children($form);
+ drupal_add_tabledrag('arrange', 'order', 'sibling', 'weight');
+
+ return $output;
+}
+
+/**
+ * Theme the expose filter form.
+ */
+function theme_views_ui_expose_filter_form($variables) {
+ $form = $variables['form'];
+ $more = drupal_render($form['more']);
+
+ $output = drupal_render($form['form_description']);
+ $output .= drupal_render($form['expose_button']);
+ $output .= drupal_render($form['group_button']);
+ if (isset($form['required'])) {
+ $output .= drupal_render($form['required']);
+ }
+ $output .= drupal_render($form['label']);
+ $output .= drupal_render($form['description']);
+
+ $output .= drupal_render($form['operator']);
+ $output .= drupal_render($form['value']);
+
+ if (isset($form['use_operator'])) {
+ $output .= '<div class="views-left-40">';
+ $output .= drupal_render($form['use_operator']);
+ $output .= '</div>';
+ }
+
+ // Only output the right column markup if there's a left column to begin with
+ if (!empty($form['operator']['#type'])) {
+ $output .= '<div class="views-right-60">';
+ $output .= drupal_render_children($form);
+ $output .= '</div>';
+ }
+ else {
+ $output .= drupal_render_children($form);
+ }
+
+ $output .= $more;
+
+ return $output;
+}
+
+ /**
+ * Theme the build group filter form.
+ */
+function theme_views_ui_build_group_filter_form($variables) {
+ $form = $variables['form'];
+ $more = drupal_render($form['more']);
+
+ $output = drupal_render($form['form_description']);
+ $output .= drupal_render($form['expose_button']);
+ $output .= drupal_render($form['group_button']);
+ if (isset($form['required'])) {
+ $output .= drupal_render($form['required']);
+ }
+
+ $output .= drupal_render($form['operator']);
+ $output .= drupal_render($form['value']);
+
+ $output .= '<div class="views-left-40">';
+ $output .= drupal_render($form['optional']);
+ $output .= drupal_render($form['remember']);
+ $output .= '</div>';
+
+ $output .= '<div class="views-right-60">';
+ $output .= drupal_render($form['widget']);
+ $output .= drupal_render($form['label']);
+ $output .= drupal_render($form['description']);
+ $output .= '</div>';
+
+
+ $header = array(
+ t('Default'),
+ t('Weight'),
+ t('Label'),
+ t('Operator'),
+ t('Value'),
+ t('Operations'),
+ );
+
+ $form['default_group'] = form_process_radios($form['default_group']);
+ $form['default_group_multiple'] = form_process_checkboxes($form['default_group_multiple']);
+ $form['default_group']['All']['#title'] = '';
+
+ drupal_render($form['default_group_multiple']['All']); // Don't render
+ $rows[] = array(
+ drupal_render($form['default_group']['All']),
+ '',
+ array(
+ 'data' => variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? t('&lt;Any&gt;') : t('- Any -'),
+ 'colspan' => 4,
+ 'class' => array('class' => 'any-default-radios-row'),
+ ),
+ );
+
+ foreach (element_children($form['group_items']) as $group_id) {
+ $form['group_items'][$group_id]['value']['#title'] = '';
+ $data = array(
+ 'default' => drupal_render($form['default_group'][$group_id]) . drupal_render($form['default_group_multiple'][$group_id]),
+ 'weight' => drupal_render($form['group_items'][$group_id]['weight']),
+ 'title' => drupal_render($form['group_items'][$group_id]['title']),
+ 'operator' => drupal_render($form['group_items'][$group_id]['operator']),
+ 'value' => drupal_render($form['group_items'][$group_id]['value']),
+ 'remove' => drupal_render($form['group_items'][$group_id]['remove']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $group_id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true)),
+ );
+ $rows[] = array('data' => $data, 'id' => 'views-row-' . $group_id, 'class' => array('draggable'));
+ }
+ $table = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('views-filter-groups'), 'id' => 'views-filter-groups'))) . drupal_render($form['add_group']);
+ drupal_add_tabledrag('views-filter-groups', 'order', 'sibling', 'weight');
+ $render_form = drupal_render_children($form);
+ return $output . $render_form . $table . $more;
+}
+
+
+/**
+ * Submit handler for rearranging form
+ */
+function views_ui_rearrange_form_submit($form, &$form_state) {
+ $types = views_object_types();
+ $display = &$form_state['view']->display[$form_state['display_id']];
+
+ $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
+ $new_fields = $order = array();
+
+ // Make an array with the weights
+ foreach ($form_state['values'] as $field => $info) {
+ // add each value that is a field with a weight to our list, but only if
+ // it has had its 'removed' checkbox checked.
+ if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
+ $order[$field] = $info['weight'];
+ }
+ }
+
+ // Sort the array
+ asort($order);
+
+ // Create a new list of fields in the new order.
+ foreach (array_keys($order) as $field) {
+ $new_fields[$field] = $old_fields[$field];
+ }
+ $display->handler->set_option($types[$form_state['type']]['plural'], $new_fields);
+
+ // Store in cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Form to rearrange items in the views UI.
+ */
+function views_ui_rearrange_filter_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+
+ $types = views_object_types();
+ if (!$view->set_display($display_id)) {
+ views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ $display = &$view->display[$display_id];
+ $form['#title'] = check_plain($display->display_title) . ': ';
+ $form['#title'] .= t('Rearrange @type', array('@type' => $types[$type]['ltitle']));
+ $form['#section'] = $display_id . 'rearrange-item';
+
+ if ($display->handler->defaultable_sections($types[$type]['plural'])) {
+ $form_state['section'] = $types[$type]['plural'];
+ views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+ }
+
+ if (!empty($view->form_cache)) {
+ $groups = $view->form_cache['groups'];
+ $handlers = $view->form_cache['handlers'];
+ }
+ else {
+ $groups = $display->handler->get_option('filter_groups');
+ $handlers = $display->handler->get_option($types[$type]['plural']);
+ }
+ $count = 0;
+
+ // Get relationship labels
+ $relationships = array();
+ foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
+ $relationships[$id] = $handler->label();
+ }
+
+ $group_options = array();
+
+ /**
+ * Filter groups is an array that contains:
+ * array(
+ * 'operator' => 'and' || 'or',
+ * 'groups' => array(
+ * $group_id => 'and' || 'or',
+ * ),
+ * );
+ */
+
+ $grouping = count(array_keys($groups['groups'])) > 1;
+
+ $form['filter_groups']['#tree'] = TRUE;
+ $form['filter_groups']['operator'] = array(
+ '#type' => 'select',
+ '#options' => array (
+ 'AND' => t('And'),
+ 'OR' => t('Or'),
+ ),
+ '#default_value' => $groups['operator'],
+ '#attributes' => array(
+ 'class' => array('warning-on-change'),
+ ),
+ '#title' => t('Operator to use on all groups'),
+ '#description' => t('Either "group 0 AND group 1 AND group 2" or "group 0 OR group 1 OR group 2", etc'),
+ '#access' => $grouping,
+ );
+
+ $form['remove_groups']['#tree'] = TRUE;
+
+ foreach ($groups['groups'] as $id => $group) {
+ $form['filter_groups']['groups'][$id] = array(
+ '#title' => t('Operator'),
+ '#type' => 'select',
+ '#options' => array(
+ 'AND' => t('And'),
+ 'OR' => t('Or'),
+ ),
+ '#default_value' => $group,
+ '#attributes' => array(
+ 'class' => array('warning-on-change'),
+ ),
+ );
+
+ $form['remove_groups'][$id] = array(); // to prevent a notice
+ if ($id != 1) {
+ $form['remove_groups'][$id] = array(
+ '#type' => 'submit',
+ '#value' => t('Remove group @group', array('@group' => $id)),
+ '#id' => "views-remove-group-$id",
+ '#attributes' => array(
+ 'class' => array('views-remove-group'),
+ ),
+ '#group' => $id,
+ );
+ }
+ $group_options[$id] = $id == 1 ? t('Default group') : t('Group @group', array('@group' => $id));
+ $form['#group_renders'][$id] = array();
+ }
+
+ $form['#group_options'] = $group_options;
+ $form['#groups'] = $groups;
+ // We don't use get_handlers() because we want items without handlers to
+ // appear and show up as 'broken' so that the user can see them.
+ $form['filters'] = array('#tree' => TRUE);
+ foreach ($handlers as $id => $field) {
+ // If the group does not exist, move the filters to the default group.
+ if (empty($field['group']) || empty($groups['groups'][$field['group']])) {
+ $field['group'] = 1;
+ }
+
+ $handler = $display->handler->get_handler($type, $id);
+ if ($grouping && $handler && !$handler->can_group()) {
+ $field['group'] = 'ungroupable';
+ }
+
+ // If not grouping and the handler is set ungroupable, move it back to
+ // the default group to prevent weird errors from having it be in its
+ // own group:
+ if (!$grouping && $field['group'] == 'ungroupable') {
+ $field['group'] = 1;
+ }
+
+ // Place this item into the proper group for rendering.
+ $form['#group_renders'][$field['group']][] = $id;
+
+ $form['filters'][$id]['weight'] = array(
+ '#type' => 'textfield',
+ '#default_value' => ++$count,
+ '#size' => 8,
+ );
+ $form['filters'][$id]['group'] = array(
+ '#type' => 'select',
+ '#options' => $group_options,
+ '#default_value' => $field['group'],
+ '#attributes' => array(
+ 'class' => array('views-region-select', 'views-region-' . $id),
+ ),
+ '#access' => $field['group'] !== 'ungroupable',
+ );
+
+ if ($handler) {
+ $name = $handler->ui_name() . ' ' . $handler->admin_summary();
+ if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
+ $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
+ }
+
+ $form['filters'][$id]['name'] = array(
+ '#markup' => $name,
+ );
+ }
+ else {
+ $form['filters'][$id]['name'] = array('#markup' => t('Broken field @id', array('@id' => $id)));
+ }
+ $form['filters'][$id]['removed'] = array(
+ '#type' => 'checkbox',
+ '#id' => 'views-removed-' . $id,
+ '#attributes' => array('class' => array('views-remove-checkbox')),
+ '#default_value' => 0,
+ );
+ }
+
+ if (isset($form_state['update_name'])) {
+ $name = $form_state['update_name'];
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_filter_form');
+ $form['buttons']['add_group'] = array(
+ '#type' => 'submit',
+ '#value' => t('Create new filter group'),
+ '#id' => 'views-add-group',
+ '#group' => 'add',
+ );
+
+ return $form;
+}
+
+/**
+ * Turn the rearrange form into a proper table
+ */
+function theme_views_ui_rearrange_filter_form(&$vars) {
+ $form = $vars['form'];
+ $rows = $ungroupable_rows = array();
+ // Enable grouping only if > 1 group.
+ $grouping = count(array_keys($form['#group_options'])) > 1;
+
+ foreach ($form['#group_renders'] as $group_id => $contents) {
+ // Header row for the group.
+ if ($group_id !== 'ungroupable') {
+ // Set up tabledrag so that it changes the group dropdown when rows are
+ // dragged between groups.
+ drupal_add_tabledrag('views-rearrange-filters', 'match', 'sibling', 'views-group-select', 'views-group-select-' . $group_id);
+
+ // Title row, spanning all columns.
+ $row = array();
+ // Add a cell to the first row, containing the group operator.
+ $row[] = array('class' => array('group', 'group-operator', 'container-inline'), 'data' => drupal_render($form['filter_groups']['groups'][$group_id]), 'rowspan' => max(array(2, count($contents) + 1)));
+ // Title.
+ $row[] = array('class' => array('group', 'group-title'), 'data' => '<span>' . $form['#group_options'][$group_id] . '</span>', 'colspan' => 4);
+ $rows[] = array('class' => array('views-group-title'), 'data' => $row, 'id' => 'views-group-title-' . $group_id);
+
+ // Row which will only appear if the group has nothing in it.
+ $row = array();
+ $class = 'group-' . (count($contents) ? 'populated' : 'empty');
+ $instructions = '<span>' . t('No filters have been added.') . '</span> <span class="js-only">' . t('Drag to add filters.') . '</span>';
+ // When JavaScript is enabled, the button for removing the group (if it's
+ // present) should be hidden, since it will be replaced by a link on the
+ // client side.
+ if (!empty($form['remove_groups'][$group_id]['#type']) && $form['remove_groups'][$group_id]['#type'] == 'submit') {
+ $form['remove_groups'][$group_id]['#attributes']['class'][] = 'js-hide';
+ }
+ $row[] = array('colspan' => 5, 'data' => $instructions . drupal_render($form['remove_groups'][$group_id]));
+ $rows[] = array('class' => array("group-message", "group-$group_id-message", $class), 'data' => $row, 'id' => 'views-group-' . $group_id);
+ }
+
+ foreach ($contents as $id) {
+ if (isset($form['filters'][$id]['name'])) {
+ $row = array();
+ $row[] = drupal_render($form['filters'][$id]['name']);
+ $form['filters'][$id]['weight']['#attributes']['class'] = array('weight');
+ $row[] = drupal_render($form['filters'][$id]['weight']);
+ $form['filters'][$id]['group']['#attributes']['class'] = array('views-group-select views-group-select-' . $group_id);
+ $row[] = drupal_render($form['filters'][$id]['group']);
+ $form['filters'][$id]['removed']['#attributes']['class'][] = 'js-hide';
+ $row[] = drupal_render($form['filters'][$id]['removed']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true));
+
+ $row = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
+ if ($group_id !== 'ungroupable') {
+ $rows[] = $row;
+ }
+ else {
+ $ungroupable_rows[] = $row;
+ }
+ }
+ }
+ }
+ if (empty($rows)) {
+ $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
+ }
+
+ $output = drupal_render($form['override']);
+ $output .= '<div class="scroll">';
+ if ($grouping) {
+ $output .= drupal_render($form['filter_groups']['operator']);
+ }
+ else {
+ $form['filter_groups']['groups'][0]['#title'] = t('Operator');
+ $output .= drupal_render($form['filter_groups']['groups'][0]);
+ }
+
+ if (!empty($ungroupable_rows)) {
+ drupal_add_tabledrag('views-rearrange-filters-ungroupable', 'order', 'sibling', 'weight');
+ $header = array(t('Ungroupable filters'), t('Weight'), array('class' => array('views-hide-label'), 'data' => t('Group')), array('class' => array('views-hide-label'), 'data' => t('Remove')));
+ $output .= theme('table', array('header' => $header, 'rows' => $ungroupable_rows, 'attributes' => array('id' => 'views-rearrange-filters-ungroupable', 'class' => array('arrange'))));
+ }
+
+ // Set up tabledrag so that the weights are changed when rows are dragged.
+ drupal_add_tabledrag('views-rearrange-filters', 'order', 'sibling', 'weight');
+ $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'views-rearrange-filters', 'class' => array('arrange'))));
+ $output .= '</div>';
+
+ // When JavaScript is enabled, the button for adding a new group should be
+ // hidden, since it will be replaced by a link on the client side.
+ $form['buttons']['add_group']['#attributes']['class'][] = 'js-hide';
+
+ // Render the rest of the form and return.
+ $output .= drupal_render_children($form);
+ return $output;
+}
+
+/**
+ * Submit handler for rearranging form
+ */
+function views_ui_rearrange_filter_form_submit($form, &$form_state) {
+ $types = views_object_types();
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ $remember_groups = array();
+
+ if (!empty($form_state['view']->form_cache)) {
+ $old_fields = $form_state['view']->form_cache['handlers'];
+ }
+ else {
+ $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
+ }
+ $count = 0;
+
+ $groups = $form_state['values']['filter_groups'];
+ // Whatever button was clicked, re-calculate field information.
+ $new_fields = $order = array();
+
+ // Make an array with the weights
+ foreach ($form_state['values']['filters'] as $field => $info) {
+ // add each value that is a field with a weight to our list, but only if
+ // it has had its 'removed' checkbox checked.
+ if (is_array($info) && empty($info['removed'])) {
+ if (isset($info['weight'])) {
+ $order[$field] = $info['weight'];
+ }
+
+ if (isset($info['group'])) {
+ $old_fields[$field]['group'] = $info['group'];
+ $remember_groups[$info['group']][] = $field;
+ }
+ }
+ }
+
+ // Sort the array
+ asort($order);
+
+ // Create a new list of fields in the new order.
+ foreach (array_keys($order) as $field) {
+ $new_fields[$field] = $old_fields[$field];
+ }
+
+ // If the #group property is set on the clicked button, that means we are
+ // either adding or removing a group, not actually updating the filters.
+ if (!empty($form_state['clicked_button']['#group'])) {
+ if ($form_state['clicked_button']['#group'] == 'add') {
+ // Add a new group
+ $groups['groups'][] = 'AND';
+ }
+ else {
+ // Renumber groups above the removed one down.
+ foreach (array_keys($groups['groups']) as $group_id) {
+ if ($group_id >= $form_state['clicked_button']['#group']) {
+ $old_group = $group_id + 1;
+ if (isset($groups['groups'][$old_group])) {
+ $groups['groups'][$group_id] = $groups['groups'][$old_group];
+ if (isset($remember_groups[$old_group])) {
+ foreach ($remember_groups[$old_group] as $id) {
+ $new_fields[$id]['group'] = $group_id;
+ }
+ }
+ }
+ else {
+ // If this is the last one, just unset it.
+ unset($groups['groups'][$group_id]);
+ }
+ }
+ }
+ }
+ // Update our cache with values so that cancel still works the way
+ // people expect.
+ $form_state['view']->form_cache = array(
+ 'key' => 'rearrange-filter',
+ 'groups' => $groups,
+ 'handlers' => $new_fields,
+ );
+
+ // Return to this form except on actual Update.
+ views_ui_add_form_to_stack('rearrange-filter', $form_state['view'], $form_state['display_id'], array($form_state['type']));
+ }
+ else {
+ // The actual update button was clicked. Remove the empty groups, and
+ // renumber them sequentially.
+ ksort($remember_groups);
+ $groups['groups'] = views_array_key_plus(array_values(array_intersect_key($groups['groups'], $remember_groups)));
+ // Change the 'group' key on each field to match. Here, $mapping is an
+ // array whose keys are the old group numbers and whose values are the new
+ // (sequentially numbered) ones.
+ $mapping = array_flip(views_array_key_plus(array_keys($remember_groups)));
+ foreach ($new_fields as &$new_field) {
+ $new_field['group'] = $mapping[$new_field['group']];
+ }
+
+ // Write the changed handler values.
+ $display->handler->set_option($types[$form_state['type']]['plural'], $new_fields);
+ $display->handler->set_option('filter_groups', $groups);
+ if (isset($form_state['view']->form_cache)) {
+ unset($form_state['view']->form_cache);
+ }
+ }
+
+ // Store in cache.
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Form to add_item items in the views UI.
+ */
+function views_ui_add_item_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+
+ $form = array(
+ 'options' => array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('scroll')),
+ ),
+ );
+
+ ctools_add_js('dependent');
+
+ if (!$view->set_display($display_id)) {
+ views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ $display = &$view->display[$display_id];
+
+ $types = views_object_types();
+ $ltitle = $types[$type]['ltitle'];
+ $section = $types[$type]['plural'];
+
+ if (!empty($types[$type]['type'])) {
+ $type = $types[$type]['type'];
+ }
+
+ $form['#title'] = t('Add @type', array('@type' => $ltitle));
+ $form['#section'] = $display_id . 'add-item';
+
+
+ // Add the display override dropdown.
+ views_ui_standard_display_dropdown($form, $form_state, $section);
+
+ // Figure out all the base tables allowed based upon what the relationships provide.
+ $base_tables = $view->get_base_tables();
+ $options = views_fetch_fields(array_keys($base_tables), $type, $display->handler->use_group_by());
+
+ if (!empty($options)) {
+ $form['options']['controls'] = array(
+ '#theme_wrappers' => array('container'),
+ '#id' => 'views-filterable-options-controls',
+ '#attributes' => array('class' => array('container-inline')),
+ );
+ $form['options']['controls']['options_search'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Search'),
+ );
+
+ $groups = array('all' => t('- All -'));
+ $form['options']['controls']['group'] = array(
+ '#type' => 'select',
+ '#title' => t('Filter'),
+ '#options' => array(),
+ '#attributes' => array('class' => array('ctools-master-dependent')),
+ );
+
+ $form['options']['name'] = array(
+ '#prefix' => '<div class="views-radio-box form-checkboxes views-filterable-options">',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ '#default_value' => 'all',
+ );
+
+ // Group options first to simplify the DOM objects that Views
+ // dependent JS will act upon.
+ $grouped_options = array();
+ foreach ($options as $key => $option) {
+ $group = preg_replace('/[^a-z0-9]/', '-', strtolower($option['group']));
+ $groups[$group] = $option['group'];
+ $grouped_options[$group][$key] = $option;
+ if (!empty($option['aliases']) && is_array($option['aliases'])) {
+ foreach ($option['aliases'] as $id => $alias) {
+ if (empty($alias['base']) || !empty($base_tables[$alias['base']])) {
+ $copy = $option;
+ $copy['group'] = $alias['group'];
+ $copy['title'] = $alias['title'];
+ if (isset($alias['help'])) {
+ $copy['help'] = $alias['help'];
+ }
+
+ $group = preg_replace('/[^a-z0-9]/', '-', strtolower($copy['group']));
+ $groups[$group] = $copy['group'];
+ $grouped_options[$group][$key . '$' . $id] = $copy;
+ }
+ }
+ }
+ }
+
+ foreach ($grouped_options as $group => $group_options) {
+ $form['options']['name'][$group . '_start']['#markup'] = '<div class="ctools-dependent-all ctools-dependent-' . $group . '">';
+ $zebra = 0;
+ foreach ($group_options as $key => $option) {
+ $zebra_class = ($zebra % 2) ? 'odd' : 'even';
+ $form['options']['name'][$key] = array(
+ '#type' => 'checkbox',
+ '#title' => t('!group: !field', array('!group' => check_plain($option['group']), '!field' => check_plain($option['title']))),
+ '#description' => filter_xss_admin($option['help']),
+ '#return_value' => $key,
+ '#prefix' => "<div class='$zebra_class filterable-option'>",
+ '#suffix' => '</div>',
+ );
+ $zebra++;
+ }
+ $form['options']['name'][$group . '_end']['#markup'] = '</div>';
+ }
+
+ $form['options']['controls']['group']['#options'] = $groups;
+ }
+ else {
+ $form['options']['markup'] = array(
+ '#markup' => '<div class="form-item">' . t('There are no @types available to add.', array('@types' => $ltitle)) . '</div>',
+ );
+ }
+ // Add a div to show the selected items
+ $form['selected'] = array(
+ '#type' => 'item',
+ '#markup' => '<div class="views-selected-options"></div>',
+ '#title' => t('Selected') . ':',
+ '#theme_wrappers' => array('form_element', 'views_container'),
+ '#attributes' => array('class' => array('container-inline', 'views-add-form-selected')),
+ );
+ ctools_include('dependent');
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_add_item_form', t('Add and configure @types', array('@types' => $ltitle)));
+
+ // Remove the default submit function.
+ $form['buttons']['submit']['#submit'] = array_diff($form['buttons']['submit']['#submit'], array('views_ui_standard_submit'));
+ $form['buttons']['submit']['#submit'][] = 'views_ui_add_item_form_submit';
+
+ return $form;
+}
+
+/**
+ * Submit handler for adding new item(s) to a view.
+ */
+function views_ui_add_item_form_submit($form, &$form_state) {
+ $type = $form_state['type'];
+ $types = views_object_types();
+ $section = $types[$type]['plural'];
+
+ // Handle the override select.
+ list($was_defaulted, $is_defaulted) = views_ui_standard_override_values($form, $form_state);
+ if ($was_defaulted && !$is_defaulted) {
+ // We were using the default display's values, but we're now overriding
+ // the default display and saving values specific to this display.
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ // set_override toggles the override of this section.
+ $display->handler->set_override($section);
+ }
+ elseif (!$was_defaulted && $is_defaulted) {
+ // We used to have an override for this display, but the user now wants
+ // to go back to the default display.
+ // Overwrite the default display with the current form values, and make
+ // the current display use the new default values.
+ $display = &$form_state['view']->display[$form_state['display_id']];
+ // options_override toggles the override of this section.
+ $display->handler->set_override($section);
+ }
+
+ if (!empty($form_state['values']['name']) && is_array($form_state['values']['name'])) {
+ // Loop through each of the items that were checked and add them to the view.
+ foreach (array_keys(array_filter($form_state['values']['name'])) as $field) {
+ list($table, $field) = explode('.', $field, 2);
+
+ if ($cut = strpos($field, '$')) {
+ $field = substr($field, 0, $cut);
+ }
+ $id = $form_state['view']->add_item($form_state['display_id'], $type, $table, $field);
+
+ // check to see if we have group by settings
+ $key = $type;
+ // Footer,header and empty text have a different internal handler type(area).
+ if (isset($types[$type]['type'])) {
+ $key = $types[$type]['type'];
+ }
+ $handler = views_get_handler($table, $field, $key);
+ if ($form_state['view']->display_handler->use_group_by() && $handler->use_group_by()) {
+ views_ui_add_form_to_stack('config-item-group', $form_state['view'], $form_state['display_id'], array($type, $id));
+ }
+
+ // check to see if this type has settings, if so add the settings form first
+ if ($handler && $handler->has_extra_options()) {
+ views_ui_add_form_to_stack('config-item-extra', $form_state['view'], $form_state['display_id'], array($type, $id));
+ }
+ // Then add the form to the stack
+ views_ui_add_form_to_stack('config-item', $form_state['view'], $form_state['display_id'], array($type, $id));
+ }
+ }
+
+ if (isset($form_state['view']->form_cache)) {
+ unset($form_state['view']->form_cache);
+ }
+
+ // Store in cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Override handler for views_ui_edit_display_form
+ */
+function views_ui_config_item_form_build_group($form, &$form_state) {
+ $item = &$form_state['handler']->options;
+ // flip. If the filter was a group, set back to a standard filter.
+ $item['is_grouped'] = empty($item['is_grouped']);
+
+ // If necessary, set new defaults:
+ if ($item['is_grouped']) {
+ $form_state['handler']->build_group_options();
+ }
+
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+ views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE, TRUE);
+
+ views_ui_cache_set($form_state['view']);
+ $form_state['rerender'] = TRUE;
+ $form_state['rebuild'] = TRUE;
+ $form_state['force_build_group_options'] = TRUE;
+}
+
+/**
+ * Add a new group to the exposed filter groups.
+ */
+function views_ui_config_item_form_add_group($form, &$form_state) {
+ $item =& $form_state['handler']->options;
+
+ // Add a new row.
+ $item['group_info']['group_items'][] = array();
+
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+ views_ui_cache_set($form_state['view']);
+ $form_state['rerender'] = TRUE;
+ $form_state['rebuild'] = TRUE;
+ $form_state['force_build_group_options'] = TRUE;
+}
+
+/**
+ * Form to config_item items in the views UI.
+ */
+function views_ui_config_item_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+ $id = $form_state['id'];
+
+ $form = array(
+ 'options' => array(
+ '#tree' => TRUE,
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('scroll')),
+ ),
+ );
+ if (!$view->set_display($display_id)) {
+ views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ $item = $view->get_item($display_id, $type, $id);
+
+ if ($item) {
+ $handler = $view->display_handler->get_handler($type, $id);
+ if (empty($handler)) {
+ $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
+ }
+ else {
+ $types = views_object_types();
+
+ // If this item can come from the default display, show a dropdown
+ // that lets the user choose which display the changes should apply to.
+ if ($view->display_handler->defaultable_sections($types[$type]['plural'])) {
+ $form_state['section'] = $types[$type]['plural'];
+ views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+ }
+
+ // A whole bunch of code to figure out what relationships are valid for
+ // this item.
+ $relationships = $view->display_handler->get_option('relationships');
+ $relationship_options = array();
+
+ foreach ($relationships as $relationship) {
+ // relationships can't link back to self. But also, due to ordering,
+ // relationships can only link to prior relationships.
+ if ($type == 'relationship' && $id == $relationship['id']) {
+ break;
+ }
+ $relationship_handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
+ // ignore invalid/broken relationships.
+ if (empty($relationship_handler)) {
+ continue;
+ }
+
+ // If this relationship is valid for this type, add it to the list.
+ $data = views_fetch_data($relationship['table']);
+ $base = $data[$relationship['field']]['relationship']['base'];
+ $base_fields = views_fetch_fields($base, $form_state['type'], $view->display_handler->use_group_by());
+ if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
+ $relationship_handler->init($view, $relationship);
+ $relationship_options[$relationship['id']] = $relationship_handler->label();
+ }
+ }
+
+ if (!empty($relationship_options)) {
+ // Make sure the existing relationship is even valid. If not, force
+ // it to none.
+ $base_fields = views_fetch_fields($view->base_table, $form_state['type'], $view->display_handler->use_group_by());
+ if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
+ $relationship_options = array_merge(array('none' => t('Do not use a relationship')), $relationship_options);
+ }
+ $rel = empty($item['relationship']) ? 'none' : $item['relationship'];
+ if (empty($relationship_options[$rel])) {
+ // Pick the first relationship.
+ $rel = key($relationship_options);
+ // We want this relationship option to get saved even if the user
+ // skips submitting the form.
+ $view->set_item_option($display_id, $type, $id, 'relationship', $rel);
+ $temp_view = $view->clone_view();
+ views_ui_cache_set($temp_view);
+ }
+
+ $form['options']['relationship'] = array(
+ '#type' => 'select',
+ '#title' => t('Relationship'),
+ '#options' => $relationship_options,
+ '#default_value' => $rel,
+ '#weight' => -500,
+ );
+ }
+ else {
+ $form['options']['relationship'] = array(
+ '#type' => 'value',
+ '#value' => 'none',
+ );
+ }
+
+ $form['#title'] = t('Configure @type: @item', array('@type' => $types[$type]['lstitle'], '@item' => $handler->ui_name()));
+
+ if (!empty($handler->definition['help'])) {
+ $form['options']['form_description'] = array(
+ '#markup' => $handler->definition['help'],
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('form-item description')),
+ '#weight' => -1000,
+ );
+ }
+
+ $form['#section'] = $display_id . '-' . $type . '-' . $id;
+
+ // Get form from the handler.
+ $handler->options_form($form['options'], $form_state);
+ $form_state['handler'] = &$handler;
+ }
+
+ $name = NULL;
+ if (isset($form_state['update_name'])) {
+ $name = $form_state['update_name'];
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_form', $name, t('Remove'), 'remove');
+ // Only validate the override values, because this values are required for
+ // the override selection.
+ $form['buttons']['remove']['#limit_validation_errors'] = array(array('override'));
+ }
+
+ return $form;
+}
+
+/**
+ * Submit handler for configing new item(s) to a view.
+ */
+function views_ui_config_item_form_validate($form, &$form_state) {
+ $form_state['handler']->options_validate($form['options'], $form_state);
+
+ if (form_get_errors()) {
+ $form_state['rerender'] = TRUE;
+ }
+}
+
+/**
+ * A submit handler that is used for storing temporary items when using
+ * multi-step changes, such as ajax requests.
+ */
+function views_ui_config_item_form_submit_temporary($form, &$form_state) {
+ // Run it through the handler's submit function.
+ $form_state['handler']->options_submit($form['options'], $form_state);
+ $item = $form_state['handler']->options;
+ $types = views_object_types();
+
+ // For footer/header $handler_type is area but $type is footer/header.
+ // For all other handle types it's the same.
+ $handler_type = $type = $form_state['type'];
+ if (!empty($types[$type]['type'])) {
+ $handler_type = $types[$type]['type'];
+ }
+
+ $override = NULL;
+ if ($form_state['view']->display_handler->use_group_by() && !empty($item['group_type'])) {
+ if (empty($form_state['view']->query)) {
+ $form_state['view']->init_query();
+ }
+ $aggregate = $form_state['view']->query->get_aggregation_info();
+ if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
+ $override = $aggregate[$item['group_type']]['handler'][$type];
+ }
+ }
+
+ // Create a new handler and unpack the options from the form onto it. We
+ // can use that for storage.
+ $handler = views_get_handler($item['table'], $item['field'], $handler_type, $override);
+ $handler->init($form_state['view'], $item);
+
+
+ // Add the incoming options to existing options because items using
+ // the extra form may not have everything in the form here.
+ $options = $form_state['values']['options'] + $form_state['handler']->options;
+
+ // This unpacks only options that are in the definition, ensuring random
+ // extra stuff on the form is not sent through.
+ $handler->unpack_options($handler->options, $options, NULL, FALSE);
+
+ // Store the item back on the view
+ $form_state['view']->temporary_options[$type][$form_state['id']] = $handler->options;
+
+ // @todo: Figure out whether views_ui_ajax_form is perhaps the better place to fix the issue.
+ // views_ui_ajax_form() drops the current form from the stack, even if it's an #ajax.
+ // So add the item back to the top of the stack.
+ views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($type, $item['id']), TRUE);
+
+ $form_state['rerender'] = TRUE;
+ $form_state['rebuild'] = TRUE;
+ // Write to cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Submit handler for configing new item(s) to a view.
+ */
+function views_ui_config_item_form_submit($form, &$form_state) {
+ // Run it through the handler's submit function.
+ $form_state['handler']->options_submit($form['options'], $form_state);
+ $item = $form_state['handler']->options;
+ $types = views_object_types();
+
+ // For footer/header $handler_type is area but $type is footer/header.
+ // For all other handle types it's the same.
+ $handler_type = $type = $form_state['type'];
+ if (!empty($types[$type]['type'])) {
+ $handler_type = $types[$type]['type'];
+ }
+
+ $override = NULL;
+ if ($form_state['view']->display_handler->use_group_by() && !empty($item['group_type'])) {
+ if (empty($form_state['view']->query)) {
+ $form_state['view']->init_query();
+ }
+ $aggregate = $form_state['view']->query->get_aggregation_info();
+ if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
+ $override = $aggregate[$item['group_type']]['handler'][$type];
+ }
+ }
+
+ // Create a new handler and unpack the options from the form onto it. We
+ // can use that for storage.
+ $handler = views_get_handler($item['table'], $item['field'], $handler_type, $override);
+ $handler->init($form_state['view'], $item);
+
+
+ // Add the incoming options to existing options because items using
+ // the extra form may not have everything in the form here.
+ $options = $form_state['values']['options'] + $form_state['handler']->options;
+
+ // This unpacks only options that are in the definition, ensuring random
+ // extra stuff on the form is not sent through.
+ $handler->unpack_options($handler->options, $options, NULL, FALSE);
+
+ // Store the item back on the view
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $handler->options);
+
+ // Ensure any temporary options are removed.
+ if (isset($form_state['view']->temporary_options[$type][$form_state['id']])) {
+ unset($form_state['view']->temporary_options[$type][$form_state['id']]);
+ }
+
+ // Write to cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Form to config_item items in the views UI.
+ */
+function views_ui_config_item_group_form($type, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+ $id = $form_state['id'];
+
+ $form = array(
+ 'options' => array(
+ '#tree' => TRUE,
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('scroll')),
+ ),
+ );
+ if (!$view->set_display($display_id)) {
+ views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+
+ $view->init_query();
+
+ $item = $view->get_item($display_id, $type, $id);
+
+ if ($item) {
+ $handler = $view->display_handler->get_handler($type, $id);
+ if (empty($handler)) {
+ $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
+ }
+ else {
+ $handler->init($view, $item);
+ $types = views_object_types();
+
+ $form['#title'] = t('Configure group settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
+
+ $handler->groupby_form($form['options'], $form_state);
+ $form_state['handler'] = &$handler;
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_group_form');
+ }
+ return $form;
+}
+
+/**
+ * Submit handler for configing group settings on a view.
+ */
+function views_ui_config_item_group_form_submit($form, &$form_state) {
+ $item =& $form_state['handler']->options;
+ $type = $form_state['type'];
+ $id = $form_state['id'];
+
+ $handler = views_get_handler($item['table'], $item['field'], $type);
+ $handler->init($form_state['view'], $item);
+
+ $handler->groupby_form_submit($form, $form_state);
+
+ // Store the item back on the view
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+ // Write to cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Submit handler for removing an item from a view
+ */
+function views_ui_config_item_form_remove($form, &$form_state) {
+ // Store the item back on the view
+ list($was_defaulted, $is_defaulted) = views_ui_standard_override_values($form, $form_state);
+ // If the display selection was changed toggle the override value.
+ if ($was_defaulted != $is_defaulted) {
+ $display =& $form_state['view']->display[$form_state['display_id']];
+ $display->handler->options_override($form, $form_state);
+ }
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], NULL);
+
+ // Write to cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Override handler for views_ui_edit_display_form
+ */
+function views_ui_config_item_form_expose($form, &$form_state) {
+ $item = &$form_state['handler']->options;
+ // flip
+ $item['exposed'] = empty($item['exposed']);
+
+ // If necessary, set new defaults:
+ if ($item['exposed']) {
+ $form_state['handler']->expose_options();
+ }
+
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+ views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE, TRUE);
+
+ views_ui_cache_set($form_state['view']);
+ $form_state['rerender'] = TRUE;
+ $form_state['rebuild'] = TRUE;
+ $form_state['force_expose_options'] = TRUE;
+}
+
+/**
+ * Form to config_item items in the views UI.
+ */
+function views_ui_config_item_extra_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+ $id = $form_state['id'];
+
+ $form = array(
+ 'options' => array(
+ '#tree' => TRUE,
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('scroll')),
+ ),
+ );
+ if (!$view->set_display($display_id)) {
+ views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ $item = $view->get_item($display_id, $type, $id);
+
+ if ($item) {
+ $handler = $view->display_handler->get_handler($type, $id);
+ if (empty($handler)) {
+ $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
+ }
+ else {
+ $handler->init($view, $item);
+ $types = views_object_types();
+
+ $form['#title'] = t('Configure extra settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
+
+ $form['#section'] = $display_id . '-' . $type . '-' . $id;
+
+ // Get form from the handler.
+ $handler->extra_options_form($form['options'], $form_state);
+ $form_state['handler'] = &$handler;
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_extra_form');
+ }
+ return $form;
+}
+
+/**
+ * Validation handler for configing new item(s) to a view.
+ */
+function views_ui_config_item_extra_form_validate($form, &$form_state) {
+ $form_state['handler']->extra_options_validate($form['options'], $form_state);
+}
+
+/**
+ * Submit handler for configing new item(s) to a view.
+ */
+function views_ui_config_item_extra_form_submit($form, &$form_state) {
+ // Run it through the handler's submit function.
+ $form_state['handler']->extra_options_submit($form['options'], $form_state);
+ $item = $form_state['handler']->options;
+
+ // Store the data we're given.
+ foreach ($form_state['values']['options'] as $key => $value) {
+ $item[$key] = $value;
+ }
+
+ // Store the item back on the view
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+ // Write to cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Form to config_style items in the views UI.
+ */
+function views_ui_config_style_form($form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+ $id = $form_state['id'];
+
+ $form = array(
+ 'options' => array(
+ '#tree' => TRUE,
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('scroll')),
+ ),
+ );
+ if (!$view->set_display($display_id)) {
+ views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ $item = $view->get_item($display_id, $type, $id);
+
+ if ($item) {
+ $handler = views_get_handler($item['table'], $item['field'], $type);
+ if (empty($handler)) {
+ $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
+ }
+ else {
+ $handler->init($view, $item);
+ $types = views_object_types();
+
+ $form['#title'] = t('Configure summary style for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
+
+ $form['#section'] = $display_id . '-' . $type . '-style-options';
+
+ $plugin = views_get_plugin('style', $handler->options['style_plugin']);
+ if ($plugin) {
+ $form['style_options'] = array(
+ '#tree' => TRUE,
+ );
+ $plugin->init($view, $view->display[$display_id], $handler->options['style_options']);
+
+ $plugin->options_form($form['style_options'], $form_state);
+ }
+
+ $form_state['handler'] = &$handler;
+ }
+
+ views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_style_form');
+ }
+ return $form;
+}
+
+/**
+ * Submit handler for configing new item(s) to a view.
+ */
+function views_ui_config_style_form_submit($form, &$form_state) {
+ // Run it through the handler's submit function.
+ $form_state['handler']->options_submit($form['style_options'], $form_state);
+ $item = $form_state['handler']->options;
+
+ // Store the data we're given.
+ $item['style_options'] = $form_state['values']['style_options'];
+
+ // Store the item back on the view
+ $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+ // Write to cache
+ views_ui_cache_set($form_state['view']);
+}
+
+/**
+ * Get a list of roles in the system.
+ */
+function views_ui_get_roles() {
+ static $roles = NULL;
+ if (!isset($roles)) {
+ $roles = array();
+ $result = db_query("SELECT r.rid, r.name FROM {role} r ORDER BY r.name");
+ foreach ($result as $obj) {
+ $roles[$obj->rid] = $obj->name;
+ }
+ }
+
+ return $roles;
+}
+
+/**
+ * Form builder for the admin display defaults page.
+ */
+function views_ui_admin_settings_basic() {
+ $form = array();
+ $form['#attached']['css'] = views_ui_get_admin_css();
+
+ $options = array();
+ foreach (list_themes() as $name => $theme) {
+ if ($theme->status) {
+ $options[$name] = $theme->info['name'];
+ }
+ }
+
+ // This is not currently a fieldset but we may want it to be later,
+ // so this will make it easier to change if we do.
+ $form['basic'] = array();
+
+ $form['basic']['views_ui_show_listing_filters'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show filters on the list of views'),
+ '#default_value' => variable_get('views_ui_show_listing_filters', FALSE),
+ );
+ $form['basic']['views_ui_show_advanced_help_warning'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show advanced help warning'),
+ '#default_value' => variable_get('views_ui_show_advanced_help_warning', TRUE),
+ );
+
+ $form['basic']['views_ui_show_master_display'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Always show the master display'),
+ '#description' => t('Advanced users of views may choose to see the master (i.e. default) display.'),
+ '#default_value' => variable_get('views_ui_show_master_display', FALSE),
+ );
+
+ $form['basic']['views_ui_show_advanced_column'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Always show advanced display settings'),
+ '#description' => t('Default to showing advanced display settings, such as relationships and contextual filters.'),
+ '#default_value' => variable_get('views_ui_show_advanced_column', FALSE),
+ );
+
+ $form['basic']['views_ui_display_embed'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show the embed display in the ui.'),
+ '#description' => t('Allow advanced user to use the embed view display. The plugin itself works if it\'s not visible in the ui'),
+ '#default_value' => variable_get('views_ui_display_embed', FALSE),
+ );
+
+ $form['basic']['views_ui_custom_theme'] = array(
+ '#type' => 'select',
+ '#title' => t('Custom admin theme for the Views UI'),
+ '#options' => array('_default' => t('- Use default -')) + $options,
+ '#default_value' => variable_get('views_ui_custom_theme', '_default'),
+ '#description' => t('In some cases you might want to select a different admin theme for the Views UI.')
+ );
+
+ $form['basic']['views_exposed_filter_any_label'] = array(
+ '#type' => 'select',
+ '#title' => t('Label for "Any" value on non-required single-select exposed filters'),
+ '#options' => array('old_any' => '<Any>', 'new_any' => t('- Any -')),
+ '#default_value' => variable_get('views_exposed_filter_any_label', 'new_any'),
+ );
+
+ $form['live_preview'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Live preview settings'),
+ );
+
+ $form['live_preview']['views_ui_always_live_preview'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Automatically update preview on changes'),
+ '#default_value' => variable_get('views_ui_always_live_preview', TRUE),
+ );
+
+// $form['live_preview']['views_ui_always_live_preview_button'] = array(
+// '#type' => 'checkbox',
+// '#title' => t('Always show the preview button, even when the automatically update option is checked'),
+// '#default_value' => variable_get('views_ui_always_live_preview_button', FALSE),
+// );
+
+ $form['live_preview']['views_ui_show_preview_information'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show information and statistics about the view during live preview'),
+ '#default_value' => variable_get('views_ui_show_preview_information', TRUE),
+ );
+
+ $form['live_preview']['views_ui_show_sql_query_where'] = array(
+ '#type' => 'radios',
+ '#options' => array(
+ 'above' => t('Above the preview'),
+ 'below' => t('Below the preview'),
+ ),
+ '#id' => 'edit-show-sql',
+ '#default_value' => variable_get('views_ui_show_sql_query_where', 'above'),
+ '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
+ '#prefix' => '<div id="edit-show-sql-wrapper" class="views-dependent">',
+ '#suffix' => '</div>',
+ );
+
+ $form['live_preview']['views_ui_show_sql_query'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show the SQL query'),
+ '#default_value' => variable_get('views_ui_show_sql_query', FALSE),
+ '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
+ );
+ $form['live_preview']['views_ui_show_performance_statistics'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show performance statistics'),
+ '#default_value' => variable_get('views_ui_show_performance_statistics', FALSE),
+ '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
+ );
+
+ $form['live_preview']['views_show_additional_queries'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show other queries run during render during live preview'),
+ '#description' => t("Drupal has the potential to run many queries while a view is being rendered. Checking this box will display every query run during view render as part of the live preview."),
+ '#default_value' => variable_get('views_show_additional_queries', FALSE),
+ '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
+ );
+
+// $form['live_preview']['views_ui_show_performance_statistics_where'] = array(
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form builder for the advanced admin settings page.
+ */
+function views_ui_admin_settings_advanced() {
+ $form = array();
+ $form['#attached']['css'] = views_ui_get_admin_css();
+
+ $form['cache'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Caching'),
+ );
+
+ $form['cache']['views_skip_cache'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Disable views data caching'),
+ '#description' => t("Views caches data about tables, modules and views available, to increase performance. By checking this box, Views will skip this cache and always rebuild this data when needed. This can have a serious performance impact on your site."),
+ '#default_value' => variable_get('views_skip_cache', FALSE),
+ );
+
+ $form['cache']['clear_cache'] = array(
+ '#type' => 'submit',
+ '#value' => t("Clear Views' cache"),
+ '#submit' => array('views_ui_tools_clear_cache'),
+ );
+
+ $form['debug'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Debugging'),
+ );
+
+ $form['debug']['views_sql_signature'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add Views signature to all SQL queries'),
+ '#description' => t("All Views-generated queries will include the name of the views and display 'view-name:display-name' as a string at the end of the SELECT clause. This makes identifying Views queries in database server logs simpler, but should only be used when troubleshooting."),
+
+ '#default_value' => variable_get('views_sql_signature', FALSE),
+ );
+
+ $form['debug']['views_no_javascript'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Disable JavaScript with Views'),
+ '#description' => t("If you are having problems with the JavaScript, you can disable it here. The Views UI should degrade and still be usable without javascript; it's just not as good."),
+ '#default_value' => variable_get('views_no_javascript', FALSE),
+ );
+
+ $form['debug']['views_devel_output'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable views performance statistics/debug messages via the Devel module'),
+ '#description' => t("Check this to enable some Views query and performance statistics/debug messages <em>if Devel is installed</em>."),
+ '#default_value' => variable_get('views_devel_output', FALSE),
+ );
+
+ $form['locale'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Localization'),
+ );
+
+ $form['locale']['views_localization_plugin'] = array(
+ '#type' => 'radios',
+ '#title' => t('Translation method'),
+ '#options' => views_fetch_plugin_names('localization', NULL, array()),
+ '#default_value' => views_get_localization_plugin(),
+ '#description' => t('Select a translation method to use for Views data like header, footer, and empty text.'),
+ );
+
+ $form['locale']['views_localize_all'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use same translation method for exported views'),
+ '#description' => t('Exported views will use Core translation by default. Enable this to always use the configured translation method.'),
+ '#default_value' => variable_get('views_localize_all', FALSE),
+ );
+
+ $regions = array();
+ $regions['watchdog'] = t('Watchdog');
+ if (module_exists('devel')) {
+ $regions['message'] = t('Devel message(dpm)');
+ $regions['drupal_debug'] = t('Devel logging (tmp://drupal_debug.txt)');
+ }
+
+ $form['debug']['views_devel_region'] = array(
+ '#type' => 'select',
+ '#title' => t('Page region to output performance statistics/debug messages'),
+ '#default_value' => variable_get('views_devel_region', 'footer'),
+ '#options' => $regions,
+ '#dependency' => array('edit-views-devel-output' => array(1)),
+ );
+
+ $options = views_fetch_plugin_names('display_extender');
+ if (!empty($options)) {
+ $form['extenders'] = array(
+ '#type' => 'fieldset',
+ );
+ ;
+ $form['extenders']['views_display_extenders'] = array(
+ '#title' => t('Display extenders'),
+ '#default_value' => views_get_enabled_display_extenders(),
+ '#options' => $options,
+ '#type' => 'checkboxes',
+ '#description' => t('Select extensions of the views interface.')
+ );
+ }
+
+ return system_settings_form($form);
+}
+
+/**
+ * Submit hook to clear the views cache.
+ */
+function views_ui_tools_clear_cache() {
+ views_invalidate_cache();
+ drupal_set_message(t('The cache has been cleared.'));
+}
+
+/**
+ * Submit hook to clear Drupal's theme registry (thereby triggering
+ * a templates rescan).
+ */
+function views_ui_config_item_form_rescan($form, &$form_state) {
+ drupal_theme_rebuild();
+
+ // The 'Theme: Information' page is about to be shown again. That page
+ // analyzes the output of theme_get_registry(). However, this latter
+ // function uses an internal cache (which was initialized before we
+ // called drupal_theme_rebuild()) so it won't reflect the
+ // current state of our theme registry. The only way to clear that cache
+ // is to re-initialize the theme system:
+ unset($GLOBALS['theme']);
+ drupal_theme_initialize();
+
+ $form_state['rerender'] = TRUE;
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Override handler for views_ui_edit_display_form
+ */
+function views_ui_edit_display_form_change_theme($form, &$form_state) {
+ // This is just a temporary variable.
+ $form_state['view']->theme = $form_state['values']['theme'];
+
+ views_ui_cache_set($form_state['view']);
+ $form_state['rerender'] = TRUE;
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Page callback for views tag autocomplete
+ */
+function views_ui_autocomplete_tag($string = '') {
+ $matches = array();
+ // get matches from default views:
+ views_include('view');
+ $views = views_get_all_views();
+ foreach ($views as $view) {
+ if (!empty($view->tag) && strpos($view->tag, $string) === 0) {
+ $matches[$view->tag] = check_plain($view->tag);
+ if (count($matches) >= 10) {
+ break;
+ }
+ }
+ }
+
+ drupal_json_output($matches);
+}
+
+// ------------------------------------------------------------------
+// Get information from the Views data
+
+function _views_weight_sort($a, $b) {
+ if ($a['weight'] != $b['weight']) {
+ return $a['weight'] < $b['weight'] ? -1 : 1;
+ }
+ if ($a['title'] != $b['title']) {
+ return $a['title'] < $b['title'] ? -1 : 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Fetch a list of all base tables available
+ *
+ * @return
+ * A keyed array of in the form of 'base_table' => 'Description'.
+ */
+function views_fetch_base_tables() {
+ static $base_tables = array();
+ if (empty($base_tables)) {
+ $weights = array();
+ $tables = array();
+ $data = views_fetch_data();
+ foreach ($data as $table => $info) {
+ if (!empty($info['table']['base'])) {
+ $tables[$table] = array(
+ 'title' => $info['table']['base']['title'],
+ 'description' => !empty($info['table']['base']['help']) ? $info['table']['base']['help'] : '',
+ 'weight' => !empty($info['table']['base']['weight']) ? $info['table']['base']['weight'] : 0,
+ );
+ }
+ }
+ uasort($tables, '_views_weight_sort');
+ $base_tables = $tables;
+ }
+
+ return $base_tables;
+}
+
+function _views_sort_types($a, $b) {
+ $a_group = drupal_strtolower($a['group']);
+ $b_group = drupal_strtolower($b['group']);
+ if ($a_group != $b_group) {
+ return $a_group < $b_group ? -1 : 1;
+ }
+
+ $a_title = drupal_strtolower($a['title']);
+ $b_title = drupal_strtolower($b['title']);
+ if ($a_title != $b_title) {
+ return $a_title < $b_title ? -1 : 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Fetch a list of all fields available for a given base type.
+ *
+ * @param (array|string) $base
+ * A list or a single base_table, for example node.
+ * @param string $type
+ * The handler type, for example field or filter.
+ * @param bool $grouping
+ * Should the result grouping by its 'group' label.
+ *
+ * @return array
+ * A keyed array of in the form of 'base_table' => 'Description'.
+ */
+function views_fetch_fields($base, $type, $grouping = FALSE) {
+ static $fields = array();
+ if (empty($fields)) {
+ $data = views_fetch_data();
+ $start = microtime(TRUE);
+ // This constructs this ginormous multi dimensional array to
+ // collect the important data about fields. In the end,
+ // the structure looks a bit like this (using nid as an example)
+ // $strings['nid']['filter']['title'] = 'string'.
+ //
+ // This is constructed this way because the above referenced strings
+ // can appear in different places in the actual data structure so that
+ // the data doesn't have to be repeated a lot. This essentially lets
+ // each field have a cheap kind of inheritance.
+
+ foreach ($data as $table => $table_data) {
+ $bases = array();
+ $strings = array();
+ $skip_bases = array();
+ foreach ($table_data as $field => $info) {
+ // Collect table data from this table
+ if ($field == 'table') {
+ // calculate what tables this table can join to.
+ if (!empty($info['join'])) {
+ $bases = array_keys($info['join']);
+ }
+ // And it obviously joins to itself.
+ $bases[] = $table;
+ continue;
+ }
+ foreach (array('field', 'sort', 'filter', 'argument', 'relationship', 'area') as $key) {
+ if (!empty($info[$key])) {
+ if ($grouping && !empty($info[$key]['no group by'])) {
+ continue;
+ }
+ if (!empty($info[$key]['skip base'])) {
+ foreach ((array) $info[$key]['skip base'] as $base_name) {
+ $skip_bases[$field][$key][$base_name] = TRUE;
+ }
+ }
+ elseif (!empty($info['skip base'])) {
+ foreach ((array) $info['skip base'] as $base_name) {
+ $skip_bases[$field][$key][$base_name] = TRUE;
+ }
+ }
+ // Don't show old fields. The real field will be added right.
+ if (isset($info[$key]['moved to'])) {
+ continue;
+ }
+ foreach (array('title', 'group', 'help', 'base', 'aliases') as $string) {
+ // First, try the lowest possible level
+ if (!empty($info[$key][$string])) {
+ $strings[$field][$key][$string] = $info[$key][$string];
+ }
+ // Then try the field level
+ elseif (!empty($info[$string])) {
+ $strings[$field][$key][$string] = $info[$string];
+ }
+ // Finally, try the table level
+ elseif (!empty($table_data['table'][$string])) {
+ $strings[$field][$key][$string] = $table_data['table'][$string];
+ }
+ else {
+ if ($string != 'base' && $string != 'base') {
+ $strings[$field][$key][$string] = t("Error: missing @component", array('@component' => $string));
+ }
+ }
+ }
+ }
+ }
+ }
+ foreach ($bases as $base_name) {
+ foreach ($strings as $field => $field_strings) {
+ foreach ($field_strings as $type_name => $type_strings) {
+ if (empty($skip_bases[$field][$type_name][$base_name])) {
+ $fields[$base_name][$type_name]["$table.$field"] = $type_strings;
+ }
+ }
+ }
+ }
+ }
+// vsm('Views UI data build time: ' . (views_microtime() - $start) * 1000 . ' ms');
+ }
+
+ // If we have an array of base tables available, go through them
+ // all and add them together. Duplicate keys will be lost and that's
+ // Just Fine.
+ if (is_array($base)) {
+ $strings = array();
+ foreach ($base as $base_table) {
+ if (isset($fields[$base_table][$type])) {
+ $strings += $fields[$base_table][$type];
+ }
+ }
+ uasort($strings, '_views_sort_types');
+ return $strings;
+ }
+
+ if (isset($fields[$base][$type])) {
+ uasort($fields[$base][$type], '_views_sort_types');
+ return $fields[$base][$type];
+ }
+ return array();
+}
+
+
+/**
+ * Theme the form for the table style plugin
+ */
+function theme_views_ui_style_plugin_table($variables) {
+ $form = $variables['form'];
+
+ $output = drupal_render($form['description_markup']);
+
+ $header = array(
+ t('Field'),
+ t('Column'),
+ t('Align'),
+ t('Separator'),
+ array(
+ 'data' => t('Sortable'),
+ 'align' => 'center',
+ ),
+ array(
+ 'data' => t('Default order'),
+ 'align' => 'center',
+ ),
+ array(
+ 'data' => t('Default sort'),
+ 'align' => 'center',
+ ),
+ array(
+ 'data' => t('Hide empty column'),
+ 'align' => 'center',
+ ),
+ );
+ $rows = array();
+ foreach (element_children($form['columns']) as $id) {
+ $row = array();
+ $row[] = check_plain(drupal_render($form['info'][$id]['name']));
+ $row[] = drupal_render($form['columns'][$id]);
+ $row[] = drupal_render($form['info'][$id]['align']);
+ $row[] = drupal_render($form['info'][$id]['separator']);
+ if (!empty($form['info'][$id]['sortable'])) {
+ $row[] = array(
+ 'data' => drupal_render($form['info'][$id]['sortable']),
+ 'align' => 'center',
+ );
+ $row[] = array(
+ 'data' => drupal_render($form['info'][$id]['default_sort_order']),
+ 'align' => 'center',
+ );
+ $row[] = array(
+ 'data' => drupal_render($form['default'][$id]),
+ 'align' => 'center',
+ );
+ }
+ else {
+ $row[] = '';
+ $row[] = '';
+ $row[] = '';
+ }
+ $row[] = array(
+ 'data' => drupal_render($form['info'][$id]['empty_column']),
+ 'align' => 'center',
+ );
+ $rows[] = $row;
+ }
+
+ // Add the special 'None' row.
+ $rows[] = array(t('None'), '', '', '', '', '', array('align' => 'center', 'data' => drupal_render($form['default'][-1])), '');
+
+ $output .= theme('table', array('header' => $header, 'rows' => $rows));
+ $output .= drupal_render_children($form);
+ return $output;
+}
+
+/**
+ * Placeholder function for overriding $display->display_title.
+ *
+ * @todo Remove this function once editing the display title is possible.
+ */
+function views_ui_get_display_label($view, $display_id, $check_changed = TRUE) {
+ $title = $display_id == 'default' ? t('Master') : $view->display[$display_id]->display_title;
+ $title = views_ui_truncate($title, 25);
+
+ if ($check_changed && !empty($view->changed_display[$display_id])) {
+ $changed = '*';
+ $title = $title . $changed;
+ }
+
+ return $title;
+}
+
+function views_ui_add_template_page() {
+ $templates = views_get_all_templates();
+
+ if (empty($templates)) {
+ return t('There are no templates available.');
+ }
+
+ $header = array(
+ t('Name'),
+ t('Description'),
+ t('Operation'),
+ );
+
+ $rows = array();
+ foreach ($templates as $name => $template) {
+ $rows[] = array(
+ array('data' => check_plain($template->get_human_name())),
+ array('data' => check_plain($template->description)),
+ array('data' => l('add', 'admin/structure/views/template/' . $template->name . '/add')),
+ );
+ }
+
+ $output = theme('table', array('header' => $header, 'rows' => $rows));
+ return $output;
+}
+
+/**
+ * #process callback for a button; determines if a button is the form's triggering element.
+ *
+ * The Form API has logic to determine the form's triggering element based on
+ * the data in $_POST. However, it only checks buttons based on a single #value
+ * per button. This function may be added to a button's #process callbacks to
+ * extend button click detection to support multiple #values per button. If the
+ * data in $_POST matches any value in the button's #values array, then the
+ * button is detected as having been clicked. This can be used when the value
+ * (label) of the same logical button may be different based on context (e.g.,
+ * "Apply" vs. "Apply and continue").
+ *
+ * @see _form_builder_handle_input_element()
+ * @see _form_button_was_clicked()
+ */
+function views_ui_form_button_was_clicked($element, &$form_state) {
+ $process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])));
+ if ($process_input && !isset($form_state['triggering_element']) && isset($element['#button_type']) && isset($form_state['input'][$element['#name']]) && isset($element['#values']) && in_array($form_state['input'][$element['#name']], $element['#values'], TRUE)) {
+ $form_state['triggering_element'] = $element;
+ }
+ return $element;
+}
+
+/**
+ * #process callback for a button; makes implicit form submissions trigger as this button.
+ *
+ * @see Drupal.behaviors.viewsImplicitFormSubmission
+ */
+function views_ui_default_button($element, &$form_state, $form) {
+ $setting['viewsImplicitFormSubmission'][$form['#id']]['defaultButton'] = $element['#id'];
+ $element['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
+ return $element;
+}
+
+/**
+ * List all instances of fields on any views.
+ *
+ * Therefore it builds up a table of each field which is used in any view.
+ *
+ * @see field_ui_fields_list()
+ */
+function views_ui_field_list() {
+ $views = views_get_all_views();
+
+ // Fetch all fieldapi fields which are used in views
+ // Therefore search in all views, displays and handler-types.
+ $fields = array();
+ foreach ($views as $view) {
+ foreach ($view->display as $display_id => $display) {
+ if ($view->set_display($display_id)) {
+ foreach (views_object_types() as $type => $info) {
+ foreach ($view->get_items($type, $display_id) as $item) {
+ $data = views_fetch_data($item['table']);
+ if (isset($data[$item['field']]) && isset($data[$item['field']][$type])
+ && $data = $data[$item['field']][$type]) {
+ // The final check that we have a fieldapi field now.
+ if (isset($data['field_name'])) {
+ $fields[$data['field_name']][$view->name] = $view->name;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $header = array(t('Field name'), t('Used in'));
+ $rows = array();
+ foreach ($fields as $field_name => $views) {
+
+ $rows[$field_name]['data'][0] = check_plain($field_name);
+ foreach ($views as $view) {
+ $rows[$field_name]['data'][1][] = l($view, "admin/structure/views/view/$view");
+ }
+ $rows[$field_name]['data'][1] = implode(', ', $rows[$field_name]['data'][1]);
+ }
+
+ // Sort rows by field name.
+ ksort($rows);
+ $output = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $rows,
+ '#empty' => t('No fields have been used in views yet.'),
+ );
+
+ return $output;
+}
+
+/**
+ * Lists all plugins and what enabled Views use them.
+ */
+function views_ui_plugin_list() {
+ $rows = views_plugin_list();
+ foreach ($rows as &$row) {
+ // Link each view name to the view itself.
+ foreach ($row['views'] as $row_name => $view) {
+ $row['views'][$row_name] = l($view, "admin/structure/views/view/$view");
+ }
+ $row['views'] = implode(', ', $row['views']);
+ }
+
+ // Sort rows by field name.
+ ksort($rows);
+ return array(
+ '#theme' => 'table',
+ '#header' => array(t('Type'), t('Name'), t('Provided by'), t('Used in')),
+ '#rows' => $rows,
+ '#empty' => t('There are no enabled views.'),
+ );
+}
diff --git a/sites/all/modules/views/includes/ajax.inc b/sites/all/modules/views/includes/ajax.inc
new file mode 100644
index 000000000..8b882d904
--- /dev/null
+++ b/sites/all/modules/views/includes/ajax.inc
@@ -0,0 +1,380 @@
+<?php
+
+/**
+ * @file
+ * Handles the server side AJAX interactions of Views.
+ */
+
+/**
+ * @defgroup ajax Views AJAX library
+ * @{
+ * Handles the server side AJAX interactions of Views.
+ */
+
+/**
+ * Menu callback to load a view via AJAX.
+ */
+function views_ajax() {
+ if (isset($_REQUEST['view_name']) && isset($_REQUEST['view_display_id'])) {
+ $name = $_REQUEST['view_name'];
+ $display_id = $_REQUEST['view_display_id'];
+ $args = isset($_REQUEST['view_args']) && $_REQUEST['view_args'] !== '' ? explode('/', $_REQUEST['view_args']) : array();
+ $path = isset($_REQUEST['view_path']) ? rawurldecode($_REQUEST['view_path']) : NULL;
+ $dom_id = isset($_REQUEST['view_dom_id']) ? preg_replace('/[^a-zA-Z0-9_-]+/', '-', $_REQUEST['view_dom_id']) : NULL;
+ $pager_element = isset($_REQUEST['pager_element']) ? intval($_REQUEST['pager_element']) : NULL;
+
+ $commands = array();
+
+ // Remove all of this stuff from $_GET so it doesn't end up in pagers and tablesort URLs.
+ foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', 'ajax_html_ids', 'ajax_page_state') as $key) {
+ if (isset($_GET[$key])) {
+ unset($_GET[$key]);
+ }
+ if (isset($_REQUEST[$key])) {
+ unset($_REQUEST[$key]);
+ }
+ if (isset($_POST[$key])) {
+ unset($_POST[$key]);
+ }
+ }
+
+ // Load the view.
+ $view = views_get_view($name);
+ if ($view && $view->access($display_id)) {
+ // Fix 'q' for paging.
+ if (!empty($path)) {
+ $_GET['q'] = $path;
+ }
+
+ // Add all $_POST data, because AJAX is always a post and many things,
+ // such as tablesorts, exposed filters and paging assume $_GET.
+ $_GET = $_POST + $_GET;
+
+ // Overwrite the destination.
+ // @see drupal_get_destination()
+ $origin_destination = $path;
+ $query = drupal_http_build_query(drupal_get_query_parameters());
+ if ($query != '') {
+ $origin_destination .= '?' . $query;
+ }
+ $destination = &drupal_static('drupal_get_destination');
+ $destination = array('destination' => $origin_destination);
+
+ // Override the display's pager_element with the one actually used.
+ if (isset($pager_element)) {
+ $commands[] = views_ajax_command_scroll_top('.view-dom-id-' . $dom_id);
+ $view->display[$display_id]->handler->set_option('pager_element', $pager_element);
+ }
+ // Reuse the same DOM id so it matches that in Drupal.settings.
+ $view->dom_id = $dom_id;
+
+ $commands[] = ajax_command_replace('.view-dom-id-' . $dom_id, $view->preview($display_id, $args));
+ }
+ drupal_alter('views_ajax_data', $commands, $view);
+ return array('#type' => 'ajax', '#commands' => $commands);
+ }
+}
+
+/**
+ * Creates a Drupal AJAX 'viewsSetForm' command.
+ *
+ * @param $output
+ * The form to display in the modal.
+ * @param $title
+ * The title.
+ * @param $url
+ * An optional URL.
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ */
+function views_ajax_command_set_form($output, $title, $url = NULL) {
+ $command = array(
+ 'command' => 'viewsSetForm',
+ 'output' => $output,
+ 'title' => $title,
+ );
+ if (isset($url)) {
+ $command['url'] = $url;
+ }
+ return $command;
+}
+
+/**
+ * Creates a Drupal AJAX 'viewsDismissForm' command.
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ */
+function views_ajax_command_dismiss_form() {
+ $command = array(
+ 'command' => 'viewsDismissForm',
+ );
+ return $command;
+}
+
+/**
+ * Creates a Drupal AJAX 'viewsHilite' command.
+ *
+ * @param $selector
+ * The selector to highlight
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ */
+function views_ajax_command_hilite($selector) {
+ return array(
+ 'command' => 'viewsHilite',
+ 'selector' => $selector,
+ );
+}
+
+/**
+ * Creates a Drupal AJAX 'addTab' command.
+ *
+ * @param $id
+ * The DOM ID.
+ * @param $title
+ * The title.
+ * @param $body
+ * The body.
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ */
+function views_ajax_command_add_tab($id, $title, $body) {
+ $command = array(
+ 'command' => 'viewsAddTab',
+ 'id' => $id,
+ 'title' => $title,
+ 'body' => $body,
+ );
+ return $command;
+}
+
+/**
+ * Scroll to top of the current view.
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ */
+function views_ajax_command_scroll_top($selector) {
+ $command = array(
+ 'command' => 'viewsScrollTop',
+ 'selector' => $selector,
+ );
+ return $command;
+}
+
+/**
+ * Shows Save and Cancel buttons.
+ *
+ * @param bool $changed
+ * Whether of not the view has changed.
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ */
+function views_ajax_command_show_buttons($changed) {
+ $command = array(
+ 'command' => 'viewsShowButtons',
+ 'changed' => (bool) $changed,
+ );
+ return $command;
+}
+
+/**
+ * Trigger the Views live preview.
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ */
+function views_ajax_command_trigger_preview() {
+ $command = array(
+ 'command' => 'viewsTriggerPreview',
+ );
+ return $command;
+}
+
+/**
+ * Replace the page title.
+ *
+ * @return
+ * An array suitable for use with the ajax_render() function.
+ */
+function views_ajax_command_replace_title($title) {
+ $command = array(
+ 'command' => 'viewsReplaceTitle',
+ 'title' => $title,
+ 'siteName' => variable_get('site_name', 'Drupal'),
+ );
+ return $command;
+}
+
+/**
+ * Return an AJAX error.
+ */
+function views_ajax_error($message) {
+ $commands = array();
+ $commands[] = views_ajax_command_set_form($message, t('Error'));
+ return $commands;
+}
+
+/**
+ * Wrapper around drupal_build_form to handle some AJAX stuff automatically.
+ * This makes some assumptions about the client.
+ */
+function views_ajax_form_wrapper($form_id, &$form_state) {
+ ctools_include('dependent');
+
+ // This won't override settings already in.
+ $form_state += array(
+ 'rerender' => FALSE,
+ 'no_redirect' => !empty($form_state['ajax']),
+ 'no_cache' => TRUE,
+ 'build_info' => array(
+ 'args' => array(),
+ ),
+ );
+
+ $form = drupal_build_form($form_id, $form_state);
+ $output = drupal_render($form);
+
+ // These forms have the title built in, so set the title here:
+ if (empty($form_state['ajax']) && !empty($form_state['title'])) {
+ drupal_set_title($form_state['title']);
+ drupal_add_css(drupal_get_path('module', 'views_ui') . '/css/views-admin.css');
+ }
+
+ if (!empty($form_state['ajax']) && (empty($form_state['executed']) || !empty($form_state['rerender']))) {
+ // If the form didn't execute and we're using ajax, build up a
+ // Ajax command list to execute.
+ $commands = array();
+
+ $display = '';
+ if ($messages = theme('status_messages')) {
+ $display = '<div class="views-messages">' . $messages . '</div>';
+ }
+ $display .= $output;
+
+ $title = empty($form_state['title']) ? '' : $form_state['title'];
+ if (!empty($form_state['help_topic'])) {
+ $module = !empty($form_state['help_module']) ? $form_state['help_module'] : 'views';
+ if (module_exists('advanced_help')) {
+ $title = theme('advanced_help_topic', array('module' => $module, 'topic' => $form_state['help_topic'])) . $title;
+ }
+ }
+
+ $url = empty($form_state['url']) ? url($_GET['q'], array('absolute' => TRUE)) : $form_state['url'];
+
+ $commands[] = views_ajax_command_set_form($display, $title, $url);
+
+ if (!empty($form_state['#section'])) {
+ $commands[] = views_ajax_command_hilite('.' . drupal_clean_css_identifier($form_state['#section']));
+ }
+
+ return $commands;
+ }
+
+ // These forms have the title built in, so set the title here:
+ if (empty($form_state['ajax']) && !empty($form_state['title'])) {
+ drupal_set_title($form_state['title']);
+ }
+
+ return $output;
+}
+
+
+/**
+ * Page callback for views user autocomplete
+ */
+function views_ajax_autocomplete_user($string = '') {
+ // The user enters a comma-separated list of user name. We only autocomplete the last name.
+ $array = drupal_explode_tags($string);
+
+ // Fetch last name
+ $last_string = trim(array_pop($array));
+ $matches = array();
+ if ($last_string != '') {
+ $prefix = count($array) ? implode(', ', $array) . ', ' : '';
+
+ if (strpos('anonymous', strtolower($last_string)) !== FALSE) {
+ $matches[$prefix . 'Anonymous'] = 'Anonymous';
+ }
+
+ $result = db_select('users', 'u')
+ ->fields('u', array('uid', 'name'))
+ ->condition('u.name', db_like($last_string) . '%', 'LIKE')
+ ->range(0, 10)
+ ->execute()
+ ->fetchAllKeyed();
+
+ foreach ($result as $account) {
+ $n = $account;
+ // Commas and quotes in terms are special cases, so encode 'em.
+ if (strpos($account, ',') !== FALSE || strpos($account, '"') !== FALSE) {
+ $n = '"' . str_replace('"', '""', $account) . '"';
+ }
+ $matches[$prefix . $n] = check_plain($account);
+ }
+ }
+
+ drupal_json_output($matches);
+}
+
+/**
+ * Page callback for views taxonomy autocomplete.
+ *
+ * @param $vid
+ * The vocabulary id of the tags which should be returned.
+ *
+ * @param $tags_typed
+ * The typed string of the user.
+ *
+ * @see taxonomy_autocomplete()
+ */
+function views_ajax_autocomplete_taxonomy($vid, $tags_typed = '') {
+ // The user enters a comma-separated list of tags. We only autocomplete the last tag.
+ $tags_typed = drupal_explode_tags($tags_typed);
+ $tag_last = drupal_strtolower(array_pop($tags_typed));
+
+ $matches = array();
+ if ($tag_last != '') {
+
+ $query = db_select('taxonomy_term_data', 't');
+ $query->addTag('translatable');
+ $query->addTag('term_access');
+
+ // Do not select already entered terms.
+ if (!empty($tags_typed)) {
+ $query->condition('t.name', $tags_typed, 'NOT IN');
+ }
+ // Select rows that match by term name.
+ $tags_return = $query
+ ->fields('t', array('tid', 'name'))
+ ->condition('t.vid', $vid)
+ ->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE')
+ ->range(0, 10)
+ ->execute()
+ ->fetchAllKeyed();
+
+ $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
+
+ $term_matches = array();
+ foreach ($tags_return as $tid => $name) {
+ $n = $name;
+ // Term names containing commas or quotes must be wrapped in quotes.
+ if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
+ $n = '"' . str_replace('"', '""', $name) . '"';
+ }
+ // Add term name to list of matches.
+ $term_matches[$prefix . $n] = check_plain($name);
+ }
+ }
+
+ drupal_json_output($term_matches);
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/includes/analyze.inc b/sites/all/modules/views/includes/analyze.inc
new file mode 100644
index 000000000..68ee334bc
--- /dev/null
+++ b/sites/all/modules/views/includes/analyze.inc
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains the view analyze tool code.
+ *
+ * This tool is a small plugin manager to perform analysis on a view and
+ * report results to the user. This tool is meant to let modules that
+ * provide data to Views also help users properly use that data by
+ * detecting invalid configurations. Views itself comes with only a
+ * small amount of analysis tools, but more could easily be added either
+ * by modules or as patches to Views itself.
+ */
+
+/**
+ * Analyze a review and return the results.
+ *
+ * @return
+ * An array of analyze results organized into arrays keyed by 'ok',
+ * 'warning' and 'error'.
+ */
+function views_analyze_view(&$view) {
+ $view->init_display();
+ $messages = module_invoke_all('views_analyze', $view);
+
+ return $messages;
+}
+
+/**
+ * Format the analyze result into a message string.
+ *
+ * This is based upon the format of drupal_set_message which uses separate
+ * boxes for "ok", "warning" and "error".
+ */
+function views_analyze_format_result($view, $messages) {
+ if (empty($messages)) {
+ $messages = array(views_ui_analysis(t('View analysis can find nothing to report.'), 'ok'));
+ }
+
+ $types = array('ok' => array(), 'warning' => array(), 'error' => array());
+ foreach ($messages as $message) {
+ if (empty($types[$message['type']])) {
+ $types[$message['type']] = array();
+ }
+ $types[$message['type']][] = $message['message'];
+ }
+
+ $output = '';
+ foreach ($types as $type => $messages) {
+ $type .= ' messages';
+ $message = '';
+ if (count($messages) > 1) {
+ $message = theme('item_list', array('items' => $messages));
+ }
+ elseif ($messages) {
+ $message = array_shift($messages);
+ }
+
+ if ($message) {
+ $output .= "<div class=\"$type\">$message</div>";
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Format an analysis message.
+ *
+ * This tool should be called by any module responding to the analyze hook
+ * to properly format the message. It is usually used in the form:
+ * @code
+ * $ret[] = views_ui_analysis(t('This is the message'), 'ok');
+ * @endcode
+ *
+ * The 'ok' status should be used to provide information about things
+ * that are acceptable. In general analysis isn't interested in 'ok'
+ * messages, but instead the 'warning', which is a category for items
+ * that may be broken unless the user knows what he or she is doing,
+ * and 'error' for items that are definitely broken are much more useful.
+ *
+ * @param $messages
+ * The message to report.
+ * @param $type
+ * The type of message. This should be "ok", "warning" or "error". Other
+ * values can be used but how they are treated by the output routine
+ * is undefined.
+ */
+function views_ui_analysis($message, $type = 'error') {
+ return array('message' => $message, 'type' => $type);
+}
+
+/**
+ * Implements hook_views_analyze().
+ *
+ * This is the basic views analysis that checks for very minimal problems.
+ * There are other analysis tools in core specific sections, such as
+ * node.views.inc as well.
+ */
+function views_ui_views_analyze($view) {
+ $ret = array();
+ // Check for something other than the default display:
+ if (count($view->display) < 2) {
+ $ret[] = views_ui_analysis(t('This view has only a default display and therefore will not be placed anywhere on your site; perhaps you want to add a page or a block display.'), 'warning');
+ }
+ // You can give a page display the same path as an alias existing in the
+ // system, so the alias will not work anymore. Report this to the user,
+ // because he probably wanted something else.
+ foreach ($view->display as $id => $display) {
+ if (empty($display->handler)) {
+ continue;
+ }
+ if ($display->handler->has_path() && $path = $display->handler->get_option('path')) {
+ $normal_path = drupal_get_normal_path($path);
+ if ($path != $normal_path) {
+ $ret[] = views_ui_analysis(t('You have configured display %display with a path which is an path alias as well. This might lead to unwanted effects so better use an internal path.', array('%display' => $display->display_title)), 'warning');
+ }
+ }
+ }
+
+ return $ret;
+}
diff --git a/sites/all/modules/views/includes/base.inc b/sites/all/modules/views/includes/base.inc
new file mode 100644
index 000000000..2d2ceb56d
--- /dev/null
+++ b/sites/all/modules/views/includes/base.inc
@@ -0,0 +1,359 @@
+<?php
+
+/**
+ * @file
+ * Provides the basic object definitions used by plugins and handlers.
+ */
+
+/**
+ * Basic definition for many views objects.
+ */
+class views_object {
+ /**
+ * Except for displays, options for the object will be held here.
+ */
+ var $options = array();
+
+ /**
+ * The top object of a view.
+ *
+ * @var view
+ */
+ var $view = NULL;
+
+ /**
+ * Handler's definition
+ *
+ * @var array
+ */
+ var $definition;
+
+ /**
+ * Information about options for all kinds of purposes will be held here.
+ * @code
+ * 'option_name' => array(
+ * - 'default' => default value,
+ * - 'translatable' => (optional) TRUE/FALSE (wrap in t() on export if true),
+ * - 'contains' => (optional) array of items this contains, with its own
+ * defaults, etc. If contains is set, the default will be ignored and
+ * assumed to be array().
+ * - 'bool' => (optional) TRUE/FALSE Is the value a boolean value. This will
+ * change the export format to TRUE/FALSE instead of 1/0.
+ * - 'export' => (optional) FALSE or a callback for special export handling
+ * if necessary.
+ * - 'unpack_translatable' => (optional) callback for special handling for
+ * translating data within the option, if necessary.
+ * ),
+ *
+ * @return array
+ * Returns the options of this handler/plugin.
+ *
+ * @see views_object::export_option()
+ * @see views_object::export_option_always()
+ * @see views_object::unpack_translatable()
+ */
+ function option_definition() { return array(); }
+
+ /**
+ * Views handlers use a special construct function so that we can more
+ * easily construct them with variable arguments.
+ */
+ function construct() { $this->set_default_options(); }
+
+ /**
+ * Set default options on this object. Called by the constructor in a
+ * complex chain to deal with backward compatibility.
+ *
+ * @deprecated since views2
+ */
+ function options(&$options) { }
+
+ /**
+ * Set default options.
+ * For backward compatibility, it sends the options array; this is a
+ * feature that will likely disappear at some point.
+ */
+ function set_default_options() {
+ $this->_set_option_defaults($this->options, $this->option_definition());
+
+ // Retained for complex defaults plus backward compatibility.
+ $this->options($this->options);
+ }
+
+ function _set_option_defaults(&$storage, $options, $level = 0) {
+ foreach ($options as $option => $definition) {
+ if (isset($definition['contains']) && is_array($definition['contains'])) {
+ $storage[$option] = array();
+ $this->_set_option_defaults($storage[$option], $definition['contains'], $level++);
+ }
+ elseif (!empty($definition['translatable']) && !empty($definition['default'])) {
+ $storage[$option] = t($definition['default']);
+ }
+ else {
+ $storage[$option] = isset($definition['default']) ? $definition['default'] : NULL;
+ }
+ }
+ }
+
+ /**
+ * Unpack options over our existing defaults, drilling down into arrays
+ * so that defaults don't get totally blown away.
+ */
+ function unpack_options(&$storage, $options, $definition = NULL, $all = TRUE, $check = TRUE, $localization_keys = array()) {
+ if ($check && !is_array($options)) {
+ return;
+ }
+
+ if (!isset($definition)) {
+ $definition = $this->option_definition();
+ }
+
+ if (!empty($this->view)) {
+ // Ensure we have a localization plugin.
+ $this->view->init_localization();
+
+ // Set up default localization keys. Handlers and such set this for us
+ if (empty($localization_keys) && isset($this->localization_keys)) {
+ $localization_keys = $this->localization_keys;
+ }
+ // but plugins don't because there isn't a common init() these days.
+ else if (!empty($this->is_plugin) && empty($localization_keys)) {
+ if ($this->plugin_type != 'display') {
+ $localization_keys = array($this->view->current_display);
+ $localization_keys[] = $this->plugin_type;
+ }
+ }
+ }
+
+ foreach ($options as $key => $value) {
+ if (is_array($value)) {
+ // Ignore arrays with no definition.
+ if (!$all && empty($definition[$key])) {
+ continue;
+ }
+
+ if (!isset($storage[$key]) || !is_array($storage[$key])) {
+ $storage[$key] = array();
+ }
+
+ // If we're just unpacking our known options, and we're dropping an
+ // unknown array (as might happen for a dependent plugin fields) go
+ // ahead and drop that in.
+ if (!$all && isset($definition[$key]) && !isset($definition[$key]['contains'])) {
+ $storage[$key] = $value;
+ continue;
+ }
+
+ $this->unpack_options($storage[$key], $value, isset($definition[$key]['contains']) ? $definition[$key]['contains'] : array(), $all, FALSE, array_merge($localization_keys, array($key)));
+ }
+ // Don't localize strings during editing. When editing, we need to work with
+ // the original data, not the translated version.
+ else if (empty($this->view->editing) && !empty($definition[$key]['translatable']) && !empty($value) || !empty($definition['contains'][$key]['translatable']) && !empty($value)) {
+ if (!empty($this->view) && $this->view->is_translatable()) {
+ // Allow other modules to make changes to the string before it's
+ // sent for translation.
+ // The $keys array is built from the view name, any localization keys
+ // sent in, and the name of the property being processed.
+ $format = NULL;
+ if (isset($definition[$key]['format_key']) && isset($options[$definition[$key]['format_key']])) {
+ $format = $options[$definition[$key]['format_key']];
+ }
+ $translation_data = array(
+ 'value' => $value,
+ 'format' => $format,
+ 'keys' => array_merge(array($this->view->name), $localization_keys, array($key)),
+ );
+ $storage[$key] = $this->view->localization_plugin->translate($translation_data);
+ }
+ // Otherwise, this is a code-based string, so we can use t().
+ else {
+ $storage[$key] = t($value);
+ }
+ }
+ else if ($all || !empty($definition[$key])) {
+ $storage[$key] = $value;
+ }
+ }
+ }
+
+ /**
+ * Let the handler know what its full definition is.
+ */
+ function set_definition($definition) {
+ $this->definition = $definition;
+ if (isset($definition['field'])) {
+ $this->real_field = $definition['field'];
+ }
+ }
+
+ function destroy() {
+ if (isset($this->view)) {
+ unset($this->view);
+ }
+
+ if (isset($this->display)) {
+ unset($this->display);
+ }
+
+ if (isset($this->query)) {
+ unset($this->query);
+ }
+ }
+
+ function export_options($indent, $prefix) {
+ $output = '';
+ foreach ($this->option_definition() as $option => $definition) {
+ $output .= $this->export_option($indent, $prefix, $this->options, $option, $definition, array());
+ }
+
+ return $output;
+ }
+
+ function export_option($indent, $prefix, $storage, $option, $definition, $parents) {
+ // Do not export options for which we have no settings.
+ if (!isset($storage[$option])) {
+ return;
+ }
+
+ if (isset($definition['export'])) {
+ if ($definition['export'] === FALSE) {
+ return;
+ }
+
+ // Special handling for some items
+ if (method_exists($this, $definition['export'])) {
+ return $this->{$definition['export']}($indent, $prefix, $storage, $option, $definition, $parents);
+ }
+ }
+
+ // Add the current option to the parents tree.
+ $parents[] = $option;
+ $output = '';
+
+ // If it has child items, export those separately.
+ if (isset($definition['contains'])) {
+ foreach ($definition['contains'] as $sub_option => $sub_definition) {
+ $output .= $this->export_option($indent, $prefix, $storage[$option], $sub_option, $sub_definition, $parents);
+ }
+ }
+ // Otherwise export just this item.
+ else {
+ $default = isset($definition['default']) ? $definition['default'] : NULL;
+ $value = $storage[$option];
+ if (isset($definition['bool'])) {
+ $value = (bool) $value;
+ }
+
+ if ($value !== $default) {
+ $output .= $indent . $prefix . "['" . implode("']['", $parents) . "'] = ";
+ if (isset($definition['bool'])) {
+ $output .= empty($storage[$option]) ? 'FALSE' : 'TRUE';
+ }
+ else {
+ $output .= views_var_export($storage[$option], $indent);
+ }
+
+ $output .= ";\n";
+ }
+ }
+ return $output;
+ }
+
+ /**
+ * Always exports the option, regardless of the default value.
+ */
+ function export_option_always($indent, $prefix, $storage, $option, $definition, $parents) {
+ // If there is no default, the option will always be exported.
+ unset($definition['default']);
+ // Unset our export method to prevent recursion.
+ unset($definition['export']);
+ return $this->export_option($indent, $prefix, $storage, $option, $definition, $parents);
+ }
+
+ /**
+ * Unpacks each handler to store translatable texts.
+ */
+ function unpack_translatables(&$translatable, $parents = array()) {
+ foreach ($this->option_definition() as $option => $definition) {
+ $this->unpack_translatable($translatable, $this->options, $option, $definition, $parents, array());
+ }
+ }
+
+ /**
+ * Unpack a single option definition.
+ *
+ * This function run's through all suboptions recursive.
+ *
+ * @param $translatable
+ * Stores all available translatable items.
+ * @param $storage
+ * @param $option
+ * @param $definition
+ * @param $parents
+ * @param $keys
+ */
+ function unpack_translatable(&$translatable, $storage, $option, $definition, $parents, $keys = array()) {
+ // Do not export options for which we have no settings.
+ if (!isset($storage[$option])) {
+ return;
+ }
+
+ // Special handling for some items
+ if (isset($definition['unpack_translatable']) && method_exists($this, $definition['unpack_translatable'])) {
+ return $this->{$definition['unpack_translatable']}($translatable, $storage, $option, $definition, $parents, $keys);
+ }
+
+ if (isset($definition['translatable'])) {
+ if ($definition['translatable'] === FALSE) {
+ return;
+ }
+ }
+
+ // Add the current option to the parents tree.
+ $parents[] = $option;
+
+ // If it has child items, unpack those separately.
+ if (isset($definition['contains'])) {
+ foreach ($definition['contains'] as $sub_option => $sub_definition) {
+ $translation_keys = array_merge($keys, array($sub_option));
+ $this->unpack_translatable($translatable, $storage[$option], $sub_option, $sub_definition, $parents, $translation_keys);
+ }
+ }
+
+ // @todo Figure out this double definition stuff.
+ $options = $storage[$option];
+ if (is_array($options)) {
+ foreach ($options as $key => $value) {
+ $translation_keys = array_merge($keys, array($key));
+ if (is_array($value)) {
+ $this->unpack_translatable($translatable, $options, $key, $definition, $parents, $translation_keys);
+ }
+ else if (!empty($definition[$key]['translatable']) && !empty($value)) {
+ // Build source data and add to the array
+ $format = NULL;
+ if (isset($definition['format_key']) && isset($options[$definition['format_key']])) {
+ $format = $options[$definition['format_key']];
+ }
+ $translatable[] = array(
+ 'value' => $value,
+ 'keys' => $translation_keys,
+ 'format' => $format,
+ );
+ }
+ }
+ }
+ else if (!empty($definition['translatable']) && !empty($options)) {
+ $value = $options;
+ // Build source data and add to the array
+ $format = NULL;
+ if (isset($definition['format_key']) && isset($storage[$definition['format_key']])) {
+ $format = $storage[$definition['format_key']];
+ }
+ $translatable[] = array(
+ 'value' => $value,
+ 'keys' => isset($translation_keys) ? $translation_keys : $parents,
+ 'format' => $format,
+ );
+ }
+ }
+}
diff --git a/sites/all/modules/views/includes/cache.inc b/sites/all/modules/views/includes/cache.inc
new file mode 100644
index 000000000..98145c5a4
--- /dev/null
+++ b/sites/all/modules/views/includes/cache.inc
@@ -0,0 +1,217 @@
+<?php
+
+/**
+ * @file
+ * Load Views' data so that it knows what is available to build queries from.
+ */
+
+/**
+ * Fetch Views' data from the cache
+ *
+ * @param $move
+ * Under certain circumstances it makes sense to not get the moved table, but the old one.
+ * One example is views_get_handler.
+ */
+function _views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
+ $cache = &drupal_static(__FUNCTION__ . '_cache');
+ $recursion_protection = &drupal_static(__FUNCTION__ . '_recursion_protected');
+ $fully_loaded = &drupal_static(__FUNCTION__ . '_fully_loaded');
+ if ($reset) {
+ $cache = NULL;
+ $fully_loaded = FALSE;
+ }
+ if ($table) {
+ if (!isset($cache[$table])) {
+ $cid = 'views_data:' . $table;
+ if ($data = views_cache_get($cid, TRUE)) {
+ $cache[$table] = $data->data;
+ }
+ else {
+ if (!$fully_loaded) {
+ // Try to load the full views cache.
+ if ($data = views_cache_get('views_data', TRUE)) {
+ $cache = $data->data;
+ }
+ else {
+ // No cache entry, rebuild.
+ $cache = _views_fetch_data_build();
+ }
+ $fully_loaded = TRUE;
+ }
+
+ // Write back a cache for this table.
+ if (isset($cache[$table])) {
+ views_cache_set($cid, $cache[$table], TRUE);
+ }
+ else {
+ // If there is still no information about that table, it is missing.
+ // Write an empty array to avoid repeated rebuilds.
+ views_cache_set($cid, array(), TRUE);
+ }
+ }
+ }
+ if (isset($cache[$table])) {
+ if (isset($cache[$table]['moved to']) && $move) {
+ $moved_table = $cache[$table]['moved to'];
+ if (!empty($recursion_protection[$table])) {
+ // recursion detected!
+ return NULL;
+ }
+ $recursion_protection[$table] = TRUE;
+ $data = _views_fetch_data($moved_table);
+ $recursion_protection = array();
+ return $data;
+ }
+ return $cache[$table];
+ }
+ }
+ else {
+ if (!$fully_loaded) {
+ if ($data = views_cache_get('views_data', TRUE)) {
+ $cache = $data->data;
+ }
+ else {
+ // No cache entry, rebuild.
+ $cache = _views_fetch_data_build();
+ }
+ $fully_loaded = TRUE;
+ }
+ return $cache;
+ }
+ // Return an empty array if there is no match.
+ return array();
+}
+
+/**
+ * Build and set the views data cache if empty.
+ */
+function _views_fetch_data_build() {
+ views_include_handlers();
+ $cache = module_invoke_all('views_data');
+ foreach (module_implements('views_data_alter') as $module) {
+ $function = $module . '_views_data_alter';
+ $function($cache);
+ }
+ _views_data_process_entity_types($cache);
+
+ // Keep a record with all data.
+ views_cache_set('views_data', $cache, TRUE);
+ return $cache;
+}
+
+/**
+ * Links tables having an 'entity type' specified to the respective generic entity-type tables.
+ */
+function _views_data_process_entity_types(&$data) {
+ foreach ($data as $table_name => $table_info) {
+ // Add in a join from the entity-table if an entity-type is given.
+ if (!empty($table_info['table']['entity type'])) {
+ $entity_table = 'views_entity_' . $table_info['table']['entity type'];
+
+ $data[$entity_table]['table']['join'][$table_name] = array(
+ 'left_table' => $table_name,
+ );
+ $data[$entity_table]['table']['entity type'] = $table_info['table']['entity type'];
+ // Copy over the default table group if we have none yet.
+ if (!empty($table_info['table']['group']) && empty($data[$entity_table]['table']['group'])) {
+ $data[$entity_table]['table']['group'] = $table_info['table']['group'];
+ }
+ }
+ }
+}
+
+/**
+ * Fetch the plugin data from cache.
+ */
+function _views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
+ static $cache = NULL;
+ if (!isset($cache) || $reset) {
+ // Load necessary code once.
+ if (!isset($cache)) {
+ views_include('plugins');
+ views_include_handlers();
+ }
+ // Because plugin data contains translated strings, and as such can be
+ // expensive to build, the results are cached per language.
+ global $language;
+ $cache_key = 'views:plugin_data:' . $language->language;
+ if (!$reset) {
+ if ($cache = cache_get($cache_key)) {
+ $cache = $cache->data;
+ }
+ }
+ // If not available in the cache, build it and cache it.
+ if (!$cache) {
+ $cache = views_discover_plugins();
+ cache_set($cache_key, $cache);
+ }
+ }
+
+ if (!$type && !$plugin) {
+ return $cache;
+ }
+ elseif (!$plugin) {
+ // Not in the if above so the else below won't run
+ if (isset($cache[$type])) {
+ return $cache[$type];
+ }
+ }
+ elseif (isset($cache[$type][$plugin])) {
+ return $cache[$type][$plugin];
+ }
+
+ // Return an empty array if there is no match.
+ return array();
+}
+
+/**
+ * Set a cached item in the views cache.
+ *
+ * This is just a convenience wrapper around cache_set().
+ *
+ * @param $cid
+ * The cache ID of the data to store.
+ * @param $data
+ * The data to store in the cache. Complex data types will be automatically serialized before insertion.
+ * Strings will be stored as plain text and not serialized.
+ * @param $use_language
+ * If TRUE, the data will be cached specific to the currently active language.
+ */
+function views_cache_set($cid, $data, $use_language = FALSE) {
+ global $language;
+
+ if (variable_get('views_skip_cache', FALSE)) {
+ return;
+ }
+ if ($use_language) {
+ $cid .= ':' . $language->language;
+ }
+
+ cache_set($cid, $data, 'cache_views');
+}
+
+/**
+ * Return data from the persistent views cache.
+ *
+ * This is just a convenience wrapper around cache_get().
+ *
+ * @param int $cid
+ * The cache ID of the data to retrieve.
+ * @param bool $use_language
+ * If TRUE, the data will be requested specific to the currently active language.
+ *
+ * @return stdClass|bool
+ * The cache or FALSE on failure.
+ */
+function views_cache_get($cid, $use_language = FALSE) {
+ global $language;
+
+ if (variable_get('views_skip_cache', FALSE)) {
+ return FALSE;
+ }
+ if ($use_language) {
+ $cid .= ':' . $language->language;
+ }
+
+ return cache_get($cid, 'cache_views');
+}
diff --git a/sites/all/modules/views/includes/handlers.inc b/sites/all/modules/views/includes/handlers.inc
new file mode 100644
index 000000000..d4781d8a8
--- /dev/null
+++ b/sites/all/modules/views/includes/handlers.inc
@@ -0,0 +1,1712 @@
+<?php
+
+/**
+ * @file
+ * Defines the various handler objects to help build and display views.
+ */
+
+/**
+ * Instantiate and construct a new handler
+ */
+function _views_create_handler($definition, $type = 'handler', $handler_type = NULL) {
+// debug('Instantiating handler ' . $definition['handler']);
+ if (empty($definition['handler'])) {
+ vpr('_views_create_handler - type: @type - failed: handler has not been provided.',
+ array('@type' => isset($handler_type) ? ( $type . '(handler type: ' . $handler_type . ')' ) : $type)
+ );
+ return;
+ }
+
+ // class_exists will automatically load the code file.
+ if (!empty($definition['override handler']) &&
+ !class_exists($definition['override handler'])) {
+ vpr(
+ '_views_create_handler - loading override handler @type failed: class @override_handler could not be loaded. ' .
+ 'Verify the class file has been registered in the corresponding .info-file (files[]).',
+ array(
+ '@type' => isset($handler_type) ? ( $type . '(handler type: ' . $handler_type . ')' ) : $type,
+ '@override_handler' => $definition['override handler']
+ )
+ );
+ return;
+ }
+
+ if (!class_exists($definition['handler'])) {
+ vpr(
+ '_views_create_handler - loading handler @type failed: class @handler could not be loaded. ' .
+ 'Verify the class file has been registered in the corresponding .info-file (files[]).',
+ array(
+ '@type' => isset($handler_type) ? ( $type . '(handler type: ' . $handler_type . ')' ) : $type,
+ '@handler' => $definition['handler']
+ )
+ );
+ return;
+ }
+
+ if (!empty($definition['override handler'])) {
+ $handler = new $definition['override handler'];
+ }
+ else {
+ $handler = new $definition['handler'];
+ }
+
+ $handler->set_definition($definition);
+ if ($type == 'handler') {
+ $handler->is_handler = TRUE;
+ $handler->handler_type = $handler_type;
+ }
+ else {
+ $handler->is_plugin = TRUE;
+ $handler->plugin_type = $type;
+ $handler->plugin_name = $definition['name'];
+ }
+
+ // let the handler have something like a constructor.
+ $handler->construct();
+
+ return $handler;
+}
+
+/**
+ * Prepare a handler's data by checking defaults and such.
+ */
+function _views_prepare_handler($definition, $data, $field, $type) {
+ foreach (array('group', 'title', 'title short', 'help', 'real field') as $key) {
+ if (!isset($definition[$key])) {
+ // First check the field level
+ if (!empty($data[$field][$key])) {
+ $definition[$key] = $data[$field][$key];
+ }
+ // Then if that doesn't work, check the table level
+ elseif (!empty($data['table'][$key])) {
+ $definition[$key] = $data['table'][$key];
+ }
+ }
+ }
+
+ return _views_create_handler($definition, 'handler', $type);
+}
+
+/**
+ * Fetch a handler to join one table to a primary table from the data cache
+ */
+function views_get_table_join($table, $base_table) {
+ $data = views_fetch_data($table);
+ if (isset($data['table']['join'][$base_table])) {
+ $h = $data['table']['join'][$base_table];
+ if (!empty($h['handler']) && class_exists($h['handler'])) {
+ $handler = new $h['handler'];
+ }
+ else {
+ $handler = new views_join();
+ }
+
+ // Fill in some easy defaults
+ $handler->definition = $h;
+ if (empty($handler->definition['table'])) {
+ $handler->definition['table'] = $table;
+ }
+ // If this is empty, it's a direct link.
+ if (empty($handler->definition['left_table'])) {
+ $handler->definition['left_table'] = $base_table;
+ }
+
+ if (isset($h['arguments'])) {
+ call_user_func_array(array(&$handler, 'construct'), $h['arguments']);
+ }
+ else {
+ $handler->construct();
+ }
+
+ return $handler;
+ }
+
+ // DEBUG -- identify missing handlers
+ vpr("Missing join: @table @base_table", array('@table' => $table, '@base_table' => $base_table));
+}
+
+/**
+ * Base handler, from which all the other handlers are derived.
+ * It creates a common interface to create consistency amongst
+ * handlers and data.
+ *
+ * This class would be abstract in PHP5, but PHP4 doesn't understand that.
+ *
+ * Definition terms:
+ * - table: The actual table this uses; only specify if different from
+ * the table this is attached to.
+ * - real field: The actual field this uses; only specify if different from
+ * the field this item is attached to.
+ * - group: A text string representing the 'group' this item is attached to,
+ * for display in the UI. Examples: "Node", "Taxonomy", "Comment",
+ * "User", etc. This may be inherited from the parent definition or
+ * the 'table' definition.
+ * - title: The title for this handler in the UI. This may be inherited from
+ * the parent definition or the 'table' definition.
+ * - help: A more informative string to give to the user to explain what this
+ * field/handler is or does.
+ * - access callback: If this field should have access control, this could
+ * be a function to use. 'user_access' is a common
+ * function to use here. If not specified, no access
+ * control is provided.
+ * - access arguments: An array of arguments for the access callback.
+ */
+class views_handler extends views_object {
+ /**
+ * The top object of a view.
+ *
+ * @var view
+ */
+ var $view = NULL;
+
+ /**
+ * Where the $query object will reside:
+ *
+ * @var views_plugin_query
+ */
+ var $query = NULL;
+
+ /**
+ * The type of the handler, for example filter/footer/field.
+ */
+ var $handler_type = NULL;
+
+ /**
+ * The alias of the table of this handler which is used in the query.
+ */
+ public $table_alias;
+
+ /**
+ * The actual field in the database table, maybe different
+ * on other kind of query plugins/special handlers.
+ */
+ var $real_field;
+
+ /**
+ * The relationship used for this field.
+ */
+ var $relationship = NULL;
+
+ /**
+ * init the handler with necessary data.
+ * @param $view
+ * The $view object this handler is attached to.
+ * @param $options
+ * The item from the database; the actual contents of this will vary
+ * based upon the type of handler.
+ */
+ function init(&$view, &$options) {
+ $this->view = &$view;
+ $display_id = $this->view->current_display;
+ // Check to see if this handler type is defaulted. Note that
+ // we have to do a lookup because the type is singular but the
+ // option is stored as the plural.
+
+ // If the 'moved to' keyword moved our handler, let's fix that now.
+ if (isset($this->actual_table)) {
+ $options['table'] = $this->actual_table;
+ }
+
+ if (isset($this->actual_field)) {
+ $options['field'] = $this->actual_field;
+ }
+
+ $types = views_object_types();
+ $plural = $this->handler_type;
+ if (isset($types[$this->handler_type]['plural'])) {
+ $plural = $types[$this->handler_type]['plural'];
+ }
+ if ($this->view->display_handler->is_defaulted($plural)) {
+ $display_id = 'default';
+ }
+
+ $this->localization_keys = array(
+ $display_id,
+ $this->handler_type,
+ $options['table'],
+ $options['id']
+ );
+
+ $this->unpack_options($this->options, $options);
+
+ // This exist on most handlers, but not all. So they are still optional.
+ if (isset($options['table'])) {
+ $this->table = $options['table'];
+ }
+
+ if (isset($this->definition['real field'])) {
+ $this->real_field = $this->definition['real field'];
+ }
+
+ if (isset($this->definition['field'])) {
+ $this->real_field = $this->definition['field'];
+ }
+
+ if (isset($options['field'])) {
+ $this->field = $options['field'];
+ if (!isset($this->real_field)) {
+ $this->real_field = $options['field'];
+ }
+ }
+
+ $this->query = &$view->query;
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['id'] = array('default' => '');
+ $options['table'] = array('default' => '');
+ $options['field'] = array('default' => '');
+ $options['relationship'] = array('default' => 'none');
+ $options['group_type'] = array('default' => 'group');
+ $options['ui_name'] = array('default' => '');
+
+ return $options;
+ }
+
+ /**
+ * Return a string representing this handler's name in the UI.
+ */
+ function ui_name($short = FALSE) {
+ if (!empty($this->options['ui_name'])) {
+ $title = check_plain($this->options['ui_name']);
+ return $title;
+ }
+ $title = ($short && isset($this->definition['title short'])) ? $this->definition['title short'] : $this->definition['title'];
+ return t('!group: !title', array('!group' => $this->definition['group'], '!title' => $title));
+ }
+
+ /**
+ * Shortcut to get a handler's raw field value.
+ *
+ * This should be overridden for handlers with formulae or other
+ * non-standard fields. Because this takes an argument, fields
+ * overriding this can just call return parent::get_field($formula)
+ */
+ function get_field($field = NULL) {
+ if (!isset($field)) {
+ if (!empty($this->formula)) {
+ $field = $this->get_formula();
+ }
+ else {
+ $field = $this->table_alias . '.' . $this->real_field;
+ }
+ }
+
+ // If grouping, check to see if the aggregation method needs to modify the field.
+ if ($this->view->display_handler->use_group_by()) {
+ $this->view->init_query();
+ if ($this->query) {
+ $info = $this->query->get_aggregation_info();
+ if (!empty($info[$this->options['group_type']]['method']) && function_exists($info[$this->options['group_type']]['method'])) {
+ return $info[$this->options['group_type']]['method']($this->options['group_type'], $field);
+ }
+ }
+ }
+
+ return $field;
+ }
+
+ /**
+ * Sanitize the value for output.
+ *
+ * @param $value
+ * The value being rendered.
+ * @param $type
+ * The type of sanitization needed. If not provided, check_plain() is used.
+ *
+ * @return string
+ * Returns the safe value.
+ */
+ function sanitize_value($value, $type = NULL) {
+ switch ($type) {
+ case 'xss':
+ $value = filter_xss($value);
+ break;
+ case 'xss_admin':
+ $value = filter_xss_admin($value);
+ break;
+ case 'url':
+ $value = check_url($value);
+ break;
+ default:
+ $value = check_plain($value);
+ break;
+ }
+ return $value;
+ }
+
+ /**
+ * Transform a string by a certain method.
+ *
+ * @param $string
+ * The input you want to transform.
+ * @param $option
+ * How do you want to transform it, possible values:
+ * - upper: Uppercase the string.
+ * - lower: lowercase the string.
+ * - ucfirst: Make the first char uppercase.
+ * - ucwords: Make each word in the string uppercase.
+ *
+ * @return string
+ * The transformed string.
+ */
+ function case_transform($string, $option) {
+ global $multibyte;
+
+ switch ($option) {
+ default:
+ return $string;
+ case 'upper':
+ return drupal_strtoupper($string);
+ case 'lower':
+ return drupal_strtolower($string);
+ case 'ucfirst':
+ return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1);
+ case 'ucwords':
+ if ($multibyte == UNICODE_MULTIBYTE) {
+ return mb_convert_case($string, MB_CASE_TITLE);
+ }
+ else {
+ return ucwords($string);
+ }
+ }
+ }
+
+ /**
+ * Validate the options form.
+ */
+ function options_validate(&$form, &$form_state) { }
+
+ /**
+ * Build the options form.
+ */
+ function options_form(&$form, &$form_state) {
+ // Some form elements belong in a fieldset for presentation, but can't
+ // be moved into one because of the form_state['values'] hierarchy. Those
+ // elements can add a #fieldset => 'fieldset_name' property, and they'll
+ // be moved to their fieldset during pre_render.
+ $form['#pre_render'][] = 'views_ui_pre_render_add_fieldset_markup';
+
+ $form['ui_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Administrative title'),
+ '#description' => t('This title will be displayed on the views edit page instead of the default one. This might be useful if you have the same item twice.'),
+ '#default_value' => $this->options['ui_name'],
+ '#fieldset' => 'more',
+ );
+
+ // This form is long and messy enough that the "Administrative title" option
+ // belongs in a "more options" fieldset at the bottom of the form.
+ $form['more'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('More'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#weight' => 150,
+ );
+ // Allow to alter the default values brought into the form.
+ drupal_alter('views_handler_options', $this->options, $view);
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) { }
+
+ /**
+ * Provides the handler some groupby.
+ */
+ function use_group_by() {
+ return TRUE;
+ }
+ /**
+ * Provide a form for aggregation settings.
+ */
+ function groupby_form(&$form, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $types = views_object_types();
+ $type = $form_state['type'];
+ $id = $form_state['id'];
+
+ $form['#title'] = check_plain($view->display[$display_id]->display_title) . ': ';
+ $form['#title'] .= t('Configure aggregation settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $this->ui_name()));
+
+ $form['#section'] = $display_id . '-' . $type . '-' . $id;
+
+ $view->init_query();
+ $info = $view->query->get_aggregation_info();
+ foreach ($info as $id => $aggregate) {
+ $group_types[$id] = $aggregate['title'];
+ }
+
+ $form['group_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Aggregation type'),
+ '#default_value' => $this->options['group_type'],
+ '#description' => t('Select the aggregation function to use on this field.'),
+ '#options' => $group_types,
+ );
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function groupby_form_submit(&$form, &$form_state) {
+ $item =& $form_state['handler']->options;
+
+ $item['group_type'] = $form_state['values']['options']['group_type'];
+ }
+
+ /**
+ * If a handler has 'extra options' it will get a little settings widget and
+ * another form called extra_options.
+ */
+ function has_extra_options() { return FALSE; }
+
+ /**
+ * Provide defaults for the handler.
+ */
+ function extra_options(&$option) { }
+
+ /**
+ * Provide a form for setting options.
+ */
+ function extra_options_form(&$form, &$form_state) { }
+
+ /**
+ * Validate the options form.
+ */
+ function extra_options_validate($form, &$form_state) { }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function extra_options_submit($form, &$form_state) { }
+
+ /**
+ * Determine if a handler can be exposed.
+ */
+ function can_expose() { return FALSE; }
+
+ /**
+ * Set new exposed option defaults when exposed setting is flipped
+ * on.
+ */
+ function expose_options() { }
+
+ /**
+ * Get information about the exposed form for the form renderer.
+ */
+ function exposed_info() { }
+
+ /**
+ * Render our chunk of the exposed handler form when selecting
+ */
+ function exposed_form(&$form, &$form_state) { }
+
+ /**
+ * Validate the exposed handler form
+ */
+ function exposed_validate(&$form, &$form_state) { }
+
+ /**
+ * Submit the exposed handler form
+ */
+ function exposed_submit(&$form, &$form_state) { }
+
+ /**
+ * Form for exposed handler options.
+ */
+ function expose_form(&$form, &$form_state) { }
+
+ /**
+ * Validate the options form.
+ */
+ function expose_validate($form, &$form_state) { }
+
+ /**
+ * Perform any necessary changes to the form exposes prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function expose_submit($form, &$form_state) { }
+
+ /**
+ * Shortcut to display the expose/hide button.
+ */
+ function show_expose_button(&$form, &$form_state) { }
+
+ /**
+ * Shortcut to display the exposed options form.
+ */
+ function show_expose_form(&$form, &$form_state) {
+ if (empty($this->options['exposed'])) {
+ return;
+ }
+
+ $this->expose_form($form, $form_state);
+
+ // When we click the expose button, we add new gadgets to the form but they
+ // have no data in $_POST so their defaults get wiped out. This prevents
+ // these defaults from getting wiped out. This setting will only be TRUE
+ // during a 2nd pass rerender.
+ if (!empty($form_state['force_expose_options'])) {
+ foreach (element_children($form['expose']) as $id) {
+ if (isset($form['expose'][$id]['#default_value']) && !isset($form['expose'][$id]['#value'])) {
+ $form['expose'][$id]['#value'] = $form['expose'][$id]['#default_value'];
+ }
+ }
+ }
+ }
+
+ /**
+ * Check whether current user has access to this handler.
+ *
+ * @return boolean
+ */
+ function access() {
+ if (isset($this->definition['access callback']) && function_exists($this->definition['access callback'])) {
+ if (isset($this->definition['access arguments']) && is_array($this->definition['access arguments'])) {
+ return call_user_func_array($this->definition['access callback'], $this->definition['access arguments']);
+ }
+ return $this->definition['access callback']();
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Run before the view is built.
+ *
+ * This gives all the handlers some time to set up before any handler has
+ * been fully run.
+ */
+ function pre_query() { }
+
+ /**
+ * Run after the view is executed, before the result is cached.
+ *
+ * This gives all the handlers some time to modify values. This is primarily
+ * used so that handlers that pull up secondary data can put it in the
+ * $values so that the raw data can be utilized externally.
+ */
+ function post_execute(&$values) { }
+
+ /**
+ * Provides a unique placeholders for handlers.
+ */
+ function placeholder() {
+ return $this->query->placeholder($this->options['table'] . '_' . $this->options['field']);
+ }
+
+ /**
+ * Called just prior to query(), this lets a handler set up any relationship
+ * it needs.
+ */
+ function set_relationship() {
+ // Ensure this gets set to something.
+ $this->relationship = NULL;
+
+ // Don't process non-existant relationships.
+ if (empty($this->options['relationship']) || $this->options['relationship'] == 'none') {
+ return;
+ }
+
+ $relationship = $this->options['relationship'];
+
+ // Ignore missing/broken relationships.
+ if (empty($this->view->relationship[$relationship])) {
+ return;
+ }
+
+ // Check to see if the relationship has already processed. If not, then we
+ // cannot process it.
+ if (empty($this->view->relationship[$relationship]->alias)) {
+ return;
+ }
+
+ // Finally!
+ $this->relationship = $this->view->relationship[$relationship]->alias;
+ }
+
+ /**
+ * Ensure the main table for this handler is in the query. This is used
+ * a lot.
+ */
+ function ensure_my_table() {
+ if (!isset($this->table_alias)) {
+ if (!method_exists($this->query, 'ensure_table')) {
+ vpr(t('Ensure my table called but query has no ensure_table method.'));
+ return;
+ }
+ $this->table_alias = $this->query->ensure_table($this->table, $this->relationship);
+ }
+ return $this->table_alias;
+ }
+
+ /**
+ * Provide text for the administrative summary
+ */
+ function admin_summary() { }
+
+ /**
+ * Determine if the argument needs a style plugin.
+ *
+ * @return TRUE/FALSE
+ */
+ function needs_style_plugin() { return FALSE; }
+
+ /**
+ * Determine if this item is 'exposed', meaning it provides form elements
+ * to let users modify the view.
+ *
+ * @return TRUE/FALSE
+ */
+ function is_exposed() {
+ return !empty($this->options['exposed']);
+ }
+
+ /**
+ * Returns TRUE if the exposed filter works like a grouped filter.
+ */
+ function is_a_group() { return FALSE; }
+
+ /**
+ * Define if the exposed input has to be submitted multiple times.
+ * This is TRUE when exposed filters grouped are using checkboxes as
+ * widgets.
+ */
+ function multiple_exposed_input() { return FALSE; }
+
+ /**
+ * Take input from exposed handlers and assign to this handler, if necessary.
+ */
+ function accept_exposed_input($input) { return TRUE; }
+
+ /**
+ * If set to remember exposed input in the session, store it there.
+ */
+ function store_exposed_input($input, $status) { return TRUE; }
+
+ /**
+ * Get the join object that should be used for this handler.
+ *
+ * This method isn't used a great deal, but it's very handy for easily
+ * getting the join if it is necessary to make some changes to it, such
+ * as adding an 'extra'.
+ */
+ function get_join() {
+ // get the join from this table that links back to the base table.
+ // Determine the primary table to seek
+ if (empty($this->query->relationships[$this->relationship])) {
+ $base_table = $this->query->base_table;
+ }
+ else {
+ $base_table = $this->query->relationships[$this->relationship]['base'];
+ }
+
+ $join = views_get_table_join($this->table, $base_table);
+ if ($join) {
+ return clone $join;
+ }
+ }
+
+ /**
+ * Validates the handler against the complete View.
+ *
+ * This is called when the complete View is being validated. For validating
+ * the handler options form use options_validate().
+ *
+ * @see views_handler::options_validate()
+ *
+ * @return
+ * Empty array if the handler is valid; an array of error strings if it is not.
+ */
+ function validate() { return array(); }
+
+ /**
+ * Determine if the handler is considered 'broken', meaning it's a
+ * a placeholder used when a handler can't be found.
+ */
+ function broken() { }
+}
+
+/**
+ * This many to one helper object is used on both arguments and filters.
+ *
+ * @todo This requires extensive documentation on how this class is to
+ * be used. For now, look at the arguments and filters that use it. Lots
+ * of stuff is just pass-through but there are definitely some interesting
+ * areas where they interact.
+ *
+ * Any handler that uses this can have the following possibly additional
+ * definition terms:
+ * - numeric: If true, treat this field as numeric, using %d instead of %s in
+ * queries.
+ *
+ */
+class views_many_to_one_helper {
+ /**
+ * Contains possible existing placeholders used by the query.
+ *
+ * @var array
+ */
+ public $placeholders = array();
+
+ function __construct(&$handler) {
+ $this->handler = &$handler;
+ }
+
+ static function option_definition(&$options) {
+ $options['reduce_duplicates'] = array('default' => FALSE, 'bool' => TRUE);
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['reduce_duplicates'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Reduce duplicates'),
+ '#description' => t('This filter can cause items that have more than one of the selected options to appear as duplicate results. If this filter causes duplicate results to occur, this checkbox can reduce those duplicates; however, the more terms it has to search for, the less performant the query will be, so use this with caution. Shouldn\'t be set on single-value fields, as it may cause values to disappear from display, if used on an incompatible field.'),
+ '#default_value' => !empty($this->handler->options['reduce_duplicates']),
+ '#weight' => 4,
+ );
+ }
+
+ /**
+ * Sometimes the handler might want us to use some kind of formula, so give
+ * it that option. If it wants us to do this, it must set $helper->formula = TRUE
+ * and implement handler->get_formula();
+ */
+ function get_field() {
+ if (!empty($this->formula)) {
+ return $this->handler->get_formula();
+ }
+ else {
+ return $this->handler->table_alias . '.' . $this->handler->real_field;
+ }
+ }
+
+ /**
+ * Add a table to the query.
+ *
+ * This is an advanced concept; not only does it add a new instance of the table,
+ * but it follows the relationship path all the way down to the relationship
+ * link point and adds *that* as a new relationship and then adds the table to
+ * the relationship, if necessary.
+ */
+ function add_table($join = NULL, $alias = NULL) {
+ // This is used for lookups in the many_to_one table.
+ $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
+
+ if (empty($join)) {
+ $join = $this->get_join();
+ }
+
+ // See if there's a chain between us and the base relationship. If so, we need
+ // to create a new relationship to use.
+ $relationship = $this->handler->relationship;
+
+ // Determine the primary table to seek
+ if (empty($this->handler->query->relationships[$relationship])) {
+ $base_table = $this->handler->query->base_table;
+ }
+ else {
+ $base_table = $this->handler->query->relationships[$relationship]['base'];
+ }
+
+ // Cycle through the joins. This isn't as error-safe as the normal
+ // ensure_path logic. Perhaps it should be.
+ $r_join = clone $join;
+ while ($r_join->left_table != $base_table) {
+ $r_join = views_get_table_join($r_join->left_table, $base_table);
+ }
+ // If we found that there are tables in between, add the relationship.
+ if ($r_join->table != $join->table) {
+ $relationship = $this->handler->query->add_relationship($this->handler->table . '_' . $r_join->table, $r_join, $r_join->table, $this->handler->relationship);
+ }
+
+ // And now add our table, using the new relationship if one was used.
+ $alias = $this->handler->query->add_table($this->handler->table, $relationship, $join, $alias);
+
+ // Store what values are used by this table chain so that other chains can
+ // automatically discard those values.
+ if (empty($this->handler->view->many_to_one_tables[$field])) {
+ $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
+ }
+ else {
+ $this->handler->view->many_to_one_tables[$field] = array_merge($this->handler->view->many_to_one_tables[$field], $this->handler->value);
+ }
+
+ return $alias;
+ }
+
+ function get_join() {
+ return $this->handler->get_join();
+ }
+
+ /**
+ * Provide the proper join for summary queries. This is important in part because
+ * it will cooperate with other arguments if possible.
+ */
+ function summary_join() {
+ $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
+ $join = $this->get_join();
+
+ // shortcuts
+ $options = $this->handler->options;
+ $view = &$this->handler->view;
+ $query = &$this->handler->query;
+
+ if (!empty($options['require_value'])) {
+ $join->type = 'INNER';
+ }
+
+ if (empty($options['add_table']) || empty($view->many_to_one_tables[$field])) {
+ return $query->ensure_table($this->handler->table, $this->handler->relationship, $join);
+ }
+ else {
+ if (!empty($view->many_to_one_tables[$field])) {
+ foreach ($view->many_to_one_tables[$field] as $value) {
+ $join->extra = array(
+ array(
+ 'field' => $this->handler->real_field,
+ 'operator' => '!=',
+ 'value' => $value,
+ 'numeric' => !empty($this->definition['numeric']),
+ ),
+ );
+ }
+ }
+ return $this->add_table($join);
+ }
+ }
+
+ /**
+ * Override ensure_my_table so we can control how this joins in.
+ * The operator actually has influence over joining.
+ */
+ function ensure_my_table() {
+ if (!isset($this->handler->table_alias)) {
+ // Case 1: Operator is an 'or' and we're not reducing duplicates.
+ // We hence get the absolute simplest:
+ $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
+ if ($this->handler->operator == 'or' && empty($this->handler->options['reduce_duplicates'])) {
+ if (empty($this->handler->options['add_table']) && empty($this->handler->view->many_to_one_tables[$field])) {
+ // query optimization, INNER joins are slightly faster, so use them
+ // when we know we can.
+ $join = $this->get_join();
+ if (isset($join)) {
+ $join->type = 'INNER';
+ }
+ $this->handler->table_alias = $this->handler->query->ensure_table($this->handler->table, $this->handler->relationship, $join);
+ $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
+ }
+ else {
+ $join = $this->get_join();
+ $join->type = 'LEFT';
+ if (!empty($this->handler->view->many_to_one_tables[$field])) {
+ foreach ($this->handler->view->many_to_one_tables[$field] as $value) {
+ $join->extra = array(
+ array(
+ 'field' => $this->handler->real_field,
+ 'operator' => '!=',
+ 'value' => $value,
+ 'numeric' => !empty($this->handler->definition['numeric']),
+ ),
+ );
+ }
+ }
+
+ $this->handler->table_alias = $this->add_table($join);
+ }
+
+ return $this->handler->table_alias;
+ }
+
+ // Case 2: it's an 'and' or an 'or'.
+ // We do one join per selected value.
+ if ($this->handler->operator != 'not') {
+ // Clone the join for each table:
+ $this->handler->table_aliases = array();
+ foreach ($this->handler->value as $value) {
+ $join = $this->get_join();
+ if ($this->handler->operator == 'and') {
+ $join->type = 'INNER';
+ }
+ $join->extra = array(
+ array(
+ 'field' => $this->handler->real_field,
+ 'value' => $value,
+ 'numeric' => !empty($this->handler->definition['numeric']),
+ ),
+ );
+
+ // The table alias needs to be unique to this value across the
+ // multiple times the filter or argument is called by the view.
+ if (!isset($this->handler->view->many_to_one_aliases[$field][$value])) {
+ if (!isset($this->handler->view->many_to_one_count[$this->handler->table])) {
+ $this->handler->view->many_to_one_count[$this->handler->table] = 0;
+ }
+ $this->handler->view->many_to_one_aliases[$field][$value] = $this->handler->table . '_value_' . ($this->handler->view->many_to_one_count[$this->handler->table]++);
+ }
+ $alias = $this->handler->table_aliases[$value] = $this->add_table($join, $this->handler->view->many_to_one_aliases[$field][$value]);
+
+ // and set table_alias to the first of these.
+ if (empty($this->handler->table_alias)) {
+ $this->handler->table_alias = $alias;
+ }
+ }
+ }
+ // Case 3: it's a 'not'.
+ // We just do one join. We'll add a where clause during
+ // the query phase to ensure that $table.$field IS NULL.
+ else {
+ $join = $this->get_join();
+ $join->type = 'LEFT';
+ $join->extra = array();
+ $join->extra_type = 'OR';
+ foreach ($this->handler->value as $value) {
+ $join->extra[] = array(
+ 'field' => $this->handler->real_field,
+ 'value' => $value,
+ 'numeric' => !empty($this->handler->definition['numeric']),
+ );
+ }
+
+ $this->handler->table_alias = $this->add_table($join);
+ }
+ }
+ return $this->handler->table_alias;
+ }
+
+ /**
+ * Provides a unique placeholders for handlers.
+ */
+ function placeholder() {
+ return $this->handler->query->placeholder($this->handler->options['table'] . '_' . $this->handler->options['field']);
+ }
+
+ function add_filter() {
+ if (empty($this->handler->value)) {
+ return;
+ }
+ $this->handler->ensure_my_table();
+
+ // Shorten some variables:
+ $field = $this->get_field();
+ $options = $this->handler->options;
+ $operator = $this->handler->operator;
+ $formula = !empty($this->formula);
+ $value = $this->handler->value;
+ if (empty($options['group'])) {
+ $options['group'] = 0;
+ }
+
+ // add_condition determines whether a single expression is enough(FALSE) or the
+ // conditions should be added via an db_or()/db_and() (TRUE).
+ $add_condition = TRUE;
+ if ($operator == 'not') {
+ $value = NULL;
+ $operator = 'IS NULL';
+ $add_condition = FALSE;
+ }
+ elseif ($operator == 'or' && empty($options['reduce_duplicates'])) {
+ if (count($value) > 1) {
+ $operator = 'IN';
+ }
+ else {
+ $value = is_array($value) ? array_pop($value) : $value;
+ $operator = '=';
+ }
+ $add_condition = FALSE;
+ }
+
+ if (!$add_condition) {
+ if ($formula) {
+ $placeholder = $this->placeholder();
+ if ($operator == 'IN') {
+ $operator = "$operator IN($placeholder)";
+ }
+ else {
+ $operator = "$operator $placeholder";
+ }
+ $placeholders = array(
+ $placeholder => $value,
+ ) + $this->placeholders;
+ $this->handler->query->add_where_expression($options['group'], "$field $operator", $placeholders);
+ }
+ else {
+ $this->handler->query->add_where($options['group'], $field, $value, $operator);
+ }
+ }
+
+ if ($add_condition) {
+ $field = $this->handler->real_field;
+ $clause = $operator == 'or' ? db_or() : db_and();
+ foreach ($this->handler->table_aliases as $value => $alias) {
+ $clause->condition("$alias.$field", $value);
+ }
+
+ // implode on either AND or OR.
+ $this->handler->query->add_where($options['group'], $clause);
+ }
+ }
+}
+
+/**
+ * Break x,y,z and x+y+z into an array. Works for strings.
+ *
+ * @param $str
+ * The string to parse.
+ * @param $object
+ * The object to use as a base. If not specified one will
+ * be created.
+ *
+ * @return $object
+ * An object containing
+ * - operator: Either 'and' or 'or'
+ * - value: An array of numeric values.
+ */
+function views_break_phrase_string($str, &$handler = NULL) {
+ if (!$handler) {
+ $handler = new stdClass();
+ }
+
+ // Set up defaults:
+ if (!isset($handler->value)) {
+ $handler->value = array();
+ }
+
+ if (!isset($handler->operator)) {
+ $handler->operator = 'or';
+ }
+
+ if ($str == '') {
+ return $handler;
+ }
+
+ // Determine if the string has 'or' operators (plus signs) or 'and' operators
+ // (commas) and split the string accordingly. If we have an 'and' operator,
+ // spaces are treated as part of the word being split, but otherwise they are
+ // treated the same as a plus sign.
+ $or_wildcard = '[^\s+,]';
+ $and_wildcard = '[^+,]';
+ if (preg_match("/^({$or_wildcard}+[+ ])+{$or_wildcard}+$/", $str)) {
+ $handler->operator = 'or';
+ $handler->value = preg_split('/[+ ]/', $str);
+ }
+ elseif (preg_match("/^({$and_wildcard}+,)*{$and_wildcard}+$/", $str)) {
+ $handler->operator = 'and';
+ $handler->value = explode(',', $str);
+ }
+
+ // Keep an 'error' value if invalid strings were given.
+ if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
+ $handler->value = array(-1);
+ return $handler;
+ }
+
+ // Doubly ensure that all values are strings only.
+ foreach ($handler->value as $id => $value) {
+ $handler->value[$id] = (string) $value;
+ }
+
+ return $handler;
+}
+
+/**
+ * Break x,y,z and x+y+z into an array. Numeric only.
+ *
+ * @param $str
+ * The string to parse.
+ * @param $handler
+ * The handler object to use as a base. If not specified one will
+ * be created.
+ *
+ * @return $handler
+ * The new handler object.
+ */
+function views_break_phrase($str, &$handler = NULL) {
+ if (!$handler) {
+ $handler = new stdClass();
+ }
+
+ // Set up defaults:
+
+ if (!isset($handler->value)) {
+ $handler->value = array();
+ }
+
+ if (!isset($handler->operator)) {
+ $handler->operator = 'or';
+ }
+
+ if (empty($str)) {
+ return $handler;
+ }
+
+ if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
+ // The '+' character in a query string may be parsed as ' '.
+ $handler->operator = 'or';
+ $handler->value = preg_split('/[+ ]/', $str);
+ }
+ elseif (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
+ $handler->operator = 'and';
+ $handler->value = explode(',', $str);
+ }
+
+ // Keep an 'error' value if invalid strings were given.
+ if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
+ $handler->value = array(-1);
+ return $handler;
+ }
+
+ // Doubly ensure that all values are numeric only.
+ foreach ($handler->value as $id => $value) {
+ $handler->value[$id] = intval($value);
+ }
+
+ return $handler;
+}
+
+// --------------------------------------------------------------------------
+// Date helper functions
+
+/**
+ * Figure out what timezone we're in; needed for some date manipulations.
+ */
+function views_get_timezone() {
+ global $user;
+ if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
+ $timezone = $user->timezone;
+ }
+ else {
+ $timezone = variable_get('date_default_timezone', 0);
+ }
+
+ // set up the database timezone
+ $db_type = Database::getConnection()->databaseType();
+ if (in_array($db_type, array('mysql', 'pgsql'))) {
+ $offset = '+00:00';
+ static $already_set = FALSE;
+ if (!$already_set) {
+ if ($db_type == 'pgsql') {
+ db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
+ }
+ elseif ($db_type == 'mysql') {
+ db_query("SET @@session.time_zone = '$offset'");
+ }
+
+ $already_set = true;
+ }
+ }
+
+ return $timezone;
+}
+
+/**
+ * Helper function to create cross-database SQL dates.
+ *
+ * @param $field
+ * The real table and field name, like 'tablename.fieldname'.
+ * @param $field_type
+ * The type of date field, 'int' or 'datetime'.
+ * @param $set_offset
+ * The name of a field that holds the timezone offset or a fixed timezone
+ * offset value. If not provided, the normal Drupal timezone handling
+ * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
+ * @return
+ * An appropriate SQL string for the db type and field type.
+ */
+function views_date_sql_field($field, $field_type = 'int', $set_offset = NULL) {
+ $db_type = Database::getConnection()->databaseType();
+ $offset = $set_offset !== NULL ? $set_offset : views_get_timezone();
+ if (isset($offset) && !is_numeric($offset)) {
+ $dtz = new DateTimeZone($offset);
+ $dt = new DateTime("now", $dtz);
+ $offset_seconds = $dtz->getOffset($dt);
+ }
+
+ switch ($db_type) {
+ case 'mysql':
+ switch ($field_type) {
+ case 'int':
+ $field = "DATE_ADD('19700101', INTERVAL $field SECOND)";
+ break;
+ case 'datetime':
+ break;
+ }
+ if (!empty($offset)) {
+ $field = "($field + INTERVAL $offset_seconds SECOND)";
+ }
+ return $field;
+ case 'pgsql':
+ switch ($field_type) {
+ case 'int':
+ $field = "TO_TIMESTAMP($field)";
+ break;
+ case 'datetime':
+ break;
+ }
+ if (!empty($offset)) {
+ $field = "($field + INTERVAL '$offset_seconds SECONDS')";
+ }
+ return $field;
+ case 'sqlite':
+ if (!empty($offset)) {
+ $field = "($field + '$offset_seconds')";
+ }
+ return $field;
+ }
+}
+
+/**
+ * Helper function to create cross-database SQL date formatting.
+ *
+ * @param $format
+ * A format string for the result, like 'Y-m-d H:i:s'.
+ * @param $field
+ * The real table and field name, like 'tablename.fieldname'.
+ * @param $field_type
+ * The type of date field, 'int' or 'datetime'.
+ * @param $set_offset
+ * The name of a field that holds the timezone offset or a fixed timezone
+ * offset value. If not provided, the normal Drupal timezone handling
+ * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
+ * @return
+ * An appropriate SQL string for the db type and field type.
+ */
+function views_date_sql_format($format, $field, $field_type = 'int', $set_offset = NULL) {
+ $db_type = Database::getConnection()->databaseType();
+ $field = views_date_sql_field($field, $field_type, $set_offset);
+ switch ($db_type) {
+ case 'mysql':
+ $replace = array(
+ 'Y' => '%Y',
+ 'y' => '%y',
+ 'M' => '%b',
+ 'm' => '%m',
+ 'n' => '%c',
+ 'F' => '%M',
+ 'D' => '%a',
+ 'd' => '%d',
+ 'l' => '%W',
+ 'j' => '%e',
+ 'W' => '%v',
+ 'H' => '%H',
+ 'h' => '%h',
+ 'i' => '%i',
+ 's' => '%s',
+ 'A' => '%p',
+ );
+ $format = strtr($format, $replace);
+ return "DATE_FORMAT($field, '$format')";
+ case 'pgsql':
+ $replace = array(
+ 'Y' => 'YYYY',
+ 'y' => 'YY',
+ 'M' => 'Mon',
+ 'm' => 'MM',
+ 'n' => 'MM', // no format for Numeric representation of a month, without leading zeros
+ 'F' => 'Month',
+ 'D' => 'Dy',
+ 'd' => 'DD',
+ 'l' => 'Day',
+ 'j' => 'DD', // no format for Day of the month without leading zeros
+ 'W' => 'WW',
+ 'H' => 'HH24',
+ 'h' => 'HH12',
+ 'i' => 'MI',
+ 's' => 'SS',
+ 'A' => 'AM',
+ );
+ $format = strtr($format, $replace);
+ return "TO_CHAR($field, '$format')";
+ case 'sqlite':
+ $replace = array(
+ 'Y' => '%Y', // 4 digit year number
+ 'y' => '%Y', // no format for 2 digit year number
+ 'M' => '%m', // no format for 3 letter month name
+ 'm' => '%m', // month number with leading zeros
+ 'n' => '%m', // no format for month number without leading zeros
+ 'F' => '%m', // no format for full month name
+ 'D' => '%d', // no format for 3 letter day name
+ 'd' => '%d', // day of month number with leading zeros
+ 'l' => '%d', // no format for full day name
+ 'j' => '%d', // no format for day of month number without leading zeros
+ 'W' => '%W', // ISO week number
+ 'H' => '%H', // 24 hour hour with leading zeros
+ 'h' => '%H', // no format for 12 hour hour with leading zeros
+ 'i' => '%M', // minutes with leading zeros
+ 's' => '%S', // seconds with leading zeros
+ 'A' => '', // no format for AM/PM
+ );
+ $format = strtr($format, $replace);
+ return "strftime('$format', $field, 'unixepoch')";
+ }
+}
+
+/**
+ * Helper function to create cross-database SQL date extraction.
+ *
+ * @param $extract_type
+ * The type of value to extract from the date, like 'MONTH'.
+ * @param $field
+ * The real table and field name, like 'tablename.fieldname'.
+ * @param $field_type
+ * The type of date field, 'int' or 'datetime'.
+ * @param $set_offset
+ * The name of a field that holds the timezone offset or a fixed timezone
+ * offset value. If not provided, the normal Drupal timezone handling
+ * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
+ * @return
+ * An appropriate SQL string for the db type and field type.
+ */
+function views_date_sql_extract($extract_type, $field, $field_type = 'int', $set_offset = NULL) {
+ $db_type = Database::getConnection()->databaseType();
+ $field = views_date_sql_field($field, $field_type, $set_offset);
+
+ // Note there is no space after FROM to avoid db_rewrite problems
+ // see http://drupal.org/node/79904.
+ switch ($extract_type) {
+ case('DATE'):
+ return $field;
+ case('YEAR'):
+ return "EXTRACT(YEAR FROM($field))";
+ case('MONTH'):
+ return "EXTRACT(MONTH FROM($field))";
+ case('DAY'):
+ return "EXTRACT(DAY FROM($field))";
+ case('HOUR'):
+ return "EXTRACT(HOUR FROM($field))";
+ case('MINUTE'):
+ return "EXTRACT(MINUTE FROM($field))";
+ case('SECOND'):
+ return "EXTRACT(SECOND FROM($field))";
+ case('WEEK'): // ISO week number for date
+ switch ($db_type) {
+ case('mysql'):
+ // WEEK using arg 3 in mysql should return the same value as postgres EXTRACT
+ return "WEEK($field, 3)";
+ case('pgsql'):
+ return "EXTRACT(WEEK FROM($field))";
+ }
+ case('DOW'):
+ switch ($db_type) {
+ case('mysql'):
+ // mysql returns 1 for Sunday through 7 for Saturday
+ // php date functions and postgres use 0 for Sunday and 6 for Saturday
+ return "INTEGER(DAYOFWEEK($field) - 1)";
+ case('pgsql'):
+ return "EXTRACT(DOW FROM($field))";
+ }
+ case('DOY'):
+ switch ($db_type) {
+ case('mysql'):
+ return "DAYOFYEAR($field)";
+ case('pgsql'):
+ return "EXTRACT(DOY FROM($field))";
+ }
+ }
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup views_join_handlers Views join handlers
+ * @{
+ * Handlers to tell Views how to join tables together.
+ *
+ * Here is how you do complex joins:
+ *
+ * @code
+ * class views_join_complex extends views_join {
+ * // PHP 4 doesn't call constructors of the base class automatically from a
+ * // constructor of a derived class. It is your responsibility to propagate
+ * // the call to constructors upstream where appropriate.
+ * function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
+ * parent::construct($table, $left_table, $left_field, $field, $extra, $type);
+ * }
+ *
+ * function build_join($select_query, $table, $view_query) {
+ * $this->extra = 'foo.bar = baz.boing';
+ * parent::build_join($select_query, $table, $view_query);
+ * }
+ * }
+ * @endcode
+ */
+
+/**
+ * A function class to represent a join and create the SQL necessary
+ * to implement the join.
+ *
+ * This is the Delegation pattern. If we had PHP5 exclusively, we would
+ * declare this an interface.
+ *
+ * Extensions of this class can be used to create more interesting joins.
+ *
+ * join definition
+ * - table: table to join (right table)
+ * - field: field to join on (right field)
+ * - left_table: The table we join to
+ * - left_field: The field we join to
+ * - type: either LEFT (default) or INNER
+ * - extra: An array of extra conditions on the join. Each condition is
+ * either a string that's directly added, or an array of items:
+ * - - table: If not set, current table; if NULL, no table. If you specify a
+ * table in cached definition, Views will try to load from an existing
+ * alias. If you use realtime joins, it works better.
+ * - - field: Field or formula
+ * in formulas we can reference the right table by using %alias
+ * @see SelectQueryInterface::addJoin()
+ * - - operator: defaults to =
+ * - - value: Must be set. If an array, operator will be defaulted to IN.
+ * - - numeric: If true, the value will not be surrounded in quotes.
+ * - - extra type: How all the extras will be combined. Either AND or OR. Defaults to AND.
+ */
+class views_join {
+ var $table = NULL;
+ var $left_table = NULL;
+ var $left_field = NULL;
+ var $field = NULL;
+ var $extra = NULL;
+ var $type = NULL;
+ var $definition = array();
+
+ /**
+ * Construct the views_join object.
+ */
+ function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
+ $this->extra_type = 'AND';
+ if (!empty($table)) {
+ $this->table = $table;
+ $this->left_table = $left_table;
+ $this->left_field = $left_field;
+ $this->field = $field;
+ $this->extra = $extra;
+ $this->type = strtoupper($type);
+ }
+ elseif (!empty($this->definition)) {
+ // if no arguments, construct from definition.
+ // These four must exist or it will throw notices.
+ $this->table = $this->definition['table'];
+ $this->left_table = $this->definition['left_table'];
+ $this->left_field = $this->definition['left_field'];
+ $this->field = $this->definition['field'];
+ if (!empty($this->definition['extra'])) {
+ $this->extra = $this->definition['extra'];
+ }
+ if (!empty($this->definition['extra type'])) {
+ $this->extra_type = strtoupper($this->definition['extra type']);
+ }
+
+ $this->type = !empty($this->definition['type']) ? strtoupper($this->definition['type']) : 'LEFT';
+ }
+ }
+
+ /**
+ * Build the SQL for the join this object represents.
+ *
+ * When possible, try to use table alias instead of table names.
+ *
+ * @param $select_query
+ * An implementation of SelectQueryInterface.
+ * @param $table
+ * The base table to join.
+ * @param $view_query
+ * The source query, implementation of views_plugin_query.
+ */
+ function build_join($select_query, $table, $view_query) {
+ if (empty($this->definition['table formula'])) {
+ $right_table = $this->table;
+ }
+ else {
+ $right_table = $this->definition['table formula'];
+ }
+
+ if ($this->left_table) {
+ $left = $view_query->get_table_info($this->left_table);
+ $left_field = "$left[alias].$this->left_field";
+ }
+ else {
+ // This can be used if left_field is a formula or something. It should be used only *very* rarely.
+ $left_field = $this->left_field;
+ }
+
+ $condition = "$left_field = $table[alias].$this->field";
+ $arguments = array();
+
+ // Tack on the extra.
+ if (isset($this->extra)) {
+ if (is_array($this->extra)) {
+ $extras = array();
+ foreach ($this->extra as $info) {
+ $extra = '';
+ // Figure out the table name. Remember, only use aliases provided
+ // if at all possible.
+ $join_table = '';
+ if (!array_key_exists('table', $info)) {
+ $join_table = $table['alias'] . '.';
+ }
+ elseif (isset($info['table'])) {
+ // If we're aware of a table alias for this table, use the table
+ // alias instead of the table name.
+ if (isset($left) && $left['table'] == $info['table']) {
+ $join_table = $left['alias'] . '.';
+ }
+ else {
+ $join_table = $info['table'] . '.';
+ }
+ }
+
+ // Convert a single-valued array of values to the single-value case,
+ // and transform from IN() notation to = notation
+ if (is_array($info['value']) && count($info['value']) == 1) {
+ if (empty($info['operator'])) {
+ $operator = '=';
+ }
+ else {
+ $operator = $info['operator'] == 'NOT IN' ? '!=' : '=';
+ }
+ $info['value'] = array_shift($info['value']);
+ }
+
+ if (is_array($info['value'])) {
+ // With an array of values, we need multiple placeholders and the
+ // 'IN' operator is implicit.
+ foreach ($info['value'] as $value) {
+ $placeholder_i = $view_query->placeholder('views_join_condition_');
+ $arguments[$placeholder_i] = $value;
+ }
+
+ $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
+ $placeholder = '( ' . implode(', ', array_keys($arguments)) . ' )';
+ }
+ else {
+ // With a single value, the '=' operator is implicit.
+ $operator = !empty($info['operator']) ? $info['operator'] : '=';
+ $placeholder = $view_query->placeholder('views_join_condition_');
+ $arguments[$placeholder] = $info['value'];
+ }
+ $extras[] = "$join_table$info[field] $operator $placeholder";
+ }
+
+ if ($extras) {
+ if (count($extras) == 1) {
+ $condition .= ' AND ' . array_shift($extras);
+ }
+ else {
+ $condition .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
+ }
+ }
+ }
+ elseif ($this->extra && is_string($this->extra)) {
+ $condition .= " AND ($this->extra)";
+ }
+ }
+
+ $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
+ }
+}
+
+/**
+ * Join handler for relationships that join with a subquery as the left field.
+ * eg:
+ * LEFT JOIN node node_term_data ON ([YOUR SUBQUERY HERE]) = node_term_data.nid
+ *
+ * join definition
+ * same as views_join class above, except:
+ * - left_query: The subquery to use in the left side of the join clause.
+ */
+class views_join_subquery extends views_join {
+ function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
+ parent::construct($table, $left_table, $left_field, $field, $extra, $type);
+ $this->left_query = $this->definition['left_query'];
+ }
+
+ /**
+ * Build the SQL for the join this object represents.
+ *
+ * @param $select_query
+ * An implementation of SelectQueryInterface.
+ * @param $table
+ * The base table to join.
+ * @param $view_query
+ * The source query, implementation of views_plugin_query.
+ * @return
+ *
+ */
+ function build_join($select_query, $table, $view_query) {
+ if (empty($this->definition['table formula'])) {
+ $right_table = "{" . $this->table . "}";
+ }
+ else {
+ $right_table = $this->definition['table formula'];
+ }
+
+ // Add our join condition, using a subquery on the left instead of a field.
+ $condition = "($this->left_query) = $table[alias].$this->field";
+ $arguments = array();
+
+ // Tack on the extra.
+ // This is just copied verbatim from the parent class, which itself has a bug: http://drupal.org/node/1118100
+ if (isset($this->extra)) {
+ if (is_array($this->extra)) {
+ $extras = array();
+ foreach ($this->extra as $info) {
+ $extra = '';
+ // Figure out the table name. Remember, only use aliases provided
+ // if at all possible.
+ $join_table = '';
+ if (!array_key_exists('table', $info)) {
+ $join_table = $table['alias'] . '.';
+ }
+ elseif (isset($info['table'])) {
+ $join_table = $info['table'] . '.';
+ }
+
+ $placeholder = ':views_join_condition_' . $select_query->nextPlaceholder();
+
+ if (is_array($info['value'])) {
+ $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
+ // Transform from IN() notation to = notation if just one value.
+ if (count($info['value']) == 1) {
+ $info['value'] = array_shift($info['value']);
+ $operator = $operator == 'NOT IN' ? '!=' : '=';
+ }
+ }
+ else {
+ $operator = !empty($info['operator']) ? $info['operator'] : '=';
+ }
+
+ $extras[] = "$join_table$info[field] $operator $placeholder";
+ $arguments[$placeholder] = $info['value'];
+ }
+
+ if ($extras) {
+ if (count($extras) == 1) {
+ $condition .= ' AND ' . array_shift($extras);
+ }
+ else {
+ $condition .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
+ }
+ }
+ }
+ elseif ($this->extra && is_string($this->extra)) {
+ $condition .= " AND ($this->extra)";
+ }
+ }
+
+ $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/includes/plugins.inc b/sites/all/modules/views/includes/plugins.inc
new file mode 100644
index 000000000..4b689825d
--- /dev/null
+++ b/sites/all/modules/views/includes/plugins.inc
@@ -0,0 +1,587 @@
+<?php
+
+/**
+ * @file
+ * Built in plugins for Views output handling.
+ */
+
+// @todo: Remove this once update.php can use the registry
+views_include('base');
+
+/**
+ * Implements hook_views_plugins().
+ */
+function views_views_plugins() {
+ $js_path = drupal_get_path('module', 'ctools') . '/js';
+ $plugins = array(
+ // display, style, row, argument default, argument validator and access.
+ 'display' => array(
+ // Default settings for all display plugins.
+ 'default' => array(
+ 'title' => t('Master'),
+ 'help' => t('Default settings for this view.'),
+ 'handler' => 'views_plugin_display_default',
+ 'theme' => 'views_view',
+ 'no ui' => TRUE,
+ 'no remove' => TRUE,
+ 'js' => array('misc/form.js', 'misc/collapse.js', 'misc/textarea.js', 'misc/tabledrag.js', 'misc/autocomplete.js', "$js_path/dependent.js"),
+ 'use ajax' => TRUE,
+ 'use pager' => TRUE,
+ 'use more' => TRUE,
+ 'accept attachments' => TRUE,
+ 'help topic' => 'display-default',
+ ),
+ 'page' => array(
+ 'title' => t('Page'),
+ 'help' => t('Display the view as a page, with a URL and menu links.'),
+ 'handler' => 'views_plugin_display_page',
+ 'theme' => 'views_view',
+ 'uses hook menu' => TRUE,
+ 'contextual links locations' => array('page'),
+ 'use ajax' => TRUE,
+ 'use pager' => TRUE,
+ 'use more' => TRUE,
+ 'accept attachments' => TRUE,
+ 'admin' => t('Page'),
+ 'help topic' => 'display-page',
+ ),
+ 'block' => array(
+ 'title' => t('Block'),
+ 'help' => t('Display the view as a block.'),
+ 'handler' => 'views_plugin_display_block',
+ 'theme' => 'views_view',
+ 'uses hook block' => TRUE,
+ 'contextual links locations' => array('block'),
+ 'use ajax' => TRUE,
+ 'use pager' => TRUE,
+ 'use more' => TRUE,
+ 'accept attachments' => TRUE,
+ 'admin' => t('Block'),
+ 'help topic' => 'display-block',
+ ),
+ 'attachment' => array(
+ 'title' => t('Attachment'),
+ 'help' => t('Attachments added to other displays to achieve multiple views in the same view.'),
+ 'handler' => 'views_plugin_display_attachment',
+ 'theme' => 'views_view',
+ 'contextual links locations' => array(),
+ 'use ajax' => TRUE,
+ 'use pager' => FALSE,
+ 'use more' => TRUE,
+ 'accept attachments' => FALSE,
+ 'help topic' => 'display-attachment',
+ ),
+ 'feed' => array(
+ 'title' => t('Feed'),
+ 'help' => t('Display the view as a feed, such as an RSS feed.'),
+ 'handler' => 'views_plugin_display_feed',
+ 'uses hook menu' => TRUE,
+ 'use ajax' => FALSE,
+ 'use pager' => FALSE,
+ 'accept attachments' => FALSE,
+ 'admin' => t('Feed'),
+ 'help topic' => 'display-feed',
+ ),
+ 'embed' => array(
+ 'title' => t('Embed'),
+ 'help' => t('Provide a display which can be embedded using the views api.'),
+ 'handler' => 'views_plugin_display_embed',
+ 'theme' => 'views_view',
+ 'uses hook menu' => FALSE,
+ 'use ajax' => TRUE,
+ 'use pager' => TRUE,
+ 'accept attachments' => FALSE,
+ 'admin' => t('Embed'),
+ 'no ui' => !variable_get('views_ui_display_embed', FALSE),
+ ),
+ ),
+ 'display_extender' => array(
+ // Default settings for all display_extender plugins.
+ 'default' => array(
+ 'title' => t('Empty display extender'),
+ 'help' => t('Default settings for this view.'),
+ 'handler' => 'views_plugin_display_extender',
+ // You can force the plugin to be enabled
+ 'enabled' => FALSE,
+ 'no ui' => TRUE,
+ ),
+ ),
+ 'style' => array(
+ // Default settings for all style plugins.
+ 'default' => array(
+ 'title' => t('Unformatted list'),
+ 'help' => t('Displays rows one after another.'),
+ 'handler' => 'views_plugin_style_default',
+ 'theme' => 'views_view_unformatted',
+ 'uses row plugin' => TRUE,
+ 'uses row class' => TRUE,
+ 'uses grouping' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-unformatted',
+ ),
+ 'list' => array(
+ 'title' => t('HTML list'),
+ 'help' => t('Displays rows as an HTML list.'),
+ 'handler' => 'views_plugin_style_list',
+ 'theme' => 'views_view_list',
+ 'uses row plugin' => TRUE,
+ 'uses row class' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-list',
+ ),
+ 'grid' => array(
+ 'title' => t('Grid'),
+ 'help' => t('Displays rows in a grid.'),
+ 'handler' => 'views_plugin_style_grid',
+ 'theme' => 'views_view_grid',
+ 'uses fields' => FALSE,
+ 'uses row plugin' => TRUE,
+ 'uses row class' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-grid',
+ ),
+ 'table' => array(
+ 'title' => t('Table'),
+ 'help' => t('Displays rows in a table.'),
+ 'handler' => 'views_plugin_style_table',
+ 'theme' => 'views_view_table',
+ 'uses row plugin' => FALSE,
+ 'uses row class' => TRUE,
+ 'uses fields' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-table',
+ ),
+ 'default_summary' => array(
+ 'title' => t('List'),
+ 'help' => t('Displays the default summary as a list.'),
+ 'handler' => 'views_plugin_style_summary',
+ 'theme' => 'views_view_summary',
+ 'type' => 'summary', // only shows up as a summary style
+ 'uses options' => TRUE,
+ 'help topic' => 'style-summary',
+ ),
+ 'unformatted_summary' => array(
+ 'title' => t('Unformatted'),
+ 'help' => t('Displays the summary unformatted, with option for one after another or inline.'),
+ 'handler' => 'views_plugin_style_summary_unformatted',
+ 'theme' => 'views_view_summary_unformatted',
+ 'type' => 'summary', // only shows up as a summary style
+ 'uses options' => TRUE,
+ 'help topic' => 'style-summary-unformatted',
+ ),
+ 'rss' => array(
+ 'title' => t('RSS Feed'),
+ 'help' => t('Generates an RSS feed from a view.'),
+ 'handler' => 'views_plugin_style_rss',
+ 'theme' => 'views_view_rss',
+ 'uses row plugin' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'feed',
+ 'help topic' => 'style-rss',
+ ),
+ ),
+ 'row' => array(
+ 'fields' => array(
+ 'title' => t('Fields'),
+ 'help' => t('Displays the fields with an optional template.'),
+ 'handler' => 'views_plugin_row_fields',
+ 'theme' => 'views_view_fields',
+ 'uses fields' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-row-fields',
+ ),
+ 'rss_fields' => array(
+ 'title' => t('Fields'),
+ 'help' => t('Display fields as RSS items.'),
+ 'handler' => 'views_plugin_row_rss_fields',
+ 'theme' => 'views_view_row_rss',
+ 'uses fields' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'feed',
+ 'help topic' => 'style-row-fields',
+ ),
+ ),
+ 'argument default' => array(
+ 'parent' => array(
+ 'no ui' => TRUE,
+ 'handler' => 'views_plugin_argument_default',
+ 'parent' => '',
+ ),
+ 'fixed' => array(
+ 'title' => t('Fixed value'),
+ 'handler' => 'views_plugin_argument_default_fixed',
+ ),
+ 'php' => array(
+ 'title' => t('PHP Code'),
+ 'handler' => 'views_plugin_argument_default_php',
+ ),
+ 'raw' => array(
+ 'title' => t('Raw value from URL'),
+ 'handler' => 'views_plugin_argument_default_raw',
+ ),
+ ),
+ 'argument validator' => array(
+ 'php' => array(
+ 'title' => t('PHP Code'),
+ 'handler' => 'views_plugin_argument_validate_php',
+ ),
+ 'numeric' => array(
+ 'title' => t('Numeric'),
+ 'handler' => 'views_plugin_argument_validate_numeric',
+ ),
+ ),
+ 'access' => array(
+ 'none' => array(
+ 'title' => t('None'),
+ 'help' => t('Will be available to all users.'),
+ 'handler' => 'views_plugin_access_none',
+ 'help topic' => 'access-none',
+ ),
+ 'role' => array(
+ 'title' => t('Role'),
+ 'help' => t('Access will be granted to users with any of the specified roles.'),
+ 'handler' => 'views_plugin_access_role',
+ 'uses options' => TRUE,
+ 'help topic' => 'access-role',
+ ),
+ 'perm' => array(
+ 'title' => t('Permission'),
+ 'help' => t('Access will be granted to users with the specified permission string.'),
+ 'handler' => 'views_plugin_access_perm',
+ 'uses options' => TRUE,
+ 'help topic' => 'access-perm',
+ ),
+ ),
+ 'query' => array(
+ 'parent' => array(
+ 'no ui' => TRUE,
+ 'handler' => 'views_plugin_query',
+ 'parent' => '',
+ ),
+ 'views_query' => array(
+ 'title' => t('SQL Query'),
+ 'help' => t('Query will be generated and run using the Drupal database API.'),
+ 'handler' => 'views_plugin_query_default'
+ ),
+ ),
+ 'cache' => array(
+ 'parent' => array(
+ 'no ui' => TRUE,
+ 'handler' => 'views_plugin_cache',
+ 'parent' => '',
+ ),
+ 'none' => array(
+ 'title' => t('None'),
+ 'help' => t('No caching of Views data.'),
+ 'handler' => 'views_plugin_cache_none',
+ 'help topic' => 'cache-none',
+ ),
+ 'time' => array(
+ 'title' => t('Time-based'),
+ 'help' => t('Simple time-based caching of data.'),
+ 'handler' => 'views_plugin_cache_time',
+ 'uses options' => TRUE,
+ 'help topic' => 'cache-time',
+ ),
+ ),
+ 'exposed_form' => array(
+ 'parent' => array(
+ 'no ui' => TRUE,
+ 'handler' => 'views_plugin_exposed_form',
+ 'parent' => '',
+ ),
+ 'basic' => array(
+ 'title' => t('Basic'),
+ 'help' => t('Basic exposed form'),
+ 'handler' => 'views_plugin_exposed_form_basic',
+ 'uses options' => TRUE,
+ 'help topic' => 'exposed-form-basic',
+ ),
+ 'input_required' => array(
+ 'title' => t('Input required'),
+ 'help' => t('An exposed form that only renders a view if the form contains user input.'),
+ 'handler' => 'views_plugin_exposed_form_input_required',
+ 'uses options' => TRUE,
+ 'help topic' => 'exposed-form-input-required',
+ ),
+ ),
+ 'pager' => array(
+ 'parent' => array(
+ 'no ui' => TRUE,
+ 'handler' => 'views_plugin_pager',
+ 'parent' => '',
+ ),
+ 'none' => array(
+ 'title' => t('Display all items'),
+ 'help' => t("Display all items that this view might find"),
+ 'handler' => 'views_plugin_pager_none',
+ 'help topic' => 'pager-none',
+ 'uses options' => TRUE,
+ 'type' => 'basic',
+ ),
+ 'some' => array(
+ 'title' => t('Display a specified number of items'),
+ 'help' => t('Display a limited number items that this view might find.'),
+ 'handler' => 'views_plugin_pager_some',
+ 'help topic' => 'pager-some',
+ 'uses options' => TRUE,
+ 'type' => 'basic',
+ ),
+ 'full' => array(
+ 'title' => t('Paged output, full pager'),
+ 'short title' => t('Full'),
+ 'help' => t('Paged output, full Drupal style'),
+ 'handler' => 'views_plugin_pager_full',
+ 'help topic' => 'pager-full',
+ 'uses options' => TRUE,
+ ),
+ 'mini' => array(
+ 'title' => t('Paged output, mini pager'),
+ 'short title' => t('Mini'),
+ 'help' => t('Use the mini pager output.'),
+ 'handler' => 'views_plugin_pager_mini',
+ 'help topic' => 'pager-mini',
+ 'uses options' => TRUE,
+ 'parent' => 'full',
+ ),
+ ),
+ 'localization' => array(
+ 'parent' => array(
+ 'no ui' => TRUE,
+ 'handler' => 'views_plugin_localization',
+ 'parent' => '',
+ ),
+ 'none' => array(
+ 'title' => t('None'),
+ 'help' => t('Do not pass admin strings for translation.'),
+ 'handler' => 'views_plugin_localization_none',
+ 'help topic' => 'localization-none',
+ ),
+ 'core' => array(
+ 'title' => t('Core'),
+ 'help' => t("Use Drupal core t() function. Not recommended, as it doesn't support updates to existing strings."),
+ 'handler' => 'views_plugin_localization_core',
+ 'help topic' => 'localization-core',
+ ),
+ ),
+ );
+ // Add a help message pointing to the i18views module if it is not present.
+ if (!module_exists('i18nviews')) {
+ $plugins['localization']['core']['help'] .= ' ' . t('If you need to translate Views labels into other languages, consider installing the <a href="!path">Internationalization</a> package\'s Views translation module.', array('!path' => url('http://drupal.org/project/i18n', array('absolute' => TRUE))));
+ }
+
+ if (module_invoke('ctools', 'api_version', '1.3')) {
+ $plugins['style']['jump_menu_summary'] = array(
+ 'title' => t('Jump menu'),
+ 'help' => t('Puts all of the results into a select box and allows the user to go to a different page based upon the results.'),
+ 'handler' => 'views_plugin_style_summary_jump_menu',
+ 'theme' => 'views_view_summary_jump_menu',
+ 'type' => 'summary', // only shows up as a summary style
+ 'uses options' => TRUE,
+ 'help topic' => 'style-summary-jump-menu',
+ );
+ $plugins['style']['jump_menu'] = array(
+ 'title' => t('Jump menu'),
+ 'help' => t('Puts all of the results into a select box and allows the user to go to a different page based upon the results.'),
+ 'handler' => 'views_plugin_style_jump_menu',
+ 'theme' => 'views_view_jump_menu',
+ 'uses row plugin' => TRUE,
+ 'uses fields' => TRUE,
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-jump-menu',
+ );
+ }
+
+ return $plugins;
+}
+
+/**
+ * Builds and return a list of all plugins available in the system.
+ *
+ * @return Nested array of plugins, grouped by type.
+ */
+function views_discover_plugins() {
+ $cache = array('display' => array(), 'style' => array(), 'row' => array(), 'argument default' => array(), 'argument validator' => array(), 'access' => array(), 'cache' => array(), 'exposed_form' => array());
+ // Get plugins from all modules.
+ foreach (module_implements('views_plugins') as $module) {
+ $function = $module . '_views_plugins';
+ $result = $function();
+ if (!is_array($result)) {
+ continue;
+ }
+
+ $module_dir = isset($result['module']) ? $result['module'] : $module;
+ // Setup automatic path/file finding for theme registration
+ if ($module_dir == 'views') {
+ $theme_path = drupal_get_path('module', $module_dir) . '/theme';
+ $theme_file = 'theme.inc';
+ $path = drupal_get_path('module', $module_dir) . '/plugins';
+ }
+ else {
+ $theme_path = $path = drupal_get_path('module', $module_dir);
+ $theme_file = "$module.views.inc";
+ }
+
+ foreach ($result as $type => $info) {
+ if ($type == 'module') {
+ continue;
+ }
+ foreach ($info as $plugin => $def) {
+ $def['module'] = $module_dir;
+ if (!isset($def['theme path'])) {
+ $def['theme path'] = $theme_path;
+ }
+ if (!isset($def['theme file'])) {
+ $def['theme file'] = $theme_file;
+ }
+ if (!isset($def['path'])) {
+ $def['path'] = $path;
+ }
+ if (!isset($def['file'])) {
+ $def['file'] = $def['handler'] . '.inc';
+ }
+ if (!isset($def['parent'])) {
+ $def['parent'] = 'parent';
+ }
+ // Set the internal name to be able to read it out later.
+ $def['name'] = $plugin;
+
+ // merge the new data in
+ $cache[$type][$plugin] = $def;
+ }
+ }
+ }
+
+ // Let other modules modify the plugins.
+ drupal_alter('views_plugins', $cache);
+ return $cache;
+}
+
+/**
+ * Abstract base class to provide interface common to all plugins.
+ */
+class views_plugin extends views_object {
+ /**
+ * The top object of a view.
+ *
+ * @var view
+ */
+ var $view = NULL;
+
+ /**
+ * The current used views display.
+ *
+ * @var views_display
+ */
+ var $display = NULL;
+
+ /**
+ * The plugin type of this plugin, for example style or query.
+ */
+ var $plugin_type = NULL;
+
+ /**
+ * The plugin name of this plugin, for example table or full.
+ */
+ var $plugin_name = NULL;
+
+ /**
+ * Init will be called after construct, when the plugin is attached to a
+ * view and a display.
+ */
+
+ /**
+ * Provide a form to edit options for this plugin.
+ */
+ function options_form(&$form, &$form_state) {
+ // Some form elements belong in a fieldset for presentation, but can't
+ // be moved into one because of the form_state['values'] hierarchy. Those
+ // elements can add a #fieldset => 'fieldset_name' property, and they'll
+ // be moved to their fieldset during pre_render.
+ $form['#pre_render'][] = 'views_ui_pre_render_add_fieldset_markup';
+ }
+
+ /**
+ * Validate the options form.
+ */
+ function options_validate(&$form, &$form_state) { }
+
+ /**
+ * Handle any special handling on the validate form.
+ */
+ function options_submit(&$form, &$form_state) { }
+
+ /**
+ * Add anything to the query that we might need to.
+ */
+ function query() { }
+
+ /**
+ * Provide a full list of possible theme templates used by this style.
+ */
+ function theme_functions() {
+ if (empty($this->definition['theme'])) {
+ $this->definition['theme'] = 'views_view';
+ }
+ return views_theme_functions($this->definition['theme'], $this->view, $this->display);
+ }
+
+ /**
+ * Provide a list of additional theme functions for the theme information page
+ */
+ function additional_theme_functions() {
+ $funcs = array();
+ if (!empty($this->definition['additional themes'])) {
+ foreach ($this->definition['additional themes'] as $theme => $type) {
+ $funcs[] = views_theme_functions($theme, $this->view, $this->display);
+ }
+ }
+ return $funcs;
+ }
+
+ /**
+ * Validate that the plugin is correct and can be saved.
+ *
+ * @return
+ * An array of error strings to tell the user what is wrong with this
+ * plugin.
+ */
+ function validate() { return array(); }
+
+ /**
+ * Returns the summary of the settings in the display.
+ */
+ function summary_title() {
+ return t('Settings');
+ }
+ /**
+ * Return the human readable name of the display.
+ *
+ * This appears on the ui beside each plugin and beside the settings link.
+ */
+ function plugin_title() {
+ if (isset($this->definition['short title'])) {
+ return check_plain($this->definition['short title']);
+ }
+ return check_plain($this->definition['title']);
+ }
+}
+
+/**
+ * Get enabled display extenders.
+ */
+function views_get_enabled_display_extenders() {
+ $enabled = array_filter(variable_get('views_display_extenders', array()));
+ $options = views_fetch_plugin_names('display_extender');
+ foreach ($options as $name => $plugin) {
+ $enabled[$name] = $name;
+ }
+
+ return $enabled;
+}
diff --git a/sites/all/modules/views/includes/view.inc b/sites/all/modules/views/includes/view.inc
new file mode 100644
index 000000000..2b737e7ea
--- /dev/null
+++ b/sites/all/modules/views/includes/view.inc
@@ -0,0 +1,2661 @@
+<?php
+
+/**
+ * @file
+ * Provides the view object type and associated methods.
+ */
+
+/**
+ * @defgroup views_objects Objects that represent a View or part of a view
+ * @{
+ * These objects are the core of Views do the bulk of the direction and
+ * storing of data. All database activity is in these objects.
+ */
+
+/**
+ * An object to contain all of the data to generate a view, plus the member
+ * functions to build the view query, execute the query and render the output.
+ */
+class view extends views_db_object {
+ var $db_table = 'views_view';
+ var $base_table = 'node';
+ var $base_field = 'nid';
+
+ /**
+ * The name of the view.
+ *
+ * @var string
+ */
+ var $name = "";
+
+ /**
+ * The id of the view, which is used only for views in the database.
+ *
+ * @var number
+ */
+ var $vid;
+
+ /**
+ * The description of the view, which is used only in the interface.
+ *
+ * @var string
+ */
+ var $description;
+
+ /**
+ * The "tags" of a view.
+ * The tags are stored as a single string, though it is used as multiple tags
+ * for example in the views overview.
+ *
+ * @var string
+ */
+ var $tag;
+
+ /**
+ * The human readable name of the view.
+ *
+ * @var string
+ */
+ var $human_name;
+
+ /**
+ * The core version the view was created for.
+ * @var int
+ */
+ var $core;
+
+ /**
+ * The views-api version this view was created by.
+ *
+ * Some examples of the variable are 3.0 or 3.0-alpha1
+ *
+ * @var string
+ */
+ var $api_version;
+
+ /**
+ * Is the view disabled.
+ *
+ * This value is used for exported view, to provide some default views which aren't enabled.
+ *
+ * @var bool
+ */
+ var $disabled;
+
+ // State variables
+ var $built = FALSE;
+ var $executed = FALSE;
+ var $editing = FALSE;
+
+ var $args = array();
+ var $build_info = array();
+
+ var $use_ajax = FALSE;
+
+ /**
+ * Where the results of a query will go.
+ *
+ * The array must use a numeric index starting at 0.
+ *
+ * @var array
+ */
+ var $result = array();
+
+ // May be used to override the current pager info.
+ var $current_page = NULL;
+ var $items_per_page = NULL;
+ var $offset = NULL;
+ var $total_rows = NULL;
+
+ // Places to put attached renderings:
+ var $attachment_before = '';
+ var $attachment_after = '';
+
+ // Exposed widget input
+ var $exposed_data = array();
+ var $exposed_input = array();
+ // Exposed widget input directly from the $form_state['values'].
+ var $exposed_raw_input = array();
+
+ // Used to store views that were previously running if we recurse.
+ var $old_view = array();
+
+ // To avoid recursion in views embedded into areas.
+ var $parent_views = array();
+
+ // Is the current stored view runned as an attachment to another view.
+ var $is_attachment = NULL;
+
+ // Stores the next steps of form items to handle.
+ // It's an array of stack items, which contain the form id, the type of form,
+ // the view, the display and some additional arguments.
+ // @see views_ui_add_form_to_stack()
+ // var $stack;
+
+ /**
+ * Identifier of the current display.
+ *
+ * @var string
+ */
+ var $current_display;
+
+ /**
+ * Where the $query object will reside:
+ *
+ * @var views_plugin_query
+ */
+ var $query = NULL;
+
+ /**
+ * The current used display plugin.
+ *
+ * @var views_plugin_display
+ */
+ var $display_handler;
+
+ /**
+ * Stores all display handlers of this view.
+ *
+ * @var array[views_display]
+ */
+ var $display;
+
+ /**
+ * The current used style plugin.
+ *
+ * @var views_plugin_style
+ */
+ var $style_plugin;
+
+ /**
+ * Stored the changed options of the style plugin.
+ *
+ * @deprecated Better use $view->style_plugin->options
+ * @var array
+ */
+ var $style_options;
+
+ /**
+ * Stores the current active row while rendering.
+ *
+ * @var int
+ */
+ var $row_index;
+
+ /**
+ * Allow to override the url of the current view.
+ *
+ * @var string
+ */
+ var $override_url = NULL;
+
+ /**
+ * Allow to override the path used for generated urls.
+ *
+ * @var string
+ */
+ var $override_path = NULL;
+
+ /**
+ * Allow to override the used database which is used for this query.
+ */
+ var $base_database = NULL;
+
+ /**
+ * Here comes a list of the possible handler which are active on this view.
+ */
+
+ /**
+ * Stores the field handlers which are initialized on this view.
+ * @var array[views_handler_field]
+ */
+ var $field;
+
+ /**
+ * Stores the argument handlers which are initialized on this view.
+ * @var array[views_handler_argument]
+ */
+ var $argument;
+
+ /**
+ * Stores the sort handlers which are initialized on this view.
+ * @var array[views_handler_sort]
+ */
+ var $sort;
+
+ /**
+ * Stores the filter handlers which are initialized on this view.
+ * @var array[views_handler_filter]
+ */
+ var $filter;
+
+ /**
+ * Stores the relationship handlers which are initialized on this view.
+ * @var array[views_handler_relationship]
+ */
+ var $relationship;
+
+ /**
+ * Stores the area handlers for the header which are initialized on this view.
+ * @var array[views_handler_area]
+ */
+ var $header;
+
+ /**
+ * Stores the area handlers for the footer which are initialized on this view.
+ * @var array[views_handler_area]
+ */
+ var $footer;
+
+ /**
+ * Stores the area handlers for the empty text which are initialized on this view.
+ * @var array[views_handler_area]
+ */
+ var $empty;
+
+ /**
+ * Constructor
+ */
+ function __construct() {
+ parent::init();
+ // Make sure all of our sub objects are arrays.
+ foreach ($this->db_objects() as $object) {
+ $this->$object = array();
+ }
+ }
+
+ /**
+ * Perform automatic updates when loading or importing a view.
+ *
+ * Over time, some things about Views or Drupal data has changed.
+ * this attempts to do some automatic updates that must happen
+ * to ensure older views will at least try to work.
+ */
+ function update() {
+ // When views are converted automatically the base_table should be renamed
+ // to have a working query.
+ $this->base_table = views_move_table($this->base_table);
+ }
+
+
+ /**
+ * Returns a list of the sub-object types used by this view. These types are
+ * stored on the display, and are used in the build process.
+ */
+ function display_objects() {
+ return array('argument', 'field', 'sort', 'filter', 'relationship', 'header', 'footer', 'empty');
+ }
+
+ /**
+ * Returns the complete list of dependent objects in a view, for the purpose
+ * of initialization and loading/saving to/from the database.
+ */
+ static function db_objects() {
+ return array('display');
+ }
+
+ /**
+ * Set the arguments that come to this view. Usually from the URL
+ * but possibly from elsewhere.
+ */
+ function set_arguments($args) {
+ $this->args = $args;
+ }
+
+ /**
+ * Change/Set the current page for the pager.
+ */
+ function set_current_page($page) {
+ $this->current_page = $page;
+
+ // If the pager is already initialized, pass it through to the pager.
+ if (!empty($this->query->pager)) {
+ return $this->query->pager->set_current_page($page);
+ }
+
+ }
+
+ /**
+ * Get the current page from the pager.
+ */
+ function get_current_page() {
+ // If the pager is already initialized, pass it through to the pager.
+ if (!empty($this->query->pager)) {
+ return $this->query->pager->get_current_page();
+ }
+
+ if (isset($this->current_page)) {
+ return $this->current_page;
+ }
+ }
+
+ /**
+ * Get the items per page from the pager.
+ */
+ function get_items_per_page() {
+ // If the pager is already initialized, pass it through to the pager.
+ if (!empty($this->query->pager)) {
+ return $this->query->pager->get_items_per_page();
+ }
+
+ if (isset($this->items_per_page)) {
+ return $this->items_per_page;
+ }
+ }
+
+ /**
+ * Set the items per page on the pager.
+ */
+ function set_items_per_page($items_per_page) {
+ $this->items_per_page = $items_per_page;
+
+ // If the pager is already initialized, pass it through to the pager.
+ if (!empty($this->query->pager)) {
+ $this->query->pager->set_items_per_page($items_per_page);
+ }
+ }
+
+ /**
+ * Get the pager offset from the pager.
+ */
+ function get_offset() {
+ // If the pager is already initialized, pass it through to the pager.
+ if (!empty($this->query->pager)) {
+ return $this->query->pager->get_offset();
+ }
+
+ if (isset($this->offset)) {
+ return $this->offset;
+ }
+ }
+
+ /**
+ * Set the offset on the pager.
+ */
+ function set_offset($offset) {
+ $this->offset = $offset;
+
+ // If the pager is already initialized, pass it through to the pager.
+ if (!empty($this->query->pager)) {
+ $this->query->pager->set_offset($offset);
+ }
+ }
+
+ /**
+ * Determine if the pager actually uses a pager.
+ */
+ function use_pager() {
+ if (!empty($this->query->pager)) {
+ return $this->query->pager->use_pager();
+ }
+ }
+
+ /**
+ * Whether or not AJAX should be used. If AJAX is used, paging,
+ * tablesorting and exposed filters will be fetched via an AJAX call
+ * rather than a page refresh.
+ */
+ function set_use_ajax($use_ajax) {
+ $this->use_ajax = $use_ajax;
+ }
+
+ /**
+ * Set the exposed filters input to an array. If unset they will be taken
+ * from $_GET when the time comes.
+ */
+ function set_exposed_input($filters) {
+ $this->exposed_input = $filters;
+ }
+
+ /**
+ * Figure out what the exposed input for this view is.
+ */
+ function get_exposed_input() {
+ // Fill our input either from $_GET or from something previously set on the
+ // view.
+ if (empty($this->exposed_input)) {
+ $this->exposed_input = $_GET;
+ // unset items that are definitely not our input:
+ foreach (array('page', 'q') as $key) {
+ if (isset($this->exposed_input[$key])) {
+ unset($this->exposed_input[$key]);
+ }
+ }
+
+ // If we have no input at all, check for remembered input via session.
+
+ // If filters are not overridden, store the 'remember' settings on the
+ // default display. If they are, store them on this display. This way,
+ // multiple displays in the same view can share the same filters and
+ // remember settings.
+ $display_id = ($this->display_handler->is_defaulted('filters')) ? 'default' : $this->current_display;
+
+ if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->name][$display_id])) {
+ $this->exposed_input = $_SESSION['views'][$this->name][$display_id];
+ }
+ }
+
+ return $this->exposed_input;
+ }
+
+ /**
+ * Set the display for this view and initialize the display handler.
+ */
+ function init_display($reset = FALSE) {
+ // The default display is always the first one in the list.
+ if (isset($this->current_display)) {
+ return TRUE;
+ }
+
+ // Instantiate all displays
+ foreach (array_keys($this->display) as $id) {
+ // Correct for shallow cloning
+ // Often we'll have a cloned view so we don't mess up each other's
+ // displays, but the clone is pretty shallow and doesn't necessarily
+ // clone the displays. We can tell this by looking to see if a handler
+ // has already been set; if it has, but $this->current_display is not
+ // set, then something is dreadfully wrong.
+ if (!empty($this->display[$id]->handler)) {
+ $this->display[$id] = clone $this->display[$id];
+ unset($this->display[$id]->handler);
+ }
+ $this->display[$id]->handler = views_get_plugin('display', $this->display[$id]->display_plugin);
+ if (!empty($this->display[$id]->handler)) {
+ $this->display[$id]->handler->localization_keys = array($id);
+ // Initialize the new display handler with data.
+ $this->display[$id]->handler->init($this, $this->display[$id]);
+ // If this is NOT the default display handler, let it know which is
+ // since it may well utilize some data from the default.
+ // This assumes that the 'default' handler is always first. It always
+ // is. Make sure of it.
+ if ($id != 'default') {
+ $this->display[$id]->handler->default_display = &$this->display['default']->handler;
+ }
+ }
+ }
+
+ $this->current_display = 'default';
+ $this->display_handler = &$this->display['default']->handler;
+
+ return TRUE;
+ }
+
+ /**
+ * Get the first display that is accessible to the user.
+ *
+ * @param $displays
+ * Either a single display id or an array of display ids.
+ */
+ function choose_display($displays) {
+ if (!is_array($displays)) {
+ return $displays;
+ }
+
+ $this->init_display();
+
+ foreach ($displays as $display_id) {
+ if ($this->display[$display_id]->handler->access()) {
+ return $display_id;
+ }
+ }
+
+ return 'default';
+ }
+
+ /**
+ * Set the display as current.
+ *
+ * @param $display_id
+ * The id of the display to mark as current.
+ */
+ function set_display($display_id = NULL) {
+ // If we have not already initialized the display, do so. But be careful.
+ if (empty($this->current_display)) {
+ $this->init_display();
+
+ // If handlers were not initialized, and no argument was sent, set up
+ // to the default display.
+ if (empty($display_id)) {
+ $display_id = 'default';
+ }
+ }
+
+ $display_id = $this->choose_display($display_id);
+
+ // If no display id sent in and one wasn't chosen above, we're finished.
+ if (empty($display_id)) {
+ return FALSE;
+ }
+
+ // Ensure the requested display exists.
+ if (empty($this->display[$display_id])) {
+ $display_id = 'default';
+ if (empty($this->display[$display_id])) {
+ vpr('set_display() called with invalid display id @display.', array('@display' => $display_id));
+ return FALSE;
+ }
+ }
+
+ // Set the current display.
+ $this->current_display = $display_id;
+
+ // Ensure requested display has a working handler.
+ if (empty($this->display[$display_id]->handler)) {
+ return FALSE;
+ }
+
+ // Set a shortcut
+ $this->display_handler = &$this->display[$display_id]->handler;
+
+ return TRUE;
+ }
+
+ /**
+ * Find and initialize the style plugin.
+ *
+ * Note that arguments may have changed which style plugin we use, so
+ * check the view object first, then ask the display handler.
+ */
+ function init_style() {
+ if (isset($this->style_plugin)) {
+ return is_object($this->style_plugin);
+ }
+
+ if (!isset($this->plugin_name)) {
+ $this->plugin_name = $this->display_handler->get_option('style_plugin');
+ $this->style_options = $this->display_handler->get_option('style_options');
+ }
+
+ $this->style_plugin = views_get_plugin('style', $this->plugin_name);
+
+ if (empty($this->style_plugin)) {
+ return FALSE;
+ }
+
+ // init the new style handler with data.
+ $this->style_plugin->init($this, $this->display[$this->current_display], $this->style_options);
+ return TRUE;
+ }
+
+ /**
+ * Attempt to discover if the view has handlers missing relationships.
+ *
+ * This will try to add relationships automatically if it can, and will
+ * remove the handlers if it cannot.
+ */
+ function fix_missing_relationships() {
+ if (isset($this->relationships_fixed)) {
+ return;
+ }
+
+ $this->relationships_fixed = TRUE;
+
+ // Go through all of our handler types and test them to see if they
+ // are missing relationships. Missing relationships can cause fatally
+ // broken Views.
+ $base_tables = array(
+ $this->base_table => TRUE,
+ '#global' => TRUE,
+ );
+
+ // For each relationship we have, make sure we mark the base it provides as
+ // available.
+ foreach ($this->display_handler->get_option('relationships') as $id => $options) {
+ $options['table'] = views_move_table($options['table']);
+ $data = views_fetch_data($options['table'], FALSE);
+ if (isset($data[$options['field']]['relationship']['base'])) {
+ $base_tables[$data[$options['field']]['relationship']['base']] = TRUE;
+ }
+ }
+
+ $base_tables = array_keys($base_tables);
+ $missing_base_tables = array();
+
+ $types = views_object_types();
+ foreach ($types as $key => $info) {
+ foreach ($this->display_handler->get_option($info['plural']) as $id => $options) {
+ $options['table'] = views_move_table($options['table']);
+ $data = views_fetch_data($options['table'], FALSE);
+
+ $valid_bases = array($options['table']);
+ if (isset($data['table']['join'])) {
+ $valid_bases = array_merge($valid_bases, array_keys($data['table']['join']));
+ }
+
+ // If the base table is missing, record it so we can try to fix it.
+ if (!array_intersect($valid_bases, $base_tables)) {
+ $missing_base_tables[$options['table']][] = array('type' => $key, 'id' => $id);
+ }
+ }
+ }
+
+ if (!empty($missing_base_tables)) {
+ // This will change handlers, so make sure any existing handlers get
+ // tossed.
+ $this->display_handler->handlers = array();
+ $this->relationships_changed = TRUE;
+ $this->changed = TRUE;
+
+ // Try to fix it.
+ foreach ($missing_base_tables as $table => $handlers) {
+ $data = views_fetch_data($table);
+ $relationship = NULL;
+
+ // Does the missing base table have a default relationship we can
+ // throw in?
+ if (isset($data['table']['default_relationship'][$this->base_table])) {
+ // Create the relationship.
+ $info = $data['table']['default_relationship'][$this->base_table];
+
+ $relationship_options = isset($info['options']) ? $info['options'] : array();
+ $relationship = $this->add_item($this->current_display, 'relationship', $info['table'], $info['field'], $relationship_options);
+ }
+ foreach ($handlers as $handler) {
+ $options = $this->display_handler->get_option($types[$handler['type']]['plural']);
+ if ($relationship) {
+ $options[$handler['id']]['relationship'] = $relationship;
+ }
+ else {
+ unset($options[$handler['id']]);
+ }
+ $this->display_handler->set_option($types[$handler['type']]['plural'], $options);
+ }
+ }
+ }
+ }
+
+ /**
+ * Acquire and attach all of the handlers.
+ */
+ function init_handlers() {
+ if (empty($this->inited)) {
+ $this->fix_missing_relationships();
+ foreach (views_object_types() as $key => $info) {
+ $this->_init_handler($key, $info);
+ }
+ $this->inited = TRUE;
+ }
+ }
+
+ /**
+ * Initialize the pager
+ *
+ * Like style initialization, pager initialization is held until late
+ * to allow for overrides.
+ */
+ function init_pager() {
+ if (empty($this->query->pager)) {
+ $this->query->pager = $this->display_handler->get_plugin('pager');
+
+ if ($this->query->pager->use_pager()) {
+ $this->query->pager->set_current_page($this->current_page);
+ }
+
+ // These overrides may have been set earlier via $view->set_*
+ // functions.
+ if (isset($this->items_per_page)) {
+ $this->query->pager->set_items_per_page($this->items_per_page);
+ }
+
+ if (isset($this->offset)) {
+ $this->query->pager->set_offset($this->offset);
+ }
+ }
+ }
+
+ /**
+ * Create a list of base tables eligible for this view. Used primarily
+ * for the UI. Display must be already initialized.
+ */
+ function get_base_tables() {
+ $base_tables = array(
+ $this->base_table => TRUE,
+ '#global' => TRUE,
+ );
+
+ foreach ($this->display_handler->get_handlers('relationship') as $handler) {
+ $base_tables[$handler->definition['base']] = TRUE;
+ }
+ return $base_tables;
+ }
+
+ /**
+ * Run the pre_query() on all active handlers.
+ */
+ function _pre_query() {
+ foreach (views_object_types() as $key => $info) {
+ $handlers = &$this->$key;
+ $position = 0;
+ foreach ($handlers as $id => $handler) {
+ $handlers[$id]->position = $position;
+ $handlers[$id]->pre_query();
+ $position++;
+ }
+ }
+ }
+
+ /**
+ * Run the post_execute() on all active handlers.
+ */
+ function _post_execute() {
+ foreach (views_object_types() as $key => $info) {
+ $handlers = &$this->$key;
+ foreach ($handlers as $id => $handler) {
+ $handlers[$id]->post_execute($this->result);
+ }
+ }
+ }
+
+ /**
+ * Attach all of the handlers for each type.
+ *
+ * @param $key
+ * One of 'argument', 'field', 'sort', 'filter', 'relationship'
+ * @param $info
+ * The $info from views_object_types for this object.
+ */
+ function _init_handler($key, $info) {
+ // Load the requested items from the display onto the object.
+ $this->$key = &$this->display_handler->get_handlers($key);
+
+ // This reference deals with difficult PHP indirection.
+ $handlers = &$this->$key;
+
+ // Run through and test for accessibility.
+ foreach ($handlers as $id => $handler) {
+ if (!$handler->access()) {
+ unset($handlers[$id]);
+ }
+ }
+ }
+
+ /**
+ * Build all the arguments.
+ */
+ function _build_arguments() {
+ // Initially, we want to build sorts and fields. This can change, though,
+ // if we get a summary view.
+ if (empty($this->argument)) {
+ return TRUE;
+ }
+
+ // build arguments.
+ $position = -1;
+
+ // Create a title for use in the breadcrumb trail.
+ $title = $this->display_handler->get_option('title');
+
+ $this->build_info['breadcrumb'] = array();
+ $breadcrumb_args = array();
+ $substitutions = array();
+
+ $status = TRUE;
+
+ // Iterate through each argument and process.
+ foreach ($this->argument as $id => $arg) {
+ $position++;
+ $argument = &$this->argument[$id];
+
+ if ($argument->broken()) {
+ continue;
+ }
+
+ $argument->set_relationship();
+
+ $arg = isset($this->args[$position]) ? $this->args[$position] : NULL;
+ $argument->position = $position;
+
+ if (isset($arg) || $argument->has_default_argument()) {
+ if (!isset($arg)) {
+ $arg = $argument->get_default_argument();
+ // make sure default args get put back.
+ if (isset($arg)) {
+ $this->args[$position] = $arg;
+ }
+ // remember that this argument was computed, not passed on the URL.
+ $argument->is_default = TRUE;
+ }
+
+ // Set the argument, which will also validate that the argument can be set.
+ if (!$argument->set_argument($arg)) {
+ $status = $argument->validate_fail($arg);
+ break;
+ }
+
+ if ($argument->is_exception()) {
+ $arg_title = $argument->exception_title();
+ }
+ else {
+ $arg_title = $argument->get_title();
+ $argument->query($this->display_handler->use_group_by());
+ }
+
+ // Add this argument's substitution
+ $substitutions['%' . ($position + 1)] = $arg_title;
+ $substitutions['!' . ($position + 1)] = strip_tags(decode_entities($arg));
+
+ // Since we're really generating the breadcrumb for the item above us,
+ // check the default action of this argument.
+ if ($this->display_handler->uses_breadcrumb() && $argument->uses_breadcrumb()) {
+ $path = $this->get_url($breadcrumb_args);
+ if (strpos($path, '%') === FALSE) {
+ if (!empty($argument->options['breadcrumb_enable']) && !empty($argument->options['breadcrumb'])) {
+ $breadcrumb = $argument->options['breadcrumb'];
+ }
+ else {
+ $breadcrumb = $title;
+ }
+ $this->build_info['breadcrumb'][$path] = str_replace(array_keys($substitutions), $substitutions, $breadcrumb);
+ }
+ }
+
+ // Allow the argument to muck with this breadcrumb.
+ $argument->set_breadcrumb($this->build_info['breadcrumb']);
+
+ // Test to see if we should use this argument's title
+ if (!empty($argument->options['title_enable']) && !empty($argument->options['title'])) {
+ $title = $argument->options['title'];
+ }
+
+ $breadcrumb_args[] = $arg;
+ }
+ else {
+ // determine default condition and handle.
+ $status = $argument->default_action();
+ break;
+ }
+
+ // Be safe with references and loops:
+ unset($argument);
+ }
+
+ // set the title in the build info.
+ if (!empty($title)) {
+ $this->build_info['title'] = $title;
+ }
+
+ // Store the arguments for later use.
+ $this->build_info['substitutions'] = $substitutions;
+
+ return $status;
+ }
+
+ /**
+ * Do some common building initialization.
+ */
+ function init_query() {
+ if (!empty($this->query)) {
+ $class = get_class($this->query);
+ if ($class && $class != 'stdClass') {
+ // return if query is already initialized.
+ return TRUE;
+ }
+ }
+
+ // Create and initialize the query object.
+ $views_data = views_fetch_data($this->base_table);
+ $this->base_field = !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : '';
+ if (!empty($views_data['table']['base']['database'])) {
+ $this->base_database = $views_data['table']['base']['database'];
+ }
+
+ // Load the options.
+ $query_options = $this->display_handler->get_option('query');
+
+ // Create and initialize the query object.
+ $plugin = !empty($views_data['table']['base']['query class']) ? $views_data['table']['base']['query class'] : 'views_query';
+ $this->query = views_get_plugin('query', $plugin);
+
+ if (empty($this->query)) {
+ return FALSE;
+ }
+
+ $this->query->init($this->base_table, $this->base_field, $query_options['options']);
+ return TRUE;
+ }
+
+ /**
+ * Build the query for the view.
+ */
+ function build($display_id = NULL) {
+ if (!empty($this->built)) {
+ return;
+ }
+
+ if (empty($this->current_display) || $display_id) {
+ if (!$this->set_display($display_id)) {
+ return FALSE;
+ }
+ }
+
+ // Let modules modify the view just prior to building it.
+ foreach (module_implements('views_pre_build') as $module) {
+ $function = $module . '_views_pre_build';
+ $function($this);
+ }
+
+ // Attempt to load from cache.
+ // @todo Load a build_info from cache.
+
+ $start = microtime(TRUE);
+ // If that fails, let's build!
+ $this->build_info = array(
+ 'query' => '',
+ 'count_query' => '',
+ 'query_args' => array(),
+ );
+
+ $this->init_query();
+
+ // Call a module hook and see if it wants to present us with a
+ // pre-built query or instruct us not to build the query for
+ // some reason.
+ // @todo: Implement this. Use the same mechanism Panels uses.
+
+ // Run through our handlers and ensure they have necessary information.
+ $this->init_handlers();
+
+ // Let the handlers interact with each other if they really want.
+ $this->_pre_query();
+
+ if ($this->display_handler->uses_exposed()) {
+ $exposed_form = $this->display_handler->get_plugin('exposed_form');
+ // (1) Record the errors before rendering the exposed form widgets.
+ $errors_before = form_set_error();
+ $this->exposed_widgets = $exposed_form->render_exposed_form();
+ // (2) Record the errors after rendering the exposed form widgets.
+ $errors_after = form_set_error();
+ // Find out if the validation of any of the elements in the exposed form
+ // has failed by comparing (1) and (2) above. Don't mess with the view
+ // otherwise.
+ $exposed_errors = count($errors_after) > count($errors_before);
+ if ($exposed_errors || !empty($this->build_info['abort'])) {
+ $this->built = TRUE;
+ // Don't execute the query, but rendering will still be executed to display the empty text.
+ $this->executed = TRUE;
+ return empty($this->build_info['fail']);
+ }
+ }
+
+ // Build all the relationships first thing.
+ $this->_build('relationship');
+
+ // Set the filtering groups.
+ if (!empty($this->filter)) {
+ $filter_groups = $this->display_handler->get_option('filter_groups');
+ if ($filter_groups) {
+ $this->query->set_group_operator($filter_groups['operator']);
+ foreach($filter_groups['groups'] as $id => $operator) {
+ $this->query->set_where_group($operator, $id);
+ }
+ }
+ }
+
+ // Build all the filters.
+ $this->_build('filter');
+
+ $this->build_sort = TRUE;
+
+ // Arguments can, in fact, cause this whole thing to abort.
+ if (!$this->_build_arguments()) {
+ $this->build_time = microtime(TRUE) - $start;
+ $this->attach_displays();
+ return $this->built;
+ }
+
+ // Initialize the style; arguments may have changed which style we use,
+ // so waiting as long as possible is important. But we need to know
+ // about the style when we go to build fields.
+ if (!$this->init_style()) {
+ $this->build_info['fail'] = TRUE;
+ return FALSE;
+ }
+
+ if ($this->style_plugin->uses_fields()) {
+ $this->_build('field');
+ }
+
+ // Build our sort criteria if we were instructed to do so.
+ if (!empty($this->build_sort)) {
+ // Allow the style handler to deal with sorting.
+ if ($this->style_plugin->build_sort()) {
+ $this->_build('sort');
+ }
+ // allow the plugin to build second sorts as well.
+ $this->style_plugin->build_sort_post();
+ }
+
+ // Allow area handlers to affect the query.
+ $this->_build('header');
+ $this->_build('footer');
+ $this->_build('empty');
+
+ // Allow display handler to affect the query:
+ $this->display_handler->query($this->display_handler->use_group_by());
+
+ // Allow style handler to affect the query:
+ $this->style_plugin->query($this->display_handler->use_group_by());
+
+ // Allow exposed form to affect the query:
+ if (isset($exposed_form)) {
+ $exposed_form->query();
+ }
+
+ if (variable_get('views_sql_signature', FALSE)) {
+ $this->query->add_signature($this);
+ }
+
+ // Let modules modify the query just prior to finalizing it.
+ $this->query->alter($this);
+
+ // Only build the query if we weren't interrupted.
+ if (empty($this->built)) {
+ // Build the necessary info to execute the query.
+ $this->query->build($this);
+ }
+
+ $this->built = TRUE;
+ $this->build_time = microtime(TRUE) - $start;
+
+ // Attach displays
+ $this->attach_displays();
+
+ // Let modules modify the view just after building it.
+ foreach (module_implements('views_post_build') as $module) {
+ $function = $module . '_views_post_build';
+ $function($this);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Internal method to build an individual set of handlers.
+ *
+ * @param string $key
+ * The type of handlers (filter etc.) which should be iterated over to
+ * build the relationship and query information.
+ */
+ function _build($key) {
+ $handlers = &$this->$key;
+ foreach ($handlers as $id => $data) {
+
+ if (!empty($handlers[$id]) && is_object($handlers[$id])) {
+ $multiple_exposed_input = array(0 => NULL);
+ if ($handlers[$id]->multiple_exposed_input()) {
+ $multiple_exposed_input = $handlers[$id]->group_multiple_exposed_input($this->exposed_data);
+ }
+ foreach ($multiple_exposed_input as $group_id) {
+ // Give this handler access to the exposed filter input.
+ if (!empty($this->exposed_data)) {
+ $converted = FALSE;
+ if ($handlers[$id]->is_a_group()) {
+ $converted = $handlers[$id]->convert_exposed_input($this->exposed_data, $group_id);
+ $handlers[$id]->store_group_input($this->exposed_data, $converted);
+ if (!$converted) {
+ continue;
+ }
+ }
+ $rc = $handlers[$id]->accept_exposed_input($this->exposed_data);
+ $handlers[$id]->store_exposed_input($this->exposed_data, $rc);
+ if (!$rc) {
+ continue;
+ }
+ }
+ $handlers[$id]->set_relationship();
+ $handlers[$id]->query($this->display_handler->use_group_by());
+ }
+ }
+ }
+ }
+
+ /**
+ * Execute the view's query.
+ *
+ * @param string $display_id
+ * The machine name of the display, which should be executed.
+ *
+ * @return bool
+ * Return whether the executing was successful, for example an argument
+ * could stop the process.
+ */
+ function execute($display_id = NULL) {
+ if (empty($this->built)) {
+ if (!$this->build($display_id)) {
+ return FALSE;
+ }
+ }
+
+ if (!empty($this->executed)) {
+ return TRUE;
+ }
+
+ // Don't allow to use deactivated displays, but display them on the live preview.
+ if (!$this->display[$this->current_display]->handler->get_option('enabled') && empty($this->live_preview)) {
+ $this->build_info['fail'] = TRUE;
+ return FALSE;
+ }
+
+ // Let modules modify the view just prior to executing it.
+ foreach (module_implements('views_pre_execute') as $module) {
+ $function = $module . '_views_pre_execute';
+ $function($this);
+ }
+
+ // Check for already-cached results.
+ if (!empty($this->live_preview)) {
+ $cache = FALSE;
+ }
+ else {
+ $cache = $this->display_handler->get_plugin('cache');
+ }
+ if ($cache && $cache->cache_get('results')) {
+ if($this->query->pager->use_pager() || !empty($this->get_total_rows)) {
+ $this->query->pager->total_items = $this->total_rows;
+ $this->query->pager->update_page_info();
+ }
+ vpr('Used cached results');
+ }
+ else {
+ $this->query->execute($this);
+ // Enforce the array key rule as documented in
+ // views_plugin_query::execute().
+ $this->result = array_values($this->result);
+ $this->_post_execute();
+ if ($cache) {
+ $cache->cache_set('results');
+ }
+ }
+
+ // Let modules modify the view just after executing it.
+ foreach (module_implements('views_post_execute') as $module) {
+ $function = $module . '_views_post_execute';
+ $function($this);
+ }
+
+ $this->executed = TRUE;
+ }
+
+ /**
+ * Render this view for a certain display.
+ *
+ * Note: You should better use just the preview function if you want to
+ * render a view.
+ *
+ * @param string $display_id
+ * The machine name of the display, which should be rendered.
+ *
+ * @return (string|NULL)
+ * Return the output of the rendered view or NULL if something failed in the process.
+ */
+ function render($display_id = NULL) {
+ $this->execute($display_id);
+
+ // Check to see if the build failed.
+ if (!empty($this->build_info['fail'])) {
+ return;
+ }
+ if (!empty($this->view->build_info['denied'])) {
+ return;
+ }
+
+ drupal_theme_initialize();
+
+ $start = microtime(TRUE);
+ if (!empty($this->live_preview) && variable_get('views_show_additional_queries', FALSE)) {
+ $this->start_query_capture();
+ }
+
+ $exposed_form = $this->display_handler->get_plugin('exposed_form');
+ $exposed_form->pre_render($this->result);
+
+ // Check for already-cached output.
+ if (!empty($this->live_preview)) {
+ $cache = FALSE;
+ }
+ else {
+ $cache = $this->display_handler->get_plugin('cache');
+ }
+ if ($cache && $cache->cache_get('output')) {
+ }
+ else {
+ if ($cache) {
+ $cache->cache_start();
+ }
+
+ // Run pre_render for the pager as it might change the result.
+ if (!empty($this->query->pager)) {
+ $this->query->pager->pre_render($this->result);
+ }
+
+ // Initialize the style plugin.
+ $this->init_style();
+
+ // Give field handlers the opportunity to perform additional queries
+ // using the entire resultset prior to rendering.
+ if ($this->style_plugin->uses_fields()) {
+ foreach ($this->field as $id => $handler) {
+ if (!empty($this->field[$id])) {
+ $this->field[$id]->pre_render($this->result);
+ }
+ }
+ }
+
+ $this->style_plugin->pre_render($this->result);
+
+ // Let modules modify the view just prior to rendering it.
+ foreach (module_implements('views_pre_render') as $module) {
+ $function = $module . '_views_pre_render';
+ $function($this);
+ }
+
+ // Let the themes play too, because pre render is a very themey thing.
+ foreach ($GLOBALS['base_theme_info'] as $base) {
+ $function = $base->name . '_views_pre_render';
+ if (function_exists($function)) {
+ $function($this);
+ }
+ }
+ $function = $GLOBALS['theme'] . '_views_pre_render';
+ if (function_exists($function)) {
+ $function($this);
+ }
+
+ $this->display_handler->output = $this->display_handler->render();
+ if ($cache) {
+ $cache->cache_set('output');
+ }
+ }
+ $this->render_time = microtime(TRUE) - $start;
+
+ $exposed_form->post_render($this->display_handler->output);
+
+ if ($cache) {
+ $cache->post_render($this->display_handler->output);
+ }
+
+ // Let modules modify the view output after it is rendered.
+ foreach (module_implements('views_post_render') as $module) {
+ $function = $module . '_views_post_render';
+ $function($this, $this->display_handler->output, $cache);
+ }
+
+ // Let the themes play too, because post render is a very themey thing.
+ foreach ($GLOBALS['base_theme_info'] as $base) {
+ $function = $base->name . '_views_post_render';
+ if (function_exists($function)) {
+ $function($this);
+ }
+ }
+ $function = $GLOBALS['theme'] . '_views_post_render';
+ if (function_exists($function)) {
+ $function($this, $this->display_handler->output, $cache);
+ }
+
+ if (!empty($this->live_preview) && variable_get('views_show_additional_queries', FALSE)) {
+ $this->end_query_capture();
+ }
+
+ return $this->display_handler->output;
+ }
+
+ /**
+ * Render a specific field via the field ID and the row #
+ *
+ * Note: You might want to use views_plugin_style::render_fields as it
+ * caches the output for you.
+ *
+ * @param string $field
+ * The id of the field to be rendered.
+ *
+ * @param int $row
+ * The row number in the $view->result which is used for the rendering.
+ *
+ * @return string
+ * The rendered output of the field.
+ */
+ function render_field($field, $row) {
+ if (isset($this->field[$field]) && isset($this->result[$row])) {
+ return $this->field[$field]->advanced_render($this->result[$row]);
+ }
+ }
+
+ /**
+ * Execute the given display, with the given arguments.
+ * To be called externally by whatever mechanism invokes the view,
+ * such as a page callback, hook_block, etc.
+ *
+ * This function should NOT be used by anything external as this
+ * returns data in the format specified by the display. It can also
+ * have other side effects that are only intended for the 'proper'
+ * use of the display, such as setting page titles and breadcrumbs.
+ *
+ * If you simply want to view the display, use view::preview() instead.
+ */
+ function execute_display($display_id = NULL, $args = array()) {
+ if (empty($this->current_display) || $this->current_display != $this->choose_display($display_id)) {
+ if (!$this->set_display($display_id)) {
+ return FALSE;
+ }
+ }
+
+ $this->pre_execute($args);
+
+ // Execute the view
+ $output = $this->display_handler->execute();
+
+ $this->post_execute();
+ return $output;
+ }
+
+ /**
+ * Preview the given display, with the given arguments.
+ *
+ * To be called externally, probably by an AJAX handler of some flavor.
+ * Can also be called when views are embedded, as this guarantees
+ * normalized output.
+ */
+ function preview($display_id = NULL, $args = array()) {
+ if (empty($this->current_display) || ((!empty($display_id)) && $this->current_display != $display_id)) {
+ if (!$this->set_display($display_id)) {
+ return FALSE;
+ }
+ }
+
+ $this->preview = TRUE;
+ $this->pre_execute($args);
+ // Preview the view.
+ $output = $this->display_handler->preview();
+
+ $this->post_execute();
+ return $output;
+ }
+
+ /**
+ * Run attachments and let the display do what it needs to do prior
+ * to running.
+ */
+ function pre_execute($args = array()) {
+ $this->old_view[] = views_get_current_view();
+ views_set_current_view($this);
+ $display_id = $this->current_display;
+
+ // Prepare the view with the information we have, but only if we were
+ // passed arguments, as they may have been set previously.
+ if ($args) {
+ $this->set_arguments($args);
+ }
+
+ // Let modules modify the view just prior to executing it.
+ foreach (module_implements('views_pre_view') as $module) {
+ $function = $module . '_views_pre_view';
+ $function($this, $display_id, $this->args);
+ }
+
+ // Allow hook_views_pre_view() to set the dom_id, then ensure it is set.
+ $this->dom_id = !empty($this->dom_id) ? $this->dom_id : md5($this->name . REQUEST_TIME . rand());
+
+ // Allow the display handler to set up for execution
+ $this->display_handler->pre_execute();
+ }
+
+ /**
+ * Unset the current view, mostly.
+ */
+ function post_execute() {
+ // unset current view so we can be properly destructed later on.
+ // Return the previous value in case we're an attachment.
+
+ if ($this->old_view) {
+ $old_view = array_pop($this->old_view);
+ }
+
+ views_set_current_view(isset($old_view) ? $old_view : FALSE);
+ }
+
+ /**
+ * Run attachment displays for the view.
+ */
+ function attach_displays() {
+ if (!empty($this->is_attachment)) {
+ return;
+ }
+
+ if (!$this->display_handler->accept_attachments()) {
+ return;
+ }
+
+ $this->is_attachment = TRUE;
+ // Give other displays an opportunity to attach to the view.
+ foreach ($this->display as $id => $display) {
+ if (!empty($this->display[$id]->handler)) {
+ $this->display[$id]->handler->attach_to($this->current_display);
+ }
+ }
+ $this->is_attachment = FALSE;
+ }
+
+ /**
+ * Called to get hook_menu() information from the view and the named display handler.
+ *
+ * @param $display_id
+ * A display id.
+ * @param $callbacks
+ * A menu callback array passed from views_menu_alter().
+ */
+ function execute_hook_menu($display_id = NULL, &$callbacks = array()) {
+ // Prepare the view with the information we have.
+
+ // This was probably already called, but it's good to be safe.
+ if (!$this->set_display($display_id)) {
+ return FALSE;
+ }
+
+ // Execute the view
+ if (isset($this->display_handler)) {
+ return $this->display_handler->execute_hook_menu($callbacks);
+ }
+ }
+
+ /**
+ * Called to get hook_block information from the view and the
+ * named display handler.
+ */
+ function execute_hook_block_list($display_id = NULL) {
+ // Prepare the view with the information we have.
+
+ // This was probably already called, but it's good to be safe.
+ if (!$this->set_display($display_id)) {
+ return FALSE;
+ }
+
+ // Execute the view
+ if (isset($this->display_handler)) {
+ return $this->display_handler->execute_hook_block_list();
+ }
+ }
+
+ /**
+ * Determine if the given user has access to the view. Note that
+ * this sets the display handler if it hasn't been.
+ */
+ function access($displays = NULL, $account = NULL) {
+ // Noone should have access to disabled views.
+ if (!empty($this->disabled)) {
+ return FALSE;
+ }
+
+ if (!isset($this->current_display)) {
+ $this->init_display();
+ }
+
+ if (!$account) {
+ $account = $GLOBALS['user'];
+ }
+
+ // We can't use choose_display() here because that function
+ // calls this one.
+ $displays = (array)$displays;
+ foreach ($displays as $display_id) {
+ if (!empty($this->display[$display_id]->handler)) {
+ if ($this->display[$display_id]->handler->access($account)) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Get the view's current title. This can change depending upon how it
+ * was built.
+ */
+ function get_title() {
+ if (empty($this->display_handler)) {
+ if (!$this->set_display('default')) {
+ return FALSE;
+ }
+ }
+
+ // During building, we might find a title override. If so, use it.
+ if (!empty($this->build_info['title'])) {
+ $title = $this->build_info['title'];
+ }
+ else {
+ $title = $this->display_handler->get_option('title');
+ }
+
+ // Allow substitutions from the first row.
+ if ($this->init_style()) {
+ $title = $this->style_plugin->tokenize_value($title, 0);
+ }
+ return $title;
+ }
+
+ /**
+ * Override the view's current title.
+ *
+ * The tokens in the title get's replaced before rendering.
+ */
+ function set_title($title) {
+ $this->build_info['title'] = $title;
+ return TRUE;
+ }
+
+ /**
+ * Return the human readable name for a view.
+ *
+ * When a certain view doesn't have a human readable name return the machine readable name.
+ */
+ function get_human_name() {
+ if (!empty($this->human_name)) {
+ $human_name = $this->human_name;
+ }
+ else {
+ $human_name = $this->name;
+ }
+ return $human_name;
+ }
+
+ /**
+ * Force the view to build a title.
+ */
+ function build_title() {
+ $this->init_display();
+
+ if (empty($this->built)) {
+ $this->init_query();
+ }
+
+ $this->init_handlers();
+
+ $this->_build_arguments();
+ }
+
+ /**
+ * Get the URL for the current view.
+ *
+ * This URL will be adjusted for arguments.
+ */
+ function get_url($args = NULL, $path = NULL) {
+ if (!empty($this->override_url)) {
+ return $this->override_url;
+ }
+
+ if (!isset($path)) {
+ $path = $this->get_path();
+ }
+ if (!isset($args)) {
+ $args = $this->args;
+
+ // Exclude arguments that were computed, not passed on the URL.
+ $position = 0;
+ if (!empty($this->argument)) {
+ foreach ($this->argument as $argument_id => $argument) {
+ if (!empty($argument->is_default) && !empty($argument->options['default_argument_skip_url'])) {
+ unset($args[$position]);
+ }
+ $position++;
+ }
+ }
+ }
+ // Don't bother working if there's nothing to do:
+ if (empty($path) || (empty($args) && strpos($path, '%') === FALSE)) {
+ return $path;
+ }
+
+ $pieces = array();
+ $argument_keys = isset($this->argument) ? array_keys($this->argument) : array();
+ $id = current($argument_keys);
+ foreach (explode('/', $path) as $piece) {
+ if ($piece != '%') {
+ $pieces[] = $piece;
+ }
+ else {
+ if (empty($args)) {
+ // Try to never put % in a url; use the wildcard instead.
+ if ($id && !empty($this->argument[$id]->options['exception']['value'])) {
+ $pieces[] = $this->argument[$id]->options['exception']['value'];
+ }
+ else {
+ $pieces[] = '*'; // gotta put something if there just isn't one.
+ }
+
+ }
+ else {
+ $pieces[] = array_shift($args);
+ }
+
+ if ($id) {
+ $id = next($argument_keys);
+ }
+ }
+ }
+
+ if (!empty($args)) {
+ $pieces = array_merge($pieces, $args);
+ }
+ return implode('/', $pieces);
+ }
+
+ /**
+ * Get the base path used for this view.
+ */
+ function get_path() {
+ if (!empty($this->override_path)) {
+ return $this->override_path;
+ }
+
+ if (empty($this->display_handler)) {
+ if (!$this->set_display('default')) {
+ return FALSE;
+ }
+ }
+ return $this->display_handler->get_path();
+ }
+
+ /**
+ * Get the breadcrumb used for this view.
+ *
+ * @param $set
+ * If true, use drupal_set_breadcrumb() to install the breadcrumb.
+ */
+ function get_breadcrumb($set = FALSE) {
+ // Now that we've built the view, extract the breadcrumb.
+ $base = TRUE;
+ $breadcrumb = array();
+
+ if (!empty($this->build_info['breadcrumb'])) {
+ foreach ($this->build_info['breadcrumb'] as $path => $title) {
+ // Check to see if the frontpage is in the breadcrumb trail; if it
+ // is, we'll remove that from the actual breadcrumb later.
+ if ($path == variable_get('site_frontpage', 'node')) {
+ $base = FALSE;
+ $title = t('Home');
+ }
+ if ($title) {
+ $breadcrumb[] = l($title, $path, array('html' => TRUE));
+ }
+ }
+
+ if ($set) {
+ if ($base) {
+ $breadcrumb = array_merge(drupal_get_breadcrumb(), $breadcrumb);
+ }
+ drupal_set_breadcrumb($breadcrumb);
+ }
+ }
+ return $breadcrumb;
+ }
+
+ /**
+ * Is this view cacheable?
+ */
+ function is_cacheable() {
+ return $this->is_cacheable;
+ }
+
+ /**
+ * Set up query capturing.
+ *
+ * db_query() stores the queries that it runs in global $queries,
+ * bit only if dev_query is set to true. In this case, we want
+ * to temporarily override that setting if it's not and we
+ * can do that without forcing a db rewrite by just manipulating
+ * $conf. This is kind of evil but it works.
+ */
+ function start_query_capture() {
+ global $conf, $queries;
+ if (empty($conf['dev_query'])) {
+ $this->fix_dev_query = TRUE;
+ $conf['dev_query'] = TRUE;
+ }
+
+ // Record the last query key used; anything already run isn't
+ // a query that we are interested in.
+ $this->last_query_key = NULL;
+
+ if (!empty($queries)) {
+ $keys = array_keys($queries);
+ $this->last_query_key = array_pop($keys);
+ }
+ }
+
+ /**
+ * Add the list of queries run during render to buildinfo.
+ *
+ * @see view::start_query_capture()
+ */
+ function end_query_capture() {
+ global $conf, $queries;
+ if (!empty($this->fix_dev_query)) {
+ $conf['dev_query'] = FALSE;
+ }
+
+ // make a copy of the array so we can manipulate it with array_splice.
+ $temp = $queries;
+
+ // Scroll through the queries until we get to our last query key.
+ // Unset anything in our temp array.
+ if (isset($this->last_query_key)) {
+ while (list($id, $query) = each($queries)) {
+ if ($id == $this->last_query_key) {
+ break;
+ }
+
+ unset($temp[$id]);
+ }
+ }
+
+ $this->additional_queries = $temp;
+ }
+
+ /**
+ * Static factory method to load a list of views based upon a $where clause.
+ *
+ * Although this method could be implemented to simply iterate over views::load(),
+ * that would be very slow. Buiding the views externally from unified queries is
+ * much faster.
+ */
+ static function load_views() {
+ $result = db_query("SELECT DISTINCT v.* FROM {views_view} v");
+ $views = array();
+
+ // Load all the views.
+ foreach ($result as $data) {
+ $view = new view;
+ $view->load_row($data);
+ $view->loaded = TRUE;
+ $view->type = t('Normal');
+ $views[$view->name] = $view;
+ $names[$view->vid] = $view->name;
+ }
+
+ // Stop if we didn't get any views.
+ if (!$views) {
+ return array();
+ }
+
+ // Now load all the subtables:
+ foreach (view::db_objects() as $key) {
+ $object_name = "views_$key";
+ $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
+ array(':vids' => array_keys($names)));
+
+ foreach ($result as $data) {
+ $object = new $object_name(FALSE);
+ $object->load_row($data);
+
+ // Because it can get complicated with this much indirection,
+ // make a shortcut reference.
+ $location = &$views[$names[$object->vid]]->$key;
+
+ // If we have a basic id field, load the item onto the view based on
+ // this ID, otherwise push it on.
+ if (!empty($object->id)) {
+ $location[$object->id] = $object;
+ }
+ else {
+ $location[] = $object;
+ }
+ }
+ }
+ return $views;
+ }
+
+ /**
+ * Save the view to the database. If the view does not already exist,
+ * A vid will be assigned to the view and also returned from this function.
+ */
+ function save() {
+ if ($this->vid == 'new') {
+ $this->vid = NULL;
+ }
+ // If there is no vid, check if a view with this machine name already exists.
+ elseif (empty($this->vid)) {
+ $vid = db_query("SELECT vid from {views_view} WHERE name = :name", array(':name' => $this->name))->fetchField();
+ $this->vid = $vid ? $vid : NULL;
+ }
+
+ $transaction = db_transaction();
+
+ try {
+ // If we have no vid or our vid is a string, this is a new view.
+ if (!empty($this->vid)) {
+ // remove existing table entries
+ foreach ($this->db_objects() as $key) {
+ db_delete('views_' . $key)
+ ->condition('vid', $this->vid)
+ ->execute();
+ }
+ }
+
+ $this->save_row(!empty($this->vid) ? 'vid' : FALSE);
+
+ // Save all of our subtables.
+ foreach ($this->db_objects() as $key) {
+ $this->_save_rows($key);
+ }
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception('views', $e);
+ throw $e;
+ }
+
+ $this->save_locale_strings();
+
+ // Clear caches.
+ views_invalidate_cache();
+ }
+
+ /**
+ * Save a row to the database for the given key, which is one of the
+ * keys from view::db_objects()
+ */
+ function _save_rows($key) {
+ $count = 0;
+ foreach ($this->$key as $position => $object) {
+ $object->position = ++$count;
+ $object->vid = $this->vid;
+ $object->save_row();
+ }
+ }
+
+ /**
+ * Delete the view from the database.
+ */
+ function delete($clear = TRUE) {
+ if (empty($this->vid)) {
+ return;
+ }
+
+ db_delete('views_view')
+ ->condition('vid', $this->vid)
+ ->execute();
+ // Delete from all of our subtables as well.
+ foreach ($this->db_objects() as $key) {
+ db_delete('views_'. $key)
+ ->condition('vid', $this->vid)
+ ->execute();
+ }
+
+ cache_clear_all('views_query:' . $this->name, 'cache_views');
+
+ if ($clear) {
+ // Clear caches.
+ views_invalidate_cache();
+ }
+ }
+
+ /**
+ * Export a view as PHP code.
+ */
+ function export($indent = '') {
+ $this->init_display();
+ $this->init_query();
+ $output = '';
+ $output .= $this->export_row('view', $indent);
+ // Set the API version
+ $output .= $indent . '$view->api_version = \'' . views_api_version() . "';\n";
+ $output .= $indent . '$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */' . "\n";
+
+ foreach ($this->display as $id => $display) {
+ $output .= "\n" . $indent . "/* Display: $display->display_title */\n";
+ $output .= $indent . '$handler = $view->new_display(' . ctools_var_export($display->display_plugin, $indent) . ', ' . ctools_var_export($display->display_title, $indent) . ', \'' . $id . "');\n";
+ if (empty($display->handler)) {
+ // @todo -- probably need a method of exporting broken displays as
+ // they may simply be broken because a module is not installed. That
+ // does not invalidate the display.
+ continue;
+ }
+
+ $output .= $display->handler->export_options($indent, '$handler->options');
+ }
+
+ // Give the localization system a chance to export translatables to code.
+ if ($this->init_localization()) {
+ $this->export_locale_strings('export');
+ $translatables = $this->localization_plugin->export_render($indent);
+ if (!empty($translatables)) {
+ $output .= $translatables;
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Make a copy of this view that has been sanitized of all database IDs
+ * and handlers and other stuff.
+ *
+ * I'd call this clone() but it's reserved.
+ */
+ function copy() {
+ $code = $this->export();
+ eval($code);
+ return $view;
+ }
+
+ /**
+ * Safely clone a view.
+ *
+ * Because views are complicated objects within objects, and PHP loves to
+ * do references to everything, if a View is not properly and safely
+ * cloned it will still have references to the original view, and can
+ * actually cause the original view to point to objects in the cloned
+ * view. This gets ugly fast.
+ *
+ * This will completely wipe a view clean so it can be considered fresh.
+ *
+ * @return view
+ * The cloned view.
+ */
+ function clone_view() {
+ $clone = version_compare(phpversion(), '5.0') < 0 ? $this : clone($this);
+
+ $keys = array('current_display', 'display_handler', 'build_info', 'built', 'executed', 'attachment_before', 'attachment_after', 'field', 'argument', 'filter', 'sort', 'relationship', 'header', 'footer', 'empty', 'query', 'inited', 'style_plugin', 'plugin_name', 'exposed_data', 'exposed_input', 'exposed_widgets', 'many_to_one_tables', 'feed_icon');
+ foreach ($keys as $key) {
+ if (isset($clone->$key)) {
+ unset($clone->$key);
+ }
+ }
+ $clone->built = $clone->executed = FALSE;
+ $clone->build_info = array();
+ $clone->attachment_before = '';
+ $clone->attachment_after = '';
+ $clone->result = array();
+
+ // shallow cloning means that all the display objects
+ // *were not cloned*. We must clone them ourselves.
+ $displays = array();
+ foreach ($clone->display as $id => $display) {
+ $displays[$id] = clone $display;
+ if (isset($displays[$id]->handler)) {
+ unset($displays[$id]->handler);
+ }
+ }
+ $clone->display = $displays;
+
+ return $clone;
+ }
+
+ /**
+ * Unset references so that a $view object may be properly garbage
+ * collected.
+ */
+ function destroy() {
+ foreach (array_keys($this->display) as $display_id) {
+ if (isset($this->display[$display_id]->handler)) {
+ $this->display[$display_id]->handler->destroy();
+ unset($this->display[$display_id]->handler);
+ }
+ }
+
+ foreach (views_object_types() as $type => $info) {
+ if (isset($this->$type)) {
+ $handlers = &$this->$type;
+ foreach ($handlers as $id => $item) {
+ $handlers[$id]->destroy();
+ }
+ unset($handlers);
+ }
+ }
+
+ if (isset($this->style_plugin)) {
+ $this->style_plugin->destroy();
+ unset($this->style_plugin);
+ }
+
+ // Clear these to make sure the view can be processed/used again.
+ if (isset($this->display_handler)) {
+ unset($this->display_handler);
+ }
+
+ if (isset($this->current_display)) {
+ unset($this->current_display);
+ }
+
+ if (isset($this->query)) {
+ unset($this->query);
+ }
+
+ $keys = array('current_display', 'display_handler', 'build_info', 'built', 'executed', 'attachment_before', 'attachment_after', 'field', 'argument', 'filter', 'sort', 'relationship', 'header', 'footer', 'empty', 'query', 'result', 'inited', 'style_plugin', 'plugin_name', 'exposed_data', 'exposed_input', 'many_to_one_tables');
+ foreach ($keys as $key) {
+ if (isset($this->$key)) {
+ unset($this->$key);
+ }
+ }
+
+ // These keys are checked by the next init, so instead of unsetting them,
+ // just set the default values.
+ $keys = array('items_per_page', 'offset', 'current_page');
+ foreach ($keys as $key) {
+ if (isset($this->$key)) {
+ $this->$key = NULL;
+ }
+ }
+
+ $this->built = $this->executed = FALSE;
+ $this->build_info = array();
+ $this->attachment_before = '';
+ $this->attachment_after = '';
+ }
+
+ /**
+ * Make sure the view is completely valid.
+ *
+ * @return
+ * TRUE if the view is valid; an array of error strings if it is not.
+ */
+ function validate() {
+ $this->init_display();
+
+ $errors = array();
+ $this->display_errors = NULL;
+
+ $current_display = $this->current_display;
+ foreach ($this->display as $id => $display) {
+ if ($display->handler) {
+ if (!empty($display->deleted)) {
+ continue;
+ }
+
+ $result = $this->display[$id]->handler->validate();
+ if (!empty($result) && is_array($result)) {
+ $errors = array_merge($errors, $result);
+ // Mark this display as having validation errors.
+ $this->display_errors[$id] = TRUE;
+ }
+ }
+ }
+
+ $this->set_display($current_display);
+ return $errors ? $errors : TRUE;
+ }
+
+ /**
+ * Find and initialize the localizer plugin.
+ */
+ function init_localization() {
+ if (isset($this->localization_plugin) && is_object($this->localization_plugin)) {
+ return TRUE;
+ }
+
+ $this->localization_plugin = views_get_plugin('localization', views_get_localization_plugin());
+
+ if (empty($this->localization_plugin)) {
+ $this->localization_plugin = views_get_plugin('localization', 'none');
+ return FALSE;
+ }
+
+ /**
+ * Figure out whether there should be options.
+ */
+ $this->localization_plugin->init($this);
+
+ return $this->localization_plugin->translate;
+ }
+
+ /**
+ * Determine whether a view supports admin string translation.
+ */
+ function is_translatable() {
+ // Use translation no matter what type of view.
+ if (variable_get('views_localize_all', FALSE)) {
+ return TRUE;
+ }
+ // If the view is normal or overridden, use admin string translation.
+ // A newly created view won't have a type. Accept this.
+ return (!isset($this->type) || in_array($this->type, array(t('Normal'), t('Overridden')))) ? TRUE : FALSE;
+ }
+
+ /**
+ * Send strings for localization.
+ */
+ function save_locale_strings() {
+ $this->process_locale_strings('save');
+ }
+
+ /**
+ * Delete localized strings.
+ */
+ function delete_locale_strings() {
+ $this->process_locale_strings('delete');
+ }
+
+ /**
+ * Export localized strings.
+ */
+ function export_locale_strings() {
+ $this->process_locale_strings('export');
+ }
+
+ /**
+ * Process strings for localization, deletion or export to code.
+ */
+ function process_locale_strings($op) {
+ // Ensure this view supports translation, we have a display, and we
+ // have a localization plugin.
+ // @fixme Export does not init every handler.
+ if (($this->is_translatable() || $op == 'export') && $this->init_display() && $this->init_localization()) {
+ $this->localization_plugin->process_locale_strings($op);
+ }
+ }
+
+}
+
+/**
+ * Base class for views' database objects.
+ */
+class views_db_object {
+ public $db_table;
+
+ /**
+ * Initialize this object, setting values from schema defaults.
+ *
+ * @param $init
+ * If an array, this is a set of values from db_fetch_object to
+ * load. Otherwse, if TRUE values will be filled in from schema
+ * defaults.
+ */
+ function init($init = TRUE) {
+ if (is_array($init)) {
+ return $this->load_row($init);
+ }
+
+ if (!$init) {
+ return;
+ }
+
+ $schema = drupal_get_schema($this->db_table);
+
+ if (!$schema) {
+ return;
+ }
+
+ // Go through our schema and build correlations.
+ foreach ($schema['fields'] as $field => $info) {
+ if ($info['type'] == 'serial') {
+ $this->$field = NULL;
+ }
+ if (!isset($this->$field)) {
+ if (!empty($info['serialize']) && isset($info['serialized default'])) {
+ $this->$field = unserialize($info['serialized default']);
+ }
+ elseif (isset($info['default'])) {
+ $this->$field = $info['default'];
+ }
+ else {
+ $this->$field = '';
+ }
+ }
+ }
+ }
+
+ /**
+ * Write the row to the database.
+ *
+ * @param $update
+ * If true this will be an UPDATE query. Otherwise it will be an INSERT.
+ */
+ function save_row($update = NULL) {
+ $fields = $defs = $values = $serials = array();
+ $schema = drupal_get_schema($this->db_table);
+
+ // Go through our schema and build correlations.
+ foreach ($schema['fields'] as $field => $info) {
+ // special case -- skip serial types if we are updating.
+ if ($info['type'] == 'serial') {
+ $serials[] = $field;
+ continue;
+ }
+ elseif ($info['type'] == 'int') {
+ $this->$field = (int) $this->$field;
+ }
+ $fields[$field] = empty($info['serialize']) ? $this->$field : serialize($this->$field);
+ }
+ if (!$update) {
+ $query = db_insert($this->db_table);
+ }
+ else {
+ $query = db_update($this->db_table)
+ ->condition($update, $this->$update);
+ }
+ $return = $query
+ ->fields($fields)
+ ->execute();
+
+ if ($serials && !$update) {
+ // get last insert ids and fill them in.
+ // Well, one ID.
+ foreach ($serials as $field) {
+ $this->$field = $return;
+ }
+ }
+ }
+
+ /**
+ * Load the object with a row from the database.
+ *
+ * This method is separate from the constructor in order to give us
+ * more flexibility in terms of how the view object is built in different
+ * contexts.
+ *
+ * @param $data
+ * An object from db_fetch_object. It should contain all of the fields
+ * that are in the schema.
+ */
+ function load_row($data) {
+ $schema = drupal_get_schema($this->db_table);
+
+ // Go through our schema and build correlations.
+ foreach ($schema['fields'] as $field => $info) {
+ $this->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
+ }
+ }
+
+ /**
+ * Export a loaded row, such as an argument, field or the view itself to PHP code.
+ *
+ * @param $identifier
+ * The variable to assign the PHP code for this object to.
+ * @param $indent
+ * An optional indentation for prettifying nested code.
+ */
+ function export_row($identifier = NULL, $indent = '') {
+ ctools_include('export');
+
+ if (!$identifier) {
+ $identifier = $this->db_table;
+ }
+ $schema = drupal_get_schema($this->db_table);
+
+ $output = $indent . '$' . $identifier . ' = new ' . get_class($this) . "();\n";
+ // Go through our schema and build correlations.
+ foreach ($schema['fields'] as $field => $info) {
+ if (!empty($info['no export'])) {
+ continue;
+ }
+ if (!isset($this->$field)) {
+ if (isset($info['default'])) {
+ $this->$field = $info['default'];
+ }
+ else {
+ $this->$field = '';
+ }
+
+ // serialized defaults must be set as serialized.
+ if (isset($info['serialize'])) {
+ $this->$field = unserialize($this->$field);
+ }
+ }
+ $value = $this->$field;
+ if ($info['type'] == 'int') {
+ if (isset($info['size']) && $info['size'] == 'tiny') {
+ $value = (bool) $value;
+ }
+ else {
+ $value = (int) $value;
+ }
+ }
+
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
+ }
+ return $output;
+ }
+
+ /**
+ * Add a new display handler to the view, automatically creating an id.
+ *
+ * @param $type
+ * The plugin type from the views plugin data. Defaults to 'page'.
+ * @param $title
+ * The title of the display; optional, may be filled in from default.
+ * @param $id
+ * The id to use.
+ * @return
+ * The key to the display in $view->display, so that the new display
+ * can be easily located.
+ */
+ function add_display($type = 'page', $title = NULL, $id = NULL) {
+ if (empty($type)) {
+ return FALSE;
+ }
+
+ $plugin = views_fetch_plugin_data('display', $type);
+ if (empty($plugin)) {
+ $plugin['title'] = t('Broken');
+ }
+
+
+ if (empty($id)) {
+ $id = $this->generate_display_id($type);
+ if ($id !== 'default') {
+ preg_match("/[0-9]+/", $id, $count);
+ $count = $count[0];
+ }
+ else {
+ $count = '';
+ }
+
+ if (empty($title)) {
+ if ($count > 1) {
+ $title = $plugin['title'] . ' ' . $count;
+ }
+ else {
+ $title = $plugin['title'];
+ }
+ }
+ }
+
+ // Create the new display object
+ $display = new views_display;
+ $display->options($type, $id, $title);
+
+ // Add the new display object to the view.
+ $this->display[$id] = $display;
+ return $id;
+ }
+
+ /**
+ * Generate a display id of a certain plugin type.
+ *
+ * @param $type
+ * Which plugin should be used for the new display id.
+ */
+ function generate_display_id($type) {
+ // 'default' is singular and is unique, so just go with 'default'
+ // for it. For all others, start counting.
+ if ($type == 'default') {
+ return 'default';
+ }
+ // Initial id.
+ $id = $type . '_1';
+ $count = 1;
+
+ // Loop through IDs based upon our style plugin name until
+ // we find one that is unused.
+ while (!empty($this->display[$id])) {
+ $id = $type . '_' . ++$count;
+ }
+
+ return $id;
+ }
+
+ /**
+ * Generates a unique ID for an item.
+ *
+ * These items are typically fields, filters, sort criteria, or arguments.
+ *
+ * @param $requested_id
+ * The requested ID for the item.
+ * @param $existing_items
+ * An array of existing items, keyed by their IDs.
+ *
+ * @return
+ * A unique ID. This will be equal to $requested_id if no item with that ID
+ * already exists. Otherwise, it will be appended with an integer to make
+ * it unique, e.g. "{$requested_id}_1", "{$requested_id}_2", etc.
+ */
+ public static function generate_item_id($requested_id, $existing_items) {
+ $count = 0;
+ $id = $requested_id;
+ while (!empty($existing_items[$id])) {
+ $id = $requested_id . '_' . ++$count;
+ }
+ return $id;
+ }
+
+ /**
+ * Create a new display and a display handler for it.
+ * @param $type
+ * The plugin type from the views plugin data. Defaults to 'page'.
+ * @param $title
+ * The title of the display; optional, may be filled in from default.
+ * @param $id
+ * The id to use.
+ * @return views_plugin_display
+ * A reference to the new handler object.
+ */
+ function &new_display($type = 'page', $title = NULL, $id = NULL) {
+ $id = $this->add_display($type, $title, $id);
+
+ // Create a handler
+ $this->display[$id]->handler = views_get_plugin('display', $this->display[$id]->display_plugin);
+ if (empty($this->display[$id]->handler)) {
+ // provide a 'default' handler as an emergency. This won't work well but
+ // it will keep things from crashing.
+ $this->display[$id]->handler = views_get_plugin('display', 'default');
+ }
+
+ if (!empty($this->display[$id]->handler)) {
+ // Initialize the new display handler with data.
+ $this->display[$id]->handler->init($this, $this->display[$id]);
+ // If this is NOT the default display handler, let it know which is
+ if ($id != 'default') {
+ $this->display[$id]->handler->default_display = &$this->display['default']->handler;
+ }
+ }
+
+ return $this->display[$id]->handler;
+ }
+
+ /**
+ * Add an item with a handler to the view.
+ *
+ * These items may be fields, filters, sort criteria, or arguments.
+ */
+ function add_item($display_id, $type, $table, $field, $options = array(), $id = NULL) {
+ $types = views_object_types();
+ $this->set_display($display_id);
+
+ $fields = $this->display[$display_id]->handler->get_option($types[$type]['plural']);
+
+ if (empty($id)) {
+ $id = $this->generate_item_id($field, $fields);
+ }
+
+ $new_item = array(
+ 'id' => $id,
+ 'table' => $table,
+ 'field' => $field,
+ ) + $options;
+
+ if (!empty($types[$type]['type'])) {
+ $handler_type = $types[$type]['type'];
+ }
+ else {
+ $handler_type = $type;
+ }
+
+ $handler = views_get_handler($table, $field, $handler_type);
+
+ $fields[$id] = $new_item;
+ $this->display[$display_id]->handler->set_option($types[$type]['plural'], $fields);
+
+ return $id;
+ }
+
+ /**
+ * Get an array of items for the current display.
+ */
+ function get_items($type, $display_id = NULL) {
+ $this->set_display($display_id);
+
+ if (!isset($display_id)) {
+ $display_id = $this->current_display;
+ }
+
+ // Get info about the types so we can get the right data.
+ $types = views_object_types();
+ return $this->display[$display_id]->handler->get_option($types[$type]['plural']);
+ }
+
+ /**
+ * Get the configuration of an item (field/sort/filter/etc) on a given
+ * display.
+ */
+ function get_item($display_id, $type, $id) {
+ // Get info about the types so we can get the right data.
+ $types = views_object_types();
+ // Initialize the display
+ $this->set_display($display_id);
+
+ // Get the existing configuration
+ $fields = $this->display[$display_id]->handler->get_option($types[$type]['plural']);
+
+ return isset($fields[$id]) ? $fields[$id] : NULL;
+ }
+
+ /**
+ * Set the configuration of an item (field/sort/filter/etc) on a given
+ * display.
+ *
+ * Pass in NULL for the $item to remove an item.
+ */
+ function set_item($display_id, $type, $id, $item) {
+ // Get info about the types so we can get the right data.
+ $types = views_object_types();
+ // Initialize the display
+ $this->set_display($display_id);
+
+ // Get the existing configuration
+ $fields = $this->display[$display_id]->handler->get_option($types[$type]['plural']);
+ if (isset($item)) {
+ $fields[$id] = $item;
+ }
+ else {
+ unset($fields[$id]);
+ }
+
+ // Store.
+ $this->display[$display_id]->handler->set_option($types[$type]['plural'], $fields);
+ }
+
+ /**
+ * Set an option on an item.
+ *
+ * Use this only if you have just 1 or 2 options to set; if you have
+ * many, consider getting the item, adding the options and doing
+ * set_item yourself.
+ */
+ function set_item_option($display_id, $type, $id, $option, $value) {
+ $item = $this->get_item($display_id, $type, $id);
+ $item[$option] = $value;
+ $this->set_item($display_id, $type, $id, $item);
+ }
+}
+
+/**
+ * A display type in a view.
+ *
+ * This is just the database storage mechanism, and isn't terribly important
+ * to the behavior of the display at all.
+ */
+class views_display extends views_db_object {
+ /**
+ * The display handler itself, which has all the methods.
+ *
+ * @var views_plugin_display
+ */
+ var $handler;
+
+ /**
+ * Stores all options of the display, like fields, filters etc.
+ *
+ * @var array
+ */
+ var $display_options;
+
+ var $db_table = 'views_display';
+ function __construct($init = TRUE) {
+ parent::init($init);
+ }
+
+ function options($type, $id, $title) {
+ $this->display_plugin = $type;
+ $this->id = $id;
+ $this->display_title = $title;
+ }
+}
+
+/**
+ * Provide a list of views object types used in a view, with some information
+ * about them.
+ */
+function views_object_types() {
+ static $retval = NULL;
+
+ // statically cache this so t() doesn't run a bajillion times.
+ if (!isset($retval)) {
+ $retval = array(
+ 'field' => array(
+ 'title' => t('Fields'), // title
+ 'ltitle' => t('fields'), // lowercase title for mid-sentence
+ 'stitle' => t('Field'), // singular title
+ 'lstitle' => t('field'), // singular lowercase title for mid sentence
+ 'plural' => 'fields',
+ ),
+ 'argument' => array(
+ 'title' => t('Contextual filters'),
+ 'ltitle' => t('contextual filters'),
+ 'stitle' => t('Contextual filter'),
+ 'lstitle' => t('contextual filter'),
+ 'plural' => 'arguments',
+ ),
+ 'sort' => array(
+ 'title' => t('Sort criteria'),
+ 'ltitle' => t('sort criteria'),
+ 'stitle' => t('Sort criterion'),
+ 'lstitle' => t('sort criterion'),
+ 'plural' => 'sorts',
+ ),
+ 'filter' => array(
+ 'title' => t('Filter criteria'),
+ 'ltitle' => t('filter criteria'),
+ 'stitle' => t('Filter criterion'),
+ 'lstitle' => t('filter criterion'),
+ 'plural' => 'filters',
+ ),
+ 'relationship' => array(
+ 'title' => t('Relationships'),
+ 'ltitle' => t('relationships'),
+ 'stitle' => t('Relationship'),
+ 'lstitle' => t('Relationship'),
+ 'plural' => 'relationships',
+ ),
+ 'header' => array(
+ 'title' => t('Header'),
+ 'ltitle' => t('header'),
+ 'stitle' => t('Header'),
+ 'lstitle' => t('Header'),
+ 'plural' => 'header',
+ 'type' => 'area',
+ ),
+ 'footer' => array(
+ 'title' => t('Footer'),
+ 'ltitle' => t('footer'),
+ 'stitle' => t('Footer'),
+ 'lstitle' => t('Footer'),
+ 'plural' => 'footer',
+ 'type' => 'area',
+ ),
+ 'empty' => array(
+ 'title' => t('No results behavior'),
+ 'ltitle' => t('no results behavior'),
+ 'stitle' => t('No results behavior'),
+ 'lstitle' => t('No results behavior'),
+ 'plural' => 'empty',
+ 'type' => 'area',
+ ),
+ );
+ }
+
+ return $retval;
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/js/ajax.js b/sites/all/modules/views/js/ajax.js
new file mode 100644
index 000000000..5c6885496
--- /dev/null
+++ b/sites/all/modules/views/js/ajax.js
@@ -0,0 +1,237 @@
+/**
+ * @file
+ * Handles AJAX submission and response in Views UI.
+ */
+(function ($) {
+
+ Drupal.ajax.prototype.commands.viewsSetForm = function (ajax, response, status) {
+ var ajax_title = Drupal.settings.views.ajax.title;
+ var ajax_body = Drupal.settings.views.ajax.id;
+ var ajax_popup = Drupal.settings.views.ajax.popup;
+ $(ajax_title).html(response.title);
+ $(ajax_body).html(response.output);
+ $(ajax_popup).dialog('open');
+ Drupal.attachBehaviors($(ajax_popup), ajax.settings);
+ if (response.url) {
+ // Identify the button that was clicked so that .ajaxSubmit() can use it.
+ // We need to do this for both .click() and .mousedown() since JavaScript
+ // code might trigger either behavior.
+ var $submit_buttons = $('input[type=submit], button', ajax_body);
+ $submit_buttons.click(function(event) {
+ this.form.clk = this;
+ });
+ $submit_buttons.mousedown(function(event) {
+ this.form.clk = this;
+ });
+
+ $('form', ajax_body).once('views-ajax-submit-processed').each(function() {
+ var element_settings = { 'url': response.url, 'event': 'submit', 'progress': { 'type': 'throbber' } };
+ var $form = $(this);
+ var id = $form.attr('id');
+ Drupal.ajax[id] = new Drupal.ajax(id, this, element_settings);
+ Drupal.ajax[id].form = $form;
+ });
+ }
+ Drupal.viewsUi.resizeModal();
+ };
+
+ Drupal.ajax.prototype.commands.viewsDismissForm = function (ajax, response, status) {
+ Drupal.ajax.prototype.commands.viewsSetForm({}, {'title': '', 'output': Drupal.settings.views.ajax.defaultForm});
+ $(Drupal.settings.views.ajax.popup).dialog('close');
+ }
+
+ Drupal.ajax.prototype.commands.viewsHilite = function (ajax, response, status) {
+ $('.hilited').removeClass('hilited');
+ $(response.selector).addClass('hilited');
+ };
+
+ Drupal.ajax.prototype.commands.viewsAddTab = function (ajax, response, status) {
+ var id = '#views-tab-' + response.id;
+ $('#views-tabset').viewsAddTab(id, response.title, 0);
+ $(id).html(response.body).addClass('views-tab');
+
+ // Update the preview widget to preview the new tab.
+ var display_id = id.replace('#views-tab-', '');
+ $("#preview-display-id").append('<option selected="selected" value="' + display_id + '">' + response.title + '</option>');
+
+ Drupal.attachBehaviors(id);
+ var instance = $.viewsUi.tabs.instances[$('#views-tabset').get(0).UI_TABS_UUID];
+ $('#views-tabset').viewsClickTab(instance.$tabs.length);
+ };
+
+ Drupal.ajax.prototype.commands.viewsShowButtons = function (ajax, response, status) {
+ $('div.views-edit-view div.form-actions').removeClass('js-hide');
+ if (response.changed) {
+ $('div.views-edit-view div.view-changed.messages').removeClass('js-hide');
+ }
+ };
+
+ Drupal.ajax.prototype.commands.viewsTriggerPreview = function (ajax, response, status) {
+ if ($('input#edit-displays-live-preview').is(':checked')) {
+ $('#preview-submit').trigger('click');
+ }
+ };
+
+ Drupal.ajax.prototype.commands.viewsReplaceTitle = function (ajax, response, status) {
+ // In case we're in the overlay, get a reference to the underlying window.
+ var doc = parent.document;
+ // For the <title> element, make a best-effort attempt to replace the page
+ // title and leave the site name alone. If the theme doesn't use the site
+ // name in the <title> element, this will fail.
+ var oldTitle = doc.title;
+ // Escape the site name, in case it has special characters in it, so we can
+ // use it in our regex.
+ var escapedSiteName = response.siteName.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ var re = new RegExp('.+ (.) ' + escapedSiteName);
+ doc.title = oldTitle.replace(re, response.title + ' $1 ' + response.siteName);
+
+ $('h1.page-title').text(response.title);
+ $('h1#overlay-title').text(response.title);
+ };
+
+ /**
+ * Get rid of irritating tabledrag messages
+ */
+ Drupal.theme.tableDragChangedWarning = function () {
+ return [];
+ }
+
+ /**
+ * Trigger preview when the "live preview" checkbox is checked.
+ */
+ Drupal.behaviors.livePreview = {
+ attach: function (context) {
+ $('input#edit-displays-live-preview', context).once('views-ajax-processed').click(function() {
+ if ($(this).is(':checked')) {
+ $('#preview-submit').click();
+ }
+ });
+ }
+ }
+
+ /**
+ * Sync preview display.
+ */
+ Drupal.behaviors.syncPreviewDisplay = {
+ attach: function (context) {
+ $("#views-tabset a").once('views-ajax-processed').click(function() {
+ var href = $(this).attr('href');
+ // Cut of #views-tabset.
+ var display_id = href.substr(11);
+ // Set the form element.
+ $("#views-live-preview #preview-display-id").val(display_id);
+ }).addClass('views-ajax-processed');
+ }
+ }
+
+ Drupal.behaviors.viewsAjax = {
+ collapseReplaced: false,
+ attach: function (context, settings) {
+ if (!settings.views) {
+ return;
+ }
+ // Create a jQuery UI dialog, but leave it closed.
+ var dialog_area = $(settings.views.ajax.popup, context);
+ dialog_area.dialog({
+ 'autoOpen': false,
+ 'dialogClass': 'views-ui-dialog',
+ 'modal': true,
+ 'position': 'center',
+ 'resizable': false,
+ 'width': 750
+ });
+
+ var base_element_settings = {
+ 'event': 'click',
+ 'progress': { 'type': 'throbber' }
+ };
+ // Bind AJAX behaviors to all items showing the class.
+ $('a.views-ajax-link', context).once('views-ajax-processed').each(function () {
+ var element_settings = base_element_settings;
+ // Set the URL to go to the anchor.
+ if ($(this).attr('href')) {
+ element_settings.url = $(this).attr('href');
+ }
+ var base = $(this).attr('id');
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ });
+
+ $('div#views-live-preview a')
+ .once('views-ajax-processed').each(function () {
+ // We don't bind to links without a URL.
+ if (!$(this).attr('href')) {
+ return true;
+ }
+
+ var element_settings = base_element_settings;
+ // Set the URL to go to the anchor.
+ element_settings.url = $(this).attr('href');
+ if (Drupal.Views.getPath(element_settings.url).substring(0, 21) != 'admin/structure/views') {
+ return true;
+ }
+
+ element_settings.wrapper = 'views-live-preview';
+ element_settings.method = 'html';
+ var base = $(this).attr('id');
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ });
+
+ // Within a live preview, make exposed widget form buttons re-trigger the
+ // Preview button.
+ // @todo Revisit this after fixing Views UI to display a Preview outside
+ // of the main Edit form.
+ $('div#views-live-preview input[type=submit]')
+ .once('views-ajax-processed').each(function(event) {
+ $(this).click(function () {
+ this.form.clk = this;
+ return true;
+ });
+ var element_settings = base_element_settings;
+ // Set the URL to go to the anchor.
+ element_settings.url = $(this.form).attr('action');
+ if (Drupal.Views.getPath(element_settings.url).substring(0, 21) != 'admin/structure/views') {
+ return true;
+ }
+
+ element_settings.wrapper = 'views-live-preview';
+ element_settings.method = 'html';
+ element_settings.event = 'click';
+
+ var base = $(this).attr('id');
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ });
+
+ if (!this.collapseReplaced && Drupal.collapseScrollIntoView) {
+ this.collapseReplaced = true;
+ Drupal.collapseScrollIntoView = function (node) {
+ for (var $parent = $(node); $parent.get(0) != document && $parent.size() != 0; $parent = $parent.parent()) {
+ if ($parent.css('overflow') == 'scroll' || $parent.css('overflow') == 'auto') {
+ if (Drupal.viewsUi.resizeModal) {
+ // If the modal is already at the max height, don't bother with
+ // this since the only reason to do it is to grow the modal.
+ if ($('.views-ui-dialog').height() < parseInt($(window).height() * .8)) {
+ Drupal.viewsUi.resizeModal('', true);
+ }
+ }
+ return;
+ }
+ }
+
+ var h = document.documentElement.clientHeight || document.body.clientHeight || 0;
+ var offset = document.documentElement.scrollTop || document.body.scrollTop || 0;
+ var posY = $(node).offset().top;
+ var fudge = 55;
+ if (posY + node.offsetHeight + fudge > h + offset) {
+ if (node.offsetHeight > h) {
+ window.scrollTo(0, posY);
+ }
+ else {
+ window.scrollTo(0, posY + node.offsetHeight - h + fudge);
+ }
+ }
+ };
+ }
+ }
+ };
+
+})(jQuery);
diff --git a/sites/all/modules/views/js/ajax_view.js b/sites/all/modules/views/js/ajax_view.js
new file mode 100644
index 000000000..ce6cc52d9
--- /dev/null
+++ b/sites/all/modules/views/js/ajax_view.js
@@ -0,0 +1,147 @@
+/**
+ * @file
+ * Handles AJAX fetching of views, including filter submission and response.
+ */
+(function ($) {
+
+/**
+ * Attaches the AJAX behavior to Views exposed filter forms and key View links.
+ */
+Drupal.behaviors.ViewsAjaxView = {};
+Drupal.behaviors.ViewsAjaxView.attach = function() {
+ if (Drupal.settings && Drupal.settings.views && Drupal.settings.views.ajaxViews) {
+ $.each(Drupal.settings.views.ajaxViews, function(i, settings) {
+ Drupal.views.instances[i] = new Drupal.views.ajaxView(settings);
+ });
+ }
+};
+
+Drupal.views = {};
+Drupal.views.instances = {};
+
+/**
+ * Javascript object for a certain view.
+ */
+Drupal.views.ajaxView = function(settings) {
+ var selector = '.view-dom-id-' + settings.view_dom_id;
+ this.$view = $(selector);
+
+ // Retrieve the path to use for views' ajax.
+ var ajax_path = Drupal.settings.views.ajax_path;
+
+ // If there are multiple views this might've ended up showing up multiple times.
+ if (ajax_path.constructor.toString().indexOf("Array") != -1) {
+ ajax_path = ajax_path[0];
+ }
+
+ // Check if there are any GET parameters to send to views.
+ var queryString = window.location.search || '';
+ if (queryString !== '') {
+ // Remove the question mark and Drupal path component if any.
+ var queryString = queryString.slice(1).replace(/q=[^&]+&?|&?render=[^&]+/, '');
+ if (queryString !== '') {
+ // If there is a '?' in ajax_path, clean url are on and & should be used to add parameters.
+ queryString = ((/\?/.test(ajax_path)) ? '&' : '?') + queryString;
+ }
+ }
+
+ this.element_settings = {
+ url: ajax_path + queryString,
+ submit: settings,
+ setClick: true,
+ event: 'click',
+ selector: selector,
+ progress: { type: 'throbber' }
+ };
+
+ this.settings = settings;
+
+ // Add the ajax to exposed forms.
+ this.$exposed_form = $('#views-exposed-form-'+ settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-'));
+ this.$exposed_form.once(jQuery.proxy(this.attachExposedFormAjax, this));
+
+ // Add the ajax to pagers.
+ this.$view
+ // Don't attach to nested views. Doing so would attach multiple behaviors
+ // to a given element.
+ .filter(jQuery.proxy(this.filterNestedViews, this))
+ .once(jQuery.proxy(this.attachPagerAjax, this));
+
+ // Add a trigger to update this view specifically. In order to trigger a
+ // refresh use the following code.
+ //
+ // @code
+ // jQuery('.view-name').trigger('RefreshView');
+ // @endcode
+ // Add a trigger to update this view specifically.
+ var self_settings = this.element_settings;
+ self_settings.event = 'RefreshView';
+ this.refreshViewAjax = new Drupal.ajax(this.selector, this.$view, self_settings);
+};
+
+Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() {
+ var button = $('input[type=submit], button[type=submit], input[type=image]', this.$exposed_form);
+ button = button[0];
+
+ this.exposedFormAjax = new Drupal.ajax($(button).attr('id'), button, this.element_settings);
+};
+
+Drupal.views.ajaxView.prototype.filterNestedViews= function() {
+ // If there is at least one parent with a view class, this view
+ // is nested (e.g., an attachment). Bail.
+ return !this.$view.parents('.view').size();
+};
+
+/**
+ * Attach the ajax behavior to each link.
+ */
+Drupal.views.ajaxView.prototype.attachPagerAjax = function() {
+ this.$view.find('ul.pager > li > a, th.views-field a, .attachment .views-summary a')
+ .each(jQuery.proxy(this.attachPagerLinkAjax, this));
+};
+
+/**
+ * Attach the ajax behavior to a singe link.
+ */
+Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) {
+ var $link = $(link);
+ var viewData = {};
+ var href = $link.attr('href');
+ // Construct an object using the settings defaults and then overriding
+ // with data specific to the link.
+ $.extend(
+ viewData,
+ this.settings,
+ Drupal.Views.parseQueryString(href),
+ // Extract argument data from the URL.
+ Drupal.Views.parseViewArgs(href, this.settings.view_base_path)
+ );
+
+ // For anchor tags, these will go to the target of the anchor rather
+ // than the usual location.
+ $.extend(viewData, Drupal.Views.parseViewArgs(href, this.settings.view_base_path));
+
+ this.element_settings.submit = viewData;
+ this.pagerAjax = new Drupal.ajax(false, $link, this.element_settings);
+};
+
+Drupal.ajax.prototype.commands.viewsScrollTop = function (ajax, response, status) {
+ // Scroll to the top of the view. This will allow users
+ // to browse newly loaded content after e.g. clicking a pager
+ // link.
+ var offset = $(response.selector).offset();
+ // We can't guarantee that the scrollable object should be
+ // the body, as the view could be embedded in something
+ // more complex such as a modal popup. Recurse up the DOM
+ // and scroll the first element that has a non-zero top.
+ var scrollTarget = response.selector;
+ while ($(scrollTarget).scrollTop() == 0 && $(scrollTarget).parent()) {
+ scrollTarget = $(scrollTarget).parent();
+ }
+ // Only scroll upward
+ if (offset.top - 10 < $(scrollTarget).scrollTop()) {
+ $(scrollTarget).animate({scrollTop: (offset.top - 10)}, 500);
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/views/js/base.js b/sites/all/modules/views/js/base.js
new file mode 100644
index 000000000..5855dceb9
--- /dev/null
+++ b/sites/all/modules/views/js/base.js
@@ -0,0 +1,110 @@
+/**
+ * @file
+ * Some basic behaviors and utility functions for Views.
+ */
+(function ($) {
+
+Drupal.Views = {};
+
+/**
+ * jQuery UI tabs, Views integration component
+ */
+Drupal.behaviors.viewsTabs = {
+ attach: function (context) {
+ if ($.viewsUi && $.viewsUi.tabs) {
+ $('#views-tabset').once('views-processed').viewsTabs({
+ selectedClass: 'active'
+ });
+ }
+
+ $('a.views-remove-link').once('views-processed').click(function(event) {
+ var id = $(this).attr('id').replace('views-remove-link-', '');
+ $('#views-row-' + id).hide();
+ $('#views-removed-' + id).attr('checked', true);
+ event.preventDefault();
+ });
+ /**
+ * Here is to handle display deletion
+ * (checking in the hidden checkbox and hiding out the row)
+ */
+ $('a.display-remove-link')
+ .addClass('display-processed')
+ .click(function() {
+ var id = $(this).attr('id').replace('display-remove-link-', '');
+ $('#display-row-' + id).hide();
+ $('#display-removed-' + id).attr('checked', true);
+ return false;
+ });
+ }
+};
+
+/**
+ * Helper function to parse a querystring.
+ */
+Drupal.Views.parseQueryString = function (query) {
+ var args = {};
+ var pos = query.indexOf('?');
+ if (pos != -1) {
+ query = query.substring(pos + 1);
+ }
+ var pairs = query.split('&');
+ for(var i in pairs) {
+ if (typeof(pairs[i]) == 'string') {
+ var pair = pairs[i].split('=');
+ // Ignore the 'q' path argument, if present.
+ if (pair[0] != 'q' && pair[1]) {
+ args[decodeURIComponent(pair[0].replace(/\+/g, ' '))] = decodeURIComponent(pair[1].replace(/\+/g, ' '));
+ }
+ }
+ }
+ return args;
+};
+
+/**
+ * Helper function to return a view's arguments based on a path.
+ */
+Drupal.Views.parseViewArgs = function (href, viewPath) {
+ var returnObj = {};
+ var path = Drupal.Views.getPath(href);
+ // Ensure we have a correct path.
+ if (viewPath && path.substring(0, viewPath.length + 1) == viewPath + '/') {
+ var args = decodeURIComponent(path.substring(viewPath.length + 1, path.length));
+ returnObj.view_args = args;
+ returnObj.view_path = path;
+ }
+ return returnObj;
+};
+
+/**
+ * Strip off the protocol plus domain from an href.
+ */
+Drupal.Views.pathPortion = function (href) {
+ // Remove e.g. http://example.com if present.
+ var protocol = window.location.protocol;
+ if (href.substring(0, protocol.length) == protocol) {
+ // 2 is the length of the '//' that normally follows the protocol
+ href = href.substring(href.indexOf('/', protocol.length + 2));
+ }
+ return href;
+};
+
+/**
+ * Return the Drupal path portion of an href.
+ */
+Drupal.Views.getPath = function (href) {
+ href = Drupal.Views.pathPortion(href);
+ href = href.substring(Drupal.settings.basePath.length, href.length);
+ // 3 is the length of the '?q=' added to the url without clean urls.
+ if (href.substring(0, 3) == '?q=') {
+ href = href.substring(3, href.length);
+ }
+ var chars = ['#', '?', '&'];
+ for (i in chars) {
+ if (href.indexOf(chars[i]) > -1) {
+ href = href.substr(0, href.indexOf(chars[i]));
+ }
+ }
+ return href;
+};
+
+})(jQuery);
diff --git a/sites/all/modules/views/js/jquery.ui.dialog.patch.js b/sites/all/modules/views/js/jquery.ui.dialog.patch.js
new file mode 100644
index 000000000..3ac7704ab
--- /dev/null
+++ b/sites/all/modules/views/js/jquery.ui.dialog.patch.js
@@ -0,0 +1,27 @@
+/**
+ * This is part of a patch to address a jQueryUI bug. The bug is responsible
+ * for the inability to scroll a page when a modal dialog is active. If the content
+ * of the dialog extends beyond the bottom of the viewport, the user is only able
+ * to scroll with a mousewheel or up/down keyboard keys.
+ *
+ * @see http://bugs.jqueryui.com/ticket/4671
+ * @see https://bugs.webkit.org/show_bug.cgi?id=19033
+ * @see views_ui.module
+ * @see js/jquery.ui.dialog.min.js
+ *
+ * This javascript patch overwrites the $.ui.dialog.overlay.events object to remove
+ * the mousedown, mouseup and click events from the list of events that are bound
+ * in $.ui.dialog.overlay.create
+ *
+ * The original code for this object:
+ * $.ui.dialog.overlay.events: $.map('focus,mousedown,mouseup,keydown,keypress,click'.split(','),
+ * function(event) { return event + '.dialog-overlay'; }).join(' '),
+ *
+ */
+
+(function ($, undefined) {
+ if ($.ui && $.ui.dialog && $.ui.dialog.overlay) {
+ $.ui.dialog.overlay.events = $.map('focus,keydown,keypress'.split(','),
+ function(event) { return event + '.dialog-overlay'; }).join(' ');
+ }
+}(jQuery));
diff --git a/sites/all/modules/views/js/views-admin.js b/sites/all/modules/views/js/views-admin.js
new file mode 100644
index 000000000..2b4ccf339
--- /dev/null
+++ b/sites/all/modules/views/js/views-admin.js
@@ -0,0 +1,1028 @@
+/**
+ * @file
+ * Some basic behaviors and utility functions for Views UI.
+ */
+Drupal.viewsUi = {};
+
+Drupal.behaviors.viewsUiEditView = {};
+
+/**
+ * Improve the user experience of the views edit interface.
+ */
+Drupal.behaviors.viewsUiEditView.attach = function (context, settings) {
+ // Only show the SQL rewrite warning when the user has chosen the
+ // corresponding checkbox.
+ jQuery('#edit-query-options-disable-sql-rewrite').click(function () {
+ jQuery('.sql-rewrite-warning').toggleClass('js-hide');
+ });
+};
+
+Drupal.behaviors.viewsUiAddView = {};
+
+/**
+ * In the add view wizard, use the view name to prepopulate form fields such as
+ * page title and menu link.
+ */
+Drupal.behaviors.viewsUiAddView.attach = function (context, settings) {
+ var $ = jQuery;
+ var exclude, replace, suffix;
+ // Set up regular expressions to allow only numbers, letters, and dashes.
+ exclude = new RegExp('[^a-z0-9\\-]+', 'g');
+ replace = '-';
+
+ // The page title, block title, and menu link fields can all be prepopulated
+ // with the view name - no regular expression needed.
+ var $fields = $(context).find('[id^="edit-page-title"], [id^="edit-block-title"], [id^="edit-page-link-properties-title"]');
+ if ($fields.length) {
+ if (!this.fieldsFiller) {
+ this.fieldsFiller = new Drupal.viewsUi.FormFieldFiller($fields);
+ }
+ else {
+ // After an AJAX response, this.fieldsFiller will still have event
+ // handlers bound to the old version of the form fields (which don't exist
+ // anymore). The event handlers need to be unbound and then rebound to the
+ // new markup. Note that jQuery.live is difficult to make work in this
+ // case because the IDs of the form fields change on every AJAX response.
+ this.fieldsFiller.rebind($fields);
+ }
+ }
+
+ // Prepopulate the path field with a URLified version of the view name.
+ var $pathField = $(context).find('[id^="edit-page-path"]');
+ if ($pathField.length) {
+ if (!this.pathFiller) {
+ this.pathFiller = new Drupal.viewsUi.FormFieldFiller($pathField, exclude, replace);
+ }
+ else {
+ this.pathFiller.rebind($pathField);
+ }
+ }
+
+ // Populate the RSS feed field with a URLified version of the view name, and
+ // an .xml suffix (to make it unique).
+ var $feedField = $(context).find('[id^="edit-page-feed-properties-path"]');
+ if ($feedField.length) {
+ if (!this.feedFiller) {
+ suffix = '.xml';
+ this.feedFiller = new Drupal.viewsUi.FormFieldFiller($feedField, exclude, replace, suffix);
+ }
+ else {
+ this.feedFiller.rebind($feedField);
+ }
+ }
+};
+
+/**
+ * Constructor for the Drupal.viewsUi.FormFieldFiller object.
+ *
+ * Prepopulates a form field based on the view name.
+ *
+ * @param $target
+ * A jQuery object representing the form field to prepopulate.
+ * @param exclude
+ * Optional. A regular expression representing characters to exclude from the
+ * target field.
+ * @param replace
+ * Optional. A string to use as the replacement value for disallowed
+ * characters.
+ * @param suffix
+ * Optional. A suffix to append at the end of the target field content.
+ */
+Drupal.viewsUi.FormFieldFiller = function ($target, exclude, replace, suffix) {
+ var $ = jQuery;
+ this.source = $('#edit-human-name');
+ this.target = $target;
+ this.exclude = exclude || false;
+ this.replace = replace || '';
+ this.suffix = suffix || '';
+
+ // Create bound versions of this instance's object methods to use as event
+ // handlers. This will let us easily unbind those specific handlers later on.
+ // NOTE: jQuery.proxy will not work for this because it assumes we want only
+ // one bound version of an object method, whereas we need one version per
+ // object instance.
+ var self = this;
+ this.populate = function () {return self._populate.call(self);};
+ this.unbind = function () {return self._unbind.call(self);};
+
+ this.bind();
+ // Object constructor; no return value.
+};
+
+/**
+ * Bind the form-filling behavior.
+ */
+Drupal.viewsUi.FormFieldFiller.prototype.bind = function () {
+ this.unbind();
+ // Populate the form field when the source changes.
+ this.source.bind('keyup.viewsUi change.viewsUi', this.populate);
+ // Quit populating the field as soon as it gets focus.
+ this.target.bind('focus.viewsUi', this.unbind);
+};
+
+/**
+ * Get the source form field value as altered by the passed-in parameters.
+ */
+Drupal.viewsUi.FormFieldFiller.prototype.getTransliterated = function () {
+ var from = this.source.val();
+ if (this.exclude) {
+ from = from.toLowerCase().replace(this.exclude, this.replace);
+ }
+ return from + this.suffix;
+};
+
+/**
+ * Populate the target form field with the altered source field value.
+ */
+Drupal.viewsUi.FormFieldFiller.prototype._populate = function () {
+ var transliterated = this.getTransliterated();
+ this.target.val(transliterated);
+};
+
+/**
+ * Stop prepopulating the form fields.
+ */
+Drupal.viewsUi.FormFieldFiller.prototype._unbind = function () {
+ this.source.unbind('keyup.viewsUi change.viewsUi', this.populate);
+ this.target.unbind('focus.viewsUi', this.unbind);
+};
+
+/**
+ * Bind event handlers to the new form fields, after they're replaced via AJAX.
+ */
+Drupal.viewsUi.FormFieldFiller.prototype.rebind = function ($fields) {
+ this.target = $fields;
+ this.bind();
+}
+
+Drupal.behaviors.addItemForm = {};
+Drupal.behaviors.addItemForm.attach = function (context) {
+ var $ = jQuery;
+ // The add item form may have an id of views-ui-add-item-form--n.
+ var $form = $(context).find('form[id^="views-ui-add-item-form"]').first();
+ // Make sure we don't add more than one event handler to the same form.
+ $form = $form.once('views-ui-add-item-form');
+ if ($form.length) {
+ new Drupal.viewsUi.addItemForm($form);
+ }
+}
+
+Drupal.viewsUi.addItemForm = function($form) {
+ this.$form = $form;
+ this.$form.find('.views-filterable-options :checkbox').click(jQuery.proxy(this.handleCheck, this));
+ // Find the wrapper of the displayed text.
+ this.$selected_div = this.$form.find('.views-selected-options').parent();
+ this.$selected_div.hide();
+ this.checkedItems = [];
+}
+
+Drupal.viewsUi.addItemForm.prototype.handleCheck = function (event) {
+ var $target = jQuery(event.target);
+ var label = jQuery.trim($target.next().text());
+ // Add/remove the checked item to the list.
+ if ($target.is(':checked')) {
+ this.$selected_div.show();
+ this.checkedItems.push(label);
+ }
+ else {
+ var length = this.checkedItems.length;
+ var position = jQuery.inArray(label, this.checkedItems);
+ // Delete the item from the list and take sure that the list doesn't have undefined items left.
+ for (var i = 0; i < this.checkedItems.length; i++) {
+ if (i == position) {
+ this.checkedItems.splice(i, 1);
+ i--;
+ break;
+ }
+ }
+ // Hide it again if none item is selected.
+ if (this.checkedItems.length == 0) {
+ this.$selected_div.hide();
+ }
+ }
+ this.refreshCheckedItems();
+}
+
+
+/**
+ * Refresh the display of the checked items.
+ */
+Drupal.viewsUi.addItemForm.prototype.refreshCheckedItems = function() {
+ // Perhaps we should precache the text div, too.
+ this.$selected_div.find('.views-selected-options').html(Drupal.checkPlain(this.checkedItems.join(', ')));
+ Drupal.viewsUi.resizeModal('', true);
+}
+
+
+/**
+ * The input field items that add displays must be rendered as <input> elements.
+ * The following behavior detaches the <input> elements from the DOM, wraps them
+ * in an unordered list, then appends them to the list of tabs.
+ */
+Drupal.behaviors.viewsUiRenderAddViewButton = {};
+
+Drupal.behaviors.viewsUiRenderAddViewButton.attach = function (context, settings) {
+ var $ = jQuery;
+ // Build the add display menu and pull the display input buttons into it.
+ var $menu = $('#views-display-menu-tabs', context).once('views-ui-render-add-view-button-processed');
+
+ if (!$menu.length) {
+ return;
+ }
+ var $addDisplayDropdown = $('<li class="add"><a href="#"><span class="icon add"></span>' + Drupal.t('Add') + '</a><ul class="action-list" style="display:none;"></ul></li>');
+ var $displayButtons = $menu.nextAll('input.add-display').detach();
+ $displayButtons.appendTo($addDisplayDropdown.find('.action-list')).wrap('<li>')
+ .parent().first().addClass('first').end().last().addClass('last');
+ // Remove the 'Add ' prefix from the button labels since they're being palced
+ // in an 'Add' dropdown.
+ // @todo This assumes English, but so does $addDisplayDropdown above. Add
+ // support for translation.
+ $displayButtons.each(function () {
+ var label = $(this).val();
+ if (label.substr(0, 4) == 'Add ') {
+ $(this).val(label.substr(4));
+ }
+ });
+ $addDisplayDropdown.appendTo($menu);
+
+ // Add the click handler for the add display button
+ $('li.add > a', $menu).bind('click', function (event) {
+ event.preventDefault();
+ var $trigger = $(this);
+ Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
+ });
+ // Add a mouseleave handler to close the dropdown when the user mouses
+ // away from the item. We use mouseleave instead of mouseout because
+ // the user is going to trigger mouseout when she moves from the trigger
+ // link to the sub menu items.
+ //
+ // We use the 'li.add' selector because the open class on this item will be
+ // toggled on and off and we want the handler to take effect in the cases
+ // that the class is present, but not when it isn't.
+ $menu.delegate('li.add', 'mouseleave', function (event) {
+ var $this = $(this);
+ var $trigger = $this.children('a[href="#"]');
+ if ($this.children('.action-list').is(':visible')) {
+ Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
+ }
+ });
+};
+
+/**
+ * @note [@jessebeach] I feel like the following should be a more generic function and
+ * not written specifically for this UI, but I'm not sure where to put it.
+ */
+Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu = function ($trigger) {
+ $trigger.parent().toggleClass('open');
+ $trigger.next().slideToggle('fast');
+}
+
+
+Drupal.behaviors.viewsUiSearchOptions = {};
+
+Drupal.behaviors.viewsUiSearchOptions.attach = function (context) {
+ var $ = jQuery;
+ // The add item form may have an id of views-ui-add-item-form--n.
+ var $form = $(context).find('form[id^="views-ui-add-item-form"]').first();
+ // Make sure we don't add more than one event handler to the same form.
+ $form = $form.once('views-ui-filter-options');
+ if ($form.length) {
+ new Drupal.viewsUi.OptionsSearch($form);
+ }
+};
+
+/**
+ * Constructor for the viewsUi.OptionsSearch object.
+ *
+ * The OptionsSearch object filters the available options on a form according
+ * to the user's search term. Typing in "taxonomy" will show only those options
+ * containing "taxonomy" in their label.
+ */
+Drupal.viewsUi.OptionsSearch = function ($form) {
+ this.$form = $form;
+ // Add a keyup handler to the search box.
+ this.$searchBox = this.$form.find('#edit-options-search');
+ this.$searchBox.keyup(jQuery.proxy(this.handleKeyup, this));
+ // Get a list of option labels and their corresponding divs and maintain it
+ // in memory, so we have as little overhead as possible at keyup time.
+ this.options = this.getOptions(this.$form.find('.filterable-option'));
+ // Restripe on initial loading.
+ this.handleKeyup();
+ // Trap the ENTER key in the search box so that it doesn't submit the form.
+ this.$searchBox.keypress(function(event) {
+ if (event.which == 13) {
+ event.preventDefault();
+ }
+ });
+};
+
+/**
+ * Assemble a list of all the filterable options on the form.
+ *
+ * @param $allOptions
+ * A jQuery object representing the rows of filterable options to be
+ * shown and hidden depending on the user's search terms.
+ */
+Drupal.viewsUi.OptionsSearch.prototype.getOptions = function ($allOptions) {
+ var $ = jQuery;
+ var i, $label, $description, $option;
+ var options = [];
+ var length = $allOptions.length;
+ for (i = 0; i < length; i++) {
+ $option = $($allOptions[i]);
+ $label = $option.find('label');
+ $description = $option.find('div.description');
+ options[i] = {
+ // Search on the lowercase version of the label text + description.
+ 'searchText': $label.text().toLowerCase() + " " + $description.text().toLowerCase(),
+ // Maintain a reference to the jQuery object for each row, so we don't
+ // have to create a new object inside the performance-sensitive keyup
+ // handler.
+ '$div': $option
+ }
+ }
+ return options;
+};
+
+/**
+ * Keyup handler for the search box that hides or shows the relevant options.
+ */
+Drupal.viewsUi.OptionsSearch.prototype.handleKeyup = function (event) {
+ var found, i, j, option, search, words, wordsLength, zebraClass, zebraCounter;
+
+ // Determine the user's search query. The search text has been converted to
+ // lowercase.
+ search = this.$searchBox.val().toLowerCase();
+ words = search.split(' ');
+ wordsLength = words.length;
+
+ // Start the counter for restriping rows.
+ zebraCounter = 0;
+
+ // Search through the search texts in the form for matching text.
+ var length = this.options.length;
+ for (i = 0; i < length; i++) {
+ // Use a local variable for the option being searched, for performance.
+ option = this.options[i];
+ found = true;
+ // Each word in the search string has to match the item in order for the
+ // item to be shown.
+ for (j = 0; j < wordsLength; j++) {
+ if (option.searchText.indexOf(words[j]) === -1) {
+ found = false;
+ }
+ }
+ if (found) {
+ // Show the checkbox row, and restripe it.
+ zebraClass = (zebraCounter % 2) ? 'odd' : 'even';
+ option.$div.show();
+ option.$div.removeClass('even odd');
+ option.$div.addClass(zebraClass);
+ zebraCounter++;
+ }
+ else {
+ // The search string wasn't found; hide this item.
+ option.$div.hide();
+ }
+ }
+};
+
+
+Drupal.behaviors.viewsUiPreview = {};
+Drupal.behaviors.viewsUiPreview.attach = function (context, settings) {
+ var $ = jQuery;
+
+ // Only act on the edit view form.
+ var contextualFiltersBucket = $('.views-display-column .views-ui-display-tab-bucket.contextual-filters', context);
+ if (contextualFiltersBucket.length == 0) {
+ return;
+ }
+
+ // If the display has no contextual filters, hide the form where you enter
+ // the contextual filters for the live preview. If it has contextual filters,
+ // show the form.
+ var contextualFilters = $('.views-display-setting a', contextualFiltersBucket);
+ if (contextualFilters.length) {
+ $('#preview-args').parent().show();
+ }
+ else {
+ $('#preview-args').parent().hide();
+ }
+
+ // Executes an initial preview.
+ if ($('#edit-displays-live-preview').once('edit-displays-live-preview').is(':checked')) {
+ $('#preview-submit').once('edit-displays-live-preview').click();
+ }
+};
+
+
+Drupal.behaviors.viewsUiRearrangeFilter = {};
+Drupal.behaviors.viewsUiRearrangeFilter.attach = function (context, settings) {
+ var $ = jQuery;
+ // Only act on the rearrange filter form.
+ if (typeof Drupal.tableDrag == 'undefined' || typeof Drupal.tableDrag['views-rearrange-filters'] == 'undefined') {
+ return;
+ }
+
+ var table = $('#views-rearrange-filters', context).once('views-rearrange-filters');
+ var operator = $('.form-item-filter-groups-operator', context).once('views-rearrange-filters');
+ if (table.length) {
+ new Drupal.viewsUi.rearrangeFilterHandler(table, operator);
+ }
+};
+
+/**
+ * Improve the UI of the rearrange filters dialog box.
+ */
+Drupal.viewsUi.rearrangeFilterHandler = function (table, operator) {
+ var $ = jQuery;
+ // Keep a reference to the <table> being altered and to the div containing
+ // the filter groups operator dropdown (if it exists).
+ this.table = table;
+ this.operator = operator;
+ this.hasGroupOperator = this.operator.length > 0;
+
+ // Keep a reference to all draggable rows within the table.
+ this.draggableRows = $('.draggable', table);
+
+ // Keep a reference to the buttons for adding and removing filter groups.
+ this.addGroupButton = $('input#views-add-group');
+ this.removeGroupButtons = $('input.views-remove-group', table);
+
+ // Add links that duplicate the functionality of the (hidden) add and remove
+ // buttons.
+ this.insertAddRemoveFilterGroupLinks();
+
+ // When there is a filter groups operator dropdown on the page, create
+ // duplicates of the dropdown between each pair of filter groups.
+ if (this.hasGroupOperator) {
+ this.dropdowns = this.duplicateGroupsOperator();
+ this.syncGroupsOperators();
+ }
+
+ // Add methods to the tableDrag instance to account for operator cells (which
+ // span multiple rows), the operator labels next to each filter (e.g., "And"
+ // or "Or"), the filter groups, and other special aspects of this tableDrag
+ // instance.
+ this.modifyTableDrag();
+
+ // Initialize the operator labels (e.g., "And" or "Or") that are displayed
+ // next to the filters in each group, and bind a handler so that they change
+ // based on the values of the operator dropdown within that group.
+ this.redrawOperatorLabels();
+ $('.views-group-title select', table)
+ .once('views-rearrange-filter-handler')
+ .bind('change.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
+
+ // Bind handlers so that when a "Remove" link is clicked, we:
+ // - Update the rowspans of cells containing an operator dropdown (since they
+ // need to change to reflect the number of rows in each group).
+ // - Redraw the operator labels next to the filters in the group (since the
+ // filter that is currently displayed last in each group is not supposed to
+ // have a label display next to it).
+ $('a.views-groups-remove-link', this.table)
+ .once('views-rearrange-filter-handler')
+ .bind('click.views-rearrange-filter-handler', $.proxy(this, 'updateRowspans'))
+ .bind('click.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
+};
+
+/**
+ * Insert links that allow filter groups to be added and removed.
+ */
+Drupal.viewsUi.rearrangeFilterHandler.prototype.insertAddRemoveFilterGroupLinks = function () {
+ var $ = jQuery;
+
+ // Insert a link for adding a new group at the top of the page, and make it
+ // match the action links styling used in a typical page.tpl.php. Note that
+ // Drupal does not provide a theme function for this markup, so this is the
+ // best we can do.
+ $('<ul class="action-links"><li><a id="views-add-group-link" href="#">' + this.addGroupButton.val() + '</a></li></ul>')
+ .prependTo(this.table.parent())
+ // When the link is clicked, dynamically click the hidden form button for
+ // adding a new filter group.
+ .once('views-rearrange-filter-handler')
+ .bind('click.views-rearrange-filter-handler', $.proxy(this, 'clickAddGroupButton'));
+
+ // Find each (visually hidden) button for removing a filter group and insert
+ // a link next to it.
+ var length = this.removeGroupButtons.length;
+ for (i = 0; i < length; i++) {
+ var $removeGroupButton = $(this.removeGroupButtons[i]);
+ var buttonId = $removeGroupButton.attr('id');
+ $('<a href="#" class="views-remove-group-link">' + Drupal.t('Remove group') + '</a>')
+ .insertBefore($removeGroupButton)
+ // When the link is clicked, dynamically click the corresponding form
+ // button.
+ .once('views-rearrange-filter-handler')
+ .bind('click.views-rearrange-filter-handler', {buttonId: buttonId}, $.proxy(this, 'clickRemoveGroupButton'));
+ }
+};
+
+/**
+ * Dynamically click the button that adds a new filter group.
+ */
+Drupal.viewsUi.rearrangeFilterHandler.prototype.clickAddGroupButton = function () {
+ // Due to conflicts between Drupal core's AJAX system and the Views AJAX
+ // system, the only way to get this to work seems to be to trigger both the
+ // .mousedown() and .submit() events.
+ this.addGroupButton.mousedown();
+ this.addGroupButton.submit();
+ return false;
+};
+
+/**
+ * Dynamically click a button for removing a filter group.
+ *
+ * @param event
+ * Event being triggered, with event.data.buttonId set to the ID of the
+ * form button that should be clicked.
+ */
+Drupal.viewsUi.rearrangeFilterHandler.prototype.clickRemoveGroupButton = function (event) {
+ // For some reason, here we only need to trigger .submit(), unlike for
+ // Drupal.viewsUi.rearrangeFilterHandler.prototype.clickAddGroupButton()
+ // where we had to trigger .mousedown() also.
+ jQuery('input#' + event.data.buttonId, this.table).submit();
+ return false;
+};
+
+/**
+ * Move the groups operator so that it's between the first two groups, and
+ * duplicate it between any subsequent groups.
+ */
+Drupal.viewsUi.rearrangeFilterHandler.prototype.duplicateGroupsOperator = function () {
+ var $ = jQuery;
+ var dropdowns, newRow;
+
+ var titleRows = $('tr.views-group-title'), titleRow;
+
+ // Get rid of the explanatory text around the operator; its placement is
+ // explanatory enough.
+ this.operator.find('label').add('div.description').addClass('element-invisible');
+ this.operator.find('select').addClass('form-select');
+
+ // Keep a list of the operator dropdowns, so we can sync their behavior later.
+ dropdowns = this.operator;
+
+ // Move the operator to a new row just above the second group.
+ titleRow = $('tr#views-group-title-2');
+ newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
+ newRow.find('td').append(this.operator);
+ newRow.insertBefore(titleRow);
+ var i, length = titleRows.length;
+ // Starting with the third group, copy the operator to a new row above the
+ // group title.
+ for (i = 2; i < length; i++) {
+ titleRow = $(titleRows[i]);
+ // Make a copy of the operator dropdown and put it in a new table row.
+ var fakeOperator = this.operator.clone();
+ fakeOperator.attr('id', '');
+ newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
+ newRow.find('td').append(fakeOperator);
+ newRow.insertBefore(titleRow);
+ dropdowns = dropdowns.add(fakeOperator);
+ }
+
+ return dropdowns;
+};
+
+/**
+ * Make the duplicated groups operators change in sync with each other.
+ */
+Drupal.viewsUi.rearrangeFilterHandler.prototype.syncGroupsOperators = function () {
+ if (this.dropdowns.length < 2) {
+ // We only have one dropdown (or none at all), so there's nothing to sync.
+ return;
+ }
+
+ this.dropdowns.change(jQuery.proxy(this, 'operatorChangeHandler'));
+};
+
+/**
+ * Click handler for the operators that appear between filter groups.
+ *
+ * Forces all operator dropdowns to have the same value.
+ */
+Drupal.viewsUi.rearrangeFilterHandler.prototype.operatorChangeHandler = function (event) {
+ var $ = jQuery;
+ var $target = $(event.target);
+ var operators = this.dropdowns.find('select').not($target);
+
+ // Change the other operators to match this new value.
+ operators.val($target.val());
+};
+
+Drupal.viewsUi.rearrangeFilterHandler.prototype.modifyTableDrag = function () {
+ var tableDrag = Drupal.tableDrag['views-rearrange-filters'];
+ var filterHandler = this;
+
+ /**
+ * Override the row.onSwap method from tabledrag.js.
+ *
+ * When a row is dragged to another place in the table, several things need
+ * to occur.
+ * - The row needs to be moved so that it's within one of the filter groups.
+ * - The operator cells that span multiple rows need their rowspan attributes
+ * updated to reflect the number of rows in each group.
+ * - The operator labels that are displayed next to each filter need to be
+ * redrawn, to account for the row's new location.
+ */
+ tableDrag.row.prototype.onSwap = function () {
+ if (filterHandler.hasGroupOperator) {
+ // Make sure the row that just got moved (this.group) is inside one of
+ // the filter groups (i.e. below an empty marker row or a draggable). If
+ // it isn't, move it down one.
+ var thisRow = jQuery(this.group);
+ var previousRow = thisRow.prev('tr');
+ if (previousRow.length && !previousRow.hasClass('group-message') && !previousRow.hasClass('draggable')) {
+ // Move the dragged row down one.
+ var next = thisRow.next();
+ if (next.is('tr')) {
+ this.swap('after', next);
+ }
+ }
+ filterHandler.updateRowspans();
+ }
+ // Redraw the operator labels that are displayed next to each filter, to
+ // account for the row's new location.
+ filterHandler.redrawOperatorLabels();
+ };
+
+ /**
+ * Override the onDrop method from tabledrag.js.
+ */
+ tableDrag.onDrop = function () {
+ var $ = jQuery;
+
+ // If the tabledrag change marker (i.e., the "*") has been inserted inside
+ // a row after the operator label (i.e., "And" or "Or") rearrange the items
+ // so the operator label continues to appear last.
+ var changeMarker = $(this.oldRowElement).find('.tabledrag-changed');
+ if (changeMarker.length) {
+ // Search for occurrences of the operator label before the change marker,
+ // and reverse them.
+ var operatorLabel = changeMarker.prevAll('.views-operator-label');
+ if (operatorLabel.length) {
+ operatorLabel.insertAfter(changeMarker);
+ }
+ }
+
+ // Make sure the "group" dropdown is properly updated when rows are dragged
+ // into an empty filter group. This is borrowed heavily from the block.js
+ // implementation of tableDrag.onDrop().
+ var groupRow = $(this.rowObject.element).prevAll('tr.group-message').get(0);
+ var groupName = groupRow.className.replace(/([^ ]+[ ]+)*group-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+ var groupField = $('select.views-group-select', this.rowObject.element);
+ if ($(this.rowObject.element).prev('tr').is('.group-message') && !groupField.is('.views-group-select-' + groupName)) {
+ var oldGroupName = groupField.attr('class').replace(/([^ ]+[ ]+)*views-group-select-([^ ]+)([ ]+[^ ]+)*/, '$2');
+ groupField.removeClass('views-group-select-' + oldGroupName).addClass('views-group-select-' + groupName);
+ groupField.val(groupName);
+ }
+ };
+};
+
+
+/**
+ * Redraw the operator labels that are displayed next to each filter.
+ */
+Drupal.viewsUi.rearrangeFilterHandler.prototype.redrawOperatorLabels = function () {
+ var $ = jQuery;
+ for (i = 0; i < this.draggableRows.length; i++) {
+ // Within the row, the operator labels are displayed inside the first table
+ // cell (next to the filter name).
+ var $draggableRow = $(this.draggableRows[i]);
+ var $firstCell = $('td:first', $draggableRow);
+ if ($firstCell.length) {
+ // The value of the operator label ("And" or "Or") is taken from the
+ // first operator dropdown we encounter, going backwards from the current
+ // row. This dropdown is the one associated with the current row's filter
+ // group.
+ var operatorValue = $draggableRow.prevAll('.views-group-title').find('option:selected').html();
+ var operatorLabel = '<span class="views-operator-label">' + operatorValue + '</span>';
+ // If the next visible row after this one is a draggable filter row,
+ // display the operator label next to the current row. (Checking for
+ // visibility is necessary here since the "Remove" links hide the removed
+ // row but don't actually remove it from the document).
+ var $nextRow = $draggableRow.nextAll(':visible').eq(0);
+ var $existingOperatorLabel = $firstCell.find('.views-operator-label');
+ if ($nextRow.hasClass('draggable')) {
+ // If an operator label was already there, replace it with the new one.
+ if ($existingOperatorLabel.length) {
+ $existingOperatorLabel.replaceWith(operatorLabel);
+ }
+ // Otherwise, append the operator label to the end of the table cell.
+ else {
+ $firstCell.append(operatorLabel);
+ }
+ }
+ // If the next row doesn't contain a filter, then this is the last row
+ // in the group. We don't want to display the operator there (since
+ // operators should only display between two related filters, e.g.
+ // "filter1 AND filter2 AND filter3"). So we remove any existing label
+ // that this row has.
+ else {
+ $existingOperatorLabel.remove();
+ }
+ }
+ }
+};
+
+/**
+ * Update the rowspan attribute of each cell containing an operator dropdown.
+ */
+Drupal.viewsUi.rearrangeFilterHandler.prototype.updateRowspans = function () {
+ var $ = jQuery;
+ var i, $row, $currentEmptyRow, draggableCount, $operatorCell;
+ var rows = $(this.table).find('tr');
+ var length = rows.length;
+ for (i = 0; i < length; i++) {
+ $row = $(rows[i]);
+ if ($row.hasClass('views-group-title')) {
+ // This row is a title row.
+ // Keep a reference to the cell containing the dropdown operator.
+ $operatorCell = $($row.find('td.group-operator'));
+ // Assume this filter group is empty, until we find otherwise.
+ draggableCount = 0;
+ $currentEmptyRow = $row.next('tr');
+ $currentEmptyRow.removeClass('group-populated').addClass('group-empty');
+ // The cell with the dropdown operator should span the title row and
+ // the "this group is empty" row.
+ $operatorCell.attr('rowspan', 2);
+ }
+ else if (($row).hasClass('draggable') && $row.is(':visible')) {
+ // We've found a visible filter row, so we now know the group isn't empty.
+ draggableCount++;
+ $currentEmptyRow.removeClass('group-empty').addClass('group-populated');
+ // The operator cell should span all draggable rows, plus the title.
+ $operatorCell.attr('rowspan', draggableCount + 1);
+ }
+ }
+};
+
+Drupal.behaviors.viewsFilterConfigSelectAll = {};
+
+/**
+ * Add a select all checkbox, which checks each checkbox at once.
+ */
+Drupal.behaviors.viewsFilterConfigSelectAll.attach = function(context) {
+ var $ = jQuery;
+ // Show the select all checkbox.
+ $('#views-ui-config-item-form div.form-item-options-value-all', context).once(function() {
+ $(this).show();
+ })
+ .find('input[type=checkbox]')
+ .click(function() {
+ var checked = $(this).is(':checked');
+ // Update all checkbox beside the select all checkbox.
+ $(this).parents('.form-checkboxes').find('input[type=checkbox]').each(function() {
+ $(this).attr('checked', checked);
+ });
+ });
+ // Uncheck the select all checkbox if any of the others are unchecked.
+ $('#views-ui-config-item-form div.form-type-checkbox').not($('.form-item-options-value-all')).find('input[type=checkbox]').each(function() {
+ $(this).click(function() {
+ if ($(this).is('checked') == 0) {
+ $('#edit-options-value-all').removeAttr('checked');
+ }
+ });
+ });
+};
+
+/**
+ * Ensure the desired default button is used when a form is implicitly submitted via an ENTER press on textfields, radios, and checkboxes.
+ *
+ * @see http://www.w3.org/TR/html5/association-of-controls-and-forms.html#implicit-submission
+ */
+Drupal.behaviors.viewsImplicitFormSubmission = {};
+Drupal.behaviors.viewsImplicitFormSubmission.attach = function (context, settings) {
+ var $ = jQuery;
+ $(':text, :password, :radio, :checkbox', context).once('viewsImplicitFormSubmission', function() {
+ $(this).keypress(function(event) {
+ if (event.which == 13) {
+ var formId = this.form.id;
+ if (formId && settings.viewsImplicitFormSubmission && settings.viewsImplicitFormSubmission[formId] && settings.viewsImplicitFormSubmission[formId].defaultButton) {
+ event.preventDefault();
+ var buttonId = settings.viewsImplicitFormSubmission[formId].defaultButton;
+ var $button = $('#' + buttonId, this.form);
+ if ($button.length == 1 && $button.is(':enabled')) {
+ if (Drupal.ajax && Drupal.ajax[buttonId]) {
+ $button.trigger(Drupal.ajax[buttonId].element_settings.event);
+ }
+ else {
+ $button.click();
+ }
+ }
+ }
+ }
+ });
+ });
+};
+
+/**
+ * Remove icon class from elements that are themed as buttons or dropbuttons.
+ */
+Drupal.behaviors.viewsRemoveIconClass = {};
+Drupal.behaviors.viewsRemoveIconClass.attach = function (context, settings) {
+ jQuery('.ctools-button', context).once('RemoveIconClass', function () {
+ var $ = jQuery;
+ var $this = $(this);
+ $('.icon', $this).removeClass('icon');
+ $('.horizontal', $this).removeClass('horizontal');
+ });
+};
+
+/**
+ * Change "Expose filter" buttons into checkboxes.
+ */
+Drupal.behaviors.viewsUiCheckboxify = {};
+Drupal.behaviors.viewsUiCheckboxify.attach = function (context, settings) {
+ var $ = jQuery;
+ var $buttons = $('#edit-options-expose-button-button, #edit-options-group-button-button').once('views-ui-checkboxify');
+ var length = $buttons.length;
+ var i;
+ for (i = 0; i < length; i++) {
+ new Drupal.viewsUi.Checkboxifier($buttons[i]);
+ }
+};
+
+/**
+ * Change the default widget to select the default group according to the
+ * selected widget for the exposed group.
+ */
+Drupal.behaviors.viewsUiChangeDefaultWidget = {};
+Drupal.behaviors.viewsUiChangeDefaultWidget.attach = function (context, settings) {
+ var $ = jQuery;
+ function change_default_widget(multiple) {
+ if (multiple) {
+ $('input.default-radios').hide();
+ $('td.any-default-radios-row').parent().hide();
+ $('input.default-checkboxes').show();
+ }
+ else {
+ $('input.default-checkboxes').hide();
+ $('td.any-default-radios-row').parent().show();
+ $('input.default-radios').show();
+ }
+ }
+ // Update on widget change.
+ $('input[name="options[group_info][multiple]"]').change(function() {
+ change_default_widget($(this).attr("checked"));
+ });
+ // Update the first time the form is rendered.
+ $('input[name="options[group_info][multiple]"]').trigger('change');
+};
+
+/**
+ * Attaches an expose filter button to a checkbox that triggers its click event.
+ *
+ * @param button
+ * The DOM object representing the button to be checkboxified.
+ */
+Drupal.viewsUi.Checkboxifier = function (button) {
+ var $ = jQuery;
+ this.$button = $(button);
+ this.$parent = this.$button.parent('div.views-expose, div.views-grouped');
+ this.$input = this.$parent.find('input:checkbox, input:radio');
+ // Hide the button and its description.
+ this.$button.hide();
+ this.$parent.find('.exposed-description, .grouped-description').hide();
+
+ this.$input.click($.proxy(this, 'clickHandler'));
+
+};
+
+/**
+ * When the checkbox is checked or unchecked, simulate a button press.
+ */
+Drupal.viewsUi.Checkboxifier.prototype.clickHandler = function (e) {
+ this.$button.mousedown();
+ this.$button.submit();
+};
+
+/**
+ * Change the Apply button text based upon the override select state.
+ */
+Drupal.behaviors.viewsUiOverrideSelect = {};
+Drupal.behaviors.viewsUiOverrideSelect.attach = function (context, settings) {
+ var $ = jQuery;
+ $('#edit-override-dropdown', context).once('views-ui-override-button-text', function() {
+ // Closures! :(
+ var $submit = $('#edit-submit', context);
+ var old_value = $submit.val();
+
+ $submit.once('views-ui-override-button-text')
+ .bind('mouseup', function() {
+ $(this).val(old_value);
+ return true;
+ });
+
+ $(this).bind('change', function() {
+ if ($(this).val() == 'default') {
+ $submit.val(Drupal.t('Apply (all displays)'));
+ }
+ else if ($(this).val() == 'default_revert') {
+ $submit.val(Drupal.t('Revert to default'));
+ }
+ else {
+ $submit.val(Drupal.t('Apply (this display)'));
+ }
+ })
+ .trigger('change');
+ });
+
+};
+
+Drupal.viewsUi.resizeModal = function (e, no_shrink) {
+ var $ = jQuery;
+ var $modal = $('.views-ui-dialog');
+ var $scroll = $('.scroll', $modal);
+ if ($modal.size() == 0 || $modal.css('display') == 'none') {
+ return;
+ }
+
+ var maxWidth = parseInt($(window).width() * .85); // 70% of window
+ var minWidth = parseInt($(window).width() * .6); // 70% of window
+
+ // Set the modal to the minwidth so that our width calculation of
+ // children works.
+ $modal.css('width', minWidth);
+ var width = minWidth;
+
+ // Don't let the window get more than 80% of the display high.
+ var maxHeight = parseInt($(window).height() * .8);
+ var minHeight = 200;
+ if (no_shrink) {
+ minHeight = $modal.height();
+ }
+
+ if (minHeight > maxHeight) {
+ minHeight = maxHeight;
+ }
+
+ var height = 0;
+
+ // Calculate the height of the 'scroll' region.
+ var scrollHeight = 0;
+
+ scrollHeight += parseInt($scroll.css('padding-top'));
+ scrollHeight += parseInt($scroll.css('padding-bottom'));
+
+ $scroll.children().each(function() {
+ var w = $(this).innerWidth();
+ if (w > width) {
+ width = w;
+ }
+ scrollHeight += $(this).outerHeight(true);
+ });
+
+ // Now, calculate what the difference between the scroll and the modal
+ // will be.
+
+ var difference = 0;
+ difference += parseInt($scroll.css('padding-top'));
+ difference += parseInt($scroll.css('padding-bottom'));
+ difference += $('.views-override').outerHeight(true);
+ difference += $('.views-messages').outerHeight(true);
+ difference += $('#views-ajax-title').outerHeight(true);
+ difference += $('.views-add-form-selected').outerHeight(true);
+ difference += $('.form-buttons', $modal).outerHeight(true);
+
+ height = scrollHeight + difference;
+
+ if (height > maxHeight) {
+ height = maxHeight;
+ scrollHeight = maxHeight - difference;
+ }
+ else if (height < minHeight) {
+ height = minHeight;
+ scrollHeight = minHeight - difference;
+ }
+
+ if (width > maxWidth) {
+ width = maxWidth;
+ }
+
+ // Get where we should move content to
+ var top = ($(window).height() / 2) - (height / 2);
+ var left = ($(window).width() / 2) - (width / 2);
+
+ $modal.css({
+ 'top': top + 'px',
+ 'left': left + 'px',
+ 'width': width + 'px',
+ 'height': height + 'px'
+ });
+
+ // Ensure inner popup height matches.
+ $(Drupal.settings.views.ajax.popup).css('height', height + 'px');
+
+ $scroll.css({
+ 'height': scrollHeight + 'px',
+ 'max-height': scrollHeight + 'px'
+ });
+
+};
+
+jQuery(function() {
+ jQuery(window).bind('resize', Drupal.viewsUi.resizeModal);
+ jQuery(window).bind('scroll', Drupal.viewsUi.resizeModal);
+});
diff --git a/sites/all/modules/views/js/views-contextual.js b/sites/all/modules/views/js/views-contextual.js
new file mode 100644
index 000000000..a2bbc2a96
--- /dev/null
+++ b/sites/all/modules/views/js/views-contextual.js
@@ -0,0 +1,16 @@
+/**
+ * @file
+ * Javascript related to contextual links.
+ */
+(function ($) {
+
+Drupal.behaviors.viewsContextualLinks = {
+ attach: function (context) {
+ // If there are views-related contextual links attached to the main page
+ // content, find the smallest region that encloses both the links and the
+ // view, and display it as a contextual links region.
+ $('.views-contextual-links-page', context).closest(':has(".view"):not("body")').addClass('contextual-links-region');
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/views/js/views-list.js b/sites/all/modules/views/js/views-list.js
new file mode 100644
index 000000000..9c6c5f06e
--- /dev/null
+++ b/sites/all/modules/views/js/views-list.js
@@ -0,0 +1,21 @@
+/**
+ * @file
+ * Javascript related to the main view list.
+ */
+(function ($) {
+
+Drupal.behaviors.viewsUIList = {
+ attach: function (context) {
+ $('#ctools-export-ui-list-items thead a').once('views-ajax-processed').each(function() {
+ $(this).click(function() {
+ var query = $.deparam.querystring(this.href);
+ $('#ctools-export-ui-list-form select[name=order]').val(query['order']);
+ $('#ctools-export-ui-list-form select[name=sort]').val(query['sort']);
+ $('#ctools-export-ui-list-form input.ctools-auto-submit-click').trigger('click');
+ return false;
+ });
+ });
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/views/modules/aggregator.views.inc b/sites/all/modules/views/modules/aggregator.views.inc
new file mode 100644
index 000000000..0fcae2c2f
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator.views.inc
@@ -0,0 +1,406 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for aggregator.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function aggregator_views_data() {
+ // ----------------------------------------------------------------------
+ // Main Aggregator Item base table
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['aggregator_item']['table']['group'] = t('Aggregator');
+
+ // Advertise this table as a possible base table
+ $data['aggregator_item']['table']['base'] = array(
+ 'field' => 'iid',
+ 'title' => t('Aggregator item'),
+ 'help' => t("Aggregator items are imported from external RSS and Atom news feeds."),
+ );
+
+ // ----------------------------------------------------------------
+ // Fields
+
+ // item id.
+ $data['aggregator_item']['iid'] = array(
+ 'title' => t('Feed Item ID'),
+ 'help' => t('The unique ID of the aggregator item.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ 'numeric' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // iid
+ $data['aggregator_item']['iid'] = array(
+ 'title' => t('Item ID'),
+ 'help' => t('The unique ID of the aggregator item.'), // The help that appears on the UI,
+ // Information for displaying the iid
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ // Information for accepting a iid as an argument
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_aggregator_iid',
+ 'name field' => 'title', // the field to display in the summary.
+ 'numeric' => TRUE,
+ ),
+ // Information for accepting a nid as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ // Information for sorting on a nid.
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // title
+ $data['aggregator_item']['title'] = array(
+ 'title' => t('Title'), // The item it appears as on the UI,
+ 'help' => t('The title of the aggregator item.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_aggregator_title_link',
+ 'extra' => array('link'),
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // link
+ $data['aggregator_item']['link'] = array(
+ 'title' => t('Link'), // The item it appears as on the UI,
+ 'help' => t('The link to the original source URL of the item.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_url',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // author
+ $data['aggregator_item']['author'] = array(
+ 'title' => t('Author'), // The item it appears as on the UI,
+ 'help' => t('The author of the original imported item.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_aggregator_xss',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // guid
+ $data['aggregator_item']['guid'] = array(
+ 'title' => t('GUID'), // The item it appears as on the UI,
+ 'help' => t('The guid of the original imported item.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_xss',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // feed body
+ $data['aggregator_item']['description'] = array(
+ 'title' => t('Body'), // The item it appears as on the UI,
+ 'help' => t('The actual content of the imported item.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_aggregator_xss',
+ 'click sortable' => FALSE,
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // item timestamp
+ $data['aggregator_item']['timestamp'] = array(
+ 'title' => t('Timestamp'), // The item it appears as on the UI,
+ 'help' => t('The date the original feed item was posted. (With some feeds, this will be the date it was imported.)'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_date',
+ ),
+ );
+
+
+ // ----------------------------------------------------------------------
+ // Aggregator feed table
+
+ $data['aggregator_feed']['table']['group'] = t('Aggregator feed');
+
+ // Explain how this table joins to others.
+ $data['aggregator_feed']['table']['join'] = array(
+ 'aggregator_item' => array(
+ 'left_field' => 'fid',
+ 'field' => 'fid',
+ ),
+ );
+
+ // fid
+ $data['aggregator_feed']['fid'] = array(
+ 'title' => t('Feed ID'),
+ 'help' => t('The unique ID of the aggregator feed.'), // The help that appears on the UI,
+ // Information for displaying the fid
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ // Information for accepting a fid as an argument
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_aggregator_fid',
+ 'name field' => 'title', // the field to display in the summary.
+ 'numeric' => TRUE,
+ ),
+ // Information for accepting a nid as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ // Information for sorting on a fid.
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // title
+ $data['aggregator_feed']['title'] = array(
+ 'title' => t('Title'), // The item it appears as on the UI,
+ 'help' => t('The title of the aggregator feed.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_aggregator_title_link',
+ 'extra' => array('link'),
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // link
+ $data['aggregator_feed']['link'] = array(
+ 'title' => t('Link'), // The item it appears as on the UI,
+ 'help' => t('The link to the source URL of the feed.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_url',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // feed last updated
+ $data['aggregator_feed']['checked'] = array(
+ 'title' => t('Last checked'), // The item it appears as on the UI,
+ 'help' => t('The date the feed was last checked for new content.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_date',
+ ),
+ );
+
+ // feed description
+ $data['aggregator_feed']['description'] = array(
+ 'title' => t('Description'), // The item it appears as on the UI,
+ 'help' => t('The description of the aggregator feed.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_xss',
+ 'click sortable' => FALSE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // feed last updated
+ $data['aggregator_feed']['modified'] = array(
+ 'title' => t('Last modified'), // The item it appears as on the UI,
+ 'help' => t('The date of the most recent new content on the feed.'),
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_date',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // Aggregator category feed table
+
+ $data['aggregator_category_feed']['table']['join'] = array(
+ 'aggregator_item' => array(
+ 'left_field' => 'fid',
+ 'field' => 'fid',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // Aggregator category table
+
+ $data['aggregator_category']['table']['group'] = t('Aggregator category');
+
+ $data['aggregator_category']['table']['join'] = array(
+ 'aggregator_item' => array(
+ 'left_table' => 'aggregator_category_feed',
+ 'left_field' => 'cid',
+ 'field' => 'cid',
+ ),
+ );
+
+ // cid
+ $data['aggregator_category']['cid'] = array(
+ 'title' => t('Category ID'),
+ 'help' => t('The unique ID of the aggregator category.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_aggregator_category_cid',
+ 'name field' => 'title',
+ 'numeric' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_aggregator_category_cid',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // title
+ $data['aggregator_category']['title'] = array(
+ 'title' => t('Category'),
+ 'help' => t('The title of the aggregator category.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_aggregator_category',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function aggregator_views_plugins() {
+ return array(
+ 'module' => 'views', // This just tells our themes are elsewhere.
+ 'row' => array(
+ 'aggregator_rss' => array(
+ 'title' => t('Aggregator item'),
+ 'help' => t('Display the aggregator item using the data from the original source.'),
+ 'handler' => 'views_plugin_row_aggregator_rss',
+ 'path' => drupal_get_path('module', 'views') . '/modules/node', // not necessary for most modules
+ 'theme' => 'views_view_row_rss',
+ 'base' => array('aggregator_item'), // only works with 'node' as base.
+ 'uses options' => TRUE,
+ 'type' => 'feed',
+ 'help topic' => 'style-aggregator-rss',
+ ),
+ ),
+ );
+}
diff --git a/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_category_cid.inc b/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_category_cid.inc
new file mode 100644
index 000000000..92ae8b7f2
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_category_cid.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_aggregator_category_cid.
+ */
+
+/**
+ * Argument handler to accept an aggregator category id.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_aggregator_category_cid extends views_handler_argument_numeric {
+ /**
+ * Override the behavior of title(). Get the title of the category.
+ */
+ function title_query() {
+ $titles = array();
+
+ $result = db_query("SELECT c.title FROM {aggregator_category} c WHERE c.cid IN (:cid)", array(':cid' => $this->value));
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->title);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_fid.inc b/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_fid.inc
new file mode 100644
index 000000000..414762688
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_fid.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_aggregator_fid.
+ */
+
+/**
+ * Argument handler to accept an aggregator feed id.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_aggregator_fid extends views_handler_argument_numeric {
+ /**
+ * Override the behavior of title(). Get the title of the feed.
+ */
+ function title_query() {
+ $titles = array();
+
+ $result = db_query("SELECT f.title FROM {aggregator_feed} f WHERE f.fid IN (:fids)", array(':fids' => $this->value));
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->title);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_iid.inc b/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_iid.inc
new file mode 100644
index 000000000..4c7824ec7
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_iid.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_aggregator_iid.
+ */
+
+/**
+ * Argument handler to accept an aggregator item id.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_aggregator_iid extends views_handler_argument_numeric {
+ /**
+ * Override the behavior of title(). Get the title of the category.
+ */
+ function title_query() {
+ $titles = array();
+ $placeholders = implode(', ', array_fill(0, sizeof($this->value), '%d'));
+
+ $result = db_select('aggregator_item', 'ai')
+ ->condition('iid', $this->value, 'IN')
+ ->fields('ai', array('title'))
+ ->execute();
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->title);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_category.inc b/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_category.inc
new file mode 100644
index 000000000..99fffa1ea
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_category.inc
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_aggregator_category.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to aggregator
+ * category.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_aggregator_category extends views_handler_field {
+ /**
+ * Constructor to provide additional field to add.
+ */
+ function construct() {
+ parent::construct();
+ $this->additional_fields['cid'] = 'cid';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_category'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Provide link to category option
+ */
+ function options_form(&$form, &$form_state) {
+ $form['link_to_category'] = array(
+ '#title' => t('Link this field to its aggregator category page'),
+ '#description' => t('This will override any other link you have set.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['link_to_category']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ /**
+ * Render whatever the data is as a link to the category.
+ *
+ * Data should be made XSS safe prior to calling this function.
+ */
+ function render_link($data, $values) {
+ $cid = $this->get_value($values, 'cid');
+ if (!empty($this->options['link_to_category']) && !empty($cid) && $data !== NULL && $data !== '') {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "aggregator/category/$cid";
+ }
+ return $data;
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_title_link.inc b/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_title_link.inc
new file mode 100644
index 000000000..d8bf5789b
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_title_link.inc
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_aggregator_title_link.
+ */
+
+/**
+ * Field handler that turns an item's title into a clickable link to the original
+ * source article.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_aggregator_title_link extends views_handler_field {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['link'] = 'link';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['display_as_link'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Provide link to the page being visited.
+ */
+ function options_form(&$form, &$form_state) {
+ $form['display_as_link'] = array(
+ '#title' => t('Display as link'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['display_as_link']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+
+ function render_link($data, $values) {
+ $link = $this->get_value($values, 'link');
+ if (!empty($this->options['display_as_link'])) {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = $link;
+ $this->options['alter']['html'] = TRUE;
+ }
+
+ return $data;
+ }
+}
diff --git a/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_xss.inc b/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_xss.inc
new file mode 100644
index 000000000..d39b10132
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator/views_handler_field_aggregator_xss.inc
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_aggregator_xss.
+ */
+
+/**
+ * Filters htmls tags from item.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_aggregator_xss extends views_handler_field {
+ function render($values) {
+ $value = $this->get_value($values);
+ return aggregator_filter_xss($value);
+ }
+}
diff --git a/sites/all/modules/views/modules/aggregator/views_handler_filter_aggregator_category_cid.inc b/sites/all/modules/views/modules/aggregator/views_handler_filter_aggregator_category_cid.inc
new file mode 100644
index 000000000..f9931c8f3
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator/views_handler_filter_aggregator_category_cid.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_aggregator_category_cid.
+ */
+
+/**
+ * Filter by aggregator category cid
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_aggregator_category_cid extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (isset($this->value_options)) {
+ return;
+ }
+
+ $this->value_options = array();
+
+ $result = db_query('SELECT * FROM {aggregator_category} ORDER BY title');
+ foreach ($result as $category) {
+ $this->value_options[$category->cid] = $category->title;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/aggregator/views_plugin_row_aggregator_rss.inc b/sites/all/modules/views/modules/aggregator/views_plugin_row_aggregator_rss.inc
new file mode 100644
index 000000000..672952e0b
--- /dev/null
+++ b/sites/all/modules/views/modules/aggregator/views_plugin_row_aggregator_rss.inc
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains the Aggregator Item RSS row style plugin.
+ */
+
+/**
+ * Plugin which loads an aggregator item and formats it as an RSS item.
+ */
+class views_plugin_row_aggregator_rss extends views_plugin_row {
+ var $base_table = 'aggregator_item';
+ var $base_field = 'iid';
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['item_length'] = array('default' => 'default');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['item_length'] = array(
+ '#type' => 'select',
+ '#title' => t('Display type'),
+ '#options' => array(
+ 'fulltext' => t('Full text'),
+ 'teaser' => t('Title plus teaser'),
+ 'title' => t('Title only'),
+ 'default' => t('Use default RSS settings'),
+ ),
+ '#default_value' => $this->options['item_length'],
+ );
+ }
+
+ function render($row) {
+ $iid = $row->{$this->field_alias};
+ $sql = "SELECT ai.iid, ai.fid, ai.title, ai.link, ai.author, ai.description, ";
+ $sql .= "ai.timestamp, ai.guid, af.title AS feed_title, ai.link AS feed_LINK ";
+ $sql .= "FROM {aggregator_item} ai LEFT JOIN {aggregator_feed} af ON ai.fid = af.fid ";
+ $sql .= "WHERE ai.iid = :iid";
+
+ $item = db_query($sql, array(':iid' => $iid))->fetchObject();
+
+ $item->elements = array(
+ array(
+ 'key' => 'pubDate',
+ 'value' => gmdate('r', $item->timestamp),
+ ),
+ array(
+ 'key' => 'dc:creator',
+ 'value' => $item->author,
+ ),
+ array(
+ 'key' => 'guid',
+ 'value' => $item->guid,
+ 'attributes' => array('isPermaLink' => 'false')
+ ),
+ );
+
+ foreach ($item->elements as $element) {
+ if (isset($element['namespace'])) {
+ $this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $element['namespace']);
+ }
+ }
+
+ return theme($this->theme_functions(), array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'row' => $item
+ ));
+ }
+}
diff --git a/sites/all/modules/views/modules/book.views.inc b/sites/all/modules/views/modules/book.views.inc
new file mode 100644
index 000000000..15a21830a
--- /dev/null
+++ b/sites/all/modules/views/modules/book.views.inc
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for book.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function book_views_data() {
+ // ----------------------------------------------------------------------
+ // book table
+
+ $data['book']['table']['group'] = t('Book');
+ $data['book']['table']['join'] = array(
+ 'node' => array(
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ );
+
+ $data['book']['bid'] = array(
+ 'title' => t('Top level book'),
+ 'help' => t('The book the node is in.'),
+ 'relationship' => array(
+ 'base' => 'node',
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Book'),
+ ),
+ // There is no argument here; if you need an argument, add the relationship
+ // and use the node: nid argument.
+ );
+
+ // ----------------------------------------------------------------------
+ // menu_links table -- this is aliased so we can get just book relations
+
+ // Book hierarchy and weight data are now in {menu_links}.
+ $data['book_menu_links']['table']['group'] = t('Book');
+ $data['book_menu_links']['table']['join'] = array(
+ 'node' => array(
+ 'table' => 'menu_links',
+ 'left_table' => 'book',
+ 'left_field' => 'mlid',
+ 'field' => 'mlid',
+ ),
+ );
+
+ $data['book_menu_links']['weight'] = array(
+ 'title' => t('Weight'),
+ 'help' => t('The weight of the book page.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ $data['book_menu_links']['depth'] = array(
+ 'title' => t('Depth'),
+ 'help' => t('The depth of the book page in the hierarchy; top level books have a depth of 1.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument',
+ ),
+ );
+
+ $data['book_menu_links']['p'] = array(
+ 'title' => t('Hierarchy'),
+ 'help' => t('The order of pages in the book hierarchy.'),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_menu_hierarchy',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // book_parent table -- this is an alias of the book table which
+ // represents the parent book.
+
+ // The {book} record for the parent node.
+ $data['book_parent']['table']['group'] = t('Book');
+ $data['book_parent']['table']['join'] = array(
+ 'node' => array(
+ 'table' => 'book',
+ 'left_table' => 'book_menu_links',
+ 'left_field' => 'plid',
+ 'field' => 'mlid',
+ ),
+ );
+
+ $data['book_parent']['nid'] = array(
+ 'title' => t('Parent'),
+ 'help' => t('The parent book node.'),
+ 'relationship' => array(
+ 'base' => 'node',
+ 'base field' => 'nid',
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Book parent'),
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function book_views_plugins() {
+ return array(
+ 'module' => 'views',
+ 'argument default' => array(
+ 'book_root' => array(
+ 'title' => t('Book root from current node'),
+ 'handler' => 'views_plugin_argument_default_book_root'
+ ),
+ ),
+ );
+}
diff --git a/sites/all/modules/views/modules/book/views_plugin_argument_default_book_root.inc b/sites/all/modules/views/modules/book/views_plugin_argument_default_book_root.inc
new file mode 100644
index 000000000..1ce30467f
--- /dev/null
+++ b/sites/all/modules/views/modules/book/views_plugin_argument_default_book_root.inc
@@ -0,0 +1,21 @@
+<?php
+/**
+ * @file
+ * Contains the book root from current node argument default plugin.
+ */
+
+/**
+ * Default argument plugin to get the current node's book root.
+ */
+class views_plugin_argument_default_book_root extends views_plugin_argument_default_node {
+ function get_argument() {
+ // Use the argument_default_node plugin to get the nid argument.
+ $nid = parent::get_argument();
+ if (!empty($nid)) {
+ $node = node_load($nid);
+ if (isset($node->book['bid'])) {
+ return $node->book['bid'];
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/comment.views.inc b/sites/all/modules/views/modules/comment.views.inc
new file mode 100644
index 000000000..65ef18cab
--- /dev/null
+++ b/sites/all/modules/views/modules/comment.views.inc
@@ -0,0 +1,662 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for comment.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+
+function comment_views_data() {
+ $data['comments']['moved to'] = 'comment';
+ $data['comment']['comment']['moved to'] = array('field_data_comment_body', 'comment_body');
+ $data['comment']['comment']['field']['moved to'] = array('field_data_comment_body', 'comment_body');
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['comment']['table']['group'] = t('Comment');
+
+ $data['comment']['table']['base'] = array(
+ 'field' => 'cid',
+ 'title' => t('Comment'),
+ 'help' => t("Comments are responses to node content."),
+ 'access query tag' => 'comment_access',
+ );
+ $data['comment']['table']['entity type'] = 'comment';
+
+ // Provide a "default relationship" to keep older views from choking.
+ $data['comment']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'cid',
+ ),
+ );
+
+ // ----------------------------------------------------------------
+ // Fields
+
+ // subject
+ $data['comment']['subject'] = array(
+ 'title' => t('Title'),
+ 'help' => t('The title of the comment.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_comment',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // cid
+ $data['comment']['cid'] = array(
+ 'title' => t('ID'),
+ 'help' => t('The comment ID of the field'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_comment',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ );
+
+ // name (of comment author)
+ $data['comment']['name'] = array(
+ 'title' => t('Author'),
+ 'help' => t("The name of the comment's author. Can be rendered as a link to the author's homepage."),
+ 'field' => array(
+ 'handler' => 'views_handler_field_comment_username',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // homepage
+ $data['comment']['homepage'] = array(
+ 'title' => t("Author's website"),
+ 'help' => t("The website address of the comment's author. Can be rendered as a link. Will be empty if the author is a registered user."),
+ 'field' => array(
+ 'handler' => 'views_handler_field_url',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // hostname
+ $data['comment']['hostname'] = array(
+ 'title' => t('Hostname'),
+ 'help' => t('Hostname of user that posted the comment.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // mail
+ $data['comment']['mail'] = array(
+ 'title' => t('Mail'),
+ 'help' => t('Email of user that posted the comment. Will be empty if the author is a registered user.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // created (when comment was posted)
+ $data['comment']['created'] = array(
+ 'title' => t('Post date'),
+ 'help' => t('Date and time of when the comment was created.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+
+ // Language field
+ if (module_exists('locale')) {
+ $data['comment']['language'] = array(
+ 'title' => t('Language'),
+ 'help' => t('The language the comment is in.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_locale_language',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_locale_language',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_locale_language',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ }
+
+ $data['comments']['timestamp']['moved to'] = array('comment', 'changed');
+ // changed (when comment was last updated)
+ $data['comment']['changed'] = array(
+ 'title' => t('Updated date'),
+ 'help' => t('Date and time of when the comment was last updated.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ $data['comments']['timestamp_fulldate']['moved to'] = array('comment', 'changed_fulldata');
+ $data['comment']['changed_fulldata'] = array(
+ 'title' => t('Created date'),
+ 'help' => t('Date in the form of CCYYMMDD.'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_fulldate',
+ ),
+ );
+
+ $data['comments']['timestamp_year_month']['moved to'] = array('comment', 'changed_year_month');
+ $data['comment']['changed_year_month'] = array(
+ 'title' => t('Created year + month'),
+ 'help' => t('Date in the form of YYYYMM.'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_year_month',
+ ),
+ );
+
+ $data['comments']['timestamp_year']['moved to'] = array('comment', 'changed_year');
+ $data['comment']['changed_year'] = array(
+ 'title' => t('Created year'),
+ 'help' => t('Date in the form of YYYY.'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_year',
+ ),
+ );
+
+ $data['comments']['timestamp_month']['moved to'] = array('comment', 'changed_month');
+ $data['comment']['changed_month'] = array(
+ 'title' => t('Created month'),
+ 'help' => t('Date in the form of MM (01 - 12).'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_month',
+ ),
+ );
+
+ $data['comments']['timestamp_day']['moved to'] = array('comment', 'changed_day');
+ $data['comment']['changed_day'] = array(
+ 'title' => t('Created day'),
+ 'help' => t('Date in the form of DD (01 - 31).'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_day',
+ ),
+ );
+
+ $data['comments']['timestamp_week']['moved to'] = array('comment', 'changed_week');
+ $data['comment']['changed_week'] = array(
+ 'title' => t('Created week'),
+ 'help' => t('Date in the form of WW (01 - 53).'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_week',
+ ),
+ );
+
+ // status (approved or not)
+ $data['comment']['status'] = array(
+ 'title' => t('Approved'),
+ 'help' => t('Whether the comment is approved (or still in the moderation queue).'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ 'output formats' => array(
+ 'approved-not-approved' => array(t('Approved'), t('Not Approved')),
+ ),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Approved comment'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // link to view comment
+ $data['comment']['view_comment'] = array(
+ 'field' => array(
+ 'title' => t('View link'),
+ 'help' => t('Provide a simple link to view the comment.'),
+ 'handler' => 'views_handler_field_comment_link',
+ ),
+ );
+
+ // link to edit comment
+ $data['comment']['edit_comment'] = array(
+ 'field' => array(
+ 'title' => t('Edit link'),
+ 'help' => t('Provide a simple link to edit the comment.'),
+ 'handler' => 'views_handler_field_comment_link_edit',
+ ),
+ );
+
+ // link to delete comment
+ $data['comment']['delete_comment'] = array(
+ 'field' => array(
+ 'title' => t('Delete link'),
+ 'help' => t('Provide a simple link to delete the comment.'),
+ 'handler' => 'views_handler_field_comment_link_delete',
+ ),
+ );
+
+
+ // link to approve comment
+ $data['comment']['approve_comment'] = array(
+ 'field' => array(
+ 'title' => t('Approve link'),
+ 'help' => t('Provide a simple link to approve the comment.'),
+ 'handler' => 'views_handler_field_comment_link_approve',
+ ),
+ );
+
+ // link to reply to comment
+ $data['comment']['replyto_comment'] = array(
+ 'field' => array(
+ 'title' => t('Reply-to link'),
+ 'help' => t('Provide a simple link to reply to the comment.'),
+ 'handler' => 'views_handler_field_comment_link_reply',
+ ),
+ );
+
+ $data['comment']['thread'] = array(
+ 'field' => array(
+ 'title' => t('Depth'),
+ 'help' => t('Display the depth of the comment if it is threaded.'),
+ 'handler' => 'views_handler_field_comment_depth',
+ ),
+ 'sort' => array(
+ 'title' => t('Thread'),
+ 'help' => t('Sort by the threaded order. This will keep child comments together with their parents.'),
+ 'handler' => 'views_handler_sort_comment_thread',
+ ),
+ );
+
+ $data['comment']['nid'] = array(
+ 'title' => t('Nid'),
+ 'help' => t('The node ID to which the comment is a reply to.'),
+ 'relationship' => array(
+ 'title' => t('Content'),
+ 'help' => t('The content to which the comment is a reply to.'),
+ 'base' => 'node',
+ 'base field' => 'nid',
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Content'),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ ),
+ );
+
+ $data['comment']['uid'] = array(
+ 'title' => t('Author uid'),
+ 'help' => t('If you need more fields than the uid add the comment: author relationship'),
+ 'relationship' => array(
+ 'title' => t('Author'),
+ 'help' => t("The User ID of the comment's author."),
+ 'base' => 'users',
+ 'base field' => 'uid',
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('author'),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ 'field' => array(
+ 'handler' => 'views_handler_field_user',
+ ),
+ );
+
+ $data['comment']['pid'] = array(
+ 'title' => t('Parent CID'),
+ 'help' => t('The Comment ID of the parent comment.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ ),
+ 'relationship' => array(
+ 'title' => t('Parent comment'),
+ 'help' => t('The parent comment.'),
+ 'base' => 'comment',
+ 'base field' => 'cid',
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Parent comment'),
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // node_comment_statistics table
+
+ // define the group
+ $data['node_comment_statistics']['table']['group'] = t('Content');
+
+ // joins
+ $data['node_comment_statistics']['table']['join'] = array(
+ //...to the node table
+ 'node' => array(
+ 'type' => 'INNER',
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ );
+
+ // last_comment_timestamp
+ $data['node_comment_statistics']['last_comment_timestamp'] = array(
+ 'title' => t('Last comment time'),
+ 'help' => t('Date and time of when the last comment was posted.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_last_comment_timestamp',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ // last_comment_name (author's name)
+ $data['node_comment_statistics']['last_comment_name'] = array(
+ 'title' => t("Last comment author"),
+ 'help' => t('The name of the author of the last posted comment.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_ncs_last_comment_name',
+ 'click sortable' => TRUE,
+ 'no group by' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_ncs_last_comment_name',
+ 'no group by' => TRUE,
+ ),
+ );
+
+ // comment_count
+ $data['node_comment_statistics']['comment_count'] = array(
+ 'title' => t('Comment count'),
+ 'help' => t('The number of comments a node has.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument',
+ ),
+ );
+
+ // last_comment_timestamp
+ $data['node_comment_statistics']['last_updated'] = array(
+ 'title' => t('Updated/commented date'),
+ 'help' => t('The most recent of last comment posted or node updated time.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_ncs_last_updated',
+ 'click sortable' => TRUE,
+ 'no group by' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_ncs_last_updated',
+ 'no group by' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_ncs_last_updated',
+ ),
+ );
+
+ $data['node_comment_statistics']['cid'] = array(
+ 'title' => t('Last comment CID'),
+ 'help' => t('Display the last comment of a node'),
+ 'relationship' => array(
+ 'title' => t('Last Comment'),
+ 'help' => t('The last comment of a node.'),
+ 'group' => t('Comment'),
+ 'base' => 'comment',
+ 'base field' => 'cid',
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Last Comment'),
+ ),
+ );
+
+ // last_comment_uid
+ $data['node_comment_statistics']['last_comment_uid'] = array(
+ 'title' => t('Last comment uid'),
+ 'help' => t('The User ID of the author of the last comment of a node.'),
+ 'relationship' => array(
+ 'title' => t('Last comment author'),
+ 'base' => 'users',
+ 'base field' => 'uid',
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Last comment author'),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Use views_data_alter to add items to the node table that are
+ * relevant to comments.
+ */
+function comment_views_data_alter(&$data) {
+ // new comments
+ $data['node']['new_comments'] = array(
+ 'title' => t('New comments'),
+ 'help' => t('The number of new comments on the node.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_node_new_comments',
+ 'no group by' => TRUE,
+ ),
+ );
+
+ $data['node']['comments_link'] = array(
+ 'field' => array(
+ 'title' => t('Add comment link'),
+ 'help' => t('Display the standard add comment link used on regular nodes, which will only display if the viewing user has access to add a comment.'),
+ 'handler' => 'views_handler_field_comment_node_link',
+ ),
+ );
+
+ // Comment status of the node
+ $data['node']['comment'] = array(
+ 'title' => t('Comment status'),
+ 'help' => t('Whether comments are enabled or disabled on the node.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_node_comment',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_node_comment',
+ ),
+ );
+
+ $data['node']['uid_touch'] = array(
+ 'title' => t('User posted or commented'),
+ 'help' => t('Display nodes only if a user posted the node or commented on the node.'),
+ 'argument' => array(
+ 'field' => 'uid',
+ 'name table' => 'users',
+ 'name field' => 'name',
+ 'handler' => 'views_handler_argument_comment_user_uid',
+ 'no group by' => TRUE,
+ ),
+ 'filter' => array(
+ 'field' => 'uid',
+ 'name table' => 'users',
+ 'name field' => 'name',
+ 'handler' => 'views_handler_filter_comment_user_uid'
+ ),
+ );
+
+ $data['node']['cid'] = array(
+ 'title' => t('Comments of the node'),
+ 'help' => t('Relate all comments on the node. This will create 1 duplicate record for every comment. Usually if you need this it is better to create a comment view.'),
+ 'relationship' => array(
+ 'group' => t('Comment'),
+ 'label' => t('Comments'),
+ 'base' => 'comment',
+ 'base field' => 'nid',
+ 'relationship field' => 'nid',
+ 'handler' => 'views_handler_relationship',
+ ),
+ );
+
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function comment_views_plugins() {
+ return array(
+ 'module' => 'views',
+ 'row' => array(
+ 'comment' => array(
+ 'title' => t('Comment'),
+ 'help' => t('Display the comment with standard comment view.'),
+ 'handler' => 'views_plugin_row_comment_view',
+ 'theme' => 'views_view_row_comment',
+ 'path' => drupal_get_path('module', 'views') . '/modules/comment', // not necessary for most modules
+ 'base' => array('comment'), // only works with 'comment' as base.
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-comment',
+ ),
+ 'comment_rss' => array(
+ 'title' => t('Comment'),
+ 'help' => t('Display the comment as RSS.'),
+ 'handler' => 'views_plugin_row_comment_rss',
+ 'theme' => 'views_view_row_rss',
+ 'path' => drupal_get_path('module', 'views') . '/modules/comment', // not necessary for most modules
+ 'base' => array('comment'), // only works with 'comment' as base.
+ 'uses options' => TRUE,
+ 'type' => 'feed',
+ 'help topic' => 'style-comment-rss',
+ ),
+ ),
+ );
+}
+
+/**
+ * Template helper for theme_views_view_row_comment
+ */
+function template_preprocess_views_view_row_comment(&$vars) {
+ $options = $vars['options'];
+ $view = &$vars['view'];
+ $plugin = &$view->style_plugin->row_plugin;
+ $comment = $plugin->comments[$vars['row']->{$vars['field_alias']}];
+ $node = $plugin->nodes[$comment->nid];
+ // Put the view on the node so we can retrieve it in the preprocess.
+ $node->view = &$view;
+
+ $build = comment_view_multiple(array($comment->cid => $comment), $node, $plugin->options['view_mode']);
+ // If we're displaying the comments without links, remove them from the
+ // renderable array. There is no way to avoid building them in the first
+ // place (see comment_build_content()).
+ if (empty($options['links'])) {
+ foreach ($build as $cid => &$comment_build) {
+ if (isset($comment_build['links'])) {
+ unset($comment_build['links']);
+ }
+ }
+ }
+ $vars['comment'] = drupal_render($build);
+}
diff --git a/sites/all/modules/views/modules/comment.views_default.inc b/sites/all/modules/views/modules/comment.views_default.inc
new file mode 100644
index 000000000..d0c4796a6
--- /dev/null
+++ b/sites/all/modules/views/modules/comment.views_default.inc
@@ -0,0 +1,285 @@
+<?php
+
+/**
+ * @file
+ * Bulk export of views_default objects generated by Bulk export module.
+ */
+
+/**
+ * Implementation of hook_views_default_views()
+ */
+function comment_views_default_views() {
+ $views = array();
+
+ $view = new view;
+ $view->name = 'comments_recent';
+ $view->description = 'Contains a block and a page to list recent comments; the block will automatically link to the page, which displays the comment body as well as a link to the node.';
+ $view->tag = 'default';
+ $view->base_table = 'comment';
+ $view->human_name = 'Recent comments';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'Recent comments';
+ $handler->display->display_options['use_more'] = TRUE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access comments';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['pager']['options']['items_per_page'] = 5;
+ $handler->display->display_options['style_plugin'] = 'list';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Relationship: Comment: Content */
+ $handler->display->display_options['relationships']['nid']['id'] = 'nid';
+ $handler->display->display_options['relationships']['nid']['table'] = 'comment';
+ $handler->display->display_options['relationships']['nid']['field'] = 'nid';
+ /* Field: Comment: Title */
+ $handler->display->display_options['fields']['subject']['id'] = 'subject';
+ $handler->display->display_options['fields']['subject']['table'] = 'comment';
+ $handler->display->display_options['fields']['subject']['field'] = 'subject';
+ $handler->display->display_options['fields']['subject']['label'] = '';
+ $handler->display->display_options['fields']['subject']['link_to_comment'] = 1;
+ /* Field: Comment: Updated date */
+ $handler->display->display_options['fields']['timestamp']['id'] = 'timestamp';
+ $handler->display->display_options['fields']['timestamp']['table'] = 'comment';
+ $handler->display->display_options['fields']['timestamp']['field'] = 'changed';
+ $handler->display->display_options['fields']['timestamp']['label'] = '';
+ $handler->display->display_options['fields']['timestamp']['date_format'] = 'time ago';
+ /* Sort criterion: Comment: Updated date */
+ $handler->display->display_options['sorts']['timestamp']['id'] = 'timestamp';
+ $handler->display->display_options['sorts']['timestamp']['table'] = 'comment';
+ $handler->display->display_options['sorts']['timestamp']['field'] = 'changed';
+ $handler->display->display_options['sorts']['timestamp']['order'] = 'DESC';
+ /* Filter criterion: Content: Published or admin */
+ $handler->display->display_options['filters']['status_extra']['id'] = 'status_extra';
+ $handler->display->display_options['filters']['status_extra']['table'] = 'node';
+ $handler->display->display_options['filters']['status_extra']['field'] = 'status_extra';
+ $handler->display->display_options['filters']['status_extra']['relationship'] = 'nid';
+ $handler->display->display_options['filters']['status_extra']['group'] = 0;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'list';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'fields';
+ $handler->display->display_options['row_options']['inline'] = array(
+ 'title' => 'title',
+ 'timestamp' => 'timestamp',
+ );
+ $handler->display->display_options['row_options']['separator'] = '&nbsp;';
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['defaults']['fields'] = FALSE;
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['relationship'] = 'nid';
+ $handler->display->display_options['fields']['title']['label'] = 'Reply to';
+ $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+ /* Field: Comment: Updated date */
+ $handler->display->display_options['fields']['timestamp']['id'] = 'timestamp';
+ $handler->display->display_options['fields']['timestamp']['table'] = 'comment';
+ $handler->display->display_options['fields']['timestamp']['field'] = 'changed';
+ $handler->display->display_options['fields']['timestamp']['label'] = '';
+ $handler->display->display_options['fields']['timestamp']['date_format'] = 'time ago';
+ /* Field: Comment: Title */
+ $handler->display->display_options['fields']['subject']['id'] = 'subject';
+ $handler->display->display_options['fields']['subject']['table'] = 'comment';
+ $handler->display->display_options['fields']['subject']['field'] = 'subject';
+ $handler->display->display_options['fields']['subject']['label'] = '';
+ $handler->display->display_options['fields']['subject']['link_to_comment'] = 1;
+ /* Field: Comment: Comment */
+ $handler->display->display_options['fields']['comment']['id'] = 'comment';
+ $handler->display->display_options['fields']['comment']['table'] = 'field_data_comment_body';
+ $handler->display->display_options['fields']['comment']['field'] = 'comment_body';
+ $handler->display->display_options['fields']['comment']['label'] = '';
+ $handler->display->display_options['path'] = 'comments/recent';
+
+ /* Display: Block */
+ $handler = $view->new_display('block', 'Block', 'block');
+ $translatables['comments_recent'] = array(
+ t('Master'),
+ t('Recent comments'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Content'),
+ t('Page'),
+ t('Reply to'),
+ t('Block'),
+ );
+
+ $views['comments_recent'] = $view;
+
+ $view = new view;
+ $view->name = 'tracker';
+ $view->description = 'Shows all new activity on system.';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Tracker';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'Recent posts';
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access content';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '25';
+ $handler->display->display_options['style_plugin'] = 'table';
+ $handler->display->display_options['style_options']['columns'] = array(
+ 'type' => 'type',
+ 'title' => 'title',
+ 'name' => 'name',
+ 'comment_count' => 'comment_count',
+ 'last_comment_timestamp' => 'last_comment_timestamp',
+ 'timestamp' => 'title',
+ 'new_comments' => 'comment_count',
+ );
+ $handler->display->display_options['style_options']['default'] = 'last_comment_timestamp';
+ $handler->display->display_options['style_options']['info'] = array(
+ 'type' => array(
+ 'sortable' => 1,
+ 'separator' => '',
+ ),
+ 'title' => array(
+ 'sortable' => 1,
+ 'separator' => '&nbsp;',
+ ),
+ 'name' => array(
+ 'sortable' => 1,
+ 'separator' => '',
+ ),
+ 'comment_count' => array(
+ 'sortable' => 1,
+ 'separator' => '<br />',
+ ),
+ 'last_comment_timestamp' => array(
+ 'sortable' => 1,
+ 'separator' => '&nbsp;',
+ ),
+ 'timestamp' => array(
+ 'separator' => '',
+ ),
+ 'new_comments' => array(
+ 'separator' => '',
+ ),
+ );
+ $handler->display->display_options['style_options']['override'] = 1;
+ $handler->display->display_options['style_options']['order'] = 'desc';
+ /* Relationship: Content: Author */
+ $handler->display->display_options['relationships']['uid']['id'] = 'uid';
+ $handler->display->display_options['relationships']['uid']['table'] = 'node';
+ $handler->display->display_options['relationships']['uid']['field'] = 'uid';
+ /* Field: Content: Type */
+ $handler->display->display_options['fields']['type']['id'] = 'type';
+ $handler->display->display_options['fields']['type']['table'] = 'node';
+ $handler->display->display_options['fields']['type']['field'] = 'type';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ /* Field: User: Name */
+ $handler->display->display_options['fields']['name']['id'] = 'name';
+ $handler->display->display_options['fields']['name']['table'] = 'users';
+ $handler->display->display_options['fields']['name']['field'] = 'name';
+ $handler->display->display_options['fields']['name']['relationship'] = 'uid';
+ $handler->display->display_options['fields']['name']['label'] = 'Author';
+ /* Field: Content: Comment count */
+ $handler->display->display_options['fields']['comment_count']['id'] = 'comment_count';
+ $handler->display->display_options['fields']['comment_count']['table'] = 'node_comment_statistics';
+ $handler->display->display_options['fields']['comment_count']['field'] = 'comment_count';
+ $handler->display->display_options['fields']['comment_count']['label'] = 'Replies';
+ /* Field: Content: Last comment time */
+ $handler->display->display_options['fields']['last_comment_timestamp']['id'] = 'last_comment_timestamp';
+ $handler->display->display_options['fields']['last_comment_timestamp']['table'] = 'node_comment_statistics';
+ $handler->display->display_options['fields']['last_comment_timestamp']['field'] = 'last_comment_timestamp';
+ $handler->display->display_options['fields']['last_comment_timestamp']['label'] = 'Last Post';
+ /* Field: Content: Has new content */
+ $handler->display->display_options['fields']['timestamp']['id'] = 'timestamp';
+ $handler->display->display_options['fields']['timestamp']['table'] = 'history';
+ $handler->display->display_options['fields']['timestamp']['field'] = 'timestamp';
+ $handler->display->display_options['fields']['timestamp']['label'] = '';
+ $handler->display->display_options['fields']['timestamp']['link_to_node'] = 0;
+ $handler->display->display_options['fields']['timestamp']['comments'] = 1;
+ /* Field: Content: New comments */
+ $handler->display->display_options['fields']['new_comments']['id'] = 'new_comments';
+ $handler->display->display_options['fields']['new_comments']['table'] = 'node';
+ $handler->display->display_options['fields']['new_comments']['field'] = 'new_comments';
+ $handler->display->display_options['fields']['new_comments']['label'] = '';
+ $handler->display->display_options['fields']['new_comments']['hide_empty'] = TRUE;
+ $handler->display->display_options['fields']['new_comments']['suffix'] = ' new';
+ $handler->display->display_options['fields']['new_comments']['link_to_comment'] = 1;
+ /* Sort criterion: Content: Last comment time */
+ $handler->display->display_options['sorts']['last_comment_timestamp']['id'] = 'last_comment_timestamp';
+ $handler->display->display_options['sorts']['last_comment_timestamp']['table'] = 'node_comment_statistics';
+ $handler->display->display_options['sorts']['last_comment_timestamp']['field'] = 'last_comment_timestamp';
+ /* Contextual filter: Content: User posted or commented */
+ $handler->display->display_options['arguments']['uid_touch']['id'] = 'uid_touch';
+ $handler->display->display_options['arguments']['uid_touch']['table'] = 'node';
+ $handler->display->display_options['arguments']['uid_touch']['field'] = 'uid_touch';
+ $handler->display->display_options['arguments']['uid_touch']['exception']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['uid_touch']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['uid_touch']['title'] = 'Recent posts for %1';
+ $handler->display->display_options['arguments']['uid_touch']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['uid_touch']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['uid_touch']['specify_validation'] = 1;
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = '1';
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'tracker';
+ $handler->display->display_options['menu']['type'] = 'normal';
+ $handler->display->display_options['menu']['title'] = 'Recent posts';
+ $translatables['tracker'] = array(
+ t('Master'),
+ t('Recent posts'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('Type'),
+ t('Title'),
+ t('Author'),
+ t('Replies'),
+ t('.'),
+ t(','),
+ t('Last Post'),
+ t(' new'),
+ t('All'),
+ t('Recent posts for %1'),
+ t('Page'),
+ );
+
+ $views['tracker'] = $view;
+
+ return $views;
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_argument_comment_user_uid.inc b/sites/all/modules/views/modules/comment/views_handler_argument_comment_user_uid.inc
new file mode 100644
index 000000000..d821f32c3
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_argument_comment_user_uid.inc
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_comment_user_uid.
+ */
+
+/**
+ * Argument handler to accept a user id to check for nodes that
+ * user posted or commented on.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_comment_user_uid extends views_handler_argument {
+ function title() {
+ if (!$this->argument) {
+ $title = variable_get('anonymous', t('Anonymous'));
+ }
+ else {
+ $title = db_query('SELECT u.name FROM {users} u WHERE u.uid = :uid', array(':uid' => $this->argument))->fetchField();
+ }
+ if (empty($title)) {
+ return t('No user');
+ }
+
+ return check_plain($title);
+ }
+
+ function default_actions($which = NULL) {
+ // Disallow summary views on this argument.
+ if (!$which) {
+ $actions = parent::default_actions();
+ unset($actions['summary asc']);
+ unset($actions['summary desc']);
+ return $actions;
+ }
+
+ if ($which != 'summary asc' && $which != 'summary desc') {
+ return parent::default_actions($which);
+ }
+ }
+
+ function query($group_by = FALSE) {
+ $this->ensure_my_table();
+
+ $subselect = db_select('comment', 'c');
+ $subselect->addField('c', 'cid');
+ $subselect->condition('c.uid', $this->argument);
+ $subselect->where("c.nid = $this->table_alias.nid");
+
+ $condition = db_or()
+ ->condition("$this->table_alias.uid", $this->argument, '=')
+ ->exists($subselect);
+
+ $this->query->add_where(0, $condition);
+ }
+
+ function get_sort_name() {
+ return t('Numerical', array(), array('context' => 'Sort order'));
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment.inc
new file mode 100644
index 000000000..7ca3256fa
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment.inc
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment.
+ */
+
+/**
+ * Field handler to allow linking to a comment.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment extends views_handler_field {
+ /**
+ * Override init function to provide generic option to link to comment.
+ */
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ if (!empty($this->options['link_to_comment'])) {
+ $this->additional_fields['cid'] = 'cid';
+ $this->additional_fields['nid'] = 'nid';
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_comment'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['link_to_node'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Provide link-to-comment option
+ */
+ function options_form(&$form, &$form_state) {
+ $form['link_to_comment'] = array(
+ '#title' => t('Link this field to its comment'),
+ '#description' => t("Enable to override this field's links."),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['link_to_comment'],
+ );
+ $form['link_to_node'] = array(
+ '#title' => t('Link field to the node if there is no comment.'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['link_to_node'],
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render_link($data, $values) {
+ if (!empty($this->options['link_to_comment'])) {
+ $this->options['alter']['make_link'] = TRUE;
+ $nid = $this->get_value($values, 'nid');
+ $cid = $this->get_value($values, 'cid');
+ if (!empty($cid)) {
+ $this->options['alter']['path'] = "comment/" . $cid;
+ $this->options['alter']['fragment'] = "comment-" . $cid;
+ }
+ // If there is no comment link to the node.
+ else if ($this->options['link_to_node']) {
+ $this->options['alter']['path'] = "node/" . $nid;
+ }
+ }
+
+ return $data;
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment_depth.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment_depth.inc
new file mode 100644
index 000000000..4840a1e5c
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment_depth.inc
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment_depth.
+ */
+
+/**
+ * Field handler to display the depth of a comment.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment_depth extends views_handler_field {
+ /**
+ * Work out the depth of this comment
+ */
+ function render($values) {
+ $comment_thread = $this->get_value($values);
+ return count(explode('.', $comment_thread)) - 1;
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment_link.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment_link.inc
new file mode 100644
index 000000000..162924e16
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment_link.inc
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment_link.
+ */
+
+/**
+ * Base field handler to present a link.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment_link extends views_handler_field_entity {
+ function construct() {
+ parent::construct();
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['text'] = array('default' => '', 'translatable' => TRUE);
+ $options['link_to_node'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text to display'),
+ '#default_value' => $this->options['text'],
+ );
+ $form['link_to_node'] = array(
+ '#title' => t('Link field to the node if there is no comment.'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['link_to_node'],
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ }
+
+ function render($values) {
+ $value = $this->get_value($values, 'cid');
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+
+ function render_link($data, $values) {
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('view');
+ $comment = $this->get_value($values);
+ $nid = $comment->nid;
+ $cid = $comment->cid;
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['html'] = TRUE;
+
+ if (!empty($cid)) {
+ $this->options['alter']['path'] = "comment/" . $cid;
+ $this->options['alter']['fragment'] = "comment-" . $cid;
+ }
+ // If there is no comment link to the node.
+ else if ($this->options['link_to_node']) {
+ $this->options['alter']['path'] = "node/" . $nid;
+ }
+
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment_link_approve.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment_link_approve.inc
new file mode 100644
index 000000000..0953d0c8d
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment_link_approve.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment_link_approve.
+ */
+
+/**
+ * Provides a comment approve link.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment_link_approve extends views_handler_field_comment_link {
+ function access() {
+ //needs permission to administer comments in general
+ return user_access('administer comments');
+ }
+
+ function render_link($data, $values) {
+ $status = $this->get_value($values, 'status');
+
+ // Don't show an approve link on published nodes.
+ if ($status == COMMENT_PUBLISHED) {
+ return;
+ }
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('approve');
+ $cid = $this->get_value($values, 'cid');
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "comment/" . $cid . "/approve";
+ $this->options['alter']['query'] = drupal_get_destination() + array('token' => drupal_get_token("comment/$cid/approve"));
+
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment_link_delete.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment_link_delete.inc
new file mode 100644
index 000000000..c55ac1cf4
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment_link_delete.inc
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment_link_delete.
+ */
+
+/**
+ * Field handler to present a link to delete a node.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment_link_delete extends views_handler_field_comment_link {
+ function access() {
+ //needs permission to administer comments in general
+ return user_access('administer comments');
+ }
+
+ function render_link($data, $values) {
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
+ $cid = $this->get_value($values, 'cid');
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "comment/" . $cid . "/delete";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment_link_edit.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment_link_edit.inc
new file mode 100644
index 000000000..0b06c0e94
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment_link_edit.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment_link_edit.
+ */
+
+/**
+ * Field handler to present a link node edit.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment_link_edit extends views_handler_field_comment_link {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['destination'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['destination'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use destination'),
+ '#description' => t('Add destination to the link'),
+ '#default_value' => $this->options['destination'],
+ '#fieldset' => 'more',
+ );
+ }
+
+ function render_link($data, $values) {
+ parent::render_link($data, $values);
+ // ensure user has access to edit this comment.
+ $comment = $this->get_value($values);
+ if (!comment_access('edit', $comment)) {
+ return;
+ }
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('edit');
+ unset($this->options['alter']['fragment']);
+
+ if (!empty($this->options['destination'])) {
+ $this->options['alter']['query'] = drupal_get_destination();
+ }
+
+ $this->options['alter']['path'] = "comment/" . $comment->cid . "/edit";
+
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment_link_reply.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment_link_reply.inc
new file mode 100644
index 000000000..47d0f1703
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment_link_reply.inc
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment_link_reply.
+ */
+
+/**
+ * Field handler to present a link to delete a node.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment_link_reply extends views_handler_field_comment_link {
+ function access() {
+ //check for permission to reply to comments
+ return user_access('post comments');
+ }
+
+ function render_link($data, $values) {
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('reply');
+ $nid = $this->get_value($values, 'nid');
+ $cid = $this->get_value($values, 'cid');
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "comment/reply/" . $nid . '/' . $cid;
+
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment_node_link.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment_node_link.inc
new file mode 100644
index 000000000..7feecfba9
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment_node_link.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment_node_link.
+ */
+
+/**
+ * Handler for showing comment module's node link.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment_node_link extends views_handler_field_entity {
+ function construct() {
+ parent::construct();
+
+ // Add the node fields that comment_link will need..
+ $this->additional_fields['nid'] = array(
+ 'field' => 'nid',
+ );
+ $this->additional_fields['type'] = array(
+ 'field' => 'type',
+ );
+ $this->additional_fields['comment'] = array(
+ 'field' => 'comment',
+ );
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['teaser'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['teaser'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show teaser-style link'),
+ '#default_value' => $this->options['teaser'],
+ '#description' => t('Show the comment link in the form used on standard node teasers, rather than the full node form.'),
+ );
+
+ parent::options_form($form, $form_state);
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ }
+
+ function render($values) {
+ // Build fake $node.
+ $node = $this->get_value($values);
+
+ // Call comment.module's hook_link: comment_link($type, $node = NULL, $teaser = FALSE)
+ // Call node by reference so that something is changed here
+ comment_node_view($node, $this->options['teaser'] ? 'teaser' : 'full');
+ // question: should we run these through: drupal_alter('link', $links, $node);
+ // might this have unexpected consequences if these hooks expect items in $node that we don't have?
+
+ // Only render the links, if they are defined.
+ return !empty($node->content['links']['comment']) ? drupal_render($node->content['links']['comment']) : '';
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_comment_username.inc b/sites/all/modules/views/modules/comment/views_handler_field_comment_username.inc
new file mode 100644
index 000000000..887a74e33
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_comment_username.inc
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_comment_username.
+ */
+
+/**
+ * Field handler to allow linking to a user account or homepage.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_comment_username extends views_handler_field {
+ /**
+ * Override init function to add uid and homepage fields.
+ */
+ function init(&$view, &$data) {
+ parent::init($view, $data);
+ $this->additional_fields['uid'] = 'uid';
+ $this->additional_fields['homepage'] = 'homepage';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_user'] = array('default' => TRUE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['link_to_user'] = array(
+ '#title' => t("Link this field to its user or an author's homepage"),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['link_to_user'],
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render_link($data, $values) {
+ if (!empty($this->options['link_to_user'])) {
+ $account = new stdClass();
+ $account->uid = $this->get_value($values, 'uid');
+ $account->name = $this->get_value($values);
+ $account->homepage = $this->get_value($values, 'homepage');
+
+ return theme('username', array(
+ 'account' => $account
+ ));
+ }
+ else {
+ return $data;
+ }
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_last_comment_timestamp.inc b/sites/all/modules/views/modules/comment/views_handler_field_last_comment_timestamp.inc
new file mode 100644
index 000000000..e7cf8bdef
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_last_comment_timestamp.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_last_comment_timestamp.
+ */
+
+/**
+ * Field handler to display the timestamp of a comment with the count of comments.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_last_comment_timestamp extends views_handler_field_date {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['comment_count'] = 'comment_count';
+ }
+
+ function render($values) {
+ $comment_count = $this->get_value($values, 'comment_count');
+ if (empty($this->options['empty_zero']) || $comment_count) {
+ return parent::render($values);
+ }
+ else {
+ return NULL;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_ncs_last_comment_name.inc b/sites/all/modules/views/modules/comment/views_handler_field_ncs_last_comment_name.inc
new file mode 100644
index 000000000..c9c6885ef
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_ncs_last_comment_name.inc
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_ncs_last_comment_name.
+ */
+
+/**
+ * Field handler to present the name of the last comment poster.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_ncs_last_comment_name extends views_handler_field {
+ function query() {
+ // last_comment_name only contains data if the user is anonymous. So we
+ // have to join in a specially related user table.
+ $this->ensure_my_table();
+ // join 'users' to this table via vid
+ $join = new views_join();
+ $join->construct('users', $this->table_alias, 'last_comment_uid', 'uid');
+ $join->extra = array(array('field' => 'uid', 'operator' => '!=', 'value' => '0'));
+
+ // ncs_user alias so this can work with the sort handler, below.
+// $this->user_table = $this->query->add_relationship(NULL, $join, 'users', $this->relationship);
+ $this->user_table = $this->query->ensure_table('ncs_users', $this->relationship, $join);
+
+ $this->field_alias = $this->query->add_field(NULL, "COALESCE($this->user_table.name, $this->table_alias.$this->field)", $this->table_alias . '_' . $this->field);
+
+ $this->user_field = $this->query->add_field($this->user_table, 'name');
+ $this->uid = $this->query->add_field($this->table_alias, 'last_comment_uid');
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['link_to_user'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function render($values) {
+ if (!empty($this->options['link_to_user'])) {
+ $account = new stdClass();
+ $account->name = $this->get_value($values);
+ $account->uid = $values->{$this->uid};
+ return theme('username', array(
+ 'account' => $account
+ ));
+ }
+ else {
+ return $this->sanitize_value($this->get_value($values));
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_ncs_last_updated.inc b/sites/all/modules/views/modules/comment/views_handler_field_ncs_last_updated.inc
new file mode 100644
index 000000000..d1d730605
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_ncs_last_updated.inc
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_ncs_last_updated.
+ */
+/**
+ * Field handler to display the newer of last comment / node updated.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_ncs_last_updated extends views_handler_field_date {
+ function query() {
+ $this->ensure_my_table();
+ $this->node_table = $this->query->ensure_table('node', $this->relationship);
+ $this->field_alias = $this->query->add_field(NULL, "GREATEST(" . $this->node_table . ".changed, " . $this->table_alias . ".last_comment_timestamp)", $this->table_alias . '_' . $this->field);
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_node_comment.inc b/sites/all/modules/views/modules/comment/views_handler_field_node_comment.inc
new file mode 100644
index 000000000..d863c4477
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_node_comment.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_comment.
+ */
+
+/**
+ * Display node comment status.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_comment extends views_handler_field {
+ function render($values) {
+ $value = $this->get_value($values);
+ switch ($value) {
+ case COMMENT_NODE_HIDDEN:
+ default:
+ return t('Hidden');
+ case COMMENT_NODE_CLOSED:
+ return t('Closed');
+ case COMMENT_NODE_OPEN:
+ return t('Open');
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_field_node_new_comments.inc b/sites/all/modules/views/modules/comment/views_handler_field_node_new_comments.inc
new file mode 100644
index 000000000..70b0581f9
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_field_node_new_comments.inc
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_new_comments.
+ */
+
+/**
+ * Field handler to display the number of new comments.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_new_comments extends views_handler_field_numeric {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ // translate an older setting:
+ if (!empty($options['no_empty'])) {
+ $this->options['hide_empty'] = TRUE;
+ unset($this->options['no_empty']);
+ }
+ }
+
+ function construct() {
+ parent::construct();
+ $this->additional_fields['nid'] = 'nid';
+ $this->additional_fields['type'] = 'type';
+ $this->additional_fields['comment_count'] = array('table' => 'node_comment_statistics', 'field' => 'comment_count');
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['link_to_comment'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['link_to_comment'] = array(
+ '#title' => t('Link this field to new comments'),
+ '#description' => t("Enable to override this field's links."),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['link_to_comment'],
+ );
+
+ parent::options_form($form, $form_state);
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ $this->field_alias = $this->table . '_' . $this->field;
+ }
+
+ function pre_render(&$values) {
+ global $user;
+ if (!$user->uid || empty($values)) {
+ return;
+ }
+
+ $nids = array();
+ $ids = array();
+ foreach ($values as $id => $result) {
+ $nids[] = $result->{$this->aliases['nid']};
+ $values[$id]->{$this->field_alias} = 0;
+ // Create a reference so we can find this record in the values again.
+ if (empty($ids[$result->{$this->aliases['nid']}])) {
+ $ids[$result->{$this->aliases['nid']}] = array();
+ }
+ $ids[$result->{$this->aliases['nid']}][] = $id;
+ }
+
+ if ($nids) {
+ $result = db_query("SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comment} c ON n.nid = c.nid
+ LEFT JOIN {history} h ON h.nid = n.nid AND h.uid = :h_uid WHERE n.nid IN (:nids)
+ AND c.changed > GREATEST(COALESCE(h.timestamp, :timestamp), :timestamp) AND c.status = :status GROUP BY n.nid ", array(
+ ':status' => COMMENT_PUBLISHED,
+ ':h_uid' => $user->uid,
+ ':nids' => $nids,
+ ':timestamp' => NODE_NEW_LIMIT,
+ ));
+
+ foreach ($result as $node) {
+ foreach ($ids[$node->nid] as $id) {
+ $values[$id]->{$this->field_alias} = $node->num_comments;
+ }
+ }
+ }
+ }
+
+ function render_link($data, $values) {
+ if (!empty($this->options['link_to_comment']) && $data !== NULL && $data !== '') {
+ $node = new stdClass();
+ $node->nid = $this->get_value($values, 'nid');
+ $node->type = $this->get_value($values, 'type');
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = 'node/' . $node->nid;
+ $this->options['alter']['query'] = comment_new_page_count($this->get_value($values, 'comment_count'), $this->get_value($values), $node);
+ $this->options['alter']['fragment'] = 'new';
+ }
+
+ return $data;
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ if (!empty($value)) {
+ return $this->render_link(parent::render($values), $values);
+ }
+ else {
+ $this->options['alter']['make_link'] = FALSE;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_filter_comment_user_uid.inc b/sites/all/modules/views/modules/comment/views_handler_filter_comment_user_uid.inc
new file mode 100644
index 000000000..e76ebb79c
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_filter_comment_user_uid.inc
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_comment_user_uid.
+ */
+
+/**
+ * Filter handler to accept a user id to check for nodes that user posted or
+ * commented on.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_comment_user_uid extends views_handler_filter_user_name {
+ function query() {
+ $this->ensure_my_table();
+
+ $subselect = db_select('comment', 'c');
+ $subselect->addField('c', 'cid');
+ $subselect->condition('c.uid', $this->value, $this->operator);
+ $subselect->where("c.nid = $this->table_alias.nid");
+
+ $condition = db_or()
+ ->condition("$this->table_alias.uid", $this->value, $this->operator)
+ ->exists($subselect);
+
+ $this->query->add_where($this->options['group'], $condition);
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_filter_ncs_last_updated.inc b/sites/all/modules/views/modules/comment/views_handler_filter_ncs_last_updated.inc
new file mode 100644
index 000000000..2319edffd
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_filter_ncs_last_updated.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_ncs_last_updated.
+ */
+
+/**
+ * Filter handler for the newer of last comment / node updated.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_ncs_last_updated extends views_handler_filter_date {
+ function query() {
+ $this->ensure_my_table();
+ $this->node_table = $this->query->ensure_table('node', $this->relationship);
+
+ $field = "GREATEST(" . $this->node_table . ".changed, " . $this->table_alias . ".last_comment_timestamp)";
+
+ $info = $this->operators();
+ if (!empty($info[$this->operator]['method'])) {
+ $this->{$info[$this->operator]['method']}($field);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_filter_node_comment.inc b/sites/all/modules/views/modules/comment/views_handler_filter_node_comment.inc
new file mode 100644
index 000000000..befce107b
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_filter_node_comment.inc
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_node_comment.
+ */
+
+/**
+ * Filter based on comment node status.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_node_comment extends views_handler_filter_in_operator {
+ function get_value_options() {
+ $this->value_options = array(
+ COMMENT_NODE_HIDDEN => t('Hidden'),
+ COMMENT_NODE_CLOSED => t('Closed'),
+ COMMENT_NODE_OPEN => t('Open'),
+ );
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_sort_comment_thread.inc b/sites/all/modules/views/modules/comment/views_handler_sort_comment_thread.inc
new file mode 100644
index 000000000..e513a93e8
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_sort_comment_thread.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_sort_comment_thread.
+ */
+
+/**
+ * Sort handler for ordering by thread.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_comment_thread extends views_handler_sort {
+ function query() {
+ $this->ensure_my_table();
+
+ //Read comment_render() in comment.module for an explanation of the
+ //thinking behind this sort.
+ if ($this->options['order'] == 'DESC') {
+ $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']);
+ }
+ else {
+ $alias = $this->table_alias . '_' . $this->real_field . 'asc';
+ //@todo is this secure?
+ $this->query->add_orderby(NULL, "SUBSTRING({$this->table_alias}.{$this->real_field}, 1, (LENGTH({$this->table_alias}.{$this->real_field}) - 1))", $this->options['order'], $alias);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_sort_ncs_last_comment_name.inc b/sites/all/modules/views/modules/comment/views_handler_sort_ncs_last_comment_name.inc
new file mode 100644
index 000000000..613045a11
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_sort_ncs_last_comment_name.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_sort_ncs_last_comment_name.
+ */
+
+/**
+ * Sort handler to sort by last comment name which might be in 2 different
+ * fields.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_ncs_last_comment_name extends views_handler_sort {
+ function query() {
+ $this->ensure_my_table();
+ $join = new views_join();
+ $join->construct('users', $this->table_alias, 'last_comment_uid', 'uid');
+
+ // @todo this might be safer if we had an ensure_relationship rather than guessing
+ // the table alias. Though if we did that we'd be guessing the relationship name
+ // so that doesn't matter that much.
+// $this->user_table = $this->query->add_relationship(NULL, $join, 'users', $this->relationship);
+ $this->user_table = $this->query->ensure_table('ncs_users', $this->relationship, $join);
+ $this->user_field = $this->query->add_field($this->user_table, 'name');
+
+ // Add the field.
+ $this->query->add_orderby(NULL, "LOWER(COALESCE($this->user_table.name, $this->table_alias.$this->field))", $this->options['order'], $this->table_alias . '_' . $this->field);
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_handler_sort_ncs_last_updated.inc b/sites/all/modules/views/modules/comment/views_handler_sort_ncs_last_updated.inc
new file mode 100644
index 000000000..83f0f5474
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_handler_sort_ncs_last_updated.inc
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_sort_ncs_last_updated.
+ */
+
+/**
+ * Sort handler for the newer of last comment / node updated.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_ncs_last_updated extends views_handler_sort_date {
+ function query() {
+ $this->ensure_my_table();
+ $this->node_table = $this->query->ensure_table('node', $this->relationship);
+ $this->field_alias = $this->query->add_orderby(NULL, "GREATEST(" . $this->node_table . ".changed, " . $this->table_alias . ".last_comment_timestamp)", $this->options['order'], $this->table_alias . '_' . $this->field);
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_plugin_row_comment_rss.inc b/sites/all/modules/views/modules/comment/views_plugin_row_comment_rss.inc
new file mode 100644
index 000000000..e349590d9
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_plugin_row_comment_rss.inc
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Contains the comment RSS row style plugin.
+ */
+
+/**
+ * Plugin which formats the comments as RSS items.
+ */
+class views_plugin_row_comment_rss extends views_plugin_row {
+ var $base_table = 'comment';
+ var $base_field = 'cid';
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['item_length'] = array('default' => 'default');
+ $options['links'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['item_length'] = array(
+ '#type' => 'select',
+ '#title' => t('Display type'),
+ '#options' => $this->options_form_summary_options(),
+ '#default_value' => $this->options['item_length'],
+ );
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display links'),
+ '#default_value' => $this->options['links'],
+ );
+ }
+
+
+ function pre_render($result) {
+ $cids = array();
+ $nids = array();
+
+ foreach ($result as $row) {
+ $cids[] = $row->cid;
+ }
+
+ $this->comments = comment_load_multiple($cids);
+ foreach ($this->comments as &$comment) {
+ $comment->depth = count(explode('.', $comment->thread)) - 1;
+ $nids[] = $comment->nid;
+ }
+
+ $this->nodes = node_load_multiple($nids);
+ }
+
+ /**
+ * Return the main options, which are shown in the summary title
+ *
+ * @see views_plugin_row_node_rss::options_form_summary_options()
+ * @todo: Maybe provide a views_plugin_row_rss_entity and reuse this method
+ * in views_plugin_row_comment|node_rss.inc
+ */
+ function options_form_summary_options() {
+ $entity_info = entity_get_info('node');
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+ $options['title'] = t('Title only');
+ $options['default'] = t('Use site default RSS settings');
+ return $options;
+ }
+
+
+ function render($row) {
+ global $base_url;
+
+ $cid = $row->{$this->field_alias};
+ if (!is_numeric($cid)) {
+ return;
+ }
+
+ $item_length = $this->options['item_length'];
+ if ($item_length == 'default') {
+ $item_length = variable_get('feed_item_length', 'teaser');
+ }
+
+ // Load the specified comment and its associated node:
+ $comment = $this->comments[$cid];
+ if (empty($comment) || empty($this->nodes[$comment->nid])) {
+ return;
+ }
+
+ $item_text = '';
+
+ $uri = entity_uri('comment', $comment);
+ $comment->link = url($uri['path'], $uri['options'] + array('absolute' => TRUE));
+ $comment->rss_namespaces = array();
+ $comment->rss_elements = array(
+ array(
+ 'key' => 'pubDate',
+ 'value' => gmdate('r', $comment->created),
+ ),
+ array(
+ 'key' => 'dc:creator',
+ 'value' => format_username($comment),
+ ),
+ array(
+ 'key' => 'guid',
+ 'value' => 'comment ' . $comment->cid . ' at ' . $base_url,
+ 'attributes' => array('isPermaLink' => 'false'),
+ ),
+ );
+
+ // The comment gets built and modules add to or modify
+ // $comment->rss_elements and $comment->rss_namespaces.
+ $build = comment_view($comment, $this->nodes[$comment->nid], 'rss');
+ unset($build['#theme']);
+
+ if (!empty($comment->rss_namespaces)) {
+ $this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $comment->rss_namespaces);
+ }
+
+ // Hide the links if desired.
+ if (!$this->options['links']) {
+ hide($build['links']);
+ }
+
+ if ($item_length != 'title') {
+ // We render comment contents and force links to be last.
+ $build['links']['#weight'] = 1000;
+ $item_text .= drupal_render($build);
+ }
+
+ $item = new stdClass();
+ $item->description = $item_text;
+ $item->title = $comment->subject;
+ $item->link = $comment->link;
+ $item->elements = $comment->rss_elements;
+ $item->cid = $comment->cid;
+
+ return theme($this->theme_functions(), array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'row' => $item
+ ));
+ }
+}
diff --git a/sites/all/modules/views/modules/comment/views_plugin_row_comment_view.inc b/sites/all/modules/views/modules/comment/views_plugin_row_comment_view.inc
new file mode 100644
index 000000000..f78fa3667
--- /dev/null
+++ b/sites/all/modules/views/modules/comment/views_plugin_row_comment_view.inc
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Contains the node RSS row style plugin.
+ */
+
+/**
+ * Plugin which performs a comment_view on the resulting object.
+ */
+class views_plugin_row_comment_view extends views_plugin_row {
+ var $base_field = 'cid';
+ var $base_table = 'comment';
+
+ /**
+ * Stores all comments which are preloaded.
+ */
+ var $comments = array();
+
+ /**
+ * Stores all nodes of all comments which are preloaded.
+ */
+ var $nodes = array();
+
+ function summary_title() {
+ return t('Settings');
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['links'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['view_mode'] = array('default' => 'full');
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $options = $this->options_form_summary_options();
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $this->options['view_mode'],
+ );
+
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display links'),
+ '#default_value' => $this->options['links'],
+ );
+ }
+
+
+ /**
+ * Return the main options, which are shown in the summary title.
+ */
+ function options_form_summary_options() {
+ $entity_info = entity_get_info('comment');
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+ if (empty($options)) {
+ $options = array(
+ 'full' => t('Full content')
+ );
+ }
+
+ return $options;
+ }
+
+ function pre_render($result) {
+ $cids = array();
+
+ foreach ($result as $row) {
+ $cids[] = $row->cid;
+ }
+
+ // Load all comments.
+ $cresult = comment_load_multiple($cids);
+ $nids = array();
+ foreach ($cresult as $comment) {
+ $comment->depth = count(explode('.', $comment->thread)) - 1;
+ $this->comments[$comment->cid] = $comment;
+ $nids[] = $comment->nid;
+ }
+
+ // Load all nodes of the comments.
+ $nodes = node_load_multiple(array_unique($nids));
+ foreach ($nodes as $node) {
+ $this->nodes[$node->nid] = $node;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/contact.views.inc b/sites/all/modules/views/modules/contact.views.inc
new file mode 100644
index 000000000..412d824db
--- /dev/null
+++ b/sites/all/modules/views/modules/contact.views.inc
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for contact.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function contact_views_data_alter(&$data) {
+ $data['users']['contact'] = array(
+ 'field' => array(
+ 'title' => t('Link to contact page'),
+ 'help' => t('Provide a simple link to the user contact page.'),
+ 'handler' => 'views_handler_field_contact_link',
+ ),
+ );
+}
diff --git a/sites/all/modules/views/modules/contact/views_handler_field_contact_link.inc b/sites/all/modules/views/modules/contact/views_handler_field_contact_link.inc
new file mode 100644
index 000000000..9d22f01d0
--- /dev/null
+++ b/sites/all/modules/views/modules/contact/views_handler_field_contact_link.inc
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_contact_link.
+ */
+
+/**
+ * A field that links to the user contact page, if access is permitted.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_contact_link extends views_handler_field_user_link {
+
+ function options_form(&$form, &$form_state) {
+ $form['text']['#title'] = t('Link label');
+ $form['text']['#required'] = TRUE;
+ $form['text']['#default_value'] = empty($this->options['text']) ? t('contact') : $this->options['text'];
+ parent::options_form($form, $form_state);
+ }
+
+ // An example of field level access control.
+ // We must override the access method in the parent class, as that requires
+ // the 'access user profiles' permission, which the contact form does not.
+ function access() {
+ return user_access('access user contact forms');
+ }
+
+ function render_link($data, $values) {
+ global $user;
+ $uid = $this->get_value($values, 'uid');
+
+ if (empty($uid)) {
+ return;
+ }
+
+ $account = user_load($uid);
+ if (empty($account)) {
+ return;
+ }
+
+ // Check access when we pull up the user account so we know
+ // if the user has made the contact page available.
+ $menu_item = menu_get_item("user/$uid/contact");
+ if (!$menu_item['access'] || empty($account->data['contact'])) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = 'user/' . $account->uid . '/contact';
+ $this->options['alter']['attributes'] = array('title' => t('Contact %user', array('%user' => $account->name)));
+
+ $text = $this->options['text'];
+
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/field.views.inc b/sites/all/modules/views/modules/field.views.inc
new file mode 100644
index 000000000..0bf1ad2b4
--- /dev/null
+++ b/sites/all/modules/views/modules/field.views.inc
@@ -0,0 +1,510 @@
+<?php
+
+/**
+ * @file
+ * Provide Views data and handlers for field.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ *
+ * Field modules can implement hook_field_views_data() to override
+ * the default behavior for adding fields.
+ */
+function field_views_data() {
+ $data = array();
+ foreach (field_info_fields() as $field) {
+ if ($field['storage']['type'] != 'field_sql_storage') {
+ continue;
+ }
+
+ $module = $field['module'];
+ $result = (array) module_invoke($module, 'field_views_data', $field);
+
+ if (empty($result)) {
+ $result = field_views_field_default_views_data($field);
+ }
+ drupal_alter('field_views_data', $result, $field, $module);
+
+ if (is_array($result)) {
+ $data = drupal_array_merge_deep($result, $data);
+ }
+ }
+
+ return $data;
+}
+
+/**
+ * Implements hook_views_data_alter().
+ *
+ * Field modules can implement hook_field_views_data_views_data_alter() to
+ * alter the views data on a per field basis. This is weirdly named so as
+ * not to conflict with the drupal_alter('field_views_data') in
+ * field_views_data.
+ */
+function field_views_data_alter(&$data) {
+ foreach (field_info_fields() as $field) {
+ if ($field['storage']['type'] != 'field_sql_storage') {
+ continue;
+ }
+
+ $function = $field['module'] . '_field_views_data_views_data_alter';
+ if (function_exists($function)) {
+ $function($data, $field);
+ }
+ }
+}
+
+/**
+ * Returns the label of a certain field.
+ *
+ * Therefore it looks up in all bundles to find the most used instance.
+ */
+function field_views_field_label($field_name) {
+ $label_counter = array();
+ $all_labels = array();
+ // Count the amount of instances per label per field.
+ $instances = field_info_instances();
+ foreach ($instances as $entity_name => $entity_type) {
+ foreach ($entity_type as $bundle) {
+ if (isset($bundle[$field_name])) {
+ $label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]['label']] : 1;
+ $all_labels[$entity_name][$bundle[$field_name]['label']] = TRUE;
+ }
+ }
+ }
+ if (empty($label_counter)) {
+ return array($field_name, $all_labels);
+ }
+ // Sort the field lables by it most used label and return the most used one.
+ arsort($label_counter);
+ $label_counter = array_keys($label_counter);
+ return array($label_counter[0], $all_labels);
+}
+
+/**
+ * Default views data implementation for a field.
+ */
+function field_views_field_default_views_data($field) {
+ $field_types = field_info_field_types();
+
+ // Check the field module is available.
+ if (!isset($field_types[$field['type']])) {
+ return;
+ }
+
+ $data = array();
+
+ $current_table = _field_sql_storage_tablename($field);
+ $revision_table = _field_sql_storage_revision_tablename($field);
+
+ // The list of entity:bundle that this field is used in.
+ $bundles_names = array();
+ $supports_revisions = FALSE;
+ $entity_tables = array();
+ $current_tables = array();
+ $revision_tables = array();
+ $groups = array();
+
+ $group_name = count($field['bundles']) > 1 ? t('Field') : NULL;
+
+ // Build the relationships between the field table and the entity tables.
+ foreach ($field['bundles'] as $entity => $bundles) {
+ $entity_info = entity_get_info($entity);
+ $groups[$entity] = $entity_info['label'];
+
+ // Override Node to Content.
+ if ($groups[$entity] == t('Node')) {
+ $groups[$entity] = t('Content');
+ }
+
+ // If only one bundle use this as the default name.
+ if (empty($group_name)) {
+ $group_name = $groups[$entity];
+ }
+
+ $entity_tables[$entity_info['base table']] = $entity;
+ $current_tables[$entity] = $entity_info['base table'];
+ if (isset($entity_info['revision table'])) {
+ $entity_tables[$entity_info['revision table']] = $entity;
+ $revision_tables[$entity] = $entity_info['revision table'];
+ }
+
+ $data[$current_table]['table']['join'][$entity_info['base table']] = array(
+ 'left_field' => $entity_info['entity keys']['id'],
+ 'field' => 'entity_id',
+ 'extra' => array(
+ array('field' => 'entity_type', 'value' => $entity),
+ array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
+ ),
+ );
+
+ if (!empty($entity_info['entity keys']['revision']) && !empty($entity_info['revision table'])) {
+ $data[$revision_table]['table']['join'][$entity_info['revision table']] = array(
+ 'left_field' => $entity_info['entity keys']['revision'],
+ 'field' => 'revision_id',
+ 'extra' => array(
+ array('field' => 'entity_type', 'value' => $entity),
+ array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
+ ),
+ );
+
+ $supports_revisions = TRUE;
+ }
+
+ foreach ($bundles as $bundle) {
+ $bundles_names[] = t('@entity:@bundle', array('@entity' => $entity, '@bundle' => $bundle));
+ }
+ }
+
+ $tables = array();
+ $tables[FIELD_LOAD_CURRENT] = $current_table;
+ if ($supports_revisions) {
+ $tables[FIELD_LOAD_REVISION] = $revision_table;
+ }
+
+ $add_fields = array('delta', 'language', 'bundle');
+ foreach ($field['columns'] as $column_name => $attributes) {
+ $add_fields[] = _field_sql_storage_columnname($field['field_name'], $column_name);
+ }
+
+ // Note: we don't have a label available here, because we are at the field
+ // level, not at the instance level. So we just go through all instances
+ // and take the one which is used the most frequently.
+ $field_name = $field['field_name'];
+ list($label, $all_labels) = field_views_field_label($field_name);
+ foreach ($tables as $type => $table) {
+ if ($type == FIELD_LOAD_CURRENT) {
+ $group = $group_name;
+ $old_column = 'entity_id';
+ $column = $field['field_name'];
+ }
+ else {
+ $group = t('@group (historical data)', array('@group' => $group_name));
+ $old_column = 'revision_id';
+ $column = $field['field_name'] . '-' . $old_column;
+ }
+
+ $data[$table][$old_column]['field']['moved to'] = array($table, $column);
+ $data[$table][$column] = array(
+ 'group' => $group,
+ 'title' => $label,
+ 'title short' => $label,
+ 'help' => t('Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))),
+ );
+
+ // Go through and create a list of aliases for all possible combinations of
+ // entity type + name.
+ $aliases = array();
+ $also_known = array();
+ foreach ($all_labels as $entity_name => $labels) {
+ foreach ($labels as $label_name => $true) {
+ if ($type == FIELD_LOAD_CURRENT) {
+ if ($group_name != $groups[$entity_name] || $label != $label_name) {
+ $aliases[] = array(
+ 'base' => $current_tables[$entity_name],
+ 'group' => $groups[$entity_name],
+ 'title' => $label_name,
+ 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)),
+ );
+ }
+ $also_known[] = t('@group: @field', array('@group' => $groups[$entity_name], '@field' => $label_name));
+ }
+ else {
+ if ($group_name != $groups[$entity_name] && $label != $label_name && isset($revision_tables[$entity_name])) {
+ $aliases[] = array(
+ 'base' => $revision_tables[$entity_name],
+ 'group' => t('@group (historical data)', array('@group' => $groups[$entity_name])),
+ 'title' => $label_name,
+ 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)),
+ );
+ }
+ $also_known[] = t('@group (historical data): @field', array('@group' => $groups[$entity_name], '@field' => $label_name));
+ }
+ }
+ }
+ if ($aliases) {
+ $data[$table][$column]['aliases'] = $aliases;
+ $data[$table][$column]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known)));
+ }
+
+ $keys = array_keys($field['columns']);
+ $real_field = reset($keys);
+ $data[$table][$column]['field'] = array(
+ 'table' => $table,
+ 'handler' => 'views_handler_field_field',
+ 'click sortable' => TRUE,
+ 'field_name' => $field['field_name'],
+ // Provide a real field for group by.
+ 'real field' => $column . '_' . $real_field,
+ 'additional fields' => $add_fields,
+ 'entity_tables' => $entity_tables,
+ // Default the element type to div, let the UI change it if necessary.
+ 'element type' => 'div',
+ 'is revision' => $type == FIELD_LOAD_REVISION,
+ );
+ }
+
+ foreach ($field['columns'] as $column => $attributes) {
+ $allow_sort = TRUE;
+
+ // Identify likely filters and arguments for each column based on field type.
+ switch ($attributes['type']) {
+ case 'int':
+ case 'mediumint':
+ case 'tinyint':
+ case 'bigint':
+ case 'serial':
+ case 'numeric':
+ case 'float':
+ $filter = 'views_handler_filter_numeric';
+ $argument = 'views_handler_argument_numeric';
+ $sort = 'views_handler_sort';
+ break;
+ case 'text':
+ case 'blob':
+ // It does not make sense to sort by blob or text.
+ $allow_sort = FALSE;
+ default:
+ $filter = 'views_handler_filter_string';
+ $argument = 'views_handler_argument_string';
+ $sort = 'views_handler_sort';
+ break;
+ }
+
+
+ if (count($field['columns']) == 1 || $column == 'value') {
+ $title = t('@label (!name)', array('@label' => $label, '!name' => $field['field_name']));
+ // CCK used the first 10 characters of $label. Probably doesn't matter.
+ $title_short = $label;
+ }
+ else {
+ $title = t('@label (!name:!column)', array('@label' => $label, '!name' => $field['field_name'], '!column' => $column));
+ $title_short = t('@label:!column', array('@label' => $label, '!column' => $column));
+ }
+
+ foreach ($tables as $type => $table) {
+ if ($type == FIELD_LOAD_CURRENT) {
+ $group = $group_name;
+ }
+ else {
+ $group = t('@group (historical data)', array('@group' => $group_name));
+ }
+ $column_real_name = $field['storage']['details']['sql'][$type][$table][$column];
+
+ // Load all the fields from the table by default.
+ $additional_fields = array_values($field['storage']['details']['sql'][$type][$table]);
+
+ $data[$table][$column_real_name] = array(
+ 'group' => $group,
+ 'title' => $title,
+ 'title short' => $title_short,
+ 'help' => t('Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))),
+ );
+
+ // Go through and create a list of aliases for all possible combinations of
+ // entity type + name.
+ $aliases = array();
+ $also_known = array();
+ foreach ($all_labels as $entity_name => $labels) {
+ foreach ($labels as $label_name => $true) {
+ if ($group_name != $groups[$entity_name] || $label != $label_name) {
+ if (count($field['columns']) == 1 || $column == 'value') {
+ $alias_title = t('@label (!name)', array('@label' => $label_name, '!name' => $field['field_name']));
+ // CCK used the first 10 characters of $label. Probably doesn't matter.
+ }
+ else {
+ $alias_title = t('@label (!name:!column)', array('@label' => $label_name, '!name' => $field['field_name'], '!column' => $column));
+ }
+ $aliases[] = array(
+ 'group' => $groups[$entity_name],
+ 'title' => $alias_title,
+ 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $title)),
+ );
+ }
+ $also_known[] = t('@group: @field', array('@group' => $groups[$entity_name], '@field' => $title));
+ }
+ }
+ if ($aliases) {
+ $data[$table][$column_real_name]['aliases'] = $aliases;
+ $data[$table][$column_real_name]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known)));
+ }
+
+ $data[$table][$column_real_name]['argument'] = array(
+ 'field' => $column_real_name,
+ 'table' => $table,
+ 'handler' => $argument,
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ 'empty field name' => t('- No value -'),
+ );
+ $data[$table][$column_real_name]['filter'] = array(
+ 'field' => $column_real_name,
+ 'table' => $table,
+ 'handler' => $filter,
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ 'allow empty' => TRUE,
+ );
+ if (!empty($allow_sort)) {
+ $data[$table][$column_real_name]['sort'] = array(
+ 'field' => $column_real_name,
+ 'table' => $table,
+ 'handler' => $sort,
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ );
+ }
+
+ // Expose additional delta column for multiple value fields.
+ if ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
+ $title_delta = t('@label (!name:delta)', array('@label' => $label, '!name' => $field['field_name']));
+ $title_short_delta = t('@label:delta', array('@label' => $label));
+
+ $data[$table]['delta'] = array(
+ 'group' => $group,
+ 'title' => $title_delta,
+ 'title short' => $title_short_delta,
+ 'help' => t('Delta - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))),
+ );
+ $data[$table]['delta']['field'] = array(
+ 'handler' => 'views_handler_field_numeric',
+ );
+ $data[$table]['delta']['argument'] = array(
+ 'field' => 'delta',
+ 'table' => $table,
+ 'handler' => 'views_handler_argument_numeric',
+ 'additional fields' => $additional_fields,
+ 'empty field name' => t('- No value -'),
+ 'field_name' => $field['field_name'],
+ );
+ $data[$table]['delta']['filter'] = array(
+ 'field' => 'delta',
+ 'table' => $table,
+ 'handler' => 'views_handler_filter_numeric',
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ 'allow empty' => TRUE,
+ );
+ $data[$table]['delta']['sort'] = array(
+ 'field' => 'delta',
+ 'table' => $table,
+ 'handler' => 'views_handler_sort',
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ );
+ }
+
+ // Expose additional language column for translatable fields.
+ if (!empty($field['translatable'])) {
+ $title_language = t('@label (!name:language)', array('@label' => $label, '!name' => $field['field_name']));
+ $title_short_language = t('@label:language', array('@label' => $label));
+
+ $data[$table]['language'] = array(
+ 'group' => $group,
+ 'title' => $title_language,
+ 'title short' => $title_short_language,
+ 'help' => t('Language - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))),
+ );
+ $data[$table]['language']['field'] = array(
+ 'handler' => 'views_handler_field_locale_language',
+ );
+ $data[$table]['language']['argument'] = array(
+ 'field' => 'language',
+ 'table' => $table,
+ 'handler' => 'views_handler_argument_locale_language',
+ 'additional fields' => $additional_fields,
+ 'empty field name' => t('<No value>'),
+ 'field_name' => $field['field_name'],
+ );
+ $data[$table]['language']['filter'] = array(
+ 'field' => 'language',
+ 'table' => $table,
+ 'handler' => 'views_handler_filter_locale_language',
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ 'allow empty' => TRUE,
+ );
+ $data[$table]['language']['sort'] = array(
+ 'field' => 'language',
+ 'table' => $table,
+ 'handler' => 'views_handler_sort',
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ );
+ }
+
+ // Expose additional language column for translatable fields.
+ if (!empty($field['translatable'])) {
+ $title_language = t('@label (!name:language)', array('@label' => $label, '!name' => $field['field_name']));
+ $title_short_language = t('@label:language', array('@label' => $label));
+
+ $data[$table]['language'] = array(
+ 'group' => $group,
+ 'title' => $title_language,
+ 'title short' => $title_short_language,
+ 'help' => t('Language - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))),
+ );
+ $data[$table]['language']['field'] = array(
+ 'handler' => 'views_handler_field_locale_language',
+ );
+ $data[$table]['language']['argument'] = array(
+ 'field' => 'language',
+ 'table' => $table,
+ 'handler' => 'views_handler_argument_locale_language',
+ 'additional fields' => $additional_fields,
+ 'empty field name' => t('<No value>'),
+ 'field_name' => $field['field_name'],
+ );
+ $data[$table]['language']['filter'] = array(
+ 'field' => 'language',
+ 'table' => $table,
+ 'handler' => 'views_handler_filter_locale_language',
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ 'allow empty' => TRUE,
+ );
+ $data[$table]['language']['sort'] = array(
+ 'field' => 'language',
+ 'table' => $table,
+ 'handler' => 'views_handler_sort',
+ 'additional fields' => $additional_fields,
+ 'field_name' => $field['field_name'],
+ );
+ }
+ }
+ }
+
+ return $data;
+}
+
+/**
+ * Have a different filter handler for lists. This should allow to select values of the list.
+ */
+function list_field_views_data($field) {
+ $data = field_views_field_default_views_data($field);
+ foreach ($data as $table_name => $table_data) {
+ foreach ($table_data as $field_name => $field_data) {
+ if (isset($field_data['filter']) && $field_name != 'delta') {
+ if ($field['type'] == 'list_boolean') {
+ // Special handler for boolean fields.
+ $data[$table_name][$field_name]['filter']['handler'] = 'views_handler_filter_field_list_boolean';
+ }
+ else {
+ $data[$table_name][$field_name]['filter']['handler'] = 'views_handler_filter_field_list';
+ }
+ }
+ if (isset($field_data['argument']) && $field_name != 'delta') {
+ if ($field['type'] == 'list_text') {
+ $data[$table_name][$field_name]['argument']['handler'] = 'views_handler_argument_field_list_string';
+ }
+ else {
+ $data[$table_name][$field_name]['argument']['handler'] = 'views_handler_argument_field_list';
+ }
+ }
+ }
+ }
+ return $data;
+}
diff --git a/sites/all/modules/views/modules/field/views_handler_argument_field_list.inc b/sites/all/modules/views/modules/field/views_handler_argument_field_list.inc
new file mode 100644
index 000000000..e0f7abe83
--- /dev/null
+++ b/sites/all/modules/views/modules/field/views_handler_argument_field_list.inc
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_field_list.
+ */
+
+/**
+ * Argument handler for list field to show the human readable name in the
+ * summary.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_field_list extends views_handler_argument_numeric {
+ /**
+ * Stores the allowed values of this field.
+ *
+ * @var array
+ */
+ var $allowed_values = NULL;
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ $field = field_info_field($this->definition['field_name']);
+ $this->allowed_values = list_allowed_values($field);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['summary']['contains']['human'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['summary']['human'] = array(
+ '#title' => t('Display list value as human readable'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['summary']['human'],
+ '#dependency' => array('radio:options[default_action]' => array('summary')),
+ );
+ }
+
+
+ function summary_name($data) {
+ $value = $data->{$this->name_alias};
+ // If the list element has a human readable name show it,
+ if (isset($this->allowed_values[$value]) && !empty($this->options['summary']['human'])) {
+ return field_filter_xss($this->allowed_values[$value]);
+ }
+ // else fallback to the key.
+ else {
+ return check_plain($value);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/field/views_handler_argument_field_list_string.inc b/sites/all/modules/views/modules/field/views_handler_argument_field_list_string.inc
new file mode 100644
index 000000000..67a9f2d99
--- /dev/null
+++ b/sites/all/modules/views/modules/field/views_handler_argument_field_list_string.inc
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_field_list_text.
+ */
+
+/**
+ * Argument handler for list field to show the human readable name in the
+ * summary.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_field_list_string extends views_handler_argument_string {
+ /**
+ * Stores the allowed values of this field.
+ *
+ * @var array
+ */
+ var $allowed_values = NULL;
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ $field = field_info_field($this->definition['field_name']);
+ $this->allowed_values = list_allowed_values($field);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['summary']['contains']['human'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['summary']['human'] = array(
+ '#title' => t('Display list value as human readable'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['summary']['human'],
+ '#dependency' => array('radio:options[default_action]' => array('summary')),
+ );
+ }
+
+
+ function summary_name($data) {
+ $value = $data->{$this->name_alias};
+ // If the list element has a human readable name show it,
+ if (isset($this->allowed_values[$value]) && !empty($this->options['summary']['human'])) {
+ return $this->case_transform(field_filter_xss($this->allowed_values[$value]), $this->options['case']);
+ }
+ // else fallback to the key.
+ else {
+ return $this->case_transform(check_plain($value), $this->options['case']);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/field/views_handler_field_field.inc b/sites/all/modules/views/modules/field/views_handler_field_field.inc
new file mode 100644
index 000000000..fc25fabe8
--- /dev/null
+++ b/sites/all/modules/views/modules/field/views_handler_field_field.inc
@@ -0,0 +1,938 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_field.
+ */
+
+/**
+ * Helper function: Return an array of formatter options for a field type.
+ *
+ * Borrowed from field_ui.
+ */
+function _field_view_formatter_options($field_type = NULL) {
+ $options = &drupal_static(__FUNCTION__);
+
+ if (!isset($options)) {
+ $field_types = field_info_field_types();
+ $options = array();
+ foreach (field_info_formatter_types() as $name => $formatter) {
+ foreach ($formatter['field types'] as $formatter_field_type) {
+ // Check that the field type exists.
+ if (isset($field_types[$formatter_field_type])) {
+ $options[$formatter_field_type][$name] = $formatter['label'];
+ }
+ }
+ }
+ }
+
+ if ($field_type) {
+ return !empty($options[$field_type]) ? $options[$field_type] : array();
+ }
+ return $options;
+}
+
+/**
+ * A field that displays fieldapi fields.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_field extends views_handler_field {
+ /**
+ * An array to store field renderable arrays for use by render_items.
+ *
+ * @var array
+ */
+ public $items = array();
+
+ /**
+ * Store the field information.
+ *
+ * @var array
+ */
+ public $field_info = array();
+
+
+ /**
+ * Does the field supports multiple field values.
+ *
+ * @var bool
+ */
+ public $multiple;
+
+ /**
+ * Does the rendered fields get limited.
+ *
+ * @var bool
+ */
+ public $limit_values;
+
+ /**
+ * A shortcut for $view->base_table.
+ *
+ * @var string
+ */
+ public $base_table;
+
+ /**
+ * Store the field instance.
+ *
+ * @var array
+ */
+ public $instance;
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ $this->field_info = $field = field_info_field($this->definition['field_name']);
+ $this->multiple = FALSE;
+ $this->limit_values = FALSE;
+
+ if ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
+ $this->multiple = TRUE;
+
+ // If "Display all values in the same row" is FALSE, then we always limit
+ // in order to show a single unique value per row.
+ if (!$this->options['group_rows']) {
+ $this->limit_values = TRUE;
+ }
+
+ // If "First and last only" is chosen, limit the values
+ if (!empty($this->options['delta_first_last'])) {
+ $this->limit_values = TRUE;
+ }
+
+ // Otherwise, we only limit values if the user hasn't selected "all", 0, or
+ // the value matching field cardinality.
+ if ((intval($this->options['delta_limit']) && ($this->options['delta_limit'] != $field['cardinality'])) || intval($this->options['delta_offset'])) {
+ $this->limit_values = TRUE;
+ }
+ }
+
+ // Convert old style entity id group column to new format.
+ // @todo Remove for next major version.
+ if ($this->options['group_column'] == 'entity id') {
+ $this->options['group_column'] = 'entity_id';
+ }
+ }
+
+ /**
+ * Check whether current user has access to this handler.
+ *
+ * @return bool
+ * Return TRUE if the user has access to view this field.
+ */
+ function access() {
+ $base_table = $this->get_base_table();
+ return field_access('view', $this->field_info, $this->definition['entity_tables'][$base_table]);
+ }
+
+ /**
+ * Set the base_table and base_table_alias.
+ *
+ * @return string
+ * The base table which is used in the current view "context".
+ */
+ function get_base_table() {
+ if (!isset($this->base_table)) {
+ // This base_table is coming from the entity not the field.
+ $this->base_table = $this->view->base_table;
+
+ // If the current field is under a relationship you can't be sure that the
+ // base table of the view is the base table of the current field.
+ // For example a field from a node author on a node view does have users as base table.
+ if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
+ $relationships = $this->view->display_handler->get_option('relationships');
+ if (!empty($relationships[$this->options['relationship']])) {
+ $options = $relationships[$this->options['relationship']];
+ $data = views_fetch_data($options['table']);
+ $this->base_table = $data[$options['field']]['relationship']['base'];
+ }
+ }
+ }
+
+ return $this->base_table;
+ }
+
+ /**
+ * Called to add the field to a query.
+ *
+ * By default, the only columns added to the query are entity_id and
+ * entity_type. This is because other needed data is fetched by entity_load().
+ * Other columns are added only if they are used in groupings, or if
+ * 'add fields to query' is specifically set to TRUE in the field definition.
+ *
+ * The 'add fields to query' switch is used by modules which need all data
+ * present in the query itself (such as "sphinx").
+ */
+ function query($use_groupby = FALSE) {
+ $this->get_base_table();
+
+ $params = array();
+ if ($use_groupby) {
+ // When grouping on a "field API" field (whose "real_field" is set to
+ // entity_id), retrieve the minimum entity_id to have a valid entity_id to
+ // pass to field_view_field().
+ $params = array(
+ 'function' => 'min',
+ );
+
+ $this->ensure_my_table();
+ }
+
+ // Get the entity type according to the base table of the field.
+ // Then add it to the query as a formula. That way we can avoid joining
+ // the field table if all we need is entity_id and entity_type.
+ $entity_type = $this->definition['entity_tables'][$this->base_table];
+ $entity_info = entity_get_info($entity_type);
+
+ if (isset($this->relationship)) {
+ $this->base_table_alias = $this->relationship;
+ }
+ else {
+ $this->base_table_alias = $this->base_table;
+ }
+
+ // We always need the base field (entity_id / revision_id).
+ if (empty($this->definition['is revision'])) {
+ $this->field_alias = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['id'], '', $params);
+ }
+ else {
+ $this->field_alias = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['revision'], '', $params);
+ $this->aliases['entity_id'] = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['id'], '', $params);
+ }
+
+
+ // The alias needs to be unique, so we use both the field table and the entity type.
+ $entity_type_alias = $this->definition['table'] . '_' . $entity_type . '_entity_type';
+ $this->aliases['entity_type'] = $this->query->add_field(NULL, "'$entity_type'", $entity_type_alias);
+
+ $fields = $this->additional_fields;
+ // We've already added entity_type, so we can remove it from the list.
+ $entity_type_key = array_search('entity_type', $fields);
+ if ($entity_type_key !== FALSE) {
+ unset($fields[$entity_type_key]);
+ }
+
+ if ($use_groupby) {
+ // Add the fields that we're actually grouping on.
+ $options = array();
+
+ if ($this->options['group_column'] != 'entity_id') {
+ $options = array($this->options['group_column'] => $this->options['group_column']);
+ }
+
+ $options += is_array($this->options['group_columns']) ? $this->options['group_columns'] : array();
+
+
+ $fields = array();
+ $rkey = $this->definition['is revision'] ? 'FIELD_LOAD_REVISION' : 'FIELD_LOAD_CURRENT';
+ // Go through the list and determine the actual column name from field api.
+ foreach ($options as $column) {
+ $name = $column;
+ if (isset($this->field_info['storage']['details']['sql'][$rkey][$this->table][$column])) {
+ $name = $this->field_info['storage']['details']['sql'][$rkey][$this->table][$column];
+ }
+
+ $fields[$column] = $name;
+ }
+
+ $this->group_fields = $fields;
+ }
+
+ // Add additional fields (and the table join itself) if needed.
+ if ($this->add_field_table($use_groupby)) {
+ $this->ensure_my_table();
+ $this->add_additional_fields($fields);
+
+ // Filter by language, if field translation is enabled.
+ $field = $this->field_info;
+ if (field_is_translatable($entity_type, $field) && !empty($this->view->display_handler->options['field_language_add_to_query'])) {
+ $column = $this->table_alias . '.language';
+ // By the same reason as field_language the field might be LANGUAGE_NONE in reality so allow it as well.
+ // @see this::field_language()
+ global $language_content;
+ $default_language = language_default('language');
+ $language = str_replace(array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'),
+ array($language_content->language, $default_language),
+ $this->view->display_handler->options['field_language']);
+ $placeholder = $this->placeholder();
+ $language_fallback_candidates = array($language);
+ if (variable_get('locale_field_language_fallback', TRUE)) {
+ require_once DRUPAL_ROOT . '/includes/language.inc';
+ $language_fallback_candidates = array_merge($language_fallback_candidates, language_fallback_get_candidates());
+ }
+ else {
+ $language_fallback_candidates[] = LANGUAGE_NONE;
+ }
+ $this->query->add_where_expression(0, "$column IN($placeholder) OR $column IS NULL", array($placeholder => $language_fallback_candidates));
+ }
+ }
+
+ // The revision id inhibits grouping.
+ // So, stop here if we're using grouping, or if aren't adding all columns to
+ // the query.
+ if ($use_groupby || empty($this->definition['add fields to query'])) {
+ return;
+ }
+
+ $this->add_additional_fields(array('revision_id'));
+ }
+
+ /**
+ * Determine if the field table should be added to the query.
+ */
+ function add_field_table($use_groupby) {
+ // Grouping is enabled, or we are explicitly required to do this.
+ if ($use_groupby || !empty($this->definition['add fields to query'])) {
+ return TRUE;
+ }
+ // This a multiple value field, but "group multiple values" is not checked.
+ if ($this->multiple && !$this->options['group_rows']) {
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Determine if this field is click sortable.
+ */
+ function click_sortable() {
+ // Not click sortable in any case.
+ if (empty($this->definition['click sortable'])) {
+ return FALSE;
+ }
+ // A field is not click sortable if it's a multiple field with
+ // "group multiple values" checked, since a click sort in that case would
+ // add a join to the field table, which would produce unwanted duplicates.
+ if ($this->multiple && $this->options['group_rows']) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Called to determine what to tell the clicksorter.
+ */
+ function click_sort($order) {
+ // No column selected, can't continue.
+ if (empty($this->options['click_sort_column'])) {
+ return;
+ }
+
+ $this->ensure_my_table();
+ $column = _field_sql_storage_columnname($this->definition['field_name'], $this->options['click_sort_column']);
+ if (!isset($this->aliases[$column])) {
+ // Column is not in query; add a sort on it (without adding the column).
+ $this->aliases[$column] = $this->table_alias . '.' . $column;
+ }
+ $this->query->add_orderby(NULL, NULL, $order, $this->aliases[$column]);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ // option_definition runs before init/construct, so no $this->field_info
+ $field = field_info_field($this->definition['field_name']);
+ $field_type = field_info_field_types($field['type']);
+ $column_names = array_keys($field['columns']);
+ $default_column = '';
+ // Try to determine a sensible default.
+ if (count($column_names) == 1) {
+ $default_column = $column_names[0];
+ }
+ elseif (in_array('value', $column_names)) {
+ $default_column = 'value';
+ }
+
+ // If the field has a "value" column, we probably need that one.
+ $options['click_sort_column'] = array(
+ 'default' => $default_column,
+ );
+ $options['type'] = array(
+ 'default' => $field_type['default_formatter'],
+ );
+ $options['settings'] = array(
+ 'default' => array(),
+ );
+ $options['group_column'] = array(
+ 'default' => $default_column,
+ );
+ $options['group_columns'] = array(
+ 'default' => array(),
+ );
+
+ // Options used for multiple value fields.
+ $options['group_rows'] = array(
+ 'default' => TRUE,
+ 'bool' => TRUE,
+ );
+ // If we know the exact number of allowed values, then that can be
+ // the default. Otherwise, default to 'all'.
+ $options['delta_limit'] = array(
+ 'default' => ($field['cardinality'] > 1) ? $field['cardinality'] : 'all',
+ );
+ $options['delta_offset'] = array(
+ 'default' => 0,
+ );
+ $options['delta_reversed'] = array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ );
+ $options['delta_first_last'] = array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ );
+
+ $options['multi_type'] = array(
+ 'default' => 'separator'
+ );
+ $options['separator'] = array(
+ 'default' => ', '
+ );
+
+ $options['field_api_classes'] = array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ );
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $field = $this->field_info;
+ $formatters = _field_view_formatter_options($field['type']);
+ $column_names = array_keys($field['columns']);
+
+ // If this is a multiple value field, add its options.
+ if ($this->multiple) {
+ $this->multiple_options_form($form, $form_state);
+ }
+
+ // No need to ask the user anything if the field has only one column.
+ if (count($field['columns']) == 1) {
+ $form['click_sort_column'] = array(
+ '#type' => 'value',
+ '#value' => isset($column_names[0]) ? $column_names[0] : '',
+ );
+ }
+ else {
+ $form['click_sort_column'] = array(
+ '#type' => 'select',
+ '#title' => t('Column used for click sorting'),
+ '#options' => drupal_map_assoc($column_names),
+ '#default_value' => $this->options['click_sort_column'],
+ '#description' => t('Used by Style: Table to determine the actual column to click sort the field on. The default is usually fine.'),
+ '#fieldset' => 'more',
+ );
+ }
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Formatter'),
+ '#options' => $formatters,
+ '#default_value' => $this->options['type'],
+ '#ajax' => array(
+ 'path' => views_ui_build_form_url($form_state),
+ ),
+ '#submit' => array('views_ui_config_item_form_submit_temporary'),
+ '#executes_submit_callback' => TRUE,
+ );
+
+ $form['field_api_classes'] = array(
+ '#title' => t('Use field template'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['field_api_classes'],
+ '#description' => t('If checked, field api classes will be added using field.tpl.php (or equivalent). This is not recommended unless your CSS depends upon these classes. If not checked, template will not be used.'),
+ '#fieldset' => 'style_settings',
+ '#weight' => 20,
+ );
+
+ if ($this->multiple) {
+ $form['field_api_classes']['#description'] .= ' ' . t('Checking this option will cause the group Display Type and Separator values to be ignored.');
+ }
+
+ // Get the currently selected formatter.
+ $format = $this->options['type'];
+
+ $formatter = field_info_formatter_types($format);
+ $settings = $this->options['settings'] + field_info_formatter_settings($format);
+
+ // Provide an instance array for hook_field_formatter_settings_form().
+ ctools_include('fields');
+ $this->instance = ctools_fields_fake_field_instance($this->definition['field_name'], '_custom', $formatter, $settings);
+
+ // Store the settings in a '_custom' view mode.
+ $this->instance['display']['_custom'] = array(
+ 'type' => $format,
+ 'settings' => $settings,
+ );
+
+ // Get the settings form.
+ $settings_form = array('#value' => array());
+ $function = $formatter['module'] . '_field_formatter_settings_form';
+ if (function_exists($function)) {
+ $settings_form = $function($field, $this->instance, '_custom', $form, $form_state);
+ }
+ $form['settings'] = $settings_form;
+ }
+
+ /**
+ * Provide options for multiple value fields.
+ */
+ function multiple_options_form(&$form, &$form_state) {
+ $field = $this->field_info;
+
+ $form['multiple_field_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Multiple field settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#weight' => 5,
+ );
+
+ $form['group_rows'] = array(
+ '#title' => t('Display all values in the same row'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['group_rows'],
+ '#description' => t('If checked, multiple values for this field will be shown in the same row. If not checked, each value in this field will create a new row. If using group by, please make sure to group by "Entity ID" for this setting to have any effect.'),
+ '#fieldset' => 'multiple_field_settings',
+ );
+
+ // Make the string translatable by keeping it as a whole rather than
+ // translating prefix and suffix separately.
+ list($prefix, $suffix) = explode('@count', t('Display @count value(s)'));
+
+ if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
+ $type = 'textfield';
+ $options = NULL;
+ $size = 5;
+ }
+ else {
+ $type = 'select';
+ $options = drupal_map_assoc(range(1, $field['cardinality']));
+ $size = 1;
+ }
+ $form['multi_type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Display type'),
+ '#options' => array(
+ 'ul' => t('Unordered list'),
+ 'ol' => t('Ordered list'),
+ 'separator' => t('Simple separator'),
+ ),
+ '#dependency' => array('edit-options-group-rows' => array(TRUE)),
+ '#default_value' => $this->options['multi_type'],
+ '#fieldset' => 'multiple_field_settings',
+ );
+
+ $form['separator'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Separator'),
+ '#default_value' => $this->options['separator'],
+ '#dependency' => array(
+ 'radio:options[multi_type]' => array('separator'),
+ 'edit-options-group-rows' => array(TRUE),
+ ),
+ '#dependency_count' => 2,
+ '#fieldset' => 'multiple_field_settings',
+ );
+
+ $form['delta_limit'] = array(
+ '#type' => $type,
+ '#size' => $size,
+ '#field_prefix' => $prefix,
+ '#field_suffix' => $suffix,
+ '#options' => $options,
+ '#default_value' => $this->options['delta_limit'],
+ '#prefix' => '<div class="container-inline">',
+ '#dependency' => array('edit-options-group-rows' => array(TRUE)),
+ '#fieldset' => 'multiple_field_settings',
+ );
+
+ list($prefix, $suffix) = explode('@count', t('starting from @count'));
+ $form['delta_offset'] = array(
+ '#type' => 'textfield',
+ '#size' => 5,
+ '#field_prefix' => $prefix,
+ '#field_suffix' => $suffix,
+ '#default_value' => $this->options['delta_offset'],
+ '#dependency' => array('edit-options-group-rows' => array(TRUE)),
+ '#description' => t('(first item is 0)'),
+ '#fieldset' => 'multiple_field_settings',
+ );
+ $form['delta_reversed'] = array(
+ '#title' => t('Reversed'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['delta_reversed'],
+ '#suffix' => $suffix,
+ '#dependency' => array('edit-options-group-rows' => array(TRUE)),
+ '#description' => t('(start from last values)'),
+ '#fieldset' => 'multiple_field_settings',
+ );
+ $form['delta_first_last'] = array(
+ '#title' => t('First and last only'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['delta_first_last'],
+ '#suffix' => '</div>',
+ '#dependency' => array('edit-options-group-rows' => array(TRUE)),
+ '#fieldset' => 'multiple_field_settings',
+ );
+ }
+
+ /**
+ * Extend the groupby form with group columns.
+ */
+ function groupby_form(&$form, &$form_state) {
+ parent::groupby_form($form, $form_state);
+ // With "field API" fields, the column target of the grouping function
+ // and any additional grouping columns must be specified.
+ $group_columns = array(
+ 'entity_id' => t('Entity ID'),
+ ) + drupal_map_assoc(array_keys($this->field_info['columns']), 'ucfirst');
+
+ $form['group_column'] = array(
+ '#type' => 'select',
+ '#title' => t('Group column'),
+ '#default_value' => $this->options['group_column'],
+ '#description' => t('Select the column of this field to apply the grouping function selected above.'),
+ '#options' => $group_columns,
+ );
+
+ $options = drupal_map_assoc(array('bundle', 'language', 'entity_type'), 'ucfirst');
+
+ // Add on defined fields, noting that they're prefixed with the field name.
+ $form['group_columns'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Group columns (additional)'),
+ '#default_value' => $this->options['group_columns'],
+ '#description' => t('Select any additional columns of this field to include in the query and to group on.'),
+ '#options' => $options + $group_columns,
+ );
+ }
+
+ function groupby_form_submit(&$form, &$form_state) {
+ parent::groupby_form_submit($form, $form_state);
+ $item =& $form_state['handler']->options;
+
+ // Add settings for "field API" fields.
+ $item['group_column'] = $form_state['values']['options']['group_column'];
+ $item['group_columns'] = array_filter($form_state['values']['options']['group_columns']);
+ }
+
+ /**
+ * Load the entities for all fields that are about to be displayed.
+ */
+ function post_execute(&$values) {
+ if (!empty($values)) {
+ // Divide the entity ids by entity type, so they can be loaded in bulk.
+ $entities_by_type = array();
+ $revisions_by_type = array();
+ foreach ($values as $key => $object) {
+ if (isset($this->aliases['entity_type']) && isset($object->{$this->aliases['entity_type']}) && isset($object->{$this->field_alias}) && !isset($values[$key]->_field_data[$this->field_alias])) {
+ $entity_type = $object->{$this->aliases['entity_type']};
+ if (empty($this->definition['is revision'])) {
+ $entity_id = $object->{$this->field_alias};
+ $entities_by_type[$entity_type][$key] = $entity_id;
+ }
+ else {
+ $revision_id = $object->{$this->field_alias};
+ $entity_id = $object->{$this->aliases['entity_id']};
+ $entities_by_type[$entity_type][$key] = array($entity_id, $revision_id);
+ }
+ }
+ }
+
+ // Load the entities.
+ foreach ($entities_by_type as $entity_type => $entity_ids) {
+ $entity_info = entity_get_info($entity_type);
+ if (empty($this->definition['is revision'])) {
+ $entities = entity_load($entity_type, $entity_ids);
+ $keys = $entity_ids;
+ }
+ else {
+ // Revisions can't be loaded multiple, so we have to load them
+ // one by one.
+ $entities = array();
+ $keys = array();
+ foreach ($entity_ids as $key => $combined) {
+ list($entity_id, $revision_id) = $combined;
+ $entity = entity_load($entity_type, array($entity_id), array($entity_info['entity keys']['revision'] => $revision_id));
+ if ($entity) {
+ $entities[$revision_id] = array_shift($entity);
+ $keys[$key] = $revision_id;
+ }
+ }
+ }
+
+ foreach ($keys as $key => $entity_id) {
+ // If this is a revision, load the revision instead.
+ if (isset($entities[$entity_id])) {
+ $values[$key]->_field_data[$this->field_alias] = array(
+ 'entity_type' => $entity_type,
+ 'entity' => $entities[$entity_id],
+ );
+ }
+ }
+ }
+
+ // Now, transfer the data back into the resultset so it can be easily used.
+ foreach ($values as $row_id => &$value) {
+ $value->{'field_' . $this->options['id']} = $this->set_items($value, $row_id);
+ }
+ }
+ }
+
+ /**
+ * Render all items in this field together.
+ *
+ * When using advanced render, each possible item in the list is rendered
+ * individually. Then the items are all pasted together.
+ */
+ function render_items($items) {
+ if (!empty($items)) {
+ if (!$this->options['group_rows']) {
+ return implode('', $items);
+ }
+
+ if ($this->options['multi_type'] == 'separator') {
+ return implode(filter_xss_admin($this->options['separator']), $items);
+ }
+ else {
+ return theme('item_list',
+ array(
+ 'items' => $items,
+ 'title' => NULL,
+ 'type' => $this->options['multi_type']
+ ));
+ }
+ }
+ }
+
+ function get_items($values) {
+ return $values->{'field_' . $this->options['id']};
+ }
+
+ function get_value($values, $field = NULL) {
+ if (!isset($values->_field_data[$this->field_alias]['entity']) || !is_object($values->_field_data[$this->field_alias]['entity'])) {
+ return array();
+ }
+
+ // Go ahead and render and store in $this->items.
+ $entity = clone $values->_field_data[$this->field_alias]['entity'];
+
+ $entity_type = $values->_field_data[$this->field_alias]['entity_type'];
+ $langcode = $this->field_language($entity_type, $entity);
+ // If we are grouping, copy our group fields into the cloned entity.
+ // It's possible this will cause some weirdness, but there's only
+ // so much we can hope to do.
+ if (!empty($this->group_fields)) {
+ // first, test to see if we have a base value.
+ $base_value = array();
+ // Note: We would copy original values here, but it can cause problems.
+ // For example, text fields store cached filtered values as
+ // 'safe_value' which doesn't appear anywhere in the field definition
+ // so we can't affect it. Other side effects could happen similarly.
+ $data = FALSE;
+ foreach ($this->group_fields as $field_name => $column) {
+ if (property_exists($values, $this->aliases[$column])) {
+ $base_value[$field_name] = $values->{$this->aliases[$column]};
+ if (isset($base_value[$field_name])) {
+ $data = TRUE;
+ }
+ }
+ }
+
+ // If any of our aggregated fields have data, fake it:
+ if ($data) {
+ // Now, overwrite the original value with our aggregated value.
+ // This overwrites it so there is always just one entry.
+ $entity->{$this->definition['field_name']}[$langcode] = array($base_value);
+ }
+ else {
+ $entity->{$this->definition['field_name']}[$langcode] = array();
+ }
+ }
+
+ // The field we are trying to display doesn't exist on this entity.
+ if (!isset($entity->{$this->definition['field_name']})) {
+ return array();
+ }
+
+ // We are supposed to show only certain deltas.
+ if ($this->limit_values && !empty($entity->{$this->definition['field_name']})) {
+ $all_values = !empty($entity->{$this->definition['field_name']}[$langcode]) ? $entity->{$this->definition['field_name']}[$langcode] : array();
+ if ($this->options['delta_reversed']) {
+ $all_values = array_reverse($all_values);
+ }
+
+ // Offset is calculated differently when row grouping for a field is
+ // not enabled. Since there are multiple rows, the delta needs to be
+ // taken into account, so that different values are shown per row.
+ if (!$this->options['group_rows'] && isset($this->aliases['delta']) && isset($values->{$this->aliases['delta']})) {
+ $delta_limit = 1;
+ $offset = $values->{$this->aliases['delta']};
+ }
+ // Single fields don't have a delta available so choose 0.
+ elseif (!$this->options['group_rows'] && !$this->multiple) {
+ $delta_limit = 1;
+ $offset = 0;
+ }
+ else {
+ $delta_limit = $this->options['delta_limit'];
+ $offset = intval($this->options['delta_offset']);
+
+ // We should only get here in this case if there's an offset, and
+ // in that case we're limiting to all values after the offset.
+ if ($delta_limit == 'all') {
+ $delta_limit = count($all_values) - $offset;
+ }
+ }
+
+ // Determine if only the first and last values should be shown
+ $delta_first_last = $this->options['delta_first_last'];
+
+ $new_values = array();
+ for ($i = 0; $i < $delta_limit; $i++) {
+ $new_delta = $offset + $i;
+
+ if (isset($all_values[$new_delta])) {
+ // If first-last option was selected, only use the first and last values
+ if (!$delta_first_last
+ // Use the first value.
+ || $new_delta == $offset
+ // Use the last value.
+ || $new_delta == ($delta_limit + $offset - 1)) {
+ $new_values[] = $all_values[$new_delta];
+ }
+ }
+ }
+ $entity->{$this->definition['field_name']}[$langcode] = $new_values;
+ }
+
+ if ($field == 'entity') {
+ return $entity;
+ }
+ else {
+ return !empty($entity->{$this->definition['field_name']}[$langcode]) ? $entity->{$this->definition['field_name']}[$langcode] : array();
+ }
+ }
+
+ /**
+ * Return an array of items for the field.
+ */
+ function set_items($values, $row_id) {
+ // In some cases the instance on the entity might be easy, see
+ // https://drupal.org/node/1161708 and https://drupal.org/node/1461536 for
+ // more information.
+ if (empty($values->_field_data[$this->field_alias]) || empty($values->_field_data[$this->field_alias]['entity']) || !isset($values->_field_data[$this->field_alias]['entity']->{$this->definition['field_name']})) {
+ return array();
+ }
+
+ $display = array(
+ 'type' => $this->options['type'],
+ 'settings' => $this->options['settings'],
+ 'label' => 'hidden',
+ // Pass the View object in the display so that fields can act on it.
+ 'views_view' => $this->view,
+ 'views_field' => $this,
+ 'views_row_id' => $row_id,
+ );
+
+
+ $entity_type = $values->_field_data[$this->field_alias]['entity_type'];
+ $entity = $this->get_value($values, 'entity');
+ if (!$entity) {
+ return array();
+ }
+
+ $langcode = $this->field_language($entity_type, $entity);
+ $render_array = field_view_field($entity_type, $entity, $this->definition['field_name'], $display, $langcode);
+
+ $items = array();
+ if ($this->options['field_api_classes']) {
+ // Make a copy.
+ $array = $render_array;
+ return array(array('rendered' => drupal_render($render_array)));
+ }
+
+ foreach (element_children($render_array) as $count) {
+ $items[$count]['rendered'] = $render_array[$count];
+ // field_view_field() adds an #access property to the render array that
+ // determines whether or not the current user is allowed to view the
+ // field in the context of the current entity. We need to respect this
+ // parameter when we pull out the children of the field array for
+ // rendering.
+ if (isset($render_array['#access'])) {
+ $items[$count]['rendered']['#access'] = $render_array['#access'];
+ }
+ // Only add the raw field items (for use in tokens) if the current user
+ // has access to view the field content.
+ if ((!isset($items[$count]['rendered']['#access']) || $items[$count]['rendered']['#access']) && !empty($render_array['#items'][$count])) {
+ $items[$count]['raw'] = $render_array['#items'][$count];
+ }
+ }
+ return $items;
+ }
+
+ function render_item($count, $item) {
+ return render($item['rendered']);
+ }
+
+ function document_self_tokens(&$tokens) {
+ $field = $this->field_info;
+ foreach ($field['columns'] as $id => $column) {
+ $tokens['[' . $this->options['id'] . '-' . $id . ']'] = t('Raw @column', array('@column' => $id));
+ }
+ }
+
+ function add_self_tokens(&$tokens, $item) {
+ $field = $this->field_info;
+ foreach ($field['columns'] as $id => $column) {
+ // Use filter_xss_admin because it's user data and we can't be sure it is safe.
+ // We know nothing about the data, though, so we can't really do much else.
+
+ if (isset($item['raw'])) {
+ // If $item['raw'] is an array then we can use as is, if it's an object
+ // we cast it to an array, if it's neither, we can't use it.
+ $raw = is_array($item['raw']) ? $item['raw'] :
+ (is_object($item['raw']) ? (array)$item['raw'] : NULL);
+ }
+ if (isset($raw) && isset($raw[$id]) && is_scalar($raw[$id])) {
+ $tokens['[' . $this->options['id'] . '-' . $id . ']'] = filter_xss_admin($raw[$id]);
+ }
+ else {
+ // Take sure that empty values are replaced as well.
+ $tokens['[' . $this->options['id'] . '-' . $id . ']'] = '';
+ }
+ }
+ }
+
+ /**
+ * Return the language code of the language the field should be displayed in,
+ * according to the settings.
+ */
+ function field_language($entity_type, $entity) {
+ global $language_content;
+
+ if (field_is_translatable($entity_type, $this->field_info)) {
+ $default_language = language_default('language');
+ $language = str_replace(array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'),
+ array($language_content->language, $default_language),
+ $this->view->display_handler->options['field_language']);
+
+ // Give the Field Language API a chance to fallback to a different language
+ // (or LANGUAGE_NONE), in case the field has no data for the selected language.
+ // field_view_field() does this as well, but since the returned language code
+ // is used before calling it, the fallback needs to happen explicitly.
+ $language = field_language($entity_type, $entity, $this->field_info['field_name'], $language);
+
+ return $language;
+ }
+ else {
+ return LANGUAGE_NONE;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/field/views_handler_filter_field_list.inc b/sites/all/modules/views/modules/field/views_handler_filter_field_list.inc
new file mode 100644
index 000000000..440d55bb9
--- /dev/null
+++ b/sites/all/modules/views/modules/field/views_handler_filter_field_list.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_field_list.
+ */
+
+/**
+ * Filter handler which uses list-fields as options.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_field_list extends views_handler_filter_many_to_one {
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ // Migrate the settings from the old filter_in_operator values to filter_many_to_one.
+ if ($this->options['operator'] == 'in') {
+ $this->options['operator'] = 'or';
+ }
+ if ($this->options['operator'] == 'not in') {
+ $this->options['operator'] = 'not';
+ }
+ $this->operator = $this->options['operator'];
+ }
+
+
+ function get_value_options() {
+ $field = field_info_field($this->definition['field_name']);
+ $this->value_options = list_allowed_values($field);
+ }
+}
diff --git a/sites/all/modules/views/modules/field/views_handler_filter_field_list_boolean.inc b/sites/all/modules/views/modules/field/views_handler_filter_field_list_boolean.inc
new file mode 100644
index 000000000..34bca5497
--- /dev/null
+++ b/sites/all/modules/views/modules/field/views_handler_filter_field_list_boolean.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_field_list_boolean.
+ */
+
+/**
+ * Filter handler for boolean fields.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_field_list_boolean extends views_handler_filter_field_list {
+
+ function get_value_options() {
+ $field = field_info_field($this->definition['field_name']);
+ $value_options = list_allowed_values($field);
+
+ // Boolean fields have an option for using the label as the 'on' value. This
+ // results in there being no label values in the allows values array.
+ // If this is the case, we need to provide the labels.
+ $filtered = array_filter($value_options);
+ if (empty($filtered)) {
+ // We can't provide the label in the same way the FieldAPI formatter does,
+ // as these are different on each instance, and we may be operating on
+ // more than one bundle.
+ $value_options[0] = t('Off');
+ $value_options[1] = t('On');
+ }
+
+ $this->value_options = $value_options;
+ }
+}
diff --git a/sites/all/modules/views/modules/field/views_handler_relationship_entity_reverse.inc b/sites/all/modules/views/modules/field/views_handler_relationship_entity_reverse.inc
new file mode 100644
index 000000000..89f248359
--- /dev/null
+++ b/sites/all/modules/views/modules/field/views_handler_relationship_entity_reverse.inc
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_relationship_entity_reverse.
+ */
+
+/**
+ * A relationship handlers which reverse entity references.
+ *
+ * @ingroup views_relationship_handlers
+ */
+class views_handler_relationship_entity_reverse extends views_handler_relationship {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ $this->field_info = field_info_field($this->definition['field_name']);
+ }
+
+ /**
+ * Called to implement a relationship in a query.
+ */
+ function query() {
+ $this->ensure_my_table();
+ // First, relate our base table to the current base table to the
+ // field, using the base table's id field to the field's column.
+ $views_data = views_fetch_data($this->table);
+ $left_field = $views_data['table']['base']['field'];
+
+ $first = array(
+ 'left_table' => $this->table_alias,
+ 'left_field' => $left_field,
+ 'table' => $this->definition['field table'],
+ 'field' => $this->definition['field field'],
+ );
+ if (!empty($this->options['required'])) {
+ $first['type'] = 'INNER';
+ }
+
+ if (!empty($this->definition['join_extra'])) {
+ $first['extra'] = $this->definition['join_extra'];
+ }
+
+ if (!empty($this->definition['join_handler']) && class_exists($this->definition['join_handler'])) {
+ $first_join = new $this->definition['join_handler'];
+ }
+ else {
+ $first_join = new views_join();
+ }
+ $first_join->definition = $first;
+ $first_join->construct();
+ $first_join->adjusted = TRUE;
+
+ $this->first_alias = $this->query->add_table($this->definition['field table'], $this->relationship, $first_join);
+
+ // Second, relate the field table to the entity specified using
+ // the entity id on the field table and the entity's id field.
+ $second = array(
+ 'left_table' => $this->first_alias,
+ 'left_field' => 'entity_id',
+ 'table' => $this->definition['base'],
+ 'field' => $this->definition['base field'],
+ );
+
+ if (!empty($this->options['required'])) {
+ $second['type'] = 'INNER';
+ }
+
+ if (!empty($this->definition['join_handler']) && class_exists($this->definition['join_handler'])) {
+ $second_join = new $this->definition['join_handler'];
+ }
+ else {
+ $second_join = new views_join();
+ }
+ $second_join->definition = $second;
+ $second_join->construct();
+ $second_join->adjusted = TRUE;
+
+ // use a short alias for this:
+ $alias = $this->definition['field_name'] . '_' . $this->table;
+
+ $this->alias = $this->query->add_relationship($alias, $second_join, $this->definition['base'], $this->relationship);
+ }
+}
diff --git a/sites/all/modules/views/modules/file.views.inc b/sites/all/modules/views/modules/file.views.inc
new file mode 100644
index 000000000..85569385e
--- /dev/null
+++ b/sites/all/modules/views/modules/file.views.inc
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for file.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_field_views_data().
+ *
+ * Views integration for file fields. Adds a file relationship to the default
+ * field data.
+ *
+ * @see field_views_field_default_views_data()
+ */
+function file_field_views_data($field) {
+ $data = field_views_field_default_views_data($field);
+ foreach ($data as $table_name => $table_data) {
+ // Add the relationship only on the fid field.
+ $data[$table_name][$field['field_name'] . '_fid']['relationship'] = array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'file_managed',
+ 'entity type' => 'file',
+ 'base field' => 'fid',
+ 'label' => t('file from !field_name', array('!field_name' => $field['field_name'])),
+ );
+ }
+
+ return $data;
+}
+
+/**
+ * Implements hook_field_views_data_views_data_alter().
+ *
+ * Views integration to provide reverse relationships on file fields.
+ */
+function file_field_views_data_views_data_alter(&$data, $field) {
+ foreach ($field['bundles'] as $entity_type => $bundles) {
+ $entity_info = entity_get_info($entity_type);
+ $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
+
+ list($label, $all_labels) = field_views_field_label($field['field_name']);
+ $entity = $entity_info['label'];
+ if ($entity == t('Node')) {
+ $entity = t('Content');
+ }
+
+ $data['file_managed'][$pseudo_field_name]['relationship'] = array(
+ 'title' => t('@entity using @field', array('@entity' => $entity, '@field' => $label)),
+ 'help' => t('Relate each @entity with a @field set to the file.', array('@entity' => $entity, '@field' => $label)),
+ 'handler' => 'views_handler_relationship_entity_reverse',
+ 'field_name' => $field['field_name'],
+ 'field table' => _field_sql_storage_tablename($field),
+ 'field field' => $field['field_name'] . '_fid',
+ 'base' => $entity_info['base table'],
+ 'base field' => $entity_info['entity keys']['id'],
+ 'label' => t('!field_name', array('!field_name' => $field['field_name'])),
+ 'join_extra' => array(
+ 0 => array(
+ 'field' => 'entity_type',
+ 'value' => $entity_type,
+ ),
+ 1 => array(
+ 'field' => 'deleted',
+ 'value' => 0,
+ 'numeric' => TRUE,
+ ),
+ ),
+ );
+ }
+}
diff --git a/sites/all/modules/views/modules/filter.views.inc b/sites/all/modules/views/modules/filter.views.inc
new file mode 100644
index 000000000..0a3b4fce0
--- /dev/null
+++ b/sites/all/modules/views/modules/filter.views.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Provide basic views data for filter.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function filter_views_data() {
+ // ----------------------------------------------------------------------
+ // filter_format table
+
+ // Have not defined $data['filter_formats']['table']['group'] since
+ // no fields are defined here yet.
+ $data['filter_formats']['moved to'] = 'filter_format';
+ $data['filter_format']['table']['join'] = array(
+ 'node_revision' => array(
+ 'left_field' => 'format',
+ 'field' => 'format',
+ ),
+ 'node' => array(
+ 'left_table' => 'node_revision',
+ 'left_field' => 'format',
+ 'field' => 'format',
+ ),
+ );
+
+ return $data;
+}
diff --git a/sites/all/modules/views/modules/filter/views_handler_field_filter_format_name.inc b/sites/all/modules/views/modules/filter/views_handler_field_filter_format_name.inc
new file mode 100644
index 000000000..0a7bf3b86
--- /dev/null
+++ b/sites/all/modules/views/modules/filter/views_handler_field_filter_format_name.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_filter_format_name.
+ */
+
+/**
+ * Field handler to output the name of an input format.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_filter_format_name extends views_handler_field {
+ function construct() {
+ parent::construct();
+ // Be explicit about the table we are using.
+ $this->additional_fields['name'] = array('table' => 'filter_formats', 'field' => 'name');
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ }
+
+ function render($values) {
+ $format_name = $this->get_value($values, 'name');
+ if (!$format_name) {
+ // Default or invalid input format.
+ // filter_formats() will reliably return the default format even if the
+ // current user is unprivileged.
+ $format = filter_formats(filter_default_format());
+ return $this->sanitize_value($format->name);
+ }
+ return $this->sanitize_value($format_name);
+ }
+}
diff --git a/sites/all/modules/views/modules/image.views.inc b/sites/all/modules/views/modules/image.views.inc
new file mode 100644
index 000000000..6fc6565b6
--- /dev/null
+++ b/sites/all/modules/views/modules/image.views.inc
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for image.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_field_views_data().
+ *
+ * Views integration for image fields. Adds an image relationship to the default
+ * field data.
+ *
+ * @see field_views_field_default_views_data()
+ */
+function image_field_views_data($field) {
+ $data = field_views_field_default_views_data($field);
+ foreach ($data as $table_name => $table_data) {
+ // Add the relationship only on the fid field.
+ $data[$table_name][$field['field_name'] . '_fid']['relationship'] = array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'file_managed',
+ 'base field' => 'fid',
+ 'label' => t('image from !field_name', array('!field_name' => $field['field_name'])),
+ );
+ }
+
+ return $data;
+}
+
+/**
+ * Implements hook_field_views_data_views_data_alter().
+ *
+ * Views integration to provide reverse relationships on image fields.
+ */
+function image_field_views_data_views_data_alter(&$data, $field) {
+ foreach ($field['bundles'] as $entity_type => $bundles) {
+ $entity_info = entity_get_info($entity_type);
+ $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
+
+ list($label, $all_labels) = field_views_field_label($field['field_name']);
+ $entity = $entity_info['label'];
+ if ($entity == t('Node')) {
+ $entity = t('Content');
+ }
+
+ $data['file_managed'][$pseudo_field_name]['relationship'] = array(
+ 'title' => t('@entity using @field', array('@entity' => $entity, '@field' => $label)),
+ 'help' => t('Relate each @entity with a @field set to the image.', array('@entity' => $entity, '@field' => $label)),
+ 'handler' => 'views_handler_relationship_entity_reverse',
+ 'field_name' => $field['field_name'],
+ 'field table' => _field_sql_storage_tablename($field),
+ 'field field' => $field['field_name'] . '_fid',
+ 'base' => $entity_info['base table'],
+ 'base field' => $entity_info['entity keys']['id'],
+ 'label' => t('!field_name', array('!field_name' => $field['field_name'])),
+ 'join_extra' => array(
+ 0 => array(
+ 'field' => 'entity_type',
+ 'value' => $entity_type,
+ ),
+ 1 => array(
+ 'field' => 'deleted',
+ 'value' => 0,
+ 'numeric' => TRUE,
+ ),
+ ),
+ );
+ }
+}
diff --git a/sites/all/modules/views/modules/locale.views.inc b/sites/all/modules/views/modules/locale.views.inc
new file mode 100644
index 000000000..3bff7dbd4
--- /dev/null
+++ b/sites/all/modules/views/modules/locale.views.inc
@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * @file
+ * Provides views data and handlers for locale.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function locale_views_data() {
+ // Basic table information.
+
+ // Define the base group of this table.
+ $data['locales_source']['table']['group'] = t('Locale source');
+
+ // Advertise this table as a possible base table.
+ $data['locales_source']['table']['base'] = array(
+ 'field' => 'lid',
+ 'title' => t('Locale source'),
+ 'help' => t('A source string for translation, in English or the default site language.'),
+ );
+
+ // lid
+ $data['locales_source']['lid'] = array(
+ 'title' => t('LID'),
+ 'help' => t('The ID of the source string.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ 'numeric' => TRUE,
+ 'validate type' => 'lid',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // location
+ $data['locales_source']['location'] = array(
+ 'group' => t('Locale source'),
+ 'title' => t('Location'),
+ 'help' => t('A description of the location or context of the string.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // Group field
+ $data['locales_source']['textgroup'] = array(
+ 'group' => t('Locale source'),
+ 'title' => t('Group'),
+ 'help' => t('The group the translation is in.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_locale_group',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_locale_group',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_locale_group',
+ ),
+ );
+
+ // Source field
+ $data['locales_source']['source'] = array(
+ 'group' => t('Locale source'),
+ 'title' => t('Source'),
+ 'help' => t('The full original string.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // Version field
+ $data['locales_source']['version'] = array(
+ 'group' => t('Locale source'),
+ 'title' => t('Version'),
+ 'help' => t('The version of Drupal core that this string is for.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_locale_version',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ $data['locales_source']['edit_lid'] = array(
+ 'group' => t('Locale source'),
+ 'field' => array(
+ 'title' => t('Edit link'),
+ 'help' => t('Provide a simple link to edit the translations.'),
+ 'handler' => 'views_handler_field_locale_link_edit',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // Locales target table
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['locales_target']['table']['group'] = t('Locale target');
+
+ // Join information
+ $data['locales_target']['table']['join'] = array(
+ 'locales_source' => array(
+ 'left_field' => 'lid',
+ 'field' => 'lid',
+ ),
+ );
+
+ // Translation field
+ $data['locales_target']['translation'] = array(
+ 'group' => t('Locale target'),
+ 'title' => t('Translation'),
+ 'help' => t('The full translation string.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // Language field
+ $data['locales_target']['language'] = array(
+ 'group' => t('Locale target'),
+ 'title' => t('Language'),
+ 'help' => t('The language this translation is in.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_locale_language',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_locale_language',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_locale_language',
+ ),
+ );
+
+ $data['locales_target']['plid'] = array(
+ 'group' => t('Locale target'),
+ 'title' => t('Singular LID'),
+ 'help' => t('The ID of the parent translation.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ ),
+ );
+
+ // Plural
+ $data['locales_target']['plural'] = array(
+ 'group' => t('Locale target'),
+ 'title' => t('Plural'),
+ 'help' => t('Whether or not the translation is plural.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Plural'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function locale_views_data_alter(&$data) {
+ // Language field
+ $data['node']['language'] = array(
+ 'title' => t('Language'),
+ 'help' => t('The language the content is in.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_node_language',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_node_language',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_language',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_argument_locale_group.inc b/sites/all/modules/views/modules/locale/views_handler_argument_locale_group.inc
new file mode 100644
index 000000000..7ced83667
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_argument_locale_group.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_locale_group.
+ */
+
+/**
+ * Argument handler to accept a language.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_locale_group extends views_handler_argument {
+ function construct() {
+ parent::construct('group');
+ }
+
+ /**
+ * Override the behavior of summary_name(). Get the user friendly version
+ * of the group.
+ */
+ function summary_name($data) {
+ return $this->locale_group($data->{$this->name_alias});
+ }
+
+ /**
+ * Override the behavior of title(). Get the user friendly version
+ * of the language.
+ */
+ function title() {
+ return $this->locale_group($this->argument);
+ }
+
+ function locale_group($group) {
+ $groups = module_invoke_all('locale', 'groups');
+ // Sort the list.
+ asort($groups);
+ return isset($groups[$group]) ? $groups[$group] : t('Unknown group');
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_argument_locale_language.inc b/sites/all/modules/views/modules/locale/views_handler_argument_locale_language.inc
new file mode 100644
index 000000000..316d4b153
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_argument_locale_language.inc
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_locale_language.
+ */
+
+/**
+ * Argument handler to accept a language.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_locale_language extends views_handler_argument {
+ function construct() {
+ parent::construct('language');
+ }
+
+ /**
+ * Override the behavior of summary_name(). Get the user friendly version
+ * of the language.
+ */
+ function summary_name($data) {
+ return $this->locale_language($data->{$this->name_alias});
+ }
+
+ /**
+ * Override the behavior of title(). Get the user friendly version
+ * of the language.
+ */
+ function title() {
+ return $this->locale_language($this->argument);
+ }
+
+ function locale_language($langcode) {
+ $languages = views_language_list();
+ return isset($languages[$langcode]) ? $languages[$langcode] : t('Unknown language');
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_field_locale_group.inc b/sites/all/modules/views/modules/locale/views_handler_field_locale_group.inc
new file mode 100644
index 000000000..393a9487c
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_field_locale_group.inc
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_locale_group.
+ */
+
+/**
+ * Field handler to translate a group into its readable form.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_locale_group extends views_handler_field {
+ function render($values) {
+ $groups = module_invoke_all('locale', 'groups');
+ // Sort the list.
+ asort($groups);
+ $value = $this->get_value($values);
+ return isset($groups[$value]) ? $groups[$value] : '';
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_field_locale_language.inc b/sites/all/modules/views/modules/locale/views_handler_field_locale_language.inc
new file mode 100644
index 000000000..8038e2b30
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_field_locale_language.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_locale_language.
+ */
+
+/**
+ * Field handler to translate a language into its readable form.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_locale_language extends views_handler_field {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['native_language'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['native_language'] = array(
+ '#title' => t('Native language'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['native_language'],
+ '#description' => t('If enabled, the native name of the language will be displayed'),
+ );
+ }
+
+ function render($values) {
+ $languages = locale_language_list(empty($this->options['native_language']) ? 'name' : 'native');
+ $value = $this->get_value($values);
+ return isset($languages[$value]) ? $languages[$value] : '';
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_field_locale_link_edit.inc b/sites/all/modules/views/modules/locale/views_handler_field_locale_link_edit.inc
new file mode 100644
index 000000000..811bcb277
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_field_locale_link_edit.inc
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_locale_link_edit.
+ */
+
+/**
+ * Field handler to present a link to edit a translation.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_locale_link_edit extends views_handler_field {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['lid'] = 'lid';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['text'] = array('default' => '', 'translatable' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text to display'),
+ '#default_value' => $this->options['text'],
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ }
+
+ function access() {
+ // Ensure user has access to edit translations.
+ return user_access('translate interface');
+ }
+
+ function render($values) {
+ $value = $this->get_value($values, 'lid');
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+
+ function render_link($data, $values) {
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('edit');
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = 'admin/config/regional/translate/edit/' . $data;
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_field_node_language.inc b/sites/all/modules/views/modules/locale/views_handler_field_node_language.inc
new file mode 100644
index 000000000..467605b03
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_field_node_language.inc
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_language.
+ */
+
+/**
+ * Field handler to translate a language into its readable form.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_language extends views_handler_field_node {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['native_language'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['native_language'] = array(
+ '#title' => t('Native language'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['native_language'],
+ '#description' => t('If enabled, the native name of the language will be displayed'),
+ );
+ }
+
+ function render($values) {
+ $languages = views_language_list(empty($this->options['native_language']) ? 'name' : 'native');
+ $value = $this->get_value($values);
+ $value = isset($languages[$value]) ? $languages[$value] : '';
+ return $this->render_link($value, $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_filter_locale_group.inc b/sites/all/modules/views/modules/locale/views_handler_filter_locale_group.inc
new file mode 100644
index 000000000..5ec1e920b
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_filter_locale_group.inc
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_locale_group.
+ */
+
+/**
+ * Filter by locale group.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_locale_group extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_title = t('Group');
+ $groups = module_invoke_all('locale', 'groups');
+ // Sort the list.
+ asort($groups);
+ $this->value_options = $groups;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_filter_locale_language.inc b/sites/all/modules/views/modules/locale/views_handler_filter_locale_language.inc
new file mode 100644
index 000000000..eee12a6d4
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_filter_locale_language.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_locale_language.
+ */
+
+/**
+ * Filter by language.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_locale_language extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_title = t('Language');
+ $languages = array(
+ '***CURRENT_LANGUAGE***' => t("Current user's language"),
+ '***DEFAULT_LANGUAGE***' => t("Default site language"),
+ LANGUAGE_NONE => t('No language')
+ );
+ $languages = array_merge($languages, views_language_list());
+ $this->value_options = $languages;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_filter_locale_version.inc b/sites/all/modules/views/modules/locale/views_handler_filter_locale_version.inc
new file mode 100644
index 000000000..717086081
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_filter_locale_version.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_locale_version.
+ */
+
+/**
+ * Filter by version.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_locale_version extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_title = t('Version');
+ // Enable filtering by the current installed Drupal version.
+ $versions = array('***CURRENT_VERSION***' => t('Current installed version'));
+ $result = db_query('SELECT DISTINCT(version) FROM {locales_source} ORDER BY version');
+ foreach ($result as $row) {
+ if (!empty($row->version)) {
+ $versions[$row->version] = $row->version;
+ }
+ }
+ $this->value_options = $versions;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/locale/views_handler_filter_node_language.inc b/sites/all/modules/views/modules/locale/views_handler_filter_node_language.inc
new file mode 100644
index 000000000..7592577b2
--- /dev/null
+++ b/sites/all/modules/views/modules/locale/views_handler_filter_node_language.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_node_language.
+ */
+
+/**
+ * Filter by language.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_node_language extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_title = t('Language');
+ $languages = array(
+ '***CURRENT_LANGUAGE***' => t("Current user's language"),
+ '***DEFAULT_LANGUAGE***' => t("Default site language"),
+ LANGUAGE_NONE => t('No language')
+ );
+ $languages = array_merge($languages, views_language_list());
+ $this->value_options = $languages;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/node.views.inc b/sites/all/modules/views/modules/node.views.inc
new file mode 100644
index 000000000..cc1da5db8
--- /dev/null
+++ b/sites/all/modules/views/modules/node.views.inc
@@ -0,0 +1,785 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for node.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function node_views_data() {
+ // ----------------------------------------------------------------
+ // node table -- basic table information.
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['node']['table']['group'] = t('Content');
+
+ // Advertise this table as a possible base table
+ $data['node']['table']['base'] = array(
+ 'field' => 'nid',
+ 'title' => t('Content'),
+ 'weight' => -10,
+ 'access query tag' => 'node_access',
+ 'defaults' => array(
+ 'field' => 'title',
+ ),
+ );
+ $data['node']['table']['entity type'] = 'node';
+
+ $data['node']['table']['default_relationship'] = array(
+ 'node_revision' => array(
+ 'table' => 'node_revision',
+ 'field' => 'vid',
+ ),
+ );
+
+ // ----------------------------------------------------------------
+ // node table -- fields
+
+ // nid
+ $data['node']['nid'] = array(
+ 'title' => t('Nid'),
+ 'help' => t('The node ID.'), // The help that appears on the UI,
+ // Information for displaying the nid
+ 'field' => array(
+ 'handler' => 'views_handler_field_node',
+ 'click sortable' => TRUE,
+ ),
+ // Information for accepting a nid as an argument
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_nid',
+ 'name field' => 'title', // the field to display in the summary.
+ 'numeric' => TRUE,
+ 'validate type' => 'nid',
+ ),
+ // Information for accepting a nid as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ // Information for sorting on a nid.
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // title
+ // This definition has more items in it than it needs to as an example.
+ $data['node']['title'] = array(
+ 'title' => t('Title'), // The item it appears as on the UI,
+ 'help' => t('The content title.'), // The help that appears on the UI,
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'field' => 'title', // the real field. This could be left out since it is the same.
+ 'group' => t('Content'), // The group it appears in on the UI. Could be left out.
+ 'handler' => 'views_handler_field_node',
+ 'click sortable' => TRUE,
+ 'link_to_node default' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ // Information for accepting a title as a filter
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // created field
+ $data['node']['created'] = array(
+ 'title' => t('Post date'), // The item it appears as on the UI,
+ 'help' => t('The date the content was posted.'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ // changed field
+ $data['node']['changed'] = array(
+ 'title' => t('Updated date'), // The item it appears as on the UI,
+ 'help' => t('The date the content was last updated.'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ // Content type
+ $data['node']['type'] = array(
+ 'title' => t('Type'), // The item it appears as on the UI,
+ 'help' => t('The content type (for example, "blog entry", "forum post", "story", etc).'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_node_type',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_node_type',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_type',
+ ),
+ );
+
+ // published status
+ $data['node']['status'] = array(
+ 'title' => t('Published'),
+ 'help' => t('Whether or not the content is published.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ 'output formats' => array(
+ 'published-notpublished' => array(t('Published'), t('Not published')),
+ ),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Published'),
+ 'type' => 'yes-no',
+ 'use equal' => TRUE, // Use status = 1 instead of status <> 0 in WHERE statment
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // published status + extra
+ $data['node']['status_extra'] = array(
+ 'title' => t('Published or admin'),
+ 'help' => t('Filters out unpublished content if the current user cannot view it.'),
+ 'filter' => array(
+ 'field' => 'status',
+ 'handler' => 'views_handler_filter_node_status',
+ 'label' => t('Published or admin'),
+ ),
+ );
+
+ // promote status
+ $data['node']['promote'] = array(
+ 'title' => t('Promoted to front page'),
+ 'help' => t('Whether or not the content is promoted to the front page.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ 'output formats' => array(
+ 'promoted-notpromoted' => array(t('Promoted'), t('Not promoted')),
+ ),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Promoted to front page'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // sticky
+ $data['node']['sticky'] = array(
+ 'title' => t('Sticky'), // The item it appears as on the UI,
+ 'help' => t('Whether or not the content is sticky.'), // The help that appears on the UI,
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ 'output formats' => array(
+ 'sticky' => array(t('Sticky'), t('Not sticky')),
+ ),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Sticky'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ 'help' => t('Whether or not the content is sticky. To list sticky content first, set this to descending.'),
+ ),
+ );
+
+ // Define some fields based upon views_handler_field_entity in the entity
+ // table so they can be re-used with other query backends.
+ // @see views_handler_field_entity
+
+ $data['views_entity_node']['table']['group'] = t('Content');
+
+ $data['node']['view_node']['moved to'] = array('views_entity_node', 'view_node');
+ $data['views_entity_node']['view_node'] = array(
+ 'field' => array(
+ 'title' => t('Link'),
+ 'help' => t('Provide a simple link to the content.'),
+ 'handler' => 'views_handler_field_node_link',
+ ),
+ );
+
+ $data['node']['edit_node']['moved to'] = array('views_entity_node', 'edit_node');
+ $data['views_entity_node']['edit_node'] = array(
+ 'field' => array(
+ 'title' => t('Edit link'),
+ 'help' => t('Provide a simple link to edit the content.'),
+ 'handler' => 'views_handler_field_node_link_edit',
+ ),
+ );
+
+ $data['node']['delete_node']['moved to'] = array('views_entity_node', 'delete_node');
+ $data['views_entity_node']['delete_node'] = array(
+ 'field' => array(
+ 'title' => t('Delete link'),
+ 'help' => t('Provide a simple link to delete the content.'),
+ 'handler' => 'views_handler_field_node_link_delete',
+ ),
+ );
+
+ $data['node']['path'] = array(
+ 'field' => array(
+ 'title' => t('Path'),
+ 'help' => t('The aliased path to this content.'),
+ 'handler' => 'views_handler_field_node_path',
+ ),
+ );
+
+
+ // Bogus fields for aliasing purposes.
+
+ $data['node']['created_fulldate'] = array(
+ 'title' => t('Created date'),
+ 'help' => t('Date in the form of CCYYMMDD.'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_fulldate',
+ ),
+ );
+
+ $data['node']['created_year_month'] = array(
+ 'title' => t('Created year + month'),
+ 'help' => t('Date in the form of YYYYMM.'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_year_month',
+ ),
+ );
+
+ $data['node']['created_year'] = array(
+ 'title' => t('Created year'),
+ 'help' => t('Date in the form of YYYY.'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_year',
+ ),
+ );
+
+ $data['node']['created_month'] = array(
+ 'title' => t('Created month'),
+ 'help' => t('Date in the form of MM (01 - 12).'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_month',
+ ),
+ );
+
+ $data['node']['created_day'] = array(
+ 'title' => t('Created day'),
+ 'help' => t('Date in the form of DD (01 - 31).'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_day',
+ ),
+ );
+
+ $data['node']['created_week'] = array(
+ 'title' => t('Created week'),
+ 'help' => t('Date in the form of WW (01 - 53).'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_week',
+ ),
+ );
+
+ $data['node']['changed_fulldate'] = array(
+ 'title' => t('Updated date'),
+ 'help' => t('Date in the form of CCYYMMDD.'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_fulldate',
+ ),
+ );
+
+ $data['node']['changed_year_month'] = array(
+ 'title' => t('Updated year + month'),
+ 'help' => t('Date in the form of YYYYMM.'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_year_month',
+ ),
+ );
+
+ $data['node']['changed_year'] = array(
+ 'title' => t('Updated year'),
+ 'help' => t('Date in the form of YYYY.'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_year',
+ ),
+ );
+
+ $data['node']['changed_month'] = array(
+ 'title' => t('Updated month'),
+ 'help' => t('Date in the form of MM (01 - 12).'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_month',
+ ),
+ );
+
+ $data['node']['changed_day'] = array(
+ 'title' => t('Updated day'),
+ 'help' => t('Date in the form of DD (01 - 31).'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_day',
+ ),
+ );
+
+ $data['node']['changed_week'] = array(
+ 'title' => t('Updated week'),
+ 'help' => t('Date in the form of WW (01 - 53).'),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_week',
+ ),
+ );
+
+ // uid field
+ $data['node']['uid'] = array(
+ 'title' => t('Author uid'),
+ 'help' => t('The user authoring the content. If you need more fields than the uid add the content: author relationship'),
+ 'relationship' => array(
+ 'title' => t('Author'),
+ 'help' => t('Relate content to the user who created it.'),
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'users',
+ 'field' => 'uid',
+ 'label' => t('author'),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_user_name',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ 'field' => array(
+ 'handler' => 'views_handler_field_user',
+ ),
+ );
+
+ $data['node']['uid_revision'] = array(
+ 'title' => t('User has a revision'),
+ 'help' => t('All nodes where a certain user has a revision'),
+ 'real field' => 'nid',
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_node_uid_revision',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_uid_revision',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // Content revision table
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['node_revisions']['moved to'] = 'node_revision';
+ $data['node_revision']['table']['entity type'] = 'node';
+ $data['node_revision']['table']['revision'] = TRUE;
+ $data['node_revision']['table']['group'] = t('Content revision');
+ // Support the conversion of the field body
+ $data['node_revisions']['body']['moved to'] = array('field_revision_data', 'body-revision_id');
+
+ // Advertise this table as a possible base table
+ $data['node_revision']['table']['base'] = array(
+ 'field' => 'vid',
+ 'title' => t('Content revision'),
+ 'help' => t('Content revision is a history of changes to content.'),
+ 'defaults' => array(
+ 'field' => 'title',
+ ),
+ );
+
+ // For other base tables, explain how we join
+ $data['node_revision']['table']['join'] = array(
+ // Directly links to node table.
+ 'node' => array(
+ 'left_field' => 'vid',
+ 'field' => 'vid',
+ ),
+ );
+
+ $data['node_revision']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'nid',
+ ),
+ );
+
+ // uid field for node revision
+ $data['node_revision']['uid'] = array(
+ 'title' => t('User'),
+ 'help' => t('Relate a content revision to the user who created the revision.'),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'users',
+ 'base field' => 'uid',
+ 'label' => t('revision user'),
+ ),
+ );
+
+ // nid
+ $data['node_revision']['nid'] = array(
+ 'title' => t('Nid'),
+ // The help that appears on the UI.
+ 'help' => t('The revision NID of the content revision.'),
+ // Information for displaying the nid.
+ 'field' => array(
+ 'click sortable' => TRUE,
+ ),
+ // Information for accepting a nid as an argument.
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_nid',
+ 'click sortable' => TRUE,
+ 'numeric' => TRUE,
+ ),
+ // Information for accepting a nid as a filter.
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ // Information for sorting on a nid.
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'node',
+ 'base field' => 'nid',
+ 'title' => t('Content'),
+ 'label' => t('Get the actual content from a content revision.'),
+ ),
+ );
+
+ // vid
+ $data['node_revision']['vid'] = array(
+ 'title' => t('Vid'),
+ // The help that appears on the UI.
+ 'help' => t('The revision ID of the content revision.'),
+ // Information for displaying the vid.
+ 'field' => array(
+ 'click sortable' => TRUE,
+ ),
+ // Information for accepting a vid as an argument.
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_vid',
+ 'click sortable' => TRUE,
+ 'numeric' => TRUE,
+ ),
+ // Information for accepting a vid as a filter.
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ // Information for sorting on a vid.
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'node',
+ 'base field' => 'vid',
+ 'title' => t('Content'),
+ 'label' => t('Get the actual content from a content revision.'),
+ ),
+ );
+
+ // title
+ $data['node_revision']['title'] = array(
+ 'title' => t('Title'), // The item it appears as on the UI,
+ 'help' => t('The content title.'), // The help that appears on the UI,
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'field' => 'title', // the real field
+ 'handler' => 'views_handler_field_node_revision',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // log field
+ $data['node_revision']['log'] = array(
+ 'title' => t('Log message'), // The item it appears as on the UI,
+ 'help' => t('The log message entered when the revision was created.'), // The help that appears on the UI,
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_xss',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // revision timestamp
+ // changed field
+ $data['node_revision']['timestamp'] = array(
+ 'title' => t('Updated date'), // The item it appears as on the UI,
+ 'help' => t('The date the node was last updated.'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ $data['node_revision']['link_to_revision'] = array(
+ 'field' => array(
+ 'title' => t('Link'),
+ 'help' => t('Provide a simple link to the revision.'),
+ 'handler' => 'views_handler_field_node_revision_link',
+ ),
+ );
+
+ $data['node_revision']['revert_revision'] = array(
+ 'field' => array(
+ 'title' => t('Revert link'),
+ 'help' => t('Provide a simple link to revert to the revision.'),
+ 'handler' => 'views_handler_field_node_revision_link_revert',
+ ),
+ );
+
+ $data['node_revision']['delete_revision'] = array(
+ 'field' => array(
+ 'title' => t('Delete link'),
+ 'help' => t('Provide a simple link to delete the content revision.'),
+ 'handler' => 'views_handler_field_node_revision_link_delete',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // Node access table
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['node_access']['table']['group'] = t('Content access');
+
+ // For other base tables, explain how we join
+ $data['node_access']['table']['join'] = array(
+ // Directly links to node table.
+ 'node' => array(
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ );
+ // nid field
+ $data['node_access']['nid'] = array(
+ 'title' => t('Access'),
+ 'help' => t('Filter by access.'),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_node_access',
+ 'help' => t('Filter for content by view access. <strong>Not necessary if you are using node as your base table.</strong>'),
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // History table
+
+ // We're actually defining a specific instance of the table, so let's
+ // alias it so that we can later add the real table for other purposes if we
+ // need it.
+ $data['history_user']['moved to'] = 'history';
+ $data['history']['table']['group'] = t('Content');
+
+ // Explain how this table joins to others.
+ $data['history']['table']['join'] = array(
+ // Directly links to node table.
+ 'node' => array(
+ 'table' => 'history',
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ 'extra' => array(
+ array('field' => 'uid', 'value' => '***CURRENT_USER***', 'numeric' => TRUE),
+ ),
+ ),
+ );
+
+ $data['history']['timestamp'] = array(
+ 'title' => t('Has new content'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_history_user_timestamp',
+ 'help' => t('Show a marker if the content is new or updated.'),
+ ),
+ 'filter' => array(
+ 'help' => t('Show only content that is new or updated.'),
+ 'handler' => 'views_handler_filter_history_user_timestamp',
+ ),
+ );
+ return $data;
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function node_views_plugins() {
+ return array(
+ 'module' => 'views', // This just tells our themes are elsewhere.
+ 'row' => array(
+ 'node' => array(
+ 'title' => t('Content'),
+ 'help' => t('Display the content with standard node view.'),
+ 'handler' => 'views_plugin_row_node_view',
+ 'path' => drupal_get_path('module', 'views') . '/modules/node', // not necessary for most modules
+ 'base' => array('node'), // only works with 'node' as base.
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-node',
+ ),
+ 'node_rss' => array(
+ 'title' => t('Content'),
+ 'help' => t('Display the content with standard node view.'),
+ 'handler' => 'views_plugin_row_node_rss',
+ 'path' => drupal_get_path('module', 'views') . '/modules/node', // not necessary for most modules
+ 'theme' => 'views_view_row_rss',
+ 'base' => array('node'), // only works with 'node' as base.
+ 'uses options' => TRUE,
+ 'type' => 'feed',
+ 'help topic' => 'style-node-rss',
+ ),
+ ),
+ 'argument validator' => array(
+ 'node' => array(
+ 'title' => t('Content'),
+ 'handler' => 'views_plugin_argument_validate_node',
+ ),
+ ),
+ 'argument default' => array(
+ 'node' => array(
+ 'title' => t('Content ID from URL'),
+ 'handler' => 'views_plugin_argument_default_node'
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_preprocess_node().
+ */
+function node_row_node_view_preprocess_node(&$vars) {
+ $node = $vars['node'];
+ $options = $vars['view']->style_plugin->row_plugin->options;
+
+ // Prevent the comment form from showing up if this is not a page display.
+ if ($vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) {
+ $node->comment = FALSE;
+ }
+
+ if (!$options['links']) {
+ unset($vars['content']['links']);
+ }
+
+ if (!empty($options['comments']) && user_access('access comments') && $node->comment) {
+ $vars['content']['comments'] = comment_node_page_additions($node);
+ }
+}
+
+/**
+ * Implements hook_views_query_substitutions().
+ */
+function node_views_query_substitutions() {
+ return array(
+ '***ADMINISTER_NODES***' => intval(user_access('administer nodes')),
+ '***VIEW_OWN_UNPUBLISHED_NODES***' => intval(user_access('view own unpublished content')),
+ '***BYPASS_NODE_ACCESS***' => intval(user_access('bypass node access')),
+ );
+}
+
+/**
+ * Implements hook_views_analyze().
+ */
+function node_views_analyze($view) {
+ $ret = array();
+ // Check for something other than the default display:
+ if ($view->base_table == 'node') {
+ foreach ($view->display as $id => $display) {
+ if (empty($display->handler)) {
+ continue;
+ }
+ if (!$display->handler->is_defaulted('access') || !$display->handler->is_defaulted('filters')) {
+ // check for no access control
+ $access = $display->handler->get_option('access');
+ if (empty($access['type']) || $access['type'] == 'none') {
+ $select = db_select('role', 'r');
+ $select->innerJoin('role_permission', 'p', 'r.rid = p.rid');
+ $result = $select->fields('r', array('name'))
+ ->fields('p', array('permission'))
+ ->condition('r.name', array('anonymous user', 'authenticated user'), 'IN')
+ ->condition('p.permission', 'access content')
+ ->execute();
+
+ foreach ($result as $role) {
+ $role->safe = TRUE;
+ $roles[$role->name] = $role;
+ }
+ if (!($roles['anonymous user']->safe && $roles['authenticated user']->safe)) {
+ $ret[] = views_ui_analysis(t('Some roles lack permission to access content, but display %display has no access control.', array('%display' => $display->display_title)), 'warning');
+ }
+ $filters = $display->handler->get_option('filters');
+ foreach ($filters as $filter) {
+ if ($filter['table'] == 'node' && ($filter['field'] == 'status' || $filter['field'] == 'status_extra')) {
+ continue 2;
+ }
+ }
+ $ret[] = views_ui_analysis(t('Display %display has no access control but does not contain a filter for published nodes.', array('%display' => $display->display_title)), 'warning');
+ }
+ }
+ }
+ }
+ foreach ($view->display as $id => $display) {
+ if ($display->display_plugin == 'page') {
+ if ($display->handler->get_option('path') == 'node/%') {
+ $ret[] = views_ui_analysis(t('Display %display has set node/% as path. This will not produce what you want. If you want to have multiple versions of the node view, use panels.', array('%display' => $display->display_title)), 'warning');
+ }
+ }
+ }
+
+ return $ret;
+}
diff --git a/sites/all/modules/views/modules/node.views_default.inc b/sites/all/modules/views/modules/node.views_default.inc
new file mode 100644
index 000000000..de619c1a1
--- /dev/null
+++ b/sites/all/modules/views/modules/node.views_default.inc
@@ -0,0 +1,318 @@
+<?php
+
+/**
+ * @file
+ * Bulk export of views_default objects generated by Bulk export module.
+ */
+
+/**
+ * Implementation of hook_views_default_views()
+ */
+function node_views_default_views() {
+ $views = array();
+
+ $view = new view;
+ $view->name = 'archive';
+ $view->description = 'Display a list of months that link to content for that month.';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Archive';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'Monthly archive';
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access content';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ /* Sort criterion: Content: Post date */
+ $handler->display->display_options['sorts']['created']['id'] = 'created';
+ $handler->display->display_options['sorts']['created']['table'] = 'node';
+ $handler->display->display_options['sorts']['created']['field'] = 'created';
+ $handler->display->display_options['sorts']['created']['order'] = 'DESC';
+ /* Contextual filter: Content: Created year + month */
+ $handler->display->display_options['arguments']['created_year_month']['id'] = 'created_year_month';
+ $handler->display->display_options['arguments']['created_year_month']['table'] = 'node';
+ $handler->display->display_options['arguments']['created_year_month']['field'] = 'created_year_month';
+ $handler->display->display_options['arguments']['created_year_month']['default_action'] = 'summary';
+ $handler->display->display_options['arguments']['created_year_month']['exception']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['created_year_month']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['created_year_month']['title'] = '%1';
+ $handler->display->display_options['arguments']['created_year_month']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['created_year_month']['summary']['sort_order'] = 'desc';
+ $handler->display->display_options['arguments']['created_year_month']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['created_year_month']['summary_options']['override'] = TRUE;
+ $handler->display->display_options['arguments']['created_year_month']['summary_options']['items_per_page'] = '30';
+ $handler->display->display_options['arguments']['created_year_month']['specify_validation'] = 1;
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'archive';
+
+ /* Display: Block */
+ $handler = $view->new_display('block', 'Block', 'block');
+ $handler->display->display_options['defaults']['arguments'] = FALSE;
+ /* Contextual filter: Content: Created year + month */
+ $handler->display->display_options['arguments']['created_year_month']['id'] = 'created_year_month';
+ $handler->display->display_options['arguments']['created_year_month']['table'] = 'node';
+ $handler->display->display_options['arguments']['created_year_month']['field'] = 'created_year_month';
+ $handler->display->display_options['arguments']['created_year_month']['default_action'] = 'summary';
+ $handler->display->display_options['arguments']['created_year_month']['exception']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['created_year_month']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['created_year_month']['title'] = '%1';
+ $handler->display->display_options['arguments']['created_year_month']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['created_year_month']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['created_year_month']['summary_options']['items_per_page'] = '30';
+ $handler->display->display_options['arguments']['created_year_month']['specify_validation'] = 1;
+ $translatables['archive'] = array(
+ t('Master'),
+ t('Monthly archive'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('All'),
+ t('%1'),
+ t('Page'),
+ t('Block'),
+ );
+
+ $views['archive'] = $view;
+
+ $view = new view;
+ $view->name = 'frontpage';
+ $view->description = 'Emulates the default Drupal front page; you may set the default home page path to this view to make it your front page.';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Front page';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access content';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ $handler->display->display_options['row_options']['links'] = 1;
+ /* Sort criterion: Content: Sticky */
+ $handler->display->display_options['sorts']['sticky']['id'] = 'sticky';
+ $handler->display->display_options['sorts']['sticky']['table'] = 'node';
+ $handler->display->display_options['sorts']['sticky']['field'] = 'sticky';
+ $handler->display->display_options['sorts']['sticky']['order'] = 'DESC';
+ /* Sort criterion: Content: Post date */
+ $handler->display->display_options['sorts']['created']['id'] = 'created';
+ $handler->display->display_options['sorts']['created']['table'] = 'node';
+ $handler->display->display_options['sorts']['created']['field'] = 'created';
+ $handler->display->display_options['sorts']['created']['order'] = 'DESC';
+ /* Filter criterion: Content: Promoted to front page */
+ $handler->display->display_options['filters']['promote']['id'] = 'promote';
+ $handler->display->display_options['filters']['promote']['table'] = 'node';
+ $handler->display->display_options['filters']['promote']['field'] = 'promote';
+ $handler->display->display_options['filters']['promote']['value'] = '1';
+ $handler->display->display_options['filters']['promote']['group'] = 0;
+ $handler->display->display_options['filters']['promote']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = '1';
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'frontpage';
+
+ /* Display: Feed */
+ $handler = $view->new_display('feed', 'Feed', 'feed');
+ $handler->display->display_options['defaults']['title'] = FALSE;
+ $handler->display->display_options['title'] = 'Front page feed';
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['style_plugin'] = 'rss';
+ $handler->display->display_options['row_plugin'] = 'node_rss';
+ $handler->display->display_options['path'] = 'rss.xml';
+ $handler->display->display_options['displays'] = array(
+ 'default' => 'default',
+ 'page' => 'page',
+ );
+ $handler->display->display_options['sitename_title'] = '1';
+ $translatables['frontpage'] = array(
+ t('Master'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('Page'),
+ t('Feed'),
+ t('Front page feed'),
+ );
+
+ $views['frontpage'] = $view;
+
+ $view = new view;
+ $view->name = 'glossary';
+ $view->description = 'A list of all content, by letter.';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Glossary';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['use_ajax'] = TRUE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access content';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = 36;
+ $handler->display->display_options['style_plugin'] = 'table';
+ $handler->display->display_options['style_options']['columns'] = array(
+ 'title' => 'title',
+ 'name' => 'name',
+ 'changed' => 'changed',
+ );
+ $handler->display->display_options['style_options']['default'] = 'title';
+ $handler->display->display_options['style_options']['info'] = array(
+ 'title' => array(
+ 'sortable' => 1,
+ 'separator' => '',
+ ),
+ 'name' => array(
+ 'sortable' => 1,
+ 'separator' => '',
+ ),
+ 'changed' => array(
+ 'sortable' => 1,
+ 'separator' => '',
+ ),
+ );
+ $handler->display->display_options['style_options']['override'] = 1;
+ $handler->display->display_options['style_options']['sticky'] = 0;
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+ /* Field: User: Name */
+ $handler->display->display_options['fields']['name']['id'] = 'name';
+ $handler->display->display_options['fields']['name']['table'] = 'users';
+ $handler->display->display_options['fields']['name']['field'] = 'name';
+ $handler->display->display_options['fields']['name']['label'] = 'Author';
+ $handler->display->display_options['fields']['name']['link_to_user'] = 1;
+ /* Field: Content: Updated date */
+ $handler->display->display_options['fields']['changed']['id'] = 'changed';
+ $handler->display->display_options['fields']['changed']['table'] = 'node';
+ $handler->display->display_options['fields']['changed']['field'] = 'changed';
+ $handler->display->display_options['fields']['changed']['label'] = 'Last update';
+ $handler->display->display_options['fields']['changed']['date_format'] = 'large';
+ /* Contextual filter: Content: Title */
+ $handler->display->display_options['arguments']['title']['id'] = 'title';
+ $handler->display->display_options['arguments']['title']['table'] = 'node';
+ $handler->display->display_options['arguments']['title']['field'] = 'title';
+ $handler->display->display_options['arguments']['title']['default_action'] = 'default';
+ $handler->display->display_options['arguments']['title']['exception']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['title']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['title']['default_argument_options']['argument'] = 'a';
+ $handler->display->display_options['arguments']['title']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['title']['specify_validation'] = 1;
+ $handler->display->display_options['arguments']['title']['glossary'] = 1;
+ $handler->display->display_options['arguments']['title']['limit'] = '1';
+ $handler->display->display_options['arguments']['title']['case'] = 'upper';
+ $handler->display->display_options['arguments']['title']['path_case'] = 'lower';
+ $handler->display->display_options['arguments']['title']['transform_dash'] = 0;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'glossary';
+ $handler->display->display_options['menu']['type'] = 'normal';
+ $handler->display->display_options['menu']['title'] = 'Glossary';
+ $handler->display->display_options['menu']['weight'] = '0';
+
+ /* Display: Attachment */
+ $handler = $view->new_display('attachment', 'Attachment', 'attachment');
+ $handler->display->display_options['pager']['type'] = 'none';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['defaults']['arguments'] = FALSE;
+ /* Contextual filter: Content: Title */
+ $handler->display->display_options['arguments']['title']['id'] = 'title';
+ $handler->display->display_options['arguments']['title']['table'] = 'node';
+ $handler->display->display_options['arguments']['title']['field'] = 'title';
+ $handler->display->display_options['arguments']['title']['default_action'] = 'summary';
+ $handler->display->display_options['arguments']['title']['exception']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['title']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['title']['default_argument_options']['argument'] = 'a';
+ $handler->display->display_options['arguments']['title']['summary']['format'] = 'unformatted_summary';
+ $handler->display->display_options['arguments']['title']['summary_options']['items_per_page'] = '25';
+ $handler->display->display_options['arguments']['title']['summary_options']['inline'] = 1;
+ $handler->display->display_options['arguments']['title']['summary_options']['separator'] = ' | ';
+ $handler->display->display_options['arguments']['title']['specify_validation'] = 1;
+ $handler->display->display_options['arguments']['title']['glossary'] = 1;
+ $handler->display->display_options['arguments']['title']['limit'] = '1';
+ $handler->display->display_options['arguments']['title']['case'] = 'upper';
+ $handler->display->display_options['arguments']['title']['path_case'] = 'lower';
+ $handler->display->display_options['arguments']['title']['transform_dash'] = 0;
+ $handler->display->display_options['displays'] = array(
+ 'default' => 'default',
+ 'page' => 'page',
+ );
+ $handler->display->display_options['inherit_arguments'] = 0;
+ $translatables['glossary'] = array(
+ t('Master'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('Title'),
+ t('Author'),
+ t('Last update'),
+ t('All'),
+ t('Page'),
+ t('Attachment'),
+ );
+
+ $views['glossary'] = $view;
+
+ return $views;
+}
diff --git a/sites/all/modules/views/modules/node.views_template.inc b/sites/all/modules/views/modules/node.views_template.inc
new file mode 100644
index 000000000..ad894146b
--- /dev/null
+++ b/sites/all/modules/views/modules/node.views_template.inc
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Contains views templates on behalf of the node module.
+ */
+
+function node_views_templates() {
+ // Only the standard install profile has the image field provided so only show it for it.
+ if (variable_get('install_profile', 'standard') != 'standard') {
+ return array();
+ }
+ $view = new view;
+ $view->name = 'image_gallery';
+ $view->description = 'Shows all images which was uploaded on the "field_image" field';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->human_name = 'Image Gallery';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Defaults */
+ $handler = $view->new_display('default', 'Defaults', 'default');
+ $handler->display->display_options['title'] = 'Image gallery';
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access content';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '24';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['pager']['options']['expose']['items_per_page_options_all'] = 0;
+ $handler->display->display_options['style_plugin'] = 'grid';
+ $handler->display->display_options['style_options']['fill_single_line'] = 1;
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Image */
+ $handler->display->display_options['fields']['field_image']['id'] = 'field_image';
+ $handler->display->display_options['fields']['field_image']['table'] = 'field_data_field_image';
+ $handler->display->display_options['fields']['field_image']['field'] = 'field_image';
+ $handler->display->display_options['fields']['field_image']['label'] = '';
+ $handler->display->display_options['fields']['field_image']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['field_image']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['field_image']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['field_image']['alter']['external'] = 0;
+ $handler->display->display_options['fields']['field_image']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['field_image']['alter']['nl2br'] = 0;
+ $handler->display->display_options['fields']['field_image']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['field_image']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['field_image']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['field_image']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['field_image']['element_label_colon'] = 1;
+ $handler->display->display_options['fields']['field_image']['element_default_classes'] = 1;
+ $handler->display->display_options['fields']['field_image']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['field_image']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['field_image']['click_sort_column'] = 'fid';
+ $handler->display->display_options['fields']['field_image']['settings'] = array(
+ 'image_style' => 'thumbnail',
+ 'image_link' => 'content',
+ );
+ $handler->display->display_options['fields']['field_image']['field_api_classes'] = 0;
+ /* Field: User: Name */
+ $handler->display->display_options['fields']['name']['id'] = 'name';
+ $handler->display->display_options['fields']['name']['table'] = 'users';
+ $handler->display->display_options['fields']['name']['field'] = 'name';
+ $handler->display->display_options['fields']['name']['label'] = 'Author';
+ $handler->display->display_options['fields']['name']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['external'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['nl2br'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['name']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['name']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['name']['element_label_colon'] = 1;
+ $handler->display->display_options['fields']['name']['element_default_classes'] = 1;
+ $handler->display->display_options['fields']['name']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['name']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['name']['link_to_user'] = 1;
+ $handler->display->display_options['fields']['name']['overwrite_anonymous'] = 0;
+ /* Contextual filter: Content: Has taxonomy term ID */
+ $handler->display->display_options['arguments']['tid']['id'] = 'tid';
+ $handler->display->display_options['arguments']['tid']['table'] = 'taxonomy_index';
+ $handler->display->display_options['arguments']['tid']['field'] = 'tid';
+ $handler->display->display_options['arguments']['tid']['default_action'] = 'summary';
+ $handler->display->display_options['arguments']['tid']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['tid']['default_argument_skip_url'] = 0;
+ $handler->display->display_options['arguments']['tid']['summary']['number_of_records'] = '1';
+ $handler->display->display_options['arguments']['tid']['summary']['format'] = 'unformatted_summary';
+ $handler->display->display_options['arguments']['tid']['summary_options']['items_per_page'] = '25';
+ $handler->display->display_options['arguments']['tid']['summary_options']['inline'] = 0;
+ $handler->display->display_options['arguments']['tid']['break_phrase'] = 0;
+ $handler->display->display_options['arguments']['tid']['add_table'] = 0;
+ $handler->display->display_options['arguments']['tid']['require_value'] = 0;
+ $handler->display->display_options['arguments']['tid']['reduce_duplicates'] = 0;
+ $handler->display->display_options['arguments']['tid']['set_breadcrumb'] = 0;
+ /* Filter criterion: Content: Image (field_image) - fid */
+ $handler->display->display_options['filters']['field_image_fid']['id'] = 'field_image_fid';
+ $handler->display->display_options['filters']['field_image_fid']['table'] = 'field_data_field_image';
+ $handler->display->display_options['filters']['field_image_fid']['field'] = 'field_image_fid';
+ $handler->display->display_options['filters']['field_image_fid']['operator'] = 'not empty';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = '1';
+
+ /* Display: Gallery page */
+ $handler = $view->new_display('page', 'Gallery page', 'page_1');
+ $handler->display->display_options['path'] = 'gallery';
+ $translatables['image_gallery'] = array(
+ t('Defaults'),
+ t('Image gallery'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('Author'),
+ t('All'),
+ t('Gallery page'),
+ );
+
+ $views[$view->name] = $view;
+
+ return $views;
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_argument_dates_various.inc b/sites/all/modules/views/modules/node/views_handler_argument_dates_various.inc
new file mode 100644
index 000000000..5f4e4b2ba
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_argument_dates_various.inc
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * @file
+ * Handlers for various date arguments.
+ *
+ * @ingroup views_argument_handlers
+ */
+
+/**
+ * Argument handler for a full date (CCYYMMDD)
+ */
+class views_handler_argument_node_created_fulldate extends views_handler_argument_date {
+ /**
+ * Constructor implementation
+ */
+ function construct() {
+ parent::construct();
+ $this->format = 'F j, Y';
+ $this->arg_format = 'Ymd';
+ $this->formula = views_date_sql_format($this->arg_format, "***table***.$this->real_field");
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function summary_name($data) {
+ $created = $data->{$this->name_alias};
+ return format_date(strtotime($created . " 00:00:00 UTC"), 'custom', $this->format, 'UTC');
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function title() {
+ return format_date(strtotime($this->argument . " 00:00:00 UTC"), 'custom', $this->format, 'UTC');
+ }
+}
+
+/**
+ * Argument handler for a year (CCYY)
+ */
+class views_handler_argument_node_created_year extends views_handler_argument_date {
+ /**
+ * Constructor implementation
+ */
+ function construct() {
+ parent::construct();
+ $this->arg_format = 'Y';
+ $this->formula = views_date_sql_extract('YEAR', "***table***.$this->real_field");
+ }
+}
+
+/**
+ * Argument handler for a year plus month (CCYYMM)
+ */
+class views_handler_argument_node_created_year_month extends views_handler_argument_date {
+ /**
+ * Constructor implementation
+ */
+ function construct() {
+ parent::construct();
+ $this->format = 'F Y';
+ $this->arg_format = 'Ym';
+ $this->formula = views_date_sql_format($this->arg_format, "***table***.$this->real_field");
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function summary_name($data) {
+ $created = $data->{$this->name_alias};
+ return format_date(strtotime($created . "15" . " 00:00:00 UTC"), 'custom', $this->format, 'UTC');
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function title() {
+ return format_date(strtotime($this->argument . "15" . " 00:00:00 UTC"), 'custom', $this->format, 'UTC');
+ }
+}
+
+/**
+ * Argument handler for a month (MM)
+ */
+class views_handler_argument_node_created_month extends views_handler_argument_date {
+ /**
+ * Constructor implementation
+ */
+ function construct() {
+ parent::construct();
+ $this->formula = views_date_sql_extract('MONTH', "***table***.$this->real_field");
+ $this->format = 'F';
+ $this->arg_format = 'm';
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function summary_name($data) {
+ $month = str_pad($data->{$this->name_alias}, 2, '0', STR_PAD_LEFT);
+ return format_date(strtotime("2005" . $month . "15" . " 00:00:00 UTC" ), 'custom', $this->format, 'UTC');
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function title() {
+ $month = str_pad($this->argument, 2, '0', STR_PAD_LEFT);
+ return format_date(strtotime("2005" . $month . "15" . " 00:00:00 UTC"), 'custom', $this->format, 'UTC');
+ }
+
+ function summary_argument($data) {
+ // Make sure the argument contains leading zeroes.
+ return str_pad($data->{$this->base_alias}, 2, '0', STR_PAD_LEFT);
+ }
+}
+
+/**
+ * Argument handler for a day (DD)
+ */
+class views_handler_argument_node_created_day extends views_handler_argument_date {
+ /**
+ * Constructor implementation
+ */
+ function construct() {
+ parent::construct();
+ $this->formula = views_date_sql_extract('DAY', "***table***.$this->real_field");
+ $this->format = 'j';
+ $this->arg_format = 'd';
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function summary_name($data) {
+ $day = str_pad($data->{$this->name_alias}, 2, '0', STR_PAD_LEFT);
+ // strtotime respects server timezone, so we need to set the time fixed as utc time
+ return format_date(strtotime("2005" . "05" . $day . " 00:00:00 UTC"), 'custom', $this->format, 'UTC');
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function title() {
+ $day = str_pad($this->argument, 2, '0', STR_PAD_LEFT);
+ return format_date(strtotime("2005" . "05" . $day . " 00:00:00 UTC"), 'custom', $this->format, 'UTC');
+ }
+
+ function summary_argument($data) {
+ // Make sure the argument contains leading zeroes.
+ return str_pad($data->{$this->base_alias}, 2, '0', STR_PAD_LEFT);
+ }
+}
+
+/**
+ * Argument handler for a week.
+ */
+class views_handler_argument_node_created_week extends views_handler_argument_date {
+ /**
+ * Constructor implementation
+ */
+ function construct() {
+ parent::construct();
+ $this->arg_format = 'w';
+ $this->formula = views_date_sql_extract('WEEK', "***table***.$this->real_field");
+ }
+
+ /**
+ * Provide a link to the next level of the view
+ */
+ function summary_name($data) {
+ $created = $data->{$this->name_alias};
+ return t('Week @week', array('@week' => $created));
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_argument_node_language.inc b/sites/all/modules/views/modules/node/views_handler_argument_node_language.inc
new file mode 100644
index 000000000..170388a3e
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_argument_node_language.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_node_language.
+ */
+
+/**
+ * Argument handler to accept a language.
+ */
+class views_handler_argument_node_language extends views_handler_argument {
+ function construct() {
+ parent::construct('language');
+ }
+
+ /**
+ * Override the behavior of summary_name(). Get the user friendly version
+ * of the language.
+ */
+ function summary_name($data) {
+ return $this->node_language($data->{$this->name_alias});
+ }
+
+ /**
+ * Override the behavior of title(). Get the user friendly version of the
+ * node type.
+ */
+ function title() {
+ return $this->node_language($this->argument);
+ }
+
+ function node_language($langcode) {
+ $languages = views_language_list();
+ return isset($languages[$langcode]) ? $languages[$langcode] : t('Unknown language');
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_argument_node_nid.inc b/sites/all/modules/views/modules/node/views_handler_argument_node_nid.inc
new file mode 100644
index 000000000..b0dbee096
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_argument_node_nid.inc
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Provide node nid argument handler.
+ */
+
+/**
+ * Argument handler to accept a node id.
+ */
+class views_handler_argument_node_nid extends views_handler_argument_numeric {
+ /**
+ * Override the behavior of title(). Get the title of the node.
+ */
+ function title_query() {
+ $titles = array();
+
+ $result = db_query("SELECT n.title FROM {node} n WHERE n.nid IN (:nids)", array(':nids' => $this->value));
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->title);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_argument_node_type.inc b/sites/all/modules/views/modules/node/views_handler_argument_node_type.inc
new file mode 100644
index 000000000..ea99d7c64
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_argument_node_type.inc
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_node_type.
+ */
+
+/**
+ * Argument handler to accept a node type.
+ */
+class views_handler_argument_node_type extends views_handler_argument_string {
+ function construct() {
+ parent::construct('type');
+ }
+
+ /**
+ * Override the behavior of summary_name(). Get the user friendly version
+ * of the node type.
+ */
+ function summary_name($data) {
+ return $this->node_type($data->{$this->name_alias});
+ }
+
+ /**
+ * Override the behavior of title(). Get the user friendly version of the
+ * node type.
+ */
+ function title() {
+ return $this->node_type($this->argument);
+ }
+
+ function node_type($type) {
+ $output = node_type_get_name($type);
+ if (empty($output)) {
+ $output = t('Unknown content type');
+ }
+ return check_plain($output);
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_argument_node_uid_revision.inc b/sites/all/modules/views/modules/node/views_handler_argument_node_uid_revision.inc
new file mode 100644
index 000000000..142882a39
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_argument_node_uid_revision.inc
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Defintion of views_handler_argument_node_uid_revision.
+ */
+
+/**
+ * Filter handler to accept a user id to check for nodes that
+ * user posted or created a revision on.
+ */
+class views_handler_argument_node_uid_revision extends views_handler_argument_comment_user_uid {
+ function query($group_by = FALSE) {
+ $this->ensure_my_table();
+ $placeholder = $this->placeholder();
+ $this->query->add_where_expression(0, "$this->table_alias.uid = $placeholder OR ((SELECT COUNT(*) FROM {node_revision} nr WHERE nr.uid = $placeholder AND nr.nid = $this->table_alias.nid) > 0)", array($placeholder => $this->argument));
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_argument_node_vid.inc b/sites/all/modules/views/modules/node/views_handler_argument_node_vid.inc
new file mode 100644
index 000000000..1f970ad17
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_argument_node_vid.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Provide node vid argument handler.
+ */
+
+/**
+ * Argument handler to accept a node revision id.
+ */
+class views_handler_argument_node_vid extends views_handler_argument_numeric {
+ // No constructor is necessary.
+
+ /**
+ * Override the behavior of title(). Get the title of the revision.
+ */
+ function title_query() {
+ $titles = array();
+
+ $result = db_query("SELECT n.title FROM {node_revision} n WHERE n.vid IN (:vids)", array(':vids' => $this->value));
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->title);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_history_user_timestamp.inc b/sites/all/modules/views/modules/node/views_handler_field_history_user_timestamp.inc
new file mode 100644
index 000000000..dfe4931eb
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_history_user_timestamp.inc
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_history_user_timestamp.
+ */
+
+/**
+ * Field handler to display the marker for new content.
+ *
+ * The handler is named history_user, because of compability reasons, the table
+ * is history.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_history_user_timestamp extends views_handler_field_node {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ global $user;
+ if ($user->uid) {
+ $this->additional_fields['created'] = array('table' => 'node', 'field' => 'created');
+ $this->additional_fields['changed'] = array('table' => 'node', 'field' => 'changed');
+ if (module_exists('comment') && !empty($this->options['comments'])) {
+ $this->additional_fields['last_comment'] = array('table' => 'node_comment_statistics', 'field' => 'last_comment_timestamp');
+ }
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['comments'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ if (module_exists('comment')) {
+ $form['comments'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Check for new comments as well'),
+ '#default_value' => !empty($this->options['comments']),
+ '#fieldset' => 'more',
+ );
+ }
+ }
+
+ function query() {
+ // Only add ourselves to the query if logged in.
+ global $user;
+ if (!$user->uid) {
+ return;
+ }
+ parent::query();
+ }
+
+ function render($values) {
+ // Let's default to 'read' state.
+ // This code shadows node_mark, but it reads from the db directly and
+ // we already have that info.
+ $mark = MARK_READ;
+ global $user;
+ if ($user->uid) {
+ $last_read = $this->get_value($values);
+ $changed = $this->get_value($values, 'changed');
+
+ $last_comment = module_exists('comment') && !empty($this->options['comments']) ? $this->get_value($values, 'last_comment') : 0;
+
+ if (!$last_read && $changed > NODE_NEW_LIMIT) {
+ $mark = MARK_NEW;
+ }
+ elseif ($changed > $last_read && $changed > NODE_NEW_LIMIT) {
+ $mark = MARK_UPDATED;
+ }
+ elseif ($last_comment > $last_read && $last_comment > NODE_NEW_LIMIT) {
+ $mark = MARK_UPDATED;
+ }
+ return $this->render_link(theme('mark', array('type' => $mark)), $values);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node.inc b/sites/all/modules/views/modules/node/views_handler_field_node.inc
new file mode 100644
index 000000000..f712a5387
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node.inc
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains the basic 'node' field handler.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to a node.
+ * Definition terms:
+ * - link_to_node default: Should this field have the checkbox "link to node" enabled by default.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node extends views_handler_field {
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ // Don't add the additional fields to groupby
+ if (!empty($this->options['link_to_node'])) {
+ $this->additional_fields['nid'] = array('table' => 'node', 'field' => 'nid');
+ if (module_exists('translation')) {
+ $this->additional_fields['language'] = array('table' => 'node', 'field' => 'language');
+ }
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_node'] = array('default' => isset($this->definition['link_to_node default']) ? $this->definition['link_to_node default'] : FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Provide link to node option
+ */
+ function options_form(&$form, &$form_state) {
+ $form['link_to_node'] = array(
+ '#title' => t('Link this field to the original piece of content'),
+ '#description' => t("Enable to override this field's links."),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['link_to_node']),
+ );
+
+ parent::options_form($form, $form_state);
+ }
+
+ /**
+ * Render whatever the data is as a link to the node.
+ *
+ * Data should be made XSS safe prior to calling this function.
+ */
+ function render_link($data, $values) {
+ if (!empty($this->options['link_to_node']) && !empty($this->additional_fields['nid'])) {
+ if ($data !== NULL && $data !== '') {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "node/" . $this->get_value($values, 'nid');
+ if (isset($this->aliases['language'])) {
+ $languages = language_list();
+ $language = $this->get_value($values, 'language');
+ if (isset($languages[$language])) {
+ $this->options['alter']['language'] = $languages[$language];
+ }
+ else {
+ unset($this->options['alter']['language']);
+ }
+ }
+ }
+ else {
+ $this->options['alter']['make_link'] = FALSE;
+ }
+ }
+ return $data;
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_link.inc b/sites/all/modules/views/modules/node/views_handler_field_node_link.inc
new file mode 100644
index 000000000..7e9bbd2a8
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_link.inc
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_link.
+ */
+
+/**
+ * Field handler to present a link to the node.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_link extends views_handler_field_entity {
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['text'] = array('default' => '', 'translatable' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text to display'),
+ '#default_value' => $this->options['text'],
+ );
+ parent::options_form($form, $form_state);
+
+ // The path is set by render_link function so don't allow to set it.
+ $form['alter']['path'] = array('#access' => FALSE);
+ $form['alter']['external'] = array('#access' => FALSE);
+ }
+
+ function render($values) {
+ if ($entity = $this->get_value($values)) {
+ return $this->render_link($entity, $values);
+ }
+ }
+
+ function render_link($node, $values) {
+ if (node_access('view', $node)) {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "node/$node->nid";
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('view');
+ return $text;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_link_delete.inc b/sites/all/modules/views/modules/node/views_handler_field_node_link_delete.inc
new file mode 100644
index 000000000..8271c0bac
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_link_delete.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_link_delete.
+ */
+
+/**
+ * Field handler to present a link to delete a node.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_link_delete extends views_handler_field_node_link {
+
+ /**
+ * Renders the link.
+ */
+ function render_link($node, $values) {
+ // Ensure user has access to delete this node.
+ if (!node_access('delete', $node)) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "node/$node->nid/delete";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_link_edit.inc b/sites/all/modules/views/modules/node/views_handler_field_node_link_edit.inc
new file mode 100644
index 000000000..4e8aad00b
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_link_edit.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_link_edit.
+ */
+
+/**
+ * Field handler to present a link node edit.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_link_edit extends views_handler_field_node_link {
+
+ /**
+ * Renders the link.
+ */
+ function render_link($node, $values) {
+ // Ensure user has access to edit this node.
+ if (!node_access('update', $node)) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "node/$node->nid/edit";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('edit');
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_path.inc b/sites/all/modules/views/modules/node/views_handler_field_node_path.inc
new file mode 100644
index 000000000..f47f85fab
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_path.inc
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Handler for node path field.
+ */
+
+/**
+ * Field handler to present the path to the node.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_path extends views_handler_field {
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['absolute'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function construct() {
+ parent::construct();
+ $this->additional_fields['nid'] = 'nid';
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['absolute'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use absolute link (begins with "http://")'),
+ '#default_value' => $this->options['absolute'],
+ '#description' => t('Enable this option to output an absolute link. Required if you want to use the path as a link destination (as in "output this field as a link" above).'),
+ '#fieldset' => 'alter',
+ );
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ }
+
+ function render($values) {
+ $nid = $this->get_value($values, 'nid');
+ return url("node/$nid", array('absolute' => $this->options['absolute']));
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_revision.inc b/sites/all/modules/views/modules/node/views_handler_field_node_revision.inc
new file mode 100644
index 000000000..d29b07087
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_revision.inc
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_revision.
+ */
+
+/**
+ * Contains the basic 'node_revision' field handler.
+ */
+
+/**
+ * A basic node_revision handler.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_revision extends views_handler_field_node {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ if (!empty($this->options['link_to_node_revision'])) {
+ $this->additional_fields['vid'] = 'vid';
+ $this->additional_fields['nid'] = 'nid';
+ if (module_exists('translation')) {
+ $this->additional_fields['language'] = array('table' => 'node', 'field' => 'language');
+ }
+ }
+ }
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_node_revision'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Provide link to revision option.
+ */
+ function options_form(&$form, &$form_state) {
+ $form['link_to_node_revision'] = array(
+ '#title' => t('Link this field to its content revision'),
+ '#description' => t('This will override any other link you have set.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['link_to_node_revision']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ /**
+ * Render whatever the data is as a link to the node.
+ *
+ * Data should be made XSS safe prior to calling this function.
+ */
+ function render_link($data, $values) {
+ if (!empty($this->options['link_to_node_revision']) && $data !== NULL && $data !== '') {
+ $this->options['alter']['make_link'] = TRUE;
+ $nid = $this->get_value($values, 'nid');
+ $vid = $this->get_value($values, 'vid');
+ $this->options['alter']['path'] = 'node/' . $nid;
+ if ($nid != $vid) {
+ $this->options['alter']['path'] .= "/revisions/$vid/view";
+ }
+ if (module_exists('translation')) {
+ $language = $this->get_value($values, 'language');
+ $languages = language_list();
+ if (isset($languages[$language])) {
+ $this->options['alter']['language'] = $languages[$language];
+ }
+ }
+ }
+ else {
+ return parent::render_link($data, $values);
+ }
+ return $data;
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_revision_link.inc b/sites/all/modules/views/modules/node/views_handler_field_node_revision_link.inc
new file mode 100644
index 000000000..69047bbb1
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_revision_link.inc
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_revision_link.
+ */
+
+/**
+ * Field handler to present a link to a node revision.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_revision_link extends views_handler_field_node_link {
+
+ function construct() {
+ parent::construct();
+ $this->additional_fields['node_vid'] = array('table' => 'node_revision', 'field' => 'vid');
+ }
+
+ function access() {
+ return user_access('view revisions') || user_access('administer nodes');
+ }
+
+ function render_link($data, $values) {
+ list($node, $vid) = $this->get_revision_entity($values, 'view');
+ if (!isset($vid)) {
+ return;
+ }
+
+ // Current revision uses the node view path.
+ $path = 'node/' . $node->nid;
+ if ($node->vid != $vid) {
+ $path .= "/revisions/$vid/view";
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = $path;
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ return !empty($this->options['text']) ? $this->options['text'] : t('view');
+ }
+
+ /**
+ * Returns the revision values of a node.
+ *
+ * @param object $values
+ * An object containing all retrieved values.
+ * @param string $op
+ * The operation being performed.
+ *
+ * @return array
+ * A numerically indexed array containing the current node object and the
+ * revision ID for this row.
+ */
+ function get_revision_entity($values, $op) {
+ $vid = $this->get_value($values, 'node_vid');
+ $node = $this->get_value($values);
+ // Unpublished nodes ignore access control.
+ $node->status = 1;
+ // Ensure user has access to perform the operation on this node.
+ if (!node_access($op, $node)) {
+ return array($node, NULL);
+ }
+ return array($node, $vid);
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_revision_link_delete.inc b/sites/all/modules/views/modules/node/views_handler_field_node_revision_link_delete.inc
new file mode 100644
index 000000000..e0d00a786
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_revision_link_delete.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_revision_link_delete.
+ */
+
+/**
+ * Field handler to present link to delete a node revision.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_revision_link_delete extends views_handler_field_node_revision_link {
+
+ function access() {
+ return user_access('delete revisions') || user_access('administer nodes');
+ }
+
+ function render_link($data, $values) {
+ list($node, $vid) = $this->get_revision_entity($values, 'delete');
+ if (!isset($vid)) {
+ return;
+ }
+
+ // Current revision cannot be deleted.
+ if ($node->vid == $vid) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = 'node/' . $node->nid . "/revisions/$vid/delete";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ return !empty($this->options['text']) ? $this->options['text'] : t('delete');
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_revision_link_revert.inc b/sites/all/modules/views/modules/node/views_handler_field_node_revision_link_revert.inc
new file mode 100644
index 000000000..af2044270
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_revision_link_revert.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_revision_link_revert.
+ */
+
+/**
+ * Field handler to present a link to revert a node to a revision.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_revision_link_revert extends views_handler_field_node_revision_link {
+
+ function access() {
+ return user_access('revert revisions') || user_access('administer nodes');
+ }
+
+ function render_link($data, $values) {
+ list($node, $vid) = $this->get_revision_entity($values, 'update');
+ if (!isset($vid)) {
+ return;
+ }
+
+ // Current revision cannot be reverted.
+ if ($node->vid == $vid) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = 'node/' . $node->nid . "/revisions/$vid/revert";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ return !empty($this->options['text']) ? $this->options['text'] : t('revert');
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_type.inc b/sites/all/modules/views/modules/node/views_handler_field_node_type.inc
new file mode 100644
index 000000000..ba8ee3eb8
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_field_node_type.inc
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_type.
+ */
+
+/**
+ * Field handler to translate a node type into its readable form.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_type extends views_handler_field_node {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['machine_name'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Provide machine_name option for to node type display.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['machine_name'] = array(
+ '#title' => t('Output machine name'),
+ '#description' => t('Display field as the content type machine name.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['machine_name']),
+ );
+ }
+
+ /**
+ * Render node type as human readable name, unless using machine_name option.
+ */
+ function render_name($data, $values) {
+ if ($this->options['machine_name'] != 1 && $data !== NULL && $data !== '') {
+ return t($this->sanitize_value(node_type_get_name($data)));
+ }
+ return $this->sanitize_value($data);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->render_name($value, $values), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_filter_history_user_timestamp.inc b/sites/all/modules/views/modules/node/views_handler_filter_history_user_timestamp.inc
new file mode 100644
index 000000000..acdb8313c
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_filter_history_user_timestamp.inc
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_history_user_timestamp.
+ */
+
+/**
+ * Filter for new content.
+ *
+ * The handler is named history_user, because of compability reasons, the table
+ * is history.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_history_user_timestamp extends views_handler_filter {
+ // Don't display empty space where the operator would be.
+ var $no_operator = TRUE;
+
+ function expose_form(&$form, &$form_state) {
+ parent::expose_form($form, $form_state);
+ // @todo There are better ways of excluding required and multiple (object flags)
+ unset($form['expose']['required']);
+ unset($form['expose']['multiple']);
+ unset($form['expose']['remember']);
+ }
+
+ function value_form(&$form, &$form_state) {
+ // Only present a checkbox for the exposed filter itself. There's no way
+ // to tell the difference between not checked and the default value, so
+ // specifying the default value via the views UI is meaningless.
+ if (!empty($form_state['exposed'])) {
+ if (isset($this->options['expose']['label'])) {
+ $label = $this->options['expose']['label'];
+ }
+ else {
+ $label = t('Has new content');
+ }
+ $form['value'] = array(
+ '#type' => 'checkbox',
+ '#title' => $label,
+ '#default_value' => $this->value,
+ );
+ }
+ }
+
+ function query() {
+ global $user;
+ // This can only work if we're logged in.
+ if (!$user || !$user->uid) {
+ return;
+ }
+
+ // Don't filter if we're exposed and the checkbox isn't selected.
+ if ((!empty($this->options['exposed'])) && empty($this->value)) {
+ return;
+ }
+
+ // Hey, Drupal kills old history, so nodes that haven't been updated
+ // since NODE_NEW_LIMIT are bzzzzzzzt outta here!
+
+ $limit = REQUEST_TIME - NODE_NEW_LIMIT;
+
+ $this->ensure_my_table();
+ $field = "$this->table_alias.$this->real_field";
+ $node = $this->query->ensure_table('node', $this->relationship);
+
+ $clause = '';
+ $clause2 = '';
+ if (module_exists('comment')) {
+ $ncs = $this->query->ensure_table('node_comment_statistics', $this->relationship);
+ $clause = ("OR $ncs.last_comment_timestamp > (***CURRENT_TIME*** - $limit)");
+ $clause2 = "OR $field < $ncs.last_comment_timestamp";
+ }
+
+ // NULL means a history record doesn't exist. That's clearly new content.
+ // Unless it's very very old content. Everything in the query is already
+ // type safe cause none of it is coming from outside here.
+ $this->query->add_where_expression($this->options['group'], "($field IS NULL AND ($node.changed > (***CURRENT_TIME*** - $limit) $clause)) OR $field < $node.changed $clause2");
+ }
+
+ function admin_summary() {
+ if (!empty($this->options['exposed'])) {
+ return t('exposed');
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_filter_node_access.inc b/sites/all/modules/views/modules/node/views_handler_filter_node_access.inc
new file mode 100644
index 000000000..a29b13f42
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_filter_node_access.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_node_access.
+ */
+
+/**
+ * Filter by node_access records.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_node_access extends views_handler_filter {
+ function admin_summary() { }
+ function operator_form(&$form, &$form_state) { }
+ function can_expose() {
+ return FALSE;
+ }
+
+ /**
+ * See _node_access_where_sql() for a non-views query based implementation.
+ */
+ function query() {
+ if (!user_access('administer nodes') && module_implements('node_grants')) {
+ $table = $this->ensure_my_table();
+ $grants = db_or();
+ foreach (node_access_grants('view') as $realm => $gids) {
+ foreach ($gids as $gid) {
+ $grants->condition(db_and()
+ ->condition($table . '.gid', $gid)
+ ->condition($table . '.realm', $realm)
+ );
+ }
+ }
+
+ $this->query->add_where('AND', $grants);
+ $this->query->add_where('AND', $table . '.grant_view', 1, '>=');
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_filter_node_status.inc b/sites/all/modules/views/modules/node/views_handler_filter_node_status.inc
new file mode 100644
index 000000000..2afb2869a
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_filter_node_status.inc
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_node_status.
+ */
+
+/**
+ * Filter by published status.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_node_status extends views_handler_filter {
+ function admin_summary() { }
+ function operator_form(&$form, &$form_state) { }
+ function can_expose() { return FALSE; }
+
+ function query() {
+ $table = $this->ensure_my_table();
+ $this->query->add_where_expression($this->options['group'], "$table.status = 1 OR ($table.uid = ***CURRENT_USER*** AND ***CURRENT_USER*** <> 0 AND ***VIEW_OWN_UNPUBLISHED_NODES*** = 1) OR ***BYPASS_NODE_ACCESS*** = 1");
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_filter_node_type.inc b/sites/all/modules/views/modules/node/views_handler_filter_node_type.inc
new file mode 100644
index 000000000..7f8ab4b7a
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_filter_node_type.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_node_type.
+ */
+
+/**
+ * Filter by node type.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_node_type extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_title = t('Content types');
+ $types = node_type_get_types();
+ $options = array();
+ foreach ($types as $type => $info) {
+ $options[$type] = t($info->name);
+ }
+ asort($options);
+ $this->value_options = $options;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_handler_filter_node_uid_revision.inc b/sites/all/modules/views/modules/node/views_handler_filter_node_uid_revision.inc
new file mode 100644
index 000000000..4d3d9a70c
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_handler_filter_node_uid_revision.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_node_uid_revision.
+ */
+
+/**
+ * Filter handler to check for revisions a certain user has created.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_node_uid_revision extends views_handler_filter_user_name {
+ function query($group_by = FALSE) {
+ $this->ensure_my_table();
+
+ $placeholder = $this->placeholder();
+
+ $args = array_values($this->value);
+
+ $this->query->add_where_expression($this->options['group'], "$this->table_alias.uid IN($placeholder) " . $condition . " OR
+ ((SELECT COUNT(*) FROM {node_revision} nr WHERE nr.uid IN($placeholder) AND nr.nid = $this->table_alias.nid) > 0)", array($placeholder => $args),
+ $args);
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_plugin_argument_default_node.inc b/sites/all/modules/views/modules/node/views_plugin_argument_default_node.inc
new file mode 100644
index 000000000..65fc0eb87
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_plugin_argument_default_node.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains the node from URL argument default plugin.
+ */
+
+/**
+ * Default argument plugin to extract a node via menu_get_object
+ *
+ * This plugin actually has no options so it odes not need to do a great deal.
+ */
+class views_plugin_argument_default_node extends views_plugin_argument_default {
+ function get_argument() {
+ foreach (range(1, 3) as $i) {
+ $node = menu_get_object('node', $i);
+ if (!empty($node)) {
+ return $node->nid;
+ }
+ }
+
+ if (arg(0) == 'node' && is_numeric(arg(1))) {
+ return arg(1);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_plugin_argument_validate_node.inc b/sites/all/modules/views/modules/node/views_plugin_argument_validate_node.inc
new file mode 100644
index 000000000..018965da2
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_plugin_argument_validate_node.inc
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Contains the 'node' argument validator plugin.
+ */
+
+/**
+ * Validate whether an argument is an acceptable node.
+ */
+class views_plugin_argument_validate_node extends views_plugin_argument_validate {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['types'] = array('default' => array());
+ $options['access'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['access_op'] = array('default' => 'view');
+ $options['nid_type'] = array('default' => 'nid');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $types = node_type_get_types();
+ $options = array();
+ foreach ($types as $type => $info) {
+ $options[$type] = check_plain(t($info->name));
+ }
+
+ $form['types'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Content types'),
+ '#options' => $options,
+ '#default_value' => $this->options['types'],
+ '#description' => t('Choose one or more content types to validate with.'),
+ );
+
+ $form['access'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Validate user has access to the content'),
+ '#default_value' => $this->options['access'],
+ );
+ $form['access_op'] = array(
+ '#type' => 'radios',
+ '#title' => t('Access operation to check'),
+ '#options' => array('view' => t('View'), 'update' => t('Edit'), 'delete' => t('Delete')),
+ '#default_value' => $this->options['access_op'],
+ '#dependency' => array('edit-options-validate-options-node-access' => array(TRUE)),
+ );
+
+ $form['nid_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Filter value format'),
+ '#options' => array(
+ 'nid' => t('Node ID'),
+ 'nids' => t('Node IDs separated by , or +'),
+ ),
+ '#default_value' => $this->options['nid_type'],
+ );
+ }
+
+ function options_submit(&$form, &$form_state, &$options = array()) {
+ // filter trash out of the options so we don't store giant unnecessary arrays
+ $options['types'] = array_filter($options['types']);
+ }
+
+ function convert_options(&$options) {
+ if (!isset($options['types']) && !empty($this->argument->options['validate_argument_node_type'])) {
+ $options['types'] = isset($this->argument->options['validate_argument_node_type']) ? $this->argument->options['validate_argument_node_type'] : array();
+ $options['access'] = !empty($this->argument->options['validate_argument_node_access']);
+ $options['access_op'] = isset($this->argument->options['validate_argument_node_access_op']) ? $this->argument->options['validate_argument_node_access_op'] : 'view';
+ $options['nid_type'] = isset($this->argument->options['validate_argument_nid_type']) ? $this->argument->options['validate_argument_nid_type'] : array();
+ }
+ }
+
+ function validate_argument($argument) {
+ $types = $this->options['types'];
+
+ switch ($this->options['nid_type']) {
+ case 'nid':
+ if (!is_numeric($argument)) {
+ return FALSE;
+ }
+ $node = node_load($argument);
+ if (!$node) {
+ return FALSE;
+ }
+
+ if (!empty($this->options['access'])) {
+ if (!node_access($this->options['access_op'], $node)) {
+ return FALSE;
+ }
+ }
+
+ // Save the title() handlers some work.
+ $this->argument->validated_title = check_plain($node->title);
+
+ if (empty($types)) {
+ return TRUE;
+ }
+
+ return isset($types[$node->type]);
+ break;
+ case 'nids':
+ $nids = new stdClass();
+ $nids->value = array($argument);
+ $nids = views_break_phrase($argument, $nids);
+ if ($nids->value == array(-1)) {
+ return FALSE;
+ }
+
+ $test = drupal_map_assoc($nids->value);
+ $titles = array();
+
+ $result = db_query("SELECT * FROM {node} WHERE nid IN (:nids)", array(':nids' => $nids->value));
+ foreach ($result as $node) {
+ if ($types && empty($types[$node->type])) {
+ return FALSE;
+ }
+
+ if (!empty($this->options['access'])) {
+ if (!node_access($this->options['access_op'], $node)) {
+ return FALSE;
+ }
+ }
+
+ $titles[] = check_plain($node->title);
+ unset($test[$node->nid]);
+ }
+
+ $this->argument->validated_title = implode($nids->operator == 'or' ? ' + ' : ', ', $titles);
+ // If this is not empty, we did not find a nid.
+ return empty($test);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_plugin_row_node_rss.inc b/sites/all/modules/views/modules/node/views_plugin_row_node_rss.inc
new file mode 100644
index 000000000..667189250
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_plugin_row_node_rss.inc
@@ -0,0 +1,174 @@
+<?php
+
+/**
+ * @file
+ * Contains the node RSS row style plugin.
+ */
+
+/**
+ * Plugin which performs a node_view on the resulting object
+ * and formats it as an RSS item.
+ */
+class views_plugin_row_node_rss extends views_plugin_row {
+ // Basic properties that let the row style follow relationships.
+ var $base_table = 'node';
+ var $base_field = 'nid';
+
+ // Stores the nodes loaded with pre_render.
+ var $nodes = array();
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['item_length'] = array('default' => 'default');
+ $options['links'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Override init function to convert fulltext view-mode to full.
+ */
+ function init(&$view, &$display, $options = NULL) {
+ parent::init($view, $display, $options);
+
+ if ($this->options['item_length'] == 'fulltext') {
+ $this->options['item_length'] = 'full';
+ }
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['item_length'] = array(
+ '#type' => 'select',
+ '#title' => t('Display type'),
+ '#options' => $this->options_form_summary_options(),
+ '#default_value' => $this->options['item_length'],
+ );
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display links'),
+ '#default_value' => $this->options['links'],
+ );
+ }
+
+ /**
+ * Return the main options, which are shown in the summary title.
+ */
+ function options_form_summary_options() {
+ $entity_info = entity_get_info('node');
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+ $options['title'] = t('Title only');
+ $options['default'] = t('Use site default RSS settings');
+ return $options;
+ }
+
+ function summary_title() {
+ $options = $this->options_form_summary_options();
+ return check_plain($options[$this->options['item_length']]);
+ }
+
+
+ function pre_render($values) {
+ $nids = array();
+ foreach ($values as $row) {
+ $nids[] = $row->{$this->field_alias};
+ }
+ if (!empty($nids)) {
+ $this->nodes = node_load_multiple($nids);
+ }
+ }
+
+ function render($row) {
+ // For the most part, this code is taken from node_feed() in node.module
+ global $base_url;
+
+ $nid = $row->{$this->field_alias};
+ if (!is_numeric($nid)) {
+ return;
+ }
+
+ $display_mode = $this->options['item_length'];
+ if ($display_mode == 'default') {
+ $display_mode = variable_get('feed_item_length', 'teaser');
+ }
+
+ // Load the specified node:
+ $node = $this->nodes[$nid];
+ if (empty($node)) {
+ return;
+ }
+
+ $item_text = '';
+
+ $uri = entity_uri('node', $node);
+ $node->link = url($uri['path'], $uri['options'] + array('absolute' => TRUE));
+ $node->rss_namespaces = array();
+ $node->rss_elements = array(
+ array(
+ 'key' => 'pubDate',
+ 'value' => gmdate('r', $node->created),
+ ),
+ array(
+ 'key' => 'dc:creator',
+ 'value' => format_username($node),
+ ),
+ array(
+ 'key' => 'guid',
+ 'value' => $node->nid . ' at ' . $base_url,
+ 'attributes' => array('isPermaLink' => 'false'),
+ ),
+ );
+
+ // The node gets built and modules add to or modify $node->rss_elements
+ // and $node->rss_namespaces.
+
+ $build_mode = $display_mode;
+
+ $build = node_view($node, $build_mode);
+ unset($build['#theme']);
+
+ if (!empty($node->rss_namespaces)) {
+ $this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $node->rss_namespaces);
+ }
+ elseif (function_exists('rdf_get_namespaces')) {
+ // Merge RDF namespaces in the XML namespaces in case they are used
+ // further in the RSS content.
+ $xml_rdf_namespaces = array();
+ foreach (rdf_get_namespaces() as $prefix => $uri) {
+ $xml_rdf_namespaces['xmlns:' . $prefix] = $uri;
+ }
+ $this->view->style_plugin->namespaces += $xml_rdf_namespaces;
+ }
+
+ // Hide the links if desired.
+ if (!$this->options['links']) {
+ hide($build['links']);
+ }
+
+ if ($display_mode != 'title') {
+ // We render node contents and force links to be last.
+ $build['links']['#weight'] = 1000;
+ $item_text .= drupal_render($build);
+ }
+
+ $item = new stdClass();
+ $item->description = $item_text;
+ $item->title = $node->title;
+ $item->link = $node->link;
+ $item->elements = $node->rss_elements;
+ $item->nid = $node->nid;
+
+ return theme($this->theme_functions(), array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'row' => $item
+ ));
+ }
+}
diff --git a/sites/all/modules/views/modules/node/views_plugin_row_node_view.inc b/sites/all/modules/views/modules/node/views_plugin_row_node_view.inc
new file mode 100644
index 000000000..4aefe461b
--- /dev/null
+++ b/sites/all/modules/views/modules/node/views_plugin_row_node_view.inc
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Contains the node view row style plugin.
+ */
+
+/**
+ * Plugin which performs a node_view on the resulting object.
+ *
+ * Most of the code on this object is in the theme function.
+ *
+ * @ingroup views_row_plugins
+ */
+class views_plugin_row_node_view extends views_plugin_row {
+ // Basic properties that let the row style follow relationships.
+ var $base_table = 'node';
+ var $base_field = 'nid';
+
+ // Stores the nodes loaded with pre_render.
+ var $nodes = array();
+
+ function init(&$view, &$display, $options = NULL) {
+ parent::init($view, $display, $options);
+ // Handle existing views with the deprecated 'teaser' option.
+ if (isset($this->options['teaser'])) {
+ $this->options['build_mode'] = $this->options['teaser'] ? 'teaser' : 'full';
+ }
+ // Handle existing views which has used build_mode instead of view_mode.
+ if (isset($this->options['build_mode'])) {
+ $this->options['view_mode'] = $this->options['build_mode'];
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['view_mode'] = array('default' => 'teaser');
+ $options['links'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['comments'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $options = $this->options_form_summary_options();
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $this->options['view_mode'],
+ );
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display links'),
+ '#default_value' => $this->options['links'],
+ );
+ $form['comments'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display comments'),
+ '#default_value' => $this->options['comments'],
+ );
+ }
+
+ /**
+ * Return the main options, which are shown in the summary title.
+ */
+ function options_form_summary_options() {
+ $entity_info = entity_get_info('node');
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+ if (empty($options)) {
+ $options = array(
+ 'teaser' => t('Teaser'),
+ 'full' => t('Full content')
+ );
+ }
+
+ return $options;
+ }
+
+ function summary_title() {
+ $options = $this->options_form_summary_options();
+ return check_plain($options[$this->options['view_mode']]);
+ }
+
+ function pre_render($values) {
+ $nids = array();
+ foreach ($values as $row) {
+ $nids[] = $row->{$this->field_alias};
+ }
+ $this->nodes = node_load_multiple($nids);
+ }
+
+ function render($row) {
+ if (isset($this->nodes[$row->{$this->field_alias}])) {
+ $node = $this->nodes[$row->{$this->field_alias}];
+ $node->view = $this->view;
+ $build = node_view($node, $this->options['view_mode']);
+
+ return drupal_render($build);
+ }
+ }
+} \ No newline at end of file
diff --git a/sites/all/modules/views/modules/poll.views.inc b/sites/all/modules/views/modules/poll.views.inc
new file mode 100644
index 000000000..d3fd76adc
--- /dev/null
+++ b/sites/all/modules/views/modules/poll.views.inc
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for poll.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function poll_views_data() {
+ // Basic table information.
+ $data['poll']['table']['group'] = t('Poll');
+
+ // Join to 'node' as a base table.
+ $data['poll']['table']['join'] = array(
+ 'node' => array(
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ );
+
+ // ----------------------------------------------------------------
+ // Fields
+
+ // poll active status
+ $data['poll']['active'] = array(
+ 'title' => t('Active'),
+ 'help' => t('Whether the poll is open for voting.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Active'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ return $data;
+}
diff --git a/sites/all/modules/views/modules/profile.views.inc b/sites/all/modules/views/modules/profile.views.inc
new file mode 100644
index 000000000..89db913a8
--- /dev/null
+++ b/sites/all/modules/views/modules/profile.views.inc
@@ -0,0 +1,217 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for user.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function profile_views_data() {
+ $data['profile_values']['moved to'] = 'profile_value';
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['profile_value']['table']['group'] = t('Profile');
+
+ $data['profile_value']['table']['join'] = array(
+ 'node' => array(
+ 'left_table' => 'profile_value',
+ 'left_field' => 'uid',
+ 'field' => 'uid',
+ ),
+ 'users' => array(
+ 'left_table' => 'profile_value',
+ 'left_field' => 'uid',
+ 'field' => 'uid',
+ ),
+ );
+
+ $fields = profile_views_get_fields();
+ foreach ($fields as $field) {
+ $table_name = 'profile_value_' . str_replace('-', '_', $field->name);
+ $data[$table_name] = array(
+ 'table' => array(
+ 'group' => t('Profile'),
+ 'join' => array(
+ 'node' => array(
+ 'table' => 'profile_value',
+ 'left_table' => 'users',
+ 'left_field' => 'uid',
+ 'field' => 'uid',
+ 'extra' => array(array('field' => 'fid', 'value' => $field->fid)),
+ ),
+ 'users' => array(
+ 'table' => 'profile_value',
+ 'left_field' => 'uid',
+ 'field' => 'uid',
+ 'extra' => array(array('field' => 'fid', 'value' => $field->fid)),
+ ),
+ ),
+ ),
+ );
+ // All fields in the table are named 'value'.
+ $data[$table_name]['value'] = profile_views_fetch_field($field);
+ }
+
+ return $data;
+}
+
+/**
+ * Get all profile fields
+ */
+function profile_views_get_fields() {
+ static $fields = NULL;
+
+ if (!isset($fields)) {
+ $fields = array();
+ $results = db_query("SELECT * FROM {profile_field} ORDER BY category, weight");
+
+ foreach ($results as $row) {
+ if (!empty($row->options)) {
+ if (!in_array(substr($row->options, 0, 2), array('a:', 'b:', 'i:', 'f:', 'o:', 's:', ))) {
+ // unserialized fields default version
+ $options = $row->options;
+ unset($row->options);
+ $row->options = $options;
+ }
+ else {
+ // serialized fields or modified version
+ $row->options = unserialize($row->options);
+ }
+ }
+ $fields[$row->fid] = $row;
+ }
+ }
+ return $fields;
+}
+
+
+/**
+ * Add profile fields to view table
+ */
+function profile_views_fetch_field($field) {
+ $data = array(
+ 'title' => t('@category: @field-name', array('@category' => $field->category, '@field-name' => $field->title)),
+ );
+
+ // Add fields specific to the profile type.
+ switch ($field->type) {
+ case 'textfield':
+ $data += array(
+ 'help' => t('Profile textfield'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_user',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ break;
+ case 'textarea':
+ $data += array(
+ 'help' => t('Profile textarea'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_markup',
+ 'format' => filter_default_format(),
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ break;
+ case 'checkbox':
+ $data += array(
+ 'help' => t('Profile checkbox'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'accept null' => TRUE,
+ ),
+ // @todo there ought to be a boolean argument handler
+ );
+
+ break;
+ case 'url':
+ $data += array(
+ 'help' => t('Profile URL'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_url',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ break;
+ case 'selection':
+ $data += array(
+ 'help' => t('Profile selection'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_profile_selection',
+ 'fid' => $field->fid,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ break;
+ case 'list':
+ $data += array(
+ 'help' => t('Profile freeform list %field-name.', array('%field-name' => $field->title)),
+ 'field' => array(
+ 'handler' => 'views_handler_field_profile_list',
+ 'no group by' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ break;
+ case 'date':
+ $data += array(
+ 'help' => t('Profile date %field-name.', array('%field-name' => $field->title)),
+ 'field' => array(
+ 'handler' => 'views_handler_field_profile_date',
+ ),
+ );
+
+ break;
+ }
+
+ // @todo: add access control to hidden fields.
+ return $data;
+}
diff --git a/sites/all/modules/views/modules/profile/views_handler_field_profile_date.inc b/sites/all/modules/views/modules/profile/views_handler_field_profile_date.inc
new file mode 100644
index 000000000..c2cf691b3
--- /dev/null
+++ b/sites/all/modules/views/modules/profile/views_handler_field_profile_date.inc
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_profile_date.
+ */
+
+/**
+ * Field handler display a profile date
+ *
+ * The dates are stored serialized, which makes them mostly useless from
+ * SQL. About all we can do is unserialize and display them.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_profile_date extends views_handler_field_date {
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ // we can't do "time ago" so remove it from the form.
+ unset($form['date_format']['#options']['time ago']);
+ }
+
+ /**
+ * Display a profile field of type 'date'
+ */
+ function render($values) {
+ $value = $this->get_value($values);
+ if (!$value) {
+ return;
+ }
+ $value = unserialize($value);
+ $format = $this->options['date_format'];
+ switch ($format) {
+ case 'custom':
+ $format = $this->options['custom_date_format'];
+ break;
+ case 'small':
+ $format = variable_get('date_format_short', 'm/d/Y - H:i');
+ break;
+ case 'medium':
+ $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
+ break;
+ case 'large':
+ $format = variable_get('date_format_long', 'l, F j, Y - H:i');
+ break;
+ }
+
+ // Note: Avoid PHP's date() because it does not handle dates before
+ // 1970 on Windows. This would make the date field useless for e.g.
+ // birthdays.
+
+ // But we *can* deal with non-year stuff:
+ $date = gmmktime(0, 0, 0, $value['month'], $value['day'], $value['year']);
+
+ $replace = array(
+ // day
+ 'd' => sprintf('%02d', $value['day']),
+ 'D' => NULL,
+ 'l' => NULL,
+ 'N' => NULL,
+ 'S' => gmdate('S', $date),
+ 'w' => NULL,
+ 'j' => $value['day'],
+ // month
+ 'F' => gmdate('F', $date),
+ 'm' => sprintf('%02d', $value['month']),
+ 'M' => gmdate('M', $date),
+ 'n' => gmdate('n', $date),
+
+ 'Y' => $value['year'],
+ 'y' => substr($value['year'], 2, 2),
+
+ // kill time stuff
+ 'a' => NULL,
+ 'A' => NULL,
+ 'g' => NULL,
+ 'G' => NULL,
+ 'h' => NULL,
+ 'H' => NULL,
+ 'i' => NULL,
+ 's' => NULL,
+ ':' => NULL,
+ 'T' => NULL,
+ ' - ' => NULL,
+ ':' => NULL,
+ );
+
+ return strtr($format, $replace);
+ }
+}
diff --git a/sites/all/modules/views/modules/profile/views_handler_field_profile_list.inc b/sites/all/modules/views/modules/profile/views_handler_field_profile_list.inc
new file mode 100644
index 000000000..8917b93ab
--- /dev/null
+++ b/sites/all/modules/views/modules/profile/views_handler_field_profile_list.inc
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_profile_list.
+ */
+
+/**
+ * Field handler display a profile list item.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_profile_list extends views_handler_field_prerender_list {
+ /**
+ * Break up our field into a proper list.
+ */
+ function pre_render(&$values) {
+ $this->items = array();
+ foreach ($values as $value) {
+ $field = $this->get_value($value);
+ $this->items[$field] = array();
+ foreach (preg_split("/[,\n\r]/", $field) as $item) {
+ if ($item != '' && $item !== NULL) {
+ $this->items[$field][] = array('item' => $item);
+ }
+ }
+ }
+ }
+
+ function render_item($count, $item) {
+ return $item['item'];
+ }
+
+ function document_self_tokens(&$tokens) {
+ $tokens['[' . $this->options['id'] . '-item' . ']'] = t('The text of the profile item.');
+ }
+
+ function add_self_tokens(&$tokens, $item) {
+ $tokens['[' . $this->options['id'] . '-item' . ']'] = $item['item'];
+ }
+}
diff --git a/sites/all/modules/views/modules/profile/views_handler_filter_profile_selection.inc b/sites/all/modules/views/modules/profile/views_handler_filter_profile_selection.inc
new file mode 100644
index 000000000..d3403c909
--- /dev/null
+++ b/sites/all/modules/views/modules/profile/views_handler_filter_profile_selection.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_profile_selection.
+ */
+
+/**
+ * Filter by a selection widget in the profile.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_profile_selection extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (isset($this->value_options)) {
+ return;
+ }
+
+ $this->value_options = array();
+ $all_options = profile_views_get_fields();
+ $field = $all_options[$this->definition['fid']];
+
+ $lines = preg_split("/[,\n\r]/", $field->options);
+ foreach ($lines as $line) {
+ if ($line = trim($line)) {
+ $this->value_options[$line] = $line;
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/search.views.inc b/sites/all/modules/views/modules/search.views.inc
new file mode 100644
index 000000000..dad84bb7c
--- /dev/null
+++ b/sites/all/modules/views/modules/search.views.inc
@@ -0,0 +1,202 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for search.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function search_views_data() {
+ // Basic table information.
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['search_index']['table']['group'] = t('Search');
+
+ // For other base tables, explain how we join
+ $data['search_index']['table']['join'] = array(
+ 'node' => array(
+ 'left_field' => 'nid',
+ 'field' => 'sid',
+ ),
+ );
+
+ $data['search_total']['table']['join'] = array(
+ 'node' => array(
+ 'left_table' => 'search_index',
+ 'left_field' => 'word',
+ 'field' => 'word',
+ ),
+ 'users' => array(
+ 'left_table' => 'search_index',
+ 'left_field' => 'word',
+ 'field' => 'word',
+ )
+ );
+
+ $data['search_dataset']['table']['join'] = array(
+ 'node' => array(
+ 'left_table' => 'search_index',
+ 'left_field' => 'sid',
+ 'field' => 'sid',
+ 'extra' => 'search_index.type = search_dataset.type',
+ 'type' => 'INNER',
+ ),
+ 'users' => array(
+ 'left_table' => 'search_index',
+ 'left_field' => 'sid',
+ 'field' => 'sid',
+ 'extra' => 'search_index.type = search_dataset.type',
+ 'type' => 'INNER',
+ ),
+ );
+
+ // ----------------------------------------------------------------
+ // Fields
+
+ // score
+ $data['search_index']['score'] = array(
+ 'title' => t('Score'),
+ 'help' => t('The score of the search item. This will not be used if the search filter is not also present.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_search_score',
+ 'click sortable' => TRUE,
+ 'float' => TRUE,
+ 'no group by' => TRUE,
+ ),
+ // Information for sorting on a search score.
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_search_score',
+ 'no group by' => TRUE,
+ ),
+ );
+
+ // Search node links: forward links.
+ $data['search_node_links_from']['table']['group'] = t('Search');
+ $data['search_node_links_from']['table']['join'] = array(
+ 'node' => array(
+ 'arguments' => array('search_node_links', 'node', 'nid', 'nid', NULL, 'INNER'),
+ ),
+ );
+ $data['search_node_links_from']['sid'] = array(
+ 'title' => t('Links from'),
+ 'help' => t('Other nodes that are linked from the node.'),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_nid',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_equality',
+ ),
+ );
+
+ // Search node links: backlinks.
+ $data['search_node_links_to']['table']['group'] = t('Search');
+ $data['search_node_links_to']['table']['join'] = array(
+ 'node' => array(
+ 'arguments' => array('search_node_links', 'node', 'nid', 'sid', NULL, 'INNER'),
+ ),
+ );
+ $data['search_node_links_to']['nid'] = array(
+ 'title' => t('Links to'),
+ 'help' => t('Other nodes that link to the node.'),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_nid',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_equality',
+ ),
+ );
+
+ // search filter
+ $data['search_index']['keys'] = array(
+ 'title' => t('Search Terms'), // The item it appears as on the UI,
+ 'help' => t('The terms to search for.'), // The help that appears on the UI,
+ // Information for searching terms using the full search syntax
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_search',
+ 'no group by' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_search',
+ 'no group by' => TRUE,
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function search_views_plugins() {
+ return;
+ // DISABLED. This currently doesn't work.
+ return array(
+ 'module' => 'views', // This just tells our themes are elsewhere.
+ 'row' => array(
+ 'search' => array(
+ 'title' => t('Search'),
+ 'help' => t('Display the results with standard search view.'),
+ 'handler' => 'views_plugin_row_search_view',
+ 'theme' => 'views_view_row_search',
+ 'path' => drupal_get_path('module', 'views') . '/modules/search', // not necessary for most modules
+ 'base' => array('node'), // only works with 'node' as base.
+ 'type' => 'normal',
+ ),
+ 'views_handler_argument_search' => array(
+ 'parent' => 'views_handler_argument',
+ ),
+ ),
+ );
+}
+
+/**
+ * Template helper for theme_views_view_row_search
+ */
+function template_preprocess_views_view_row_search(&$vars) {
+ $vars['node'] = ''; // make sure var is defined.
+ $nid = $vars['row']->nid;
+ if (!is_numeric($nid)) {
+ return;
+ }
+
+ // @todo: Once the search row is fixed this node_load should be replace by a node_load_multiple
+ $node = node_load($nid);
+
+ if (empty($node)) {
+ return;
+ }
+
+ // Build the node body.
+ $node = node_build_content($node, FALSE, FALSE);
+ $node->body = drupal_render($node->content);
+
+ // Fetch comments for snippet
+ $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
+
+ // Fetch terms for snippet
+ $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
+
+ $vars['url'] = url('node/' . $nid);
+ $vars['title'] = check_plain($node->title);
+
+ $info = array();
+ $info['type'] = node_type_get_name($node);
+ $info['user'] = theme('username', array('acccount' => $node));
+ $info['date'] = format_date($node->changed, 'small');
+ $extra = module_invoke_all('node_search_result', $node);
+ if (isset($extra) && is_array($extra)) {
+ $info = array_merge($info, $extra);
+ }
+ $vars['info_split'] = $info;
+ $vars['info'] = implode(' - ', $info);
+
+ $vars['node'] = $node;
+ // @todo: get score from ???
+//$vars['score'] = $item->score;
+ $vars['snippet'] = search_excerpt($vars['view']->value, $node->body);
+}
diff --git a/sites/all/modules/views/modules/search.views_default.inc b/sites/all/modules/views/modules/search.views_default.inc
new file mode 100644
index 000000000..dcde6224a
--- /dev/null
+++ b/sites/all/modules/views/modules/search.views_default.inc
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * @file
+ * Bulk export of views_default objects generated by Bulk export module.
+ */
+
+/**
+ * Implementation of hook_views_default_views()
+ */
+function search_views_default_views() {
+ $views = array();
+
+ $view = new view;
+ $view->name = 'backlinks';
+ $view->description = 'Displays a list of nodes that link to the node, using the search backlinks table.';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Backlinks';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access content';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = 30;
+ $handler->display->display_options['style_plugin'] = 'list';
+ $handler->display->display_options['style_options']['type'] = 'ol';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* No results behavior: Global: Text area */
+ $handler->display->display_options['empty']['text']['id'] = 'area';
+ $handler->display->display_options['empty']['text']['table'] = 'views';
+ $handler->display->display_options['empty']['text']['field'] = 'area';
+ $handler->display->display_options['empty']['text']['empty'] = FALSE;
+ $handler->display->display_options['empty']['text']['content'] = 'No backlinks found.';
+ $handler->display->display_options['empty']['text']['format'] = '1';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+ /* Contextual filter: Search: Links to */
+ $handler->display->display_options['arguments']['nid']['id'] = 'nid';
+ $handler->display->display_options['arguments']['nid']['table'] = 'search_node_links_to';
+ $handler->display->display_options['arguments']['nid']['field'] = 'nid';
+ $handler->display->display_options['arguments']['nid']['default_action'] = 'not found';
+ $handler->display->display_options['arguments']['nid']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['nid']['title'] = 'Pages that link to %1';
+ $handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['nid']['specify_validation'] = 1;
+ $handler->display->display_options['arguments']['nid']['validate']['type'] = 'node';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'node/%/backlinks';
+ $handler->display->display_options['menu']['type'] = 'tab';
+ $handler->display->display_options['menu']['title'] = 'What links here';
+ $handler->display->display_options['menu']['weight'] = '0';
+
+ /* Display: What links here */
+ $handler = $view->new_display('block', 'What links here', 'block');
+ $handler->display->display_options['defaults']['use_more'] = FALSE;
+ $handler->display->display_options['use_more'] = TRUE;
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'list';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'fields';
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['defaults']['arguments'] = FALSE;
+ /* Contextual filter: Search: Links to */
+ $handler->display->display_options['arguments']['nid']['id'] = 'nid';
+ $handler->display->display_options['arguments']['nid']['table'] = 'search_node_links_to';
+ $handler->display->display_options['arguments']['nid']['field'] = 'nid';
+ $handler->display->display_options['arguments']['nid']['default_action'] = 'default';
+ $handler->display->display_options['arguments']['nid']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['nid']['title'] = 'What links here';
+ $handler->display->display_options['arguments']['nid']['default_argument_type'] = 'node';
+ $handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['nid']['specify_validation'] = 1;
+ $handler->display->display_options['arguments']['nid']['validate']['type'] = 'node';
+ $translatables['backlinks'] = array(
+ t('Master'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('No backlinks found.'),
+ t('All'),
+ t('Pages that link to %1'),
+ t('Page'),
+ t('What links here'),
+ );
+
+ $views['backlinks'] = $view;
+
+ return $views;
+}
diff --git a/sites/all/modules/views/modules/search/views_handler_argument_search.inc b/sites/all/modules/views/modules/search/views_handler_argument_search.inc
new file mode 100644
index 000000000..f0a4a448d
--- /dev/null
+++ b/sites/all/modules/views/modules/search/views_handler_argument_search.inc
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_search.
+ */
+
+/**
+ * Argument that accepts query keys for search.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_search extends views_handler_argument {
+
+ /**
+ * Take sure that parseSearchExpression is runned and everything is set up for it.
+ *
+ * @param $input
+ * The search phrase which was input by the user.
+ */
+ function query_parse_search_expression($input) {
+ if (!isset($this->search_query)) {
+ $this->search_query = db_select('search_index', 'i', array('target' => 'slave'))->extend('viewsSearchQuery');
+ $this->search_query->searchExpression($input, $this->view->base_table);
+ $this->search_query->publicParseSearchExpression();
+ }
+ }
+
+ /**
+ * Add this argument to the query.
+ */
+ function query($group_by = FALSE) {
+ $required = FALSE;
+ $this->query_parse_search_expression($this->argument);
+ if (!isset($this->search_query)) {
+ $required = TRUE;
+ }
+ else {
+ $words = $this->search_query->words();
+ if (empty($words)) {
+ $required = TRUE;
+ }
+ }
+ if ($required) {
+ if ($this->operator == 'required') {
+ $this->query->add_where(0, 'FALSE');
+ }
+ }
+ else {
+ $search_index = $this->ensure_my_table();
+
+ $search_condition = db_and();
+
+ // Create a new join to relate the 'search_total' table to our current 'search_index' table.
+ $join = new views_join;
+ $join->construct('search_total', $search_index, 'word', 'word');
+ $search_total = $this->query->add_relationship('search_total', $join, $search_index);
+
+ $this->search_score = $this->query->add_field('', "SUM($search_index.score * $search_total.count)", 'score', array('aggregate' => TRUE));
+
+ if (empty($this->query->relationships[$this->relationship])) {
+ $base_table = $this->query->base_table;
+ }
+ else {
+ $base_table = $this->query->relationships[$this->relationship]['base'];
+ }
+ $search_condition->condition("$search_index.type", $base_table);
+
+ if (!$this->search_query->simple()) {
+ $search_dataset = $this->query->add_table('search_dataset');
+ $conditions = $this->search_query->conditions();
+ $condition_conditions =& $conditions->conditions();
+ foreach ($condition_conditions as $key => &$condition) {
+ // Take sure we just look at real conditions.
+ if (is_numeric($key)) {
+ // Replace the conditions with the table alias of views.
+ $this->search_query->condition_replace_string('d.', "$search_dataset.", $condition);
+ }
+ }
+ $search_conditions =& $search_condition->conditions();
+ $search_conditions = array_merge($search_conditions, $condition_conditions);
+ }
+ else {
+ // Stores each condition, so and/or on the filter level will still work.
+ $or = db_or();
+ foreach ($words as $word) {
+ $or->condition("$search_index.word", $word);
+ }
+
+ $search_condition->condition($or);
+ }
+
+ $this->query->add_where(0, $search_condition);
+ $this->query->add_groupby("$search_index.sid");
+ $matches = $this->search_query->matches();
+ $placeholder = $this->placeholder();
+ $this->query->add_having_expression(0, "COUNT(*) >= $placeholder", array($placeholder => $matches));
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/search/views_handler_field_search_score.inc b/sites/all/modules/views/modules/search/views_handler_field_search_score.inc
new file mode 100644
index 000000000..0feddac61
--- /dev/null
+++ b/sites/all/modules/views/modules/search/views_handler_field_search_score.inc
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_search_score.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to a node.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_search_score extends views_handler_field_numeric {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['alternate_sort'] = array('default' => '');
+ $options['alternate_order'] = array('default' => 'asc');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $style_options = $this->view->display_handler->get_option('style_options');
+ if (isset($style_options['default']) && $style_options['default'] == $this->options['id']) {
+ $handlers = $this->view->display_handler->get_handlers('field');
+ $options = array('' => t('No alternate'));
+ foreach ($handlers as $id => $handler) {
+ $options[$id] = $handler->ui_name();
+ }
+
+ $form['alternate_sort'] = array(
+ '#type' => 'select',
+ '#title' => t('Alternative sort'),
+ '#description' => t('Pick an alternative default table sort field to use when the search score field is unavailable.'),
+ '#options' => $options,
+ '#default_value' => $this->options['alternate_sort'],
+ );
+
+ $form['alternate_order'] = array(
+ '#type' => 'select',
+ '#title' => t('Alternate sort order'),
+ '#options' => array('asc' => t('Ascending'), 'desc' => t('Descending')),
+ '#default_value' => $this->options['alternate_order'],
+ );
+ }
+
+ parent::options_form($form, $form_state);
+ }
+
+ function query() {
+ // Check to see if the search filter added 'score' to the table.
+ // Our filter stores it as $handler->search_score -- and we also
+ // need to check its relationship to make sure that we're using the same
+ // one or obviously this won't work.
+ foreach ($this->view->filter as $handler) {
+ if (isset($handler->search_score) && $handler->relationship == $this->relationship) {
+ $this->field_alias = $handler->search_score;
+ $this->table_alias = $handler->table_alias;
+ return;
+ }
+ }
+
+ // Hide this field if no search filter is in place.
+ $this->options['exclude'] = TRUE;
+ if (!empty($this->options['alternate_sort'])) {
+ if (isset($this->view->style_plugin->options['default']) && $this->view->style_plugin->options['default'] == $this->options['id']) {
+ // Since the style handler initiates fields, we plug these values right into the active handler.
+ $this->view->style_plugin->options['default'] = $this->options['alternate_sort'];
+ $this->view->style_plugin->options['order'] = $this->options['alternate_order'];
+ }
+ }
+ }
+
+ function render($values) {
+ // Only render if we exist.
+ if (isset($this->table_alias)) {
+ return parent::render($values);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/search/views_handler_filter_search.inc b/sites/all/modules/views/modules/search/views_handler_filter_search.inc
new file mode 100644
index 000000000..16515a769
--- /dev/null
+++ b/sites/all/modules/views/modules/search/views_handler_filter_search.inc
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * @file
+ * Contains a search filter handler.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to a node.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_search extends views_handler_filter {
+ var $always_multiple = TRUE;
+
+ /**
+ * Stores a viewsSearchQuery object to be able to use the search.module "api".
+ *
+ * @var viewsSearchQuery
+ */
+ var $search_query = NULL;
+
+ /**
+ * Checks if the search query has been parsed.
+ */
+ var $parsed = FALSE;
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['operator']['default'] = 'optional';
+ $options['remove_score'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Overrides views_handler_filter::options_form().
+ *
+ * Add an option to remove search scores from the query.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['remove_score'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remove search score'),
+ '#description' => t('Check this box to remove the search score from the query. This can help reduce help reduce duplicate search results when using this filter.'),
+ '#default_value' => $this->options['remove_score'],
+ );
+ }
+
+
+ /**
+ * Provide simple equality operator
+ */
+ function operator_form(&$form, &$form_state) {
+ $form['operator'] = array(
+ '#type' => 'radios',
+ '#title' => t('On empty input'),
+ '#default_value' => $this->operator,
+ '#options' => array(
+ 'optional' => t('Show All'),
+ 'required' => t('Show None'),
+ ),
+ );
+ }
+
+ /**
+ * Provide a simple textfield for equality
+ */
+ function value_form(&$form, &$form_state) {
+ $form['value'] = array(
+ '#type' => 'textfield',
+ '#size' => 15,
+ '#default_value' => $this->value,
+ '#attributes' => array('title' => t('Enter the terms you wish to search for.')),
+ '#title' => empty($form_state['exposed']) ? t('Value') : '',
+ );
+ }
+
+ /**
+ * Validate the options form.
+ */
+ function exposed_validate(&$form, &$form_state) {
+ if (!isset($this->options['expose']['identifier'])) {
+ return;
+ }
+
+ $key = $this->options['expose']['identifier'];
+ if (!empty($form_state['values'][$key])) {
+ $this->query_parse_search_expression($form_state['values'][$key]);
+ if (count($this->search_query->words()) == 0) {
+ form_set_error($key, format_plural(variable_get('minimum_word_size', 3), 'You must include at least one positive keyword with 1 character or more.', 'You must include at least one positive keyword with @count characters or more.'));
+ }
+ }
+ }
+
+ /**
+ * Take sure that parseSearchExpression is runned and everything is set up for it.
+ *
+ * @param $input
+ * The search phrase which was input by the user.
+ */
+ function query_parse_search_expression($input) {
+ if (!isset($this->search_query)) {
+ $this->parsed = TRUE;
+ $this->search_query = db_select('search_index', 'i', array('target' => 'slave'))->extend('viewsSearchQuery');
+ $this->search_query->searchExpression($input, $this->view->base_table);
+ $this->search_query->publicParseSearchExpression();
+ }
+ }
+
+ /**
+ * Add this filter to the query.
+ *
+ * Due to the nature of fapi, the value and the operator have an unintended
+ * level of indirection. You will find them in $this->operator
+ * and $this->value respectively.
+ */
+ function query() {
+ // Since attachment views don't validate the exposed input, parse the search
+ // expression if required.
+ if (!$this->parsed) {
+ $this->query_parse_search_expression($this->value);
+ }
+ $required = FALSE;
+ if (!isset($this->search_query)) {
+ $required = TRUE;
+ }
+ else {
+ $words = $this->search_query->words();
+ if (empty($words)) {
+ $required = TRUE;
+ }
+ }
+ if ($required) {
+ if ($this->operator == 'required') {
+ $this->query->add_where($this->options['group'], 'FALSE');
+ }
+ }
+ else {
+ $search_index = $this->ensure_my_table();
+
+ $search_condition = db_and();
+
+ if (!$this->options['remove_score']) {
+ // Create a new join to relate the 'serach_total' table to our current 'search_index' table.
+ $join = new views_join;
+ $join->construct('search_total', $search_index, 'word', 'word');
+ $search_total = $this->query->add_relationship('search_total', $join, $search_index);
+
+ $this->search_score = $this->query->add_field('', "SUM($search_index.score * $search_total.count)", 'score', array('aggregate' => TRUE));
+ }
+
+ if (empty($this->query->relationships[$this->relationship])) {
+ $base_table = $this->query->base_table;
+ }
+ else {
+ $base_table = $this->query->relationships[$this->relationship]['base'];
+ }
+ $search_condition->condition("$search_index.type", $base_table);
+ if (!$this->search_query->simple()) {
+ $search_dataset = $this->query->add_table('search_dataset');
+ $conditions = $this->search_query->conditions();
+ $condition_conditions =& $conditions->conditions();
+ foreach ($condition_conditions as $key => &$condition) {
+ // Take sure we just look at real conditions.
+ if (is_numeric($key)) {
+ // Replace the conditions with the table alias of views.
+ $this->search_query->condition_replace_string('d.', "$search_dataset.", $condition);
+ }
+ }
+ $search_conditions =& $search_condition->conditions();
+ $search_conditions = array_merge($search_conditions, $condition_conditions);
+ }
+ else {
+ // Stores each condition, so and/or on the filter level will still work.
+ $or = db_or();
+ foreach ($words as $word) {
+ $or->condition("$search_index.word", $word);
+ }
+
+ $search_condition->condition($or);
+ }
+
+ $this->query->add_where($this->options['group'], $search_condition);
+ $this->query->add_groupby("$search_index.sid");
+ $matches = $this->search_query->matches();
+ $placeholder = $this->placeholder();
+ $this->query->add_having_expression($this->options['group'], "COUNT(*) >= $placeholder", array($placeholder => $matches));
+ }
+ // Set to NULL to prevent PDO exception when views object is cached.
+ $this->search_query = NULL;
+ }
+}
+
+/**
+ * Extends the core SearchQuery.
+ */
+class viewsSearchQuery extends SearchQuery {
+ public function &conditions() {
+ return $this->conditions;
+ }
+ public function words() {
+ return $this->words;
+ }
+
+ public function simple() {
+ return $this->simple;
+ }
+
+ public function matches() {
+ return $this->matches;
+ }
+
+ public function publicParseSearchExpression() {
+ return $this->parseSearchExpression();
+ }
+
+ function condition_replace_string($search, $replace, &$condition) {
+ if ($condition['field'] instanceof DatabaseCondition) {
+ $conditions =& $condition['field']->conditions();
+ foreach ($conditions as $key => &$subcondition) {
+ if (is_numeric($key)) {
+ $this->condition_replace_string($search, $replace, $subcondition);
+ }
+ }
+ }
+ else {
+ $condition['field'] = str_replace($search, $replace, $condition['field']);
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/search/views_handler_sort_search_score.inc b/sites/all/modules/views/modules/search/views_handler_sort_search_score.inc
new file mode 100644
index 000000000..d37fb6513
--- /dev/null
+++ b/sites/all/modules/views/modules/search/views_handler_sort_search_score.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_sort_search_score.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to a node.
+ *
+ * @ingroup views_sort_handlers
+ */
+class views_handler_sort_search_score extends views_handler_sort {
+ function query() {
+ // Check to see if the search filter/argument added 'score' to the table.
+ // Our filter stores it as $handler->search_score -- and we also
+ // need to check its relationship to make sure that we're using the same
+ // one or obviously this won't work.
+ foreach (array('filter', 'argument') as $type) {
+ foreach ($this->view->{$type} as $handler) {
+ if (isset($handler->search_score) && $handler->relationship == $this->relationship) {
+ $this->query->add_orderby(NULL, NULL, $this->options['order'], $handler->search_score);
+ $this->table_alias = $handler->table_alias;
+ return;
+ }
+ }
+ }
+
+ // Do absolutely nothing if there is no filter/argument in place; there is no reason to
+ // sort on the raw scores with this handler.
+ }
+}
diff --git a/sites/all/modules/views/modules/search/views_plugin_row_search_view.inc b/sites/all/modules/views/modules/search/views_plugin_row_search_view.inc
new file mode 100644
index 000000000..e4aacdc36
--- /dev/null
+++ b/sites/all/modules/views/modules/search/views_plugin_row_search_view.inc
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_row_search_view.
+ */
+
+/**
+ * Plugin which performs a node_view on the resulting object.
+ */
+class views_plugin_row_search_view extends views_plugin_row {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['score'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['score'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display score'),
+ '#default_value' => $this->options['score'],
+ );
+ }
+
+ /**
+ * Override the behavior of the render() function.
+ */
+ function render($row) {
+ return theme($this->theme_functions(),
+ array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'row' => $row
+ ));
+ }
+}
diff --git a/sites/all/modules/views/modules/statistics.views.inc b/sites/all/modules/views/modules/statistics.views.inc
new file mode 100644
index 000000000..d6637f3ed
--- /dev/null
+++ b/sites/all/modules/views/modules/statistics.views.inc
@@ -0,0 +1,263 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for statistics.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function statistics_views_data() {
+ // Basic table information.
+
+ // ----------------------------------------------------------------
+ // node_counter table
+
+ $data['node_counter']['table']['group'] = t('Content statistics');
+
+ $data['node_counter']['table']['join'] = array(
+ // ...to the node table
+ 'node' => array(
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ );
+
+ // totalcount
+ $data['node_counter']['totalcount'] = array(
+ 'title' => t('Total views'),
+ 'help' => t('The total number of times the node has been viewed.'),
+
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // daycount
+ $data['node_counter']['daycount'] = array(
+ 'title' => t('Views today'),
+ 'help' => t('The total number of times the node has been viewed today.'),
+
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // timestamp
+ $data['node_counter']['timestamp'] = array(
+ 'title' => t('Most recent view'),
+ 'help' => t('The most recent time the node has been viewed.'),
+
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+
+ // ----------------------------------------------------------------
+ // accesslog table
+
+ $data['accesslog']['table']['group'] = t('Access log');
+
+ // Advertise this table as a possible base table
+ $data['accesslog']['table']['base'] = array(
+ 'field' => 'aid',
+ 'title' => t('Access log'),
+ 'help' => t('Stores site access information.'),
+ 'weight' => 10,
+ );
+
+ // For other base tables, explain how we join
+ $data['accesslog']['table']['join'] = array(
+ 'users' => array(
+ 'field' => 'uid',
+ 'left_field' => 'uid',
+ ),
+ );
+
+ // accesslog.aid
+ $data['accesslog']['aid'] = array(
+ 'title' => t('Aid'),
+ 'help' => t('Unique access event ID.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ 'name field' => 'wid',
+ 'numeric' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // session id
+ $data['accesslog']['sid'] = array(
+ 'title' => t('Session ID'),
+ 'help' => t('Browser session ID of user that visited page.'),
+
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // title
+ $data['accesslog']['title'] = array(
+ 'title' => t('Page title'),
+ 'help' => t('Title of page visited.'),
+
+ 'field' => array(
+ 'handler' => 'views_handler_field_accesslog_path',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // path
+ $data['accesslog']['path'] = array(
+ 'title' => t('Path'),
+ 'help' => t('Internal path to page visited (relative to Drupal root.)'),
+
+ 'field' => array(
+ 'handler' => 'views_handler_field_accesslog_path',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ //No argument here. Can't send forward slashes as arguments.
+ //Can be worked around by node ID.
+ //(but what about aliases?)
+ );
+
+ // referrer
+ $data['accesslog']['url'] = array(
+ 'title' => t('Referrer'),
+ 'help' => t('Referrer URI.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_url',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // hostname
+ $data['accesslog']['hostname'] = array(
+ 'title' => t('Hostname'),
+ 'help' => t('Hostname of user that visited the page.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // user
+ $data['accesslog']['uid'] = array(
+ 'title' => t('User'),
+ 'help' => t('The user who visited the site.'),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'users',
+ 'base field' => 'uid',
+ ),
+ );
+
+ // timer
+ $data['accesslog']['timer'] = array(
+ 'title' => t('Timer'),
+ 'help' => t('Time in milliseconds that the page took to load.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // timestamp
+ $data['accesslog']['timestamp'] = array(
+ 'title' => t('Timestamp'),
+ 'help' => t('Timestamp of when the page was visited.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+
+ return $data;
+}
diff --git a/sites/all/modules/views/modules/statistics.views_default.inc b/sites/all/modules/views/modules/statistics.views_default.inc
new file mode 100644
index 000000000..7f527d59a
--- /dev/null
+++ b/sites/all/modules/views/modules/statistics.views_default.inc
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * @file
+ * Bulk export of views_default objects generated by Bulk export module.
+ */
+
+/**
+ * Implementation of hook_views_default_views()
+ */
+function statistics_views_default_views() {
+ $views = array();
+
+ $view = new view;
+ $view->name = 'popular';
+ $view->description = 'Shows the most-viewed nodes on the site. This requires the statistics to be enabled at administer >> reports >> access log settings.';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Popular content';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'Popular content';
+ $handler->display->display_options['use_more'] = TRUE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access content';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '25';
+ $handler->display->display_options['style_plugin'] = 'table';
+ $handler->display->display_options['style_options']['columns'] = array(
+ 'type' => 'type',
+ 'title' => 'title',
+ 'name' => 'name',
+ 'timestamp' => 'title',
+ 'totalcount' => 'totalcount',
+ );
+ $handler->display->display_options['style_options']['default'] = '-1';
+ $handler->display->display_options['style_options']['info'] = array(
+ 'type' => array(
+ 'sortable' => 0,
+ 'separator' => '',
+ ),
+ 'title' => array(
+ 'sortable' => 0,
+ 'separator' => '',
+ ),
+ 'name' => array(
+ 'sortable' => 0,
+ 'separator' => '',
+ ),
+ 'timestamp' => array(
+ 'separator' => '',
+ ),
+ 'totalcount' => array(
+ 'sortable' => 0,
+ 'separator' => '',
+ ),
+ );
+ $handler->display->display_options['style_options']['override'] = 0;
+ $handler->display->display_options['style_options']['order'] = 'desc';
+ /* Field: Content: Type */
+ $handler->display->display_options['fields']['type']['id'] = 'type';
+ $handler->display->display_options['fields']['type']['table'] = 'node';
+ $handler->display->display_options['fields']['type']['field'] = 'type';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ /* Field: User: Name */
+ $handler->display->display_options['fields']['name']['id'] = 'name';
+ $handler->display->display_options['fields']['name']['table'] = 'users';
+ $handler->display->display_options['fields']['name']['field'] = 'name';
+ $handler->display->display_options['fields']['name']['label'] = 'Author';
+ /* Field: Content: Has new content */
+ $handler->display->display_options['fields']['timestamp']['id'] = 'timestamp';
+ $handler->display->display_options['fields']['timestamp']['table'] = 'history';
+ $handler->display->display_options['fields']['timestamp']['field'] = 'timestamp';
+ $handler->display->display_options['fields']['timestamp']['label'] = '';
+ $handler->display->display_options['fields']['timestamp']['link_to_node'] = 0;
+ $handler->display->display_options['fields']['timestamp']['comments'] = 1;
+ /* Sort criterion: Content statistics: Total views */
+ $handler->display->display_options['sorts']['totalcount']['id'] = 'totalcount';
+ $handler->display->display_options['sorts']['totalcount']['table'] = 'node_counter';
+ $handler->display->display_options['sorts']['totalcount']['field'] = 'totalcount';
+ $handler->display->display_options['sorts']['totalcount']['order'] = 'DESC';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = '1';
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content statistics: Total views */
+ $handler->display->display_options['filters']['totalcount']['id'] = 'totalcount';
+ $handler->display->display_options['filters']['totalcount']['table'] = 'node_counter';
+ $handler->display->display_options['filters']['totalcount']['field'] = 'totalcount';
+ $handler->display->display_options['filters']['totalcount']['operator'] = '>';
+ $handler->display->display_options['filters']['totalcount']['value']['value'] = '0';
+ $handler->display->display_options['filters']['totalcount']['group'] = 0;
+ $handler->display->display_options['filters']['totalcount']['expose']['operator'] = FALSE;
+
+ /* Display: Popular (page) */
+ $handler = $view->new_display('page', 'Popular (page)', 'page');
+ $handler->display->display_options['path'] = 'popular/all';
+ $handler->display->display_options['menu']['type'] = 'default tab';
+ $handler->display->display_options['menu']['title'] = 'Popular content';
+ $handler->display->display_options['menu']['weight'] = '-1';
+ $handler->display->display_options['tab_options']['type'] = 'normal';
+ $handler->display->display_options['tab_options']['title'] = 'Popular content';
+ $handler->display->display_options['tab_options']['weight'] = '';
+
+ /* Display: Today (page) */
+ $handler = $view->new_display('page', 'Today (page)', 'page_1');
+ $handler->display->display_options['defaults']['fields'] = FALSE;
+ /* Field: Content: Type */
+ $handler->display->display_options['fields']['type']['id'] = 'type';
+ $handler->display->display_options['fields']['type']['table'] = 'node';
+ $handler->display->display_options['fields']['type']['field'] = 'type';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ /* Field: User: Name */
+ $handler->display->display_options['fields']['name']['id'] = 'name';
+ $handler->display->display_options['fields']['name']['table'] = 'users';
+ $handler->display->display_options['fields']['name']['field'] = 'name';
+ $handler->display->display_options['fields']['name']['label'] = 'Author';
+ /* Field: Content: Has new content */
+ $handler->display->display_options['fields']['timestamp']['id'] = 'timestamp';
+ $handler->display->display_options['fields']['timestamp']['table'] = 'history';
+ $handler->display->display_options['fields']['timestamp']['field'] = 'timestamp';
+ $handler->display->display_options['fields']['timestamp']['label'] = '';
+ $handler->display->display_options['fields']['timestamp']['link_to_node'] = 0;
+ $handler->display->display_options['fields']['timestamp']['comments'] = 1;
+ /* Field: Content statistics: Views today */
+ $handler->display->display_options['fields']['daycount']['id'] = 'daycount';
+ $handler->display->display_options['fields']['daycount']['table'] = 'node_counter';
+ $handler->display->display_options['fields']['daycount']['field'] = 'daycount';
+ $handler->display->display_options['defaults']['sorts'] = FALSE;
+ /* Sort criterion: Content statistics: Views today */
+ $handler->display->display_options['sorts']['daycount']['id'] = 'daycount';
+ $handler->display->display_options['sorts']['daycount']['table'] = 'node_counter';
+ $handler->display->display_options['sorts']['daycount']['field'] = 'daycount';
+ $handler->display->display_options['sorts']['daycount']['order'] = 'DESC';
+ $handler->display->display_options['path'] = 'popular/today';
+ $handler->display->display_options['menu']['type'] = 'tab';
+ $handler->display->display_options['menu']['title'] = 'Today\'s popular content';
+ $handler->display->display_options['menu']['weight'] = '0';
+ $handler->display->display_options['tab_options']['type'] = 'normal';
+ $handler->display->display_options['tab_options']['title'] = 'Popular content';
+ $handler->display->display_options['tab_options']['weight'] = '0';
+
+ /* Display: Popular (block) */
+ $handler = $view->new_display('block', 'Popular (block)', 'block');
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'list';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'fields';
+ $handler->display->display_options['row_options']['inline'] = array(
+ 'title' => 'title',
+ 'totalcount' => 'totalcount',
+ );
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['defaults']['fields'] = FALSE;
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+ /* Field: Content statistics: Total views */
+ $handler->display->display_options['fields']['totalcount']['id'] = 'totalcount';
+ $handler->display->display_options['fields']['totalcount']['table'] = 'node_counter';
+ $handler->display->display_options['fields']['totalcount']['field'] = 'totalcount';
+ $handler->display->display_options['fields']['totalcount']['label'] = '';
+ $handler->display->display_options['fields']['totalcount']['prefix'] = ' (';
+ $handler->display->display_options['fields']['totalcount']['suffix'] = ')';
+
+ /* Display: Today (block) */
+ $handler = $view->new_display('block', 'Today (block)', 'block_1');
+ $handler->display->display_options['defaults']['title'] = FALSE;
+ $handler->display->display_options['title'] = 'Today\'s popular content';
+ $handler->display->display_options['defaults']['link_display'] = FALSE;
+ $handler->display->display_options['link_display'] = 'page_1';
+ $handler->display->display_options['defaults']['style_plugin'] = FALSE;
+ $handler->display->display_options['style_plugin'] = 'list';
+ $handler->display->display_options['defaults']['style_options'] = FALSE;
+ $handler->display->display_options['defaults']['row_plugin'] = FALSE;
+ $handler->display->display_options['row_plugin'] = 'fields';
+ $handler->display->display_options['row_options']['inline'] = array(
+ 'title' => 'title',
+ 'daycount' => 'daycount',
+ );
+ $handler->display->display_options['defaults']['row_options'] = FALSE;
+ $handler->display->display_options['defaults']['fields'] = FALSE;
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+ /* Field: Content statistics: Views today */
+ $handler->display->display_options['fields']['daycount']['id'] = 'daycount';
+ $handler->display->display_options['fields']['daycount']['table'] = 'node_counter';
+ $handler->display->display_options['fields']['daycount']['field'] = 'daycount';
+ $handler->display->display_options['fields']['daycount']['label'] = '';
+ $handler->display->display_options['fields']['daycount']['prefix'] = ' (';
+ $handler->display->display_options['fields']['daycount']['suffix'] = ')';
+ $handler->display->display_options['defaults']['sorts'] = FALSE;
+ /* Sort criterion: Content statistics: Views today */
+ $handler->display->display_options['sorts']['daycount']['id'] = 'daycount';
+ $handler->display->display_options['sorts']['daycount']['table'] = 'node_counter';
+ $handler->display->display_options['sorts']['daycount']['field'] = 'daycount';
+ $handler->display->display_options['sorts']['daycount']['order'] = 'DESC';
+ $translatables['popular'] = array(
+ t('Master'),
+ t('Popular content'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('Type'),
+ t('Title'),
+ t('Author'),
+ t('Popular (page)'),
+ t('Today (page)'),
+ t('Views today'),
+ t('.'),
+ t(','),
+ t('Popular (block)'),
+ t(' ('),
+ t(')'),
+ t('Today (block)'),
+ t('Today\'s popular content'),
+ );
+
+ $views['popular'] = $view;
+
+ return $views;
+}
diff --git a/sites/all/modules/views/modules/statistics/views_handler_field_accesslog_path.inc b/sites/all/modules/views/modules/statistics/views_handler_field_accesslog_path.inc
new file mode 100644
index 000000000..85b2352da
--- /dev/null
+++ b/sites/all/modules/views/modules/statistics/views_handler_field_accesslog_path.inc
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_accesslog_path.
+ */
+
+/**
+ * Field handler to provide simple renderer that turns a URL into a clickable link.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_accesslog_path extends views_handler_field {
+ /**
+ * Override init function to provide generic option to link to node.
+ */
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ if (!empty($this->options['display_as_link'])) {
+ $this->additional_fields['path'] = 'path';
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['display_as_link'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Provide link to the page being visited.
+ */
+ function options_form(&$form, &$form_state) {
+ $form['display_as_link'] = array(
+ '#title' => t('Display as link'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['display_as_link']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+
+ function render_link($data, $values) {
+ if (!empty($this->options['display_as_link'])) {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = $this->get_value($values, 'path');
+ $this->options['alter']['html'] = TRUE;
+ }
+
+ return $data;
+ }
+}
diff --git a/sites/all/modules/views/modules/system.views.inc b/sites/all/modules/views/modules/system.views.inc
new file mode 100644
index 000000000..243cbc725
--- /dev/null
+++ b/sites/all/modules/views/modules/system.views.inc
@@ -0,0 +1,578 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for system.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function system_views_data() {
+ $data = array();
+
+ // ----------------------------------------------------------------------
+ // file_managed table
+
+ $data['files']['moved to'] = 'file_managed';
+ $data['file_managed']['table']['group'] = t('File');
+
+ // Advertise this table as a possible base table
+ $data['file_managed']['table']['base'] = array(
+ 'field' => 'fid',
+ 'title' => t('File'),
+ 'help' => t("Files maintained by Drupal and various modules."),
+ 'defaults' => array(
+ 'field' => 'filename'
+ ),
+ );
+ $data['file_managed']['table']['entity type'] = 'file';
+
+ // fid
+ $data['file_managed']['fid'] = array(
+ 'title' => t('File ID'),
+ 'help' => t('The ID of the file.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_file_fid',
+ 'name field' => 'filename', // the field to display in the summary.
+ 'numeric' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // filename
+ $data['file_managed']['filename'] = array(
+ 'title' => t('Name'),
+ 'help' => t('The name of the file.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // uri
+ $data['file_managed']['uri'] = array(
+ 'title' => t('Path'),
+ 'help' => t('The path of the file.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_uri',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // filemime
+ $data['file_managed']['filemime'] = array(
+ 'title' => t('Mime type'),
+ 'help' => t('The mime type of the file.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_filemime',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // extension
+ $data['file_managed']['extension'] = array(
+ 'title' => t('Extension'),
+ 'help' => t('The extension of the file.'),
+ 'real field' => 'filename',
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_extension',
+ 'click sortable' => FALSE,
+ ),
+ );
+
+ // filesize
+ $data['file_managed']['filesize'] = array(
+ 'title' => t('Size'),
+ 'help' => t('The size of the file.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_size',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ );
+
+ // status
+ $data['file_managed']['status'] = array(
+ 'title' => t('Status'),
+ 'help' => t('The status of the file.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_file_status',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_file_status',
+ ),
+ );
+
+ // timestamp field
+ $data['file_managed']['timestamp'] = array(
+ 'title' => t('Upload date'),
+ 'help' => t('The date the file was uploaded.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ // uid
+ $data['file_managed']['uid'] = array(
+ 'title' => t('User who uploaded'),
+ 'help' => t('The user that uploaded the file.'),
+ 'relationship' => array(
+ 'title' => t('User who uploaded'),
+ 'label' => t('User who uploaded'),
+ 'base' => 'users',
+ 'base field' => 'uid',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // file_usage table
+
+ $data['file_usage']['table']['group'] = t('File Usage');
+
+ // Provide field-type-things to several base tables; on the core files table ("file_managed") so
+ // that we can create relationships from files to entities, and then on each core entity type base
+ // table so that we can provide general relationships between entities and files.
+ $data['file_usage']['table']['join'] = array(
+ // Link ourself to the {file_managed} table so we can provide file->entity relationships.
+ 'file_managed' => array(
+ 'field' => 'fid',
+ 'left_field' => 'fid',
+ ),
+ // Link ourself to the {node} table so we can provide node->file relationships.
+ 'node' => array(
+ 'field' => 'id',
+ 'left_field' => 'nid',
+ 'extra' => array(array('field' => 'type', 'value' => 'node')),
+ ),
+ // Link ourself to the {users} table so we can provide user->file relationships.
+ 'users' => array(
+ 'field' => 'id',
+ 'left_field' => 'uid',
+ 'extra' => array(array('field' => 'type', 'value' => 'user')),
+ ),
+ // Link ourself to the {comment} table so we can provide comment->file relationships.
+ 'comment' => array(
+ 'field' => 'id',
+ 'left_field' => 'cid',
+ 'extra' => array(array('field' => 'type', 'value' => 'comment')),
+ ),
+ // Link ourself to the {taxonomy_term_data} table so we can provide taxonomy_term->file relationships.
+ 'taxonomy_term_data' => array(
+ 'field' => 'id',
+ 'left_field' => 'tid',
+ 'extra' => array(array('field' => 'type', 'value' => 'taxonomy_term')),
+ ),
+ // Link ourself to the {taxonomy_vocabulary} table so we can provide taxonomy_vocabulary->file relationships.
+ 'taxonomy_vocabulary' => array(
+ 'field' => 'id',
+ 'left_field' => 'vid',
+ 'extra' => array(array('field' => 'type', 'value' => 'taxonomy_vocabulary')),
+ ),
+ );
+
+ // Provide a relationship between the files table and each entity type, and between each entity
+ // type and the files table. Entity->file relationships are type-restricted in the joins
+ // declared above, and file->entity relationships are type-restricted in the relationship
+ // declarations below.
+
+ // Relationships between files and nodes.
+ $data['file_usage']['file_to_node'] = array(
+ 'title' => t('Content'),
+ 'help' => t('Content that is associated with this file, usually because this file is in a field on the content.'),
+ // Only provide this field/relationship/etc. when the 'file_managed' base table is present.
+ 'skip base' => array('node', 'node_revision', 'users', 'comment', 'taxonomy_term_data', 'taxonomy_vocabulary'),
+ 'real field' => 'id',
+ 'relationship' => array(
+ 'title' => t('Content'),
+ 'label' => t('Content'),
+ 'base' => 'node',
+ 'base field' => 'nid',
+ 'relationship field' => 'id',
+ 'extra' => array(array('table' => 'file_usage', 'field' => 'type', 'operator' => '=', 'value' => 'node')),
+ ),
+ );
+ $data['file_usage']['node_to_file'] = array(
+ 'title' => t('File'),
+ 'help' => t('A file that is associated with this node, usually because it is in a field on the node.'),
+ // Only provide this field/relationship/etc. when the 'node' base table is present.
+ 'skip base' => array('file_managed', 'users', 'comment', 'taxonomy_term_data', 'taxonomy_vocabulary'),
+ 'real field' => 'fid',
+ 'relationship' => array(
+ 'title' => t('File'),
+ 'label' => t('File'),
+ 'base' => 'file_managed',
+ 'base field' => 'fid',
+ 'relationship field' => 'fid',
+ ),
+ );
+
+ // Relationships between files and users.
+ $data['file_usage']['file_to_user'] = array(
+ 'title' => t('User'),
+ 'help' => t('A user that is associated with this file, usually because this file is in a field on the user.'),
+ // Only provide this field/relationship/etc. when the 'file_managed' base table is present.
+ 'skip base' => array('node', 'node_revision', 'users', 'comment', 'taxonomy_term_data', 'taxonomy_vocabulary'),
+ 'real field' => 'id',
+ 'relationship' => array(
+ 'title' => t('User'),
+ 'label' => t('User'),
+ 'base' => 'users',
+ 'base field' => 'uid',
+ 'relationship field' => 'id',
+ 'extra' => array(array('table' => 'file_usage', 'field' => 'type', 'operator' => '=', 'value' => 'user')),
+ ),
+ );
+ $data['file_usage']['user_to_file'] = array(
+ 'title' => t('File'),
+ 'help' => t('A file that is associated with this user, usually because it is in a field on the user.'),
+ // Only provide this field/relationship/etc. when the 'users' base table is present.
+ 'skip base' => array('file_managed', 'node', 'node_revision', 'comment', 'taxonomy_term_data', 'taxonomy_vocabulary'),
+ 'real field' => 'fid',
+ 'relationship' => array(
+ 'title' => t('File'),
+ 'label' => t('File'),
+ 'base' => 'file_managed',
+ 'base field' => 'fid',
+ 'relationship field' => 'fid',
+ ),
+ );
+
+ // Relationships between files and comments.
+ $data['file_usage']['file_to_comment'] = array(
+ 'title' => t('Comment'),
+ 'help' => t('A comment that is associated with this file, usually because this file is in a field on the comment.'),
+ // Only provide this field/relationship/etc. when the 'file_managed' base table is present.
+ 'skip base' => array('node', 'node_revision', 'users', 'comment', 'taxonomy_term_data', 'taxonomy_vocabulary'),
+ 'real field' => 'id',
+ 'relationship' => array(
+ 'title' => t('Comment'),
+ 'label' => t('Comment'),
+ 'base' => 'comment',
+ 'base field' => 'cid',
+ 'relationship field' => 'id',
+ 'extra' => array(array('table' => 'file_usage', 'field' => 'type', 'operator' => '=', 'value' => 'comment')),
+ ),
+ );
+ $data['file_usage']['comment_to_file'] = array(
+ 'title' => t('File'),
+ 'help' => t('A file that is associated with this comment, usually because it is in a field on the comment.'),
+ // Only provide this field/relationship/etc. when the 'comment' base table is present.
+ 'skip base' => array('file_managed', 'node', 'node_revision', 'users', 'taxonomy_term_data', 'taxonomy_vocabulary'),
+ 'real field' => 'fid',
+ 'relationship' => array(
+ 'title' => t('File'),
+ 'label' => t('File'),
+ 'base' => 'file_managed',
+ 'base field' => 'fid',
+ 'relationship field' => 'fid',
+ ),
+ );
+
+ // Relationships between files and taxonomy_terms.
+ $data['file_usage']['file_to_taxonomy_term'] = array(
+ 'title' => t('Taxonomy Term'),
+ 'help' => t('A taxonomy term that is associated with this file, usually because this file is in a field on the taxonomy term.'),
+ // Only provide this field/relationship/etc. when the 'file_managed' base table is present.
+ 'skip base' => array('node', 'node_revision', 'users', 'comment', 'taxonomy_term_data', 'taxonomy_vocabulary'),
+ 'real field' => 'id',
+ 'relationship' => array(
+ 'title' => t('Taxonomy Term'),
+ 'label' => t('Taxonomy Term'),
+ 'base' => 'taxonomy_term_data',
+ 'base field' => 'tid',
+ 'relationship field' => 'id',
+ 'extra' => array(array('table' => 'file_usage', 'field' => 'type', 'operator' => '=', 'value' => 'taxonomy_term')),
+ ),
+ );
+ $data['file_usage']['taxonomy_term_to_file'] = array(
+ 'title' => t('File'),
+ 'help' => t('A file that is associated with this taxonomy term, usually because it is in a field on the taxonomy term.'),
+ // Only provide this field/relationship/etc. when the 'taxonomy_term_data' base table is present.
+ 'skip base' => array('file_managed', 'node', 'node_revision', 'users', 'comment', 'taxonomy_vocabulary'),
+ 'real field' => 'fid',
+ 'relationship' => array(
+ 'title' => t('File'),
+ 'label' => t('File'),
+ 'base' => 'file_managed',
+ 'base field' => 'fid',
+ 'relationship field' => 'fid',
+ ),
+ );
+
+ // Relationships between files and taxonomy_vocabulary items.
+ $data['file_usage']['file_to_taxonomy_vocabulary'] = array(
+ 'title' => t('Taxonomy Vocabulary'),
+ 'help' => t('A taxonomy vocabulary that is associated with this file, usually because this file is in a field on the taxonomy vocabulary.'),
+ // Only provide this field/relationship/etc. when the 'file_managed' base table is present.
+ 'skip base' => array('node', 'node_revision', 'users', 'comment', 'taxonomy_term_data', 'taxonomy_vocabulary'),
+ 'real field' => 'id',
+ 'relationship' => array(
+ 'title' => t('Taxonomy Vocabulary'),
+ 'label' => t('Taxonomy Vocabulary'),
+ 'base' => 'taxonomy_vocabulary',
+ 'base field' => 'vid',
+ 'relationship field' => 'id',
+ 'extra' => array(array('table' => 'file_usage', 'field' => 'type', 'operator' => '=', 'value' => 'taxonomy_vocabulary')),
+ ),
+ );
+ $data['file_usage']['taxonomy_vocabulary_to_file'] = array(
+ 'title' => t('File'),
+ 'help' => t('A file that is associated with this taxonomy vocabulary, usually because it is in a field on the taxonomy vocabulary.'),
+ // Only provide this field/relationship/etc. when the 'taxonomy_vocabulary' base table is present.
+ 'skip base' => array('file_managed', 'node', 'node_revision', 'users', 'comment', 'taxonomy_term_data'),
+ 'real field' => 'fid',
+ 'relationship' => array(
+ 'title' => t('File'),
+ 'label' => t('File'),
+ 'base' => 'file_managed',
+ 'base field' => 'fid',
+ 'relationship field' => 'fid',
+ ),
+ );
+
+ // Provide basic fields from the {file_usage} table to all of the base tables we've declared
+ // joins to (because there is no 'skip base' property on these fields).
+ $data['file_usage']['module'] = array(
+ 'title' => t('Module'),
+ 'help' => t('The module managing this file relationship.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['file_usage']['type'] = array(
+ 'title' => t('Entity type'),
+ 'help' => t('The type of entity that is related to the file.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['file_usage']['id'] = array(
+ 'title' => t('Entity ID'),
+ 'help' => t('The ID of the entity that is related to the file.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['file_usage']['count'] = array(
+ 'title' => t('Use count'),
+ 'help' => t('The number of times the file is used by this entity.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // system table
+ $data['system']['table']['group'] = t('System');
+
+ // Advertise this table as a possible base table
+ $data['system']['table']['base'] = array(
+ 'field' => 'filename',
+ 'title' => t('Module/Theme/Theme engine'),
+ 'help' => t('Modules/Themes/Theme engines in your codebase.'),
+ );
+
+ // fields
+ // - filename
+ $data['system']['filename'] = array(
+ 'title' => t('Module/Theme/Theme engine filename'),
+ 'help' => t('The path of the primary file for this item, relative to the Drupal root; e.g. modules/node/node.module.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ 'name field' => 'filename', // the field to display in the summary.
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ // - name
+ $data['system']['name'] = array(
+ 'title' => t('Module/Theme/Theme engine name'),
+ 'help' => t('The name of the item; e.g. node.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ 'name field' => 'name', // the field to display in the summary.
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ // - type
+ $data['system']['type'] = array(
+ 'title' => t('Type'),
+ 'help' => t('The type of the item, either module, theme, or theme_engine.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ 'name field' => 'type', // the field to display in the summary.
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_system_type',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ // - status
+ $data['system']['status'] = array(
+ 'title' => t('Status'),
+ 'help' => t('Boolean indicating whether or not this item is enabled.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ 'name field' => 'status', // the field to display in the summary.
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ // - schema version
+ $data['system']['schema_version'] = array(
+ 'title' => t('Schema version'),
+ 'help' => t("The module's database schema version number. -1 if the module is not installed (its tables do not exist); 0 or the largest N of the module's hook_update_N() function that has either been run or existed when the module was first installed."),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ 'name field' => 'schema_version', // the field to display in the summary.
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ return $data;
+}
+
+function _views_file_status($choice = NULL) {
+ $status = array(
+ 0 => t('Temporary'),
+ FILE_STATUS_PERMANENT => t('Permanent'),
+ );
+
+ if (isset($choice)) {
+ return isset($status[$choice]) ? $status[$choice] : t('Unknown');
+ }
+
+ return $status;
+}
diff --git a/sites/all/modules/views/modules/system/views_handler_argument_file_fid.inc b/sites/all/modules/views/modules/system/views_handler_argument_file_fid.inc
new file mode 100644
index 000000000..aa2d94719
--- /dev/null
+++ b/sites/all/modules/views/modules/system/views_handler_argument_file_fid.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_file_fid.
+ */
+
+/**
+ * Argument handler to accept multiple file ids.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_file_fid extends views_handler_argument_numeric {
+ /**
+ * Override the behavior of title_query(). Get the filenames.
+ */
+ function title_query() {
+ $titles = db_select('file_managed', 'f')
+ ->fields('f', array('filename'))
+ ->condition('fid', $this->value)
+ ->execute()
+ ->fetchCol();
+ foreach ($titles as &$title) {
+ $title = check_plain($title);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/system/views_handler_field_file.inc b/sites/all/modules/views/modules/system/views_handler_field_file.inc
new file mode 100644
index 000000000..4168acf75
--- /dev/null
+++ b/sites/all/modules/views/modules/system/views_handler_field_file.inc
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to a file.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file extends views_handler_field {
+ /**
+ * Constructor to provide additional field to add.
+ */
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ if (!empty($options['link_to_file'])) {
+ $this->additional_fields['uri'] = 'uri';
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_file'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Provide link to file option
+ */
+ function options_form(&$form, &$form_state) {
+ $form['link_to_file'] = array(
+ '#title' => t('Link this field to download the file'),
+ '#description' => t("Enable to override this field's links."),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['link_to_file']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ /**
+ * Render whatever the data is as a link to the file.
+ *
+ * Data should be made XSS safe prior to calling this function.
+ */
+ function render_link($data, $values) {
+ if (!empty($this->options['link_to_file']) && $data !== NULL && $data !== '') {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = file_create_url($this->get_value($values, 'uri'));
+ }
+
+ return $data;
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/system/views_handler_field_file_extension.inc b/sites/all/modules/views/modules/system/views_handler_field_file_extension.inc
new file mode 100644
index 000000000..b543d8a23
--- /dev/null
+++ b/sites/all/modules/views/modules/system/views_handler_field_file_extension.inc
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_extension.
+ */
+
+/**
+ * Returns a pure file extension of the file, for example 'module'.
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_extension extends views_handler_field {
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options['extension_detect_tar'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['extension_detect_tar'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Detect if tar is part of the extension'),
+ '#description' => t("See if the previous extension is '.tar' and if so, add that, so we see 'tar.gz' or 'tar.bz2' instead of just 'gz'."),
+ '#default_value' => $this->options['extension_detect_tar'],
+ );
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ if (!$this->options['extension_detect_tar']) {
+ if (preg_match('/\.([^\.]+)$/', $value, $match)) {
+ return $this->sanitize_value($match[1]);
+ }
+ }
+ else {
+ $file_parts = explode('.', basename($value));
+ // If there is an extension.
+ if (count($file_parts) > 1) {
+ $extension = array_pop($file_parts);
+ $last_part_in_name = array_pop($file_parts);
+ if ($last_part_in_name === 'tar') {
+ $extension = 'tar.' . $extension;
+ }
+ return $this->sanitize_value($extension);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/system/views_handler_field_file_filemime.inc b/sites/all/modules/views/modules/system/views_handler_field_file_filemime.inc
new file mode 100644
index 000000000..318fdcff3
--- /dev/null
+++ b/sites/all/modules/views/modules/system/views_handler_field_file_filemime.inc
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_filemime.
+ */
+
+/**
+ * Field handler to add rendering MIME type images as an option on the filemime field.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_filemime extends views_handler_field_file {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['filemime_image'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['filemime_image'] = array(
+ '#title' => t('Display an icon representing the file type, instead of the MIME text (such as "image/jpeg")'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['filemime_image']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render($values) {
+ $data = $values->{$this->field_alias};
+ if (!empty($this->options['filemime_image']) && $data !== NULL && $data !== '') {
+ $fake_file = (object) array('filemime' => $data);
+ $data = theme('file_icon', array('file' => $fake_file));
+ }
+
+ return $this->render_link($data, $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/system/views_handler_field_file_status.inc b/sites/all/modules/views/modules/system/views_handler_field_file_status.inc
new file mode 100644
index 000000000..ac1022c8d
--- /dev/null
+++ b/sites/all/modules/views/modules/system/views_handler_field_file_status.inc
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_status.
+ */
+
+/**
+ * Field handler to translate a node type into its readable form.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_file_status extends views_handler_field {
+ function render($values) {
+ $value = $this->get_value($values);
+ return _views_file_status($value);
+ }
+}
diff --git a/sites/all/modules/views/modules/system/views_handler_field_file_uri.inc b/sites/all/modules/views/modules/system/views_handler_field_file_uri.inc
new file mode 100644
index 000000000..334e5051a
--- /dev/null
+++ b/sites/all/modules/views/modules/system/views_handler_field_file_uri.inc
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_file_uri.
+ */
+
+/**
+ * Field handler to add rendering file paths as file URLs instead of as internal file URIs.
+ */
+class views_handler_field_file_uri extends views_handler_field_file {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['file_download_path'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['file_download_path'] = array(
+ '#title' => t('Display download path instead of file storage URI'),
+ '#description' => t('This will provide the full download URL rather than the internal filestream address.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['file_download_path']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render($values) {
+ $data = $values->{$this->field_alias};
+ if (!empty($this->options['file_download_path']) && $data !== NULL && $data !== '') {
+ $data = file_create_url($data);
+ }
+ return $this->render_link($data, $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/system/views_handler_filter_file_status.inc b/sites/all/modules/views/modules/system/views_handler_filter_file_status.inc
new file mode 100644
index 000000000..6194395bf
--- /dev/null
+++ b/sites/all/modules/views/modules/system/views_handler_filter_file_status.inc
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_file_status.
+ */
+
+/**
+ * Filter by file status.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_file_status extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_options = _views_file_status();
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/system/views_handler_filter_system_type.inc b/sites/all/modules/views/modules/system/views_handler_filter_system_type.inc
new file mode 100644
index 000000000..84d4bcd86
--- /dev/null
+++ b/sites/all/modules/views/modules/system/views_handler_filter_system_type.inc
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_system_type.
+ */
+
+/**
+ * Filter by system type.
+ */
+class views_handler_filter_system_type extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (!isset($this->value_options)) {
+ $this->value_title = t('Type');
+ // Enable filtering by type.
+ $types = array();
+ $types = db_query('SELECT DISTINCT(type) FROM {system} ORDER BY type')->fetchAllKeyed(0, 0);
+ $this->value_options = $types;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy.views.inc b/sites/all/modules/views/modules/taxonomy.views.inc
new file mode 100644
index 000000000..58d62d146
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy.views.inc
@@ -0,0 +1,540 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for taxonomy.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function taxonomy_views_data() {
+ $data = array();
+
+ // ----------------------------------------------------------------------
+ // taxonomy_vocabulary table
+
+ $data['vocabulary']['moved to'] = 'taxonomy_vocabulary';
+ $data['taxonomy_vocabulary']['table']['group'] = t('Taxonomy vocabulary');
+
+ $data['taxonomy_vocabulary']['table']['join'] = array(
+ // vocabulary links to taxonomy_term_data directly via vid.
+ 'taxonomy_term_data' => array(
+ 'left_field' => 'vid',
+ 'field' => 'vid',
+ ),
+ );
+
+ // Provide a "default relationship" to keep older views from choking.
+ $data['taxonomy_vocabulary']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'term_node_tid',
+ ),
+ );
+
+ // vocabulary name
+ $data['taxonomy_vocabulary']['name'] = array(
+ 'title' => t('Name'), // The item it appears as on the UI,
+ 'field' => array(
+ 'help' => t('Name of the vocabulary a term is a member of. This will be the vocabulary that whichever term the "Taxonomy: Term" field is; and can similarly cause duplicates.'),
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ 'help' => t('The taxonomy vocabulary name'),
+ ),
+ );
+ $data['taxonomy_vocabulary']['machine_name'] = array(
+ 'title' => t('Machine name'), // The item it appears as on the UI,
+ 'field' => array(
+ 'help' => t('Machine-Name of the vocabulary a term is a member of. This will be the vocabulary that whichever term the "Taxonomy: Term" field is; and can similarly cause duplicates.'),
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'help' => t('Filter the results of "Taxonomy: Term" to a particular vocabulary.'),
+ 'handler' => 'views_handler_filter_vocabulary_machine_name',
+ ),
+ 'argument' => array(
+ 'help' => t('Filter the results of "Taxonomy: Term" to a particular vocabulary.'),
+ 'handler' => 'views_handler_argument_vocabulary_machine_name',
+ ),
+ );
+ $data['taxonomy_vocabulary']['vid'] = array(
+ 'title' => t('Vocabulary ID'), // The item it appears as on the UI,
+ 'help' => t('The taxonomy vocabulary ID'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_vocabulary_vid',
+ 'name field' => 'name',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['taxonomy_vocabulary']['description'] = array(
+ 'title' => t('Description'), // The item it appears as on the UI,
+ 'help' => t('The taxonomy vocabulary description'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ ),
+ );
+ $data['taxonomy_vocabulary']['weight'] = array(
+ 'title' => t('Weight'),
+ 'help' => t('The taxonomy vocabulary weight'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ 'name field' => 'weight',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // taxonomy_term_data table
+
+ $data['term_data']['moved to'] = 'taxonomy_term_data';
+ $data['taxonomy_term_data']['table']['group'] = t('Taxonomy term');
+ $data['taxonomy_term_data']['table']['base'] = array(
+ 'field' => 'tid',
+ 'title' => t('Term'),
+ 'help' => t('Taxonomy terms are attached to nodes.'),
+ 'access query tag' => 'term_access',
+ );
+ $data['taxonomy_term_data']['table']['entity type'] = 'taxonomy_term';
+
+
+
+ // The term data table
+ $data['taxonomy_term_data']['table']['join'] = array(
+ 'taxonomy_vocabulary' => array(
+ 'field' => 'vid',
+ 'left_field' => 'vid',
+ ),
+ // This is provided for many_to_one argument
+ 'taxonomy_index' => array(
+ 'field' => 'tid',
+ 'left_field' => 'tid',
+ ),
+ );
+
+ // Provide a "default relationship" to keep older views from choking.
+ $data['taxonomy_term_data']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'term_node_tid',
+ ),
+ );
+
+ // tid field
+ $data['taxonomy_term_data']['tid'] = array(
+ 'title' => t('Term ID'),
+ 'help' => t('The tid of a taxonomy term.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_taxonomy',
+ 'name field' => 'name',
+ 'zero is null' => TRUE,
+ ),
+ 'filter' => array(
+ 'title' => t('Term'),
+ 'help' => t('Taxonomy term chosen from autocomplete or select widget.'),
+ 'handler' => 'views_handler_filter_term_node_tid',
+ 'hierarchy table' => 'taxonomy_term_hierarchy',
+ 'numeric' => TRUE,
+ ),
+ );
+
+ // raw tid field
+ $data['taxonomy_term_data']['tid_raw'] = array(
+ 'title' => t('Term ID'),
+ 'help' => t('The tid of a taxonomy term.'),
+ 'real field' => 'tid',
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ 'allow empty' => TRUE,
+ ),
+ );
+
+ $data['taxonomy_term_data']['tid_representative'] = array(
+ 'relationship' => array(
+ 'title' => t('Representative node'),
+ 'label' => t('Representative node'),
+ 'help' => t('Obtains a single representative node for each term, according to a chosen sort criterion.'),
+ 'handler' => 'views_handler_relationship_groupwise_max',
+ 'relationship field' => 'tid',
+ 'outer field' => 'taxonomy_term_data.tid',
+ 'argument table' => 'taxonomy_term_data',
+ 'argument field' => 'tid',
+ 'base' => 'node',
+ 'field' => 'nid',
+ ),
+ );
+
+ // Term name field
+ $data['taxonomy_term_data']['name'] = array(
+ 'title' => t('Name'),
+ 'help' => t('The taxonomy term name.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_taxonomy',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ 'help' => t('Taxonomy term name.'),
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ 'help' => t('Taxonomy term name.'),
+ 'many to one' => TRUE,
+ 'empty field name' => t('Uncategorized'),
+ ),
+ );
+
+ // taxonomy weight
+ $data['taxonomy_term_data']['weight'] = array(
+ 'title' => t('Weight'),
+ 'help' => t('The term weight field'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ );
+
+ // Term description
+ $data['taxonomy_term_data']['description'] = array(
+ 'title' => t('Term description'),
+ 'help' => t('The description associated with a taxonomy term.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_markup',
+ 'format' => array('field' => 'format'),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ // Term vocabulary
+ $data['taxonomy_term_data']['vid'] = array(
+ 'title' => t('Vocabulary'),
+ 'help' => t('Filter the results of "Taxonomy: Term" to a particular vocabulary.'),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_vocabulary_vid',
+ ),
+ );
+
+ // Link to edit the term
+ $data['taxonomy_term_data']['edit_term'] = array(
+ 'field' => array(
+ 'title' => t('Term edit link'),
+ 'help' => t('Provide a simple link to edit the term.'),
+ 'handler' => 'views_handler_field_term_link_edit',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // taxonomy_index table
+
+ $data['term_node']['moved to'] = 'taxonomy_index';
+ $data['taxonomy_index']['table']['group'] = t('Taxonomy term');
+
+ $data['taxonomy_index']['table']['join'] = array(
+ 'taxonomy_term_data' => array(
+ // links directly to taxonomy_term_data via tid
+ 'left_field' => 'tid',
+ 'field' => 'tid',
+ ),
+ 'node' => array(
+ // links directly to node via nid
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ 'taxonomy_term_hierarchy' => array(
+ 'left_field' => 'tid',
+ 'field' => 'tid',
+ ),
+ );
+
+ $data['taxonomy_index']['nid'] = array(
+ 'title' => t('Content with term'),
+ 'help' => t('Relate all content tagged with a term.'),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'node',
+ 'base field' => 'nid',
+ 'label' => t('node'),
+ 'skip base' => 'node',
+ ),
+ );
+
+ // @todo This stuff needs to move to a node field since
+ // really it's all about nodes.
+ // tid field
+ $data['taxonomy_index']['tid'] = array(
+ 'group' => t('Content'),
+ 'title' => t('Has taxonomy term ID'),
+ 'help' => t('Display content if it has the selected taxonomy terms.'),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_term_node_tid',
+ 'name table' => 'taxonomy_term_data',
+ 'name field' => 'name',
+ 'empty field name' => t('Uncategorized'),
+ 'numeric' => TRUE,
+ 'skip base' => 'taxonomy_term_data',
+ ),
+ 'filter' => array(
+ 'title' => t('Has taxonomy term'),
+ 'handler' => 'views_handler_filter_term_node_tid',
+ 'hierarchy table' => 'taxonomy_term_hierarchy',
+ 'numeric' => TRUE,
+ 'skip base' => 'taxonomy_term_data',
+ 'allow empty' => TRUE,
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // term_hierarchy table
+
+ $data['taxonomy_term_hierarchy']['table']['group'] = t('Taxonomy term');
+
+ $data['term_hierarchy']['moved to'] = 'taxonomy_term_hierarchy';
+ $data['taxonomy_term_hierarchy']['table']['join'] = array(
+ 'taxonomy_term_hierarchy' => array(
+ // links to self through left.parent = right.tid (going down in depth)
+ 'left_field' => 'tid',
+ 'field' => 'parent',
+ ),
+ 'taxonomy_term_data' => array(
+ // links directly to taxonomy_term_data via tid
+ 'left_field' => 'tid',
+ 'field' => 'tid',
+ ),
+ );
+
+ // Provide a "default relationship" to keep older views from choking.
+ $data['taxonomy_term_hierarchy']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'term_node_tid',
+ ),
+ );
+
+ $data['taxonomy_term_hierarchy']['parent'] = array(
+ 'title' => t('Parent term'),
+ 'help' => t('The parent term of the term. This can produce duplicate entries if you are using a vocabulary that allows multiple parents.'),
+ 'relationship' => array(
+ 'base' => 'taxonomy_term_data',
+ 'field' => 'parent',
+ 'label' => t('Parent'),
+ ),
+ 'filter' => array(
+ 'help' => t('Filter the results of "Taxonomy: Term" by the parent pid.'),
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'argument' => array(
+ 'help' => t('The parent term of the term.'),
+ 'handler' => 'views_handler_argument_taxonomy',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function taxonomy_views_data_alter(&$data) {
+ $data['node']['term_node_tid'] = array(
+ 'title' => t('Taxonomy terms on node'),
+ 'help' => t('Relate nodes to taxonomy terms, specifiying which vocabulary or vocabularies to use. This relationship will cause duplicated records if there are multiple terms.'),
+ 'relationship' => array(
+ 'handler' => 'views_handler_relationship_node_term_data',
+ 'label' => t('term'),
+ 'base' => 'taxonomy_term_data',
+ ),
+ 'field' => array(
+ 'title' => t('All taxonomy terms'),
+ 'help' => t('Display all taxonomy terms associated with a node from specified vocabularies.'),
+ 'handler' => 'views_handler_field_term_node_tid',
+ 'no group by' => TRUE,
+ ),
+ );
+
+ $data['node']['term_node_tid_depth'] = array(
+ 'help' => t('Display content if it has the selected taxonomy terms, or children of the selected terms. Due to additional complexity, this has fewer options than the versions without depth.'),
+ 'real field' => 'nid',
+ 'argument' => array(
+ 'title' => t('Has taxonomy term ID (with depth)'),
+ 'handler' => 'views_handler_argument_term_node_tid_depth',
+ 'accept depth modifier' => TRUE,
+ ),
+ 'filter' => array(
+ 'title' => t('Has taxonomy terms (with depth)'),
+ 'handler' => 'views_handler_filter_term_node_tid_depth',
+ ),
+ );
+
+ $data['node']['term_node_tid_depth_modifier'] = array(
+ 'title' => t('Has taxonomy term ID depth modifier'),
+ 'help' => t('Allows the "depth" for Taxonomy: Term ID (with depth) to be modified via an additional contextual filter value.'),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_term_node_tid_depth_modifier',
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_views_data().
+ *
+ * Views integration for taxonomy_term_reference fields. Adds a term relationship to the default
+ * field data.
+ *
+ * @see field_views_field_default_views_data()
+ */
+function taxonomy_field_views_data($field) {
+ $data = field_views_field_default_views_data($field);
+ foreach ($data as $table_name => $table_data) {
+ foreach ($table_data as $field_name => $field_data) {
+ if (isset($field_data['filter']) && $field_name != 'delta') {
+ $data[$table_name][$field_name]['filter']['handler'] = 'views_handler_filter_term_node_tid';
+ $data[$table_name][$field_name]['filter']['vocabulary'] = $field['settings']['allowed_values'][0]['vocabulary'];
+ }
+ }
+
+ // Add the relationship only on the tid field.
+ $data[$table_name][$field['field_name'] . '_tid']['relationship'] = array(
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'taxonomy_term_data',
+ 'base field' => 'tid',
+ 'label' => t('term from !field_name', array('!field_name' => $field['field_name'])),
+ );
+
+ }
+
+ return $data;
+}
+
+/**
+ * Implements hook_field_views_data_views_data_alter().
+ *
+ * Views integration to provide reverse relationships on term references.
+ */
+function taxonomy_field_views_data_views_data_alter(&$data, $field) {
+ foreach ($field['bundles'] as $entity_type => $bundles) {
+ $entity_info = entity_get_info($entity_type);
+ $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
+
+ list($label, $all_labels) = field_views_field_label($field['field_name']);
+ $entity = $entity_info['label'];
+ if ($entity == t('Node')) {
+ $entity = t('Content');
+ }
+
+ $data['taxonomy_term_data'][$pseudo_field_name]['relationship'] = array(
+ 'title' => t('@entity using @field', array('@entity' => $entity, '@field' => $label)),
+ 'help' => t('Relate each @entity with a @field set to the term.', array('@entity' => $entity, '@field' => $label)),
+ 'handler' => 'views_handler_relationship_entity_reverse',
+ 'field_name' => $field['field_name'],
+ 'field table' => _field_sql_storage_tablename($field),
+ 'field field' => $field['field_name'] . '_tid',
+ 'base' => $entity_info['base table'],
+ 'base field' => $entity_info['entity keys']['id'],
+ 'label' => t('!field_name', array('!field_name' => $field['field_name'])),
+ 'join_extra' => array(
+ 0 => array(
+ 'field' => 'entity_type',
+ 'value' => $entity_type,
+ ),
+ 1 => array(
+ 'field' => 'deleted',
+ 'value' => 0,
+ 'numeric' => TRUE,
+ ),
+ ),
+ );
+ }
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function taxonomy_views_plugins() {
+ return array(
+ 'module' => 'views', // This just tells our themes are elsewhere.
+ 'argument validator' => array(
+ 'taxonomy_term' => array(
+ 'title' => t('Taxonomy term'),
+ 'handler' => 'views_plugin_argument_validate_taxonomy_term',
+ 'path' => drupal_get_path('module', 'views') . '/modules/taxonomy', // not necessary for most modules
+ ),
+ ),
+ 'argument default' => array(
+ 'taxonomy_tid' => array(
+ 'title' => t('Taxonomy term ID from URL'),
+ 'handler' => 'views_plugin_argument_default_taxonomy_tid',
+ 'path' => drupal_get_path('module', 'views') . '/modules/taxonomy',
+ 'parent' => 'fixed',
+ ),
+ ),
+ );
+}
+
+/**
+ * Helper function to set a breadcrumb for taxonomy.
+ */
+function views_taxonomy_set_breadcrumb(&$breadcrumb, &$argument) {
+ if (empty($argument->options['set_breadcrumb'])) {
+ return;
+ }
+
+ $args = $argument->view->args;
+ $parents = taxonomy_get_parents_all($argument->argument);
+ foreach (array_reverse($parents) as $parent) {
+ // Unfortunately parents includes the current argument. Skip.
+ if ($parent->tid == $argument->argument) {
+ continue;
+ }
+ if (!empty($argument->options['use_taxonomy_term_path'])) {
+ $path = taxonomy_term_uri($parent);
+ $path = $path['path'];
+ }
+ else {
+ $args[$argument->position] = $parent->tid;
+ $path = $argument->view->get_url($args);
+ }
+ $breadcrumb[$path] = check_plain($parent->name);
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy.views_default.inc b/sites/all/modules/views/modules/taxonomy.views_default.inc
new file mode 100644
index 000000000..f43a0828f
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy.views_default.inc
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Bulk export of views_default objects generated by Bulk export module.
+ */
+
+/**
+ * Implementation of hook_views_default_views()
+ */
+function taxonomy_views_default_views() {
+ $views = array();
+
+ $view = new view;
+ $view->name = 'taxonomy_term';
+ $view->description = 'A view to emulate Drupal core\'s handling of taxonomy/term.';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Taxonomy term';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'access content';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ /* Sort criterion: Content: Sticky */
+ $handler->display->display_options['sorts']['sticky']['id'] = 'sticky';
+ $handler->display->display_options['sorts']['sticky']['table'] = 'node';
+ $handler->display->display_options['sorts']['sticky']['field'] = 'sticky';
+ $handler->display->display_options['sorts']['sticky']['order'] = 'DESC';
+ /* Sort criterion: Content: Post date */
+ $handler->display->display_options['sorts']['created']['id'] = 'created';
+ $handler->display->display_options['sorts']['created']['table'] = 'node';
+ $handler->display->display_options['sorts']['created']['field'] = 'created';
+ $handler->display->display_options['sorts']['created']['order'] = 'DESC';
+ /* Contextual filter: Content: Has taxonomy term ID (with depth) */
+ $handler->display->display_options['arguments']['term_node_tid_depth']['id'] = 'term_node_tid_depth';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['table'] = 'node';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['field'] = 'term_node_tid_depth';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['default_action'] = 'not found';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['exception']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['term_node_tid_depth']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['term_node_tid_depth']['title'] = '%1';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['specify_validation'] = 1;
+ $handler->display->display_options['arguments']['term_node_tid_depth']['validate']['type'] = 'taxonomy_term';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['depth'] = '0';
+ $handler->display->display_options['arguments']['term_node_tid_depth']['break_phrase'] = 1;
+ /* Contextual filter: Content: Has taxonomy term ID depth modifier */
+ $handler->display->display_options['arguments']['term_node_tid_depth_modifier']['id'] = 'term_node_tid_depth_modifier';
+ $handler->display->display_options['arguments']['term_node_tid_depth_modifier']['table'] = 'node';
+ $handler->display->display_options['arguments']['term_node_tid_depth_modifier']['field'] = 'term_node_tid_depth_modifier';
+ $handler->display->display_options['arguments']['term_node_tid_depth_modifier']['exception']['title_enable'] = 1;
+ $handler->display->display_options['arguments']['term_node_tid_depth_modifier']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['term_node_tid_depth_modifier']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['term_node_tid_depth_modifier']['specify_validation'] = 1;
+ /* Filter criterion: Content: Published or admin */
+ $handler->display->display_options['filters']['status_extra']['id'] = 'status_extra';
+ $handler->display->display_options['filters']['status_extra']['table'] = 'node';
+ $handler->display->display_options['filters']['status_extra']['field'] = 'status_extra';
+ $handler->display->display_options['filters']['status_extra']['group'] = 0;
+ $handler->display->display_options['filters']['status_extra']['expose']['operator'] = FALSE;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'taxonomy/term/%';
+
+ /* Display: Feed */
+ $handler = $view->new_display('feed', 'Feed', 'feed');
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = 15;
+ $handler->display->display_options['style_plugin'] = 'rss';
+ $handler->display->display_options['row_plugin'] = 'node_rss';
+ $handler->display->display_options['path'] = 'taxonomy/term/%/%/feed';
+ $handler->display->display_options['displays'] = array(
+ 'page' => 'page',
+ 'default' => 0,
+ );
+ $translatables['taxonomy_term'] = array(
+ t('Master'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort by'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('All'),
+ t('%1'),
+ t('Page'),
+ t('Feed'),
+ );
+
+ $views['taxonomy_term'] = $view;
+
+ return $views;
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_argument_taxonomy.inc b/sites/all/modules/views/modules/taxonomy/views_handler_argument_taxonomy.inc
new file mode 100644
index 000000000..10fc500b3
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_argument_taxonomy.inc
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_taxonomy.
+ */
+
+/**
+ * Argument handler for basic taxonomy tid.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_taxonomy extends views_handler_argument_numeric {
+
+ /**
+ * Override the behavior of title(). Get the title of the node.
+ */
+ function title() {
+ // There might be no valid argument.
+ if ($this->argument) {
+ $term = taxonomy_term_load($this->argument);
+ if (!empty($term)) {
+ return check_plain($term->name);
+ }
+ }
+ // TODO review text
+ return t('No name');
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid.inc b/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid.inc
new file mode 100644
index 000000000..f47f08a85
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid.inc
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_term_node_tid.
+ */
+
+/**
+ * Allow taxonomy term ID(s) as argument.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_term_node_tid extends views_handler_argument_many_to_one {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['set_breadcrumb'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['set_breadcrumb'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("Set the breadcrumb for the term parents"),
+ '#description' => t('If selected, the breadcrumb trail will include all parent terms, each one linking to this view. Note that this only works if just one term was received.'),
+ '#default_value' => !empty($this->options['set_breadcrumb']),
+ );
+ }
+
+ function set_breadcrumb(&$breadcrumb) {
+ if (empty($this->options['set_breadcrumb']) || !is_numeric($this->argument)) {
+ return;
+ }
+
+ return views_taxonomy_set_breadcrumb($breadcrumb, $this);
+ }
+
+ function title_query() {
+ $titles = array();
+ $result = db_select('taxonomy_term_data', 'td')
+ ->fields('td', array('name'))
+ ->condition('td.tid', $this->value)
+ ->execute();
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->name);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid_depth.inc b/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid_depth.inc
new file mode 100644
index 000000000..116a4dede
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid_depth.inc
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_term_node_tid_depth.
+ */
+
+/**
+ * Argument handler for taxonomy terms with depth.
+ *
+ * This handler is actually part of the node table and has some restrictions,
+ * because it uses a subquery to find nodes with.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_term_node_tid_depth extends views_handler_argument {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['depth'] = array('default' => 0);
+ $options['break_phrase'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['set_breadcrumb'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['use_taxonomy_term_path'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['depth'] = array(
+ '#type' => 'weight',
+ '#title' => t('Depth'),
+ '#default_value' => $this->options['depth'],
+ '#description' => t('The depth will match nodes tagged with terms in the hierarchy. For example, if you have the term "fruit" and a child term "apple", with a depth of 1 (or higher) then filtering for the term "fruit" will get nodes that are tagged with "apple" as well as "fruit". If negative, the reverse is true; searching for "apple" will also pick up nodes tagged with "fruit" if depth is -1 (or lower).'),
+ );
+
+ $form['break_phrase'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple values'),
+ '#description' => t('If selected, users can enter multiple values in the form of 1+2+3. Due to the number of JOINs it would require, AND will be treated as OR with this filter.'),
+ '#default_value' => !empty($this->options['break_phrase']),
+ );
+
+ $form['set_breadcrumb'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("Set the breadcrumb for the term parents"),
+ '#description' => t('If selected, the breadcrumb trail will include all parent terms, each one linking to this view. Note that this only works if just one term was received.'),
+ '#default_value' => !empty($this->options['set_breadcrumb']),
+ );
+
+ $form['use_taxonomy_term_path'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("Use Drupal's taxonomy term path to create breadcrumb links"),
+ '#description' => t('If selected, the links in the breadcrumb trail will be created using the standard drupal method instead of the custom views method. This is useful if you are using modules like taxonomy redirect to modify your taxonomy term links.'),
+ '#default_value' => !empty($this->options['use_taxonomy_term_path']),
+ '#dependency' => array('edit-options-set-breadcrumb' => array(TRUE)),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function set_breadcrumb(&$breadcrumb) {
+ if (empty($this->options['set_breadcrumb']) || !is_numeric($this->argument)) {
+ return;
+ }
+
+ return views_taxonomy_set_breadcrumb($breadcrumb, $this);
+ }
+
+ /**
+ * Override default_actions() to remove summary actions.
+ */
+ function default_actions($which = NULL) {
+ if ($which) {
+ if (in_array($which, array('ignore', 'not found', 'empty', 'default'))) {
+ return parent::default_actions($which);
+ }
+ return;
+ }
+ $actions = parent::default_actions();
+ unset($actions['summary asc']);
+ unset($actions['summary desc']);
+ unset($actions['summary asc by count']);
+ unset($actions['summary desc by count']);
+ return $actions;
+ }
+
+ function query($group_by = FALSE) {
+ $this->ensure_my_table();
+
+ if (!empty($this->options['break_phrase'])) {
+ $tids = new stdClass();
+ $tids->value = $this->argument;
+ $tids = views_break_phrase($this->argument, $tids);
+ if ($tids->value == array(-1)) {
+ return FALSE;
+ }
+
+ if (count($tids->value) > 1) {
+ $operator = 'IN';
+ }
+ else {
+ $operator = '=';
+ }
+
+ $tids = $tids->value;
+ }
+ else {
+ $operator = "=";
+ $tids = $this->argument;
+ }
+ // Now build the subqueries.
+ $subquery = db_select('taxonomy_index', 'tn');
+ $subquery->addField('tn', 'nid');
+ $where = db_or()->condition('tn.tid', $tids, $operator);
+ $last = "tn";
+
+ if ($this->options['depth'] > 0) {
+ $subquery->leftJoin('taxonomy_term_hierarchy', 'th', "th.tid = tn.tid");
+ $last = "th";
+ foreach (range(1, abs($this->options['depth'])) as $count) {
+ $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.parent = th$count.tid");
+ $where->condition("th$count.tid", $tids, $operator);
+ $last = "th$count";
+ }
+ }
+ elseif ($this->options['depth'] < 0) {
+ foreach (range(1, abs($this->options['depth'])) as $count) {
+ $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.tid = th$count.parent");
+ $where->condition("th$count.tid", $tids, $operator);
+ $last = "th$count";
+ }
+ }
+
+ $subquery->condition($where);
+ $this->query->add_where(0, "$this->table_alias.$this->real_field", $subquery, 'IN');
+ }
+
+ function title() {
+ $term = taxonomy_term_load($this->argument);
+ if (!empty($term)) {
+ return check_plain($term->name);
+ }
+ // TODO review text
+ return t('No name');
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid_depth_modifier.inc b/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid_depth_modifier.inc
new file mode 100644
index 000000000..2f9dd4e7b
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_argument_term_node_tid_depth_modifier.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_term_node_tid_depth_modif.
+ */
+
+/**
+ * Argument handler for to modify depth for a previous term.
+ *
+ * This handler is actually part of the node table and has some restrictions,
+ * because it uses a subquery to find nodes with.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_term_node_tid_depth_modifier extends views_handler_argument {
+ function options_form(&$form, &$form_state) { }
+ function query($group_by = FALSE) { }
+ function pre_query() {
+ // We don't know our argument yet, but it's based upon our position:
+ $argument = isset($this->view->args[$this->position]) ? $this->view->args[$this->position] : NULL;
+ if (!is_numeric($argument)) {
+ return;
+ }
+
+ if ($argument > 10) {
+ $argument = 10;
+ }
+
+ if ($argument < -10) {
+ $argument = -10;
+ }
+
+ // figure out which argument preceded us.
+ $keys = array_reverse(array_keys($this->view->argument));
+ $skip = TRUE;
+ foreach ($keys as $key) {
+ if ($key == $this->options['id']) {
+ $skip = FALSE;
+ continue;
+ }
+
+ if ($skip) {
+ continue;
+ }
+
+ if (empty($this->view->argument[$key])) {
+ continue;
+ }
+
+ if (isset($handler)) {
+ unset($handler);
+ }
+
+ $handler = &$this->view->argument[$key];
+ if (empty($handler->definition['accept depth modifier'])) {
+ continue;
+ }
+
+ // Finally!
+ $handler->options['depth'] = $argument;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_argument_vocabulary_machine_name.inc b/sites/all/modules/views/modules/taxonomy/views_handler_argument_vocabulary_machine_name.inc
new file mode 100644
index 000000000..427cf2b0b
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_argument_vocabulary_machine_name.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_vocabulary_machine_name.
+ */
+
+/**
+ * Argument handler to accept a vocabulary machine name.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_vocabulary_machine_name extends views_handler_argument_string {
+ /**
+ * Override the behavior of title(). Get the name of the vocabulary..
+ */
+ function title() {
+ $title = db_query("SELECT v.name FROM {taxonomy_vocabulary} v WHERE v.machine_name = :machine_name", array(':machine_name' => $this->argument))->fetchField();
+
+ if (empty($title)) {
+ return t('No vocabulary');
+ }
+
+ return check_plain($title);
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_argument_vocabulary_vid.inc b/sites/all/modules/views/modules/taxonomy/views_handler_argument_vocabulary_vid.inc
new file mode 100644
index 000000000..c69664052
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_argument_vocabulary_vid.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_vocabulary_vid.
+ */
+
+/**
+ * Argument handler to accept a vocabulary id.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_vocabulary_vid extends views_handler_argument_numeric {
+ /**
+ * Override the behavior of title(). Get the name of the vocabulary.
+ */
+ function title() {
+ $title = db_query("SELECT v.name FROM {taxonomy_vocabulary} v WHERE v.vid = :vid", array(':vid' => $this->argument))->fetchField();
+
+ if (empty($title)) {
+ return t('No vocabulary');
+ }
+
+ return check_plain($title);
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_field_taxonomy.inc b/sites/all/modules/views/modules/taxonomy/views_handler_field_taxonomy.inc
new file mode 100644
index 000000000..48da283d9
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_field_taxonomy.inc
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_taxonomy.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to a taxonomy
+ * term.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_taxonomy extends views_handler_field {
+ /**
+ * Constructor to provide additional field to add.
+ *
+ * This constructer assumes the taxonomy_term_data table. If using another
+ * table, we'll need to be more specific.
+ */
+ function construct() {
+ parent::construct();
+ $this->additional_fields['vid'] = 'vid';
+ $this->additional_fields['tid'] = 'tid';
+ $this->additional_fields['vocabulary_machine_name'] = array(
+ 'table' => 'taxonomy_vocabulary',
+ 'field' => 'machine_name',
+ );
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_taxonomy'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['convert_spaces'] = array('default' => FALSE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Provide link to taxonomy option
+ */
+ function options_form(&$form, &$form_state) {
+ $form['link_to_taxonomy'] = array(
+ '#title' => t('Link this field to its taxonomy term page'),
+ '#description' => t("Enable to override this field's links."),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['link_to_taxonomy']),
+ );
+ $form['convert_spaces'] = array(
+ '#title' => t('Convert spaces in term names to hyphens'),
+ '#description' => t('This allows links to work with Views taxonomy term arguments.'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['convert_spaces']),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ /**
+ * Render whatever the data is as a link to the taxonomy.
+ *
+ * Data should be made XSS safe prior to calling this function.
+ */
+ function render_link($data, $values) {
+ $tid = $this->get_value($values, 'tid');
+ if (!empty($this->options['link_to_taxonomy']) && !empty($tid) && $data !== NULL && $data !== '') {
+ $term = new stdClass();
+ $term->tid = $tid;
+ $term->vid = $this->get_value($values, 'vid');
+ $term->vocabulary_machine_name = $values->{$this->aliases['vocabulary_machine_name']};
+ $this->options['alter']['make_link'] = TRUE;
+ $uri = entity_uri('taxonomy_term', $term);
+ $this->options['alter']['path'] = $uri['path'];
+ }
+
+ if (!empty($this->options['convert_spaces'])) {
+ $data = str_replace(' ', '-', $data);
+ }
+
+ return $data;
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_field_term_link_edit.inc b/sites/all/modules/views/modules/taxonomy/views_handler_field_term_link_edit.inc
new file mode 100644
index 000000000..2efb4a653
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_field_term_link_edit.inc
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_term_link_edit.
+ */
+
+/**
+ * Field handler to present a term edit link.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_term_link_edit extends views_handler_field {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['tid'] = 'tid';
+ $this->additional_fields['vid'] = 'vid';
+ $this->additional_fields['vocabulary_machine_name'] = array(
+ 'table' => 'taxonomy_vocabulary',
+ 'field' => 'machine_name',
+ );
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['text'] = array('default' => '', 'translatable' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text to display'),
+ '#default_value' => $this->options['text'],
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ }
+
+ function render($values) {
+ // Check there is an actual value, as on a relationship there may not be.
+ if ($tid = $this->get_value($values, 'tid')) {
+ // Mock a term object for taxonomy_term_edit_access(). Use machine name and
+ // vid to ensure compatibility with vid based and machine name based
+ // access checks. See http://drupal.org/node/995156
+ $term = new stdClass();
+ $term->vid = $values->{$this->aliases['vid']};
+ $term->vocabulary_machine_name = $values->{$this->aliases['vocabulary_machine_name']};
+ if (taxonomy_term_edit_access($term)) {
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('edit');
+ $tid = $this->get_value($values, 'tid');
+ return l($text, 'taxonomy/term/'. $tid . '/edit', array('query' => drupal_get_destination()));
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_field_term_node_tid.inc b/sites/all/modules/views/modules/taxonomy/views_handler_field_term_node_tid.inc
new file mode 100644
index 000000000..4c6362e5d
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_field_term_node_tid.inc
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_term_node_tid.
+ */
+
+/**
+ * Field handler to display all taxonomy terms of a node.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_term_node_tid extends views_handler_field_prerender_list {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ // @todo: Wouldn't it be possible to use $this->base_table and no if here?
+ if ($view->base_table == 'node_revision') {
+ $this->additional_fields['nid'] = array('table' => 'node_revision', 'field' => 'nid');
+ }
+ else {
+ $this->additional_fields['nid'] = array('table' => 'node', 'field' => 'nid');
+ }
+
+ // Convert legacy vids option to machine name vocabularies.
+ if (!empty($this->options['vids'])) {
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($this->options['vids'] as $vid) {
+ if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
+ $this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
+ }
+ }
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['link_to_taxonomy'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['limit'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['vocabularies'] = array('default' => array());
+
+ return $options;
+ }
+
+ /**
+ * Provide "link to term" option.
+ */
+ function options_form(&$form, &$form_state) {
+ $form['link_to_taxonomy'] = array(
+ '#title' => t('Link this field to its term page'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['link_to_taxonomy']),
+ );
+
+ $form['limit'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Limit terms by vocabulary'),
+ '#default_value'=> $this->options['limit'],
+ );
+
+ $options = array();
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($vocabularies as $voc) {
+ $options[$voc->machine_name] = check_plain($voc->name);
+ }
+
+ $form['vocabularies'] = array(
+ '#prefix' => '<div><div id="edit-options-vocabularies">',
+ '#suffix' => '</div></div>',
+ '#type' => 'checkboxes',
+ '#title' => t('Vocabularies'),
+ '#options' => $options,
+ '#default_value' => $this->options['vocabularies'],
+ '#dependency' => array('edit-options-limit' => array(TRUE)),
+ );
+
+ parent::options_form($form, $form_state);
+ }
+
+ /**
+ * Add this term to the query
+ */
+ function query() {
+ $this->add_additional_fields();
+ }
+
+ function pre_render(&$values) {
+ $this->field_alias = $this->aliases['nid'];
+ $nids = array();
+ foreach ($values as $result) {
+ if (!empty($result->{$this->aliases['nid']})) {
+ $nids[] = $result->{$this->aliases['nid']};
+ }
+ }
+
+ if ($nids) {
+ $query = db_select('taxonomy_term_data', 'td');
+ $query->innerJoin('taxonomy_index', 'tn', 'td.tid = tn.tid');
+ $query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
+ $query->fields('td');
+ $query->addField('tn', 'nid', 'node_nid');
+ $query->addField('tv', 'name', 'vocabulary');
+ $query->addField('tv', 'machine_name', 'vocabulary_machine_name');
+ $query->orderby('td.weight');
+ $query->orderby('td.name');
+ $query->condition('tn.nid', $nids);
+ $query->addTag('term_access');
+ $vocabs = array_filter($this->options['vocabularies']);
+ if (!empty($this->options['limit']) && !empty($vocabs)) {
+ $query->condition('tv.machine_name', $vocabs);
+ }
+ $result = $query->execute();
+
+ foreach ($result as $term) {
+ $this->items[$term->node_nid][$term->tid]['name'] = check_plain($term->name);
+ $this->items[$term->node_nid][$term->tid]['tid'] = $term->tid;
+ $this->items[$term->node_nid][$term->tid]['vocabulary_machine_name'] = check_plain($term->vocabulary_machine_name);
+ $this->items[$term->node_nid][$term->tid]['vocabulary'] = check_plain($term->vocabulary);
+
+ if (!empty($this->options['link_to_taxonomy'])) {
+ $this->items[$term->node_nid][$term->tid]['make_link'] = TRUE;
+ $this->items[$term->node_nid][$term->tid]['path'] = 'taxonomy/term/' . $term->tid;
+ }
+ }
+ }
+ }
+
+ function render_item($count, $item) {
+ return $item['name'];
+ }
+
+ function document_self_tokens(&$tokens) {
+ $tokens['[' . $this->options['id'] . '-tid' . ']'] = t('The taxonomy term ID for the term.');
+ $tokens['[' . $this->options['id'] . '-name' . ']'] = t('The taxonomy term name for the term.');
+ $tokens['[' . $this->options['id'] . '-vocabulary-machine-name' . ']'] = t('The machine name for the vocabulary the term belongs to.');
+ $tokens['[' . $this->options['id'] . '-vocabulary' . ']'] = t('The name for the vocabulary the term belongs to.');
+ }
+
+ function add_self_tokens(&$tokens, $item) {
+ foreach(array('tid', 'name', 'vocabulary_machine_name', 'vocabulary') as $token) {
+ // Replace _ with - for the vocabulary machine name.
+ $tokens['[' . $this->options['id'] . '-' . str_replace('_', '-', $token). ']'] = isset($item[$token]) ? $item[$token] : '';
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_filter_term_node_tid.inc b/sites/all/modules/views/modules/taxonomy/views_handler_filter_term_node_tid.inc
new file mode 100644
index 000000000..5ca6d181e
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_filter_term_node_tid.inc
@@ -0,0 +1,375 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_term_node_tid.
+ */
+
+/**
+ * Filter by term id.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_term_node_tid extends views_handler_filter_many_to_one {
+ // Stores the exposed input for this filter.
+ var $validated_exposed_input = NULL;
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+ if (!empty($this->definition['vocabulary'])) {
+ $this->options['vocabulary'] = $this->definition['vocabulary'];
+ }
+
+ // Convert legacy vid option to machine name vocabulary.
+ if (isset($this->options['vid']) && !empty($this->options['vid']) & empty($this->options['vocabulary'])) {
+ $vocabularies = taxonomy_get_vocabularies();
+ $vid = $this->options['vid'];
+ if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
+ $this->options['vocabulary'] = $vocabularies[$vid]->machine_name;
+ }
+ }
+ }
+
+ function has_extra_options() { return TRUE; }
+
+ function get_value_options() { /* don't overwrite the value options */ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['type'] = array('default' => 'textfield');
+ $options['limit'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['vocabulary'] = array('default' => 0);
+ $options['hierarchy'] = array('default' => 0);
+ $options['error_message'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function extra_options_form(&$form, &$form_state) {
+ $vocabularies = taxonomy_get_vocabularies();
+ $options = array();
+ foreach ($vocabularies as $voc) {
+ $options[$voc->machine_name] = check_plain($voc->name);
+ }
+
+ if ($this->options['limit']) {
+ // We only do this when the form is displayed.
+ if (empty($this->options['vocabulary'])) {
+ $first_vocabulary = reset($vocabularies);
+ $this->options['vocabulary'] = $first_vocabulary->machine_name;
+ }
+
+ if (empty($this->definition['vocabulary'])) {
+ $form['vocabulary'] = array(
+ '#type' => 'radios',
+ '#title' => t('Vocabulary'),
+ '#options' => $options,
+ '#description' => t('Select which vocabulary to show terms for in the regular options.'),
+ '#default_value' => $this->options['vocabulary'],
+ );
+ }
+ }
+
+ $form['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Selection type'),
+ '#options' => array('select' => t('Dropdown'), 'textfield' => t('Autocomplete')),
+ '#default_value' => $this->options['type'],
+ );
+
+ $form['hierarchy'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show hierarchy in dropdown'),
+ '#default_value' => !empty($this->options['hierarchy']),
+ '#dependency' => array('radio:options[type]' => array('select')),
+ );
+ }
+
+ function value_form(&$form, &$form_state) {
+ $vocabulary = taxonomy_vocabulary_machine_name_load($this->options['vocabulary']);
+ if (empty($vocabulary) && $this->options['limit']) {
+ $form['markup'] = array(
+ '#markup' => '<div class="form-item">' . t('An invalid vocabulary is selected. Please change it in the options.') . '</div>',
+ );
+ return;
+ }
+
+ if ($this->options['type'] == 'textfield') {
+ $default = '';
+ if ($this->value) {
+ $result = taxonomy_term_load_multiple($this->value);
+ foreach ($result as $entity_term) {
+ if ($default) {
+ $default .= ', ';
+ }
+ $default .= entity_label('taxonomy_term', $entity_term);
+ }
+ }
+
+ $form['value'] = array(
+ '#title' => $this->options['limit'] ? t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)) : t('Select terms'),
+ '#type' => 'textfield',
+ '#default_value' => $default,
+ );
+
+ if ($this->options['limit']) {
+ $form['value']['#autocomplete_path'] = 'admin/views/ajax/autocomplete/taxonomy/' . $vocabulary->vid;
+ }
+ }
+ else {
+ if (!empty($this->options['hierarchy']) && $this->options['limit']) {
+ $tree = taxonomy_get_tree($vocabulary->vid, 0, NULL, TRUE);
+ $options = array();
+
+ if ($tree) {
+ // Translation system needs full entity objects, so we have access to label.
+ foreach ($tree as $term) {
+ $choice = new stdClass();
+ $choice->option = array($term->tid => str_repeat('-', $term->depth) . entity_label('taxonomy_term', $term));
+ $options[] = $choice;
+ }
+ }
+ }
+ else {
+ $options = array();
+ $query = db_select('taxonomy_term_data', 'td');
+ $query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
+ $query->fields('td');
+ $query->orderby('tv.weight');
+ $query->orderby('tv.name');
+ $query->orderby('td.weight');
+ $query->orderby('td.name');
+ $query->addTag('term_access');
+ if ($this->options['limit']) {
+ $query->condition('tv.machine_name', $vocabulary->machine_name);
+ }
+ $result = $query->execute();
+
+ $tids = array();
+ foreach ($result as $term) {
+ $tids[] = $term->tid;
+ }
+ $entities = taxonomy_term_load_multiple($tids);
+ foreach ($entities as $entity_term) {
+ $options[$entity_term->tid] = entity_label('taxonomy_term', $entity_term);
+ }
+ }
+
+ $default_value = (array) $this->value;
+
+ if (!empty($form_state['exposed'])) {
+ $identifier = $this->options['expose']['identifier'];
+
+ if (!empty($this->options['expose']['reduce'])) {
+ $options = $this->reduce_value_options($options);
+
+ if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) {
+ $default_value = array();
+ }
+ }
+
+ if (empty($this->options['expose']['multiple'])) {
+ if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) {
+ $default_value = 'All';
+ }
+ elseif (empty($default_value)) {
+ $keys = array_keys($options);
+ $default_value = array_shift($keys);
+ }
+ // Due to #1464174 there is a chance that array('') was saved in the admin ui.
+ // Let's choose a safe default value.
+ elseif ($default_value == array('')) {
+ $default_value = 'All';
+ }
+ else {
+ $copy = $default_value;
+ $default_value = array_shift($copy);
+ }
+ }
+ }
+ $form['value'] = array(
+ '#type' => 'select',
+ '#title' => $this->options['limit'] ? t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)) : t('Select terms'),
+ '#multiple' => TRUE,
+ '#options' => $options,
+ '#size' => min(9, count($options)),
+ '#default_value' => $default_value,
+ '#description' => t('Leave blank for all. Otherwise, the first selected term will be the default instead of "Any".'),
+ );
+
+ if (!empty($form_state['exposed']) && isset($identifier) && !isset($form_state['input'][$identifier])) {
+ $form_state['input'][$identifier] = $default_value;
+ }
+ }
+
+
+ if (empty($form_state['exposed'])) {
+ // Retain the helper option
+ $this->helper->options_form($form, $form_state);
+ }
+ }
+
+ function value_validate($form, &$form_state) {
+ // We only validate if they've chosen the text field style.
+ if ($this->options['type'] != 'textfield') {
+ return;
+ }
+
+ $values = drupal_explode_tags($form_state['values']['options']['value']);
+ $tids = $this->validate_term_strings($form['value'], $values);
+
+ if ($tids) {
+ $form_state['values']['options']['value'] = $tids;
+ }
+ }
+
+ function accept_exposed_input($input) {
+ if (empty($this->options['exposed'])) {
+ return TRUE;
+ }
+
+ // We need to know the operator, which is normally set in
+ // views_handler_filter::accept_exposed_input(), before we actually call
+ // the parent version of ourselves.
+ if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
+ $this->operator = $input[$this->options['expose']['operator_id']];
+ }
+
+ // If view is an attachment and is inheriting exposed filters, then assume
+ // exposed input has already been validated
+ if (!empty($this->view->is_attachment) && $this->view->display_handler->uses_exposed()) {
+ $this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']];
+ }
+
+ // If we're checking for EMPTY or NOT, we don't need any input, and we can
+ // say that our input conditions are met by just having the right operator.
+ if ($this->operator == 'empty' || $this->operator == 'not empty') {
+ return TRUE;
+ }
+
+ // If it's non-required and there's no value don't bother filtering.
+ if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
+ return FALSE;
+ }
+
+ $rc = parent::accept_exposed_input($input);
+ if ($rc) {
+ // If we have previously validated input, override.
+ if (!$this->is_a_group() && isset($this->validated_exposed_input)) {
+ $this->value = $this->validated_exposed_input;
+ }
+ }
+
+ return $rc;
+ }
+
+ function exposed_validate(&$form, &$form_state) {
+ if (empty($this->options['exposed'])) {
+ return;
+ }
+
+ $identifier = $this->options['expose']['identifier'];
+
+ // We only validate if they've chosen the text field style.
+ if ($this->options['type'] != 'textfield') {
+ if ($form_state['values'][$identifier] != 'All') {
+ $this->validated_exposed_input = (array) $form_state['values'][$identifier];
+ }
+ return;
+ }
+
+ if (empty($this->options['expose']['identifier'])) {
+ return;
+ }
+
+ $values = drupal_explode_tags($form_state['values'][$identifier]);
+
+ $tids = $this->validate_term_strings($form[$identifier], $values);
+ if ($tids) {
+ $this->validated_exposed_input = $tids;
+ }
+ }
+
+ /**
+ * Validate the user string. Since this can come from either the form
+ * or the exposed filter, this is abstracted out a bit so it can
+ * handle the multiple input sources.
+ *
+ * @param $form
+ * The form which is used, either the views ui or the exposed filters.
+ * @param $values
+ * The taxonomy names which will be converted to tids.
+ *
+ * @return array
+ * The taxonomy ids fo all validated terms.
+ */
+ function validate_term_strings(&$form, $values) {
+ if (empty($values)) {
+ return array();
+ }
+
+ $tids = array();
+ $names = array();
+ $missing = array();
+ foreach ($values as $value) {
+ $missing[strtolower($value)] = TRUE;
+ $names[] = $value;
+ }
+
+ if (!$names) {
+ return FALSE;
+ }
+
+ $query = db_select('taxonomy_term_data', 'td');
+ $query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
+ $query->fields('td');
+ $query->condition('td.name', $names);
+ $query->condition('tv.machine_name', $this->options['vocabulary']);
+ $query->addTag('term_access');
+ $result = $query->execute();
+ foreach ($result as $term) {
+ unset($missing[strtolower($term->name)]);
+ $tids[] = $term->tid;
+ }
+
+ if ($missing && !empty($this->options['error_message'])) {
+ form_error($form, format_plural(count($missing), 'Unable to find term: @terms', 'Unable to find terms: @terms', array('@terms' => implode(', ', array_keys($missing)))));
+ }
+ elseif ($missing && empty($this->options['error_message'])) {
+ $tids = array(0);
+ }
+
+ return $tids;
+ }
+
+ function value_submit($form, &$form_state) {
+ // prevent array_filter from messing up our arrays in parent submit.
+ }
+
+ function expose_form(&$form, &$form_state) {
+ parent::expose_form($form, $form_state);
+ if ($this->options['type'] != 'select') {
+ unset($form['expose']['reduce']);
+ }
+ $form['error_message'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display error message'),
+ '#default_value' => !empty($this->options['error_message']),
+ );
+ }
+
+ function admin_summary() {
+ // set up $this->value_options for the parent summary
+ $this->value_options = array();
+
+ if ($this->value) {
+ $this->value = array_filter($this->value);
+ $result = taxonomy_term_load_multiple($this->value);
+ foreach ($result as $entity_term) {
+ $this->value_options[$entity_term->tid] = entity_label('taxonomy_term', $entity_term);
+ }
+ }
+ return parent::admin_summary();
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_filter_term_node_tid_depth.inc b/sites/all/modules/views/modules/taxonomy/views_handler_filter_term_node_tid_depth.inc
new file mode 100644
index 000000000..fe12780f4
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_filter_term_node_tid_depth.inc
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_term_node_tid_depth.
+ */
+
+/**
+ * Filter handler for taxonomy terms with depth.
+ *
+ * This handler is actually part of the node table and has some restrictions,
+ * because it uses a subquery to find nodes with.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_term_node_tid_depth extends views_handler_filter_term_node_tid {
+ function operator_options($which = 'title') {
+ return array(
+ 'or' => t('Is one of'),
+ );
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['depth'] = array('default' => 0);
+
+ return $options;
+ }
+
+ function extra_options_form(&$form, &$form_state) {
+ parent::extra_options_form($form, $form_state);
+
+ $form['depth'] = array(
+ '#type' => 'weight',
+ '#title' => t('Depth'),
+ '#default_value' => $this->options['depth'],
+ '#description' => t('The depth will match nodes tagged with terms in the hierarchy. For example, if you have the term "fruit" and a child term "apple", with a depth of 1 (or higher) then filtering for the term "fruit" will get nodes that are tagged with "apple" as well as "fruit". If negative, the reverse is true; searching for "apple" will also pick up nodes tagged with "fruit" if depth is -1 (or lower).'),
+ );
+ }
+
+ function query() {
+ // If no filter values are present, then do nothing.
+ if (count($this->value) == 0) {
+ return;
+ }
+ elseif (count($this->value) == 1) {
+ // Somethis $this->value is an array with a single element so convert it.
+ if (is_array($this->value)) {
+ $this->value = current($this->value);
+ }
+ $operator = '=';
+ }
+ else {
+ $operator = 'IN';# " IN (" . implode(', ', array_fill(0, sizeof($this->value), '%d')) . ")";
+ }
+
+ // The normal use of ensure_my_table() here breaks Views.
+ // So instead we trick the filter into using the alias of the base table.
+ // See http://drupal.org/node/271833
+ // If a relationship is set, we must use the alias it provides.
+ if (!empty($this->relationship)) {
+ $this->table_alias = $this->relationship;
+ }
+ // If no relationship, then use the alias of the base table.
+ elseif (isset($this->query->table_queue[$this->query->base_table]['alias'])) {
+ $this->table_alias = $this->query->table_queue[$this->query->base_table]['alias'];
+ }
+ // This should never happen, but if it does, we fail quietly.
+ else {
+ return;
+ }
+
+ // Now build the subqueries.
+ $subquery = db_select('taxonomy_index', 'tn');
+ $subquery->addField('tn', 'nid');
+ $where = db_or()->condition('tn.tid', $this->value, $operator);
+ $last = "tn";
+
+ if ($this->options['depth'] > 0) {
+ $subquery->leftJoin('taxonomy_term_hierarchy', 'th', "th.tid = tn.tid");
+ $last = "th";
+ foreach (range(1, abs($this->options['depth'])) as $count) {
+ $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.parent = th$count.tid");
+ $where->condition("th$count.tid", $this->value, $operator);
+ $last = "th$count";
+ }
+ }
+ elseif ($this->options['depth'] < 0) {
+ foreach (range(1, abs($this->options['depth'])) as $count) {
+ $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.tid = th$count.parent");
+ $where->condition("th$count.tid", $this->value, $operator);
+ $last = "th$count";
+ }
+ }
+
+ $subquery->condition($where);
+ $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", $subquery, 'IN');
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_filter_vocabulary_machine_name.inc b/sites/all/modules/views/modules/taxonomy/views_handler_filter_vocabulary_machine_name.inc
new file mode 100644
index 000000000..062450c70
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_filter_vocabulary_machine_name.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_vocabulary_machine_name.
+ */
+
+/**
+ * Filter by vocabulary machine name.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_vocabulary_machine_name extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (isset($this->value_options)) {
+ return;
+ }
+
+ $this->value_options = array();
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($vocabularies as $voc) {
+ $this->value_options[$voc->machine_name] = $voc->name;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_filter_vocabulary_vid.inc b/sites/all/modules/views/modules/taxonomy/views_handler_filter_vocabulary_vid.inc
new file mode 100644
index 000000000..2759ee1ec
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_filter_vocabulary_vid.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_vocabulary_vid.
+ */
+
+/**
+ * Filter by vocabulary id.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_vocabulary_vid extends views_handler_filter_in_operator {
+ function get_value_options() {
+ if (isset($this->value_options)) {
+ return;
+ }
+
+ $this->value_options = array();
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($vocabularies as $voc) {
+ $this->value_options[$voc->vid] = $voc->name;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_relationship_node_term_data.inc b/sites/all/modules/views/modules/taxonomy/views_handler_relationship_node_term_data.inc
new file mode 100644
index 000000000..cf7228830
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_handler_relationship_node_term_data.inc
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_relationship_node_term_data.
+ */
+
+/**
+ * Relationship handler to return the taxonomy terms of nodes.
+ *
+ * @ingroup views_relationship_handlers
+ */
+class views_handler_relationship_node_term_data extends views_handler_relationship {
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ // Convert legacy vids option to machine name vocabularies.
+ if (!empty($this->options['vids'])) {
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($this->options['vids'] as $vid) {
+ if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
+ $this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
+ }
+ }
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['vocabularies'] = array('default' => array());
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $vocabularies = taxonomy_get_vocabularies();
+ $options = array();
+ foreach ($vocabularies as $voc) {
+ $options[$voc->machine_name] = check_plain($voc->name);
+ }
+
+ $form['vocabularies'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Vocabularies'),
+ '#options' => $options,
+ '#default_value' => $this->options['vocabularies'],
+ '#description' => t('Choose which vocabularies you wish to relate. Remember that every term found will create a new record, so this relationship is best used on just one vocabulary that has only one term per node.'),
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ /**
+ * Called to implement a relationship in a query.
+ */
+ function query() {
+ $this->ensure_my_table();
+
+ $def = $this->definition;
+ $def['table'] = 'taxonomy_term_data';
+
+ if (!array_filter($this->options['vocabularies'])) {
+ $taxonomy_index = $this->query->add_table('taxonomy_index', $this->relationship);
+ $def['left_table'] = $taxonomy_index;
+ $def['left_field'] = 'tid';
+ $def['field'] = 'tid';
+ $def['type'] = empty($this->options['required']) ? 'LEFT' : 'INNER';
+ }
+ else {
+ // If vocabularies are supplied join a subselect instead
+ $def['left_table'] = $this->table_alias;
+ $def['left_field'] = 'nid';
+ $def['field'] = 'nid';
+ $def['type'] = empty($this->options['required']) ? 'LEFT' : 'INNER';
+
+ $query = db_select('taxonomy_term_data', 'td');
+ $query->addJoin($def['type'], 'taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
+ $query->addJoin($def['type'], 'taxonomy_index', 'tn', 'tn.tid = td.tid');
+ $query->condition('tv.machine_name', array_filter($this->options['vocabularies']));
+ if (empty($this->query->options['disable_sql_rewrite'])) {
+ $query->addTag('term_access');
+ }
+ $query->fields('td');
+ $query->fields('tn', array('nid'));
+ $def['table formula'] = $query;
+ }
+
+ $join = new views_join();
+
+ $join->definition = $def;
+ $join->construct();
+ $join->adjusted = TRUE;
+
+ // use a short alias for this:
+ $alias = $def['table'] . '_' . $this->table;
+
+ $this->alias = $this->query->add_relationship($alias, $join, 'taxonomy_term_data', $this->relationship);
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_plugin_argument_default_taxonomy_tid.inc b/sites/all/modules/views/modules/taxonomy/views_plugin_argument_default_taxonomy_tid.inc
new file mode 100644
index 000000000..9c1d81f9c
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_plugin_argument_default_taxonomy_tid.inc
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_argument_default_taxonomy_tid.
+ */
+
+/**
+ * Taxonomy tid default argument.
+ */
+class views_plugin_argument_default_taxonomy_tid extends views_plugin_argument_default {
+ function init(&$view, &$argument, $options) {
+ parent::init($view, $argument, $options);
+
+ // Convert legacy vids option to machine name vocabularies.
+ if (!empty($this->options['vids'])) {
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($this->options['vids'] as $vid) {
+ if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
+ $this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
+ }
+ }
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['term_page'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['node'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['anyall'] = array('default' => ',');
+ $options['limit'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['vocabularies'] = array('default' => array());
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['term_page'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Load default filter from term page'),
+ '#default_value' => $this->options['term_page'],
+ );
+ $form['node'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Load default filter from node page, that\'s good for related taxonomy blocks'),
+ '#default_value' => $this->options['node'],
+ );
+
+ $form['limit'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Limit terms by vocabulary'),
+ '#default_value'=> $this->options['limit'],
+ '#process' => array('form_process_checkbox', 'ctools_dependent_process'),
+ '#dependency' => array(
+ 'edit-options-argument-default-taxonomy-tid-node' => array(1),
+ ),
+ );
+
+ $options = array();
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($vocabularies as $voc) {
+ $options[$voc->machine_name] = check_plain($voc->name);
+ }
+
+ $form['vocabularies'] = array(
+ '#prefix' => '<div><div id="edit-options-vids">',
+ '#suffix' => '</div></div>',
+ '#type' => 'checkboxes',
+ '#title' => t('Vocabularies'),
+ '#options' => $options,
+ '#default_value' => $this->options['vocabularies'],
+ '#process' => array('form_process_checkboxes', 'ctools_dependent_process'),
+ '#dependency' => array(
+ 'edit-options-argument-default-taxonomy-tid-limit' => array(1),
+ 'edit-options-argument-default-taxonomy-tid-node' => array(1),
+ ),
+ );
+
+ $form['anyall'] = array(
+ '#type' => 'radios',
+ '#title' => t('Multiple-value handling'),
+ '#default_value'=> $this->options['anyall'],
+ '#process' => array('form_process_radios', 'ctools_dependent_process'),
+ '#options' => array(
+ ',' => t('Filter to items that share all terms'),
+ '+' => t('Filter to items that share any term'),
+ ),
+ '#dependency' => array(
+ 'edit-options-argument-default-taxonomy-tid-node' => array(1),
+ ),
+ );
+ }
+
+ function options_submit(&$form, &$form_state, &$options = array()) {
+ // Filter unselected items so we don't unnecessarily store giant arrays.
+ $options['vocabularies'] = array_filter($options['vocabularies']);
+ }
+
+ function get_argument() {
+ // Load default argument from taxonomy page.
+ if (!empty($this->options['term_page'])) {
+ if (arg(0) == 'taxonomy' && arg(1) == 'term' && is_numeric(arg(2))) {
+ return arg(2);
+ }
+ }
+ // Load default argument from node.
+ if (!empty($this->options['node'])) {
+ foreach (range(1, 3) as $i) {
+ $node = menu_get_object('node', $i);
+ if (!empty($node)) {
+ break;
+ }
+ }
+ // Just check, if a node could be detected.
+ if ($node) {
+ $taxonomy = array();
+ $fields = field_info_instances('node', $node->type);
+ foreach ($fields as $name => $info) {
+ $field_info = field_info_field($name);
+ if ($field_info['type'] == 'taxonomy_term_reference') {
+ $items = field_get_items('node', $node, $name);
+ if (is_array($items)) {
+ foreach ($items as $item) {
+ $taxonomy[$item['tid']] = $field_info['settings']['allowed_values'][0]['vocabulary'];
+ }
+ }
+ }
+ }
+ if (!empty($this->options['limit'])) {
+ $tids = array();
+ // filter by vocabulary
+ foreach ($taxonomy as $tid => $vocab) {
+ if (!empty($this->options['vocabularies'][$vocab])) {
+ $tids[] = $tid;
+ }
+ }
+ return implode($this->options['anyall'], $tids);
+ }
+ // Return all tids.
+ else {
+ return implode($this->options['anyall'], array_keys($taxonomy));
+ }
+ }
+ }
+
+ // If the current page is a view that takes tid as an argument,
+ // find the tid argument and return it.
+ $views_page = views_get_page_view();
+ if ($views_page && isset($views_page->argument['tid'])) {
+ return $views_page->argument['tid']->argument;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc b/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc
new file mode 100644
index 000000000..435db0ddf
--- /dev/null
+++ b/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc
@@ -0,0 +1,223 @@
+<?php
+
+/**
+ * @file
+ * Contains the 'taxonomy term' argument validator plugin.
+ */
+
+/**
+ * Validate whether an argument is an acceptable node.
+ */
+class views_plugin_argument_validate_taxonomy_term extends views_plugin_argument_validate {
+ function init(&$view, &$argument, $options) {
+ parent::init($view, $argument, $options);
+
+ // Convert legacy vids option to machine name vocabularies.
+ if (!empty($this->options['vids'])) {
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($this->options['vids'] as $vid) {
+ if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
+ $this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
+ }
+ }
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['vocabularies'] = array('default' => array());
+ $options['type'] = array('default' => 'tid');
+ $options['transform'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $vocabularies = taxonomy_get_vocabularies();
+ $options = array();
+ foreach ($vocabularies as $voc) {
+ $options[$voc->machine_name] = check_plain($voc->name);
+ }
+
+ $form['vocabularies'] = array(
+ '#type' => 'checkboxes',
+ '#prefix' => '<div id="edit-options-validate-argument-vocabulary-wrapper">',
+ '#suffix' => '</div>',
+ '#title' => t('Vocabularies'),
+ '#options' => $options,
+ '#default_value' => $this->options['vocabularies'],
+ '#description' => t('If you wish to validate for specific vocabularies, check them; if none are checked, all terms will pass.'),
+ );
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Filter value type'),
+ '#options' => array(
+ 'tid' => t('Term ID'),
+ 'tids' => t('Term IDs separated by , or +'),
+ 'name' => t('Term name'),
+ 'convert' => t('Term name converted to Term ID'),
+ ),
+ '#default_value' => $this->options['type'],
+ '#description' => t('Select the form of this filter value; if using term name, it is generally more efficient to convert it to a term ID and use Taxonomy: Term ID rather than Taxonomy: Term Name" as the filter.'),
+ );
+
+ $form['transform'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Transform dashes in URL to spaces in term name filter values'),
+ '#default_value' => $this->options['transform'],
+ );
+ }
+
+ function options_submit(&$form, &$form_state, &$options = array()) {
+ // Filter unselected items so we don't unnecessarily store giant arrays.
+ $options['vocabularies'] = array_filter($options['vocabularies']);
+ }
+
+ function convert_options(&$options) {
+ if (!isset($options['vocabularies']) && !empty($this->argument->options['validate_argument_vocabulary'])) {
+ $options['vocabularies'] = $this->argument->options['validate_argument_vocabulary'];
+ $options['type'] = $this->argument->options['validate_argument_type'];
+ $options['transform'] = isset($this->argument->options['validate_argument_transform']) ? $this->argument->options['validate_argument_transform'] : FALSE;
+ }
+ }
+
+ function validate_argument($argument) {
+ $vocabularies = array_filter($this->options['vocabularies']);
+ $type = $this->options['type'];
+ $transform = $this->options['transform'];
+
+ switch ($type) {
+ case 'tid':
+ if (!is_numeric($argument)) {
+ return FALSE;
+ }
+
+ $query = db_select('taxonomy_term_data', 'td');
+ $query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
+ $query->fields('td');
+ $query->condition('td.tid', $argument);
+ $query->addTag('term_access');
+ $term = $query->execute()->fetchObject();
+ if (!$term) {
+ return FALSE;
+ }
+ $term = taxonomy_term_load($term->tid);
+ $this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term));
+ return empty($vocabularies) || !empty($vocabularies[$term->vocabulary_machine_name]);
+
+ case 'tids':
+ // An empty argument is not a term so doesn't pass.
+ if (empty($argument)) {
+ return FALSE;
+ }
+
+ $tids = new stdClass();
+ $tids->value = $argument;
+ $tids = views_break_phrase($argument, $tids);
+ if ($tids->value == array(-1)) {
+ return FALSE;
+ }
+
+ $test = drupal_map_assoc($tids->value);
+ $titles = array();
+
+ // check, if some tids already verified
+ static $validated_cache = array();
+ foreach ($test as $tid) {
+ if (isset($validated_cache[$tid])) {
+ if ($validated_cache[$tid] === FALSE) {
+ return FALSE;
+ }
+ else {
+ $titles[] = $validated_cache[$tid];
+ unset($test[$tid]);
+ }
+ }
+ }
+
+ // if unverified tids left - verify them and cache results
+ if (count($test)) {
+ $query = db_select('taxonomy_term_data', 'td');
+ $query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
+ $query->fields('td');
+ $query->fields('tv', array('machine_name'));
+ $query->condition('td.tid', $test);
+
+ $result = $query->execute();
+
+ foreach ($result as $term) {
+ if ($vocabularies && empty($vocabularies[$term->machine_name])) {
+ $validated_cache[$term->tid] = FALSE;
+ return FALSE;
+ }
+ $term = taxonomy_term_load($term->tid);
+ $titles[] = $validated_cache[$term->tid] = check_plain(entity_label('taxonomy_term', $term));
+ unset($test[$term->tid]);
+ }
+ }
+
+ // Remove duplicate titles
+ $titles = array_unique($titles);
+
+ $this->argument->validated_title = implode($tids->operator == 'or' ? ' + ' : ', ', $titles);
+ // If this is not empty, we did not find a tid.
+ return empty($test);
+
+ case 'name':
+ case 'convert':
+ $query = db_select('taxonomy_term_data', 'td');
+ $query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
+ $query->fields('td');
+ $query->fields('tv', array('machine_name'));
+ if (!empty($vocabularies)) {
+ $query->condition('tv.machine_name', $vocabularies);
+ }
+ if ($transform) {
+ $query->where("replace(td.name, ' ', '-') = :name", array(':name' => $argument));
+ }
+ else {
+ $query->condition('td.name', $argument);
+ }
+ $term = $query->execute()->fetchObject();
+
+ if ($term && (empty($vocabularies) || !empty($vocabularies[$term->machine_name]))) {
+ if ($type == 'convert') {
+ $this->argument->argument = $term->tid;
+ }
+ $term = taxonomy_term_load($term->tid);
+ $this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term));
+ return TRUE;
+ }
+ return FALSE;
+ }
+ }
+
+ function process_summary_arguments(&$args) {
+ $type = $this->options['type'];
+ $transform = $this->options['transform'];
+ $vocabularies = array_filter($this->options['vocabularies']);
+
+ if ($type == 'convert') {
+ $arg_keys = array_flip($args);
+
+ $query = db_select('taxonomy_term_data', 'td');
+ $query->condition('tid', $args);
+ $query->addField('td', 'tid', 'tid');
+ if (!empty($vocabularies)) {
+ $query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
+ $query->condition('tv.machine_name', $vocabularies);
+ }
+ if ($transform) {
+ $query->addExpression("REPLACE(td.name, ' ', '-')", 'name');
+ }
+ else {
+ $query->addField('td', 'name', 'name');
+ }
+
+ foreach ($query->execute()->fetchAllKeyed() as $tid => $term) {
+ $args[$arg_keys[$tid]] = $term;
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/tracker.views.inc b/sites/all/modules/views/modules/tracker.views.inc
new file mode 100644
index 000000000..ee14589d4
--- /dev/null
+++ b/sites/all/modules/views/modules/tracker.views.inc
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for tracker.module.
+ *
+ * @ingroup views_module_handlers
+ */
+/**
+ * Implementation of hook_views_data().
+ */
+function tracker_views_data() {
+ $data = array();
+
+ $data['tracker_node']['table']['group'] = t('Tracker');
+ $data['tracker_node']['table']['join'] = array(
+ 'node' => array(
+ 'type' => 'INNER',
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ );
+ $data['tracker_node']['nid'] = array(
+ 'title' => t('Nid'),
+ 'help' => t('The node ID of the node.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_node',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_nid',
+ 'name field' => 'title',
+ 'numeric' => TRUE,
+ 'validate type' => 'nid',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['tracker_node']['changed'] = array(
+ 'title' => t('Updated date'),
+ 'help' => t('The date the node was last updated.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+ $data['tracker_node']['published'] = array(
+ 'title' => t('Published'),
+ 'help' => t('Whether or not the node is published.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_tracker_boolean_operator',
+ 'label' => t('Published'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ $data['tracker_user']['table']['group'] = t('Tracker - User');
+ $data['tracker_user']['table']['join'] = array(
+ 'node' => array(
+ 'type' => 'INNER',
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ 'user' => array(
+ 'type' => 'INNER',
+ 'left_field' => 'uid',
+ 'field' => 'uid',
+ ),
+ );
+ $data['tracker_user']['nid'] = array(
+ 'title' => t('Nid'),
+ 'help' => t('The node ID of the node a user created or commented on. You must use an argument or filter on UID or you will get misleading results using this field.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_node',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_nid',
+ 'name field' => 'title',
+ 'numeric' => TRUE,
+ 'validate type' => 'nid',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['tracker_user']['uid'] = array(
+ 'title' => t('Uid'),
+ 'help' => t('The user ID of a user who touched the node (either created or commented on it).'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_user',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_user_uid',
+ 'name field' => 'name',
+ ),
+ 'filter' => array(
+ 'title' => t('Name'),
+ 'handler' => 'views_handler_filter_user_name',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['tracker_user']['changed'] = array(
+ 'title' => t('Updated date'),
+ 'help' => t('The date the node was last updated or commented on. You must use an argument or filter on UID or you will get misleading results using this field.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+ $data['tracker_user']['published'] = array(
+ 'title' => t('Published'),
+ 'help' => t('Whether or not the node is published. You must use an argument or filter on UID or you will get misleading results using this field.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_tracker_boolean_operator',
+ 'label' => t('Published'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Implementation of hook_views_data_alter().
+ */
+function tracker_views_data_alter(&$data) {
+ // Provide additional uid_touch handlers which are handled by tracker
+ $data['node']['uid_touch_tracker'] = array(
+ 'group' => t('Tracker - User'),
+ 'title' => t('User posted or commented'),
+ 'help' => t('Display nodes only if a user posted the node or commented on the node.'),
+ 'argument' => array(
+ 'field' => 'uid',
+ 'name table' => 'users',
+ 'name field' => 'name',
+ 'handler' => 'views_handler_argument_tracker_comment_user_uid',
+ 'no group by' => TRUE,
+ ),
+ 'filter' => array(
+ 'field' => 'uid',
+ 'name table' => 'users',
+ 'name field' => 'name',
+ 'handler' => 'views_handler_filter_tracker_comment_user_uid'
+ ),
+ );
+}
diff --git a/sites/all/modules/views/modules/tracker/views_handler_argument_tracker_comment_user_uid.inc b/sites/all/modules/views/modules/tracker/views_handler_argument_tracker_comment_user_uid.inc
new file mode 100644
index 000000000..e614482d4
--- /dev/null
+++ b/sites/all/modules/views/modules/tracker/views_handler_argument_tracker_comment_user_uid.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains views_handler_argument_tracker_comment_user_uid
+ */
+
+/**
+ * UID argument to check for nodes that user posted or commented on.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_tracker_comment_user_uid extends views_handler_argument_comment_user_uid {
+
+ /**
+ * Overrides views_handler_argument_comment_user_uid::query().
+ */
+ function query($group_by = FALSE) {
+ // Because this handler thinks it's an argument for a field on the {node}
+ // table, we need to make sure {tracker_user} is JOINed and use its alias
+ // for the WHERE clause.
+ $tracker_user_alias = $this->query->ensure_table('tracker_user');
+ $this->query->add_where(0, "$tracker_user_alias.uid", $this->argument);
+ }
+
+}
diff --git a/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_boolean_operator.inc b/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_boolean_operator.inc
new file mode 100644
index 000000000..455e82420
--- /dev/null
+++ b/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_boolean_operator.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains views_handler_filter_tracker_boolean_operator
+ */
+
+ /**
+ * Filter handler for boolean values to use = 1 instead of <> 0.
+ */
+class views_handler_filter_tracker_boolean_operator extends views_handler_filter_boolean_operator {
+
+ /**
+ * Overrides views_handler_filter_boolean_operator::query().
+ */
+ function query() {
+ $this->ensure_my_table();
+ $where = "$this->table_alias.$this->real_field ";
+ if (empty($this->value)) {
+ $where .= '= 0';
+ if ($this->accept_null) {
+ $where = '(' . $where . " OR $this->table_alias.$this->real_field IS NULL)";
+ }
+ }
+ else {
+ $where .= '= 1';
+ }
+ $this->query->add_where_expression($this->options['group'], $where);
+ }
+
+}
diff --git a/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_comment_user_uid.inc b/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_comment_user_uid.inc
new file mode 100644
index 000000000..da6f65bc3
--- /dev/null
+++ b/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_comment_user_uid.inc
@@ -0,0 +1,23 @@
+<?php
+/**
+ * @file
+ * Contains views_handler_filter_tracker_comment_user_uid
+ */
+
+/**
+ * UID filter to check for nodes that user posted or commented on.
+ */
+class views_handler_filter_tracker_comment_user_uid extends views_handler_filter_comment_user_uid {
+
+ /**
+ * Overrides views_handler_filter_comment_user_uid::query()
+ */
+ function query() {
+ // Because this handler thinks it's an argument for a field on the {node}
+ // table, we need to make sure {tracker_user} is JOINed and use its alias
+ // for the WHERE clause.
+ $tracker_user_alias = $this->query->ensure_table('tracker_user');
+ $this->query->add_where(0, "$tracker_user_alias.uid", $this->value);
+ }
+
+}
diff --git a/sites/all/modules/views/modules/translation.views.inc b/sites/all/modules/views/modules/translation.views.inc
new file mode 100644
index 000000000..584c63f97
--- /dev/null
+++ b/sites/all/modules/views/modules/translation.views.inc
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for translation.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data_alter().
+ *
+ * Add translation information to the node table.
+ */
+function translation_views_data_alter(&$data) {
+
+ // Joins
+ $data['node']['table']['join']['node'] = array(
+ 'left_field' => 'tnid',
+ 'field' => 'tnid',
+ );
+
+ // The translation ID (nid of the "source" translation)
+ $data['node']['tnid'] = array(
+ 'group' => t('Content translation'),
+ 'title' => t('Translation set node ID'),
+ 'help' => t('The ID of the translation set the content belongs to.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_node',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_tnid',
+ 'name field' => 'title', // the field to display in the summary.
+ 'numeric' => TRUE,
+ 'validate type' => 'tnid',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'relationship' => array(
+ 'title' => t('Source translation'),
+ 'help' => t('The source that this content was translated from.'),
+ 'base' => 'node',
+ 'base field' => 'nid',
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Source translation'),
+ ),
+ );
+
+ // All translations.
+ $data['node']['translation'] = array(
+ 'group' => t('Content translation'),
+ 'title' => t('Translations'),
+ 'help' => t('Versions of content in different languages.'),
+ 'relationship' => array(
+ 'title' => t('Translations'),
+ 'help' => t('Versions of content in different languages.'),
+ 'base' => 'node',
+ 'base field' => 'tnid',
+ 'relationship table' => 'node',
+ 'relationship field' => 'tnid',
+ 'handler' => 'views_handler_relationship_translation',
+ 'label' => t('Translations'),
+ ),
+ );
+
+ // The source translation.
+ $data['node']['source_translation'] = array(
+ 'group' => t('Content translation'),
+ 'title' => t('Source translation'),
+ 'help' => t('Content that is either untranslated or is the original version of a translation set.'),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_node_tnid',
+ ),
+ );
+
+ // Child translation.
+ $data['node']['child_translation'] = array(
+ 'group' => t('Node translation'),
+ 'title' => t('Child translation'),
+ 'help' => t('Content that is a translation of a source translation.'),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_node_tnid_child',
+ ),
+ );
+
+ // Translation status
+ $data['node']['translate'] = array(
+ 'group' => t('Content translation'),
+ 'title' => t('Translation status'),
+ 'help' => t('The translation status of the content - whether or not the translation needs to be updated.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Outdated'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // Translate node link.
+ $data['node']['translate_node'] = array(
+ 'group' => t('Content translation'),
+ 'title' => t('Translate link'),
+ 'help' => t('Provide a simple link to translate the node.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_node_link_translate',
+ ),
+ );
+
+
+}
diff --git a/sites/all/modules/views/modules/translation/views_handler_argument_node_tnid.inc b/sites/all/modules/views/modules/translation/views_handler_argument_node_tnid.inc
new file mode 100644
index 000000000..61e9ebabf
--- /dev/null
+++ b/sites/all/modules/views/modules/translation/views_handler_argument_node_tnid.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Provide node tnid argument handler.
+ */
+
+/**
+ * Argument handler to accept a node translation id.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_node_tnid extends views_handler_argument_numeric {
+ /**
+ * Override the behavior of title(). Get the title of the node.
+ */
+ function title_query() {
+ $titles = array();
+
+ $result = db_query("SELECT n.title FROM {node} n WHERE n.tnid IN (:tnids)", array(':tnids' => $this->value));
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->title);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/translation/views_handler_field_node_link_translate.inc b/sites/all/modules/views/modules/translation/views_handler_field_node_link_translate.inc
new file mode 100644
index 000000000..3e30725c9
--- /dev/null
+++ b/sites/all/modules/views/modules/translation/views_handler_field_node_link_translate.inc
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_link_translate.
+ */
+
+/**
+ * Field handler to present a link node translate.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_link_translate extends views_handler_field_node_link {
+ function render_link($data, $values) {
+ // ensure user has access to edit this node.
+ $node = $this->get_value($values);
+ $node->status = 1; // unpublished nodes ignore access control
+ if (empty($node->language) || !translation_supported_type($node->type) || !node_access('view', $node) || !user_access('translate content')) {
+ return;
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "node/$node->nid/translate";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('translate');
+ return $text;
+ }
+}
diff --git a/sites/all/modules/views/modules/translation/views_handler_field_node_translation_link.inc b/sites/all/modules/views/modules/translation/views_handler_field_node_translation_link.inc
new file mode 100644
index 000000000..9d5036929
--- /dev/null
+++ b/sites/all/modules/views/modules/translation/views_handler_field_node_translation_link.inc
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_node_translation_link.
+ */
+
+/**
+ * Field handler to present a link to the node.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_node_translation_link extends views_handler_field {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['nid'] = 'nid';
+ $this->additional_fields['tnid'] = 'tnid';
+ $this->additional_fields['title'] = 'title';
+ $this->additional_fields['language'] = 'language';
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ }
+
+ function render($values) {
+ $value = $this->get_value($values, 'tnid');
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+
+ function render_link($data, $values) {
+ global $language;
+
+ $tnid = $this->get_value($values, 'tnid');
+ // Only load translations if the node isn't in the current language.
+ if ($this->get_value($values, 'language') != $language->language) {
+ $translations = translation_node_get_translations($tnid);
+ if (isset($translations[$language->language])) {
+ $values->{$this->aliases['nid']} = $translations[$language->language]->nid;
+ $values->{$this->aliases['title']} = $translations[$language->language]->title;
+ }
+ }
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "node/" . $this->get_value($values, 'nid');
+ return $this->get_value($values, 'title');
+ }
+}
diff --git a/sites/all/modules/views/modules/translation/views_handler_filter_node_tnid.inc b/sites/all/modules/views/modules/translation/views_handler_filter_node_tnid.inc
new file mode 100644
index 000000000..ed4d6a9a7
--- /dev/null
+++ b/sites/all/modules/views/modules/translation/views_handler_filter_node_tnid.inc
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_node_tnid.
+ */
+
+/**
+ * Filter by whether the node is the original translation.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_node_tnid extends views_handler_filter {
+ function admin_summary() { }
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['operator']['default'] = 1;
+
+ return $options;
+ }
+
+ /**
+ * Provide simple boolean operator
+ */
+ function operator_form(&$form, &$form_state) {
+ $form['operator'] = array(
+ '#type' => 'radios',
+ '#title' => t('Include untranslated content'),
+ '#default_value' => $this->operator,
+ '#options' => array(
+ 1 => t('Yes'),
+ 0 => t('No'),
+ ),
+ );
+ }
+
+ function can_expose() { return FALSE; }
+
+ function query() {
+ $table = $this->ensure_my_table();
+ // Select for source translations (tnid = nid). Conditionally, also accept either untranslated nodes (tnid = 0).
+ $this->query->add_where_expression($this->options['group'], "$table.tnid = $table.nid" . ($this->operator ? " OR $table.tnid = 0" : ''));
+ }
+}
diff --git a/sites/all/modules/views/modules/translation/views_handler_filter_node_tnid_child.inc b/sites/all/modules/views/modules/translation/views_handler_filter_node_tnid_child.inc
new file mode 100644
index 000000000..51316eb33
--- /dev/null
+++ b/sites/all/modules/views/modules/translation/views_handler_filter_node_tnid_child.inc
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_node_tnid_child.
+ */
+
+/**
+ * Filter by whether the node is not the original translation.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_node_tnid_child extends views_handler_filter {
+ function admin_summary() { }
+ function operator_form(&$form, &$form_state) { }
+ function can_expose() { return FALSE; }
+
+ function query() {
+ $table = $this->ensure_my_table();
+ $this->query->add_where_expression($this->options['group'], "$table.tnid <> $table.nid AND $table.tnid > 0");
+ }
+}
diff --git a/sites/all/modules/views/modules/translation/views_handler_relationship_translation.inc b/sites/all/modules/views/modules/translation/views_handler_relationship_translation.inc
new file mode 100644
index 000000000..509a9352d
--- /dev/null
+++ b/sites/all/modules/views/modules/translation/views_handler_relationship_translation.inc
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_relationship_translation.
+ */
+
+/**
+ * Handles relationships for content translation sets and provides multiple
+ * options.
+ *
+ * @ingroup views_relationship_handlers
+ */
+class views_handler_relationship_translation extends views_handler_relationship {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['language'] = array('default' => 'current');
+
+ return $options;
+ }
+
+ /**
+ * Add a translation selector.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $options = array(
+ 'all' => t('All'),
+ 'current' => t('Current language'),
+ 'default' => t('Default language'),
+ );
+ $options = array_merge($options, locale_language_list());
+ $form['language'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => $this->options['language'],
+ '#title' => t('Translation option'),
+ '#description' => t('The translation options allows you to select which translation or translations in a translation set join on. Select "Current language" or "Default language" to join on the translation in the current or default language respectively. Select a specific language to join on a translation in that language. If you select "All", each translation will create a new row, which may appear to cause duplicates.'),
+ );
+ }
+
+ /**
+ * Called to implement a relationship in a query.
+ */
+ function query() {
+ // Figure out what base table this relationship brings to the party.
+ $table_data = views_fetch_data($this->definition['base']);
+ $base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field'];
+
+ $this->ensure_my_table();
+
+ $def = $this->definition;
+ $def['table'] = $this->definition['base'];
+ $def['field'] = $base_field;
+ $def['left_table'] = $this->table_alias;
+ $def['left_field'] = $this->field;
+ if (!empty($this->options['required'])) {
+ $def['type'] = 'INNER';
+ }
+
+ $def['extra'] = array();
+ if ($this->options['language'] != 'all') {
+ switch ($this->options['language']) {
+ case 'current':
+ $def['extra'][] = array(
+ 'field' => 'language',
+ 'value' => '***CURRENT_LANGUAGE***',
+ );
+ break;
+ case 'default':
+ $def['extra'][] = array(
+ 'field' => 'language',
+ 'value' => '***DEFAULT_LANGUAGE***',
+ );
+ break;
+ // Other values will be the language codes.
+ default:
+ $def['extra'][] = array(
+ 'field' => 'language',
+ 'value' => $this->options['language'],
+ );
+ break;
+ }
+ }
+
+ if (!empty($def['join_handler']) && class_exists($def['join_handler'])) {
+ $join = new $def['join_handler'];
+ }
+ else {
+ $join = new views_join();
+ }
+
+ $join->definition = $def;
+ $join->construct();
+ $join->adjusted = TRUE;
+
+ // use a short alias for this:
+ $alias = $def['table'] . '_' . $this->table;
+
+ $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship);
+ }
+}
diff --git a/sites/all/modules/views/modules/user.views.inc b/sites/all/modules/views/modules/user.views.inc
new file mode 100644
index 000000000..70771045d
--- /dev/null
+++ b/sites/all/modules/views/modules/user.views.inc
@@ -0,0 +1,575 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers for user.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function user_views_data() {
+ // ----------------------------------------------------------------
+ // users table
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['users']['table']['group'] = t('User');
+
+ $data['users']['table']['base'] = array(
+ 'field' => 'uid',
+ 'title' => t('User'),
+ 'help' => t('Users who have created accounts on your site.'),
+ 'access query tag' => 'user_access',
+ );
+ $data['users']['table']['entity type'] = 'user';
+
+
+ $data['users']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'uid',
+ ),
+ 'node_revision' => array(
+ 'table' => 'node_revision',
+ 'field' => 'uid',
+ ),
+ 'file' => array(
+ 'table' => 'file',
+ 'field' => 'uid',
+ ),
+ );
+
+ // uid
+ $data['users']['uid'] = array(
+ 'title' => t('Uid'),
+ 'help' => t('The user ID'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_user',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_user_uid',
+ 'name field' => 'name', // display this field in the summary
+ ),
+ 'filter' => array(
+ 'title' => t('Name'),
+ 'handler' => 'views_handler_filter_user_name',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'relationship' => array(
+ 'title' => t('Content authored'),
+ 'help' => t('Relate content to the user who created it. This relationship will create one record for each content item created by the user.'),
+ 'handler' => 'views_handler_relationship',
+ 'base' => 'node',
+ 'base field' => 'uid',
+ 'field' => 'uid',
+ 'label' => t('nodes'),
+ ),
+ );
+
+ // uid_raw
+ $data['users']['uid_raw'] = array(
+ 'help' => t('The raw numeric user ID.'),
+ 'real field' => 'uid',
+ 'filter' => array(
+ 'title' => t('The user ID'),
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ );
+
+ // uid
+ $data['users']['uid_representative'] = array(
+ 'relationship' => array(
+ 'title' => t('Representative node'),
+ 'label' => t('Representative node'),
+ 'help' => t('Obtains a single representative node for each user, according to a chosen sort criterion.'),
+ 'handler' => 'views_handler_relationship_groupwise_max',
+ 'relationship field' => 'uid',
+ 'outer field' => 'users.uid',
+ 'argument table' => 'users',
+ 'argument field' => 'uid',
+ 'base' => 'node',
+ 'field' => 'nid',
+ ),
+ );
+
+ // uid
+ $data['users']['uid_current'] = array(
+ 'real field' => 'uid',
+ 'title' => t('Current'),
+ 'help' => t('Filter the view to the currently logged in user.'),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_user_current',
+ 'type' => 'yes-no',
+ ),
+ );
+
+ // name
+ $data['users']['name'] = array(
+ 'title' => t('Name'), // The item it appears as on the UI,
+ 'help' => t('The user or author name.'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_user_name',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ 'title' => t('Name (raw)'),
+ 'help' => t('The user or author name. This filter does not check if the user exists and allows partial matching. Does not utilize autocomplete.')
+ ),
+ );
+
+ // mail
+ // Note that this field implements field level access control.
+ $data['users']['mail'] = array(
+ 'title' => t('E-mail'), // The item it appears as on the UI,
+ 'help' => t('Email address for a given user. This field is normally not shown to users, so be cautious when using it.'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_user_mail',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // language
+ $data['users']['language'] = array(
+ 'title' => t('Language'), // The item it appears as on the UI,
+ 'help' => t('Language of the user'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_user_language',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_node_language',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_node_language',
+ ),
+ );
+
+ // picture
+ $data['users']['picture_fid']['moved to'] = array('users', 'picture');
+ $data['users']['picture'] = array(
+ 'title' => t('Picture'),
+ 'help' => t("The user's picture, if allowed."), // The help that appears on the UI,
+ // Information for displaying the uid
+ 'field' => array(
+ 'handler' => 'views_handler_field_user_picture',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Has Avatar'),
+ 'type' => 'yes-no',
+ ),
+ );
+
+ // link
+ $data['users']['view_user'] = array(
+ 'field' => array(
+ 'title' => t('Link'),
+ 'help' => t('Provide a simple link to the user.'),
+ 'handler' => 'views_handler_field_user_link',
+ ),
+ );
+
+ // created field
+ $data['users']['created'] = array(
+ 'title' => t('Created date'), // The item it appears as on the UI,
+ 'help' => t('The date the user was created.'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ $data['users']['created_fulldate'] = array(
+ 'title' => t('Created date'),
+ 'help' => t('Date in the form of CCYYMMDD.'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_fulldate',
+ ),
+ );
+
+ $data['users']['created_year_month'] = array(
+ 'title' => t('Created year + month'),
+ 'help' => t('Date in the form of YYYYMM.'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_year_month',
+ ),
+ );
+
+ $data['users']['timestamp_year']['moved to'] = array('users', 'created_year');
+ $data['users']['created_year'] = array(
+ 'title' => t('Created year'),
+ 'help' => t('Date in the form of YYYY.'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_year',
+ ),
+ );
+
+ $data['users']['created_month'] = array(
+ 'title' => t('Created month'),
+ 'help' => t('Date in the form of MM (01 - 12).'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_month',
+ ),
+ );
+
+ $data['users']['created_day'] = array(
+ 'title' => t('Created day'),
+ 'help' => t('Date in the form of DD (01 - 31).'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_day',
+ ),
+ );
+
+ $data['users']['created_week'] = array(
+ 'title' => t('Created week'),
+ 'help' => t('Date in the form of WW (01 - 53).'),
+ 'argument' => array(
+ 'field' => 'created',
+ 'handler' => 'views_handler_argument_node_created_week',
+ ),
+ );
+
+ // access field
+ $data['users']['access'] = array(
+ 'title' => t('Last access'), // The item it appears as on the UI,
+ 'help' => t("The user's last access date."), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ // login field
+ $data['users']['login'] = array(
+ 'title' => t('Last login'), // The item it appears as on the UI,
+ 'help' => t("The user's last login date."), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ // active status
+ $data['users']['status'] = array(
+ 'title' => t('Active'), // The item it appears as on the UI,
+ 'help' => t('Whether a user is active or blocked.'), // The help that appears on the UI,
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ 'output formats' => array(
+ 'active-blocked' => array(t('Active'), t('Blocked')),
+ ),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ 'label' => t('Active'),
+ 'type' => 'yes-no',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // log field
+ $data['users']['signature'] = array(
+ 'title' => t('Signature'), // The item it appears as on the UI,
+ 'help' => t("The user's signature."), // The help that appears on the UI,
+ // Information for displaying a title as a field
+ 'field' => array(
+ 'handler' => 'views_handler_field_markup',
+ 'format' => filter_fallback_format(),
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ );
+
+ $data['users']['edit_node'] = array(
+ 'field' => array(
+ 'title' => t('Edit link'),
+ 'help' => t('Provide a simple link to edit the user.'),
+ 'handler' => 'views_handler_field_user_link_edit',
+ ),
+ );
+
+ $data['users']['cancel_node'] = array(
+ 'field' => array(
+ 'title' => t('Cancel link'),
+ 'help' => t('Provide a simple link to cancel the user.'),
+ 'handler' => 'views_handler_field_user_link_cancel',
+ ),
+ );
+
+ $data['users']['data'] = array(
+ 'title' => t('Data'),
+ 'help' => t('Provide serialized data of the user'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_serialized',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // users_roles table
+
+ $data['users_roles']['table']['group'] = t('User');
+
+ // Explain how this table joins to others.
+ $data['users_roles']['table']['join'] = array(
+ // Directly links to users table.
+ 'users' => array(
+ 'left_field' => 'uid',
+ 'field' => 'uid',
+ ),
+ );
+
+ $data['users_roles']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'uid',
+ ),
+ 'node_revision' => array(
+ 'table' => 'node_revision',
+ 'field' => 'uid',
+ ),
+ );
+
+ $data['users_roles']['rid'] = array(
+ 'title' => t('Roles'),
+ 'help' => t('Roles that a user belongs to.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_user_roles',
+ 'no group by' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_user_roles',
+ 'numeric' => TRUE,
+ 'allow empty' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_users_roles_rid',
+ 'name table' => 'role',
+ 'name field' => 'name',
+ 'empty field name' => t('No role'),
+ 'zero is null' => TRUE,
+ 'numeric' => TRUE,
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // role table
+
+ $data['role']['table']['join'] = array(
+ // Directly links to users table.
+ 'users' => array(
+ 'left_table' => 'users_roles',
+ 'left_field' => 'rid',
+ 'field' => 'rid',
+ ),
+ // needed for many to one helper sometimes
+ 'users_roles' => array(
+ 'left_field' => 'rid',
+ 'field' => 'rid',
+ ),
+ );
+
+ $data['role']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'uid',
+ ),
+ 'node_revision' => array(
+ 'table' => 'node_revision',
+ 'field' => 'uid',
+ ),
+ );
+
+ // permission table
+ $data['role_permission']['table']['group'] = t('User');
+ $data['role_permission']['table']['join'] = array(
+ // Directly links to users table.
+ 'users' => array(
+ 'left_table' => 'users_roles',
+ 'left_field' => 'rid',
+ 'field' => 'rid',
+ ),
+ );
+
+ $data['role_permission']['permission'] = array(
+ 'title' => t('Permission'),
+ 'help' => t('The user permissions.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_user_permissions',
+ 'no group by' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_user_permissions',
+ ),
+ );
+
+ // ----------------------------------------------------------------------
+ // authmap table
+
+ $data['authmap']['table']['group'] = t('User');
+ $data['authmap']['table']['join'] = array(
+ // Directly links to users table.
+ 'users' => array(
+ 'left_field' => 'uid',
+ 'field' => 'uid',
+ ),
+ );
+
+ $data['authmap']['table']['default_relationship'] = array(
+ 'node' => array(
+ 'table' => 'node',
+ 'field' => 'uid',
+ ),
+ 'node_revision' => array(
+ 'table' => 'node_revision',
+ 'field' => 'uid',
+ ),
+ );
+
+ $data['authmap']['aid'] = array(
+ 'title' => t('Authmap ID'),
+ 'help' => t('The Authmap ID.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ 'numeric' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ 'numeric' => TRUE,
+ ),
+ );
+ $data['authmap']['authname'] = array(
+ 'title' => t('Authentication name'),
+ 'help' => t('The unique authentication name.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+ $data['authmap']['module'] = array(
+ 'title' => t('Authentication module'),
+ 'help' => t('The name of the module managing the authentication entry.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function user_views_plugins() {
+ return array(
+ 'module' => 'views', // This just tells our themes are elsewhere.
+ 'row' => array(
+ 'user' => array(
+ 'title' => t('User'),
+ 'help' => t('Display the user with standard user view.'),
+ 'handler' => 'views_plugin_row_user_view',
+ 'base' => array('users'), // only works with 'users' as base.
+ 'uses options' => TRUE,
+ 'type' => 'normal',
+ 'help topic' => 'style-users',
+ ),
+ ),
+ 'argument default' => array(
+ 'user' => array(
+ 'title' => t('User ID from URL'),
+ 'handler' => 'views_plugin_argument_default_user',
+ 'path' => drupal_get_path('module', 'views') . '/modules/user', // not necessary for most modules
+ ),
+ 'current_user' => array(
+ 'title' => t('User ID from logged in user'),
+ 'handler' => 'views_plugin_argument_default_current_user',
+ 'path' => drupal_get_path('module', 'views') . '/modules/user', // not necessary for most modules
+ ),
+ ),
+ 'argument validator' => array(
+ 'user' => array(
+ 'title' => t('User'),
+ 'handler' => 'views_plugin_argument_validate_user',
+ 'path' => drupal_get_path('module', 'views') . '/modules/user', // not necessary for most modules
+ ),
+ ),
+ );
+}
+
+/**
+ * Allow replacement of current userid so we can cache these queries
+ */
+function user_views_query_substitutions($view) {
+ global $user;
+ return array('***CURRENT_USER***' => intval($user->uid));
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_argument_user_uid.inc b/sites/all/modules/views/modules/user/views_handler_argument_user_uid.inc
new file mode 100644
index 000000000..6ab9167d5
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_argument_user_uid.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_user_uid.
+ */
+
+/**
+ * Argument handler to accept a user id.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_user_uid extends views_handler_argument_numeric {
+ /**
+ * Override the behavior of title(). Get the name of the user.
+ *
+ * @return array
+ * A list of usernames.
+ */
+ function title_query() {
+ if (!$this->argument) {
+ return array(variable_get('anonymous', t('Anonymous')));
+ }
+
+ $titles = array();
+
+ $result = db_query("SELECT u.name FROM {users} u WHERE u.uid IN (:uids)", array(':uids' => $this->value));
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->name);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_argument_users_roles_rid.inc b/sites/all/modules/views/modules/user/views_handler_argument_users_roles_rid.inc
new file mode 100644
index 000000000..31c581451
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_argument_users_roles_rid.inc
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_argument_users_roles_rid.
+ */
+
+/**
+ * Allow role ID(s) as argument.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_users_roles_rid extends views_handler_argument_many_to_one {
+ function title_query() {
+ $titles = array();
+
+ $result = db_query("SELECT name FROM {role} WHERE rid IN (:rids)", array(':rids' => $this->value));
+ foreach ($result as $term) {
+ $titles[] = check_plain($term->name);
+ }
+ return $titles;
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user.inc b/sites/all/modules/views/modules/user/views_handler_field_user.inc
new file mode 100644
index 000000000..f6b15b5bc
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user.inc
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows linking to a user.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user extends views_handler_field {
+ /**
+ * Override init function to provide generic option to link to user.
+ */
+ function init(&$view, &$data) {
+ parent::init($view, $data);
+ if (!empty($this->options['link_to_user'])) {
+ $this->additional_fields['uid'] = 'uid';
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_user'] = array('default' => TRUE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Provide link to node option
+ */
+ function options_form(&$form, &$form_state) {
+ $form['link_to_user'] = array(
+ '#title' => t('Link this field to its user'),
+ '#description' => t("Enable to override this field's links."),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['link_to_user'],
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ function render_link($data, $values) {
+ if (!empty($this->options['link_to_user']) && user_access('access user profiles') && ($uid = $this->get_value($values, 'uid')) && $data !== NULL && $data !== '') {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "user/" . $uid;
+ }
+ return $data;
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_language.inc b/sites/all/modules/views/modules/user/views_handler_field_user_language.inc
new file mode 100644
index 000000000..e29da31a1
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_language.inc
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_language.
+ */
+
+/**
+ * Views field handler for user language.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_language extends views_handler_field_user {
+
+ function render_link($data, $values) {
+ $uid = $this->get_value($values, 'uid');
+ if (!empty($this->options['link_to_user'])) {
+ $uid = $this->get_value($values, 'uid');
+ if (user_access('access user profiles') && $uid) {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = 'user/' . $uid;
+ }
+ }
+ if (empty($data)) {
+ $lang = language_default();
+ }
+ else {
+ $lang = language_list();
+ $lang = $lang[$data];
+ }
+
+ return $this->sanitize_value($lang->name);
+ }
+
+ function render($values) {
+ $value = $this->get_value($values);
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_link.inc b/sites/all/modules/views/modules/user/views_handler_field_user_link.inc
new file mode 100644
index 000000000..03b5e0d4a
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_link.inc
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_link.
+ */
+
+/**
+ * Field handler to present a link to the user.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_link extends views_handler_field {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['uid'] = 'uid';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['text'] = array('default' => '', 'translatable' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text to display'),
+ '#default_value' => $this->options['text'],
+ );
+ parent::options_form($form, $form_state);
+ }
+
+ // An example of field level access control.
+ function access() {
+ return user_access('access user profiles');
+ }
+
+ function query() {
+ $this->ensure_my_table();
+ $this->add_additional_fields();
+ }
+
+ function render($values) {
+ $value = $this->get_value($values, 'uid');
+ return $this->render_link($this->sanitize_value($value), $values);
+ }
+
+ function render_link($data, $values) {
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('view');
+
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "user/" . $data;
+
+ return $text;
+ }
+
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_link_cancel.inc b/sites/all/modules/views/modules/user/views_handler_field_user_link_cancel.inc
new file mode 100644
index 000000000..fe038dad8
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_link_cancel.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_link_cancel.
+ */
+
+/**
+ * Field handler to present a link to user cancel.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_link_cancel extends views_handler_field_user_link {
+
+ function render_link($data, $values) {
+ $uid = $values->{$this->aliases['uid']};
+
+ // Build a pseudo account object to be able to check the access.
+ $account = new stdClass();
+ $account->uid = $uid;
+
+ if ($uid && user_cancel_access($account)) {
+ $this->options['alter']['make_link'] = TRUE;
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('Cancel account');
+
+ $this->options['alter']['path'] = "user/$uid/cancel";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ return $text;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_link_edit.inc b/sites/all/modules/views/modules/user/views_handler_field_user_link_edit.inc
new file mode 100644
index 000000000..e37feae4d
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_link_edit.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_link_edit.
+ */
+
+/**
+ * Field handler to present a link to user edit.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_link_edit extends views_handler_field_user_link {
+ function render_link($data, $values) {
+ // Build a pseudo account object to be able to check the access.
+ $account = new stdClass();
+ $account->uid = $data;
+
+ if ($data && user_edit_access($account)) {
+ $this->options['alter']['make_link'] = TRUE;
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('edit');
+
+ $this->options['alter']['path'] = "user/$data/edit";
+ $this->options['alter']['query'] = drupal_get_destination();
+
+ return $text;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_mail.inc b/sites/all/modules/views/modules/user/views_handler_field_user_mail.inc
new file mode 100644
index 000000000..82d193388
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_mail.inc
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_mail.
+ */
+
+/**
+ * Field handler to provide acess control for the email field.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_mail extends views_handler_field_user {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_to_user'] = array('default' => 'mailto');
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['link_to_user'] = array(
+ '#title' => t('Link this field'),
+ '#type' => 'radios',
+ '#options' => array(
+ 0 => t('No link'),
+ 'user' => t('To the user'),
+ 'mailto' => t("With a mailto:"),
+ ),
+ '#default_value' => $this->options['link_to_user'],
+ );
+ }
+
+ function render_link($data, $values) {
+ parent::render_link($data, $values);
+
+ if ($this->options['link_to_user'] == 'mailto') {
+ $this->options['alter']['make_link'] = TRUE;
+ $this->options['alter']['path'] = "mailto:" . $data;
+ }
+
+ return $data;
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_name.inc b/sites/all/modules/views/modules/user/views_handler_field_user_name.inc
new file mode 100644
index 000000000..45514519d
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_name.inc
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_name.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows using a themed user link.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_name extends views_handler_field_user {
+ /**
+ * Add uid in the query so we can test for anonymous if needed.
+ */
+ function init(&$view, &$data) {
+ parent::init($view, $data);
+ if (!empty($this->options['overwrite_anonymous']) || !empty($this->options['format_username'])) {
+ $this->additional_fields['uid'] = 'uid';
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['overwrite_anonymous'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['anonymous_text'] = array('default' => '', 'translatable' => TRUE);
+ $options['format_username'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['format_username'] = array(
+ '#title' => t('Use formatted username'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['format_username']),
+ '#description' => t('If checked, the username will be formatted by the system. If unchecked, it will be displayed raw.'),
+ '#fieldset' => 'more',
+ );
+ $form['overwrite_anonymous'] = array(
+ '#title' => t('Overwrite the value to display for anonymous users'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['overwrite_anonymous']),
+ '#description' => t('Enable to display different text for anonymous users.'),
+ '#fieldset' => 'more',
+ );
+ $form['anonymous_text'] = array(
+ '#title' => t('Text to display for anonymous users'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['anonymous_text'],
+ '#dependency' => array(
+ 'edit-options-overwrite-anonymous' => array(1),
+ ),
+ '#fieldset' => 'more',
+ );
+
+ parent::options_form($form, $form_state);
+ }
+
+ function render_link($data, $values) {
+ $account = new stdClass();
+ $account->uid = $this->get_value($values, 'uid');
+ $account->name = $this->get_value($values);
+ if (!empty($this->options['link_to_user']) || !empty($this->options['overwrite_anonymous'])) {
+ if (!empty($this->options['overwrite_anonymous']) && !$account->uid) {
+ // This is an anonymous user, and we're overriting the text.
+ return check_plain($this->options['anonymous_text']);
+ }
+ elseif (!empty($this->options['link_to_user'])) {
+ $account->name = $this->get_value($values);
+ return theme('username', array('account' => $account));
+ }
+ }
+ // If we want a formatted username, do that.
+ if (!empty($this->options['format_username'])) {
+ return format_username($account);
+ }
+ // Otherwise, there's no special handling, so return the data directly.
+ return $data;
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_permissions.inc b/sites/all/modules/views/modules/user/views_handler_field_user_permissions.inc
new file mode 100644
index 000000000..edc9c4461
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_permissions.inc
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_permissions.
+ */
+
+/**
+ * Field handler to provide a list of permissions.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_permissions extends views_handler_field_prerender_list {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['uid'] = array('table' => 'users', 'field' => 'uid');
+ }
+
+ function query() {
+ $this->add_additional_fields();
+ $this->field_alias = $this->aliases['uid'];
+ }
+
+ function pre_render(&$values) {
+ $uids = array();
+ $this->items = array();
+
+ foreach ($values as $result) {
+ $uids[] = $this->get_value($result, NULL, TRUE);
+ }
+
+ if ($uids) {
+ // Get a list of all the modules implementing a hook_permission() and sort by
+ // display name.
+ $module_info = system_get_info('module');
+ $modules = array();
+ foreach (module_implements('permission') as $module) {
+ $modules[$module] = $module_info[$module]['name'];
+ }
+ asort($modules);
+
+ $permissions = module_invoke_all('permission');
+
+ $result = db_query("SELECT u.uid, u.rid, rp.permission FROM {role_permission} rp INNER JOIN {users_roles} u ON u.rid = rp.rid WHERE u.uid IN (:uids) AND rp.module IN (:modules) ORDER BY rp.permission",
+ array(':uids' => $uids, ':modules' => array_keys($modules)));
+
+ foreach ($result as $perm) {
+ $this->items[$perm->uid][$perm->permission]['permission'] = $permissions[$perm->permission]['title'];
+ }
+ }
+ }
+
+ function render_item($count, $item) {
+ return $item['permission'];
+ }
+
+ /*
+ function document_self_tokens(&$tokens) {
+ $tokens['[' . $this->options['id'] . '-role' . ']'] = t('The name of the role.');
+ $tokens['[' . $this->options['id'] . '-rid' . ']'] = t('The role ID of the role.');
+ }
+
+ function add_self_tokens(&$tokens, $item) {
+ $tokens['[' . $this->options['id'] . '-role' . ']'] = $item['role'];
+ $tokens['[' . $this->options['id'] . '-rid' . ']'] = $item['rid'];
+ }
+ */
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_picture.inc b/sites/all/modules/views/modules/user/views_handler_field_user_picture.inc
new file mode 100644
index 000000000..babbae55f
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_picture.inc
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_picture.
+ */
+
+/**
+ * Field handler to provide simple renderer that allows using a themed user link.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_picture extends views_handler_field {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['uid'] = 'uid';
+ $this->additional_fields['name'] = 'name';
+ $this->additional_fields['mail'] = 'mail';
+ }
+
+ function element_type($none_supported = FALSE, $default_empty = FALSE, $inline = FALSE) {
+ if ($inline) {
+ return 'span';
+ }
+ if ($none_supported) {
+ if ($this->options['element_type'] === '0') {
+ return '';
+ }
+ }
+ if ($this->options['element_type']) {
+ return check_plain($this->options['element_type']);
+ }
+ if ($default_empty) {
+ return '';
+ }
+ if (isset($this->definition['element type'])) {
+ return $this->definition['element type'];
+ }
+
+ return 'div';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['link_photo_to_profile'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['image_style'] = array('default' => '');
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['link_photo_to_profile'] = array(
+ '#title' => t("Link to user's profile"),
+ '#description' => t("Link the user picture to the user's profile"),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['link_photo_to_profile'],
+ );
+
+ if (module_exists('image')) {
+ $styles = image_styles();
+ $style_options = array('' => t('Default'));
+ foreach ($styles as $style) {
+ $style_options[$style['name']] = $style['name'];
+ }
+
+ $form['image_style'] = array(
+ '#title' => t('Image style'),
+ '#description' => t('Using <em>Default</em> will use the site-wide image style for user pictures set in the <a href="!account-settings">Account settings</a>.', array('!account-settings' => url('admin/config/people/accounts', array('fragment' => 'edit-personalization')))),
+ '#type' => 'select',
+ '#options' => $style_options,
+ '#default_value' => $this->options['image_style'],
+ );
+ }
+ }
+
+ function render($values) {
+ if ($this->options['image_style'] && module_exists('image')) {
+ // @todo: Switch to always using theme('user_picture') when it starts
+ // supporting image styles. See http://drupal.org/node/1021564
+ if ($picture_fid = $this->get_value($values)) {
+ $picture = file_load($picture_fid);
+ $picture_filepath = $picture->uri;
+ }
+ else {
+ $picture_filepath = variable_get('user_picture_default', '');
+ }
+ if (file_valid_uri($picture_filepath)) {
+ $output = theme('image_style', array('style_name' => $this->options['image_style'], 'path' => $picture_filepath));
+ if ($this->options['link_photo_to_profile'] && user_access('access user profiles')) {
+ $uid = $this->get_value($values, 'uid');
+ $output = l($output, "user/$uid", array('html' => TRUE));
+ }
+ }
+ else {
+ $output = '';
+ }
+ }
+ else {
+ // Fake an account object.
+ $account = new stdClass();
+ if ($this->options['link_photo_to_profile']) {
+ // Prevent template_preprocess_user_picture from adding a link
+ // by not setting the uid.
+ $account->uid = $this->get_value($values, 'uid');
+ }
+ $account->name = $this->get_value($values, 'name');
+ $account->mail = $this->get_value($values, 'mail');
+ $account->picture = $this->get_value($values);
+ $output = theme('user_picture', array('account' => $account));
+ }
+
+ return $output;
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_field_user_roles.inc b/sites/all/modules/views/modules/user/views_handler_field_user_roles.inc
new file mode 100644
index 000000000..e6571cdbd
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_field_user_roles.inc
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_field_user_roles.
+ */
+
+/**
+ * Field handler to provide a list of roles.
+ *
+ * @ingroup views_field_handlers
+ */
+class views_handler_field_user_roles extends views_handler_field_prerender_list {
+ function construct() {
+ parent::construct();
+ $this->additional_fields['uid'] = array('table' => 'users', 'field' => 'uid');
+ }
+
+ function query() {
+ $this->add_additional_fields();
+ $this->field_alias = $this->aliases['uid'];
+ }
+
+ function pre_render(&$values) {
+ $uids = array();
+ $this->items = array();
+
+ foreach ($values as $result) {
+ $uids[] = $this->get_value($result, NULL, TRUE);
+ }
+
+ if ($uids) {
+ $result = db_query("SELECT u.uid, u.rid, r.name FROM {role} r INNER JOIN {users_roles} u ON u.rid = r.rid WHERE u.uid IN (:uids) ORDER BY r.name",
+ array(':uids' => $uids));
+ foreach ($result as $role) {
+ $this->items[$role->uid][$role->rid]['role'] = check_plain($role->name);
+ $this->items[$role->uid][$role->rid]['rid'] = $role->rid;
+ }
+ }
+ }
+
+ function render_item($count, $item) {
+ return $item['role'];
+ }
+
+ function document_self_tokens(&$tokens) {
+ $tokens['[' . $this->options['id'] . '-role' . ']'] = t('The name of the role.');
+ $tokens['[' . $this->options['id'] . '-rid' . ']'] = t('The role ID of the role.');
+ }
+
+ function add_self_tokens(&$tokens, $item) {
+ if (!empty($item['role'])) {
+ $tokens['[' . $this->options['id'] . '-role' . ']'] = $item['role'];
+ $tokens['[' . $this->options['id'] . '-rid' . ']'] = $item['rid'];
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_filter_user_current.inc b/sites/all/modules/views/modules/user/views_handler_filter_user_current.inc
new file mode 100644
index 000000000..5f8fe4c80
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_filter_user_current.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_user_current.
+ */
+
+/**
+ * Filter handler for the current user.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_user_current extends views_handler_filter_boolean_operator {
+ function construct() {
+ parent::construct();
+ $this->value_value = t('Is the logged in user');
+ }
+
+ function query() {
+ $this->ensure_my_table();
+
+ $field = $this->table_alias . '.' . $this->real_field . ' ';
+ $or = db_or();
+
+ if (empty($this->value)) {
+ $or->condition($field, '***CURRENT_USER***', '<>');
+ if ($this->accept_null) {
+ $or->isNull($field);
+ }
+ }
+ else {
+ $or->condition($field, '***CURRENT_USER***', '=');
+ }
+ $this->query->add_where($this->options['group'], $or);
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_filter_user_name.inc b/sites/all/modules/views/modules/user/views_handler_filter_user_name.inc
new file mode 100644
index 000000000..300607fe3
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_filter_user_name.inc
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_user_name.
+ */
+
+/**
+ * Filter handler for usernames.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_user_name extends views_handler_filter_in_operator {
+ var $always_multiple = TRUE;
+
+ function value_form(&$form, &$form_state) {
+ $values = array();
+ if ($this->value) {
+ $result = db_query("SELECT * FROM {users} u WHERE uid IN (:uids)", array(':uids' => $this->value));
+ foreach ($result as $account) {
+ if ($account->uid) {
+ $values[] = $account->name;
+ }
+ else {
+ $values[] = 'Anonymous'; // Intentionally NOT translated.
+ }
+ }
+ }
+
+ sort($values);
+ $default_value = implode(', ', $values);
+ $form['value'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Usernames'),
+ '#description' => t('Enter a comma separated list of user names.'),
+ '#default_value' => $default_value,
+ '#autocomplete_path' => 'admin/views/ajax/autocomplete/user',
+ );
+
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$this->options['expose']['identifier']])) {
+ $form_state['input'][$this->options['expose']['identifier']] = $default_value;
+ }
+ }
+
+ function value_validate($form, &$form_state) {
+ $values = drupal_explode_tags($form_state['values']['options']['value']);
+ $uids = $this->validate_user_strings($form['value'], $values);
+
+ if ($uids) {
+ $form_state['values']['options']['value'] = $uids;
+ }
+ }
+
+ function accept_exposed_input($input) {
+ $rc = parent::accept_exposed_input($input);
+
+ if ($rc) {
+ // If we have previously validated input, override.
+ if (isset($this->validated_exposed_input)) {
+ $this->value = $this->validated_exposed_input;
+ }
+ }
+
+ return $rc;
+ }
+
+ function exposed_validate(&$form, &$form_state) {
+ if (empty($this->options['exposed'])) {
+ return;
+ }
+
+ if (empty($this->options['expose']['identifier'])) {
+ return;
+ }
+
+ $identifier = $this->options['expose']['identifier'];
+ $input = $form_state['values'][$identifier];
+
+ if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) {
+ $this->operator = $this->options['group_info']['group_items'][$input]['operator'];
+ $input = $this->options['group_info']['group_items'][$input]['value'];
+ }
+
+ $values = drupal_explode_tags($input);
+
+ if (!$this->options['is_grouped'] || ($this->options['is_grouped'] && ($input != 'All'))) {
+ $uids = $this->validate_user_strings($form[$identifier], $values);
+ }
+ else {
+ $uids = FALSE;
+ }
+
+ if ($uids) {
+ $this->validated_exposed_input = $uids;
+ }
+ }
+
+ /**
+ * Validate the user string. Since this can come from either the form
+ * or the exposed filter, this is abstracted out a bit so it can
+ * handle the multiple input sources.
+ */
+ function validate_user_strings(&$form, $values) {
+ $uids = array();
+ $placeholders = array();
+ $args = array();
+ $results = array();
+ foreach ($values as $value) {
+ if (strtolower($value) == 'anonymous') {
+ $uids[] = 0;
+ }
+ else {
+ $missing[strtolower($value)] = TRUE;
+ $args[] = $value;
+ $placeholders[] = "'%s'";
+ }
+ }
+
+ if (!$args) {
+ return $uids;
+ }
+
+ $result = db_query("SELECT * FROM {users} WHERE name IN (:names)", array(':names' => $args));
+ foreach ($result as $account) {
+ unset($missing[strtolower($account->name)]);
+ $uids[] = $account->uid;
+ }
+
+ if ($missing) {
+ form_error($form, format_plural(count($missing), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', array_keys($missing)))));
+ }
+
+ return $uids;
+ }
+
+ function value_submit($form, &$form_state) {
+ // prevent array filter from removing our anonymous user.
+ }
+
+ // Override to do nothing.
+ function get_value_options() { }
+
+ function admin_summary() {
+ // set up $this->value_options for the parent summary
+ $this->value_options = array();
+
+ if ($this->value) {
+ $result = db_query("SELECT * FROM {users} u WHERE uid IN (:uids)", array(':uids' => $this->value));
+
+ foreach ($result as $account) {
+ if ($account->uid) {
+ $this->value_options[$account->uid] = $account->name;
+ }
+ else {
+ $this->value_options[$account->uid] = 'Anonymous'; // Intentionally NOT translated.
+ }
+ }
+ }
+
+ return parent::admin_summary();
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_filter_user_permissions.inc b/sites/all/modules/views/modules/user/views_handler_filter_user_permissions.inc
new file mode 100644
index 000000000..f999045d0
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_filter_user_permissions.inc
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_user_permissions.
+ */
+
+/**
+ * Filter handler for user roles.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_user_permissions extends views_handler_filter_many_to_one {
+ function get_value_options() {
+ $module_info = system_get_info('module');
+
+ // Get a list of all the modules implementing a hook_permission() and sort by
+ // display name.
+ $modules = array();
+ foreach (module_implements('permission') as $module) {
+ $modules[$module] = $module_info[$module]['name'];
+ }
+ asort($modules);
+
+ $this->value_options = array();
+ foreach ($modules as $module => $display_name) {
+ if ($permissions = module_invoke($module, 'permission')) {
+ foreach ($permissions as $perm => $perm_item) {
+ // @todo: group by module but views_handler_filter_many_to_one does not support this.
+ $this->value_options[$perm] = check_plain(strip_tags($perm_item['title']));
+ }
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_handler_filter_user_roles.inc b/sites/all/modules/views/modules/user/views_handler_filter_user_roles.inc
new file mode 100644
index 000000000..ab9b8a2f0
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_handler_filter_user_roles.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_handler_filter_user_roles.
+ */
+
+/**
+ * Filter handler for user roles.
+ *
+ * @ingroup views_filter_handlers
+ */
+class views_handler_filter_user_roles extends views_handler_filter_many_to_one {
+ function get_value_options() {
+ $this->value_options = user_roles(TRUE);
+ unset($this->value_options[DRUPAL_AUTHENTICATED_RID]);
+ }
+
+ /**
+ * Override empty and not empty operator labels to be clearer for user roles.
+ */
+ function operators() {
+ $operators = parent::operators();
+ $operators['empty']['title'] = t("Only has the 'authenticated user' role");
+ $operators['not empty']['title'] = t("Has roles in addition to 'authenticated user'");
+ return $operators;
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_plugin_argument_default_current_user.inc b/sites/all/modules/views/modules/user/views_plugin_argument_default_current_user.inc
new file mode 100644
index 000000000..e11c70223
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_plugin_argument_default_current_user.inc
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Contains the current user argument default plugin.
+ */
+
+/**
+ * Default argument plugin to extract the global $user
+ *
+ * This plugin actually has no options so it odes not need to do a great deal.
+ */
+class views_plugin_argument_default_current_user extends views_plugin_argument_default {
+ function get_argument() {
+ global $user;
+ return $user->uid;
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_plugin_argument_default_user.inc b/sites/all/modules/views/modules/user/views_plugin_argument_default_user.inc
new file mode 100644
index 000000000..bb104296c
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_plugin_argument_default_user.inc
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains the user from URL argument default plugin.
+ */
+
+/**
+ * Default argument plugin to extract a user via menu_get_object.
+ */
+class views_plugin_argument_default_user extends views_plugin_argument_default {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['user'] = array('default' => '', 'bool' => TRUE, 'translatable' => FALSE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['user'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Also look for a node and use the node author'),
+ '#default_value' => $this->options['user'],
+ );
+ }
+
+ function convert_options(&$options) {
+ if (!isset($options['user']) && isset($this->argument->options['default_argument_user'])) {
+ $options['user'] = $this->argument->options['default_argument_user'];
+ }
+ }
+
+ function get_argument() {
+ foreach (range(1, 3) as $i) {
+ $user = menu_get_object('user', $i);
+ if (!empty($user)) {
+ return $user->uid;
+ }
+ }
+
+ foreach (range(1, 3) as $i) {
+ $user = menu_get_object('user_uid_optional', $i);
+ if (!empty($user)) {
+ return $user->uid;
+ }
+ }
+
+ if (!empty($this->options['user'])) {
+ foreach (range(1, 3) as $i) {
+ $node = menu_get_object('node', $i);
+ if (!empty($node)) {
+ return $node->uid;
+ }
+ }
+ }
+
+ if (arg(0) == 'user' && is_numeric(arg(1))) {
+ return arg(1);
+ }
+
+ if (!empty($this->options['user'])) {
+ if (arg(0) == 'node' && is_numeric(arg(1))) {
+ $node = node_load(arg(1));
+ if ($node) {
+ return $node->uid;
+ }
+ }
+ }
+
+ // If the current page is a view that takes uid as an argument, return the uid.
+ $view = views_get_page_view();
+
+ if ($view && isset($view->argument['uid'])) {
+ return $view->argument['uid']->argument;
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_plugin_argument_validate_user.inc b/sites/all/modules/views/modules/user/views_plugin_argument_validate_user.inc
new file mode 100644
index 000000000..b72709445
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_plugin_argument_validate_user.inc
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_argument_validate_user.
+ */
+
+/**
+ * Validate whether an argument is a valid user.
+ *
+ * This supports either numeric arguments (UID) or strings (username) and
+ * converts either one into the user's UID. This validator also sets the
+ * argument's title to the username.
+ */
+class views_plugin_argument_validate_user extends views_plugin_argument_validate {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['type'] = array('default' => 'uid');
+ $options['restrict_roles'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['roles'] = array('default' => array());
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Type of user filter value to allow'),
+ '#options' => array(
+ 'uid' => t('Only allow numeric UIDs'),
+ 'name' => t('Only allow string usernames'),
+ 'either' => t('Allow both numeric UIDs and string usernames'),
+ ),
+ '#default_value' => $this->options['type'],
+ );
+
+ $form['restrict_roles'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Restrict user based on role'),
+ '#default_value' => $this->options['restrict_roles'],
+ );
+
+ $form['roles'] = array(
+ '#type' => 'checkboxes',
+ '#prefix' => '<div id="edit-options-validate-options-user-roles-wrapper">',
+ '#suffix' => '</div>',
+ '#title' => t('Restrict to the selected roles'),
+ '#options' => array_map('check_plain', user_roles(TRUE)),
+ '#default_value' => $this->options['roles'],
+ '#description' => t('If no roles are selected, users from any role will be allowed.'),
+ '#dependency' => array(
+ 'edit-options-validate-options-user-restrict-roles' => array(1),
+ ),
+ );
+ }
+
+ function options_submit(&$form, &$form_state, &$options = array()) {
+ // filter trash out of the options so we don't store giant unnecessary arrays
+ $options['roles'] = array_filter($options['roles']);
+ }
+
+ function convert_options(&$options) {
+ if (!isset($options['type']) && isset($this->argument->options['validate_user_argument_type'])) {
+ $options['type'] = $this->argument->options['validate_user_argument_type'];
+ $options['restrict_roles'] = $this->argument->options['validate_user_restrict_roles'];
+ $options['roles'] = $this->argument->options['validate_user_roles'];
+ }
+ }
+
+ function validate_argument($argument) {
+ $type = $this->options['type'];
+ // is_numeric() can return false positives, so we ensure it's an integer.
+ // However, is_integer() will always fail, since $argument is a string.
+ if (is_numeric($argument) && $argument == (int)$argument) {
+ if ($type == 'uid' || $type == 'either') {
+ if ($argument == $GLOBALS['user']->uid) {
+ // If you assign an object to a variable in PHP, the variable
+ // automatically acts as a reference, not a copy, so we use
+ // clone to ensure that we don't actually mess with the
+ // real global $user object.
+ $account = clone $GLOBALS['user'];
+ }
+ $where = 'uid = :argument';
+ }
+ }
+ else {
+ if ($type == 'name' || $type == 'either') {
+ $name = !empty($GLOBALS['user']->name) ? $GLOBALS['user']->name : variable_get('anonymous', t('Anonymous'));
+ if ($argument == $name) {
+ $account = clone $GLOBALS['user'];
+ }
+ $where = "name = :argument";
+ }
+ }
+
+ // If we don't have a WHERE clause, the argument is invalid.
+ if (empty($where)) {
+ return FALSE;
+ }
+
+ if (!isset($account)) {
+ $query = "SELECT uid, name FROM {users} WHERE $where";
+ $account = db_query($query, array(':argument' => $argument))->fetchObject();
+ }
+ if (empty($account)) {
+ // User not found.
+ return FALSE;
+ }
+
+ // See if we're filtering users based on roles.
+ if (!empty($this->options['restrict_roles']) && !empty($this->options['roles'])) {
+ $roles = $this->options['roles'];
+ $account->roles = array();
+ $account->roles[] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
+ $result = db_query('SELECT rid FROM {users_roles} WHERE uid = :uid', array(':uid' => $account->uid));
+ foreach ($result as $role) {
+ $account->roles[] = $role->rid;
+ }
+ if (!(bool) array_intersect($account->roles, $roles)) {
+ return FALSE;
+ }
+ }
+
+ $this->argument->argument = $account->uid;
+ $this->argument->validated_title = check_plain(format_username($account));
+ return TRUE;
+ }
+
+ function process_summary_arguments(&$args) {
+ // If the validation says the input is an username, we should reverse the
+ // argument so it works for example for generation summary urls.
+ $uids_arg_keys = array_flip($args);
+ if ($this->options['type'] == 'name') {
+ $users = user_load_multiple($args);
+ foreach ($users as $uid => $account) {
+ $args[$uids_arg_keys[$uid]] = $account->name;
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/modules/user/views_plugin_row_user_view.inc b/sites/all/modules/views/modules/user/views_plugin_row_user_view.inc
new file mode 100644
index 000000000..b48f4596e
--- /dev/null
+++ b/sites/all/modules/views/modules/user/views_plugin_row_user_view.inc
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains the user view row plugin.
+ */
+
+/**
+ * A row plugin which renders a user via user_view.
+ *
+ * @ingroup views_row_plugins
+ */
+class views_plugin_row_user_view extends views_plugin_row {
+ var $base_table = 'users';
+ var $base_field = 'uid';
+
+ // Store the users to be used for pre_render.
+ var $users = array();
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['view_mode'] = array('default' => 'full');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $options = $this->options_form_summary_options();
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $this->options['view_mode'],
+ );
+ $form['help']['#markup'] = t("Display the user with standard user view. It might be necessary to add a user-profile.tpl.php in your themes template folder, because the default <a href=\"@user-profile-api-link\">user-profile</a>e template don't show the username per default.", array('@user-profile-api-link' => url('http://api.drupal.org/api/drupal/modules--user--user-profile.tpl.php/7')));
+ }
+
+
+ /**
+ * Return the main options, which are shown in the summary title.
+ */
+ function options_form_summary_options() {
+ $entity_info = entity_get_info('user');
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+ if (empty($options)) {
+ $options = array(
+ 'full' => t('User account')
+ );
+ }
+
+ return $options;
+ }
+
+ function summary_title() {
+ $options = $this->options_form_summary_options();
+ return check_plain($options[$this->options['view_mode']]);
+ }
+
+ function pre_render($values) {
+ $uids = array();
+ foreach ($values as $row) {
+ $uids[] = $row->{$this->field_alias};
+ }
+ $this->users = user_load_multiple($uids);
+ }
+
+ function render($row) {
+ $account = $this->users[$row->{$this->field_alias}];
+ $account->view = $this->view;
+ $build = user_view($account, $this->options['view_mode']);
+
+ return drupal_render($build);
+ }
+}
diff --git a/sites/all/modules/views/modules/views.views.inc b/sites/all/modules/views/modules/views.views.inc
new file mode 100644
index 000000000..7c3f4db92
--- /dev/null
+++ b/sites/all/modules/views/modules/views.views.inc
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Provide views data and handlers that aren't tied to any other module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ */
+function views_views_data() {
+ $data['views']['table']['group'] = t('Global');
+ $data['views']['table']['join'] = array(
+ // #global is a special flag which let's a table appear all the time.
+ '#global' => array(),
+ );
+
+ $data['views']['random'] = array(
+ 'title' => t('Random'),
+ 'help' => t('Randomize the display order.'),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_random',
+ ),
+ );
+
+ $data['views']['null'] = array(
+ 'title' => t('Null'),
+ 'help' => t('Allow a contextual filter value to be ignored. The query will not be altered by this contextual filter value. Can be used when contextual filter values come from the URL, and a part of the URL needs to be ignored.'),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_null',
+ ),
+ );
+
+ $data['views']['nothing'] = array(
+ 'title' => t('Custom text'),
+ 'help' => t('Provide custom text or link.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_custom',
+ ),
+ );
+
+ $data['views']['counter'] = array(
+ 'title' => t('View result counter'),
+ 'help' => t('Displays the actual position of the view result'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_counter',
+ ),
+ );
+
+ $data['views']['area'] = array(
+ 'title' => t('Text area'),
+ 'help' => t('Provide markup text for the area.'),
+ 'area' => array(
+ 'handler' => 'views_handler_area_text',
+ ),
+ );
+
+ $data['views']['area_text_custom'] = array(
+ 'title' => t('Unfiltered text'),
+ 'help' => t('Add unrestricted, custom text or markup. This is similar to the custom text field.'),
+ 'area' => array(
+ 'handler' => 'views_handler_area_text_custom',
+ ),
+ );
+
+ $data['views']['view'] = array(
+ 'title' => t('View area'),
+ 'help' => t('Insert a view inside an area.'),
+ 'area' => array(
+ 'handler' => 'views_handler_area_view',
+ ),
+ );
+
+ $data['views']['result'] = array(
+ 'title' => t('Result summary'),
+ 'help' => t('Shows result summary, for example the items per page.'),
+ 'area' => array(
+ 'handler' => 'views_handler_area_result',
+ ),
+ );
+
+ $data['views']['messages'] = array(
+ 'title' => t('Messages'),
+ 'help' => t('Displays messages in the area.'),
+ 'area' => array(
+ 'handler' => 'views_handler_area_messages',
+ ),
+ );
+
+ if (module_exists('contextual')) {
+ $data['views']['contextual_links'] = array(
+ 'title' => t('Contextual Links'),
+ 'help' => t('Display fields in a contextual links menu.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_contextual_links',
+ ),
+ );
+ }
+
+ $data['views']['combine'] = array(
+ 'title' => t('Combine fields filter'),
+ 'help' => t('Combine two fields together and search by them.'),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_combine',
+ ),
+ );
+
+ if (module_invoke('ctools', 'api_version', '1.7.1')) {
+ $data['views']['expression'] = array(
+ 'title' => t('Math expression'),
+ 'help' => t('Evaluates a mathematical expression and displays it.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_math',
+ 'float' => TRUE,
+ ),
+ );
+ }
+
+ $data['views']['fields_compare'] = array(
+ 'title' => t('Fields comparison'),
+ 'help' => t('Compare database fields against eachother.'),
+ 'filter' => array(
+ 'help' => t('Use fields comparison to filter the result of the view.'),
+ 'handler' => 'views_handler_filter_fields_compare',
+ )
+ );
+
+ return $data;
+}
diff --git a/sites/all/modules/views/plugins/export_ui/views_ui.class.php b/sites/all/modules/views/plugins/export_ui/views_ui.class.php
new file mode 100644
index 000000000..9d8013828
--- /dev/null
+++ b/sites/all/modules/views/plugins/export_ui/views_ui.class.php
@@ -0,0 +1,447 @@
+<?php
+
+/**
+ * @file
+ * Contains the CTools Export UI integration code.
+ *
+ * Note that this is only a partial integration.
+ */
+
+/**
+ * CTools Export UI class handler for Views UI.
+ */
+class views_ui extends ctools_export_ui {
+
+ function init($plugin) {
+ // We modify the plugin info here so that we take the defaults and
+ // twiddle, rather than completely override them.
+
+ // Reset the edit path to match what we're really using.
+ $plugin['menu']['items']['edit']['path'] = 'view/%ctools_export_ui/edit';
+ $plugin['menu']['items']['clone']['path'] = 'view/%ctools_export_ui/clone';
+ $plugin['menu']['items']['clone']['type'] = MENU_VISIBLE_IN_BREADCRUMB;
+ $plugin['menu']['items']['export']['path'] = 'view/%ctools_export_ui/export';
+ $plugin['menu']['items']['export']['type'] = MENU_VISIBLE_IN_BREADCRUMB;
+ $plugin['menu']['items']['enable']['path'] = 'view/%ctools_export_ui/enable';
+ $plugin['menu']['items']['disable']['path'] = 'view/%ctools_export_ui/disable';
+ $plugin['menu']['items']['delete']['path'] = 'view/%ctools_export_ui/delete';
+ $plugin['menu']['items']['delete']['type'] = MENU_VISIBLE_IN_BREADCRUMB;
+ $plugin['menu']['items']['revert']['path'] = 'view/%ctools_export_ui/revert';
+ $plugin['menu']['items']['revert']['type'] = MENU_VISIBLE_IN_BREADCRUMB;
+
+ $prefix_count = count(explode('/', $plugin['menu']['menu prefix']));
+ $plugin['menu']['items']['add-template'] = array(
+ 'path' => 'template/%/add',
+ 'title' => 'Add from template',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'add_template', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'add_template', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+
+ return parent::init($plugin);
+ }
+
+ function hook_menu(&$items) {
+ // We are using our own 'edit' still, rather than having edit on this
+ // object (maybe in the future) so unset the edit callbacks:
+
+ // Store this so we can put them back as sometimes they're needed
+ // again laster:
+ $stored_items = $this->plugin['menu']['items'];
+ // We leave these to make sure the operations still exist in the plugin so
+ // that the path finder.
+ unset($this->plugin['menu']['items']['edit']);
+ unset($this->plugin['menu']['items']['add']);
+ unset($this->plugin['menu']['items']['import']);
+ unset($this->plugin['menu']['items']['edit callback']);
+
+ parent::hook_menu($items);
+
+ $this->plugin['menu']['items'] = $stored_items;
+ }
+
+ function load_item($item_name) {
+ return views_ui_cache_load($item_name);
+ }
+
+ function list_form(&$form, &$form_state) {
+ $row_class = 'container-inline';
+ if (!variable_get('views_ui_show_listing_filters', FALSE)) {
+ $row_class .= " element-invisible";
+ }
+
+ views_include('admin');
+
+ parent::list_form($form, $form_state);
+
+ // ctools only has two rows. We want four.
+ // That's why we create our own structure.
+ $form['bottom row']['submit']['#attributes']['class'][] = 'js-hide';
+ $form['first row'] = array(
+ '#prefix' => '<div class="' . $row_class . ' ctools-export-ui-row ctools-export-ui-first-row clearfix">',
+ '#suffix' => '</div>',
+ 'search' => $form['top row']['search'],
+ 'submit' => $form['bottom row']['submit'],
+ 'reset' => $form['bottom row']['reset'],
+ );
+ $form['second row'] = array(
+ '#prefix' => '<div class="' . $row_class . ' ctools-export-ui-row ctools-export-ui-second-row clearfix">',
+ '#suffix' => '</div>',
+ 'storage' => $form['top row']['storage'],
+ 'disabled' => $form['top row']['disabled'],
+ );
+ $form['third row'] = array(
+ '#prefix' => '<div class="' . $row_class . ' ctools-export-ui-row ctools-export-ui-third-row clearfix element-hidden">',
+ '#suffix' => '</div>',
+ 'order' => $form['bottom row']['order'],
+ 'sort' => $form['bottom row']['sort'],
+ );
+ unset($form['top row']);
+ unset($form['bottom row']);
+
+ // Modify the look and contents of existing form elements.
+ $form['second row']['storage']['#title'] = '';
+ $form['second row']['storage']['#options'] = array(
+ 'all' => t('All storage'),
+ t('Normal') => t('In database'),
+ t('Default') => t('In code'),
+ t('Overridden') => t('Database overriding code'),
+ );
+ $form['second row']['disabled']['#title'] = '';
+ $form['second row']['disabled']['#options']['all'] = t('All status');
+ $form['third row']['sort']['#title'] = '';
+
+ // And finally, add our own.
+ $this->bases = array();
+ foreach (views_fetch_base_tables() as $table => $info) {
+ $this->bases[$table] = $info['title'];
+ }
+
+ $form['second row']['base'] = array(
+ '#type' => 'select',
+ '#options' => array_merge(array('all' => t('All types')), $this->bases),
+ '#default_value' => 'all',
+ '#weight' => -1,
+ );
+
+ $tags = array();
+ if (isset($form_state['object']->items)) {
+ foreach ($form_state['object']->items as $name => $view) {
+ if (!empty($view->tag)) {
+ $view_tags = drupal_explode_tags($view->tag);
+ foreach ($view_tags as $tag) {
+ $tags[$tag] = $tag;
+ }
+ }
+ }
+ }
+ asort($tags);
+
+ $form['second row']['tag'] = array(
+ '#type' => 'select',
+ '#title' => t('Filter'),
+ '#options' => array_merge(array('all' => t('All tags')), array('none' => t('No tags')), $tags),
+ '#default_value' => 'all',
+ '#weight' => -9,
+ );
+
+ $displays = array();
+ foreach (views_fetch_plugin_data('display') as $id => $info) {
+ if (!empty($info['admin'])) {
+ $displays[$id] = $info['admin'];
+ }
+ }
+ asort($displays);
+
+ $form['second row']['display'] = array(
+ '#type' => 'select',
+ '#options' => array_merge(array('all' => t('All displays')), $displays),
+ '#default_value' => 'all',
+ '#weight' => -1,
+ );
+ }
+
+ function list_filter($form_state, $view) {
+ // Don't filter by tags if all is set up.
+ if ($form_state['values']['tag'] != 'all') {
+ // If none is selected check whether the view has a tag.
+ if ($form_state['values']['tag'] == 'none') {
+ return !empty($view->tag);
+ }
+ else {
+ // Check whether the tag can be found in the views tag.
+ return strpos($view->tag, $form_state['values']['tag']) === FALSE;
+ }
+ }
+ if ($form_state['values']['base'] != 'all' && $form_state['values']['base'] != $view->base_table) {
+ return TRUE;
+ }
+
+ return parent::list_filter($form_state, $view);
+ }
+
+ function list_sort_options() {
+ return array(
+ 'disabled' => t('Enabled, name'),
+ 'name' => t('Name'),
+ 'path' => t('Path'),
+ 'tag' => t('Tag'),
+ 'storage' => t('Storage'),
+ );
+ }
+
+
+ function list_build_row($view, &$form_state, $operations) {
+ if (!empty($view->human_name)) {
+ $title = $view->human_name;
+ }
+ else {
+ $title = $view->get_title();
+ if (empty($title)) {
+ $title = $view->name;
+ }
+ }
+
+ $paths = _views_ui_get_paths($view);
+ $paths = implode(", ", $paths);
+
+ $base = !empty($this->bases[$view->base_table]) ? $this->bases[$view->base_table] : t('Broken');
+
+ $info = theme('views_ui_view_info', array('view' => $view, 'base' => $base));
+
+ // Reorder the operations so that enable is the default action for a templatic views
+ if (!empty($operations['enable'])) {
+ $operations = array('enable' => $operations['enable']) + $operations;
+ }
+
+ // Set up sorting
+ switch ($form_state['values']['order']) {
+ case 'disabled':
+ $this->sorts[$view->name] = strtolower(empty($view->disabled) . $title);
+ break;
+ case 'name':
+ $this->sorts[$view->name] = strtolower($title);
+ break;
+ case 'path':
+ $this->sorts[$view->name] = strtolower($paths);
+ break;
+ case 'tag':
+ $this->sorts[$view->name] = strtolower($view->tag);
+ break;
+ case 'storage':
+ $this->sorts[$view->name] = strtolower($view->type . $title);
+ break;
+ }
+
+ $ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
+
+ $this->rows[$view->name] = array(
+ 'data' => array(
+ array('data' => $info, 'class' => array('views-ui-name')),
+ array('data' => check_plain($view->description), 'class' => array('views-ui-description')),
+ array('data' => check_plain($view->tag), 'class' => array('views-ui-tag')),
+ array('data' => $paths, 'class' => array('views-ui-path')),
+ array('data' => $ops, 'class' => array('views-ui-operations')),
+ ),
+ 'title' => t('Machine name: ') . check_plain($view->name),
+ 'class' => array(!empty($view->disabled) ? 'ctools-export-ui-disabled' : 'ctools-export-ui-enabled'),
+ );
+ }
+
+ function list_render(&$form_state) {
+ views_include('admin');
+ views_ui_add_admin_css();
+ if (empty($_REQUEST['js'])) {
+ views_ui_check_advanced_help();
+ }
+ drupal_add_library('system', 'jquery.bbq');
+ views_add_js('views-list');
+
+ $this->active = $form_state['values']['order'];
+ $this->order = $form_state['values']['sort'];
+
+ $query = tablesort_get_query_parameters();
+
+ $header = array(
+ $this->tablesort_link(t('View name'), 'name', 'views-ui-name'),
+ array('data' => t('Description'), 'class' => array('views-ui-description')),
+ $this->tablesort_link(t('Tag'), 'tag', 'views-ui-tag'),
+ $this->tablesort_link(t('Path'), 'path', 'views-ui-path'),
+ array('data' => t('Operations'), 'class' => array('views-ui-operations')),
+ );
+
+ $table = array(
+ 'header' => $header,
+ 'rows' => $this->rows,
+ 'empty' => t('No views match the search criteria.'),
+ 'attributes' => array('id' => 'ctools-export-ui-list-items'),
+ );
+ return theme('table', $table);
+ }
+
+ function tablesort_link($label, $field, $class) {
+ $title = t('sort by @s', array('@s' => $label));
+ $initial = 'asc';
+
+ if ($this->active == $field) {
+ $initial = ($this->order == 'asc') ? 'desc' : 'asc';
+ $label .= theme('tablesort_indicator', array('style' => $initial));
+ }
+
+ $query['order'] = $field;
+ $query['sort'] = $initial;
+ $link_options = array(
+ 'html' => TRUE,
+ 'attributes' => array('title' => $title),
+ 'query' => $query,
+ );
+ $link = l($label, $_GET['q'], $link_options);
+ if ($this->active == $field) {
+ $class .= ' active';
+ }
+
+ return array('data' => $link, 'class' => $class);
+ }
+
+ function clone_page($js, $input, $item, $step = NULL) {
+ drupal_set_title($this->get_page_title('clone', $item));
+
+ $name = $item->{$this->plugin['export']['key']};
+
+ $form_state = array(
+ 'plugin' => $this->plugin,
+ 'object' => &$this,
+ 'ajax' => $js,
+ 'item' => $item,
+ 'op' => 'add',
+ 'form type' => 'clone',
+ 'original name' => $name,
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ 'step' => $step,
+ // Store these in case additional args are needed.
+ 'function args' => func_get_args(),
+ );
+
+ $output = drupal_build_form('views_ui_clone_form', $form_state);
+ if (!empty($form_state['executed'])) {
+ $item->name = $form_state['values']['name'];
+ $item->human_name = $form_state['values']['human_name'];
+ $item->vid = NULL;
+ views_ui_cache_set($item);
+
+ drupal_goto(ctools_export_ui_plugin_menu_path($this->plugin, 'edit', $item->name));
+ }
+
+ return $output;
+ }
+
+ function add_template_page($js, $input, $name, $step = NULL) {
+ $templates = views_get_all_templates();
+
+ if (empty($templates[$name])) {
+ return MENU_NOT_FOUND;
+ }
+
+ $template = $templates[$name];
+
+ // The template description probably describes the template, not the
+ // view that will be created from it, but users aren't that likely to
+ // touch it.
+ if (!empty($template->description)) {
+ unset($template->description);
+ }
+
+ $template->is_template = TRUE;
+ $template->type = t('Default');
+
+ $output = $this->clone_page($js, $input, $template, $step);
+ drupal_set_title(t('Create view from template @template', array('@template' => $template->get_human_name())));
+ return $output;
+ }
+
+ function set_item_state($state, $js, $input, $item) {
+ ctools_export_set_object_status($item, $state);
+ menu_rebuild();
+
+ if (!$js) {
+ drupal_goto(ctools_export_ui_plugin_base_path($this->plugin));
+ }
+ else {
+ return $this->list_page($js, $input);
+ }
+ }
+
+ function list_page($js, $input) {
+ // Remove filters values from session if filters are hidden.
+ if (!variable_get('views_ui_show_listing_filters', FALSE) && isset($_SESSION['ctools_export_ui'][$this->plugin['name']])) {
+ unset($_SESSION['ctools_export_ui'][$this->plugin['name']]);
+ }
+
+ // wrap output in a div for CSS
+ $output = parent::list_page($js, $input);
+ if (is_string($output)) {
+ $output = '<div id="views-ui-list-page">' . $output . '</div>';
+ return $output;
+ }
+ }
+}
+
+/**
+ * Form callback to edit an exportable item using the wizard
+ *
+ * This simply loads the object defined in the plugin and hands it off.
+ */
+function views_ui_clone_form($form, &$form_state) {
+ $counter = 1;
+
+ if (!isset($form_state['item'])) {
+ $view = views_get_view($form_state['original name']);
+ }
+ else {
+ $view = $form_state['item'];
+ }
+ do {
+ if (empty($form_state['item']->is_template)) {
+ $name = format_plural($counter, 'Clone of', 'Clone @count of') . ' ' . $view->get_human_name();
+ }
+ else {
+ $name = $view->get_human_name();
+ if ($counter > 1) {
+ $name .= ' ' . $counter;
+ }
+ }
+ $counter++;
+ $machine_name = preg_replace('/[^a-z0-9_]+/', '_', drupal_strtolower($name));
+ } while (ctools_export_crud_load($form_state['plugin']['schema'], $machine_name));
+
+ $form['human_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('View name'),
+ '#default_value' => $name,
+ '#size' => 32,
+ '#maxlength' => 255,
+ );
+
+ $form['name'] = array(
+ '#title' => t('View name'),
+ '#type' => 'machine_name',
+ '#required' => TRUE,
+ '#maxlength' => 128,
+ '#size' => 128,
+ '#machine_name' => array(
+ 'exists' => 'ctools_export_ui_edit_name_exists',
+ 'source' => array('human_name'),
+ ),
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Continue'),
+ );
+
+ return $form;
+}
diff --git a/sites/all/modules/views/plugins/export_ui/views_ui.inc b/sites/all/modules/views/plugins/export_ui/views_ui.inc
new file mode 100644
index 000000000..042fc79cd
--- /dev/null
+++ b/sites/all/modules/views/plugins/export_ui/views_ui.inc
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Plugin definition for CTools Export UI integration.
+ */
+
+$plugin = array(
+ 'schema' => 'views_view',
+ 'access' => 'administer views',
+
+ 'menu' => array(
+ 'menu item' => 'views',
+ 'menu title' => 'Views',
+ 'menu description' => 'Manage customized lists of content.',
+ ),
+
+ 'title singular' => t('view'),
+ 'title singular proper' => t('View'),
+ 'title plural' => t('views'),
+ 'title plural proper' => t('Views'),
+
+ 'handler' => 'views_ui',
+
+ 'strings' => array(
+ 'confirmation' => array(
+ 'revert' => array(
+ 'information' => t('This action will permanently remove any customizations made to this view.'),
+ 'success' => t('The view has been reverted.'),
+ ),
+ 'delete' => array(
+ 'information' => t('This action will permanently remove the view from your database.'),
+ 'success' => t('The view has been deleted.'),
+ ),
+ ),
+ ),
+);
diff --git a/sites/all/modules/views/plugins/views_plugin_access.inc b/sites/all/modules/views/plugins/views_plugin_access.inc
new file mode 100644
index 000000000..7f80d9b7c
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_access.inc
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_access.
+ */
+
+/**
+ * @defgroup views_access_plugins Views access plugins
+ * @{
+ * @todo.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * The base plugin to handle access control.
+ */
+class views_plugin_access extends views_plugin {
+ /**
+ * Initialize the plugin.
+ *
+ * @param $view
+ * The view object.
+ * @param $display
+ * The display handler.
+ */
+ function init(&$view, &$display) {
+ $this->view = &$view;
+ $this->display = &$display;
+
+ if (is_object($display->handler)) {
+ $options = $display->handler->get_option('access');
+ // Overlay incoming options on top of defaults
+ $this->unpack_options($this->options, $options);
+ }
+ }
+
+ /**
+ * Retrieve the options when this is a new access
+ * control plugin
+ */
+ function option_definition() { return array(); }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) { }
+
+ /**
+ * Provide the default form form for validating options
+ */
+ function options_validate(&$form, &$form_state) { }
+
+ /**
+ * Provide the default form form for submitting options
+ */
+ function options_submit(&$form, &$form_state) { }
+
+ /**
+ * Return a string to display as the clickable title for the
+ * access control.
+ */
+ function summary_title() {
+ return t('Unknown');
+ }
+
+ /**
+ * Determine if the current user has access or not.
+ */
+ function access($account) {
+ // default to no access control.
+ return TRUE;
+ }
+
+ /**
+ * Determine the access callback and arguments.
+ *
+ * This information will be embedded in the menu in order to reduce
+ * performance hits during menu item access testing, which happens
+ * a lot.
+ *
+ * @return an array; the first item should be the function to call,
+ * and the second item should be an array of arguments. The first
+ * item may also be TRUE (bool only) which will indicate no
+ * access control.)
+ */
+ function get_access_callback() {
+ // default to no access control.
+ return TRUE;
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_access_none.inc b/sites/all/modules/views/plugins/views_plugin_access_none.inc
new file mode 100644
index 000000000..d69fe8e44
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_access_none.inc
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_access_none.
+ */
+
+/**
+ * Access plugin that provides no access control at all.
+ *
+ * @ingroup views_access_plugins
+ */
+class views_plugin_access_none extends views_plugin_access {
+ function summary_title() {
+ return t('Unrestricted');
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_access_perm.inc b/sites/all/modules/views/plugins/views_plugin_access_perm.inc
new file mode 100644
index 000000000..7279d7db7
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_access_perm.inc
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_access_perm.
+ */
+
+/**
+ * Access plugin that provides permission-based access control.
+ *
+ * @ingroup views_access_plugins
+ */
+class views_plugin_access_perm extends views_plugin_access {
+ function access($account) {
+ return views_check_perm($this->options['perm'], $account);
+ }
+
+ function get_access_callback() {
+ return array('views_check_perm', array($this->options['perm']));
+ }
+
+ function summary_title() {
+ $permissions = module_invoke_all('permission');
+ if (isset($permissions[$this->options['perm']])) {
+ return $permissions[$this->options['perm']]['title'];
+ }
+
+ return t($this->options['perm']);
+ }
+
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['perm'] = array('default' => 'access content');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $perms = array();
+ $module_info = system_get_info('module');
+
+ // Get list of permissions
+ foreach (module_implements('permission') as $module) {
+ $permissions = module_invoke($module, 'permission');
+ foreach ($permissions as $name => $perm) {
+ $perms[$module_info[$module]['name']][$name] = strip_tags($perm['title']);
+ }
+ }
+
+ ksort($perms);
+
+ $form['perm'] = array(
+ '#type' => 'select',
+ '#options' => $perms,
+ '#title' => t('Permission'),
+ '#default_value' => $this->options['perm'],
+ '#description' => t('Only users with the selected permission flag will be able to access this display. Note that users with "access all views" can see any view, regardless of other permissions.'),
+ );
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_access_role.inc b/sites/all/modules/views/plugins/views_plugin_access_role.inc
new file mode 100644
index 000000000..b06812eaf
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_access_role.inc
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_access_role.
+ */
+
+/**
+ * Access plugin that provides role-based access control.
+ *
+ * @ingroup views_access_plugins
+ */
+class views_plugin_access_role extends views_plugin_access {
+ function access($account) {
+ return views_check_roles(array_filter($this->options['role']), $account);
+ }
+
+ function get_access_callback() {
+ return array('views_check_roles', array(array_filter($this->options['role'])));
+ }
+
+ function summary_title() {
+ $count = count($this->options['role']);
+ if ($count < 1) {
+ return t('No role(s) selected');
+ }
+ elseif ($count > 1) {
+ return t('Multiple roles');
+ }
+ else {
+ $rids = views_ui_get_roles();
+ $rid = reset($this->options['role']);
+ return check_plain($rids[$rid]);
+ }
+ }
+
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['role'] = array('default' => array());
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['role'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Role'),
+ '#default_value' => $this->options['role'],
+ '#options' => array_map('check_plain', views_ui_get_roles()),
+ '#description' => t('Only the checked roles will be able to access this display. Note that users with "access all views" can see any view, regardless of role.'),
+ );
+ }
+
+ function options_validate(&$form, &$form_state) {
+ if (!array_filter($form_state['values']['access_options']['role'])) {
+ form_error($form['role'], t('You must select at least one role if type is "by role"'));
+ }
+ }
+
+ function options_submit(&$form, &$form_state) {
+ // I hate checkboxes.
+ $form_state['values']['access_options']['role'] = array_filter($form_state['values']['access_options']['role']);
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_argument_default.inc b/sites/all/modules/views/plugins/views_plugin_argument_default.inc
new file mode 100644
index 000000000..2b877300e
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_argument_default.inc
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_argument_default.
+ */
+
+/**
+ * @defgroup views_argument_default_plugins Views argument default plugins
+ * @{
+ * Allow specialized methods of filling in arguments when they aren't provided.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * The fixed argument default handler; also used as the base.
+ */
+class views_plugin_argument_default extends views_plugin {
+ /**
+ * Return the default argument.
+ *
+ * This needs to be overridden by every default argument handler to properly do what is needed.
+ */
+ function get_argument() { }
+
+ /**
+ * Initialize this plugin with the view and the argument
+ * it is linked to.
+ */
+ function init(&$view, &$argument, $options) {
+ $this->view = &$view;
+ $this->argument = &$argument;
+
+ $this->convert_options($options);
+ $this->unpack_options($this->options, $options);
+ }
+
+ /**
+ * Retrieve the options when this is a new access
+ * control plugin
+ */
+ function option_definition() { return array(); }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) { }
+
+ /**
+ * Provide the default form form for validating options
+ */
+ function options_validate(&$form, &$form_state) { }
+
+ /**
+ * Provide the default form form for submitting options
+ */
+ function options_submit(&$form, &$form_state, &$options = array()) { }
+
+ /**
+ * Determine if the administrator has the privileges to use this
+ * plugin
+ */
+ function access() { return TRUE; }
+
+ /**
+ * If we don't have access to the form but are showing it anyway, ensure that
+ * the form is safe and cannot be changed from user input.
+ *
+ * This is only called by child objects if specified in the options_form(),
+ * so it will not always be used.
+ */
+ function check_access(&$form, $option_name) {
+ if (!$this->access()) {
+ $form[$option_name]['#disabled'] = TRUE;
+ $form[$option_name]['#value'] = $form[$this->option_name]['#default_value'];
+ $form[$option_name]['#description'] .= ' <strong>' . t('Note: you do not have permission to modify this. If you change the default filter type, this setting will be lost and you will NOT be able to get it back.') . '</strong>';
+ }
+ }
+
+ /**
+ * Convert options from the older style.
+ *
+ * In Views 3, the method of storing default argument options has changed
+ * and each plugin now gets its own silo. This method can be used to
+ * move arguments from the old style to the new style. See
+ * views_plugin_argument_default_fixed for a good example of this method.
+ */
+ function convert_options(&$options) { }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_argument_default_fixed.inc b/sites/all/modules/views/plugins/views_plugin_argument_default_fixed.inc
new file mode 100644
index 000000000..38ede3487
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_argument_default_fixed.inc
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains the fixed argument default plugin.
+ */
+
+/**
+ * The fixed argument default handler.
+ *
+ * @ingroup views_argument_default_plugins
+ */
+class views_plugin_argument_default_fixed extends views_plugin_argument_default {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['argument'] = array('default' => '');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['argument'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Fixed value'),
+ '#default_value' => $this->options['argument'],
+ );
+ }
+
+ /**
+ * Return the default argument.
+ */
+ function get_argument() {
+ return $this->options['argument'];
+ }
+
+ function convert_options(&$options) {
+ if (!isset($options['argument']) && isset($this->argument->options['default_argument_fixed'])) {
+ $options['argument'] = $this->argument->options['default_argument_fixed'];
+ }
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_argument_default_php.inc b/sites/all/modules/views/plugins/views_plugin_argument_default_php.inc
new file mode 100644
index 000000000..c2fb14f56
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_argument_default_php.inc
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains the php code argument default plugin.
+ */
+
+/**
+ * Default argument plugin to provide a PHP code block.
+ *
+ * @ingroup views_argument_default_plugins
+ */
+class views_plugin_argument_default_php extends views_plugin_argument_default {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['code'] = array('default' => '');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['code'] = array(
+ '#type' => 'textarea',
+ '#title' => t('PHP contextual filter code'),
+ '#default_value' => $this->options['code'],
+ '#description' => t('Enter PHP code that returns a value to use for this filter. Do not use &lt;?php ?&gt;. You must return only a single value for just this filter. Some variables are available: the view object will be "$view". The argument handler will be "$argument", for example you may change the title used for substitutions for this argument by setting "argument->validated_title"".'),
+ );
+
+ // Only do this if using one simple standard form gadget
+ $this->check_access($form, 'code');
+ }
+
+ function convert_options(&$options) {
+ if (!isset($options['code']) && isset($this->argument->options['default_argument_php'])) {
+ $options['code'] = $this->argument->options['default_argument_php'];
+ }
+ }
+
+ /**
+ * Only let users with PHP block visibility permissions set/modify this
+ * default plugin.
+ */
+ function access() {
+ return user_access('use PHP for settings');
+ }
+
+ function get_argument() {
+ // set up variables to make it easier to reference during the argument.
+ $view = &$this->view;
+ $argument = &$this->argument;
+ ob_start();
+ $result = eval($this->options['code']);
+ ob_end_clean();
+ return $result;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_argument_default_raw.inc b/sites/all/modules/views/plugins/views_plugin_argument_default_raw.inc
new file mode 100644
index 000000000..385ca9126
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_argument_default_raw.inc
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains the raw value argument default plugin.
+ */
+
+/**
+ * Default argument plugin to use the raw value from the URL.
+ *
+ * @ingroup views_argument_default_plugins
+ */
+class views_plugin_argument_default_raw extends views_plugin_argument_default {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['index'] = array('default' => '');
+ $options['use_alias'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ // Using range(1, 10) will create an array keyed 0-9, which allows arg() to
+ // properly function since it is also zero-based.
+ $form['index'] = array(
+ '#type' => 'select',
+ '#title' => t('Path component'),
+ '#default_value' => $this->options['index'],
+ '#options' => range(1, 10),
+ '#description' => t('The numbering starts from 1, e.g. on the page admin/structure/types, the 3rd path component is "types".'),
+ );
+ $form['use_alias'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use path alias'),
+ '#default_value' => $this->options['use_alias'],
+ '#description' => t('Use path alias instead of internal path.'),
+ );
+ }
+
+ function get_argument() {
+ $path = NULL;
+ if ($this->options['use_alias']) {
+ $path = drupal_get_path_alias();
+ }
+ if ($arg = arg($this->options['index'], $path)) {
+ return $arg;
+ }
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_argument_validate.inc b/sites/all/modules/views/plugins/views_plugin_argument_validate.inc
new file mode 100644
index 000000000..350cb4395
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_argument_validate.inc
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains the base argument validator plugin.
+ */
+
+/**
+ * @defgroup views_argument_validate_plugins Views argument validate plugins
+ * @{
+ * Allow specialized methods of validating arguments.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * Base argument validator plugin to provide basic functionality.
+ */
+class views_plugin_argument_validate extends views_plugin {
+
+ /**
+ * Initialize this plugin with the view and the argument
+ * it is linked to.
+ */
+ function init(&$view, &$argument, $options) {
+ $this->view = &$view;
+ $this->argument = &$argument;
+
+ $this->convert_options($options);
+ $this->unpack_options($this->options, $options);
+ }
+
+ /**
+ * Retrieve the options when this is a new access
+ * control plugin
+ */
+ function option_definition() { return array(); }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) { }
+
+ /**
+ * Provide the default form form for validating options
+ */
+ function options_validate(&$form, &$form_state) { }
+
+ /**
+ * Provide the default form form for submitting options
+ */
+ function options_submit(&$form, &$form_state, &$options = array()) { }
+
+ /**
+ * Convert options from the older style.
+ *
+ * In Views 3, the method of storing default argument options has changed
+ * and each plugin now gets its own silo. This method can be used to
+ * move arguments from the old style to the new style. See
+ * views_plugin_argument_default_fixed for a good example of this method.
+ */
+ function convert_options(&$options) { }
+
+ /**
+ * Determine if the administrator has the privileges to use this plugin
+ */
+ function access() { return TRUE; }
+
+ /**
+ * If we don't have access to the form but are showing it anyway, ensure that
+ * the form is safe and cannot be changed from user input.
+ *
+ * This is only called by child objects if specified in the options_form(),
+ * so it will not always be used.
+ */
+ function check_access(&$form, $option_name) {
+ if (!$this->access()) {
+ $form[$option_name]['#disabled'] = TRUE;
+ $form[$option_name]['#value'] = $form[$this->option_name]['#default_value'];
+ $form[$option_name]['#description'] .= ' <strong>' . t('Note: you do not have permission to modify this. If you change the default filter type, this setting will be lost and you will NOT be able to get it back.') . '</strong>';
+ }
+ }
+
+ function validate_argument($arg) { return TRUE; }
+
+ /**
+ * Process the summary arguments for displaying.
+ *
+ * Some plugins alter the argument so it uses something else internally.
+ * For example the user validation set's the argument to the uid,
+ * for a faster query. But there are use cases where you want to use
+ * the old value again, for example the summary.
+ */
+ function process_summary_arguments(&$args) { }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_argument_validate_numeric.inc b/sites/all/modules/views/plugins/views_plugin_argument_validate_numeric.inc
new file mode 100644
index 000000000..049531bbf
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_argument_validate_numeric.inc
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains the numeric argument validator plugin.
+ */
+
+/**
+ * Validate whether an argument is numeric or not.
+ *
+ * @ingroup views_argument_validate_plugins
+ */
+class views_plugin_argument_validate_numeric extends views_plugin_argument_validate {
+ function validate_argument($argument) {
+ return is_numeric($argument);
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_argument_validate_php.inc b/sites/all/modules/views/plugins/views_plugin_argument_validate_php.inc
new file mode 100644
index 000000000..83b22b4a9
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_argument_validate_php.inc
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains the php code argument validator plugin.
+ */
+
+/**
+ * Provide PHP code to validate whether or not an argument is ok.
+ *
+ * @ingroup views_argument_validate_plugins
+ */
+class views_plugin_argument_validate_php extends views_plugin_argument_validate {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['code'] = array('default' => '');
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['code'] = array(
+ '#type' => 'textarea',
+ '#title' => t('PHP validate code'),
+ '#default_value' => $this->options['code'],
+ '#description' => t('Enter PHP code that returns TRUE or FALSE. No return is the same as FALSE, so be SURE to return something if you do not want to declare the argument invalid. Do not use &lt;?php ?&gt;. The argument to validate will be "$argument" and the view will be "$view". You may change the argument by setting "$handler->argument". You may change the title used for substitutions for this argument by setting "$handler->validated_title".'),
+ );
+
+ $this->check_access($form, 'code');
+ }
+
+ /**
+ * Only let users with PHP block visibility permissions set/modify this
+ * validate plugin.
+ */
+ function access() {
+ return user_access('use PHP for settings');
+ }
+
+ function convert_options(&$options) {
+ if (!isset($options['code']) && isset($this->argument->options['validate_argument_php'])) {
+ $options['code'] = $this->argument->options['validate_argument_php'];
+ }
+ }
+
+ function validate_argument($argument) {
+ // set up variables to make it easier to reference during the argument.
+ $view = &$this->view;
+ $handler = &$this->argument;
+
+ ob_start();
+ $result = eval($this->options['code']);
+ ob_end_clean();
+ return $result;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_cache.inc b/sites/all/modules/views/plugins/views_plugin_cache.inc
new file mode 100644
index 000000000..0dfc9114b
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_cache.inc
@@ -0,0 +1,350 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_cache.
+ */
+
+/**
+ * @defgroup views_cache_plugins Views cache plugins
+ * @{
+ * @todo.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * The base plugin to handle caching.
+ */
+class views_plugin_cache extends views_plugin {
+ /**
+ * Contains all data that should be written/read from cache.
+ */
+ var $storage = array();
+
+ /**
+ * What table to store data in.
+ */
+ var $table = 'cache_views_data';
+
+ /**
+ * Initialize the plugin.
+ *
+ * @param $view
+ * The view object.
+ * @param $display
+ * The display handler.
+ */
+ function init(&$view, &$display) {
+ $this->view = &$view;
+ $this->display = &$display;
+
+ if (is_object($display->handler)) {
+ $options = $display->handler->get_option('cache');
+ // Overlay incoming options on top of defaults
+ $this->unpack_options($this->options, $options);
+ }
+ }
+
+ /**
+ * Return a string to display as the clickable title for the
+ * access control.
+ */
+ function summary_title() {
+ return t('Unknown');
+ }
+
+ /**
+ * Determine the expiration time of the cache type, or NULL if no expire.
+ *
+ * Plugins must override this to implement expiration.
+ *
+ * @param $type
+ * The cache type, either 'query', 'result' or 'output'.
+ */
+ function cache_expire($type) { }
+
+ /**
+ * Determine expiration time in the cache table of the cache type
+ * or CACHE_PERMANENT if item shouldn't be removed automatically from cache.
+ *
+ * Plugins must override this to implement expiration in the cache table.
+ *
+ * @param $type
+ * The cache type, either 'query', 'result' or 'output'.
+ */
+ function cache_set_expire($type) {
+ return CACHE_PERMANENT;
+ }
+
+
+ /**
+ * Save data to the cache.
+ *
+ * A plugin should override this to provide specialized caching behavior.
+ */
+ function cache_set($type) {
+ switch ($type) {
+ case 'query':
+ // Not supported currently, but this is certainly where we'd put it.
+ break;
+ case 'results':
+ $data = array(
+ 'result' => $this->view->result,
+ 'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
+ 'current_page' => $this->view->get_current_page(),
+ );
+ cache_set($this->get_results_key(), $data, $this->table, $this->cache_set_expire($type));
+ break;
+ case 'output':
+ $this->gather_headers();
+ $this->storage['output'] = $this->view->display_handler->output;
+ cache_set($this->get_output_key(), $this->storage, $this->table, $this->cache_set_expire($type));
+ break;
+ }
+ }
+
+
+ /**
+ * Retrieve data from the cache.
+ *
+ * A plugin should override this to provide specialized caching behavior.
+ */
+ function cache_get($type) {
+ $cutoff = $this->cache_expire($type);
+ switch ($type) {
+ case 'query':
+ // Not supported currently, but this is certainly where we'd put it.
+ return FALSE;
+ case 'results':
+ // Values to set: $view->result, $view->total_rows, $view->execute_time,
+ // $view->current_page.
+ if ($cache = cache_get($this->get_results_key(), $this->table)) {
+ if (!$cutoff || $cache->created > $cutoff) {
+ $this->view->result = $cache->data['result'];
+ $this->view->total_rows = $cache->data['total_rows'];
+ $this->view->set_current_page($cache->data['current_page']);
+ $this->view->execute_time = 0;
+ return TRUE;
+ }
+ }
+ return FALSE;
+ case 'output':
+ if ($cache = cache_get($this->get_output_key(), $this->table)) {
+ if (!$cutoff || $cache->created > $cutoff) {
+ $this->storage = $cache->data;
+ $this->view->display_handler->output = $cache->data['output'];
+ $this->restore_headers();
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+ }
+
+ /**
+ * Clear out cached data for a view.
+ *
+ * We're just going to nuke anything related to the view, regardless of display,
+ * to be sure that we catch everything. Maybe that's a bad idea.
+ */
+ function cache_flush() {
+ cache_clear_all($this->view->name . ':', $this->table, TRUE);
+ }
+
+ /**
+ * Post process any rendered data.
+ *
+ * This can be valuable to be able to cache a view and still have some level of
+ * dynamic output. In an ideal world, the actual output will include HTML
+ * comment based tokens, and then the post process can replace those tokens.
+ *
+ * Example usage. If it is known that the view is a node view and that the
+ * primary field will be a nid, you can do something like this:
+ *
+ * <!--post-FIELD-NID-->
+ *
+ * And then in the post render, create an array with the text that should
+ * go there:
+ *
+ * strtr($output, array('<!--post-FIELD-1-->', 'output for FIELD of nid 1');
+ *
+ * All of the cached result data will be available in $view->result, as well,
+ * so all ids used in the query should be discoverable.
+ */
+ function post_render(&$output) { }
+
+ /**
+ * Start caching javascript, css and other out of band info.
+ *
+ * This takes a snapshot of the current system state so that we don't
+ * duplicate it. Later on, when gather_headers() is run, this information
+ * will be removed so that we don't hold onto it.
+ */
+ function cache_start() {
+ $this->storage['head'] = drupal_add_html_head();
+ $this->storage['css'] = drupal_add_css();
+ $this->storage['js'] = drupal_add_js();
+ $this->storage['headers'] = drupal_get_http_header();
+ }
+
+ /**
+ * Gather out of band data, compare it to what we started with and store the difference.
+ */
+ function gather_headers() {
+ // Simple replacement for head
+ if (isset($this->storage['head'])) {
+ $this->storage['head'] = str_replace($this->storage['head'], '', drupal_add_html_head());
+ }
+ else {
+ $this->storage['head'] = '';
+ }
+
+ // Check if the advanced mapping function of D 7.23 is available.
+ $array_mapping_func = function_exists('drupal_array_diff_assoc_recursive') ? 'drupal_array_diff_assoc_recursive' : 'array_diff_assoc';
+
+ // Slightly less simple for CSS:
+ $css = drupal_add_css();
+ $css_start = isset($this->storage['css']) ? $this->storage['css'] : array();
+ $this->storage['css'] = $this->assetDiff($css, $css_start, $array_mapping_func);
+
+ // Get javascript after/before views renders.
+ $js = drupal_add_js();
+ $js_start = isset($this->storage['js']) ? $this->storage['js'] : array();
+ // If there are any differences between the old and the new javascript then
+ // store them to be added later.
+ $this->storage['js'] = $this->assetDiff($js, $js_start, $array_mapping_func);
+
+ // Special case the settings key and get the difference of the data.
+ $settings = isset($js['settings']['data']) ? $js['settings']['data'] : array();
+ $settings_start = isset($js_start['settings']['data']) ? $js_start['settings']['data'] : array();
+ $this->storage['js']['settings'] = $array_mapping_func($settings, $settings_start);
+
+ // Get difference of HTTP headers.
+ $this->storage['headers'] = $array_mapping_func(drupal_get_http_header(), $this->storage['headers']);
+ }
+
+ /**
+ * Computes the differences between two JS/CSS asset arrays.
+ *
+ * @param array $assets
+ * The current asset array.
+ * @param array $start_assets
+ * The original asset array.
+ * @param string $diff_function
+ * The function that should be used for computing the diff.
+ *
+ * @return array
+ * A CSS or JS asset array that contains all entries that are new/different
+ * in $assets.
+ */
+ protected function assetDiff(array $assets, array $start_assets, $diff_function) {
+ $diff = $diff_function($assets, $start_assets);
+
+ // Cleanup the resulting array since drupal_array_diff_assoc_recursive() can
+ // leave half populated arrays behind.
+ foreach ($diff as $key => $entry) {
+ // If only the weight was different we can remove this entry.
+ if (count($entry) == 1 && isset($entry['weight'])) {
+ unset($diff[$key]);
+ }
+ // If there are other differences we override with the latest entry.
+ elseif ($entry != $assets[$key]) {
+ $diff[$key] = $assets[$key];
+ }
+ }
+ return $diff;
+ }
+
+ /**
+ * Restore out of band data saved to cache. Copied from Panels.
+ */
+ function restore_headers() {
+ if (!empty($this->storage['head'])) {
+ drupal_add_html_head($this->storage['head']);
+ }
+ if (!empty($this->storage['css'])) {
+ foreach ($this->storage['css'] as $args) {
+ drupal_add_css($args['data'], $args);
+ }
+ }
+ if (!empty($this->storage['js'])) {
+ foreach ($this->storage['js'] as $key => $args) {
+ if ($key !== 'settings') {
+ drupal_add_js($args['data'], $args);
+ }
+ else {
+ foreach ($args as $setting) {
+ drupal_add_js($setting, 'setting');
+ }
+ }
+ }
+ }
+ if (!empty($this->storage['headers'])) {
+ foreach ($this->storage['headers'] as $name => $value) {
+ drupal_add_http_header($name, $value);
+ }
+ }
+ }
+
+ function get_results_key() {
+ if (!isset($this->_results_key)) {
+ $this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . $this->get_cache_key();
+ }
+
+ return $this->_results_key;
+ }
+
+ function get_output_key() {
+ if (!isset($this->_output_key)) {
+ $key_data = array(
+ 'theme' => $GLOBALS['theme'],
+ );
+ $this->_output_key = $this->view->name . ':' . $this->display->id . ':output:' . $this->get_cache_key($key_data);
+ }
+
+ return $this->_output_key;
+ }
+
+ /**
+ * Returns cache key.
+ *
+ * @param array $key_data
+ * Additional data for cache segmentation and/or overrides for default
+ * segmentation.
+ *
+ * @return string
+ */
+ function get_cache_key($key_data = array()) {
+ global $user;
+
+ $key_data += array(
+ 'roles' => array_keys($user->roles),
+ 'super-user' => $user->uid == 1, // special caching for super user.
+ 'language' => $GLOBALS['language']->language,
+ 'base_url' => $GLOBALS['base_url'],
+ );
+
+ if (empty($key_data['build_info'])) {
+ $build_info = $this->view->build_info;
+ foreach (array('query','count_query') as $index) {
+ // If the default query back-end is used generate SQL query strings from
+ // the query objects.
+ if ($build_info[$index] instanceof SelectQueryInterface) {
+ $query = clone $build_info[$index];
+ $query->preExecute();
+ $key_data['build_info'][$index] = array(
+ 'sql' => (string) $query,
+ 'arguments' => $query->getArguments(),
+ );
+ }
+ }
+ }
+ $key = md5(serialize($key_data));
+ return $key;
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_cache_none.inc b/sites/all/modules/views/plugins/views_plugin_cache_none.inc
new file mode 100644
index 000000000..9927a9d71
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_cache_none.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_cache_none.
+ */
+
+/**
+ * Caching plugin that provides no caching at all.
+ *
+ * @ingroup views_cache_plugins
+ */
+class views_plugin_cache_none extends views_plugin_cache {
+ function cache_start() { /* do nothing */ }
+
+ function summary_title() {
+ return t('None');
+ }
+
+ function cache_get($type) {
+ return FALSE;
+ }
+
+ function cache_set($type) { }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_cache_time.inc b/sites/all/modules/views/plugins/views_plugin_cache_time.inc
new file mode 100644
index 000000000..25245ea4c
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_cache_time.inc
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_cache_time.
+ */
+
+/**
+ * Simple caching of query results for Views displays.
+ *
+ * @ingroup views_cache_plugins
+ */
+class views_plugin_cache_time extends views_plugin_cache {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['results_lifespan'] = array('default' => 3600);
+ $options['results_lifespan_custom'] = array('default' => 0);
+ $options['output_lifespan'] = array('default' => 3600);
+ $options['output_lifespan_custom'] = array('default' => 0);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $options = array(60, 300, 1800, 3600, 21600, 518400);
+ $options = drupal_map_assoc($options, 'format_interval');
+ $options = array(-1 => t('Never cache')) + $options + array('custom' => t('Custom'));
+
+ $form['results_lifespan'] = array(
+ '#type' => 'select',
+ '#title' => t('Query results'),
+ '#description' => t('The length of time raw query results should be cached.'),
+ '#options' => $options,
+ '#default_value' => $this->options['results_lifespan'],
+ );
+ $form['results_lifespan_custom'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Seconds'),
+ '#size' => '25',
+ '#maxlength' => '30',
+ '#description' => t('Length of time in seconds raw query results should be cached.'),
+ '#default_value' => $this->options['results_lifespan_custom'],
+ '#process' => array('form_process_select','ctools_dependent_process'),
+ '#dependency' => array(
+ 'edit-cache-options-results-lifespan' => array('custom'),
+ ),
+ );
+ $form['output_lifespan'] = array(
+ '#type' => 'select',
+ '#title' => t('Rendered output'),
+ '#description' => t('The length of time rendered HTML output should be cached.'),
+ '#options' => $options,
+ '#default_value' => $this->options['output_lifespan'],
+ );
+ $form['output_lifespan_custom'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Seconds'),
+ '#size' => '25',
+ '#maxlength' => '30',
+ '#description' => t('Length of time in seconds rendered HTML output should be cached.'),
+ '#default_value' => $this->options['output_lifespan_custom'],
+ '#process' => array('form_process_select','ctools_dependent_process'),
+ '#dependency' => array(
+ 'edit-cache-options-output-lifespan' => array('custom'),
+ ),
+ );
+ }
+
+ function options_validate(&$form, &$form_state) {
+ $custom_fields = array('output_lifespan', 'results_lifespan');
+ foreach ($custom_fields as $field) {
+ if ($form_state['values']['cache_options'][$field] == 'custom' && !is_numeric($form_state['values']['cache_options'][$field . '_custom'])) {
+ form_error($form[$field .'_custom'], t('Custom time values must be numeric.'));
+ }
+ }
+ }
+
+ function summary_title() {
+ $results_lifespan = $this->get_lifespan('results');
+ $output_lifespan = $this->get_lifespan('output');
+ return format_interval($results_lifespan, 1) . '/' . format_interval($output_lifespan, 1);
+ }
+
+ function get_lifespan($type) {
+ $lifespan = $this->options[$type . '_lifespan'] == 'custom' ? $this->options[$type . '_lifespan_custom'] : $this->options[$type . '_lifespan'];
+ return $lifespan;
+ }
+
+ function cache_expire($type) {
+ $lifespan = $this->get_lifespan($type);
+ if ($lifespan) {
+ $cutoff = REQUEST_TIME - $lifespan;
+ return $cutoff;
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ function cache_set_expire($type) {
+ $lifespan = $this->get_lifespan($type);
+ if ($lifespan) {
+ return time() + $lifespan;
+ }
+ else {
+ return CACHE_PERMANENT;
+ }
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_display.inc b/sites/all/modules/views/plugins/views_plugin_display.inc
new file mode 100644
index 000000000..b6b73be71
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_display.inc
@@ -0,0 +1,3094 @@
+<?php
+
+/**
+ * @file
+ * Contains the base display plugin.
+ */
+
+/**
+ * @defgroup views_display_plugins Views display plugins
+ * @{
+ * Display plugins control how Views interact with the rest of Drupal.
+ *
+ * They can handle creating Views from a Drupal page hook; they can
+ * handle creating Views from a Drupal block hook. They can also
+ * handle creating Views from an external module source, such as
+ * a Panels pane, or an insert view, or a CCK field type.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * The default display plugin handler. Display plugins handle options and
+ * basic mechanisms for different output methods.
+ */
+class views_plugin_display extends views_plugin {
+ /**
+ * The top object of a view.
+ *
+ * @var view
+ */
+ var $view = NULL;
+
+ var $handlers = array();
+
+ /**
+ * Stores all available display extenders.
+ */
+ var $extender = array();
+
+ function init(&$view, &$display, $options = NULL) {
+ $this->view = &$view;
+ $this->display = &$display;
+
+ // Load extenders as soon as possible.
+ $this->extender = array();
+ $extenders = views_get_enabled_display_extenders();
+ // If you update to the dev version the registry might not be loaded yet.
+ if (!empty($extenders) && class_exists('views_plugin_display_extender')) {
+ foreach ($extenders as $extender) {
+ $plugin = views_get_plugin('display_extender', $extender);
+ if ($plugin) {
+ $plugin->init($this->view, $this);
+ $this->extender[$extender] = $plugin;
+ }
+ else {
+ vpr('Invalid display extender @extender', array('@handler' => $extender));
+ }
+ }
+ }
+
+ // Track changes that the user should know about.
+ $changed = FALSE;
+
+ // Make some modifications:
+ if (!isset($options) && isset($display->display_options)) {
+ $options = $display->display_options;
+ }
+
+ if ($this->is_default_display() && isset($options['defaults'])) {
+ unset($options['defaults']);
+ }
+
+ // Cache for unpack_options, but not if we are in the ui.
+ static $unpack_options = array();
+ if (empty($view->editing)) {
+ $cid = 'unpack_options:' . md5(serialize(array($this->options, $options)));
+ if (empty($unpack_options[$cid])) {
+ $cache = views_cache_get($cid, TRUE);
+ if (!empty($cache->data)) {
+ $this->options = $cache->data;
+ }
+ else {
+ $this->unpack_options($this->options, $options);
+ views_cache_set($cid, $this->options, TRUE);
+ }
+ $unpack_options[$cid] = $this->options;
+ }
+ else {
+ $this->options = $unpack_options[$cid];
+ }
+ }
+ else {
+ $this->unpack_options($this->options, $options);
+ }
+
+ // Translate changed settings:
+ $items_per_page = $this->get_option('items_per_page');
+ $offset = $this->get_option('offset');
+ $use_pager = $this->get_option('use_pager');
+ $pager = $this->get_option('pager');
+ // Check if the pager options were already converted.
+ // The pager settings of a Views 2.x view specifying 10 items with an
+ // offset of 0 and no pager is the same as of a Views 3.x view with
+ // default settings. In this case, the only way to determine which case we
+ // are dealing with is checking the API version but that's only available
+ // for exported Views as it's not stored in the database.
+ // If you would like to change this code, really take care that you thought
+ // of every possibility.
+ // @TODO: Provide a way to convert the database views as well.
+ if (((!empty($items_per_page) && $items_per_page != 10) || !empty($offset) || !empty($use_pager))
+ || (!empty($view->api_version) && $view->api_version == 2)) {
+ // Find out the right pager type.
+ // If the view "use pager" it's a normal/full pager.
+ if ($use_pager) {
+ $type = 'full';
+ }
+ // If it does not use pager, but 0 items per page it should not page
+ // else it should display just a certain amount of items.
+ else {
+ $type = $items_per_page ? 'some' : 'none';
+ }
+
+ // Setup the pager options.
+ $pager = array(
+ 'type' => $type,
+ 'options' => array(
+ 'offset' => intval($offset)
+ ),
+ );
+
+ if ($items_per_page) {
+ $pager['options']['items_per_page'] = $items_per_page;
+ }
+ // Setup the pager element.
+ if ($id = $this->get_option('pager_element')) {
+ $pager['options']['id'] = $id;
+ }
+
+ // Unset the previous options
+ // After edit and save the view they will be erased
+ $this->set_option('items_per_page', NULL);
+ $this->set_option('offset', NULL);
+ $this->set_option('use_pager', NULL);
+ $this->set_option('pager', $pager);
+ $changed = TRUE;
+ }
+
+
+ // Plugable headers, footer and empty texts are
+ // not compatible with previous version of views
+ // This code converts old values into a configured handler for each area
+ foreach (array('header', 'footer', 'empty') as $area) {
+ $converted = FALSE;
+ if (isset($this->options[$area]) && !is_array($this->options[$area])) {
+ if (!empty($this->options[$area])) {
+ $content = $this->get_option($area);
+ if (!empty($content) && !is_array($content)) {
+ $format = $this->get_option($area . '_format');
+ $options = array(
+ 'id' => 'area',
+ 'table' => 'views',
+ 'field' => 'area',
+ 'label' => '',
+ 'relationship' => 'none',
+ 'group_type' => 'group',
+ 'content' => $content,
+ 'format' => !empty($format) ? $format : filter_default_format(),
+ );
+
+ if ($area != 'empty' && $empty = $this->get_option($area . '_empty')) {
+ $options['empty'] = $empty;
+ }
+ $this->set_option($area, array('text' => $options));
+ $converted = TRUE;
+ $changed = TRUE;
+ }
+ }
+ // Ensure that options are at least an empty array
+ if (!$converted) {
+ $this->set_option($area, array());
+ }
+ }
+ }
+
+ // Convert distinct setting from display to query settings.
+ $distinct = $this->get_option('distinct');
+ if (!empty($distinct)) {
+ $query_settings = $this->get_option('query');
+ $query_settings['options']['distinct'] = $distinct;
+ $this->set_option('query', $query_settings);
+ // Clear the values
+ $this->set_option('distinct', NULL);
+ $changed = TRUE;
+ }
+
+ // Convert field language settings.
+ $query_options = $this->get_option('query');
+ if (isset($query_options['options']['field_language'])) {
+ $this->set_option('field_language', $query_options['options']['field_language']);
+ unset($query_options['options']['field_language']);
+ $changed = TRUE;
+ }
+ if (isset($query_options['options']['field_language_add_to_query'])) {
+ $this->set_option('field_language_add_to_query', $query_options['options']['field_language_add_to_query']);
+ unset($query_options['options']['field_language_add_to_query']);
+ $changed = TRUE;
+ }
+ $this->set_option('query', $query_options);
+
+ // Convert filter groups.
+ $filter_groups = $this->get_option('filter_groups');
+ // Only convert if it wasn't converted yet, which is the case if there is a 0 group.
+ if (isset($filter_groups['groups'][0])) {
+ // Update filter groups.
+ $filter_groups ['groups'] = views_array_key_plus($filter_groups['groups']);
+ $this->set_option('filter_groups', $filter_groups);
+ // Update the filter group on each filter.
+ $filters = $this->get_option('filters');
+ foreach ($filters as &$filter) {
+ if (isset($filter['group'])) {
+ $filter['group']++;
+ }
+ else {
+ $filter['group'] = 1;
+ }
+ }
+ $this->set_option('filters', $filters);
+ $changed = TRUE;
+ }
+
+ // Filter groups were allowed to be rewritten without its filters, so
+ // before this update the view was using the default values. To be sure that
+ // the existing view isn't broken, don't use this overridden values but copy
+ // them from the default display. Only do this if the filters are overridden
+ // but the filter_groups are not marked as so.
+ if (!$this->is_default_display() && !$this->options['defaults']['filters'] && $this->options['defaults']['filter_groups']) {
+ // Set filter_groups to be overridden and save the value in the
+ // display_options as well.
+ $this->options['defaults']['filter_groups'] = FALSE;
+ $this->display->display_options['defaults']['filter_groups'] = $this->options['defaults']['filter_groups'];
+ // Copy the filter_groups from the default, and add them to the
+ // display_options as well. $this->default_display is not initialized at
+ // this point.
+ $this->options['filter_groups'] = $this->view->display['default']->handler->options['filter_groups'];
+ $this->display->display_options['filter_groups'] = $this->options['filter_groups'];
+
+ $changed = TRUE;
+ }
+
+ // Mark the view as changed so the user has a chance to save it.
+ if ($changed) {
+ $this->view->changed = TRUE;
+ }
+ }
+
+ function destroy() {
+ parent::destroy();
+
+ foreach ($this->handlers as $type => $handlers) {
+ foreach ($handlers as $id => $handler) {
+ if (is_object($handler)) {
+ $this->handlers[$type][$id]->destroy();
+ }
+ }
+ }
+
+ if (isset($this->default_display)) {
+ unset($this->default_display);
+ }
+
+ foreach ($this->extender as $extender) {
+ $extender->destroy();
+ }
+ }
+
+ /**
+ * Determine if this display is the 'default' display which contains
+ * fallback settings
+ */
+ function is_default_display() { return FALSE; }
+
+ /**
+ * Determine if this display uses exposed filters, so the view
+ * will know whether or not to build them.
+ */
+ function uses_exposed() {
+ if (!isset($this->has_exposed)) {
+ foreach ($this->handlers as $type => $value) {
+ foreach ($this->view->$type as $id => $handler) {
+ if ($handler->can_expose() && $handler->is_exposed()) {
+ // one is all we need; if we find it, return true.
+ $this->has_exposed = TRUE;
+ return TRUE;
+ }
+ }
+ }
+ $pager = $this->get_plugin('pager');
+ if (isset($pager) && $pager->uses_exposed()) {
+ $this->has_exposed = TRUE;
+ return TRUE;
+ }
+ $this->has_exposed = FALSE;
+ }
+
+ return $this->has_exposed;
+ }
+
+ /**
+ * Determine if this display should display the exposed
+ * filters widgets, so the view will know whether or not
+ * to render them.
+ *
+ * Regardless of what this function
+ * returns, exposed filters will not be used nor
+ * displayed unless uses_exposed() returns TRUE.
+ */
+ function displays_exposed() {
+ return TRUE;
+ }
+
+ /**
+ * Does the display use AJAX?
+ */
+ function use_ajax() {
+ if (!empty($this->definition['use ajax'])) {
+ return $this->get_option('use_ajax');
+ }
+ return FALSE;
+ }
+
+ /**
+ * Does the display have a pager enabled?
+ */
+ function use_pager() {
+ $pager = $this->get_plugin('pager');
+ if ($pager) {
+ return $pager->use_pager();
+ }
+ }
+
+ /**
+ * Does the display have a more link enabled?
+ */
+ function use_more() {
+ if (!empty($this->definition['use more'])) {
+ return $this->get_option('use_more');
+ }
+ return FALSE;
+ }
+
+ /**
+ * Does the display have groupby enabled?
+ */
+ function use_group_by() {
+ return $this->get_option('group_by');
+ }
+
+ /**
+ * Should the enabled display more link be shown when no more items?
+ */
+ function use_more_always() {
+ if (!empty($this->definition['use more'])) {
+ return $this->get_option('use_more_always');
+ }
+ return FALSE;
+ }
+
+ /**
+ * Does the display have custom link text?
+ */
+ function use_more_text() {
+ if (!empty($this->definition['use more'])) {
+ return $this->get_option('use_more_text');
+ }
+ return FALSE;
+ }
+
+ /**
+ * Can this display accept attachments?
+ */
+ function accept_attachments() {
+ if (empty($this->definition['accept attachments'])) {
+ return FALSE;
+ }
+ if (!empty($this->view->argument) && $this->get_option('hide_attachment_summary')) {
+ foreach ($this->view->argument as $argument_id => $argument) {
+ if ($argument->needs_style_plugin() && empty($argument->argument_validated)) {
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+ }
+
+ /**
+ * Allow displays to attach to other views.
+ */
+ function attach_to($display_id) { }
+
+ /**
+ * Static member function to list which sections are defaultable
+ * and what items each section contains.
+ */
+ function defaultable_sections($section = NULL) {
+ $sections = array(
+ 'access' => array('access', 'access_options'),
+ 'access_options' => array('access', 'access_options'),
+ 'cache' => array('cache', 'cache_options'),
+ 'cache_options' => array('cache', 'cache_options'),
+ 'title' => array('title'),
+ 'css_class' => array('css_class'),
+ 'use_ajax' => array('use_ajax'),
+ 'hide_attachment_summary' => array('hide_attachment_summary'),
+ 'hide_admin_links' => array('hide_admin_links'),
+ 'group_by' => array('group_by'),
+ 'query' => array('query'),
+ 'use_more' => array('use_more', 'use_more_always', 'use_more_text'),
+ 'use_more_always' => array('use_more', 'use_more_always', 'use_more_text'),
+ 'use_more_text' => array('use_more', 'use_more_always', 'use_more_text'),
+ 'link_display' => array('link_display', 'link_url'),
+
+ // Force these to cascade properly.
+ 'style_plugin' => array('style_plugin', 'style_options', 'row_plugin', 'row_options'),
+ 'style_options' => array('style_plugin', 'style_options', 'row_plugin', 'row_options'),
+ 'row_plugin' => array('style_plugin', 'style_options', 'row_plugin', 'row_options'),
+ 'row_options' => array('style_plugin', 'style_options', 'row_plugin', 'row_options'),
+
+ 'pager' => array('pager', 'pager_options'),
+ 'pager_options' => array('pager', 'pager_options'),
+
+ 'exposed_form' => array('exposed_form', 'exposed_form_options'),
+ 'exposed_form_options' => array('exposed_form', 'exposed_form_options'),
+
+ // These guys are special
+ 'header' => array('header'),
+ 'footer' => array('footer'),
+ 'empty' => array('empty'),
+ 'relationships' => array('relationships'),
+ 'fields' => array('fields'),
+ 'sorts' => array('sorts'),
+ 'arguments' => array('arguments'),
+ 'filters' => array('filters', 'filter_groups'),
+ 'filter_groups' => array('filters', 'filter_groups'),
+ );
+
+ // If the display cannot use a pager, then we cannot default it.
+ if (empty($this->definition['use pager'])) {
+ unset($sections['pager']);
+ unset($sections['items_per_page']);
+ }
+
+ foreach ($this->extender as $extender) {
+ $extender->defaultable_sections($sections, $section);
+ }
+
+ if ($section) {
+ if (!empty($sections[$section])) {
+ return $sections[$section];
+ }
+ }
+ else {
+ return $sections;
+ }
+ }
+
+ function option_definition() {
+ $options = array(
+ 'defaults' => array(
+ 'default' => array(
+ 'access' => TRUE,
+ 'cache' => TRUE,
+ 'query' => TRUE,
+ 'title' => TRUE,
+ 'css_class' => TRUE,
+
+ 'display_description' => FALSE,
+ 'use_ajax' => TRUE,
+ 'hide_attachment_summary' => TRUE,
+ 'hide_admin_links' => TRUE,
+ 'pager' => TRUE,
+ 'pager_options' => TRUE,
+ 'use_more' => TRUE,
+ 'use_more_always' => TRUE,
+ 'use_more_text' => TRUE,
+ 'exposed_form' => TRUE,
+ 'exposed_form_options' => TRUE,
+
+ 'link_display' => TRUE,
+ 'link_url' => '',
+ 'group_by' => TRUE,
+
+ 'style_plugin' => TRUE,
+ 'style_options' => TRUE,
+ 'row_plugin' => TRUE,
+ 'row_options' => TRUE,
+
+ 'header' => TRUE,
+ 'footer' => TRUE,
+ 'empty' => TRUE,
+
+ 'relationships' => TRUE,
+ 'fields' => TRUE,
+ 'sorts' => TRUE,
+ 'arguments' => TRUE,
+ 'filters' => TRUE,
+ 'filter_groups' => TRUE,
+ ),
+ 'export' => FALSE,
+ ),
+
+ 'title' => array(
+ 'default' => '',
+ 'translatable' => TRUE,
+ ),
+ 'enabled' => array(
+ 'default' => TRUE,
+ 'translatable' => FALSE,
+ 'bool' => TRUE,
+ ),
+ 'display_comment' => array(
+ 'default' => '',
+ ),
+ 'css_class' => array(
+ 'default' => '',
+ 'translatable' => FALSE,
+ ),
+ 'display_description' => array(
+ 'default' => '',
+ 'translatable' => TRUE,
+ ),
+ 'use_ajax' => array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ ),
+ 'hide_attachment_summary' => array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ ),
+ 'hide_admin_links' => array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ ),
+ // This is legacy code:
+ // Items_per/offset/use_pager is moved to the pager plugin
+ // but the automatic update path needs this items defined, so don't remove it.
+ // @see views_plugin_display::init()
+ 'items_per_page' => array(
+ 'default' => 10,
+ ),
+ 'offset' => array(
+ 'default' => 0,
+ ),
+ 'use_pager' => array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ ),
+ 'use_more' => array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ ),
+ 'use_more_always' => array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ 'export' => 'export_option_always',
+ ),
+ 'use_more_text' => array(
+ 'default' => 'more',
+ 'translatable' => TRUE,
+ ),
+ 'link_display' => array(
+ 'default' => '',
+ ),
+ 'link_url' => array(
+ 'default' => '',
+ ),
+ 'group_by' => array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ ),
+ 'field_language' => array(
+ 'default' => '***CURRENT_LANGUAGE***',
+ ),
+ 'field_language_add_to_query' => array(
+ 'default' => 1,
+ ),
+
+ // These types are all plugins that can have individual settings
+ // and therefore need special handling.
+ 'access' => array(
+ 'contains' => array(
+ 'type' => array('default' => 'none', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin'),
+ ),
+ ),
+ 'cache' => array(
+ 'contains' => array(
+ 'type' => array('default' => 'none', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin'),
+ ),
+ ),
+ 'query' => array(
+ 'contains' => array(
+ 'type' => array('default' => 'views_query', 'export' => 'export_plugin'),
+ 'options' => array('default' => array(), 'export' => FALSE),
+ ),
+ ),
+ // Note that exposed_form plugin has options in a separate array,
+ // while access and cache do not. access and cache are legacy and
+ // that pattern should not be repeated, but it is left as is to
+ // reduce the need to modify older views. Let's consider the
+ // pattern used here to be the template from which future plugins
+ // should be copied.
+ 'exposed_form' => array(
+ 'contains' => array(
+ 'type' => array('default' => 'basic', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin'),
+ 'options' => array('default' => array(), 'export' => FALSE),
+ ),
+ ),
+ 'pager' => array(
+ 'contains' => array(
+ 'type' => array('default' => 'full', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin'),
+ 'options' => array('default' => array(), 'export' => FALSE),
+ ),
+ ),
+
+ // Note that the styles have their options completely independent.
+ // Like access and cache above, this is a legacy pattern and
+ // should not be repeated.
+ 'style_plugin' => array(
+ 'default' => 'default',
+ 'export' => 'export_style',
+ 'unpack_translatable' => 'unpack_style',
+ ),
+ 'style_options' => array(
+ 'default' => array(),
+ 'export' => FALSE,
+ ),
+ 'row_plugin' => array(
+ 'default' => 'fields',
+ 'export' => 'export_style',
+ 'unpack_translatable' => 'unpack_style',
+ ),
+ 'row_options' => array(
+ 'default' => array(),
+ 'export' => FALSE,
+ ),
+
+ 'exposed_block' => array(
+ 'default' => FALSE,
+ ),
+
+ 'header' => array(
+ 'default' => array(),
+ 'export' => 'export_handler',
+ 'unpack_translatable' => 'unpack_handler',
+ ),
+ 'footer' => array(
+ 'default' => array(),
+ 'export' => 'export_handler',
+ 'unpack_translatable' => 'unpack_handler',
+ ),
+ 'empty' => array(
+ 'default' => array(),
+ 'export' => 'export_handler',
+ 'unpack_translatable' => 'unpack_handler',
+ ),
+
+ // We want these to export last.
+ // These are the 5 handler types.
+ 'relationships' => array(
+ 'default' => array(),
+ 'export' => 'export_handler',
+ 'unpack_translatable' => 'unpack_handler',
+
+ ),
+ 'fields' => array(
+ 'default' => array(),
+ 'export' => 'export_handler',
+ 'unpack_translatable' => 'unpack_handler',
+ ),
+ 'sorts' => array(
+ 'default' => array(),
+ 'export' => 'export_handler',
+ 'unpack_translatable' => 'unpack_handler',
+ ),
+ 'arguments' => array(
+ 'default' => array(),
+ 'export' => 'export_handler',
+ 'unpack_translatable' => 'unpack_handler',
+ ),
+ 'filter_groups' => array(
+ 'contains' => array(
+ 'operator' => array('default' => 'AND'),
+ 'groups' => array('default' => array(1 => 'AND')),
+ ),
+ ),
+ 'filters' => array(
+ 'default' => array(),
+ 'export' => 'export_handler',
+ 'unpack_translatable' => 'unpack_handler',
+ ),
+ );
+
+ if (empty($this->definition['use pager'])) {
+ $options['defaults']['default']['use_pager'] = FALSE;
+ $options['defaults']['default']['items_per_page'] = FALSE;
+ $options['defaults']['default']['offset'] = FALSE;
+ $options['defaults']['default']['pager'] = FALSE;
+ $options['pager']['contains']['type']['default'] = 'some';
+ }
+
+ if ($this->is_default_display()) {
+ unset($options['defaults']);
+ }
+
+ foreach ($this->extender as $extender) {
+ $extender->options_definition_alter($options);
+ }
+
+ return $options;
+ }
+
+ /**
+ * Check to see if the display has a 'path' field.
+ *
+ * This is a pure function and not just a setting on the definition
+ * because some displays (such as a panel pane) may have a path based
+ * upon configuration.
+ *
+ * By default, displays do not have a path.
+ */
+ function has_path() { return FALSE; }
+
+ /**
+ * Check to see if the display has some need to link to another display.
+ *
+ * For the most part, displays without a path will use a link display. However,
+ * sometimes displays that have a path might also need to link to another display.
+ * This is true for feeds.
+ */
+ function uses_link_display() { return !$this->has_path(); }
+
+ /**
+ * Check to see if the display can put the exposed form in a block.
+ *
+ * By default, displays that do not have a path cannot disconnect
+ * the exposed form and put it in a block, because the form has no
+ * place to go and Views really wants the forms to go to a specific
+ * page.
+ */
+ function uses_exposed_form_in_block() { return $this->has_path(); }
+
+ /**
+ * Check to see which display to use when creating links within
+ * a view using this display.
+ */
+ function get_link_display() {
+ $display_id = $this->get_option('link_display');
+ // If unknown, pick the first one.
+ if (empty($display_id) || empty($this->view->display[$display_id])) {
+ foreach ($this->view->display as $display_id => $display) {
+ if (!empty($display->handler) && $display->handler->has_path()) {
+ return $display_id;
+ }
+ }
+ }
+ else {
+ return $display_id;
+ }
+ // fall-through returns NULL
+ }
+
+ /**
+ * Return the base path to use for this display.
+ *
+ * This can be overridden for displays that do strange things
+ * with the path.
+ */
+ function get_path() {
+ if ($this->has_path()) {
+ return $this->get_option('path');
+ }
+
+ $display_id = $this->get_link_display();
+ if ($display_id && !empty($this->view->display[$display_id]) && is_object($this->view->display[$display_id]->handler)) {
+ return $this->view->display[$display_id]->handler->get_path();
+ }
+
+ if ($this->get_option('link_display') == 'custom_url' && $link_url = $this->get_option('link_url')) {
+ return $link_url;
+ }
+ }
+
+ function get_url() {
+ return $this->view->get_url();
+ }
+
+ /**
+ * Check to see if the display needs a breadcrumb
+ *
+ * By default, displays do not need breadcrumbs
+ */
+ function uses_breadcrumb() { return FALSE; }
+
+ /**
+ * Determine if a given option is set to use the default display or the
+ * current display
+ *
+ * @return
+ * TRUE for the default display
+ */
+ function is_defaulted($option) {
+ return !$this->is_default_display() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
+ }
+
+ /**
+ * Intelligently get an option either from this display or from the
+ * default display, if directed to do so.
+ */
+ function get_option($option) {
+ if ($this->is_defaulted($option)) {
+ return $this->default_display->get_option($option);
+ }
+
+ if (array_key_exists($option, $this->options)) {
+ return $this->options[$option];
+ }
+ }
+
+ /**
+ * Determine if the display's style uses fields.
+ */
+ function uses_fields() {
+ $plugin = $this->get_plugin();
+ if ($plugin) {
+ return $plugin->uses_fields();
+ }
+ }
+
+ /**
+ * Get the instance of a plugin, for example style or row.
+ *
+ * @param string $type
+ * The type of the plugin.
+ * @param string $name
+ * The name of the plugin defined in hook_views_plugins.
+ *
+ * @return views_plugin|FALSE
+ */
+ function get_plugin($type = 'style', $name = NULL) {
+ static $cache = array();
+ if (!isset($cache[$type][$name])) {
+ switch ($type) {
+ case 'style':
+ case 'row':
+ $option_name = $type . '_plugin';
+ $options = $this->get_option($type . '_options');
+ if (!$name) {
+ $name = $this->get_option($option_name);
+ }
+
+ break;
+ case 'query':
+ $views_data = views_fetch_data($this->view->base_table);
+ $name = !empty($views_data['table']['base']['query class']) ? $views_data['table']['base']['query class'] : 'views_query';
+ default:
+ $option_name = $type;
+ $options = $this->get_option($type);
+ if (!$name) {
+ $name = $options['type'];
+ }
+
+ // access & cache store their options as siblings with the
+ // type; all others use an 'options' array.
+ if ($type != 'access' && $type != 'cache') {
+ $options = $options['options'];
+ }
+ }
+ $plugin = views_get_plugin($type, $name);
+
+ if (!$plugin) {
+ return;
+ }
+ if ($type != 'query') {
+ $plugin->init($this->view, $this->display, $options);
+ }
+ else {
+ $display_id = $this->is_defaulted($option_name) ? $this->display->id : 'default';
+ $plugin->localization_keys = array($display_id, $type);
+
+ if (!isset($this->base_field)) {
+ $views_data = views_fetch_data($this->view->base_table);
+ $this->view->base_field = !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : '';
+ }
+ $plugin->init($this->view->base_table, $this->view->base_field, $options);
+ }
+ $cache[$type][$name] = $plugin;
+ }
+
+ return $cache[$type][$name];
+ }
+
+ /**
+ * Get the handler object for a single handler.
+ */
+ function &get_handler($type, $id) {
+ if (!isset($this->handlers[$type])) {
+ $this->get_handlers($type);
+ }
+
+ if (isset($this->handlers[$type][$id])) {
+ return $this->handlers[$type][$id];
+ }
+
+ // So we can return a reference.
+ $null = NULL;
+ return $null;
+ }
+
+ /**
+ * Get a full array of handlers for $type. This caches them.
+ */
+ function &get_handlers($type) {
+ if (!isset($this->handlers[$type])) {
+ $this->handlers[$type] = array();
+ $types = views_object_types();
+ $plural = $types[$type]['plural'];
+
+ foreach ($this->get_option($plural) as $id => $info) {
+ // If this is during form submission and there are temporary options
+ // which can only appear if the view is in the edit cache, use those
+ // options instead. This is used for AJAX multi-step stuff.
+ if (isset($_POST['form_id']) && isset($this->view->temporary_options[$type][$id])) {
+ $info = $this->view->temporary_options[$type][$id];
+ }
+
+ if ($info['id'] != $id) {
+ $info['id'] = $id;
+ }
+
+ // If aggregation is on, the group type might override the actual
+ // handler that is in use. This piece of code checks that and,
+ // if necessary, sets the override handler.
+ $override = NULL;
+ if ($this->use_group_by() && !empty($info['group_type'])) {
+ if (empty($this->view->query)) {
+ $this->view->init_query();
+ }
+ $aggregate = $this->view->query->get_aggregation_info();
+ if (!empty($aggregate[$info['group_type']]['handler'][$type])) {
+ $override = $aggregate[$info['group_type']]['handler'][$type];
+ }
+ }
+
+ if (!empty($types[$type]['type'])) {
+ $handler_type = $types[$type]['type'];
+ }
+ else {
+ $handler_type = $type;
+ }
+
+ $handler = views_get_handler($info['table'], $info['field'], $handler_type, $override);
+ if ($handler) {
+ // Special override for area types so they know where they come from.
+ if ($handler_type == 'area') {
+ $handler->handler_type = $type;
+ }
+
+ $handler->init($this->view, $info);
+ $this->handlers[$type][$id] = &$handler;
+ }
+
+ // Prevent reference problems.
+ unset($handler);
+ }
+ }
+
+ return $this->handlers[$type];
+ }
+
+ /**
+ * Retrieve a list of fields for the current display with the
+ * relationship associated if it exists.
+ *
+ * @param $groupable_only
+ * Return only an array of field labels from handler that return TRUE
+ * from use_string_group_by method.
+ */
+ function get_field_labels() {
+ // Use func_get_arg so the function signature isn't amended
+ // but we can still pass TRUE into the function to filter
+ // by groupable handlers.
+ $args = func_get_args();
+ $groupable_only = isset($args[0]) ? $args[0] : FALSE;
+
+ $options = array();
+ foreach ($this->get_handlers('relationship') as $relationship => $handler) {
+ if ($label = $handler->label()) {
+ $relationships[$relationship] = $label;
+ }
+ else {
+ $relationships[$relationship] = $handler->ui_name();
+ }
+ }
+
+ foreach ($this->get_handlers('field') as $id => $handler) {
+ if ($groupable_only && !$handler->use_string_group_by()) {
+ // Continue to next handler if it's not groupable.
+ continue;
+ }
+ if ($label = $handler->label()) {
+ $options[$id] = $label;
+ }
+ else {
+ $options[$id] = $handler->ui_name();
+ }
+ if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) {
+ $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id];
+ }
+ }
+ return $options;
+ }
+
+ /**
+ * Intelligently set an option either from this display or from the
+ * default display, if directed to do so.
+ */
+ function set_option($option, $value) {
+ if ($this->is_defaulted($option)) {
+ return $this->default_display->set_option($option, $value);
+ }
+
+ // Set this in two places: On the handler where we'll notice it
+ // but also on the display object so it gets saved. This should
+ // only be a temporary fix.
+ $this->display->display_options[$option] = $value;
+ return $this->options[$option] = $value;
+ }
+
+ /**
+ * Set an option and force it to be an override.
+ */
+ function override_option($option, $value) {
+ $this->set_override($option, FALSE);
+ $this->set_option($option, $value);
+ }
+
+ /**
+ * Because forms may be split up into sections, this provides
+ * an easy URL to exactly the right section. Don't override this.
+ */
+ function option_link($text, $section, $class = '', $title = '') {
+ views_add_js('ajax');
+ if (!empty($class)) {
+ $text = '<span>' . $text . '</span>';
+ }
+
+ if (!trim($text)) {
+ $text = t('Broken field');
+ }
+
+ if (empty($title)) {
+ $title = $text;
+ }
+
+ return l($text, 'admin/structure/views/nojs/display/' . $this->view->name . '/' . $this->display->id . '/' . $section, array('attributes' => array('class' => 'views-ajax-link ' . $class, 'title' => $title, 'id' => drupal_html_id('views-' . $this->display->id . '-' . $section)), 'html' => TRUE));
+ }
+
+ /**
+ * Returns to tokens for arguments.
+ *
+ * This function is similar to views_handler_field::get_render_tokens()
+ * but without fields tokens.
+ */
+ function get_arguments_tokens() {
+ $tokens = array();
+ if (!empty($this->view->build_info['substitutions'])) {
+ $tokens = $this->view->build_info['substitutions'];
+ }
+ $count = 0;
+ foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
+ $token = '%' . ++$count;
+ if (!isset($tokens[$token])) {
+ $tokens[$token] = '';
+ }
+
+ // Use strip tags as there should never be HTML in the path.
+ // However, we need to preserve special characters like " that
+ // were removed by check_plain().
+ $tokens['!' . $count] = isset($this->view->args[$count - 1]) ? strip_tags(decode_entities($this->view->args[$count - 1])) : '';
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Provide the default summary for options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) {
+ $categories = array(
+ 'title' => array(
+ 'title' => t('Title'),
+ 'column' => 'first',
+ ),
+ 'format' => array(
+ 'title' => t('Format'),
+ 'column' => 'first',
+ ),
+ 'filters' => array(
+ 'title' => t('Filters'),
+ 'column' => 'first',
+ ),
+ 'fields' => array(
+ 'title' => t('Fields'),
+ 'column' => 'first',
+ ),
+ 'pager' => array(
+ 'title' => t('Pager'),
+ 'column' => 'second',
+ ),
+ 'exposed' => array(
+ 'title' => t('Exposed form'),
+ 'column' => 'third',
+ 'build' => array(
+ '#weight' => 1,
+ ),
+ ),
+ 'access' => array(
+ 'title' => '',
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -5,
+ ),
+ ),
+ 'other' => array(
+ 'title' => t('Other'),
+ 'column' => 'third',
+ 'build' => array(
+ '#weight' => 2,
+ ),
+ ),
+ );
+
+ if ($this->display->id != 'default') {
+ $options['display_id'] = array(
+ 'category' => 'other',
+ 'title' => t('Machine Name'),
+ 'value' => !empty($this->display->new_id) ? check_plain($this->display->new_id) : check_plain($this->display->id),
+ 'desc' => t('Change the machine name of this display.'),
+ );
+ }
+
+ $display_comment = check_plain(views_ui_truncate($this->get_option('display_comment'), 80));
+ $options['display_comment'] = array(
+ 'category' => 'other',
+ 'title' => t('Comment'),
+ 'value' => !empty($display_comment) ? $display_comment : t('No comment'),
+ 'desc' => t('Comment or document this display.'),
+ );
+
+ $title = strip_tags($this->get_option('title'));
+ if (!$title) {
+ $title = t('None');
+ }
+
+ $options['title'] = array(
+ 'category' => 'title',
+ 'title' => t('Title'),
+ 'value' => $title,
+ 'desc' => t('Change the title that this display will use.'),
+ );
+
+ $style_plugin = views_fetch_plugin_data('style', $this->get_option('style_plugin'));
+ $style_plugin_instance = $this->get_plugin('style');
+ $style_summary = empty($style_plugin['title']) ? t('Missing style plugin') : $style_plugin_instance->summary_title();
+ $style_title = empty($style_plugin['title']) ? t('Missing style plugin') : $style_plugin_instance->plugin_title();
+
+ $style = '';
+
+ $options['style_plugin'] = array(
+ 'category' => 'format',
+ 'title' => t('Format'),
+ 'value' => $style_title,
+ 'setting' => $style_summary,
+ 'desc' => t('Change the way content is formatted.'),
+ );
+
+ // This adds a 'Settings' link to the style_options setting if the style has options.
+ if (!empty($style_plugin['uses options'])) {
+ $options['style_plugin']['links']['style_options'] = t('Change settings for this format');
+ }
+
+ if (!empty($style_plugin['uses row plugin'])) {
+ $row_plugin = views_fetch_plugin_data('row', $this->get_option('row_plugin'));
+ $row_plugin_instance = $this->get_plugin('row');
+ $row_summary = empty($row_plugin['title']) ? t('Missing style plugin') : $row_plugin_instance->summary_title();
+ $row_title = empty($row_plugin['title']) ? t('Missing style plugin') : $row_plugin_instance->plugin_title();
+
+ $options['row_plugin'] = array(
+ 'category' => 'format',
+ 'title' => t('Show'),
+ 'value' => $row_title,
+ 'setting' => $row_summary,
+ 'desc' => t('Change the way each row in the view is styled.'),
+ );
+ // This adds a 'Settings' link to the row_options setting if the row style has options.
+ if (!empty($row_plugin['uses options'])) {
+ $options['row_plugin']['links']['row_options'] = t('Change settings for this style');
+ }
+ }
+ if (!empty($this->definition['use ajax'])) {
+ $options['use_ajax'] = array(
+ 'category' => 'other',
+ 'title' => t('Use AJAX'),
+ 'value' => $this->get_option('use_ajax') ? t('Yes') : t('No'),
+ 'desc' => t('Change whether or not this display will use AJAX.'),
+ );
+ }
+ if (!empty($this->definition['accept attachments'])) {
+ $options['hide_attachment_summary'] = array(
+ 'category' => 'other',
+ 'title' => t('Hide attachments in summary'),
+ 'value' => $this->get_option('hide_attachment_summary') ? t('Yes') : t('No'),
+ 'desc' => t('Change whether or not to display attachments when displaying a contextual filter summary.'),
+ );
+ }
+ if (!isset($this->definition['contextual links locations']) || !empty($this->definition['contextual links locations'])) {
+ $options['hide_admin_links'] = array(
+ 'category' => 'other',
+ 'title' => t('Hide contextual links'),
+ 'value' => $this->get_option('hide_admin_links') ? t('Yes') : t('No'),
+ 'desc' => t('Change whether or not to display contextual links for this view.'),
+ );
+ }
+
+ $pager_plugin = $this->get_plugin('pager');
+ if (!$pager_plugin) {
+ // default to the no pager plugin.
+ $pager_plugin = views_get_plugin('pager', 'none');
+ }
+
+ $pager_str = $pager_plugin->summary_title();
+
+ $options['pager'] = array(
+ 'category' => 'pager',
+ 'title' => t('Use pager'),
+ 'value' => $pager_plugin->plugin_title(),
+ 'setting' => $pager_str,
+ 'desc' => t("Change this display's pager setting."),
+ );
+
+ // If pagers aren't allowed, change the text of the item:
+ if (empty($this->definition['use pager'])) {
+ $options['pager']['title'] = t('Items to display');
+ }
+
+ if (!empty($pager_plugin->definition['uses options'])) {
+ $options['pager']['links']['pager_options'] = t('Change settings for this pager type.');
+ }
+
+ if (!empty($this->definition['use more'])) {
+ $options['use_more'] = array(
+ 'category' => 'pager',
+ 'title' => t('More link'),
+ 'value' => $this->get_option('use_more') ? t('Yes') : t('No'),
+ 'desc' => t('Specify whether this display will provide a "more" link.'),
+ );
+ }
+
+ $this->view->init_query();
+ if ($this->view->query->get_aggregation_info()) {
+ $options['group_by'] = array(
+ 'category' => 'other',
+ 'title' => t('Use aggregation'),
+ 'value' => $this->get_option('group_by') ? t('Yes') : t('No'),
+ 'desc' => t('Allow grouping and aggregation (calculation) of fields.'),
+ );
+ }
+
+ $options['query'] = array(
+ 'category' => 'other',
+ 'title' => t('Query settings'),
+ 'value' => t('Settings'),
+ 'desc' => t('Allow to set some advanced settings for the query plugin'),
+ );
+
+ $languages = array(
+ '***CURRENT_LANGUAGE***' => t("Current user's language"),
+ '***DEFAULT_LANGUAGE***' => t("Default site language"),
+ LANGUAGE_NONE => t('Language neutral'),
+ );
+ if (module_exists('locale')) {
+ $languages = array_merge($languages, locale_language_list());
+ }
+ $field_language = array();
+ $options['field_language'] = array(
+ 'category' => 'other',
+ 'title' => t('Field Language'),
+ 'value' => $languages[$this->get_option('field_language')],
+ 'desc' => t('All fields which support translations will be displayed in the selected language.'),
+ );
+
+ $access_plugin = $this->get_plugin('access');
+ if (!$access_plugin) {
+ // default to the no access control plugin.
+ $access_plugin = views_get_plugin('access', 'none');
+ }
+
+ $access_str = $access_plugin->summary_title();
+
+ $options['access'] = array(
+ 'category' => 'access',
+ 'title' => t('Access'),
+ 'value' => $access_plugin->plugin_title(),
+ 'setting' => $access_str,
+ 'desc' => t('Specify access control type for this display.'),
+ );
+
+ if (!empty($access_plugin->definition['uses options'])) {
+ $options['access']['links']['access_options'] = t('Change settings for this access type.');
+ }
+
+ $cache_plugin = $this->get_plugin('cache');
+ if (!$cache_plugin) {
+ // default to the no cache control plugin.
+ $cache_plugin = views_get_plugin('cache', 'none');
+ }
+
+ $cache_str = $cache_plugin->summary_title();
+
+ $options['cache'] = array(
+ 'category' => 'other',
+ 'title' => t('Caching'),
+ 'value' => $cache_plugin->plugin_title(),
+ 'setting' => $cache_str,
+ 'desc' => t('Specify caching type for this display.'),
+ );
+
+ if (!empty($cache_plugin->definition['uses options'])) {
+ $options['cache']['links']['cache_options'] = t('Change settings for this caching type.');
+ }
+
+ if (!empty($access_plugin->definition['uses options'])) {
+ $options['access']['links']['access_options'] = t('Change settings for this access type.');
+ }
+
+ if ($this->uses_link_display()) {
+ $display_id = $this->get_link_display();
+ $link_display = empty($this->view->display[$display_id]) ? t('None') : check_plain($this->view->display[$display_id]->display_title);
+ $link_display = $this->get_option('link_display') == 'custom_url' ? t('Custom URL') : $link_display;
+ $options['link_display'] = array(
+ 'category' => 'other',
+ 'title' => t('Link display'),
+ 'value' => $link_display,
+ 'desc' => t('Specify which display or custom url this display will link to.'),
+ );
+ }
+
+ if ($this->uses_exposed_form_in_block()) {
+ $options['exposed_block'] = array(
+ 'category' => 'exposed',
+ 'title' => t('Exposed form in block'),
+ 'value' => $this->get_option('exposed_block') ? t('Yes') : t('No'),
+ 'desc' => t('Allow the exposed form to appear in a block instead of the view.'),
+ );
+ }
+
+ $exposed_form_plugin = $this->get_plugin('exposed_form');
+ if (!$exposed_form_plugin) {
+ // default to the no cache control plugin.
+ $exposed_form_plugin = views_get_plugin('exposed_form', 'basic');
+ }
+
+ $exposed_form_str = $exposed_form_plugin->summary_title();
+
+ $options['exposed_form'] = array(
+ 'category' => 'exposed',
+ 'title' => t('Exposed form style'),
+ 'value' => $exposed_form_plugin->plugin_title(),
+ 'setting' => $exposed_form_str,
+ 'desc' => t('Select the kind of exposed filter to use.'),
+ );
+
+ if (!empty($exposed_form_plugin->definition['uses options'])) {
+ $options['exposed_form']['links']['exposed_form_options'] = t('Exposed form settings for this exposed form style.');
+ }
+
+ $css_class = check_plain(trim($this->get_option('css_class')));
+ if (!$css_class) {
+ $css_class = t('None');
+ }
+
+ $options['css_class'] = array(
+ 'category' => 'other',
+ 'title' => t('CSS class'),
+ 'value' => $css_class,
+ 'desc' => t('Change the CSS class name(s) that will be added to this display.'),
+ );
+
+ $options['analyze-theme'] = array(
+ 'category' => 'other',
+ 'title' => t('Theme'),
+ 'value' => t('Information'),
+ 'desc' => t('Get information on how to theme this display'),
+ );
+
+ foreach ($this->extender as $extender) {
+ $extender->options_summary($categories, $options);
+ }
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ if ($this->defaultable_sections($form_state['section'])) {
+ views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+ }
+ $form['#title'] = check_plain($this->display->display_title) . ': ';
+
+ // Set the 'section' to highlight on the form.
+ // If it's the item we're looking at is pulling from the default display,
+ // reflect that. Don't use is_defaulted since we want it to show up even
+ // on the default display.
+ if (!empty($this->options['defaults'][$form_state['section']])) {
+ $form['#section'] = 'default-' . $form_state['section'];
+ }
+ else {
+ $form['#section'] = $this->display->id . '-' . $form_state['section'];
+ }
+
+ switch ($form_state['section']) {
+ case 'display_id':
+ $form['#title'] .= t('The machine name of this display');
+ $form['display_id'] = array(
+ '#type' => 'textfield',
+ '#description' => t('This is machine name of the display.'),
+ '#default_value' => !empty($this->display->new_id) ? $this->display->new_id : $this->display->id,
+ '#required' => TRUE,
+ '#size' => 64,
+ );
+ break;
+ case 'display_title':
+ $form['#title'] .= t('The name and the description of this display');
+ $form['display_title'] = array(
+ '#title' => t('Name'),
+ '#type' => 'textfield',
+ '#description' => t('This name will appear only in the administrative interface for the View.'),
+ '#default_value' => $this->display->display_title,
+ );
+ $form['display_description'] = array(
+ '#title' => t('Description'),
+ '#type' => 'textfield',
+ '#description' => t('This description will appear only in the administrative interface for the View.'),
+ '#default_value' => $this->get_option('display_description'),
+ );
+ break;
+ case 'display_comment':
+ $form['#title'] .= t("This display's comments");
+ $form['display_comment'] = array(
+ '#type' => 'textarea',
+ '#description' => t('This value will be seen and used only within the Views UI and can be used to document this display. You can use this to provide notes for other or future maintainers of your site about how or why this display is configured.'),
+ '#default_value' => $this->get_option('display_comment'),
+ );
+ break;
+ case 'title':
+ $form['#title'] .= t('The title of this view');
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#description' => t('This title will be displayed with the view, wherever titles are normally displayed; i.e, as the page title, block title, etc.'),
+ '#default_value' => $this->get_option('title'),
+ );
+ break;
+ case 'css_class':
+ $form['#title'] .= t('CSS class');
+ $form['css_class'] = array(
+ '#type' => 'textfield',
+ '#description' => t('The CSS class names will be added to the view. This enables you to use specific CSS code for each view. You may define multiples classes separated by spaces.'),
+ '#default_value' => $this->get_option('css_class'),
+ );
+ break;
+ case 'use_ajax':
+ $form['#title'] .= t('Use AJAX when available to load this view');
+ $form['description'] = array(
+ '#markup' => '<div class="description form-item">' . t('If set, this view will use an AJAX mechanism for paging, table sorting and exposed filters. This means the entire page will not refresh. It is not recommended that you use this if this view is the main content of the page as it will prevent deep linking to specific pages, but it is very useful for side content.') . '</div>',
+ );
+ $form['use_ajax'] = array(
+ '#type' => 'radios',
+ '#options' => array(1 => t('Yes'), 0 => t('No')),
+ '#default_value' => $this->get_option('use_ajax') ? 1 : 0,
+ );
+ break;
+ case 'hide_attachment_summary':
+ $form['#title'] .= t('Hide attachments when displaying a contextual filter summary');
+ $form['hide_attachment_summary'] = array(
+ '#type' => 'radios',
+ '#options' => array(1 => t('Yes'), 0 => t('No')),
+ '#default_value' => $this->get_option('hide_attachment_summary') ? 1 : 0,
+ );
+ break;
+ case 'hide_admin_links':
+ $form['#title'] .= t('Hide contextual links on this view.');
+ $form['hide_admin_links'] = array(
+ '#type' => 'radios',
+ '#options' => array(1 => t('Yes'), 0 => t('No')),
+ '#default_value' => $this->get_option('hide_admin_links') ? 1 : 0,
+ );
+ break;
+ case 'use_more':
+ $form['#title'] .= t('Add a more link to the bottom of the display.');
+ $form['use_more'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create more link'),
+ '#description' => t("This will add a more link to the bottom of this view, which will link to the page view. If you have more than one page view, the link will point to the display specified in 'Link display' section under advanced. You can override the url at the link display setting."),
+ '#default_value' => $this->get_option('use_more'),
+ );
+ $form['use_more_always'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("Display 'more' link only if there is more content"),
+ '#description' => t("Leave this unchecked to display the 'more' link even if there are no more items to display."),
+ '#default_value' => !$this->get_option('use_more_always'),
+ '#dependency' => array(
+ 'edit-use-more' => array(TRUE),
+ ),
+ );
+ $form['use_more_text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('More link text'),
+ '#description' => t("The text to display for the more link."),
+ '#default_value' => $this->get_option('use_more_text'),
+ '#dependency' => array(
+ 'edit-use-more' => array(TRUE),
+ ),
+ );
+ break;
+ case 'group_by':
+ $form['#title'] .= t('Allow grouping and aggregation (calculation) of fields.');
+ $form['group_by'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Aggregate'),
+ '#description' => t('If enabled, some fields may become unavailable. All fields that are selected for grouping will be collapsed to one record per distinct value. Other fields which are selected for aggregation will have the function run on them. For example, you can group nodes on title and count the number of nids in order to get a list of duplicate titles.'),
+ '#default_value' => $this->get_option('group_by'),
+ );
+ break;
+ case 'access':
+ $form['#title'] .= t('Access restrictions');
+ $form['access'] = array(
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+
+ $access = $this->get_option('access');
+ $form['access']['type'] = array(
+ '#type' => 'radios',
+ '#options' => views_fetch_plugin_names('access', NULL, array($this->view->base_table)),
+ '#default_value' => $access['type'],
+ );
+
+ $access_plugin = views_fetch_plugin_data('access', $access['type']);
+ if (!empty($access_plugin['uses options'])) {
+ $form['markup'] = array(
+ '#prefix' => '<div class="form-item description">',
+ '#markup' => t('You may also adjust the !settings for the currently selected access restriction.', array('!settings' => $this->option_link(t('settings'), 'access_options'))),
+ '#suffix' => '</div>',
+ );
+ }
+
+ break;
+ case 'access_options':
+ $access = $this->get_option('access');
+ $plugin = $this->get_plugin('access');
+ $form['#title'] .= t('Access options');
+ if ($plugin) {
+ if (!empty($plugin->definition['help topic'])) {
+ $form['#help_topic'] = $plugin->definition['help topic'];
+ }
+ if (!empty($plugin->definition['module'])) {
+ $form['#help_module'] = $plugin->definition['module'];
+ }
+
+ $form['access_options'] = array(
+ '#tree' => TRUE,
+ );
+ $form['access_options']['type'] = array(
+ '#type' => 'value',
+ '#value' => $access['type'],
+ );
+ $plugin->options_form($form['access_options'], $form_state);
+ }
+ break;
+ case 'cache':
+ $form['#title'] .= t('Caching');
+ $form['cache'] = array(
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+
+ $cache = $this->get_option('cache');
+ $form['cache']['type'] = array(
+ '#type' => 'radios',
+ '#options' => views_fetch_plugin_names('cache', NULL, array($this->view->base_table)),
+ '#default_value' => $cache['type'],
+ );
+
+ $cache_plugin = views_fetch_plugin_data('cache', $cache['type']);
+ if (!empty($cache_plugin['uses options'])) {
+ $form['markup'] = array(
+ '#prefix' => '<div class="form-item description">',
+ '#suffix' => '</div>',
+ '#markup' => t('You may also adjust the !settings for the currently selected cache mechanism.', array('!settings' => $this->option_link(t('settings'), 'cache_options'))),
+ );
+ }
+ break;
+ case 'cache_options':
+ $cache = $this->get_option('cache');
+ $plugin = $this->get_plugin('cache');
+ $form['#title'] .= t('Caching options');
+ if ($plugin) {
+ if (!empty($plugin->definition['help topic'])) {
+ $form['#help_topic'] = $plugin->definition['help topic'];
+ }
+ if (!empty($plugin->definition['module'])) {
+ $form['#help_module'] = $plugin->definition['module'];
+ }
+
+ $form['cache_options'] = array(
+ '#tree' => TRUE,
+ );
+ $form['cache_options']['type'] = array(
+ '#type' => 'value',
+ '#value' => $cache['type'],
+ );
+ $plugin->options_form($form['cache_options'], $form_state);
+ }
+ break;
+ case 'query':
+ $query_options = $this->get_option('query');
+ $plugin_name = $query_options['type'];
+
+ $form['#title'] .= t('Query options');
+ $this->view->init_query();
+ if ($this->view->query) {
+ if (!empty($this->view->query->definition['help topic'])) {
+ $form['#help_topic'] = $this->view->query->definition['help topic'];
+ }
+ if (!empty($this->view->query->definition['module'])) {
+ $form['#help_module'] = $this->view->query->definition['module'];
+ }
+
+ $form['query'] = array(
+ '#tree' => TRUE,
+ 'type' => array(
+ '#type' => 'value',
+ '#value' => $plugin_name,
+ ),
+ 'options' => array(
+ '#tree' => TRUE,
+ ),
+ );
+
+ $this->view->query->options_form($form['query']['options'], $form_state);
+ }
+ break;
+ case 'field_language':
+ $form['#title'] .= t('Field Language');
+
+ $entities = entity_get_info();
+ $entity_tables = array();
+ $has_translation_handlers = FALSE;
+ foreach ($entities as $type => $entity_info) {
+ $entity_tables[] = $entity_info['base table'];
+
+ if (!empty($entity_info['translation'])) {
+ $has_translation_handlers = TRUE;
+ }
+ }
+
+ // Doesn't make sense to show a field setting here if we aren't querying
+ // an entity base table. Also, we make sure that there's at least one
+ // entity type with a translation handler attached.
+ if (in_array($this->view->base_table, $entity_tables) && $has_translation_handlers) {
+ $languages = array(
+ '***CURRENT_LANGUAGE***' => t("Current user's language"),
+ '***DEFAULT_LANGUAGE***' => t("Default site language"),
+ LANGUAGE_NONE => t('Language neutral'),
+ );
+ $languages = array_merge($languages, views_language_list());
+
+ $form['field_language'] = array(
+ '#type' => 'select',
+ '#title' => t('Field Language'),
+ '#description' => t('All fields which support translations will be displayed in the selected language.'),
+ '#options' => $languages,
+ '#default_value' => $this->get_option('field_language'),
+ );
+ $form['field_language_add_to_query'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('When needed, add the field language condition to the query'),
+ '#default_value' => $this->get_option('field_language_add_to_query'),
+ );
+ }
+ else {
+ $form['field_language']['#markup'] = t("You don't have translatable entity types.");
+ }
+ break;
+ case 'style_plugin':
+ $form['#title'] .= t('How should this view be styled');
+ $form['#help_topic'] = 'style';
+ $form['style_plugin'] = array(
+ '#type' => 'radios',
+ '#options' => views_fetch_plugin_names('style', $this->get_style_type(), array($this->view->base_table)),
+ '#default_value' => $this->get_option('style_plugin'),
+ '#description' => t('If the style you choose has settings, be sure to click the settings button that will appear next to it in the View summary.'),
+ );
+
+ $style_plugin = views_fetch_plugin_data('style', $this->get_option('style_plugin'));
+ if (!empty($style_plugin['uses options'])) {
+ $form['markup'] = array(
+ '#markup' => '<div class="form-item description">' . t('You may also adjust the !settings for the currently selected style.', array('!settings' => $this->option_link(t('settings'), 'style_options'))) . '</div>',
+ );
+ }
+
+ break;
+ case 'style_options':
+ $form['#title'] .= t('Style options');
+ $style = TRUE;
+ $type = 'style_plugin';
+ $name = $this->get_option('style_plugin');
+
+ case 'row_options':
+ if (!isset($name)) {
+ $name = $this->get_option('row_plugin');
+ }
+ // if row, $style will be empty.
+ if (empty($style)) {
+ $form['#title'] .= t('Row style options');
+ $type = 'row_plugin';
+ }
+ $plugin = $this->get_plugin(empty($style) ? 'row' : 'style');
+ if ($plugin) {
+ if (!empty($plugin->definition['help topic'])) {
+ $form['#help_topic'] = $plugin->definition['help topic'];
+ }
+ if (!empty($plugin->definition['module'])) {
+ $form['#help_module'] = $plugin->definition['module'];
+ }
+ $form[$form_state['section']] = array(
+ '#tree' => TRUE,
+ );
+ $plugin->options_form($form[$form_state['section']], $form_state);
+ }
+ break;
+ case 'row_plugin':
+ $form['#title'] .= t('How should each row in this view be styled');
+ $form['#help_topic'] = 'style-row';
+ $form['row_plugin'] = array(
+ '#type' => 'radios',
+ '#options' => views_fetch_plugin_names('row', $this->get_style_type(), array($this->view->base_table)),
+ '#default_value' => $this->get_option('row_plugin'),
+ );
+
+ $row_plugin = views_fetch_plugin_data('row', $this->get_option('row_plugin'));
+ if (!empty($row_plugin['uses options'])) {
+ $form['markup'] = array(
+ '#markup' => '<div class="form-item description">' . t('You may also adjust the !settings for the currently selected row style.', array('!settings' => $this->option_link(t('settings'), 'row_options'))) . '</div>',
+ );
+ }
+
+ break;
+ case 'link_display':
+ $form['#title'] .= t('Which display to use for path');
+ foreach ($this->view->display as $display_id => $display) {
+ if ($display->handler->has_path()) {
+ $options[$display_id] = $display->display_title;
+ }
+ }
+ $options['custom_url'] = t('Custom URL');
+ if (count($options)) {
+ $form['link_display'] = array(
+ '#type' => 'radios',
+ '#options' => $options,
+ '#description' => t("Which display to use to get this display's path for things like summary links, rss feed links, more links, etc."),
+ '#default_value' => $this->get_option('link_display'),
+ );
+ }
+
+ $options = array();
+ $count = 0; // This lets us prepare the key as we want it printed.
+ foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
+ $options[t('Arguments')]['%' . ++$count] = t('@argument title', array('@argument' => $handler->ui_name()));
+ $options[t('Arguments')]['!' . $count] = t('@argument input', array('@argument' => $handler->ui_name()));
+ }
+
+ // Default text.
+ // We have some options, so make a list.
+ $output = '';
+ if (!empty($options)) {
+ $output = t('<p>The following tokens are available for this link.</p>');
+ foreach (array_keys($options) as $type) {
+ if (!empty($options[$type])) {
+ $items = array();
+ foreach ($options[$type] as $key => $value) {
+ $items[] = $key . ' == ' . $value;
+ }
+ $output .= theme('item_list',
+ array(
+ 'items' => $items,
+ 'type' => $type
+ ));
+ }
+ }
+ }
+
+ $form['link_url'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Custom URL'),
+ '#default_value' => $this->get_option('link_url'),
+ '#description' => t('A Drupal path or external URL the more link will point to. Note that this will override the link display setting above.') . $output,
+ '#dependency' => array('radio:link_display' => array('custom_url')),
+ );
+ break;
+ case 'analyze-theme':
+ $form['#title'] .= t('Theming information');
+ $form['#help_topic'] = 'analyze-theme';
+
+ if (isset($_POST['theme'])) {
+ $this->theme = $_POST['theme'];
+ }
+ elseif (empty($this->theme)) {
+ $this->theme = variable_get('theme_default', 'bartik');
+ }
+
+ if (isset($GLOBALS['theme']) && $GLOBALS['theme'] == $this->theme) {
+ $this->theme_registry = theme_get_registry();
+ $theme_engine = $GLOBALS['theme_engine'];
+ }
+ else {
+ $themes = list_themes();
+ $theme = $themes[$this->theme];
+
+ // Find all our ancestor themes and put them in an array.
+ $base_theme = array();
+ $ancestor = $this->theme;
+ while ($ancestor && isset($themes[$ancestor]->base_theme)) {
+ $ancestor = $themes[$ancestor]->base_theme;
+ $base_theme[] = $themes[$ancestor];
+ }
+
+ // The base themes should be initialized in the right order.
+ $base_theme = array_reverse($base_theme);
+
+ // This code is copied directly from _drupal_theme_initialize()
+ $theme_engine = NULL;
+
+ // Initialize the theme.
+ if (isset($theme->engine)) {
+ // Include the engine.
+ include_once DRUPAL_ROOT . '/' . $theme->owner;
+
+ $theme_engine = $theme->engine;
+ if (function_exists($theme_engine . '_init')) {
+ foreach ($base_theme as $base) {
+ call_user_func($theme_engine . '_init', $base);
+ }
+ call_user_func($theme_engine . '_init', $theme);
+ }
+ }
+ else {
+ // include non-engine theme files
+ foreach ($base_theme as $base) {
+ // Include the theme file or the engine.
+ if (!empty($base->owner)) {
+ include_once DRUPAL_ROOT . '/' . $base->owner;
+ }
+ }
+ // and our theme gets one too.
+ if (!empty($theme->owner)) {
+ include_once DRUPAL_ROOT . '/' . $theme->owner;
+ }
+ }
+ $this->theme_registry = _theme_load_registry($theme, $base_theme, $theme_engine);
+ }
+
+ // If there's a theme engine involved, we also need to know its extension
+ // so we can give the proper filename.
+ $this->theme_extension = '.tpl.php';
+ if (isset($theme_engine)) {
+ $extension_function = $theme_engine . '_extension';
+ if (function_exists($extension_function)) {
+ $this->theme_extension = $extension_function();
+ }
+ }
+
+ $funcs = array();
+ // Get theme functions for the display. Note that some displays may
+ // not have themes. The 'feed' display, for example, completely
+ // delegates to the style.
+ if (!empty($this->definition['theme'])) {
+ $funcs[] = $this->option_link(t('Display output'), 'analyze-theme-display') . ': ' . $this->format_themes($this->theme_functions());
+ $themes = $this->additional_theme_functions();
+ if ($themes) {
+ foreach ($themes as $theme) {
+ $funcs[] = $this->option_link(t('Alternative display output'), 'analyze-theme-display') . ': ' . $this->format_themes($theme);
+ }
+ }
+ }
+
+ $plugin = $this->get_plugin();
+ if ($plugin) {
+ $funcs[] = $this->option_link(t('Style output'), 'analyze-theme-style') . ': ' . $this->format_themes($plugin->theme_functions(), $plugin->additional_theme_functions());
+ $themes = $plugin->additional_theme_functions();
+ if ($themes) {
+ foreach ($themes as $theme) {
+ $funcs[] = $this->option_link(t('Alternative style'), 'analyze-theme-style') . ': ' . $this->format_themes($theme);
+ }
+ }
+
+ if ($plugin->uses_row_plugin()) {
+ $row_plugin = $this->get_plugin('row');
+ if ($row_plugin) {
+ $funcs[] = $this->option_link(t('Row style output'), 'analyze-theme-row') . ': ' . $this->format_themes($row_plugin->theme_functions());
+ $themes = $row_plugin->additional_theme_functions();
+ if ($themes) {
+ foreach ($themes as $theme) {
+ $funcs[] = $this->option_link(t('Alternative row style'), 'analyze-theme-row') . ': ' . $this->format_themes($theme);
+ }
+ }
+ }
+ }
+
+ if ($plugin->uses_fields()) {
+ foreach ($this->get_handlers('field') as $id => $handler) {
+ $funcs[] = $this->option_link(t('Field @field (ID: @id)', array('@field' => $handler->ui_name(), '@id' => $id)), 'analyze-theme-field') . ': ' . $this->format_themes($handler->theme_functions());
+ }
+ }
+ }
+
+ $form['important'] = array(
+ '#markup' => '<div class="form-item description"><p>' . t('This section lists all possible templates for the display plugin and for the style plugins, ordered roughly from the least specific to the most specific. The active template for each plugin -- which is the most specific template found on the system -- is highlighted in bold.') . '</p></div>',
+ );
+
+ if (isset($this->view->display[$this->view->current_display]->new_id)) {
+ $form['important']['new_id'] = array(
+ '#prefix' => '<div class="description">',
+ '#suffix' => '</div>',
+ '#value' => t("<strong>Important!</strong> You have changed the display's machine name. Anything that attached to this display specifically, such as theming, may stop working until it is updated. To see theme suggestions for it, you need to save the view."),
+ );
+ }
+
+ foreach (list_themes() as $key => $theme) {
+ if (!empty($theme->info['hidden'])) {
+ continue;
+ }
+ $options[$key] = $theme->info['name'];
+ }
+
+ $form['box'] = array(
+ '#prefix' => '<div class="container-inline">',
+ '#suffix' => '</div>',
+ );
+ $form['box']['theme'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => $this->theme,
+ );
+
+ $form['box']['change'] = array(
+ '#type' => 'submit',
+ '#value' => t('Change theme'),
+ '#submit' => array('views_ui_edit_display_form_change_theme'),
+ );
+
+ $form['analysis'] = array(
+ '#markup' => '<div class="form-item">' . theme('item_list', array('items' => $funcs)) . '</div>',
+ );
+
+ $form['rescan_button'] = array(
+ '#prefix' => '<div class="form-item">',
+ '#suffix' => '</div>',
+ );
+ $form['rescan_button']['button'] = array(
+ '#type' => 'submit',
+ '#value' => t('Rescan template files'),
+ '#submit' => array('views_ui_config_item_form_rescan'),
+ );
+ $form['rescan_button']['markup'] = array(
+ '#markup' => '<div class="description">' . t("<strong>Important!</strong> When adding, removing, or renaming template files, it is necessary to make Drupal aware of the changes by making it rescan the files on your system. By clicking this button you clear Drupal's theme registry and thereby trigger this rescanning process. The highlighted templates above will then reflect the new state of your system.") . '</div>',
+ );
+
+ $form_state['ok_button'] = TRUE;
+ break;
+ case 'analyze-theme-display':
+ $form['#title'] .= t('Theming information (display)');
+ $output = '<p>' . t('Back to !info.', array('!info' => $this->option_link(t('theming information'), 'analyze-theme'))) . '</p>';
+
+ if (empty($this->definition['theme'])) {
+ $output .= t('This display has no theming information');
+ }
+ else {
+ $output .= '<p>' . t('This is the default theme template used for this display.') . '</p>';
+ $output .= '<pre>' . check_plain(file_get_contents('./' . $this->definition['theme path'] . '/' . strtr($this->definition['theme'], '_', '-') . '.tpl.php')) . '</pre>';
+ }
+
+ if (!empty($this->definition['additional themes'])) {
+ foreach ($this->definition['additional themes'] as $theme => $type) {
+ $output .= '<p>' . t('This is an alternative template for this display.') . '</p>';
+ $output .= '<pre>' . check_plain(file_get_contents('./' . $this->definition['theme path'] . '/' . strtr($theme, '_', '-') . '.tpl.php')) . '</pre>';
+ }
+ }
+
+ $form['analysis'] = array(
+ '#markup' => '<div class="form-item">' . $output . '</div>',
+ );
+
+ $form_state['ok_button'] = TRUE;
+ break;
+ case 'analyze-theme-style':
+ $form['#title'] .= t('Theming information (style)');
+ $output = '<p>' . t('Back to !info.', array('!info' => $this->option_link(t('theming information'), 'analyze-theme'))) . '</p>';
+
+ $plugin = $this->get_plugin();
+
+ if (empty($plugin->definition['theme'])) {
+ $output .= t('This display has no style theming information');
+ }
+ else {
+ $output .= '<p>' . t('This is the default theme template used for this style.') . '</p>';
+ $output .= '<pre>' . check_plain(file_get_contents('./' . $plugin->definition['theme path'] . '/' . strtr($plugin->definition['theme'], '_', '-') . '.tpl.php')) . '</pre>';
+ }
+
+ if (!empty($plugin->definition['additional themes'])) {
+ foreach ($plugin->definition['additional themes'] as $theme => $type) {
+ $output .= '<p>' . t('This is an alternative template for this style.') . '</p>';
+ $output .= '<pre>' . check_plain(file_get_contents('./' . $plugin->definition['theme path'] . '/' . strtr($theme, '_', '-') . '.tpl.php')) . '</pre>';
+ }
+ }
+
+ $form['analysis'] = array(
+ '#markup' => '<div class="form-item">' . $output . '</div>',
+ );
+
+ $form_state['ok_button'] = TRUE;
+ break;
+ case 'analyze-theme-row':
+ $form['#title'] .= t('Theming information (row style)');
+ $output = '<p>' . t('Back to !info.', array('!info' => $this->option_link(t('theming information'), 'analyze-theme'))) . '</p>';
+
+ $plugin = $this->get_plugin('row');
+
+ if (empty($plugin->definition['theme'])) {
+ $output .= t('This display has no row style theming information');
+ }
+ else {
+ $output .= '<p>' . t('This is the default theme template used for this row style.') . '</p>';
+ $output .= '<pre>' . check_plain(file_get_contents('./' . $plugin->definition['theme path'] . '/' . strtr($plugin->definition['theme'], '_', '-') . '.tpl.php')) . '</pre>';
+ }
+
+ if (!empty($plugin->definition['additional themes'])) {
+ foreach ($plugin->definition['additional themes'] as $theme => $type) {
+ $output .= '<p>' . t('This is an alternative template for this row style.') . '</p>';
+ $output .= '<pre>' . check_plain(file_get_contents('./' . $plugin->definition['theme path'] . '/' . strtr($theme, '_', '-') . '.tpl.php')) . '</pre>';
+ }
+ }
+
+ $form['analysis'] = array(
+ '#markup' => '<div class="form-item">' . $output . '</div>',
+ );
+
+ $form_state['ok_button'] = TRUE;
+ break;
+ case 'analyze-theme-field':
+ $form['#title'] .= t('Theming information (row style)');
+ $output = '<p>' . t('Back to !info.', array('!info' => $this->option_link(t('theming information'), 'analyze-theme'))) . '</p>';
+
+ $output .= '<p>' . t('This is the default theme template used for this row style.') . '</p>';
+
+ // Field templates aren't registered the normal way...and they're always
+ // this one, anyhow.
+ $output .= '<pre>' . check_plain(file_get_contents(drupal_get_path('module', 'views') . '/theme/views-view-field.tpl.php')) . '</pre>';
+
+ $form['analysis'] = array(
+ '#markup' => '<div class="form-item">' . $output . '</div>',
+ );
+ $form_state['ok_button'] = TRUE;
+ break;
+
+ case 'exposed_block':
+ $form['#title'] .= t('Put the exposed form in a block');
+ $form['description'] = array(
+ '#markup' => '<div class="description form-item">' . t('If set, any exposed widgets will not appear with this view. Instead, a block will be made available to the Drupal block administration system, and the exposed form will appear there. Note that this block must be enabled manually, Views will not enable it for you.') . '</div>',
+ );
+ $form['exposed_block'] = array(
+ '#type' => 'radios',
+ '#options' => array(1 => t('Yes'), 0 => t('No')),
+ '#default_value' => $this->get_option('exposed_block') ? 1 : 0,
+ );
+ break;
+ case 'exposed_form':
+ $form['#title'] .= t('Exposed Form');
+ $form['exposed_form'] = array(
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+
+ $exposed_form = $this->get_option('exposed_form');
+ $form['exposed_form']['type'] = array(
+ '#type' => 'radios',
+ '#options' => views_fetch_plugin_names('exposed_form', NULL, array($this->view->base_table)),
+ '#default_value' => $exposed_form['type'],
+ );
+
+ $exposed_form_plugin = views_fetch_plugin_data('exposed_form', $exposed_form['type']);
+ if (!empty($exposed_form_plugin['uses options'])) {
+ $form['markup'] = array(
+ '#prefix' => '<div class="form-item description">',
+ '#suffix' => '</div>',
+ '#markup' => t('You may also adjust the !settings for the currently selected style.', array('!settings' => $this->option_link(t('settings'), 'exposed_form_options'))),
+ );
+ }
+ break;
+ case 'exposed_form_options':
+ $plugin = $this->get_plugin('exposed_form');
+ $form['#title'] .= t('Exposed form options');
+ if ($plugin) {
+ if (!empty($plugin->definition['help topic'])) {
+ $form['#help_topic'] = $plugin->definition['help topic'];
+ }
+ if (!empty($plugin->definition['module'])) {
+ $form['#help_module'] = $plugin->definition['module'];
+ }
+
+ $form['exposed_form_options'] = array(
+ '#tree' => TRUE,
+ );
+ $plugin->options_form($form['exposed_form_options'], $form_state);
+ }
+ break;
+ case 'pager':
+ $form['#title'] .= t('Select which pager, if any, to use for this view');
+ $form['pager'] = array(
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+
+ $pager = $this->get_option('pager');
+ $form['pager']['type'] = array(
+ '#type' => 'radios',
+ '#options' => views_fetch_plugin_names('pager', empty($this->definition['use pager']) ? 'basic' : NULL, array($this->view->base_table)),
+ '#default_value' => $pager['type'],
+ );
+
+ $pager_plugin = views_fetch_plugin_data('pager', $pager['type']);
+ if (!empty($pager_plugin['uses options'])) {
+ $form['markup'] = array(
+ '#prefix' => '<div class="form-item description">',
+ '#suffix' => '</div>',
+ '#markup' => t('You may also adjust the !settings for the currently selected pager.', array('!settings' => $this->option_link(t('settings'), 'pager_options'))),
+ );
+ }
+
+ break;
+ case 'pager_options':
+ $plugin = $this->get_plugin('pager');
+ $form['#title'] .= t('Pager options');
+ if ($plugin) {
+ if (!empty($plugin->definition['help topic'])) {
+ $form['#help_topic'] = $plugin->definition['help topic'];
+ }
+ if (!empty($plugin->definition['module'])) {
+ $form['#help_module'] = $plugin->definition['module'];
+ }
+
+ $form['pager_options'] = array(
+ '#tree' => TRUE,
+ );
+ $plugin->options_form($form['pager_options'], $form_state);
+ }
+ break;
+ }
+
+ foreach ($this->extender as $extender) {
+ $extender->options_form($form, $form_state);
+ }
+ }
+
+ /**
+ * Format a list of theme templates for output by the theme info helper.
+ */
+ function format_themes($themes) {
+ $registry = $this->theme_registry;
+ $extension = $this->theme_extension;
+
+ $output = '';
+ $picked = FALSE;
+ foreach ($themes as $theme) {
+ $template = strtr($theme, '_', '-') . $extension;
+ if (!$picked && !empty($registry[$theme])) {
+ $template_path = isset($registry[$theme]['path']) ? $registry[$theme]['path'] . '/' : './';
+ if (file_exists($template_path . $template)) {
+ $hint = t('File found in folder @template-path', array('@template-path' => $template_path));
+ $template = '<strong title="'. $hint .'">' . $template . '</strong>';
+ }
+ else {
+ $template = '<strong class="error">' . $template . ' ' . t('(File not found, in folder @template-path)', array('@template-path' => $template_path)) . '</strong>';
+ }
+ $picked = TRUE;
+ }
+ $fixed[] = $template;
+ }
+
+ return implode(', ', array_reverse($fixed));
+ }
+
+ /**
+ * Validate the options form.
+ */
+ function options_validate(&$form, &$form_state) {
+ switch ($form_state['section']) {
+ case 'display_title':
+ if (empty($form_state['values']['display_title'])) {
+ form_error($form['display_title'], t('Display title may not be empty.'));
+ }
+ break;
+ case 'css_class':
+ $css_class = $form_state['values']['css_class'];
+ if (preg_match('/[^a-zA-Z0-9-_ ]/', $css_class)) {
+ form_error($form['css_class'], t('CSS classes must be alphanumeric or dashes only.'));
+ }
+ break;
+ case 'display_id':
+ if ($form_state['values']['display_id']) {
+ if (preg_match('/[^a-z0-9_]/', $form_state['values']['display_id'])) {
+ form_error($form['display_id'], t('Display name must be letters, numbers, or underscores only.'));
+ }
+
+ foreach ($this->view->display as $id => $display) {
+ if ($id != $this->view->current_display && ($form_state['values']['display_id'] == $id || (isset($display->new_id) && $form_state['values']['display_id'] == $display->new_id))) {
+ form_error($form['display_id'], t('Display id should be unique.'));
+ }
+ }
+ }
+ break;
+ case 'style_options':
+ $style = TRUE;
+ case 'row_options':
+ // if row, $style will be empty.
+ $plugin = $this->get_plugin(empty($style) ? 'row' : 'style');
+ if ($plugin) {
+ $plugin->options_validate($form[$form_state['section']], $form_state);
+ }
+ break;
+ case 'access_options':
+ $plugin = $this->get_plugin('access');
+ if ($plugin) {
+ $plugin->options_validate($form['access_options'], $form_state);
+ }
+ break;
+ case 'query':
+ if ($this->view->query) {
+ $this->view->query->options_validate($form['query'], $form_state);
+ }
+ break;
+ case 'cache_options':
+ $plugin = $this->get_plugin('cache');
+ if ($plugin) {
+ $plugin->options_validate($form['cache_options'], $form_state);
+ }
+ break;
+ case 'exposed_form_options':
+ $plugin = $this->get_plugin('exposed_form');
+ if ($plugin) {
+ $plugin->options_validate($form['exposed_form_options'], $form_state);
+ }
+ break;
+ case 'pager_options':
+ $plugin = $this->get_plugin('pager');
+ if ($plugin) {
+ $plugin->options_validate($form['pager_options'], $form_state);
+ }
+ break;
+ }
+
+ foreach ($this->extender as $extender) {
+ $extender->options_validate($form, $form_state);
+ }
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) {
+ // Not sure I like this being here, but it seems (?) like a logical place.
+ $cache_plugin = $this->get_plugin('cache');
+ if ($cache_plugin) {
+ $cache_plugin->cache_flush();
+ }
+
+ $section = $form_state['section'];
+ switch ($section) {
+ case 'display_id':
+ if (isset($form_state['values']['display_id'])) {
+ $this->display->new_id = $form_state['values']['display_id'];
+ }
+ break;
+ case 'display_title':
+ $this->display->display_title = $form_state['values']['display_title'];
+ $this->set_option('display_description', $form_state['values']['display_description']);
+ break;
+ case 'access':
+ $access = $this->get_option('access');
+ if ($access['type'] != $form_state['values']['access']['type']) {
+ $plugin = views_get_plugin('access', $form_state['values']['access']['type']);
+ if ($plugin) {
+ $access = array('type' => $form_state['values']['access']['type']);
+ $this->set_option('access', $access);
+ if (!empty($plugin->definition['uses options'])) {
+ views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('access_options'));
+ }
+ }
+ }
+
+ break;
+ case 'access_options':
+ $plugin = views_get_plugin('access', $form_state['values'][$section]['type']);
+ if ($plugin) {
+ $plugin->options_submit($form['access_options'], $form_state);
+ $this->set_option('access', $form_state['values'][$section]);
+ }
+ break;
+ case 'cache':
+ $cache = $this->get_option('cache');
+ if ($cache['type'] != $form_state['values']['cache']['type']) {
+ $plugin = views_get_plugin('cache', $form_state['values']['cache']['type']);
+ if ($plugin) {
+ $cache = array('type' => $form_state['values']['cache']['type']);
+ $this->set_option('cache', $cache);
+ if (!empty($plugin->definition['uses options'])) {
+ views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('cache_options'));
+ }
+ }
+ }
+
+ break;
+ case 'cache_options':
+ $plugin = views_get_plugin('cache', $form_state['values'][$section]['type']);
+ if ($plugin) {
+ $plugin->options_submit($form['cache_options'], $form_state);
+ $this->set_option('cache', $form_state['values'][$section]);
+ }
+ break;
+ case 'query':
+ $plugin = $this->get_plugin('query');
+ if ($plugin) {
+ $plugin->options_submit($form['query']['options'], $form_state);
+ $this->set_option('query', $form_state['values'][$section]);
+ }
+ break;
+
+ case 'link_display':
+ $this->set_option('link_url', $form_state['values']['link_url']);
+ case 'title':
+ case 'css_class':
+ case 'display_comment':
+ $this->set_option($section, $form_state['values'][$section]);
+ break;
+ case 'field_language':
+ $this->set_option('field_language', $form_state['values']['field_language']);
+ $this->set_option('field_language_add_to_query', $form_state['values']['field_language_add_to_query']);
+ break;
+ case 'use_ajax':
+ case 'hide_attachment_summary':
+ case 'hide_admin_links':
+ $this->set_option($section, (bool)$form_state['values'][$section]);
+ break;
+ case 'use_more':
+ $this->set_option($section, intval($form_state['values'][$section]));
+ $this->set_option('use_more_always', !intval($form_state['values']['use_more_always']));
+ $this->set_option('use_more_text', $form_state['values']['use_more_text']);
+ case 'distinct':
+ $this->set_option($section, $form_state['values'][$section]);
+ break;
+ case 'group_by':
+ $this->set_option($section, $form_state['values'][$section]);
+ break;
+ case 'row_plugin':
+ // This if prevents resetting options to default if they don't change
+ // the plugin.
+ if ($this->get_option($section) != $form_state['values'][$section]) {
+ $plugin = views_get_plugin('row', $form_state['values'][$section]);
+ if ($plugin) {
+ $this->set_option($section, $form_state['values'][$section]);
+ $this->set_option('row_options', array());
+
+ // send ajax form to options page if we use it.
+ if (!empty($plugin->definition['uses options'])) {
+ views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('row_options'));
+ }
+ }
+ }
+ break;
+ case 'style_plugin':
+ // This if prevents resetting options to default if they don't change
+ // the plugin.
+ if ($this->get_option($section) != $form_state['values'][$section]) {
+ $plugin = views_get_plugin('style', $form_state['values'][$section]);
+ if ($plugin) {
+ $this->set_option($section, $form_state['values'][$section]);
+ $this->set_option('style_options', array());
+ // send ajax form to options page if we use it.
+ if (!empty($plugin->definition['uses options'])) {
+ views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('style_options'));
+ }
+ }
+ }
+ break;
+ case 'style_options':
+ $style = TRUE;
+ case 'row_options':
+ // if row, $style will be empty.
+ $plugin = $this->get_plugin(empty($style) ? 'row' : 'style');
+ if ($plugin) {
+ $plugin->options_submit($form['options'][$section], $form_state);
+ }
+ $this->set_option($section, $form_state['values'][$section]);
+ break;
+ case 'exposed_block':
+ $this->set_option($section, (bool) $form_state['values'][$section]);
+ break;
+ case 'exposed_form':
+ $exposed_form = $this->get_option('exposed_form');
+ if ($exposed_form['type'] != $form_state['values']['exposed_form']['type']) {
+ $plugin = views_get_plugin('exposed_form', $form_state['values']['exposed_form']['type']);
+ if ($plugin) {
+ $exposed_form = array('type' => $form_state['values']['exposed_form']['type'], 'options' => array());
+ $this->set_option('exposed_form', $exposed_form);
+ if (!empty($plugin->definition['uses options'])) {
+ views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('exposed_form_options'));
+ }
+ }
+ }
+
+ break;
+ case 'exposed_form_options':
+ $plugin = $this->get_plugin('exposed_form');
+ if ($plugin) {
+ $exposed_form = $this->get_option('exposed_form');
+ $plugin->options_submit($form['exposed_form_options'], $form_state);
+ $exposed_form['options'] = $form_state['values'][$section];
+ $this->set_option('exposed_form', $exposed_form);
+ }
+ break;
+ case 'pager':
+ $pager = $this->get_option('pager');
+ if ($pager['type'] != $form_state['values']['pager']['type']) {
+ $plugin = views_get_plugin('pager', $form_state['values']['pager']['type']);
+ if ($plugin) {
+ // Because pagers have very similar options, let's allow pagers to
+ // try to carry the options over.
+ $plugin->init($this->view, $this->display, $pager['options']);
+
+ $pager = array('type' => $form_state['values']['pager']['type'], 'options' => $plugin->options);
+ $this->set_option('pager', $pager);
+ if (!empty($plugin->definition['uses options'])) {
+ views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('pager_options'));
+ }
+ }
+ }
+
+ break;
+ case 'pager_options':
+ $plugin = $this->get_plugin('pager');
+ if ($plugin) {
+ $pager = $this->get_option('pager');
+ $plugin->options_submit($form['pager_options'], $form_state);
+ $pager['options'] = $form_state['values'][$section];
+ $this->set_option('pager', $pager);
+ }
+ break;
+ }
+
+ foreach ($this->extender as $extender) {
+ $extender->options_submit($form, $form_state);
+ }
+ }
+
+ /**
+ * If override/revert was clicked, perform the proper toggle.
+ */
+ function options_override($form, &$form_state) {
+ $this->set_override($form_state['section']);
+ }
+
+ /**
+ * Flip the override setting for the given section.
+ *
+ * @param string $section
+ * Which option should be marked as overridden, for example "filters".
+ * @param bool $new_state
+ * Select the new state of the option.
+ * - TRUE: Revert to default.
+ * - FALSE: Mark it as overridden.
+ */
+ function set_override($section, $new_state = NULL) {
+ $options = $this->defaultable_sections($section);
+ if (!$options) {
+ return;
+ }
+
+ if (!isset($new_state)) {
+ $new_state = empty($this->options['defaults'][$section]);
+ }
+
+ // For each option that is part of this group, fix our settings.
+ foreach ($options as $option) {
+ if ($new_state) {
+ // Revert to defaults.
+ unset($this->options[$option]);
+ unset($this->display->display_options[$option]);
+ }
+ else {
+ // copy existing values into our display.
+ $this->options[$option] = $this->get_option($option);
+ $this->display->display_options[$option] = $this->options[$option];
+ }
+ $this->options['defaults'][$option] = $new_state;
+ $this->display->display_options['defaults'][$option] = $new_state;
+ }
+ }
+
+ /**
+ * Inject anything into the query that the display handler needs.
+ */
+ function query() {
+ foreach ($this->extender as $extender) {
+ $extender->query();
+ }
+ }
+
+ /**
+ * Not all display plugins will support filtering
+ */
+ function render_filters() { }
+
+ /**
+ * Not all display plugins will suppert pager rendering.
+ */
+ function render_pager() {
+ return TRUE;
+ }
+
+ /**
+ * Render the 'more' link
+ */
+ function render_more_link() {
+ if ($this->use_more() && ($this->use_more_always() || (!empty($this->view->query->pager) && $this->view->query->pager->has_more_records()))) {
+ $path = $this->get_path();
+
+ if ($this->get_option('link_display') == 'custom_url' && $override_path = $this->get_option('link_url')) {
+ $tokens = $this->get_arguments_tokens();
+ $path = strtr($override_path, $tokens);
+ }
+
+ if ($path) {
+ if (empty($override_path)) {
+ $path = $this->view->get_url(NULL, $path);
+ }
+ $url_options = array();
+ if (!empty($this->view->exposed_raw_input)) {
+ $url_options['query'] = $this->view->exposed_raw_input;
+ }
+ $theme = views_theme_functions('views_more', $this->view, $this->display);
+ $path = check_url(url($path, $url_options));
+
+ return theme($theme, array('more_url' => $path, 'link_text' => check_plain($this->use_more_text()), 'view' => $this->view));
+ }
+ }
+ }
+
+
+ /**
+ * Legacy functions.
+ */
+
+ /**
+ * Render the header of the view.
+ */
+ function render_header() {
+ $empty = !empty($this->view->result);
+ return $this->render_area('header', $empty);
+ }
+
+ /**
+ * Render the footer of the view.
+ */
+ function render_footer() {
+ $empty = !empty($this->view->result);
+ return $this->render_area('footer', $empty);
+ }
+
+ function render_empty() {
+ return $this->render_area('empty');
+ }
+
+ /**
+ * If this display creates a block, implement one of these.
+ */
+ function hook_block_list($delta = 0, $edit = array()) { return array(); }
+
+ /**
+ * If this display creates a page with a menu item, implement it here.
+ */
+ function hook_menu() { return array(); }
+
+ /**
+ * Render this display.
+ */
+ function render() {
+ return theme($this->theme_functions(), array('view' => $this->view));
+ }
+
+ function render_area($area, $empty = FALSE) {
+ $return = '';
+ foreach ($this->get_handlers($area) as $area) {
+ $return .= $area->render($empty);
+ }
+ return $return;
+ }
+
+
+ /**
+ * Determine if the user has access to this display of the view.
+ */
+ function access($account = NULL) {
+ if (!isset($account)) {
+ global $user;
+ $account = $user;
+ }
+
+ // Full override.
+ if (user_access('access all views', $account)) {
+ return TRUE;
+ }
+
+ $plugin = $this->get_plugin('access');
+ if ($plugin) {
+ return $plugin->access($account);
+ }
+
+ // fallback to all access if no plugin.
+ return TRUE;
+ }
+
+ /**
+ * Set up any variables on the view prior to execution. These are separated
+ * from execute because they are extremely common and unlikely to be
+ * overridden on an individual display.
+ */
+ function pre_execute() {
+ $this->view->set_use_ajax($this->use_ajax());
+ if ($this->use_more() && !$this->use_more_always()) {
+ $this->view->get_total_rows = TRUE;
+ }
+ $this->view->init_handlers();
+ if ($this->uses_exposed()) {
+ $exposed_form = $this->get_plugin('exposed_form');
+ $exposed_form->pre_execute();
+ }
+
+ foreach ($this->extender as $extender) {
+ $extender->pre_execute();
+ }
+
+ if ($this->get_option('hide_admin_links')) {
+ $this->view->hide_admin_links = TRUE;
+ }
+ }
+
+ /**
+ * When used externally, this is how a view gets run and returns
+ * data in the format required.
+ *
+ * The base class cannot be executed.
+ */
+ function execute() { }
+
+ /**
+ * Fully render the display for the purposes of a live preview or
+ * some other AJAXy reason.
+ */
+ function preview() { return $this->view->render(); }
+
+ /**
+ * Displays can require a certain type of style plugin. By default, they will
+ * be 'normal'.
+ */
+ function get_style_type() { return 'normal'; }
+
+ /**
+ * Make sure the display and all associated handlers are valid.
+ *
+ * @return
+ * Empty array if the display is valid; an array of error strings if it is not.
+ */
+ function validate() {
+ $errors = array();
+ // Make sure displays that use fields HAVE fields.
+ if ($this->uses_fields()) {
+ $fields = FALSE;
+ foreach ($this->get_handlers('field') as $field) {
+ if (empty($field->options['exclude'])) {
+ $fields = TRUE;
+ }
+ }
+
+ if (!$fields) {
+ $errors[] = t('Display "@display" uses fields but there are none defined for it or all are excluded.', array('@display' => $this->display->display_title));
+ }
+ }
+
+ if ($this->has_path() && !$this->get_option('path')) {
+ $errors[] = t('Display "@display" uses a path but the path is undefined.', array('@display' => $this->display->display_title));
+ }
+
+ // Validate style plugin
+ $style = $this->get_plugin();
+ if (empty($style)) {
+ $errors[] = t('Display "@display" has an invalid style plugin.', array('@display' => $this->display->display_title));
+ }
+ else {
+ $result = $style->validate();
+ if (!empty($result) && is_array($result)) {
+ $errors = array_merge($errors, $result);
+ }
+ }
+
+ // Validate query plugin.
+ $query = $this->get_plugin('query');
+ $result = $query->validate();
+ if (!empty($result) && is_array($result)) {
+ $errors = array_merge($errors, $result);
+ }
+
+ // Validate handlers
+ foreach (views_object_types() as $type => $info) {
+ foreach ($this->get_handlers($type) as $handler) {
+ $result = $handler->validate();
+ if (!empty($result) && is_array($result)) {
+ $errors = array_merge($errors, $result);
+ }
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Check if the provided identifier is unique.
+ *
+ * @param string $id
+ * The id of the handler which is checked.
+ * @param string $identifier
+ * The actual get identifier configured in the exposed settings.
+ *
+ * @return bool
+ * Returns whether the identifier is unique on all handlers.
+ *
+ */
+ function is_identifier_unique($id, $identifier) {
+ foreach (views_object_types() as $type => $info) {
+ foreach ($this->get_handlers($type) as $key => $handler) {
+ if ($handler->can_expose() && $handler->is_exposed()) {
+ if ($handler->is_a_group()) {
+ if ($id != $key && $identifier == $handler->options['group_info']['identifier']) {
+ return FALSE;
+ }
+ }
+ else {
+ if ($id != $key && $identifier == $handler->options['expose']['identifier']) {
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+ return TRUE;
+ }
+
+ /**
+ * Provide the block system with any exposed widget blocks for this display.
+ */
+ function get_special_blocks() {
+ $blocks = array();
+
+ if ($this->uses_exposed_form_in_block()) {
+ $delta = '-exp-' . $this->view->name . '-' . $this->display->id;
+ $desc = t('Exposed form: @view-@display_id', array('@view' => $this->view->name, '@display_id' => $this->display->id));
+
+ $blocks[$delta] = array(
+ 'info' => $desc,
+ 'cache' => DRUPAL_NO_CACHE,
+ );
+ }
+
+ return $blocks;
+ }
+
+ /**
+ * Render any special blocks provided for this display.
+ */
+ function view_special_blocks($type) {
+ if ($type == '-exp') {
+ // avoid interfering with the admin forms.
+ if (arg(0) == 'admin' && arg(1) == 'structure' && arg(2) == 'views') {
+ return;
+ }
+ $this->view->init_handlers();
+
+ if ($this->uses_exposed() && $this->get_option('exposed_block')) {
+ $exposed_form = $this->get_plugin('exposed_form');
+ return array(
+ 'content' => $exposed_form->render_exposed_form(TRUE),
+ );
+ }
+ }
+ }
+
+ /**
+ * Override of export_option()
+ *
+ * Because displays do not want to export options that are NOT overridden from the
+ * default display, we need some special handling during the export process.
+ */
+ function export_option($indent, $prefix, $storage, $option, $definition, $parents) {
+ // The $prefix is wrong because we store our actual options a little differently:
+ $prefix = '$handler->display->display_options';
+ $output = '';
+ if (!$parents && !$this->is_default_display()) {
+ // Do not export items that are not overridden.
+ if ($this->is_defaulted($option)) {
+ return;
+ }
+
+ // If this is not defaulted and is overrideable, flip the switch to say this
+ // is overridden.
+ if ($this->defaultable_sections($option)) {
+ $output .= $indent . $prefix . "['defaults']['$option'] = FALSE;\n";
+ }
+ }
+
+ $output .= parent::export_option($indent, $prefix, $storage, $option, $definition, $parents);
+ return $output;
+ }
+
+ /**
+ * Special method to export items that have handlers.
+ *
+ * This method was specified in the option_definition() as the method to utilize to
+ * export fields, filters, sort criteria, relationships and arguments. This passes
+ * the export off to the individual handlers so that they can export themselves
+ * properly.
+ */
+ function export_handler($indent, $prefix, $storage, $option, $definition, $parents) {
+ $output = '';
+
+ // cut the 's' off because the data is stored as the plural form but we need
+ // the singular form. Who designed that anyway? Oh yeah, I did. :(
+ if ($option != 'header' && $option != 'footer' && $option != 'empty') {
+ $type = substr($option, 0, -1);
+ }
+ else {
+ $type = $option;
+ }
+ $types = views_object_types();
+ foreach ($storage[$option] as $id => $info) {
+ if (!empty($types[$type]['type'])) {
+ $handler_type = $types[$type]['type'];
+ }
+ else {
+ $handler_type = $type;
+ }
+ // If aggregation is on, the group type might override the actual
+ // handler that is in use. This piece of code checks that and,
+ // if necessary, sets the override handler.
+ $override = NULL;
+ if ($this->use_group_by() && !empty($info['group_type'])) {
+ if (empty($this->view->query)) {
+ $this->view->init_query();
+ }
+ $aggregate = $this->view->query->get_aggregation_info();
+ if (!empty($aggregate[$info['group_type']]['handler'][$type])) {
+ $override = $aggregate[$info['group_type']]['handler'][$type];
+ }
+ }
+ $handler = views_get_handler($info['table'], $info['field'], $handler_type, $override);
+ if ($handler) {
+ $handler->init($this->view, $info);
+ $output .= $indent . '/* ' . $types[$type]['stitle'] . ': ' . $handler->ui_name() . " */\n";
+ $output .= $handler->export_options($indent, $prefix . "['$option']['$id']");
+ }
+
+ // Prevent reference problems.
+ unset($handler);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Special handling for the style export.
+ *
+ * Styles are stored as style_plugin and style_options or row_plugin and
+ * row_options accordingly. The options are told not to export, and the
+ * export for the plugin should export both.
+ */
+ function export_style($indent, $prefix, $storage, $option, $definition, $parents) {
+ $output = '';
+ $style_plugin = $this->get_plugin();
+ if ($option == 'style_plugin') {
+ $type = 'style';
+ $options_field = 'style_options';
+ $plugin = $style_plugin;
+ }
+ else {
+ if (!$style_plugin || !$style_plugin->uses_row_plugin()) {
+ return;
+ }
+
+ $type = 'row';
+ $options_field = 'row_options';
+ $plugin = $this->get_plugin('row');
+ // If the style plugin doesn't use row plugins, don't even bother.
+ }
+
+ if ($plugin) {
+ // Write which plugin to use.
+ $value = $this->get_option($option);
+ $output .= $indent . $prefix . "['$option'] = '$value';\n";
+
+ // Pass off to the plugin to export itself.
+ $output .= $plugin->export_options($indent, $prefix . "['$options_field']");
+ }
+
+ return $output;
+ }
+
+ /**
+ * Special handling for plugin export
+ *
+ * Plugins other than styles are stored in array with 'type' being the key
+ * to the plugin. For modern plugins, the options are stored in the 'options'
+ * array, but for legacy plugins (access and cache) options are stored as
+ * siblings to the type.
+ */
+ function export_plugin($indent, $prefix, $storage, $option, $definition, $parents) {
+ $output = '';
+ $plugin_type = end($parents);
+ $plugin = $this->get_plugin($plugin_type);
+ if ($plugin) {
+ // Write which plugin to use.
+ $value = $storage[$option];
+ $new_prefix = $prefix . "['$plugin_type']";
+
+ $output .= $indent . $new_prefix . "['$option'] = '$value';\n";
+
+ if ($plugin_type != 'access' && $plugin_type!= 'cache') {
+ $new_prefix .= "['options']";
+ }
+
+ // Pass off to the plugin to export itself.
+ $output .= $plugin->export_options($indent, $new_prefix);
+ }
+
+ return $output;
+ }
+
+ function unpack_style($indent, $prefix, $storage, $option, $definition, $parents) {
+ $output = '';
+ $style_plugin = $this->get_plugin();
+ if ($option == 'style_plugin') {
+ $type = 'style';
+ $options_field = 'style_options';
+ $plugin = $style_plugin;
+ }
+ else {
+ if (!$style_plugin || !$style_plugin->uses_row_plugin()) {
+ return;
+ }
+
+ $type = 'row';
+ $options_field = 'row_options';
+ $plugin = $this->get_plugin('row');
+ // If the style plugin doesn't use row plugins, don't even bother.
+ }
+
+ if ($plugin) {
+ return $plugin->unpack_translatables($translatable, $parents);
+ }
+ }
+
+ /**
+ * Special handling for plugin unpacking.
+ */
+ function unpack_plugin(&$translatable, $storage, $option, $definition, $parents) {
+ $plugin_type = end($parents);
+ $plugin = $this->get_plugin($plugin_type);
+ if ($plugin) {
+ // Write which plugin to use.
+ return $plugin->unpack_translatables($translatable, $parents);
+ }
+ }
+
+ /**
+ * Special method to unpack items that have handlers.
+ *
+ * This method was specified in the option_definition() as the method to utilize to
+ * export fields, filters, sort criteria, relationships and arguments. This passes
+ * the export off to the individual handlers so that they can export themselves
+ * properly.
+ */
+ function unpack_handler(&$translatable, $storage, $option, $definition, $parents) {
+ $output = '';
+
+ // cut the 's' off because the data is stored as the plural form but we need
+ // the singular form. Who designed that anyway? Oh yeah, I did. :(
+ if ($option != 'header' && $option != 'footer' && $option != 'empty') {
+ $type = substr($option, 0, -1);
+ }
+ else {
+ $type = $option;
+ }
+ $types = views_object_types();
+ foreach ($storage[$option] as $id => $info) {
+ if (!empty($types[$type]['type'])) {
+ $handler_type = $types[$type]['type'];
+ }
+ else {
+ $handler_type = $type;
+ }
+ $handler = views_get_handler($info['table'], $info['field'], $handler_type);
+ if ($handler) {
+ $handler->init($this->view, $info);
+ $handler->unpack_translatables($translatable, array_merge($parents, array($type, $info['table'], $info['id'])));
+ }
+
+ // Prevent reference problems.
+ unset($handler);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Provide some helpful text for the arguments.
+ * The result should contain of an array with
+ * - filter value present: The title of the fieldset in the argument
+ * where you can configure what should be done with a given argument.
+ * - filter value not present: The tiel of the fieldset in the argument
+ * where you can configure what should be done if the argument does not
+ * exist.
+ * - description: A description about how arguments comes to the display.
+ * For example blocks don't get it from url.
+ */
+ function get_argument_text() {
+ return array(
+ 'filter value not present' => t('When the filter value is <em>NOT</em> available'),
+ 'filter value present' => t('When the filter value <em>IS</em> available or a default is provided'),
+ 'description' => t("This display does not have a source for contextual filters, so no contextual filter value will be available unless you select 'Provide default'."),
+ );
+ }
+
+ /**
+ * Provide some helpful text for pagers.
+ *
+ * The result should contain of an array within
+ * - items per page title
+ */
+ function get_pager_text() {
+ return array(
+ 'items per page title' => t('Items to display'),
+ 'items per page description' => t('The number of items to display. Enter 0 for no limit.')
+ );
+ }
+}
+
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_display_attachment.inc b/sites/all/modules/views/plugins/views_plugin_display_attachment.inc
new file mode 100644
index 000000000..8608cfc89
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_display_attachment.inc
@@ -0,0 +1,284 @@
+<?php
+
+/**
+ * @file
+ * Contains the attachment display plugin.
+ */
+
+/**
+ * The plugin that handles an attachment display.
+ *
+ * Attachment displays are secondary displays that are 'attached' to a primary
+ * display. Effectively they are a simple way to get multiple views within
+ * the same view. They can share some information.
+ *
+ * @ingroup views_display_plugins
+ */
+class views_plugin_display_attachment extends views_plugin_display {
+ function option_definition () {
+ $options = parent::option_definition();
+
+ $options['displays'] = array('default' => array());
+ $options['attachment_position'] = array('default' => 'before');
+ $options['inherit_arguments'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['inherit_exposed_filters'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['inherit_pager'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['render_pager'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function execute() {
+ return $this->view->render($this->display->id);
+ }
+
+ function attachment_positions($position = NULL) {
+ $positions = array(
+ 'before' => t('Before'),
+ 'after' => t('After'),
+ 'both' => t('Both'),
+ );
+
+ if ($position) {
+ return $positions[$position];
+ }
+
+ return $positions;
+ }
+
+ /**
+ * Provide the summary for attachment options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) {
+ // It is very important to call the parent function here:
+ parent::options_summary($categories, $options);
+
+ $categories['attachment'] = array(
+ 'title' => t('Attachment settings'),
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -10,
+ ),
+ );
+
+ $displays = array_filter($this->get_option('displays'));
+ if (count($displays) > 1) {
+ $attach_to = t('Multiple displays');
+ }
+ elseif (count($displays) == 1) {
+ $display = array_shift($displays);
+ if (!empty($this->view->display[$display])) {
+ $attach_to = check_plain($this->view->display[$display]->display_title);
+ }
+ }
+
+ if (!isset($attach_to)) {
+ $attach_to = t('Not defined');
+ }
+
+ $options['displays'] = array(
+ 'category' => 'attachment',
+ 'title' => t('Attach to'),
+ 'value' => $attach_to,
+ );
+
+ $options['attachment_position'] = array(
+ 'category' => 'attachment',
+ 'title' => t('Attachment position'),
+ 'value' => $this->attachment_positions($this->get_option('attachment_position')),
+ );
+
+ $options['inherit_arguments'] = array(
+ 'category' => 'attachment',
+ 'title' => t('Inherit contextual filters'),
+ 'value' => $this->get_option('inherit_arguments') ? t('Yes') : t('No'),
+ );
+
+ $options['inherit_exposed_filters'] = array(
+ 'category' => 'attachment',
+ 'title' => t('Inherit exposed filters'),
+ 'value' => $this->get_option('inherit_exposed_filters') ? t('Yes') : t('No'),
+ );
+
+ $options['inherit_pager'] = array(
+ 'category' => 'pager',
+ 'title' => t('Inherit pager'),
+ 'value' => $this->get_option('inherit_pager') ? t('Yes') : t('No'),
+ );
+
+ $options['render_pager'] = array(
+ 'category' => 'pager',
+ 'title' => t('Render pager'),
+ 'value' => $this->get_option('render_pager') ? t('Yes') : t('No'),
+ );
+
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_form($form, $form_state);
+
+ switch ($form_state['section']) {
+ case 'inherit_arguments':
+ $form['#title'] .= t('Inherit contextual filters');
+ $form['inherit_arguments'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Inherit'),
+ '#description' => t('Should this display inherit its contextual filter values from the parent display to which it is attached?'),
+ '#default_value' => $this->get_option('inherit_arguments'),
+ );
+ break;
+ case 'inherit_exposed_filters':
+ $form['#title'] .= t('Inherit exposed filters');
+ $form['inherit_exposed_filters'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Inherit'),
+ '#description' => t('Should this display inherit its exposed filter values from the parent display to which it is attached?'),
+ '#default_value' => $this->get_option('inherit_exposed_filters'),
+ );
+ break;
+ case 'inherit_pager':
+ $form['#title'] .= t('Inherit pager');
+ $form['inherit_pager'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Inherit'),
+ '#description' => t('Should this display inherit its paging values from the parent display to which it is attached?'),
+ '#default_value' => $this->get_option('inherit_pager'),
+ );
+ break;
+ case 'render_pager':
+ $form['#title'] .= t('Render pager');
+ $form['render_pager'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Render'),
+ '#description' => t('Should this display render the pager values? This is only meaningful if inheriting a pager.'),
+ '#default_value' => $this->get_option('render_pager'),
+ );
+ break;
+ case 'attachment_position':
+ $form['#title'] .= t('Position');
+ $form['attachment_position'] = array(
+ '#type' => 'radios',
+ '#description' => t('Attach before or after the parent display?'),
+ '#options' => $this->attachment_positions(),
+ '#default_value' => $this->get_option('attachment_position'),
+ );
+ break;
+ case 'displays':
+ $form['#title'] .= t('Attach to');
+ $displays = array();
+ foreach ($this->view->display as $display_id => $display) {
+ if (!empty($display->handler) && $display->handler->accept_attachments()) {
+ $displays[$display_id] = $display->display_title;
+ }
+ }
+ $form['displays'] = array(
+ '#type' => 'checkboxes',
+ '#description' => t('Select which display or displays this should attach to.'),
+ '#options' => $displays,
+ '#default_value' => $this->get_option('displays'),
+ );
+ break;
+ }
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_submit($form, $form_state);
+ switch ($form_state['section']) {
+ case 'inherit_arguments':
+ case 'inherit_pager':
+ case 'render_pager':
+ case 'inherit_exposed_filters':
+ case 'attachment_position':
+ case 'displays':
+ $this->set_option($form_state['section'], $form_state['values'][$form_state['section']]);
+ break;
+ }
+ }
+
+ /**
+ * Attach to another view.
+ */
+ function attach_to($display_id) {
+ $displays = $this->get_option('displays');
+
+ if (empty($displays[$display_id])) {
+ return;
+ }
+
+ if (!$this->access()) {
+ return;
+ }
+
+ // Get a fresh view because our current one has a lot of stuff on it because it's
+ // already been executed.
+ $view = $this->view->clone_view();
+ $view->original_args = $view->args;
+
+ $args = $this->get_option('inherit_arguments') ? $this->view->args : array();
+ $view->set_arguments($args);
+ $exposed_input = $this->get_option('inherit_exposed_filters') ? $this->view->exposed_input : array();
+ $view->set_exposed_input($exposed_input);
+ $view->set_display($this->display->id);
+ if ($this->get_option('inherit_pager')) {
+ $view->display_handler->use_pager = $this->view->display[$display_id]->handler->use_pager();
+ $view->display_handler->set_option('pager', $this->view->display[$display_id]->handler->get_option('pager'));
+ }
+
+ $attachment = $view->execute_display($this->display->id, $args);
+
+ switch ($this->get_option('attachment_position')) {
+ case 'before':
+ $this->view->attachment_before .= $attachment;
+ break;
+ case 'after':
+ $this->view->attachment_after .= $attachment;
+ break;
+ case 'both':
+ $this->view->attachment_before .= $attachment;
+ $this->view->attachment_after .= $attachment;
+ break;
+ }
+
+ $view->destroy();
+ }
+
+ /**
+ * Attachment displays only use exposed widgets if
+ * they are set to inherit the exposed filter settings
+ * of their parent display.
+ */
+ function uses_exposed() {
+ if (!empty($this->options['inherit_exposed_filters']) && parent::uses_exposed()) {
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * If an attachment is set to inherit the exposed filter
+ * settings from its parent display, then don't render and
+ * display a second set of exposed filter widgets.
+ */
+ function displays_exposed() {
+ return $this->options['inherit_exposed_filters'] ? FALSE : TRUE;
+ }
+
+ function use_pager() {
+ return !empty($this->use_pager);
+ }
+
+ function render_pager() {
+ return !empty($this->use_pager) && $this->get_option('render_pager');
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_display_block.inc b/sites/all/modules/views/plugins/views_plugin_display_block.inc
new file mode 100644
index 000000000..d581a16a1
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_display_block.inc
@@ -0,0 +1,243 @@
+<?php
+
+/**
+ * @file
+ * Contains the block display plugin.
+ */
+
+/**
+ * The plugin that handles a block.
+ *
+ * @ingroup views_display_plugins
+ */
+class views_plugin_display_block extends views_plugin_display {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['block_description'] = array('default' => '', 'translatable' => TRUE);
+ $options['block_caching'] = array('default' => DRUPAL_NO_CACHE);
+
+ return $options;
+ }
+
+ /**
+ * The default block handler doesn't support configurable items,
+ * but extended block handlers might be able to do interesting
+ * stuff with it.
+ */
+ function execute_hook_block_list($delta = 0, $edit = array()) {
+ $delta = $this->view->name . '-' . $this->display->id;
+ $desc = $this->get_option('block_description');
+
+ if (empty($desc)) {
+ if ($this->display->display_title == $this->definition['title']) {
+ $desc = t('View: !view', array('!view' => $this->view->get_human_name()));
+ }
+ else {
+ $desc = t('View: !view: !display', array('!view' => $this->view->get_human_name(), '!display' => $this->display->display_title));
+ }
+ }
+ return array(
+ $delta => array(
+ 'info' => $desc,
+ 'cache' => $this->get_cache_type()
+ ),
+ );
+ }
+
+ /**
+ * The display block handler returns the structure necessary for a block.
+ */
+ function execute() {
+ // Prior to this being called, the $view should already be set to this
+ // display, and arguments should be set on the view.
+ $info['content'] = $this->view->render();
+ $info['subject'] = filter_xss_admin($this->view->get_title());
+ if (!empty($this->view->result) || $this->get_option('empty') || !empty($this->view->style_plugin->definition['even empty'])) {
+ return $info;
+ }
+ }
+
+ /**
+ * Provide the summary for page options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) {
+ // It is very important to call the parent function here:
+ parent::options_summary($categories, $options);
+
+ $categories['block'] = array(
+ 'title' => t('Block settings'),
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -10,
+ ),
+ );
+
+ $block_description = strip_tags($this->get_option('block_description'));
+ if (empty($block_description)) {
+ $block_description = t('None');
+ }
+
+ $options['block_description'] = array(
+ 'category' => 'block',
+ 'title' => t('Block name'),
+ 'value' => views_ui_truncate($block_description, 24),
+ );
+
+ $types = $this->block_caching_modes();
+ $options['block_caching'] = array(
+ 'category' => 'other',
+ 'title' => t('Block caching'),
+ 'value' => $types[$this->get_cache_type()],
+ );
+ }
+
+ /**
+ * Provide a list of core's block caching modes.
+ */
+ function block_caching_modes() {
+ return array(
+ DRUPAL_NO_CACHE => t('Do not cache'),
+ DRUPAL_CACHE_GLOBAL => t('Cache once for everything (global)'),
+ DRUPAL_CACHE_PER_PAGE => t('Per page'),
+ DRUPAL_CACHE_PER_ROLE => t('Per role'),
+ DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE => t('Per role per page'),
+ DRUPAL_CACHE_PER_USER => t('Per user'),
+ DRUPAL_CACHE_PER_USER | DRUPAL_CACHE_PER_PAGE => t('Per user per page'),
+ );
+ }
+
+ /**
+ * Provide a single method to figure caching type, keeping a sensible default
+ * for when it's unset.
+ */
+ function get_cache_type() {
+ $cache_type = $this->get_option('block_caching');
+ if (empty($cache_type)) {
+ $cache_type = DRUPAL_NO_CACHE;
+ }
+ return $cache_type;
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_form($form, $form_state);
+
+ switch ($form_state['section']) {
+ case 'block_description':
+ $form['#title'] .= t('Block admin description');
+ $form['block_description'] = array(
+ '#type' => 'textfield',
+ '#description' => t('This will appear as the name of this block in administer >> structure >> blocks.'),
+ '#default_value' => $this->get_option('block_description'),
+ );
+ break;
+ case 'block_caching':
+ $form['#title'] .= t('Block caching type');
+
+ $form['block_caching'] = array(
+ '#type' => 'radios',
+ '#description' => t("This sets the default status for Drupal's built-in block caching method; this requires that caching be turned on in block administration, and be careful because you have little control over when this cache is flushed."),
+ '#options' => $this->block_caching_modes(),
+ '#default_value' => $this->get_cache_type(),
+ );
+ break;
+ case 'exposed_form_options':
+ $this->view->init_handlers();
+ if (!$this->uses_exposed() && parent::uses_exposed()) {
+ $form['exposed_form_options']['warning'] = array(
+ '#weight' => -10,
+ '#markup' => '<div class="messages warning">' . t('Exposed filters in block displays require "Use AJAX" to be set to work correctly.') . '</div>',
+ );
+ }
+ }
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_submit($form, $form_state);
+ switch ($form_state['section']) {
+ case 'display_id':
+ $this->update_block_bid($form_state['view']->name, $this->display->id, $this->display->new_id);
+ break;
+ case 'block_description':
+ $this->set_option('block_description', $form_state['values']['block_description']);
+ break;
+ case 'block_caching':
+ $this->set_option('block_caching', $form_state['values']['block_caching']);
+ $this->save_block_cache($form_state['view']->name . '-'. $form_state['display_id'], $form_state['values']['block_caching']);
+ break;
+ }
+ }
+
+ /**
+ * Block views use exposed widgets only if AJAX is set.
+ */
+ function uses_exposed() {
+ if ($this->use_ajax()) {
+ return parent::uses_exposed();
+ }
+ return FALSE;
+ }
+
+ /**
+ * Update the block delta when you change the machine readable name of the display.
+ */
+ function update_block_bid($name, $old_delta, $delta) {
+ $old_hashes = $hashes = variable_get('views_block_hashes', array());
+
+ $old_delta = $name . '-' . $old_delta;
+ $delta = $name . '-' . $delta;
+ if (strlen($old_delta) >= 32) {
+ $old_delta = md5($old_delta);
+ unset($hashes[$old_delta]);
+ }
+ if (strlen($delta) >= 32) {
+ $md5_delta = md5($delta);
+ $hashes[$md5_delta] = $delta;
+ $delta = $md5_delta;
+ }
+
+ // Maybe people don't have block module installed, so let's skip this.
+ if (db_table_exists('block')) {
+ db_update('block')
+ ->fields(array('delta' => $delta))
+ ->condition('delta', $old_delta)
+ ->execute();
+ }
+
+ // Update the hashes if needed.
+ if ($hashes != $old_hashes) {
+ variable_set('views_block_hashes', $hashes);
+ }
+ }
+
+ /**
+ * Save the block cache setting in the blocks table if this block already
+ * exists in the blocks table. Dirty fix until http://drupal.org/node/235673 gets in.
+ */
+ function save_block_cache($delta, $cache_setting) {
+ if (strlen($delta) >= 32) {
+ $delta = md5($delta);
+ }
+ if (db_table_exists('block') && $bid = db_query("SELECT bid FROM {block} WHERE module = 'views' AND delta = :delta", array(
+ ':delta' => $delta))->fetchField()) {
+ db_update('block')
+ ->fields(array(
+ 'cache' => $cache_setting,
+ ))
+ ->condition('module','views')
+ ->condition('delta', $delta)
+ ->execute();
+ }
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_display_default.inc b/sites/all/modules/views/plugins/views_plugin_display_default.inc
new file mode 100644
index 000000000..4b1fc086d
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_display_default.inc
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains the default display plugin.
+ */
+
+/**
+ * A plugin to handle defaults on a view.
+ *
+ * @ingroup views_display_plugins
+ */
+class views_plugin_display_default extends views_plugin_display {
+ /**
+ * Determine if this display is the 'default' display which contains
+ * fallback settings
+ */
+ function is_default_display() { return TRUE; }
+
+ /**
+ * The default execute handler fully renders the view.
+ *
+ * For the simplest use:
+ * @code
+ * $output = $view->execute_display('default', $args);
+ * @endcode
+ *
+ * For more complex usages, a view can be partially built:
+ * @code
+ * $view->set_arguments($args);
+ * $view->build('default'); // Build the query
+ * $view->pre_execute(); // Pre-execute the query.
+ * $view->execute(); // Run the query
+ * $output = $view->render(); // Render the view
+ * @endcode
+ *
+ * If short circuited at any point, look in $view->build_info for
+ * information about the query. After execute, look in $view->result
+ * for the array of objects returned from db_query.
+ *
+ * You can also do:
+ * @code
+ * $view->set_arguments($args);
+ * $output = $view->render('default'); // Render the view
+ * @endcode
+ *
+ * This illustrates that render is smart enough to call build and execute
+ * if these items have not already been accomplished.
+ *
+ * Note that execute also must accomplish other tasks, such
+ * as setting page titles, breadcrumbs, and generating exposed filter
+ * data if necessary.
+ */
+ function execute() {
+ return $this->view->render($this->display->id);
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_display_embed.inc b/sites/all/modules/views/plugins/views_plugin_display_embed.inc
new file mode 100644
index 000000000..8b25cf961
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_display_embed.inc
@@ -0,0 +1,14 @@
+<?php
+/**
+ * @file
+ * Contains the embed display plugin.
+ */
+
+/**
+ * The plugin that handles an embed display.
+ *
+ * @ingroup views_display_plugins
+ */
+class views_plugin_display_embed extends views_plugin_display {
+ // This display plugin does nothing apart from exist.
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_display_extender.inc b/sites/all/modules/views/plugins/views_plugin_display_extender.inc
new file mode 100644
index 000000000..08e981a77
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_display_extender.inc
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_display_extender.
+ */
+
+/**
+ * @todo.
+ *
+ * @ingroup views_display_plugins
+ */
+class views_plugin_display_extender extends views_plugin {
+ function init(&$view, &$display) {
+ $this->view = $view;
+ $this->display = $display;
+ }
+
+
+ /**
+ * Provide a form to edit options for this plugin.
+ */
+ function options_definition_alter(&$options) { }
+
+ /**
+ * Provide a form to edit options for this plugin.
+ */
+ function options_form(&$form, &$form_state) { }
+
+ /**
+ * Validate the options form.
+ */
+ function options_validate(&$form, &$form_state) { }
+
+ /**
+ * Handle any special handling on the validate form.
+ */
+ function options_submit(&$form, &$form_state) { }
+
+ /**
+ * Set up any variables on the view prior to execution.
+ */
+ function pre_execute() { }
+
+ /**
+ * Inject anything into the query that the display_extender handler needs.
+ */
+ function query() { }
+
+ /**
+ * Provide the default summary for options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) { }
+
+ /**
+ * Static member function to list which sections are defaultable
+ * and what items each section contains.
+ */
+ function defaultable_sections(&$sections, $section = NULL) { }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_display_feed.inc b/sites/all/modules/views/plugins/views_plugin_display_feed.inc
new file mode 100644
index 000000000..5eb68e321
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_display_feed.inc
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * @file
+ * Contains the feed display plugin.
+ */
+
+/**
+ * The plugin that handles a feed, such as RSS or atom.
+ *
+ * For the most part, feeds are page displays but with some subtle differences.
+ *
+ * @ingroup views_display_plugins
+ */
+class views_plugin_display_feed extends views_plugin_display_page {
+ function init(&$view, &$display, $options = NULL) {
+ parent::init($view, $display, $options);
+
+ // Set the default row style. Ideally this would be part of the option
+ // definition, but in this case it's dependent on the view's base table,
+ // which we don't know until init().
+ $row_plugins = views_fetch_plugin_names('row', $this->get_style_type(), array($view->base_table));
+ $default_row_plugin = key($row_plugins);
+ if ($this->options['row_plugin'] == '') {
+ $this->options['row_plugin'] = $default_row_plugin;
+ }
+ }
+
+ function uses_breadcrumb() { return FALSE; }
+ function get_style_type() { return 'feed'; }
+
+ /**
+ * Feeds do not go through the normal page theming mechanism. Instead, they
+ * go through their own little theme function and then return NULL so that
+ * Drupal believes that the page has already rendered itself...which it has.
+ */
+ function execute() {
+ $output = $this->view->render();
+ if (empty($output)) {
+ return MENU_NOT_FOUND;
+ }
+ print $output;
+ }
+
+ function preview() {
+ if (!empty($this->view->live_preview)) {
+ return '<pre>' . check_plain($this->view->render()) . '</pre>';
+ }
+ return $this->view->render();
+ }
+
+ /**
+ * Instead of going through the standard views_view.tpl.php, delegate this
+ * to the style handler.
+ */
+ function render() {
+ return $this->view->style_plugin->render($this->view->result);
+ }
+
+ function defaultable_sections($section = NULL) {
+ if (in_array($section, array('style_options', 'style_plugin', 'row_options', 'row_plugin',))) {
+ return FALSE;
+ }
+
+ $sections = parent::defaultable_sections($section);
+
+ // Tell views our sitename_title option belongs in the title section.
+ if ($section == 'title') {
+ $sections[] = 'sitename_title';
+ }
+ elseif (!$section) {
+ $sections['title'][] = 'sitename_title';
+ }
+ return $sections;
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['displays'] = array('default' => array());
+
+ // Overrides for standard stuff:
+ $options['style_plugin']['default'] = 'rss';
+ $options['style_options']['default'] = array('description' => '');
+ $options['sitename_title']['default'] = FALSE;
+ $options['row_plugin']['default'] = '';
+ $options['defaults']['default']['style_plugin'] = FALSE;
+ $options['defaults']['default']['style_options'] = FALSE;
+ $options['defaults']['default']['row_plugin'] = FALSE;
+ $options['defaults']['default']['row_options'] = FALSE;
+
+ return $options;
+ }
+
+ function options_summary(&$categories, &$options) {
+ // It is very important to call the parent function here:
+ parent::options_summary($categories, $options);
+
+ // Since we're childing off the 'page' type, we'll still *call* our
+ // category 'page' but let's override it so it says feed settings.
+ $categories['page'] = array(
+ 'title' => t('Feed settings'),
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -10,
+ ),
+ );
+
+ if ($this->get_option('sitename_title')) {
+ $options['title']['value'] = t('Using the site name');
+ }
+
+ // I don't think we want to give feeds menus directly.
+ unset($options['menu']);
+
+ $displays = array_filter($this->get_option('displays'));
+ if (count($displays) > 1) {
+ $attach_to = t('Multiple displays');
+ }
+ elseif (count($displays) == 1) {
+ $display = array_shift($displays);
+ if (!empty($this->view->display[$display])) {
+ $attach_to = check_plain($this->view->display[$display]->display_title);
+ }
+ }
+
+ if (!isset($attach_to)) {
+ $attach_to = t('None');
+ }
+
+ $options['displays'] = array(
+ 'category' => 'page',
+ 'title' => t('Attach to'),
+ 'value' => $attach_to,
+ );
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ // It is very important to call the parent function here.
+ parent::options_form($form, $form_state);
+
+ switch ($form_state['section']) {
+ case 'title':
+ $title = $form['title'];
+ // A little juggling to move the 'title' field beyond our checkbox.
+ unset($form['title']);
+ $form['sitename_title'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use the site name for the title'),
+ '#default_value' => $this->get_option('sitename_title'),
+ );
+ $form['title'] = $title;
+ $form['title']['#dependency'] = array('edit-sitename-title' => array(FALSE));
+ break;
+ case 'displays':
+ $form['#title'] .= t('Attach to');
+ $displays = array();
+ foreach ($this->view->display as $display_id => $display) {
+ if (!empty($display->handler) && $display->handler->accept_attachments()) {
+ $displays[$display_id] = $display->display_title;
+ }
+ }
+ $form['displays'] = array(
+ '#type' => 'checkboxes',
+ '#description' => t('The feed icon will be available only to the selected displays.'),
+ '#options' => $displays,
+ '#default_value' => $this->get_option('displays'),
+ );
+ break;
+ case 'path':
+ $form['path']['#description'] = t('This view will be displayed by visiting this path on your site. It is recommended that the path be something like "path/%/%/feed" or "path/%/%/rss.xml", putting one % in the path for each contextual filter you have defined in the view.');
+ }
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_submit($form, $form_state);
+ switch ($form_state['section']) {
+ case 'title':
+ $this->set_option('sitename_title', $form_state['values']['sitename_title']);
+ break;
+ case 'displays':
+ $this->set_option($form_state['section'], $form_state['values'][$form_state['section']]);
+ break;
+ }
+ }
+
+ /**
+ * Attach to another view.
+ */
+ function attach_to($display_id) {
+ $displays = $this->get_option('displays');
+ if (empty($displays[$display_id])) {
+ return;
+ }
+
+ // Defer to the feed style; it may put in meta information, and/or
+ // attach a feed icon.
+ $plugin = $this->get_plugin();
+ if ($plugin) {
+ $clone = $this->view->clone_view();
+ $clone->set_display($this->display->id);
+ $clone->build_title();
+ $plugin->attach_to($display_id, $this->get_path(), $clone->get_title());
+
+ // Clean up
+ $clone->destroy();
+ unset($clone);
+ }
+ }
+
+ function uses_link_display() {
+ return TRUE;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_display_page.inc b/sites/all/modules/views/plugins/views_plugin_display_page.inc
new file mode 100644
index 000000000..4bcec0bc2
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_display_page.inc
@@ -0,0 +1,569 @@
+<?php
+
+/**
+ * @file
+ * Contains the page display plugin.
+ */
+
+/**
+ * The plugin that handles a full page.
+ *
+ * @ingroup views_display_plugins
+ */
+class views_plugin_display_page extends views_plugin_display {
+ /**
+ * The page display has a path.
+ */
+ function has_path() { return TRUE; }
+ function uses_breadcrumb() { return TRUE; }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['path'] = array('default' => '');
+ $options['menu'] = array(
+ 'contains' => array(
+ 'type' => array('default' => 'none'),
+ // Do not translate menu and title as menu system will.
+ 'title' => array('default' => '', 'translatable' => FALSE),
+ 'description' => array('default' => '', 'translatable' => FALSE),
+ 'weight' => array('default' => 0),
+ 'name' => array('default' => variable_get('menu_default_node_menu', 'navigation')),
+ 'context' => array('default' => ''),
+ 'context_only_inline' => array('default' => FALSE),
+ ),
+ );
+ $options['tab_options'] = array(
+ 'contains' => array(
+ 'type' => array('default' => 'none'),
+ // Do not translate menu and title as menu system will.
+ 'title' => array('default' => '', 'translatable' => FALSE),
+ 'description' => array('default' => '', 'translatable' => FALSE),
+ 'weight' => array('default' => 0),
+ 'name' => array('default' => 'navigation'),
+ ),
+ );
+
+ return $options;
+ }
+
+ /**
+ * Add this display's path information to Drupal's menu system.
+ */
+ function execute_hook_menu($callbacks) {
+ $items = array();
+ // Replace % with the link to our standard views argument loader
+ // views_arg_load -- which lives in views.module
+
+ $bits = explode('/', $this->get_option('path'));
+ $page_arguments = array($this->view->name, $this->display->id);
+ $this->view->init_handlers();
+ $view_arguments = $this->view->argument;
+
+ // Replace % with %views_arg for menu autoloading and add to the
+ // page arguments so the argument actually comes through.
+ foreach ($bits as $pos => $bit) {
+ if ($bit == '%') {
+ $argument = array_shift($view_arguments);
+ if (!empty($argument->options['specify_validation']) && $argument->options['validate']['type'] != 'none') {
+ $bits[$pos] = '%views_arg';
+ }
+ $page_arguments[] = $pos;
+ }
+ }
+
+ $path = implode('/', $bits);
+
+ $access_plugin = $this->get_plugin('access');
+ if (!isset($access_plugin)) {
+ $access_plugin = views_get_plugin('access', 'none');
+ }
+
+ // Get access callback might return an array of the callback + the dynamic arguments.
+ $access_plugin_callback = $access_plugin->get_access_callback();
+
+ if (is_array($access_plugin_callback)) {
+ $access_arguments = array();
+
+ // Find the plugin arguments.
+ $access_plugin_method = array_shift($access_plugin_callback);
+ $access_plugin_arguments = array_shift($access_plugin_callback);
+ if (!is_array($access_plugin_arguments)) {
+ $access_plugin_arguments = array();
+ }
+
+ $access_arguments[0] = array($access_plugin_method, &$access_plugin_arguments);
+
+ // Move the plugin arguments to the access arguments array.
+ $i = 1;
+ foreach ($access_plugin_arguments as $key => $value) {
+ if (is_int($value)) {
+ $access_arguments[$i] = $value;
+ $access_plugin_arguments[$key] = $i;
+ $i++;
+ }
+ }
+ }
+ else {
+ $access_arguments = array($access_plugin_callback);
+ }
+
+ if ($path) {
+ $items[$path] = array(
+ // default views page entry
+ 'page callback' => 'views_page',
+ 'page arguments' => $page_arguments,
+ // Default access check (per display)
+ 'access callback' => 'views_access',
+ 'access arguments' => $access_arguments,
+ // Identify URL embedded arguments and correlate them to a handler
+ 'load arguments' => array($this->view->name, $this->display->id, '%index'),
+ );
+ $menu = $this->get_option('menu');
+ if (empty($menu)) {
+ $menu = array('type' => 'none');
+ }
+ // Set the title and description if we have one.
+ if ($menu['type'] != 'none') {
+ $items[$path]['title'] = $menu['title'];
+ $items[$path]['description'] = $menu['description'];
+ }
+
+ if (isset($menu['weight'])) {
+ $items[$path]['weight'] = intval($menu['weight']);
+ }
+
+ switch ($menu['type']) {
+ case 'none':
+ default:
+ $items[$path]['type'] = MENU_CALLBACK;
+ break;
+ case 'normal':
+ $items[$path]['type'] = MENU_NORMAL_ITEM;
+ // Insert item into the proper menu
+ $items[$path]['menu_name'] = $menu['name'];
+ break;
+ case 'tab':
+ $items[$path]['type'] = MENU_LOCAL_TASK;
+ break;
+ case 'default tab':
+ $items[$path]['type'] = MENU_DEFAULT_LOCAL_TASK;
+ break;
+ }
+
+ // Add context for contextual links.
+ // @see menu_contextual_links()
+ if (!empty($menu['context'])) {
+ $items[$path]['context'] = !empty($menu['context_only_inline']) ? MENU_CONTEXT_INLINE : (MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE);
+ }
+
+ // If this is a 'default' tab, check to see if we have to create teh
+ // parent menu item.
+ if ($menu['type'] == 'default tab') {
+ $tab_options = $this->get_option('tab_options');
+ if (!empty($tab_options['type']) && $tab_options['type'] != 'none') {
+ $bits = explode('/', $path);
+ // Remove the last piece.
+ $bit = array_pop($bits);
+
+ // we can't do this if they tried to make the last path bit variable.
+ // @todo: We can validate this.
+ if ($bit != '%views_arg' && !empty($bits)) {
+ $default_path = implode('/', $bits);
+ $items[$default_path] = array(
+ // default views page entry
+ 'page callback' => 'views_page',
+ 'page arguments' => $page_arguments,
+ // Default access check (per display)
+ 'access callback' => 'views_access',
+ 'access arguments' => $access_arguments,
+ // Identify URL embedded arguments and correlate them to a handler
+ 'load arguments' => array($this->view->name, $this->display->id, '%index'),
+ 'title' => $tab_options['title'],
+ 'description' => $tab_options['description'],
+ 'menu_name' => $tab_options['name'],
+ );
+ switch ($tab_options['type']) {
+ default:
+ case 'normal':
+ $items[$default_path]['type'] = MENU_NORMAL_ITEM;
+ break;
+ case 'tab':
+ $items[$default_path]['type'] = MENU_LOCAL_TASK;
+ break;
+ }
+ if (isset($tab_options['weight'])) {
+ $items[$default_path]['weight'] = intval($tab_options['weight']);
+ }
+ }
+ }
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * The display page handler returns a normal view, but it also does
+ * a drupal_set_title for the page, and does a views_set_page_view
+ * on the view.
+ */
+ function execute() {
+ // Let the world know that this is the page view we're using.
+ views_set_page_view($this->view);
+
+ // Prior to this being called, the $view should already be set to this
+ // display, and arguments should be set on the view.
+ $this->view->build();
+ if (!empty($this->view->build_info['fail'])) {
+ return MENU_NOT_FOUND;
+ }
+
+ if (!empty($this->view->build_info['denied'])) {
+ return MENU_ACCESS_DENIED;
+ }
+
+ $this->view->get_breadcrumb(TRUE);
+
+
+ // And now render the view.
+ $render = $this->view->render();
+
+ // First execute the view so it's possible to get tokens for the title.
+ // And the title, which is much easier.
+ drupal_set_title(filter_xss_admin($this->view->get_title()), PASS_THROUGH);
+ return $render;
+ }
+
+ /**
+ * Provide the summary for page options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) {
+ // It is very important to call the parent function here:
+ parent::options_summary($categories, $options);
+
+ $categories['page'] = array(
+ 'title' => t('Page settings'),
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -10,
+ ),
+ );
+
+ $path = strip_tags($this->get_option('path'));
+ if (empty($path)) {
+ $path = t('No path is set');
+ }
+ else {
+ $path = '/' . $path;
+ }
+
+ $options['path'] = array(
+ 'category' => 'page',
+ 'title' => t('Path'),
+ 'value' => views_ui_truncate($path, 24),
+ );
+
+ $menu = $this->get_option('menu');
+ if (!is_array($menu)) {
+ $menu = array('type' => 'none');
+ }
+ switch($menu['type']) {
+ case 'none':
+ default:
+ $menu_str = t('No menu');
+ break;
+ case 'normal':
+ $menu_str = t('Normal: @title', array('@title' => $menu['title']));
+ break;
+ case 'tab':
+ case 'default tab':
+ $menu_str = t('Tab: @title', array('@title' => $menu['title']));
+ break;
+ }
+
+ $options['menu'] = array(
+ 'category' => 'page',
+ 'title' => t('Menu'),
+ 'value' => views_ui_truncate($menu_str, 24),
+ );
+
+ // This adds a 'Settings' link to the style_options setting if the style has options.
+ if ($menu['type'] == 'default tab') {
+ $options['menu']['setting'] = t('Parent menu item');
+ $options['menu']['links']['tab_options'] = t('Change settings for the parent menu');
+ }
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_form($form, $form_state);
+
+ switch ($form_state['section']) {
+ case 'path':
+ $form['#title'] .= t('The menu path or URL of this view');
+ $form['#help_topic'] = 'path';
+ $form['path'] = array(
+ '#type' => 'textfield',
+ '#description' => t('This view will be displayed by visiting this path on your site. You may use "%" in your URL to represent values that will be used for contextual filters: For example, "node/%/feed".'),
+ '#default_value' => $this->get_option('path'),
+ '#field_prefix' => '<span dir="ltr">' . url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
+ '#field_suffix' => '</span>&lrm;',
+ '#attributes' => array('dir'=>'ltr'),
+ );
+ break;
+ case 'menu':
+ $form['#title'] .= t('Menu item entry');
+ $form['#help_topic'] = 'menu';
+ $form['menu'] = array(
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+ $menu = $this->get_option('menu');
+ if (empty($menu)) {
+ $menu = array('type' => 'none', 'title' => '', 'weight' => 0);
+ }
+ $form['menu']['type'] = array(
+ '#prefix' => '<div class="views-left-30">',
+ '#suffix' => '</div>',
+ '#title' => t('Type'),
+ '#type' => 'radios',
+ '#options' => array(
+ 'none' => t('No menu entry'),
+ 'normal' => t('Normal menu entry'),
+ 'tab' => t('Menu tab'),
+ 'default tab' => t('Default menu tab')
+ ),
+ '#default_value' => $menu['type'],
+ );
+ $form['menu']['title'] = array(
+ '#prefix' => '<div class="views-left-50">',
+ '#title' => t('Title'),
+ '#type' => 'textfield',
+ '#default_value' => $menu['title'],
+ '#description' => t('If set to normal or tab, enter the text to use for the menu item.'),
+ '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab')),
+ );
+ $form['menu']['description'] = array(
+ '#title' => t('Description'),
+ '#type' => 'textfield',
+ '#default_value' => $menu['description'],
+ '#description' => t("If set to normal or tab, enter the text to use for the menu item's description."),
+ '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab')),
+ );
+
+ // Only display the menu selector if menu module is enabled.
+ if (module_exists('menu')) {
+ $form['menu']['name'] = array(
+ '#title' => t('Menu'),
+ '#type' => 'select',
+ '#options' => menu_get_menus(),
+ '#default_value' => $menu['name'],
+ '#description' => t('Insert item into an available menu.'),
+ '#dependency' => array('radio:menu[type]' => array('normal', 'tab')),
+ );
+ }
+ else {
+ $form['menu']['name'] = array(
+ '#type' => 'value',
+ '#value' => $menu['name'],
+ );
+ $form['menu']['markup'] = array(
+ '#markup' => t('Menu selection requires the activation of menu module.'),
+ );
+ }
+ $form['menu']['weight'] = array(
+ '#title' => t('Weight'),
+ '#type' => 'textfield',
+ '#default_value' => isset($menu['weight']) ? $menu['weight'] : 0,
+ '#description' => t('The lower the weight the higher/further left it will appear.'),
+ '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab')),
+ );
+ $form['menu']['context'] = array(
+ '#title' => t('Context'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($menu['context']),
+ '#description' => t('Displays the link in contextual links'),
+ '#dependency' => array('radio:menu[type]' => array('tab')),
+ );
+ $form['menu']['context_only_inline'] = array(
+ '#title' => t('Hide menu tab'),
+ '#suffix' => '</div>',
+ '#type' => 'checkbox',
+ '#default_value' => !empty($menu['context_only_inline']),
+ '#description' => t('Only display menu item entry in contextual links. Menu tab should not be displayed.'),
+ '#dependency' => array(
+ 'radio:menu[type]' => array('tab'),
+ 'edit-menu-context' => array(1),
+ ),
+ '#dependency_count' => 2,
+ );
+ break;
+ case 'tab_options':
+ $form['#title'] .= t('Default tab options');
+ $tab_options = $this->get_option('tab_options');
+ if (empty($tab_options)) {
+ $tab_options = array('type' => 'none', 'title' => '', 'weight' => 0);
+ }
+
+ $form['tab_markup'] = array(
+ '#markup' => '<div class="form-item description">' . t('When providing a menu item as a tab, Drupal needs to know what the parent menu item of that tab will be. Sometimes the parent will already exist, but other times you will need to have one created. The path of a parent item will always be the same path with the last part left off. i.e, if the path to this view is <em>foo/bar/baz</em>, the parent path would be <em>foo/bar</em>.') . '</div>',
+ );
+
+ $form['tab_options'] = array(
+ '#prefix' => '<div class="clearfix">',
+ '#suffix' => '</div>',
+ '#tree' => TRUE,
+ );
+ $form['tab_options']['type'] = array(
+ '#prefix' => '<div class="views-left-25">',
+ '#suffix' => '</div>',
+ '#title' => t('Parent menu item'),
+ '#type' => 'radios',
+ '#options' => array('none' => t('Already exists'), 'normal' => t('Normal menu item'), 'tab' => t('Menu tab')),
+ '#default_value' => $tab_options['type'],
+ );
+ $form['tab_options']['title'] = array(
+ '#prefix' => '<div class="views-left-75">',
+ '#title' => t('Title'),
+ '#type' => 'textfield',
+ '#default_value' => $tab_options['title'],
+ '#description' => t('If creating a parent menu item, enter the title of the item.'),
+ '#dependency' => array('radio:tab_options[type]' => array('normal', 'tab')),
+ );
+ $form['tab_options']['description'] = array(
+ '#title' => t('Description'),
+ '#type' => 'textfield',
+ '#default_value' => $tab_options['description'],
+ '#description' => t('If creating a parent menu item, enter the description of the item.'),
+ '#dependency' => array('radio:tab_options[type]' => array('normal', 'tab')),
+ );
+ // Only display the menu selector if menu module is enabled.
+ if (module_exists('menu')) {
+ $form['tab_options']['name'] = array(
+ '#title' => t('Menu'),
+ '#type' => 'select',
+ '#options' => menu_get_menus(),
+ '#default_value' => $tab_options['name'],
+ '#description' => t('Insert item into an available menu.'),
+ '#dependency' => array('radio:tab_options[type]' => array('normal')),
+ );
+ }
+ else {
+ $form['tab_options']['name'] = array(
+ '#type' => 'value',
+ '#value' => $tab_options['name'],
+ );
+ $form['tab_options']['markup'] = array(
+ '#markup' => t('Menu selection requires the activation of menu module.'),
+ );
+ }
+ $form['tab_options']['weight'] = array(
+ '#suffix' => '</div>',
+ '#title' => t('Tab weight'),
+ '#type' => 'textfield',
+ '#default_value' => $tab_options['weight'],
+ '#size' => 5,
+ '#description' => t('If the parent menu item is a tab, enter the weight of the tab. The lower the number, the more to the left it will be.'),
+ '#dependency' => array('radio:tab_options[type]' => array('tab')),
+ );
+ break;
+ }
+ }
+
+ function options_validate(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_validate($form, $form_state);
+ switch ($form_state['section']) {
+ case 'path':
+ if (strpos($form_state['values']['path'], '$arg') !== FALSE) {
+ form_error($form['path'], t('"$arg" is no longer supported. Use % instead.'));
+ }
+
+ if (strpos($form_state['values']['path'], '%') === 0) {
+ form_error($form['path'], t('"%" may not be used for the first segment of a path.'));
+ }
+
+ // automatically remove '/' and trailing whitespace from path.
+ $form_state['values']['path'] = trim($form_state['values']['path'], '/ ');
+ break;
+ case 'menu':
+ $path = $this->get_option('path');
+ if ($form_state['values']['menu']['type'] == 'normal' && strpos($path, '%') !== FALSE) {
+ form_error($form['menu']['type'], t('Views cannot create normal menu items for paths with a % in them.'));
+ }
+
+ if ($form_state['values']['menu']['type'] == 'default tab' || $form_state['values']['menu']['type'] == 'tab') {
+ $bits = explode('/', $path);
+ $last = array_pop($bits);
+ if ($last == '%') {
+ form_error($form['menu']['type'], t('A display whose path ends with a % cannot be a tab.'));
+ }
+ }
+
+ if ($form_state['values']['menu']['type'] != 'none' && empty($form_state['values']['menu']['title'])) {
+ form_error($form['menu']['title'], t('Title is required for this menu type.'));
+ }
+ break;
+ }
+ }
+
+ function options_submit(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_submit($form, $form_state);
+ switch ($form_state['section']) {
+ case 'path':
+ $this->set_option('path', $form_state['values']['path']);
+ break;
+ case 'menu':
+ $this->set_option('menu', $form_state['values']['menu']);
+ // send ajax form to options page if we use it.
+ if ($form_state['values']['menu']['type'] == 'default tab') {
+ views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('tab_options'));
+ }
+ break;
+ case 'tab_options':
+ $this->set_option('tab_options', $form_state['values']['tab_options']);
+ break;
+ }
+ }
+
+ function validate() {
+ $errors = parent::validate();
+
+ $menu = $this->get_option('menu');
+ if (!empty($menu['type']) && $menu['type'] != 'none' && empty($menu['title'])) {
+ $errors[] = t('Display @display is set to use a menu but the menu link text is not set.', array('@display' => $this->display->display_title));
+ }
+
+ if ($menu['type'] == 'default tab') {
+ $tab_options = $this->get_option('tab_options');
+ if (!empty($tab_options['type']) && $tab_options['type'] != 'none' && empty($tab_options['title'])) {
+ $errors[] = t('Display @display is set to use a parent menu but the parent menu link text is not set.', array('@display' => $this->display->display_title));
+ }
+ }
+
+ return $errors;
+ }
+
+ function get_argument_text() {
+ return array(
+ 'filter value not present' => t('When the filter value is <em>NOT</em> in the URL'),
+ 'filter value present' => t('When the filter value <em>IS</em> in the URL or a default is provided'),
+ 'description' => t('The contextual filter values is provided by the URL.'),
+ );
+ }
+
+ function get_pager_text() {
+ return array(
+ 'items per page title' => t('Items per page'),
+ 'items per page description' => t('The number of items to display per page. Enter 0 for no limit.')
+ );
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_exposed_form.inc b/sites/all/modules/views/plugins/views_plugin_exposed_form.inc
new file mode 100644
index 000000000..5d5460046
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_exposed_form.inc
@@ -0,0 +1,334 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_exposed_form.
+ */
+
+/**
+ * @defgroup views_exposed_form_plugins Views exposed form plugins
+ * @{
+ * Plugins that handle the validation/submission and rendering of exposed forms.
+ *
+ * If needed, it is possible to use them to add additional form elements.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * The base plugin to handle exposed filter forms.
+ */
+class views_plugin_exposed_form extends views_plugin {
+
+ /**
+ * Initialize the plugin.
+ *
+ * @param $view
+ * The view object.
+ * @param $display
+ * The display handler.
+ */
+ function init(&$view, &$display, $options = array()) {
+ $this->view = &$view;
+ $this->display = &$display;
+
+ $this->unpack_options($this->options, $options);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['submit_button'] = array('default' => 'Apply', 'translatable' => TRUE);
+ $options['reset_button'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['reset_button_label'] = array('default' => 'Reset', 'translatable' => TRUE);
+ $options['exposed_sorts_label'] = array('default' => 'Sort by', 'translatable' => TRUE);
+ $options['expose_sort_order'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['sort_asc_label'] = array('default' => 'Asc', 'translatable' => TRUE);
+ $options['sort_desc_label'] = array('default' => 'Desc', 'translatable' => TRUE);
+ $options['autosubmit'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['autosubmit_hide'] = array('default' => TRUE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['submit_button'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Submit button text'),
+ '#description' => t('Text to display in the submit button of the exposed form.'),
+ '#default_value' => $this->options['submit_button'],
+ '#required' => TRUE,
+ );
+
+ $form['reset_button'] = array (
+ '#type' => 'checkbox',
+ '#title' => t('Include reset button'),
+ '#description' => t('If checked the exposed form will provide a button to reset all the applied exposed filters'),
+ '#default_value' => $this->options['reset_button'],
+ );
+
+ $form['reset_button_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Reset button label'),
+ '#description' => t('Text to display in the reset button of the exposed form.'),
+ '#default_value' => $this->options['reset_button_label'],
+ '#required' => TRUE,
+ '#dependency' => array(
+ 'edit-exposed-form-options-reset-button' => array(1)
+ ),
+ );
+
+ $form['exposed_sorts_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Exposed sorts label'),
+ '#description' => t('Text to display as the label of the exposed sort select box.'),
+ '#default_value' => $this->options['exposed_sorts_label'],
+ '#required' => TRUE,
+ );
+
+ $form['expose_sort_order'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Expose sort order'),
+ '#description' => t('Allow the user to choose the sort order. If sort order is not exposed, the sort criteria settings for each sort will determine its order.'),
+ '#default_value' => $this->options['expose_sort_order'],
+ );
+
+ $form['sort_asc_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Ascending'),
+ '#description' => t('Text to use when exposed sort is ordered ascending.'),
+ '#default_value' => $this->options['sort_asc_label'],
+ '#required' => TRUE,
+ '#dependency' => array('edit-exposed-form-options-expose-sort-order' => array(TRUE)),
+ );
+
+ $form['sort_desc_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Descending'),
+ '#description' => t('Text to use when exposed sort is ordered descending.'),
+ '#default_value' => $this->options['sort_desc_label'],
+ '#required' => TRUE,
+ '#dependency' => array('edit-exposed-form-options-expose-sort-order' => array(TRUE)),
+ );
+
+ $form['autosubmit'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Autosubmit'),
+ '#description' => t('Automatically submit the form once an element is changed.'),
+ '#default_value' => $this->options['autosubmit'],
+ );
+
+ $form['autosubmit_hide'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide submit button'),
+ '#description' => t('Hide submit button if javascript is enabled.'),
+ '#default_value' => $this->options['autosubmit_hide'],
+ '#dependency' => array(
+ 'edit-exposed-form-options-autosubmit' => array(1),
+ ),
+ );
+ }
+
+ /**
+ * Render the exposed filter form.
+ *
+ * This actually does more than that; because it's using FAPI, the form will
+ * also assign data to the appropriate handlers for use in building the
+ * query.
+ */
+ function render_exposed_form($block = FALSE) {
+ // Deal with any exposed filters we may have, before building.
+ $form_state = array(
+ 'view' => &$this->view,
+ 'display' => &$this->display,
+ 'method' => 'get',
+ 'rerender' => TRUE,
+ 'no_redirect' => TRUE,
+ 'always_process' => TRUE,
+ );
+
+ // Some types of displays (eg. attachments) may wish to use the exposed
+ // filters of their parent displays instead of showing an additional
+ // exposed filter form for the attachment as well as that for the parent.
+ if (!$this->view->display_handler->displays_exposed() || (!$block && $this->view->display_handler->get_option('exposed_block'))) {
+ unset($form_state['rerender']);
+ }
+
+ if (!empty($this->ajax)) {
+ $form_state['ajax'] = TRUE;
+ }
+
+ $form_state['exposed_form_plugin'] = $this;
+ $form = drupal_build_form('views_exposed_form', $form_state);
+ $output = drupal_render($form);
+
+ if (!$this->view->display_handler->displays_exposed() || (!$block && $this->view->display_handler->get_option('exposed_block'))) {
+ return "";
+ }
+ else {
+ return $output;
+ }
+ }
+
+ function query() {
+ $view = $this->view;
+ $exposed_data = isset($view->exposed_data) ? $view->exposed_data : array();
+ $sort_by = isset($exposed_data['sort_by']) ? $exposed_data['sort_by'] : NULL;
+ if (!empty($sort_by) && $this->view->style_plugin->build_sort()) {
+ // Make sure the original order of sorts is preserved
+ // (e.g. a sticky sort is often first)
+ if (isset($view->sort[$sort_by])) {
+ $view->query->orderby = array();
+ foreach ($view->sort as $key => $sort) {
+ if (!$sort->is_exposed()) {
+ $sort->query();
+ }
+ else if ($key == $sort_by) {
+ if (isset($exposed_data['sort_order']) && in_array($exposed_data['sort_order'], array('ASC', 'DESC'))) {
+ $sort->options['order'] = $exposed_data['sort_order'];
+ }
+ $sort->set_relationship();
+ $sort->query();
+ }
+ }
+ }
+ }
+ }
+
+ function pre_render($values) { }
+
+ function post_render(&$output) { }
+
+ function pre_execute() { }
+
+ function post_execute() { }
+
+ function exposed_form_alter(&$form, &$form_state) {
+ if (!empty($this->options['reset_button'])) {
+ $form['reset'] = array(
+ '#value' => $this->options['reset_button_label'],
+ '#type' => 'submit',
+ );
+ }
+
+ $form['submit']['#value'] = $this->options['submit_button'];
+ // Check if there is exposed sorts for this view
+ $exposed_sorts = array();
+ foreach ($this->view->sort as $id => $handler) {
+ if ($handler->can_expose() && $handler->is_exposed()) {
+ $exposed_sorts[$id] = check_plain($handler->options['expose']['label']);
+ }
+ }
+
+ if (count($exposed_sorts)) {
+ $form['sort_by'] = array(
+ '#type' => 'select',
+ '#options' => $exposed_sorts,
+ '#title' => $this->options['exposed_sorts_label'],
+ );
+ $sort_order = array(
+ 'ASC' => $this->options['sort_asc_label'],
+ 'DESC' => $this->options['sort_desc_label'],
+ );
+ if (isset($form_state['input']['sort_by']) && isset($this->view->sort[$form_state['input']['sort_by']])) {
+ $default_sort_order = $this->view->sort[$form_state['input']['sort_by']]->options['order'];
+ } else {
+ $first_sort = reset($this->view->sort);
+ $default_sort_order = $first_sort->options['order'];
+ }
+
+ if (!isset($form_state['input']['sort_by'])) {
+ $keys = array_keys($exposed_sorts);
+ $form_state['input']['sort_by'] = array_shift($keys);
+ }
+
+ if ($this->options['expose_sort_order']) {
+ $form['sort_order'] = array(
+ '#type' => 'select',
+ '#options' => $sort_order,
+ '#title' => t('Order'),
+ '#default_value' => $default_sort_order,
+ );
+ }
+ $form['submit']['#weight'] = 10;
+ if (isset($form['reset'])) {
+ $form['reset']['#weight'] = 10;
+ }
+ }
+
+ $pager = $this->view->display_handler->get_plugin('pager');
+ if ($pager) {
+ $pager->exposed_form_alter($form, $form_state);
+ $form_state['pager_plugin'] = $pager;
+ }
+
+
+ // Apply autosubmit values.
+ if (!empty($this->options['autosubmit'])) {
+ $form = array_merge_recursive($form, array('#attributes' => array('class' => array('ctools-auto-submit-full-form'))));
+ $form['submit']['#attributes']['class'][] = 'ctools-use-ajax';
+ $form['submit']['#attributes']['class'][] = 'ctools-auto-submit-click';
+ $form['#attached']['js'][] = drupal_get_path('module', 'ctools') . '/js/auto-submit.js';
+
+ if (!empty($this->options['autosubmit_hide'])) {
+ $form['submit']['#attributes']['class'][] = 'js-hide';
+ }
+ }
+ }
+
+ function exposed_form_validate(&$form, &$form_state) {
+ if (isset($form_state['pager_plugin'])) {
+ $form_state['pager_plugin']->exposed_form_validate($form, $form_state);
+ }
+ }
+
+ /**
+ * This function is executed when exposed form is submited.
+ *
+ * @param $form
+ * Nested array of form elements that comprise the form.
+ * @param $form_state
+ * A keyed array containing the current state of the form.
+ * @param $exclude
+ * Nested array of keys to exclude of insert into
+ * $view->exposed_raw_input
+ */
+ function exposed_form_submit(&$form, &$form_state, &$exclude) {
+ if (!empty($form_state['values']['op']) && $form_state['values']['op'] == $this->options['reset_button_label']) {
+ $this->reset_form($form, $form_state);
+ }
+ if (isset($form_state['pager_plugin'])) {
+ $form_state['pager_plugin']->exposed_form_submit($form, $form_state, $exclude);
+ $exclude[] = 'pager_plugin';
+ }
+ }
+
+ function reset_form(&$form, &$form_state) {
+ // _SESSION is not defined for users who are not logged in.
+
+ // If filters are not overridden, store the 'remember' settings on the
+ // default display. If they are, store them on this display. This way,
+ // multiple displays in the same view can share the same filters and
+ // remember settings.
+ $display_id = ($this->view->display_handler->is_defaulted('filters')) ? 'default' : $this->view->current_display;
+
+ if (isset($_SESSION['views'][$this->view->name][$display_id])) {
+ unset($_SESSION['views'][$this->view->name][$display_id]);
+ }
+
+ // Set the form to allow redirect.
+ if (empty($this->view->live_preview)) {
+ $form_state['no_redirect'] = FALSE;
+ }
+ else {
+ $form_state['rebuild'] = TRUE;
+ $this->view->exposed_data = array();
+ }
+
+ $form_state['redirect'] = current_path();
+ $form_state['values'] = array();
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_exposed_form_basic.inc b/sites/all/modules/views/plugins/views_plugin_exposed_form_basic.inc
new file mode 100644
index 000000000..73ae54aa0
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_exposed_form_basic.inc
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_exposed_form_basic.
+ */
+
+/**
+ * Exposed form plugin that provides a basic exposed form.
+ *
+ * @ingroup views_exposed_form_plugins
+ */
+class views_plugin_exposed_form_basic extends views_plugin_exposed_form { }
diff --git a/sites/all/modules/views/plugins/views_plugin_exposed_form_input_required.inc b/sites/all/modules/views/plugins/views_plugin_exposed_form_input_required.inc
new file mode 100644
index 000000000..ca97674ea
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_exposed_form_input_required.inc
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_exposed_form_input_required.
+ */
+
+/**
+ * Exposed form plugin that provides an exposed form with required input.
+ *
+ * @ingroup views_exposed_form_plugins
+ */
+class views_plugin_exposed_form_input_required extends views_plugin_exposed_form {
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['text_input_required'] = array('default' => 'Select any filter and click on Apply to see results', 'translatable' => TRUE);
+ $options['text_input_required_format'] = array('default' => NULL);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['text_input_required'] = array(
+ '#type' => 'text_format',
+ '#title' => t('Text on demand'),
+ '#description' => t('Text to display instead of results until the user selects and applies an exposed filter.'),
+ '#default_value' => $this->options['text_input_required'],
+ '#format' => isset($this->options['text_input_required_format']) ? $this->options['text_input_required_format'] : filter_default_format(),
+ '#wysiwyg' => FALSE,
+ );
+ }
+
+ function options_submit(&$form, &$form_state) {
+ $form_state['values']['exposed_form_options']['text_input_required_format'] = $form_state['values']['exposed_form_options']['text_input_required']['format'];
+ $form_state['values']['exposed_form_options']['text_input_required'] = $form_state['values']['exposed_form_options']['text_input_required']['value'];
+ parent::options_submit($form, $form_state);
+ }
+
+ function exposed_filter_applied() {
+ static $cache = NULL;
+ if (!isset($cache)) {
+ $view = $this->view;
+ if (is_array($view->filter) && count($view->filter)) {
+ foreach ($view->filter as $filter_id => $filter) {
+ if ($filter->is_exposed()) {
+ $identifier = $filter->options['expose']['identifier'];
+ if (isset($view->exposed_input[$identifier])) {
+ $cache = TRUE;
+ return $cache;
+ }
+ }
+ }
+ }
+ $cache = FALSE;
+ }
+
+ return $cache;
+ }
+
+ function pre_render($values) {
+ if (!$this->exposed_filter_applied()) {
+ $options = array(
+ 'id' => 'area',
+ 'table' => 'views',
+ 'field' => 'area',
+ 'label' => '',
+ 'relationship' => 'none',
+ 'group_type' => 'group',
+ 'content' => $this->options['text_input_required'],
+ 'format' => $this->options['text_input_required_format'],
+ 'empty' => TRUE,
+ );
+ $handler = views_get_handler('views', 'area', 'area');
+ $handler->init($this->view, $options);
+ $this->display->handler->handlers['empty'] = array(
+ 'area' => $handler,
+ );
+ $this->display->handler->set_option('empty', array('text' => $options));
+ }
+ }
+
+ function query() {
+ if (!$this->exposed_filter_applied()) {
+ // We return with no query; this will force the empty text.
+ $this->view->built = TRUE;
+ $this->view->executed = TRUE;
+ $this->view->result = array();
+ }
+ else {
+ parent::query();
+ }
+ }
+
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_localization.inc b/sites/all/modules/views/plugins/views_plugin_localization.inc
new file mode 100644
index 000000000..08caf9e08
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_localization.inc
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * @file
+ * Contains the base class for views localization plugins.
+ */
+
+/**
+ * @defgroup views_localization_plugins Views localization plugins
+ * @{
+ * @todo.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * The base plugin to handle localization of Views strings.
+ */
+class views_plugin_localization extends views_plugin {
+ // Store for exported strings
+ var $export_strings = array();
+ var $translate = TRUE;
+
+ /**
+ * Initialize the plugin.
+ *
+ * @param $view
+ * The view object.
+ */
+ function init(&$view) {
+ $this->view = &$view;
+ }
+
+ /**
+ * Translate a string / text with format
+ *
+ * The $source parameter is an array with the following elements:
+ * - value, source string
+ * - format, input format in case the text has some format to be applied
+ * - keys. An array of keys to identify the string. Generally constructed from
+ * view name, display_id, and a property, e.g., 'header'.
+ *
+ * @param $source
+ * Full data for the string to be translated.
+ *
+ * @return string
+ * Translated string / text
+ */
+ function translate($source) {
+ // Allow other modules to make changes to the string before and after translation
+ $source['pre_process'] = $this->invoke_translation_process($source, 'pre');
+ $source['translation'] = $this->translate_string($source['value'], $source['keys'], $source['format']);
+ $source['post_process'] = $this->invoke_translation_process($source, 'post');
+ return $source['translation'];
+ }
+
+ /**
+ * Translate a string.
+ *
+ * @param $string
+ * The string to be translated.
+ * @param $keys
+ * An array of keys to identify the string. Generally constructed from
+ * view name, display_id, and a property, e.g., 'header'.
+ * @param $format
+ * The input format of the string. This is optional.
+ */
+ function translate_string($string, $keys = array(), $format = '') {}
+
+ /**
+ * Save string source for translation.
+ *
+ * @param $source
+ * Full data for the string to be translated.
+ */
+ function save($source) {
+ // Allow other modules to make changes to the string before saving
+ $source['pre_process'] = $this->invoke_translation_process($source, 'pre');
+ $this->save_string($source['value'], $source['keys'], isset($source['format']) ? $source['format'] : '');
+ }
+
+ /**
+ * Save a string for translation
+ *
+ * @param $string
+ * The string to be translated.
+ * @param $keys
+ * An array of keys to identify the string. Generally constructed from
+ * view name, display_id, and a property, e.g., 'header'.
+ * @param $format
+ * The input format of the string. This is optional.
+ */
+ function save_string($string, $keys = array(), $format = '') {}
+
+ /**
+ * Delete a string.
+ *
+ * @param $source
+ * Full data for the string to be translated.
+ */
+ function delete($source) { }
+
+ /**
+ * Collect strings to be exported to code.
+ *
+ * @param $source
+ * Full data for the string to be translated.
+ */
+ function export($source) { }
+
+ /**
+ * Render any collected exported strings to code.
+ *
+ * @param $indent
+ * An optional indentation for prettifying nested code.
+ */
+ function export_render($indent = ' ') { }
+
+ /**
+ * Invoke hook_translation_pre_process() or hook_translation_post_process().
+ *
+ * Like node_invoke_nodeapi(), this function is needed to enable both passing
+ * by reference and fetching return values.
+ */
+ function invoke_translation_process(&$value, $op) {
+ $return = array();
+ $hook = 'translation_' . $op . '_process';
+ foreach (module_implements($hook) as $module) {
+ $function = $module . '_' . $hook;
+ $result = $function($value);
+ if (isset($result)) {
+ $return[$module] = $result;
+ }
+ }
+ return $return;
+ }
+
+ function process_locale_strings($op) {
+ $this->view->init_display();
+
+ foreach ($this->view->display as $display_id => $display) {
+ $translatable = array();
+ // Special handling for display title.
+ if (isset($display->display_title)) {
+ $translatable[] = array('value' => $display->display_title, 'keys' => array('display_title'));
+ }
+ // Unpack handlers.
+ if (is_object($this->view->display[$display_id]->handler)) {
+ $this->view->display[$display_id]->handler->unpack_translatables($translatable);
+ }
+ foreach ($translatable as $data) {
+ $data['keys'] = array_merge(array($this->view->name, $display_id), $data['keys']);
+ switch ($op) {
+ case 'save':
+ $this->save($data);
+ break;
+ case 'delete':
+ $this->delete($data);
+ break;
+ case 'export':
+ $this->export($data);
+ break;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_localization_core.inc b/sites/all/modules/views/plugins/views_plugin_localization_core.inc
new file mode 100644
index 000000000..87443ca0d
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_localization_core.inc
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Contains the Drupal core localization plugin.
+ */
+
+/**
+ * Localization plugin to pass translatable strings through t().
+ *
+ * @ingroup views_localization_plugins
+ */
+class views_plugin_localization_core extends views_plugin_localization {
+
+ /**
+ * Translate a string.
+ *
+ * @param $string
+ * The string to be translated.
+ * @param $keys
+ * An array of keys to identify the string. Generally constructed from
+ * view name, display_id, and a property, e.g., 'header'.
+ * @param $format
+ * The input format of the string. This is optional.
+ */
+ function translate_string($string, $keys = array(), $format = '') {
+ return t($string);
+ }
+
+ /**
+ * Save a string for translation.
+ *
+ * @param $string
+ * The string to be translated.
+ * @param $keys
+ * An array of keys to identify the string. Generally constructed from
+ * view name, display_id, and a property, e.g., 'header'.
+ * @param $format
+ * The input format of the string. This is optional.
+ */
+ function save_string($string, $keys = array(), $format = '') {
+ global $language;
+
+ // If the current language is 'en', we need to reset the language
+ // in order to trigger an update.
+ // TODO: add test for number of languages.
+ if ($language->language == 'en') {
+ $changed = TRUE;
+ $languages = language_list();
+ $cached_language = $language;
+ unset($languages['en']);
+ if (!empty($languages)) {
+ $language = current($languages);
+ }
+ }
+
+ t($string);
+
+ if (isset($cached_language)) {
+ $language = $cached_language;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Delete a string.
+ *
+ * Deletion is not supported.
+ *
+ * @param $source
+ * Full data for the string to be translated.
+ */
+ function delete($source) {
+ return FALSE;
+ }
+
+ /**
+ * Collect strings to be exported to code.
+ *
+ * String identifiers are not supported so strings are anonymously in an array.
+ *
+ * @param $source
+ * Full data for the string to be translated.
+ */
+ function export($source) {
+ if (!empty($source['value'])) {
+ $this->export_strings[] = $source['value'];
+ }
+ }
+
+ /**
+ * Render any collected exported strings to code.
+ *
+ * @param $indent
+ * An optional indentation for prettifying nested code.
+ */
+ function export_render($indent = ' ') {
+ $output = '';
+ if (!empty($this->export_strings)) {
+ $this->export_strings = array_unique($this->export_strings);
+ $output = $indent . '$translatables[\'' . $this->view->name . '\'] = array(' . "\n";
+ foreach ($this->export_strings as $string) {
+ $output .= $indent . " t('" . str_replace("'", "\'", $string) . "'),\n";
+ }
+ $output .= $indent . ");\n";
+ }
+ return $output;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_localization_none.inc b/sites/all/modules/views/plugins/views_plugin_localization_none.inc
new file mode 100644
index 000000000..620352a26
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_localization_none.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains the 'none' localization plugin.
+ */
+
+/**
+ * Localization plugin for no localization.
+ *
+ * @ingroup views_localization_plugins
+ */
+class views_plugin_localization_none extends views_plugin_localization {
+ var $translate = FALSE;
+
+ /**
+ * Translate a string; simply return the string.
+ */
+ function translate($source) {
+ return $source['value'];
+ }
+
+ /**
+ * Save a string for translation; not supported.
+ */
+ function save($source) {
+ return FALSE;
+ }
+
+ /**
+ * Delete a string; not supported.
+ */
+ function delete($source) {
+ return FALSE;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_pager.inc b/sites/all/modules/views/plugins/views_plugin_pager.inc
new file mode 100644
index 000000000..312f54b9f
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_pager.inc
@@ -0,0 +1,236 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_pager.
+ */
+
+/**
+ * @defgroup views_pager_plugins Views pager plugins
+ * @{
+ * @todo.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * The base plugin to handle pager.
+ */
+class views_plugin_pager extends views_plugin {
+ var $current_page = NULL;
+ var $total_items = 0;
+
+ /**
+ * Initialize the plugin.
+ *
+ * @param $view
+ * The view object.
+ * @param $display
+ * The display handler.
+ */
+ function init(&$view, &$display, $options = array()) {
+ $this->view = &$view;
+ $this->display = &$display;
+
+ $this->unpack_options($this->options, $options);
+ }
+
+ /**
+ * Get how many items per page this pager will display.
+ *
+ * All but the leanest pagers should probably return a value here, so
+ * most pagers will not need to override this method.
+ */
+ function get_items_per_page() {
+ return isset($this->options['items_per_page']) ? $this->options['items_per_page'] : 0;
+ }
+
+ /**
+ * Set how many items per page this pager will display.
+ *
+ * This is mostly used for things that will override the value.
+ */
+ function set_items_per_page($items) {
+ $this->options['items_per_page'] = $items;
+ }
+
+ /**
+ * Get the page offset, or how many items to skip.
+ *
+ * Even pagers that don't actually page can skip items at the beginning,
+ * so few pagers will need to override this method.
+ */
+ function get_offset() {
+ return isset($this->options['offset']) ? $this->options['offset'] : 0;
+ }
+
+ /**
+ * Set the page offset, or how many items to skip.
+ */
+ function set_offset($offset) {
+ $this->options['offset'] = $offset;
+ }
+
+ /**
+ * Get the current page.
+ *
+ * If NULL, we do not know what the current page is.
+ */
+ function get_current_page() {
+ return $this->current_page;
+ }
+
+ /**
+ * Set the current page.
+ *
+ * @param $number
+ * If provided, the page number will be set to this. If NOT provided,
+ * the page number will be set from the global page array.
+ */
+ function set_current_page($number = NULL) {
+ if (!is_numeric($number) || $number < 0) {
+ $number = 0;
+ }
+ $this->current_page = $number;
+ }
+
+ /**
+ * Get the total number of items.
+ *
+ * If NULL, we do not yet know what the total number of items are.
+ */
+ function get_total_items() {
+ return $this->total_items;
+ }
+
+ /**
+ * Get the pager id, if it exists
+ */
+ function get_pager_id() {
+ return !empty($this->options['id']) ? $this->options['id'] : 0;
+ }
+
+ /**
+ * Provide the default form form for validating options
+ */
+ function options_validate(&$form, &$form_state) { }
+
+ /**
+ * Provide the default form form for submitting options
+ */
+ function options_submit(&$form, &$form_state) { }
+
+ /**
+ * Return a string to display as the clickable title for the
+ * pager plugin.
+ */
+ function summary_title() {
+ return t('Unknown');
+ }
+
+ /**
+ * Determine if this pager actually uses a pager.
+ *
+ * Only a couple of very specific pagers will set this to false.
+ */
+ function use_pager() {
+ return TRUE;
+ }
+
+ /**
+ * Determine if a pager needs a count query.
+ *
+ * If a pager needs a count query, a simple query
+ */
+ function use_count_query() {
+ return TRUE;
+ }
+
+ /**
+ * Execute the count query, which will be done just prior to the query
+ * itself being executed.
+ */
+ function execute_count_query(&$count_query) {
+ $this->total_items = $count_query->execute()->fetchField();
+ if (!empty($this->options['offset'])) {
+ $this->total_items -= $this->options['offset'];
+ }
+
+ $this->update_page_info();
+ return $this->total_items;
+ }
+
+ /**
+ * If there are pagers that need global values set, this method can
+ * be used to set them. It will be called when the count query is run.
+ */
+ function update_page_info() {
+
+ }
+
+ /**
+ * Modify the query for paging
+ *
+ * This is called during the build phase and can directly modify the query.
+ */
+ function query() { }
+
+ /**
+ * Perform any needed actions just prior to the query executing.
+ */
+ function pre_execute(&$query) { }
+
+ /**
+ * Perform any needed actions just after the query executing.
+ */
+ function post_execute(&$result) { }
+
+ /**
+ * Perform any needed actions just before rendering.
+ */
+ function pre_render(&$result) { }
+
+ /**
+ * Render the pager.
+ *
+ * Called during the view render process, this will render the
+ * pager.
+ *
+ * @param $input
+ * Any extra GET parameters that should be retained, such as exposed
+ * input.
+ */
+ function render($input) { }
+
+ /**
+ * Determine if there are more records available.
+ *
+ * This is primarily used to control the display of a more link.
+ */
+ function has_more_records() {
+ return $this->get_items_per_page()
+ && $this->total_items > (intval($this->current_page) + 1) * $this->get_items_per_page();
+ }
+
+ function exposed_form_alter(&$form, &$form_state) { }
+
+ function exposed_form_validate(&$form, &$form_state) { }
+
+ function exposed_form_submit(&$form, &$form_state, &$exclude) { }
+
+ function uses_exposed() {
+ return FALSE;
+ }
+
+ function items_per_page_exposed() {
+ return FALSE;
+ }
+
+ function offset_exposed() {
+ return FALSE;
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_pager_full.inc b/sites/all/modules/views/plugins/views_plugin_pager_full.inc
new file mode 100644
index 000000000..aedb2237a
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_pager_full.inc
@@ -0,0 +1,424 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_pager_full.
+ */
+
+/**
+ * The plugin to handle full pager.
+ *
+ * @ingroup views_pager_plugins
+ */
+class views_plugin_pager_full extends views_plugin_pager {
+ function summary_title() {
+ if (!empty($this->options['offset'])) {
+ return format_plural($this->options['items_per_page'], '@count item, skip @skip', 'Paged, @count items, skip @skip', array('@count' => $this->options['items_per_page'], '@skip' => $this->options['offset']));
+ }
+ return format_plural($this->options['items_per_page'], '@count item', 'Paged, @count items', array('@count' => $this->options['items_per_page']));
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['items_per_page'] = array('default' => 10);
+ $options['offset'] = array('default' => 0);
+ $options['id'] = array('default' => 0);
+ $options['total_pages'] = array('default' => '');
+ // Use the same default quantity that core uses by default.
+ $options['quantity'] = array('default' => 9);
+ $options['expose'] = array(
+ 'contains' => array(
+ 'items_per_page' => array('default' => FALSE, 'bool' => TRUE),
+ 'items_per_page_label' => array('default' => 'Items per page', 'translatable' => TRUE),
+ 'items_per_page_options' => array('default' => '5, 10, 20, 40, 60'),
+ 'items_per_page_options_all' => array('default' => FALSE, 'bool' => TRUE),
+ 'items_per_page_options_all_label' => array('default' => '- All -', 'translatable' => TRUE),
+
+ 'offset' => array('default' => FALSE, 'bool' => TRUE),
+ 'offset_label' => array('default' => 'Offset', 'translatable' => TRUE),
+ ),
+ );
+ $options['tags'] = array(
+ 'contains' => array(
+ 'first' => array('default' => '« first', 'translatable' => TRUE),
+ 'previous' => array('default' => '‹ previous', 'translatable' => TRUE),
+ 'next' => array('default' => 'next ›', 'translatable' => TRUE),
+ 'last' => array('default' => 'last »', 'translatable' => TRUE),
+ ),
+ );
+ return $options;
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $pager_text = $this->display->handler->get_pager_text();
+ $form['items_per_page'] = array(
+ '#title' => $pager_text['items per page title'],
+ '#type' => 'textfield',
+ '#description' => $pager_text['items per page description'],
+ '#default_value' => $this->options['items_per_page'],
+ );
+
+ $form['offset'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Offset'),
+ '#description' => t('The number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed.'),
+ '#default_value' => $this->options['offset'],
+ );
+
+ $form['id'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Pager ID'),
+ '#description' => t("Unless you're experiencing problems with pagers related to this view, you should leave this at 0. If using multiple pagers on one page you may need to set this number to a higher value so as not to conflict within the ?page= array. Large values will add a lot of commas to your URLs, so avoid if possible."),
+ '#default_value' => $this->options['id'],
+ );
+
+ $form['total_pages'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Number of pages'),
+ '#description' => t('The total number of pages. Leave empty to show all pages.'),
+ '#default_value' => $this->options['total_pages'],
+ );
+
+ $form['quantity'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Number of pager links visible'),
+ '#description' => t('Specify the number of links to pages to display in the pager.'),
+ '#default_value' => $this->options['quantity'],
+ );
+
+ $form['tags'] = array (
+ '#type' => 'fieldset',
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ '#tree' => TRUE,
+ '#title' => t('Tags'),
+ '#input' => TRUE,
+ '#description' => t('A lists of labels for the controls in the pager'),
+ );
+
+ $form['tags']['first'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text for "first"-link'),
+ '#description' => t('Text for "first"-link'),
+ '#default_value' => $this->options['tags']['first'],
+ );
+
+ $form['tags']['previous'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text for "previous"-link'),
+ '#description' => t('Text for "previous"-link'),
+ '#default_value' => $this->options['tags']['previous'],
+ );
+
+ $form['tags']['next'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text for "next"-link'),
+ '#description' => t('Text for "next"-link'),
+ '#default_value' => $this->options['tags']['next'],
+ );
+
+ $form['tags']['last'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text for "last"-link'),
+ '#description' => t('Text for "last"-link'),
+ '#default_value' => $this->options['tags']['last'],
+ );
+
+ $form['expose'] = array (
+ '#type' => 'fieldset',
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ '#tree' => TRUE,
+ '#title' => t('Exposed options'),
+ '#input' => TRUE,
+ '#description' => t('Exposing this options allows users to define their values in a exposed form when view is displayed'),
+ );
+
+ $form['expose']['items_per_page'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Expose items per page'),
+ '#description' => t('When checked, users can determine how many items per page show in a view'),
+ '#default_value' => $this->options['expose']['items_per_page'],
+ );
+
+ $form['expose']['items_per_page_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Items per page label'),
+ '#required' => TRUE,
+ '#description' => t('Label to use in the exposed items per page form element.'),
+ '#default_value' => $this->options['expose']['items_per_page_label'],
+ '#dependency' => array(
+ 'edit-pager-options-expose-items-per-page' => array(1)
+ ),
+ );
+
+ $form['expose']['items_per_page_options'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Exposed items per page options'),
+ '#required' => TRUE,
+ '#description' => t('Set between which values the user can choose when determining the items per page. Separated by comma.'),
+ '#default_value' => $this->options['expose']['items_per_page_options'],
+ '#dependency' => array(
+ 'edit-pager-options-expose-items-per-page' => array(1)
+ ),
+ );
+
+
+ $form['expose']['items_per_page_options_all'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include all items option'),
+ '#description' => t('If checked, an extra item will be included to items per page to display all items'),
+ '#default_value' => $this->options['expose']['items_per_page_options_all'],
+ );
+
+ $form['expose']['items_per_page_options_all_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('All items label'),
+ '#description' => t('Which label will be used to display all items'),
+ '#default_value' => $this->options['expose']['items_per_page_options_all_label'],
+ '#dependency' => array(
+ 'edit-items-per-page-options-all' => array(1),
+ ),
+ );
+
+ $form['expose']['offset'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Expose Offset'),
+ '#description' => t('When checked, users can determine how many items should be skipped at the beginning.'),
+ '#default_value' => $this->options['expose']['offset'],
+ );
+
+ $form['expose']['offset_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Offset label'),
+ '#required' => TRUE,
+ '#description' => t('Label to use in the exposed offset form element.'),
+ '#default_value' => $this->options['expose']['offset_label'],
+ '#dependency' => array(
+ 'edit-pager-options-expose-offset' => array(1)
+ ),
+ );
+ }
+
+ function options_validate(&$form, &$form_state) {
+ // Only accept integer values.
+ $error = FALSE;
+ $exposed_options = $form_state['values']['pager_options']['expose']['items_per_page_options'];
+ if (strpos($exposed_options, '.') !== FALSE) {
+ $error = TRUE;
+ }
+ $options = explode(',',$exposed_options);
+ if (!$error && is_array($options)) {
+ foreach ($options as $option) {
+ if (!is_numeric($option) || intval($option) == 0) {
+ $error = TRUE;
+ }
+ }
+ }
+ else {
+ $error = TRUE;
+ }
+ if ($error) {
+ form_set_error('pager_options][expose][items_per_page_options', t('Please insert a list of integer numeric values separated by commas: e.g: 10, 20, 50, 100'));
+ }
+
+ // Take sure that the items_per_page is part of the expose settings.
+ if (!empty($form_state['values']['pager_options']['expose']['items_per_page']) && !empty($form_state['values']['pager_options']['items_per_page'])) {
+ $items_per_page = $form_state['values']['pager_options']['items_per_page'];
+ if (array_search($items_per_page, $options) === FALSE) {
+ form_set_error('pager_options][expose][items_per_page_options', t('Please insert the items per page (@items_per_page) from above.',
+ array('@items_per_page' => $items_per_page))
+ );
+ }
+ }
+ }
+
+ function query() {
+ if ($this->items_per_page_exposed()) {
+ if (!empty($_GET['items_per_page']) && $_GET['items_per_page'] > 0) {
+ $this->options['items_per_page'] = $_GET['items_per_page'];
+ }
+ elseif (!empty($_GET['items_per_page']) && $_GET['items_per_page'] == 'All' && $this->options['expose']['items_per_page_options_all']) {
+ $this->options['items_per_page'] = 0;
+ }
+ }
+ if ($this->offset_exposed()) {
+ if (isset($_GET['offset']) && $_GET['offset'] >= 0) {
+ $this->options['offset'] = $_GET['offset'];
+ }
+ }
+
+ $limit = $this->options['items_per_page'];
+ $offset = $this->current_page * $this->options['items_per_page'] + $this->options['offset'];
+ if (!empty($this->options['total_pages'])) {
+ if ($this->current_page >= $this->options['total_pages']) {
+ $limit = $this->options['items_per_page'];
+ $offset = $this->options['total_pages'] * $this->options['items_per_page'];
+ }
+ }
+
+ $this->view->query->set_limit($limit);
+ $this->view->query->set_offset($offset);
+ }
+
+ function render($input) {
+ $pager_theme = views_theme_functions('pager', $this->view, $this->display);
+ // The 0, 1, 3, 4 index are correct. See theme_pager documentation.
+ $tags = array(
+ 0 => $this->options['tags']['first'],
+ 1 => $this->options['tags']['previous'],
+ 3 => $this->options['tags']['next'],
+ 4 => $this->options['tags']['last'],
+ );
+ $output = theme($pager_theme, array(
+ 'tags' => $tags,
+ 'element' => $this->get_pager_id(),
+ 'parameters' => $input,
+ 'quantity' => $this->options['quantity'],
+ ));
+ return $output;
+ }
+
+ /**
+ * Set the current page.
+ *
+ * @param $number
+ * If provided, the page number will be set to this. If NOT provided,
+ * the page number will be set from the global page array.
+ */
+ function set_current_page($number = NULL) {
+ if (isset($number)) {
+ $this->current_page = $number;
+ return;
+ }
+
+ // If the current page number was not specified, extract it from the global
+ // page array.
+ global $pager_page_array;
+
+ if (empty($pager_page_array)) {
+ $pager_page_array = array();
+ }
+
+ // Fill in missing values in the global page array, in case the global page
+ // array hasn't been initialized before.
+ $page = isset($_GET['page']) ? explode(',', $_GET['page']) : array();
+
+ $pager_id = $this->get_pager_id();
+ for ($i = 0; $i <= $pager_id || $i < count($pager_page_array); $i++) {
+ $pager_page_array[$i] = empty($page[$i]) ? 0 : $page[$i];
+ }
+
+ $this->current_page = intval($pager_page_array[$pager_id]);
+
+ if ($this->current_page < 0) {
+ $this->current_page = 0;
+ }
+ }
+
+ function get_pager_total() {
+ if ($items_per_page = intval($this->get_items_per_page())) {
+ return ceil($this->total_items / $items_per_page);
+ }
+ else {
+ return 1;
+ }
+ }
+
+ /**
+ * Update global paging info.
+ *
+ * This is called after the count query has been run to set the total
+ * items available and to update the current page if the requested
+ * page is out of range.
+ */
+ function update_page_info() {
+ if (!empty($this->options['total_pages'])) {
+ if (($this->options['total_pages'] * $this->options['items_per_page']) < $this->total_items) {
+ $this->total_items = $this->options['total_pages'] * $this->options['items_per_page'];
+ }
+ }
+
+ // Don't set pager settings for items per page = 0.
+ $items_per_page = $this->get_items_per_page();
+ if (!empty($items_per_page)) {
+ // Dump information about what we already know into the globals.
+ global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
+ // Set the limit.
+ $pager_id = $this->get_pager_id();
+ $pager_limits[$pager_id] = $this->options['items_per_page'];
+ // Set the item count for the pager.
+ $pager_total_items[$pager_id] = $this->total_items;
+ // Calculate and set the count of available pages.
+ $pager_total[$pager_id] = $this->get_pager_total();
+
+ // See if the requested page was within range:
+ if ($this->current_page < 0) {
+ $this->current_page = 0;
+ }
+ else if ($this->current_page >= $pager_total[$pager_id]) {
+ // Pages are numbered from 0 so if there are 10 pages, the last page is 9.
+ $this->current_page = $pager_total[$pager_id] - 1;
+ }
+
+ // Put this number in to guarantee that we do not generate notices when the pager
+ // goes to look for it later.
+ $pager_page_array[$pager_id] = $this->current_page;
+ }
+ }
+
+ function uses_exposed() {
+ return $this->items_per_page_exposed() || $this->offset_exposed();
+ }
+
+ function items_per_page_exposed() {
+ return !empty($this->options['expose']['items_per_page']);
+ }
+
+ function offset_exposed() {
+ return !empty($this->options['expose']['offset']);
+ }
+
+ function exposed_form_alter(&$form, &$form_state) {
+ if ($this->items_per_page_exposed()) {
+ $options = explode(',', $this->options['expose']['items_per_page_options']);
+ $sanitized_options = array();
+ if (is_array($options)) {
+ foreach ($options as $option) {
+ $sanitized_options[intval($option)] = intval($option);
+ }
+ if (!empty($this->options['expose']['items_per_page_options_all']) && !empty($this->options['expose']['items_per_page_options_all_label'])) {
+ $sanitized_options['All'] = $this->options['expose']['items_per_page_options_all_label'];
+ }
+ $form['items_per_page'] = array(
+ '#type' => 'select',
+ '#title' => $this->options['expose']['items_per_page_label'],
+ '#options' => $sanitized_options,
+ '#default_value' => $this->get_items_per_page(),
+ );
+ }
+ }
+
+ if ($this->offset_exposed()) {
+ $form['offset'] = array(
+ '#type' => 'textfield',
+ '#size' => 10,
+ '#maxlength' => 10,
+ '#title' => $this->options['expose']['offset_label'],
+ '#default_value' => $this->get_offset(),
+ );
+ }
+ }
+
+ function exposed_form_validate(&$form, &$form_state) {
+ if (!empty($form_state['values']['offset']) && trim($form_state['values']['offset'])) {
+ if (!is_numeric($form_state['values']['offset']) || $form_state['values']['offset'] < 0) {
+ form_set_error('offset', t('Offset must be an number greather or equal than 0.'));
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_pager_mini.inc b/sites/all/modules/views/plugins/views_plugin_pager_mini.inc
new file mode 100644
index 000000000..87d55410a
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_pager_mini.inc
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_pager_mini.
+ */
+
+/**
+ * The plugin to handle mini pager.
+ *
+ * @ingroup views_pager_plugins
+ */
+class views_plugin_pager_mini extends views_plugin_pager_full {
+ function summary_title() {
+ if (!empty($this->options['offset'])) {
+ return format_plural($this->options['items_per_page'], 'Mini pager, @count item, skip @skip', 'Mini pager, @count items, skip @skip', array('@count' => $this->options['items_per_page'], '@skip' => $this->options['offset']));
+ }
+ return format_plural($this->options['items_per_page'], 'Mini pager, @count item', 'Mini pager, @count items', array('@count' => $this->options['items_per_page']));
+ }
+
+ /**
+ * Overrides views_plugin_pager_full::option_definition().
+ *
+ * Overrides the full pager options form by deleting unused settings.
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+
+ unset($options['quantity']);
+ unset($options['tags']['first']);
+ unset($options['tags']['last']);
+ $options['tags']['previous']['default'] = '‹‹';
+ $options['tags']['next']['default'] = '››';
+
+ return $options;
+ }
+
+ /**
+ * Overrides views_plugin_pager_full::options_form().
+ *
+ * Overrides the full pager options form by deleting unused settings.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ unset($form['quantity']);
+ unset($form['tags']['first']);
+ unset($form['tags']['last']);
+ }
+
+ /**
+ * Overrides views_plugin_pager_full::render().
+ *
+ * Overrides the full pager renderer by changing the theme function
+ * and leaving out variables that are not used in the mini pager.
+ */
+ function render($input) {
+ $pager_theme = views_theme_functions('views_mini_pager', $this->view, $this->display);
+ // The 1, 3 index are correct.
+ // @see theme_pager().
+ $tags = array(
+ 1 => $this->options['tags']['previous'],
+ 3 => $this->options['tags']['next'],
+ );
+ return theme($pager_theme, array(
+ 'tags' => $tags,
+ 'element' => $this->get_pager_id(),
+ 'parameters' => $input,
+ ));
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_pager_none.inc b/sites/all/modules/views/plugins/views_plugin_pager_none.inc
new file mode 100644
index 000000000..12b96d04d
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_pager_none.inc
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_pager_none.
+ */
+
+/**
+ * Plugin for views without pagers.
+ *
+ * @ingroup views_pager_plugins
+ */
+class views_plugin_pager_none extends views_plugin_pager {
+
+ function init(&$view, &$display, $options = array()) {
+ parent::init($view, $display, $options);
+
+ // If the pager is set to none, then it should show all items.
+ $this->set_items_per_page(0);
+ }
+
+ function summary_title() {
+ if (!empty($this->options['offset'])) {
+ return t('All items, skip @skip', array('@skip' => $this->options['offset']));
+ }
+ return t('All items');
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['offset'] = array('default' => 0);
+
+ return $options;
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['offset'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Offset'),
+ '#description' => t('The number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed.'),
+ '#default_value' => $this->options['offset'],
+ );
+ }
+
+ function use_pager() {
+ return FALSE;
+ }
+
+ function use_count_query() {
+ return FALSE;
+ }
+
+ function get_items_per_page() {
+ return 0;
+ }
+
+ function execute_count_query(&$count_query) {
+ // If we are displaying all items, never count. But we can update the count in post_execute.
+ }
+
+ function post_execute(&$result) {
+ $this->total_items = count($result);
+ }
+
+ function query() {
+ // The only query modifications we might do are offsets.
+ if (!empty($this->options['offset'])) {
+ $this->view->query->set_offset($this->options['offset']);
+ }
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_pager_some.inc b/sites/all/modules/views/plugins/views_plugin_pager_some.inc
new file mode 100644
index 000000000..09452ce35
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_pager_some.inc
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_pager_some.
+ */
+
+/**
+ * Plugin for views without pagers.
+ *
+ * @ingroup views_pager_plugins
+ */
+class views_plugin_pager_some extends views_plugin_pager {
+ function summary_title() {
+ if (!empty($this->options['offset'])) {
+ return format_plural($this->options['items_per_page'], '@count item, skip @skip', '@count items, skip @skip', array('@count' => $this->options['items_per_page'], '@skip' => $this->options['offset']));
+ }
+ return format_plural($this->options['items_per_page'], '@count item', '@count items', array('@count' => $this->options['items_per_page']));
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['items_per_page'] = array('default' => 10);
+ $options['offset'] = array('default' => 0);
+
+ return $options;
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $pager_text = $this->display->handler->get_pager_text();
+ $form['items_per_page'] = array(
+ '#title' => $pager_text['items per page title'],
+ '#type' => 'textfield',
+ '#description' => $pager_text['items per page description'],
+ '#default_value' => $this->options['items_per_page'],
+ );
+
+ $form['offset'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Offset'),
+ '#description' => t('The number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed.'),
+ '#default_value' => $this->options['offset'],
+ );
+ }
+
+ function use_pager() {
+ return FALSE;
+ }
+
+ function use_count_query() {
+ return FALSE;
+ }
+
+ function query() {
+ $this->view->query->set_limit($this->options['items_per_page']);
+ $this->view->query->set_offset($this->options['offset']);
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_query.inc b/sites/all/modules/views/plugins/views_plugin_query.inc
new file mode 100644
index 000000000..d39ed9898
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_query.inc
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * @file
+ * Defines the base query class, which is the underlying layer in a View.
+ */
+
+/**
+ * @defgroup views_query_plugins Views query plugins
+ * @{
+ * A Views query plugin builds SQL to execute using the Drupal database API.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * Object used to create a SELECT query.
+ */
+class views_plugin_query extends views_plugin {
+ /**
+ * A pager plugin that should be provided by the display.
+ *
+ * @var views_plugin_pager
+ */
+ var $pager = NULL;
+
+ /**
+ * Constructor; Create the basic query object and fill with default values.
+ */
+ function init($base_table, $base_field, $options) {
+ $this->base_table = $base_table;
+ $this->base_field = $base_field;
+ $this->unpack_options($this->options, $options);
+ }
+
+ /**
+ * Generate a query and a countquery from all of the information supplied
+ * to the object.
+ *
+ * @param $get_count
+ * Provide a countquery if this is true, otherwise provide a normal query.
+ */
+ function query($get_count = FALSE) { }
+
+ /**
+ * Let modules modify the query just prior to finalizing it.
+ *
+ * @param view $view
+ * The view which is executed.
+ */
+ function alter(&$view) { }
+
+ /**
+ * Builds the necessary info to execute the query.
+ *
+ * @param view $view
+ * The view which is executed.
+ */
+ function build(&$view) { }
+
+ /**
+ * Executes the query and fills the associated view object with according
+ * values.
+ *
+ * Values to set: $view->result, $view->total_rows, $view->execute_time,
+ * $view->pager['current_page'].
+ *
+ * $view->result should contain an array of objects. The array must use a
+ * numeric index starting at 0.
+ *
+ * @param view $view
+ * The view which is executed.
+ */
+ function execute(&$view) { }
+
+ /**
+ * Add a signature to the query, if such a thing is feasible.
+ *
+ * This signature is something that can be used when perusing query logs to
+ * discern where particular queries might be coming from.
+ *
+ * @param view $view
+ * The view which is executed.
+ */
+ function add_signature(&$view) { }
+
+ /**
+ * Get aggregation info for group by queries.
+ *
+ * If NULL, aggregation is not allowed.
+ */
+ function get_aggregation_info() { }
+
+ /**
+ * Add settings for the ui.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ }
+
+ function options_validate(&$form, &$form_state) { }
+
+ function options_submit(&$form, &$form_state) { }
+
+ function summary_title() {
+ return t('Settings');
+ }
+
+ /**
+ * Set a LIMIT on the query, specifying a maximum number of results.
+ */
+ function set_limit($limit) {
+ $this->limit = $limit;
+ }
+
+ /**
+ * Set an OFFSET on the query, specifying a number of results to skip
+ */
+ function set_offset($offset) {
+ $this->offset = $offset;
+ }
+
+ /**
+ * Render the pager, if necessary.
+ */
+ function render_pager($exposed_input) {
+ if (!empty($this->pager) && $this->pager->use_pager()) {
+ return $this->pager->render($exposed_input);
+ }
+
+ return '';
+ }
+
+ /**
+ * Create a new grouping for the WHERE or HAVING clause.
+ *
+ * @param $type
+ * Either 'AND' or 'OR'. All items within this group will be added
+ * to the WHERE clause with this logical operator.
+ * @param $group
+ * An ID to use for this group. If unspecified, an ID will be generated.
+ * @param $where
+ * 'where' or 'having'.
+ *
+ * @return $group
+ * The group ID generated.
+ */
+ function set_where_group($type = 'AND', $group = NULL, $where = 'where') {
+ // Set an alias.
+ $groups = &$this->$where;
+
+ if (!isset($group)) {
+ $group = empty($groups) ? 1 : max(array_keys($groups)) + 1;
+ }
+
+ // Create an empty group
+ if (empty($groups[$group])) {
+ $groups[$group] = array('conditions' => array(), 'args' => array());
+ }
+
+ $groups[$group]['type'] = strtoupper($type);
+ return $group;
+ }
+
+ /**
+ * Control how all WHERE and HAVING groups are put together.
+ *
+ * @param $type
+ * Either 'AND' or 'OR'
+ */
+ function set_group_operator($type = 'AND') {
+ $this->group_operator = strtoupper($type);
+ }
+
+ /**
+ * Returns the according entity objects for the given query results.
+ */
+ function get_result_entities($results, $relationship = NULL) {
+ return FALSE;
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_query_default.inc b/sites/all/modules/views/plugins/views_plugin_query_default.inc
new file mode 100644
index 000000000..0ec501af8
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_query_default.inc
@@ -0,0 +1,1686 @@
+<?php
+
+/**
+ * @file
+ * Defines the default query object.
+ */
+
+/**
+ * Object used to create a SELECT query.
+ *
+ * @ingroup views_query_plugins
+ */
+class views_plugin_query_default extends views_plugin_query {
+
+ /**
+ * A list of tables in the order they should be added, keyed by alias.
+ */
+ var $table_queue = array();
+
+ /**
+ * Holds an array of tables and counts added so that we can create aliases
+ */
+ var $tables = array();
+
+ /**
+ * Holds an array of relationships, which are aliases of the primary
+ * table that represent different ways to join the same table in.
+ */
+ var $relationships = array();
+
+ /**
+ * An array of sections of the WHERE query. Each section is in itself
+ * an array of pieces and a flag as to whether or not it should be AND
+ * or OR.
+ */
+ var $where = array();
+ /**
+ * An array of sections of the HAVING query. Each section is in itself
+ * an array of pieces and a flag as to whether or not it should be AND
+ * or OR.
+ */
+ var $having = array();
+ /**
+ * The default operator to use when connecting the WHERE groups. May be
+ * AND or OR.
+ */
+ var $group_operator = 'AND';
+
+ /**
+ * A simple array of order by clauses.
+ */
+ var $orderby = array();
+
+ /**
+ * A simple array of group by clauses.
+ */
+ var $groupby = array();
+
+
+ /**
+ * An array of fields.
+ */
+ var $fields = array();
+
+
+ /**
+ * The table header to use for tablesort. This matters because tablesort
+ * needs to modify the query and needs the header.
+ */
+ var $header = array();
+
+ /**
+ * A flag as to whether or not to make the primary field distinct.
+ */
+ var $distinct = FALSE;
+
+ var $has_aggregate = FALSE;
+
+ /**
+ * Should this query be optimized for counts, for example no sorts.
+ */
+ var $get_count_optimized = NULL;
+
+ /**
+ * The current used pager plugin.
+ *
+ * @var views_plugin_pager
+ */
+ var $pager = NULL;
+
+ /**
+ * An array mapping table aliases and field names to field aliases.
+ */
+ var $field_aliases = array();
+
+ /**
+ * Query tags which will be passed over to the dbtng query object.
+ */
+ var $tags = array();
+
+ /**
+ * Is the view marked as not distinct.
+ *
+ * @var bool
+ */
+ var $no_distinct;
+
+ /**
+ * Defines the distinct type.
+ * - FALSE if it's distinct by base field.
+ * - TRUE if it just adds the sql distinct keyword.
+ *
+ * @var bool
+ */
+ public $pure_distinct = FALSE;
+
+ /**
+ * Constructor; Create the basic query object and fill with default values.
+ */
+ function init($base_table = 'node', $base_field = 'nid', $options) {
+ parent::init($base_table, $base_field, $options);
+ $this->base_table = $base_table; // Predefine these above, for clarity.
+ $this->base_field = $base_field;
+ $this->relationships[$base_table] = array(
+ 'link' => NULL,
+ 'table' => $base_table,
+ 'alias' => $base_table,
+ 'base' => $base_table
+ );
+
+ // init the table queue with our primary table.
+ $this->table_queue[$base_table] = array(
+ 'alias' => $base_table,
+ 'table' => $base_table,
+ 'relationship' => $base_table,
+ 'join' => NULL,
+ );
+
+ // init the tables with our primary table
+ $this->tables[$base_table][$base_table] = array(
+ 'count' => 1,
+ 'alias' => $base_table,
+ );
+
+/**
+ * -- we no longer want the base field to appear automatically.
+ if ($base_field) {
+ $this->fields[$base_field] = array(
+ 'table' => $base_table,
+ 'field' => $base_field,
+ 'alias' => $base_field,
+ );
+ }
+ */
+
+ $this->count_field = array(
+ 'table' => $base_table,
+ 'field' => $base_field,
+ 'alias' => $base_field,
+ 'count' => TRUE,
+ );
+ }
+
+ // ----------------------------------------------------------------
+ // Utility methods to set flags and data.
+
+ /**
+ * Set the view to be distinct.
+ *
+ * There are either distinct per base field or distinct in the pure sql way,
+ * based on $pure_distinct.
+ *
+ * @param bool $value
+ * Should the view by distincted.
+ * @param bool $pure_distinct
+ * Should only the sql keyword be added.
+ */
+ function set_distinct($value = TRUE, $pure_distinct = FALSE) {
+ if (!(isset($this->no_distinct) && $value)) {
+ $this->distinct = $value;
+ $this->pure_distinct = $pure_distinct;
+ }
+ }
+
+ /**
+ * Set what field the query will count() on for paging.
+ */
+ function set_count_field($table, $field, $alias = NULL) {
+ if (empty($alias)) {
+ $alias = $table . '_' . $field;
+ }
+ $this->count_field = array(
+ 'table' => $table,
+ 'field' => $field,
+ 'alias' => $alias,
+ 'count' => TRUE,
+ );
+ }
+
+ /**
+ * Set the table header; used for click-sorting because it's needed
+ * info to modify the ORDER BY clause.
+ */
+ function set_header($header) {
+ $this->header = $header;
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['disable_sql_rewrite'] = array(
+ 'default' => FALSE,
+ 'translatable' => FALSE,
+ 'bool' => TRUE,
+ );
+ $options['distinct'] = array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ );
+ $options['pure_distinct'] = array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ );
+ $options['slave'] = array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ );
+ $options['query_comment'] = array(
+ 'default' => '',
+ );
+ $options['query_tags'] = array(
+ 'default' => array(),
+ );
+
+ return $options;
+ }
+
+ /**
+ * Add settings for the ui.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['disable_sql_rewrite'] = array(
+ '#title' => t('Disable SQL rewriting'),
+ '#description' => t('Disabling SQL rewriting will disable node_access checks as well as other modules that implement hook_query_alter().'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['disable_sql_rewrite']),
+ '#suffix' => '<div class="messages warning sql-rewrite-warning js-hide">' . t('WARNING: Disabling SQL rewriting means that node access security is disabled. This may allow users to see data they should not be able to see if your view is misconfigured. Please use this option only if you understand and accept this security risk.') . '</div>',
+ );
+ $form['distinct'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Distinct'),
+ '#description' => t('This will make the view display only distinct items. If there are multiple identical items, each will be displayed only once. You can use this to try and remove duplicates from a view, though it does not always work. Note that this can slow queries down, so use it with caution.'),
+ '#default_value' => !empty($this->options['distinct']),
+ );
+ $form['pure_distinct'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Pure Distinct'),
+ '#description' => t('This will prevent views from adding the base column to the distinct field. If this is not selected and the base column is a primary key, then a non-pure distinct will not function properly because the primary key is always unique.'),
+ '#default_value' => !empty($this->options['pure_distinct']),
+ '#dependency' => array('edit-query-options-distinct' => '1'),
+ );
+ $form['slave'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use Slave Server'),
+ '#description' => t('This will make the query attempt to connect to a slave server if available. If no slave server is defined or available, it will fall back to the default server.'),
+ '#default_value' => !empty($this->options['slave']),
+ );
+ $form['query_comment'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Query Comment'),
+ '#description' => t('If set, this comment will be embedded in the query and passed to the SQL server. This can be helpful for logging or debugging.'),
+ '#default_value' => $this->options['query_comment'],
+ );
+ $form['query_tags'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Query Tags'),
+ '#description' => t('If set, these tags will be appended to the query and can be used to identify the query in a module. This can be helpful for altering queries.'),
+ '#default_value' => implode(', ', $this->options['query_tags']),
+ '#element_validate' => array('views_element_validate_tags'),
+ );
+ }
+
+ /**
+ * Special submit handling.
+ */
+ function options_submit(&$form, &$form_state) {
+ $element = array('#parents' => array('query', 'options', 'query_tags'));
+ $value = explode(',', drupal_array_get_nested_value($form_state['values'], $element['#parents']));
+ $value = array_filter(array_map('trim', $value));
+ form_set_value($element, $value, $form_state);
+ }
+
+ // ----------------------------------------------------------------
+ // Table/join adding
+
+ /**
+ * A relationship is an alternative endpoint to a series of table
+ * joins. Relationships must be aliases of the primary table and
+ * they must join either to the primary table or to a pre-existing
+ * relationship.
+ *
+ * An example of a relationship would be a nodereference table.
+ * If you have a nodereference named 'book_parent' which links to a
+ * parent node, you could set up a relationship 'node_book_parent'
+ * to 'node'. Then, anything that links to 'node' can link to
+ * 'node_book_parent' instead, thus allowing all properties of
+ * both nodes to be available in the query.
+ *
+ * @param $alias
+ * What this relationship will be called, and is also the alias
+ * for the table.
+ * @param views_join $join
+ * A views_join object (or derived object) to join the alias in.
+ * @param $base
+ * The name of the 'base' table this relationship represents; this
+ * tells the join search which path to attempt to use when finding
+ * the path to this relationship.
+ * @param $link_point
+ * If this relationship links to something other than the primary
+ * table, specify that table here. For example, a 'track' node
+ * might have a relationship to an 'album' node, which might
+ * have a relationship to an 'artist' node.
+ */
+ function add_relationship($alias, $join, $base, $link_point = NULL) {
+ if (empty($link_point)) {
+ $link_point = $this->base_table;
+ }
+ elseif (!array_key_exists($link_point, $this->relationships)) {
+ return FALSE;
+ }
+
+ // Make sure $alias isn't already used; if it, start adding stuff.
+ $alias_base = $alias;
+ $count = 1;
+ while (!empty($this->relationships[$alias])) {
+ $alias = $alias_base . '_' . $count++;
+ }
+
+ // Make sure this join is adjusted for our relationship.
+ if ($link_point && isset($this->relationships[$link_point])) {
+ $join = $this->adjust_join($join, $link_point);
+ }
+
+ // Add the table directly to the queue to avoid accidentally marking
+ // it.
+ $this->table_queue[$alias] = array(
+ 'table' => $join->table,
+ 'num' => 1,
+ 'alias' => $alias,
+ 'join' => $join,
+ 'relationship' => $link_point,
+ );
+
+ $this->relationships[$alias] = array(
+ 'link' => $link_point,
+ 'table' => $join->table,
+ 'base' => $base,
+ );
+
+ $this->tables[$this->base_table][$alias] = array(
+ 'count' => 1,
+ 'alias' => $alias,
+ );
+
+ return $alias;
+ }
+
+ /**
+ * Add a table to the query, ensuring the path exists.
+ *
+ * This function will test to ensure that the path back to the primary
+ * table is valid and exists; if you do not wish for this testing to
+ * occur, use $query->queue_table() instead.
+ *
+ * @param $table
+ * The name of the table to add. It needs to exist in the global table
+ * array.
+ * @param $relationship
+ * An alias of a table; if this is set, the path back to this table will
+ * be tested prior to adding the table, making sure that all intermediary
+ * tables exist and are properly aliased. If set to NULL the path to
+ * the primary table will be ensured. If the path cannot be made, the
+ * table will NOT be added.
+ * @param views_join $join
+ * In some join configurations this table may actually join back through
+ * a different method; this is most likely to be used when tracing
+ * a hierarchy path. (node->parent->parent2->parent3). This parameter
+ * will specify how this table joins if it is not the default.
+ * @param $alias
+ * A specific alias to use, rather than the default alias.
+ *
+ * @return $alias
+ * The alias of the table; this alias can be used to access information
+ * about the table and should always be used to refer to the table when
+ * adding parts to the query. Or FALSE if the table was not able to be
+ * added.
+ */
+ function add_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
+ if (!$this->ensure_path($table, $relationship, $join)) {
+ return FALSE;
+ }
+
+ if ($join && $relationship) {
+ $join = $this->adjust_join($join, $relationship);
+ }
+
+ return $this->queue_table($table, $relationship, $join, $alias);
+ }
+
+ /**
+ * Add a table to the query without ensuring the path.
+ *
+ * This is a pretty internal function to Views and add_table() or
+ * ensure_table() should be used instead of this one, unless you are
+ * absolutely sure this is what you want.
+ *
+ * @param $table
+ * The name of the table to add. It needs to exist in the global table
+ * array.
+ * @param $relationship
+ * The primary table alias this table is related to. If not set, the
+ * primary table will be used.
+ * @param views_join $join
+ * In some join configurations this table may actually join back through
+ * a different method; this is most likely to be used when tracing
+ * a hierarchy path. (node->parent->parent2->parent3). This parameter
+ * will specify how this table joins if it is not the default.
+ * @param $alias
+ * A specific alias to use, rather than the default alias.
+ *
+ * @return $alias
+ * The alias of the table; this alias can be used to access information
+ * about the table and should always be used to refer to the table when
+ * adding parts to the query. Or FALSE if the table was not able to be
+ * added.
+ */
+ function queue_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
+ // If the alias is set, make sure it doesn't already exist.
+ if (isset($this->table_queue[$alias])) {
+ return $alias;
+ }
+
+ if (empty($relationship)) {
+ $relationship = $this->base_table;
+ }
+
+ if (!array_key_exists($relationship, $this->relationships)) {
+ return FALSE;
+ }
+
+ if (!$alias && $join && $relationship && !empty($join->adjusted) && $table != $join->table) {
+ if ($relationship == $this->base_table) {
+ $alias = $table;
+ }
+ else {
+ $alias = $relationship . '_' . $table;
+ }
+ }
+
+ // Check this again to make sure we don't blow up existing aliases for already
+ // adjusted joins.
+ if (isset($this->table_queue[$alias])) {
+ return $alias;
+ }
+
+ $alias = $this->mark_table($table, $relationship, $alias);
+
+ // If no alias is specified, give it the default.
+ if (!isset($alias)) {
+ $alias = $this->tables[$relationship][$table]['alias'] . $this->tables[$relationship][$table]['count'];
+ }
+
+ // If this is a relationship based table, add a marker with
+ // the relationship as a primary table for the alias.
+ if ($table != $alias) {
+ $this->mark_table($alias, $this->base_table, $alias);
+ }
+
+ // If no join is specified, pull it from the table data.
+ if (!isset($join)) {
+ $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
+ if (empty($join)) {
+ return FALSE;
+ }
+
+ $join = $this->adjust_join($join, $relationship);
+ }
+
+ $this->table_queue[$alias] = array(
+ 'table' => $table,
+ 'num' => $this->tables[$relationship][$table]['count'],
+ 'alias' => $alias,
+ 'join' => $join,
+ 'relationship' => $relationship,
+ );
+
+ return $alias;
+ }
+
+ function mark_table($table, $relationship, $alias) {
+ // Mark that this table has been added.
+ if (empty($this->tables[$relationship][$table])) {
+ if (!isset($alias)) {
+ $alias = '';
+ if ($relationship != $this->base_table) {
+ // double underscore will help prevent accidental name
+ // space collisions.
+ $alias = $relationship . '__';
+ }
+ $alias .= $table;
+ }
+ $this->tables[$relationship][$table] = array(
+ 'count' => 1,
+ 'alias' => $alias,
+ );
+ }
+ else {
+ $this->tables[$relationship][$table]['count']++;
+ }
+
+ return $alias;
+ }
+
+ /**
+ * Ensure a table exists in the queue; if it already exists it won't
+ * do anything, but if it doesn't it will add the table queue. It will ensure
+ * a path leads back to the relationship table.
+ *
+ * @param $table
+ * The unaliased name of the table to ensure.
+ * @param $relationship
+ * The relationship to ensure the table links to. Each relationship will
+ * get a unique instance of the table being added. If not specified,
+ * will be the primary table.
+ * @param views_join $join
+ * A views_join object (or derived object) to join the alias in.
+ *
+ * @return
+ * The alias used to refer to this specific table, or NULL if the table
+ * cannot be ensured.
+ */
+ function ensure_table($table, $relationship = NULL, $join = NULL) {
+ // ensure a relationship
+ if (empty($relationship)) {
+ $relationship = $this->base_table;
+ }
+
+ // If the relationship is the primary table, this actually be a relationship
+ // link back from an alias. We store all aliases along with the primary table
+ // to detect this state, because eventually it'll hit a table we already
+ // have and that's when we want to stop.
+ if ($relationship == $this->base_table && !empty($this->tables[$relationship][$table])) {
+ return $this->tables[$relationship][$table]['alias'];
+ }
+
+ if (!array_key_exists($relationship, $this->relationships)) {
+ return FALSE;
+ }
+
+ if ($table == $this->relationships[$relationship]['base']) {
+ return $relationship;
+ }
+
+ // If we do not have join info, fetch it.
+ if (!isset($join)) {
+ $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
+ }
+
+ // If it can't be fetched, this won't work.
+ if (empty($join)) {
+ return;
+ }
+
+ // Adjust this join for the relationship, which will ensure that the 'base'
+ // table it links to is correct. Tables adjoined to a relationship
+ // join to a link point, not the base table.
+ $join = $this->adjust_join($join, $relationship);
+
+ if ($this->ensure_path($table, $relationship, $join)) {
+ // Attempt to eliminate redundant joins. If this table's
+ // relationship and join exactly matches an existing table's
+ // relationship and join, we do not have to join to it again;
+ // just return the existing table's alias. See
+ // http://groups.drupal.org/node/11288 for details.
+ //
+ // This can be done safely here but not lower down in
+ // queue_table(), because queue_table() is also used by
+ // add_table() which requires the ability to intentionally add
+ // the same table with the same join multiple times. For
+ // example, a view that filters on 3 taxonomy terms using AND
+ // needs to join taxonomy_term_data 3 times with the same join.
+
+ // scan through the table queue to see if a matching join and
+ // relationship exists. If so, use it instead of this join.
+
+ // TODO: Scanning through $this->table_queue results in an
+ // O(N^2) algorithm, and this code runs every time the view is
+ // instantiated (Views 2 does not currently cache queries).
+ // There are a couple possible "improvements" but we should do
+ // some performance testing before picking one.
+ foreach ($this->table_queue as $queued_table) {
+ // In PHP 4 and 5, the == operation returns TRUE for two objects
+ // if they are instances of the same class and have the same
+ // attributes and values.
+ if ($queued_table['relationship'] == $relationship && $queued_table['join'] == $join) {
+ return $queued_table['alias'];
+ }
+ }
+
+ return $this->queue_table($table, $relationship, $join);
+ }
+ }
+
+ /**
+ * Make sure that the specified table can be properly linked to the primary
+ * table in the JOINs. This function uses recursion. If the tables
+ * needed to complete the path back to the primary table are not in the
+ * query they will be added, but additional copies will NOT be added
+ * if the table is already there.
+ */
+ function ensure_path($table, $relationship = NULL, $join = NULL, $traced = array(), $add = array()) {
+ if (!isset($relationship)) {
+ $relationship = $this->base_table;
+ }
+
+ if (!array_key_exists($relationship, $this->relationships)) {
+ return FALSE;
+ }
+
+ // If we do not have join info, fetch it.
+ if (!isset($join)) {
+ $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
+ }
+
+ // If it can't be fetched, this won't work.
+ if (empty($join)) {
+ return FALSE;
+ }
+
+ // Does a table along this path exist?
+ if (isset($this->tables[$relationship][$table]) ||
+ ($join && $join->left_table == $relationship) ||
+ ($join && $join->left_table == $this->relationships[$relationship]['table'])) {
+
+ // Make sure that we're linking to the correct table for our relationship.
+ foreach (array_reverse($add) as $table => $path_join) {
+ $this->queue_table($table, $relationship, $this->adjust_join($path_join, $relationship));
+ }
+ return TRUE;
+ }
+
+ // Have we been this way?
+ if (isset($traced[$join->left_table])) {
+ // we looped. Broked.
+ return FALSE;
+ }
+
+ // Do we have to add this table?
+ $left_join = $this->get_join_data($join->left_table, $this->relationships[$relationship]['base']);
+ if (!isset($this->tables[$relationship][$join->left_table])) {
+ $add[$join->left_table] = $left_join;
+ }
+
+ // Keep looking.
+ $traced[$join->left_table] = TRUE;
+ return $this->ensure_path($join->left_table, $relationship, $left_join, $traced, $add);
+ }
+
+ /**
+ * Fix a join to adhere to the proper relationship; the left table can vary
+ * based upon what relationship items are joined in on.
+ */
+ function adjust_join($join, $relationship) {
+ if (!empty($join->adjusted)) {
+ return $join;
+ }
+
+ if (empty($relationship) || empty($this->relationships[$relationship])) {
+ return $join;
+ }
+
+ // Adjusts the left table for our relationship.
+ if ($relationship != $this->base_table) {
+ // If we're linking to the primary table, the relationship to use will
+ // be the prior relationship. Unless it's a direct link.
+
+ // Safety! Don't modify an original here.
+ $join = clone $join;
+
+ // Do we need to try to ensure a path?
+ if ($join->left_table != $this->relationships[$relationship]['table'] &&
+ $join->left_table != $this->relationships[$relationship]['base'] &&
+ !isset($this->tables[$relationship][$join->left_table]['alias'])) {
+ $this->ensure_table($join->left_table, $relationship);
+ }
+
+ // First, if this is our link point/anchor table, just use the relationship
+ if ($join->left_table == $this->relationships[$relationship]['table']) {
+ $join->left_table = $relationship;
+ }
+ // then, try the base alias.
+ elseif (isset($this->tables[$relationship][$join->left_table]['alias'])) {
+ $join->left_table = $this->tables[$relationship][$join->left_table]['alias'];
+ }
+ // But if we're already looking at an alias, use that instead.
+ elseif (isset($this->table_queue[$relationship]['alias'])) {
+ $join->left_table = $this->table_queue[$relationship]['alias'];
+ }
+ }
+
+ $join->adjusted = TRUE;
+ return $join;
+ }
+
+ /**
+ * Retrieve join data from the larger join data cache.
+ *
+ * @param $table
+ * The table to get the join information for.
+ * @param $base_table
+ * The path we're following to get this join.
+ *
+ * @return views_join
+ * A views_join object or child object, if one exists.
+ */
+ function get_join_data($table, $base_table) {
+ // Check to see if we're linking to a known alias. If so, get the real
+ // table's data instead.
+ if (!empty($this->table_queue[$table])) {
+ $table = $this->table_queue[$table]['table'];
+ }
+ return views_get_table_join($table, $base_table);
+ }
+
+ /**
+ * Get the information associated with a table.
+ *
+ * If you need the alias of a table with a particular relationship, use
+ * ensure_table().
+ */
+ function get_table_info($table) {
+ if (!empty($this->table_queue[$table])) {
+ return $this->table_queue[$table];
+ }
+
+ // In rare cases we might *only* have aliased versions of the table.
+ if (!empty($this->tables[$this->base_table][$table])) {
+ $alias = $this->tables[$this->base_table][$table]['alias'];
+ if (!empty($this->table_queue[$alias])) {
+ return $this->table_queue[$alias];
+ }
+ }
+ }
+
+ /**
+ * Add a field to the query table, possibly with an alias. This will
+ * automatically call ensure_table to make sure the required table
+ * exists, *unless* $table is unset.
+ *
+ * @param $table
+ * The table this field is attached to. If NULL, it is assumed this will
+ * be a formula; otherwise, ensure_table is used to make sure the
+ * table exists.
+ * @param $field
+ * The name of the field to add. This may be a real field or a formula.
+ * @param $alias
+ * The alias to create. If not specified, the alias will be $table_$field
+ * unless $table is NULL. When adding formulae, it is recommended that an
+ * alias be used.
+ * @param $params
+ * An array of parameters additional to the field that will control items
+ * such as aggregation functions and DISTINCT.
+ *
+ * @return $name
+ * The name that this field can be referred to as. Usually this is the alias.
+ */
+ function add_field($table, $field, $alias = '', $params = array()) {
+ // We check for this specifically because it gets a special alias.
+ if ($table == $this->base_table && $field == $this->base_field && empty($alias)) {
+ $alias = $this->base_field;
+ }
+
+ if ($table && empty($this->table_queue[$table])) {
+ $this->ensure_table($table);
+ }
+
+ if (!$alias && $table) {
+ $alias = $table . '_' . $field;
+ }
+
+ // Make sure an alias is assigned
+ $alias = $alias ? $alias : $field;
+
+ // PostgreSQL truncates aliases to 63 characters: http://drupal.org/node/571548
+
+ // We limit the length of the original alias up to 60 characters
+ // to get a unique alias later if its have duplicates
+ $alias = strtolower(substr($alias, 0, 60));
+
+ // Create a field info array.
+ $field_info = array(
+ 'field' => $field,
+ 'table' => $table,
+ 'alias' => $alias,
+ ) + $params;
+
+ // Test to see if the field is actually the same or not. Due to
+ // differing parameters changing the aggregation function, we need
+ // to do some automatic alias collision detection:
+ $base = $alias;
+ $counter = 0;
+ while (!empty($this->fields[$alias]) && $this->fields[$alias] != $field_info) {
+ $field_info['alias'] = $alias = $base . '_' . ++$counter;
+ }
+
+ if (empty($this->fields[$alias])) {
+ $this->fields[$alias] = $field_info;
+ }
+
+ // Keep track of all aliases used.
+ $this->field_aliases[$table][$field] = $alias;
+
+ return $alias;
+ }
+
+ /**
+ * Remove all fields that may've been added; primarily used for summary
+ * mode where we're changing the query because we didn't get data we needed.
+ */
+ function clear_fields() {
+ $this->fields = array();
+ }
+
+ /**
+ * Add a simple WHERE clause to the query. The caller is responsible for
+ * ensuring that all fields are fully qualified (TABLE.FIELD) and that
+ * the table already exists in the query.
+ *
+ * @param $group
+ * The WHERE group to add these to; groups are used to create AND/OR
+ * sections. Groups cannot be nested. Use 0 as the default group.
+ * If the group does not yet exist it will be created as an AND group.
+ * @param $field
+ * The name of the field to check.
+ * @param $value
+ * The value to test the field against. In most cases, this is a scalar. For more
+ * complex options, it is an array. The meaning of each element in the array is
+ * dependent on the $operator.
+ * @param $operator
+ * The comparison operator, such as =, <, or >=. It also accepts more complex
+ * options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is an array
+ * = otherwise. If $field is a string you have to use 'formula' here.
+ *
+ * The $field, $value and $operator arguments can also be passed in with a
+ * single DatabaseCondition object, like this:
+ * @code
+ * $this->query->add_where(
+ * $this->options['group'],
+ * db_or()
+ * ->condition($field, $value, 'NOT IN')
+ * ->condition($field, $value, 'IS NULL')
+ * );
+ * @endcode
+ *
+ * @see QueryConditionInterface::condition()
+ * @see DatabaseCondition
+ */
+ function add_where($group, $field, $value = NULL, $operator = NULL) {
+ // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
+ // the default group.
+ if (empty($group)) {
+ $group = 0;
+ }
+
+ // Check for a group.
+ if (!isset($this->where[$group])) {
+ $this->set_where_group('AND', $group);
+ }
+
+ $this->where[$group]['conditions'][] = array(
+ 'field' => $field,
+ 'value' => $value,
+ 'operator' => $operator,
+ );
+ }
+
+ /**
+ * Add a complex WHERE clause to the query.
+ *
+ * The caller is responsible for ensuring that all fields are fully qualified
+ * (TABLE.FIELD) and that the table already exists in the query.
+ * Internally the dbtng method "where" is used.
+ *
+ * @param $group
+ * The WHERE group to add these to; groups are used to create AND/OR
+ * sections. Groups cannot be nested. Use 0 as the default group.
+ * If the group does not yet exist it will be created as an AND group.
+ * @param $snippet
+ * The snippet to check. This can be either a column or
+ * a complex expression like "UPPER(table.field) = 'value'"
+ * @param $args
+ * An associative array of arguments.
+ *
+ * @see QueryConditionInterface::where()
+ */
+ function add_where_expression($group, $snippet, $args = array()) {
+ // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
+ // the default group.
+ if (empty($group)) {
+ $group = 0;
+ }
+
+ // Check for a group.
+ if (!isset($this->where[$group])) {
+ $this->set_where_group('AND', $group);
+ }
+
+ $this->where[$group]['conditions'][] = array(
+ 'field' => $snippet,
+ 'value' => $args,
+ 'operator' => 'formula',
+ );
+ }
+
+ /**
+ * Add a simple HAVING clause to the query.
+ *
+ * The caller is responsible for ensuring that all fields are fully qualified
+ * (TABLE.FIELD) and that the table and an appropriate GROUP BY already exist in the query.
+ * Internally the dbtng method "havingCondition" is used.
+ *
+ * @param $group
+ * The HAVING group to add these to; groups are used to create AND/OR
+ * sections. Groups cannot be nested. Use 0 as the default group.
+ * If the group does not yet exist it will be created as an AND group.
+ * @param $field
+ * The name of the field to check.
+ * @param $value
+ * The value to test the field against. In most cases, this is a scalar. For more
+ * complex options, it is an array. The meaning of each element in the array is
+ * dependent on the $operator.
+ * @param $operator
+ * The comparison operator, such as =, <, or >=. It also accepts more complex
+ * options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is an array
+ * = otherwise. If $field is a string you have to use 'formula' here.
+ *
+ * @see SelectQueryInterface::havingCondition()
+ */
+ function add_having($group, $field, $value = NULL, $operator = NULL) {
+ // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
+ // the default group.
+ if (empty($group)) {
+ $group = 0;
+ }
+
+ // Check for a group.
+ if (!isset($this->having[$group])) {
+ $this->set_where_group('AND', $group, 'having');
+ }
+
+ // Add the clause and the args.
+ $this->having[$group]['conditions'][] = array(
+ 'field' => $field,
+ 'value' => $value,
+ 'operator' => $operator,
+ );
+ }
+
+ /**
+ * Add a complex HAVING clause to the query.
+ * The caller is responsible for ensuring that all fields are fully qualified
+ * (TABLE.FIELD) and that the table and an appropriate GROUP BY already exist in the query.
+ * Internally the dbtng method "having" is used.
+ *
+ * @param $group
+ * The HAVING group to add these to; groups are used to create AND/OR
+ * sections. Groups cannot be nested. Use 0 as the default group.
+ * If the group does not yet exist it will be created as an AND group.
+ * @param $snippet
+ * The snippet to check. This can be either a column or
+ * a complex expression like "COUNT(table.field) > 3"
+ * @param $args
+ * An associative array of arguments.
+ *
+ * @see QueryConditionInterface::having()
+ */
+ function add_having_expression($group, $snippet, $args = array()) {
+ // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
+ // the default group.
+ if (empty($group)) {
+ $group = 0;
+ }
+
+ // Check for a group.
+ if (!isset($this->having[$group])) {
+ $this->set_where_group('AND', $group, 'having');
+ }
+
+ // Add the clause and the args.
+ $this->having[$group]['conditions'][] = array(
+ 'field' => $snippet,
+ 'value' => $args,
+ 'operator' => 'formula',
+ );
+ }
+
+ /**
+ * Add an ORDER BY clause to the query.
+ *
+ * @param $table
+ * The table this field is part of. If a formula, enter NULL.
+ * If you want to orderby random use "rand" as table and nothing else.
+ * @param $field
+ * The field or formula to sort on. If already a field, enter NULL
+ * and put in the alias.
+ * @param $order
+ * Either ASC or DESC.
+ * @param $alias
+ * The alias to add the field as. In SQL, all fields in the order by
+ * must also be in the SELECT portion. If an $alias isn't specified
+ * one will be generated for from the $field; however, if the
+ * $field is a formula, this alias will likely fail.
+ * @param $params
+ * Any params that should be passed through to the add_field.
+ */
+ function add_orderby($table, $field = NULL, $order = 'ASC', $alias = '', $params = array()) {
+ // Only ensure the table if it's not the special random key.
+ // @todo: Maybe it would make sense to just add a add_orderby_rand or something similar.
+ if ($table && $table != 'rand') {
+ $this->ensure_table($table);
+ }
+
+ // Only fill out this aliasing if there is a table;
+ // otherwise we assume it is a formula.
+ if (!$alias && $table) {
+ $as = $table . '_' . $field;
+ }
+ else {
+ $as = $alias;
+ }
+
+ if ($field) {
+ $as = $this->add_field($table, $field, $as, $params);
+ }
+
+ $this->orderby[] = array(
+ 'field' => $as,
+ 'direction' => strtoupper($order)
+ );
+
+ /**
+ * -- removing, this should be taken care of by field adding now.
+ * -- leaving commented because I am unsure.
+ // If grouping, all items in the order by must also be in the
+ // group by clause. Check $table to ensure that this is not a
+ // formula.
+ if ($this->groupby && $table) {
+ $this->add_groupby($as);
+ }
+ */
+ }
+
+ /**
+ * Add a simple GROUP BY clause to the query. The caller is responsible
+ * for ensuring that the fields are fully qualified and the table is properly
+ * added.
+ */
+ function add_groupby($clause) {
+ // Only add it if it's not already in there.
+ if (!in_array($clause, $this->groupby)) {
+ $this->groupby[] = $clause;
+ }
+ }
+
+ /**
+ * Returns the alias for the given field added to $table.
+ *
+ * @see views_plugin_query_default::add_field()
+ */
+ function get_field_alias($table_alias, $field) {
+ return isset($this->field_aliases[$table_alias][$field]) ? $this->field_aliases[$table_alias][$field] : FALSE;
+ }
+
+ /**
+ * Adds a query tag to the sql object.
+ *
+ * @see SelectQuery::addTag()
+ */
+ function add_tag($tag) {
+ $this->tags[] = $tag;
+ }
+
+ /**
+ * Generates a unique placeholder used in the db query.
+ */
+ function placeholder($base = 'views') {
+ static $placeholders = array();
+ if (!isset($placeholders[$base])) {
+ $placeholders[$base] = 0;
+ return ':' . $base;
+ }
+ else {
+ return ':' . $base . ++$placeholders[$base];
+ }
+ }
+
+ /**
+ * Construct the "WHERE" or "HAVING" part of the query.
+ *
+ * As views has to wrap the conditions from arguments with AND, a special
+ * group is wrapped around all conditions. This special group has the ID 0.
+ * There is other code in filters which makes sure that the group IDs are
+ * higher than zero.
+ *
+ * @param $where
+ * 'where' or 'having'.
+ */
+ function build_condition($where = 'where') {
+ $has_condition = FALSE;
+ $has_arguments = FALSE;
+ $has_filter = FALSE;
+
+ $main_group = db_and();
+ $filter_group = $this->group_operator == 'OR' ? db_or() : db_and();
+
+ foreach ($this->$where as $group => $info) {
+
+ if (!empty($info['conditions'])) {
+ $sub_group = $info['type'] == 'OR' ? db_or() : db_and();
+ foreach ($info['conditions'] as $key => $clause) {
+ // DBTNG doesn't support to add the same subquery twice to the main
+ // query and the count query, so clone the subquery to have two instances
+ // of the same object. - http://drupal.org/node/1112854
+ if (is_object($clause['value']) && $clause['value'] instanceof SelectQuery) {
+ $clause['value'] = clone $clause['value'];
+ }
+ if ($clause['operator'] == 'formula') {
+ $has_condition = TRUE;
+ $sub_group->where($clause['field'], $clause['value']);
+ }
+ else {
+ $has_condition = TRUE;
+ $sub_group->condition($clause['field'], $clause['value'], $clause['operator']);
+ }
+ }
+
+ // Add the item to the filter group.
+ if ($group != 0) {
+ $has_filter = TRUE;
+ $filter_group->condition($sub_group);
+ }
+ else {
+ $has_arguments = TRUE;
+ $main_group->condition($sub_group);
+ }
+ }
+ }
+
+ if ($has_filter) {
+ $main_group->condition($filter_group);
+ }
+
+ if (!$has_arguments && $has_condition) {
+ return $filter_group;
+ }
+ if ($has_arguments && $has_condition) {
+ return $main_group;
+ }
+ }
+
+ /**
+ * Build fields array.
+ */
+ function compile_fields($fields_array, $query) {
+ $non_aggregates = array();
+ foreach ($fields_array as $field) {
+ $string = '';
+ if (!empty($field['table'])) {
+ $string .= $field['table'] . '.';
+ }
+ $string .= $field['field'];
+ $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
+
+ if (!empty($field['distinct'])) {
+ throw new Exception("Column-level distinct is not supported anymore.");
+ }
+
+ if (!empty($field['count'])) {
+ // Retained for compatibility
+ $field['function'] = 'count';
+ // It seems there's no way to abstract the table+column reference
+ // without adding a field, aliasing, and then using the alias.
+ }
+
+ if (!empty($field['function'])) {
+ $info = $this->get_aggregation_info();
+ if (!empty($info[$field['function']]['method']) && function_exists($info[$field['function']]['method'])) {
+ $string = $info[$field['function']]['method']($field['function'], $string);
+ $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : array();
+ $query->addExpression($string, $fieldname, $placeholders);
+ }
+
+ $this->has_aggregate = TRUE;
+ }
+ // This is a formula, using no tables.
+ elseif (empty($field['table'])) {
+ $non_aggregates[] = $fieldname;
+ $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : array();
+ $query->addExpression($string, $fieldname, $placeholders);
+ }
+
+ elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
+ // d7cx: This code was there, apparently needed for PostgreSQL
+ // $string = db_driver() == 'pgsql' ? "FIRST($string)" : $string;
+ $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
+ }
+ elseif (empty($field['aggregate'])) {
+ $non_aggregates[] = $fieldname;
+ $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
+ }
+
+ // @TODO Remove this old code.
+ if (!empty($field['distinct']) && empty($field['function'])) {
+ $distinct[] = $string;
+ }
+ else {
+ $fields[] = $string;
+ }
+
+ if ($this->get_count_optimized) {
+ // We only want the first field in this case.
+ break;
+ }
+ }
+ return array(
+ $non_aggregates,
+ );
+ }
+
+ /**
+ * Generate a query and a countquery from all of the information supplied
+ * to the object.
+ *
+ * @param $get_count
+ * Provide a countquery if this is true, otherwise provide a normal query.
+ */
+ function query($get_count = FALSE) {
+ // Check query distinct value.
+ if (empty($this->no_distinct) && $this->distinct && !empty($this->fields)) {
+ if ($this->pure_distinct === FALSE){
+ $base_field_alias = $this->add_field($this->base_table, $this->base_field);
+ $this->add_groupby($base_field_alias);
+ }
+ $distinct = TRUE;
+ }
+
+ /**
+ * An optimized count query includes just the base field instead of all the fields.
+ * Determine of this query qualifies by checking for a groupby or distinct.
+ */
+ $fields_array = $this->fields;
+ if ($get_count && !$this->groupby) {
+ foreach ($fields_array as $field) {
+ if (!empty($field['distinct']) || !empty($field['function'])) {
+ $this->get_count_optimized = FALSE;
+ break;
+ }
+ }
+ }
+ else {
+ $this->get_count_optimized = FALSE;
+ }
+ if (!isset($this->get_count_optimized)) {
+ $this->get_count_optimized = TRUE;
+ }
+
+ $options = array();
+ $target = 'default';
+ $key = 'default';
+ // Detect an external database and set the
+ if (isset($this->view->base_database)) {
+ $key = $this->view->base_database;
+ }
+
+ // Set the slave target if the slave option is set
+ if (!empty($this->options['slave'])) {
+ $target = 'slave';
+ }
+
+ // Go ahead and build the query.
+ // db_select doesn't support to specify the key, so use getConnection directly.
+ $query = Database::getConnection($target, $key)
+ ->select($this->base_table, $this->base_table, $options)
+ ->addTag('views')
+ ->addTag('views_' . $this->view->name);
+
+ // Add the tags added to the view itself.
+ foreach ($this->tags as $tag) {
+ $query->addTag($tag);
+ }
+
+ if (!empty($distinct)) {
+ $query->distinct();
+ }
+
+ $joins = $where = $having = $orderby = $groupby = '';
+ $fields = $distinct = array();
+
+ // Add all the tables to the query via joins. We assume all LEFT joins.
+ foreach ($this->table_queue as $table) {
+ if (is_object($table['join'])) {
+ $table['join']->build_join($query, $table, $this);
+ }
+ }
+
+ $this->has_aggregate = FALSE;
+ $non_aggregates = array();
+
+ list($non_aggregates) = $this->compile_fields($fields_array, $query);
+
+ if (count($this->having)) {
+ $this->has_aggregate = TRUE;
+ }
+ elseif (!$this->has_aggregate) {
+ // Allow 'GROUP BY' even no aggregation function has been set.
+ $this->has_aggregate = $this->view->display_handler->get_option('group_by');
+ }
+ if ($this->has_aggregate && (!empty($this->groupby) || !empty($non_aggregates))) {
+ $groupby = array_unique(array_merge($this->groupby, $non_aggregates));
+ foreach ($groupby as $field) {
+ $query->groupBy($field);
+ }
+ if (!empty($this->having) && $condition = $this->build_condition('having')) {
+ $query->havingCondition($condition);
+ }
+ }
+
+ if (!$this->get_count_optimized) {
+ // we only add the orderby if we're not counting.
+ if ($this->orderby) {
+ foreach ($this->orderby as $order) {
+ if ($order['field'] == 'rand_') {
+ $query->orderRandom();
+ }
+ else {
+ $query->orderBy($order['field'], $order['direction']);
+ }
+ }
+ }
+ }
+
+ if (!empty($this->where) && $condition = $this->build_condition('where')) {
+ $query->condition($condition);
+ }
+
+ // Add a query comment.
+ if (!empty($this->options['query_comment'])) {
+ $query->comment($this->options['query_comment']);
+ }
+
+ // Add the query tags.
+ if (!empty($this->options['query_tags'])) {
+ foreach ($this->options['query_tags'] as $tag) {
+ $query->addTag($tag);
+ }
+ }
+
+ // Add all query substitutions as metadata.
+ $query->addMetaData('views_substitutions', module_invoke_all('views_query_substitutions', $this));
+
+ if (!$get_count) {
+ if (!empty($this->limit) || !empty($this->offset)) {
+ // We can't have an offset without a limit, so provide a very large limit
+ // instead.
+ $limit = intval(!empty($this->limit) ? $this->limit : 999999);
+ $offset = intval(!empty($this->offset) ? $this->offset : 0);
+ $query->range($offset, $limit);
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * Get the arguments attached to the WHERE and HAVING clauses of this query.
+ */
+ function get_where_args() {
+ $args = array();
+ foreach ($this->where as $group => $where) {
+ $args = array_merge($args, $where['args']);
+ }
+ foreach ($this->having as $group => $having) {
+ $args = array_merge($args, $having['args']);
+ }
+ return $args;
+ }
+
+ /**
+ * Let modules modify the query just prior to finalizing it.
+ */
+ function alter(&$view) {
+ foreach (module_implements('views_query_alter') as $module) {
+ $function = $module . '_views_query_alter';
+ $function($view, $this);
+ }
+ }
+
+ /**
+ * Builds the necessary info to execute the query.
+ */
+ function build(&$view) {
+ // Make the query distinct if the option was set.
+ if (!empty($this->options['distinct'])) {
+ $this->set_distinct(TRUE, !empty($this->options['pure_distinct']));
+ }
+
+ // Store the view in the object to be able to use it later.
+ $this->view = $view;
+
+ $view->init_pager();
+
+ // Let the pager modify the query to add limits.
+ $this->pager->query();
+
+ $view->build_info['query'] = $this->query();
+ $view->build_info['count_query'] = $this->query(TRUE);
+ }
+
+ /**
+ * Executes the query and fills the associated view object with according
+ * values.
+ *
+ * Values to set: $view->result, $view->total_rows, $view->execute_time,
+ * $view->current_page.
+ */
+ function execute(&$view) {
+ $external = FALSE; // Whether this query will run against an external database.
+ $query = $view->build_info['query'];
+ $count_query = $view->build_info['count_query'];
+
+ $query->addMetaData('view', $view);
+ $count_query->addMetaData('view', $view);
+
+ if (empty($this->options['disable_sql_rewrite'])) {
+ $base_table_data = views_fetch_data($this->base_table);
+ if (isset($base_table_data['table']['base']['access query tag'])) {
+ $access_tag = $base_table_data['table']['base']['access query tag'];
+ $query->addTag($access_tag);
+ $count_query->addTag($access_tag);
+ }
+ }
+
+ $items = array();
+ if ($query) {
+ $additional_arguments = module_invoke_all('views_query_substitutions', $view);
+
+ // Count queries must be run through the preExecute() method.
+ // If not, then hook_query_node_access_alter() may munge the count by
+ // adding a distinct against an empty query string
+ // (e.g. COUNT DISTINCT(1) ...) and no pager will return.
+ // See pager.inc > PagerDefault::execute()
+ // http://api.drupal.org/api/drupal/includes--pager.inc/function/PagerDefault::execute/7
+ // See http://drupal.org/node/1046170.
+ $count_query->preExecute();
+
+ // Build the count query.
+ $count_query = $count_query->countQuery();
+
+ // Add additional arguments as a fake condition.
+ // XXX: this doesn't work... because PDO mandates that all bound arguments
+ // are used on the query. TODO: Find a better way to do this.
+ if (!empty($additional_arguments)) {
+ // $query->where('1 = 1', $additional_arguments);
+ // $count_query->where('1 = 1', $additional_arguments);
+ }
+
+ $start = microtime(TRUE);
+
+
+ try {
+ if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
+ $this->pager->execute_count_query($count_query);
+ }
+
+ $this->pager->pre_execute($query);
+
+ $result = $query->execute();
+
+ $view->result = array();
+ foreach ($result as $item) {
+ $view->result[] = $item;
+ }
+
+ $this->pager->post_execute($view->result);
+
+ if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
+ $view->total_rows = $this->pager->get_total_items();
+ }
+ }
+ catch (Exception $e) {
+ $view->result = array();
+ if (!empty($view->live_preview)) {
+ drupal_set_message($e->getMessage(), 'error');
+ }
+ else {
+ vpr('Exception in @human_name[@view_name]: @message', array('@human_name' => $view->human_name, '@view_name' => $view->name, '@message' => $e->getMessage()));
+ }
+ }
+
+ }
+ else {
+ $start = microtime(TRUE);
+ }
+ $view->execute_time = microtime(TRUE) - $start;
+ }
+
+ function add_signature(&$view) {
+ $view->query->add_field(NULL, "'" . $view->name . ':' . $view->current_display . "'", 'view_name');
+ }
+
+ function get_aggregation_info() {
+ // @todo -- need a way to get database specific and customized aggregation
+ // functions into here.
+ return array(
+ 'group' => array(
+ 'title' => t('Group results together'),
+ 'is aggregate' => FALSE,
+ ),
+ 'count' => array(
+ 'title' => t('Count'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'field' => 'views_handler_field_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'count_distinct' => array(
+ 'title' => t('Count DISTINCT'),
+ 'method' => 'views_query_default_aggregation_method_distinct',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'field' => 'views_handler_field_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'sum' => array(
+ 'title' => t('Sum'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'avg' => array(
+ 'title' => t('Average'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'min' => array(
+ 'title' => t('Minimum'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'max' => array(
+ 'title' => t('Maximum'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ ),
+ 'stddev_pop' => array(
+ 'title' => t('Standard deviation'),
+ 'method' => 'views_query_default_aggregation_method_simple',
+ 'handler' => array(
+ 'argument' => 'views_handler_argument_group_by_numeric',
+ 'filter' => 'views_handler_filter_group_by_numeric',
+ 'sort' => 'views_handler_sort_group_by_numeric',
+ ),
+ )
+ );
+ }
+
+ /**
+ * Returns the according entity objects for the given query results.
+ *
+ */
+ function get_result_entities($results, $relationship = NULL) {
+ $base_table = $this->base_table;
+ $base_table_alias = $base_table;
+
+ if (!empty($relationship)) {
+ foreach ($this->view->relationship as $current) {
+ if ($current->alias == $relationship) {
+ $base_table = $current->definition['base'];
+ $base_table_alias = $relationship;
+ break;
+ }
+ }
+ }
+ $table_data = views_fetch_data($base_table);
+
+ // Bail out if the table has not specified the according entity-type.
+ if (!isset($table_data['table']['entity type'])) {
+ return FALSE;
+ }
+ $entity_type = $table_data['table']['entity type'];
+ $info = entity_get_info($entity_type);
+ $is_revision = !empty($table_data['table']['revision']);
+ $id_alias = $this->get_field_alias($base_table_alias, $info['entity keys'][$is_revision ? 'revision' : 'id']);
+
+ // Assemble the ids of the entities to load.
+ $ids = array();
+ foreach ($results as $key => $result) {
+ if (isset($result->$id_alias)) {
+ $ids[$key] = $result->$id_alias;
+ }
+ }
+
+ if (!$is_revision) {
+ $entities = entity_load($entity_type, $ids);
+
+ // Re-key the array by row-index.
+ $result = array();
+ foreach ($ids as $key => $id) {
+ $result[$key] = isset($entities[$id]) ? $entities[$id] : FALSE;
+ }
+ }
+ else {
+ // There's no way in core to load revisions in bulk.
+ $result = array();
+ foreach ($ids as $key => $id) {
+ // Nodes can be dealt with in core.
+ if ($entity_type == 'node') {
+ $result[$key] = node_load(NULL, $id);
+ }
+ // Otherwise see if entity is enabled.
+ elseif (module_exists('entity')) {
+ $result[$key] = entity_revision_load($entity_type, $id);
+ }
+ else {
+ // Otherwise this isn't supported.
+ watchdog('views', 'Attempt to load a revision on an unsupported entity type @entity_type.', array('@entity_type' => $entity_type), WATCHDOG_WARNING);
+ }
+ }
+ }
+
+ return array($entity_type, $result);
+ }
+}
+
+function views_query_default_aggregation_method_simple($group_type, $field) {
+ return strtoupper($group_type) . '(' . $field . ')';
+}
+
+function views_query_default_aggregation_method_distinct($group_type, $field) {
+ $group_type = str_replace('_distinct', '', $group_type);
+ return strtoupper($group_type) . '(DISTINCT ' . $field . ')';
+}
+
+/**
+ * Validation callback for query tags.
+ */
+function views_element_validate_tags($element, &$form_state) {
+ $values = array_map('trim', explode(',', $element['#value']));
+ foreach ($values as $value) {
+ if (preg_match("/[^a-z_]/", $value)) {
+ form_error($element, t('The query tags may only contain lower-case alphabetical characters and underscores.'));
+ return;
+ }
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_row.inc b/sites/all/modules/views/plugins/views_plugin_row.inc
new file mode 100644
index 000000000..157cc26d5
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_row.inc
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Contains the base row style plugin.
+ */
+
+/**
+ * @defgroup views_row_plugins Views row plugins
+ * @{
+ * Row plugins control how Views outputs an individual record.
+ *
+ * They are tightly coupled to style plugins, in that a style plugin is what
+ * calls the row plugin.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * Default plugin to view a single row of a table. This is really just a wrapper around
+ * a theme function.
+ */
+class views_plugin_row extends views_plugin {
+ /**
+ * Initialize the row plugin.
+ */
+ function init(&$view, &$display, $options = NULL) {
+ $this->view = &$view;
+ $this->display = &$display;
+
+ // Overlay incoming options on top of defaults
+ $this->unpack_options($this->options, isset($options) ? $options : $display->handler->get_option('row_options'));
+ }
+
+ function uses_fields() {
+ return !empty($this->definition['uses fields']);
+ }
+
+
+ function option_definition() {
+ $options = parent::option_definition();
+ if (isset($this->base_table)) {
+ $options['relationship'] = array('default' => 'none');
+ }
+
+ return $options;
+ }
+
+ /**
+ * Provide a form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ if (isset($this->base_table)) {
+ $view = &$form_state['view'];
+
+ // A whole bunch of code to figure out what relationships are valid for
+ // this item.
+ $relationships = $view->display_handler->get_option('relationships');
+ $relationship_options = array();
+
+ foreach ($relationships as $relationship) {
+ $relationship_handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
+
+ // If this relationship is valid for this type, add it to the list.
+ $data = views_fetch_data($relationship['table']);
+ $base = $data[$relationship['field']]['relationship']['base'];
+ if ($base == $this->base_table) {
+ $relationship_handler->init($view, $relationship);
+ $relationship_options[$relationship['id']] = $relationship_handler->label();
+ }
+ }
+
+ if (!empty($relationship_options)) {
+ $relationship_options = array_merge(array('none' => t('Do not use a relationship')), $relationship_options);
+ $rel = empty($this->options['relationship']) ? 'none' : $this->options['relationship'];
+ if (empty($relationship_options[$rel])) {
+ // Pick the first relationship.
+ $rel = key($relationship_options);
+ }
+
+ $form['relationship'] = array(
+ '#type' => 'select',
+ '#title' => t('Relationship'),
+ '#options' => $relationship_options,
+ '#default_value' => $rel,
+ );
+ }
+ else {
+ $form['relationship'] = array(
+ '#type' => 'value',
+ '#value' => 'none',
+ );
+ }
+ }
+ }
+
+ /**
+ * Validate the options form.
+ */
+ function options_validate(&$form, &$form_state) { }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) { }
+
+ function query() {
+ if (isset($this->base_table)) {
+ if (isset($this->options['relationship']) && isset($this->view->relationship[$this->options['relationship']])) {
+ $relationship = $this->view->relationship[$this->options['relationship']];
+ $this->field_alias = $this->view->query->add_field($relationship->alias, $this->base_field);
+ }
+ else {
+ $this->field_alias = $this->view->query->add_field($this->base_table, $this->base_field);
+ }
+ }
+ }
+
+ /**
+ * Allow the style to do stuff before each row is rendered.
+ *
+ * @param $result
+ * The full array of results from the query.
+ */
+ function pre_render($result) { }
+
+ /**
+ * Render a row object. This usually passes through to a theme template
+ * of some form, but not always.
+ *
+ * @param stdClass $row
+ * A single row of the query result, so an element of $view->result.
+ *
+ * @return string
+ * The rendered output of a single row, used by the style plugin.
+ */
+ function render($row) {
+ return theme($this->theme_functions(),
+ array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'row' => $row,
+ 'field_alias' => isset($this->field_alias) ? $this->field_alias : '',
+ ));
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_row_fields.inc b/sites/all/modules/views/plugins/views_plugin_row_fields.inc
new file mode 100644
index 000000000..b1c02e1fd
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_row_fields.inc
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains the base row style plugin.
+ */
+
+/**
+ * The basic 'fields' row plugin
+ *
+ * This displays fields one after another, giving options for inline
+ * or not.
+ *
+ * @ingroup views_row_plugins
+ */
+class views_plugin_row_fields extends views_plugin_row {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['inline'] = array('default' => array());
+ $options['separator'] = array('default' => '');
+ $options['hide_empty'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['default_field_elements'] = array('default' => TRUE, 'bool' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Provide a form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $options = $this->display->handler->get_field_labels();
+
+ if (empty($this->options['inline'])) {
+ $this->options['inline'] = array();
+ }
+
+ $form['default_field_elements'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Provide default field wrapper elements'),
+ '#default_value' => $this->options['default_field_elements'],
+ '#description' => t('If not checked, fields that are not configured to customize their HTML elements will get no wrappers at all for their field, label and field + label wrappers. You can use this to quickly reduce the amount of markup the view provides by default, at the cost of making it more difficult to apply CSS.'),
+ );
+
+ $form['inline'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Inline fields'),
+ '#options' => $options,
+ '#default_value' => $this->options['inline'],
+ '#description' => t('Inline fields will be displayed next to each other rather than one after another. Note that some fields will ignore this if they are block elements, particularly body fields and other formatted HTML.'),
+ '#dependency' => array(
+ 'edit-row-options-default-field-elements' => array(1),
+ ),
+ '#prefix' => '<div id="edit-row-options-inline-wrapper"><div>',
+ '#suffix' => '</div></div>',
+
+ );
+
+ $form['separator'] = array(
+ '#title' => t('Separator'),
+ '#type' => 'textfield',
+ '#size' => 10,
+ '#default_value' => isset($this->options['separator']) ? $this->options['separator'] : '',
+ '#description' => t('The separator may be placed between inline fields to keep them from squishing up next to each other. You can use HTML in this field.'),
+ '#dependency' => array(
+ 'edit-row-options-default-field-elements' => array(1),
+ ),
+ );
+
+ $form['hide_empty'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide empty fields'),
+ '#default_value' => $this->options['hide_empty'],
+ '#description' => t('Do not display fields, labels or markup for fields that are empty.'),
+ );
+
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) {
+ $form_state['values']['row_options']['inline'] = array_filter($form_state['values']['row_options']['inline']);
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_row_rss_fields.inc b/sites/all/modules/views/plugins/views_plugin_row_rss_fields.inc
new file mode 100644
index 000000000..9355e8333
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_row_rss_fields.inc
@@ -0,0 +1,180 @@
+<?php
+/**
+ * @file
+ * Contains an implementation of RSS items based on fields on a row plugin.
+ */
+
+/**
+ * Renders an RSS item based on fields.
+ */
+class views_plugin_row_rss_fields extends views_plugin_row {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['title_field'] = array('default' => '');
+ $options['link_field'] = array('default' => '');
+ $options['description_field'] = array('default' => '');
+ $options['creator_field'] = array('default' => '');
+ $options['date_field'] = array('default' => '');
+ $options['guid_field_options']['guid_field'] = array('default' => '');
+ $options['guid_field_options']['guid_field_is_permalink'] = array('default' => TRUE, 'bool' => TRUE);
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $initial_labels = array('' => t('- None -'));
+ $view_fields_labels = $this->display->handler->get_field_labels();
+ $view_fields_labels = array_merge($initial_labels, $view_fields_labels);
+
+ $form['title_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Title field'),
+ '#description' => t('The field that is going to be used as the RSS item title for each row.'),
+ '#options' => $view_fields_labels,
+ '#default_value' => $this->options['title_field'],
+ '#required' => TRUE,
+ );
+ $form['link_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Link field'),
+ '#description' => t('The field that is going to be used as the RSS item link for each row. This must be a drupal relative path.'),
+ '#options' => $view_fields_labels,
+ '#default_value' => $this->options['link_field'],
+ '#required' => TRUE,
+ );
+ $form['description_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Description field'),
+ '#description' => t('The field that is going to be used as the RSS item description for each row.'),
+ '#options' => $view_fields_labels,
+ '#default_value' => $this->options['description_field'],
+ '#required' => TRUE,
+ );
+ $form['creator_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Creator field'),
+ '#description' => t('The field that is going to be used as the RSS item creator for each row.'),
+ '#options' => $view_fields_labels,
+ '#default_value' => $this->options['creator_field'],
+ '#required' => TRUE,
+ );
+ $form['date_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Publication date field'),
+ '#description' => t('The field that is going to be used as the RSS item pubDate for each row. It needs to be in RFC 2822 format.'),
+ '#options' => $view_fields_labels,
+ '#default_value' => $this->options['date_field'],
+ '#required' => TRUE,
+ );
+ $form['guid_field_options'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('GUID settings'),
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ );
+ $form['guid_field_options']['guid_field'] = array(
+ '#type' => 'select',
+ '#title' => t('GUID field'),
+ '#description' => t('The globally unique identifier of the RSS item.'),
+ '#options' => $view_fields_labels,
+ '#default_value' => $this->options['guid_field_options']['guid_field'],
+ '#required' => TRUE,
+ );
+ $form['guid_field_options']['guid_field_is_permalink'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('GUID is permalink'),
+ '#description' => t('The RSS item GUID is a permalink.'),
+ '#default_value' => $this->options['guid_field_options']['guid_field_is_permalink'],
+ );
+ }
+
+ function validate() {
+ $errors = parent::validate();
+ $required_options = array('title_field', 'link_field', 'description_field', 'creator_field', 'date_field');
+ foreach ($required_options as $required_option) {
+ if (empty($this->options[$required_option])) {
+ $errors[] = t('Row style plugin requires specifying which views fields to use for RSS item.');
+ break;
+ }
+ }
+ // Once more for guid.
+ if (empty($this->options['guid_field_options']['guid_field'])) {
+ $errors[] = t('Row style plugin requires specifying which views fields to use for RSS item.');
+ }
+ return $errors;
+ }
+
+ function render($row) {
+ static $row_index;
+ if (!isset($row_index)) {
+ $row_index = 0;
+ }
+ if (function_exists('rdf_get_namespaces')) {
+ // Merge RDF namespaces in the XML namespaces in case they are used
+ // further in the RSS content.
+ $xml_rdf_namespaces = array();
+ foreach (rdf_get_namespaces() as $prefix => $uri) {
+ $xml_rdf_namespaces['xmlns:' . $prefix] = $uri;
+ }
+ $this->view->style_plugin->namespaces += $xml_rdf_namespaces;
+ }
+
+ // Create the RSS item object.
+ $item = new stdClass();
+ $item->title = $this->get_field($row_index, $this->options['title_field']);
+ $item->link = url($this->get_field($row_index, $this->options['link_field']), array('absolute' => TRUE));
+ $item->description = $this->get_field($row_index, $this->options['description_field']);
+ $item->elements = array(
+ array('key' => 'pubDate', 'value' => $this->get_field($row_index, $this->options['date_field'])),
+ array(
+ 'key' => 'dc:creator',
+ 'value' => $this->get_field($row_index, $this->options['creator_field']),
+ 'namespace' => array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/'),
+ ),
+ );
+ $guid_is_permalink_string = 'false';
+ $item_guid = $this->get_field($row_index, $this->options['guid_field_options']['guid_field']);
+ if ($this->options['guid_field_options']['guid_field_is_permalink']) {
+ $guid_is_permalink_string = 'true';
+ $item_guid = url($item_guid, array('absolute' => TRUE));
+ }
+ $item->elements[] = array(
+ 'key' => 'guid',
+ 'value' => $item_guid,
+ 'attributes' => array('isPermaLink' => $guid_is_permalink_string),
+ );
+
+ $row_index++;
+
+ foreach ($item->elements as $element) {
+ if (isset($element['namespace'])) {
+ $this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $element['namespace']);
+ }
+ }
+
+ return theme($this->theme_functions(),
+ array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'row' => $item,
+ 'field_alias' => isset($this->field_alias) ? $this->field_alias : '',
+ ));
+ }
+
+ /**
+ * Retrieves a views field value from the style plugin.
+ *
+ * @param $index
+ * The index count of the row as expected by views_plugin_style::get_field().
+ * @param $field_id
+ * The ID assigned to the required field in the display.
+ */
+ function get_field($index, $field_id) {
+ if (empty($this->view->style_plugin) || !is_object($this->view->style_plugin) || empty($field_id)) {
+ return '';
+ }
+ return $this->view->style_plugin->get_field($index, $field_id);
+ }
+
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style.inc b/sites/all/modules/views/plugins/views_plugin_style.inc
new file mode 100644
index 000000000..6c5dd5e9c
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style.inc
@@ -0,0 +1,598 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_style.
+ */
+
+/**
+ * @defgroup views_style_plugins Views style plugins
+ * @{
+ * Style plugins control how a view is rendered. For example, they
+ * can choose to display a collection of fields, node_view() output,
+ * table output, or any kind of crazy output they want.
+ *
+ * Many style plugins can have an optional 'row' plugin, that displays
+ * a single record. Not all style plugins can utilize this, so it is
+ * up to the plugin to set this up and call through to the row plugin.
+ *
+ * @see hook_views_plugins()
+ */
+
+/**
+ * Base class to define a style plugin handler.
+ */
+class views_plugin_style extends views_plugin {
+ /**
+ * Store all available tokens row rows.
+ */
+ var $row_tokens = array();
+
+ /**
+ * Contains the row plugin, if it's initialized
+ * and the style itself supports it.
+ *
+ * @var views_plugin_row
+ */
+ var $row_plugin;
+
+ /**
+ * Initialize a style plugin.
+ *
+ * @param $view
+ * @param $display
+ * @param $options
+ * The style options might come externally as the style can be sourced
+ * from at least two locations. If it's not included, look on the display.
+ */
+ function init(&$view, &$display, $options = NULL) {
+ $this->view = &$view;
+ $this->display = &$display;
+
+ // Overlay incoming options on top of defaults
+ $this->unpack_options($this->options, isset($options) ? $options : $display->handler->get_option('style_options'));
+
+ if ($this->uses_row_plugin() && $display->handler->get_option('row_plugin')) {
+ $this->row_plugin = $display->handler->get_plugin('row');
+ }
+
+ $this->options += array(
+ 'grouping' => array(),
+ );
+
+ $this->definition += array(
+ 'uses grouping' => TRUE,
+ );
+ }
+
+ function destroy() {
+ parent::destroy();
+
+ if (isset($this->row_plugin)) {
+ $this->row_plugin->destroy();
+ }
+ }
+
+ /**
+ * Return TRUE if this style also uses a row plugin.
+ */
+ function uses_row_plugin() {
+ return !empty($this->definition['uses row plugin']);
+ }
+
+ /**
+ * Return TRUE if this style also uses a row plugin.
+ */
+ function uses_row_class() {
+ return !empty($this->definition['uses row class']);
+ }
+
+ /**
+ * Return TRUE if this style also uses fields.
+ *
+ * @return bool
+ */
+ function uses_fields() {
+ // If we use a row plugin, ask the row plugin. Chances are, we don't
+ // care, it does.
+ $row_uses_fields = FALSE;
+ if ($this->uses_row_plugin() && !empty($this->row_plugin)) {
+ $row_uses_fields = $this->row_plugin->uses_fields();
+ }
+ // Otherwise, check the definition or the option.
+ return $row_uses_fields || !empty($this->definition['uses fields']) || !empty($this->options['uses_fields']);
+ }
+
+ /**
+ * Return TRUE if this style uses tokens.
+ *
+ * Used to ensure we don't fetch tokens when not needed for performance.
+ */
+ function uses_tokens() {
+ if ($this->uses_row_class()) {
+ $class = $this->options['row_class'];
+ if (strpos($class, '[') !== FALSE || strpos($class, '!') !== FALSE || strpos($class, '%') !== FALSE) {
+ return TRUE;
+ }
+ }
+ }
+
+ /**
+ * Return the token replaced row class for the specified row.
+ */
+ function get_row_class($row_index) {
+ if ($this->uses_row_class()) {
+ $class = $this->options['row_class'];
+ if ($this->uses_fields() && $this->view->field) {
+ $class = strip_tags($this->tokenize_value($class, $row_index));
+ }
+
+ $classes = explode(' ', $class);
+ foreach ($classes as &$class) {
+ $class = drupal_clean_css_identifier($class);
+ }
+ return implode(' ', $classes);
+ }
+ }
+
+ /**
+ * Take a value and apply token replacement logic to it.
+ */
+ function tokenize_value($value, $row_index) {
+ if (strpos($value, '[') !== FALSE || strpos($value, '!') !== FALSE || strpos($value, '%') !== FALSE) {
+ $fake_item = array(
+ 'alter_text' => TRUE,
+ 'text' => $value,
+ );
+
+ // Row tokens might be empty, for example for node row style.
+ $tokens = isset($this->row_tokens[$row_index]) ? $this->row_tokens[$row_index] : array();
+ if (!empty($this->view->build_info['substitutions'])) {
+ $tokens += $this->view->build_info['substitutions'];
+ }
+
+ if ($tokens) {
+ $value = strtr($value, $tokens);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Should the output of the style plugin be rendered even if it's a empty view.
+ */
+ function even_empty() {
+ return !empty($this->definition['even empty']);
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['grouping'] = array('default' => array());
+ if ($this->uses_row_class()) {
+ $options['row_class'] = array('default' => '');
+ $options['default_row_class'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['row_class_special'] = array('default' => TRUE, 'bool' => TRUE);
+ }
+ $options['uses_fields'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ // Only fields-based views can handle grouping. Style plugins can also exclude
+ // themselves from being groupable by setting their "use grouping" definition
+ // key to FALSE.
+ // @TODO: Document "uses grouping" in docs.php when docs.php is written.
+ if ($this->uses_fields() && $this->definition['uses grouping']) {
+ $options = array('' => t('- None -'));
+ $field_labels = $this->display->handler->get_field_labels(TRUE);
+ $options += $field_labels;
+ // If there are no fields, we can't group on them.
+ if (count($options) > 1) {
+ // This is for backward compatibility, when there was just a single select form.
+ if (is_string($this->options['grouping'])) {
+ $grouping = $this->options['grouping'];
+ $this->options['grouping'] = array();
+ $this->options['grouping'][0]['field'] = $grouping;
+ }
+ if (isset($this->options['group_rendered']) && is_string($this->options['group_rendered'])) {
+ $this->options['grouping'][0]['rendered'] = $this->options['group_rendered'];
+ unset($this->options['group_rendered']);
+ }
+
+ $c = count($this->options['grouping']);
+ // Add a form for every grouping, plus one.
+ for ($i = 0; $i <= $c; $i++) {
+ $grouping = !empty($this->options['grouping'][$i]) ? $this->options['grouping'][$i] : array();
+ $grouping += array('field' => '', 'rendered' => TRUE, 'rendered_strip' => FALSE);
+ $form['grouping'][$i]['field'] = array(
+ '#type' => 'select',
+ '#title' => t('Grouping field Nr.@number', array('@number' => $i + 1)),
+ '#options' => $options,
+ '#default_value' => $grouping['field'],
+ '#description' => t('You may optionally specify a field by which to group the records. Leave blank to not group.'),
+ );
+ $form['grouping'][$i]['rendered'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use rendered output to group rows'),
+ '#default_value' => $grouping['rendered'],
+ '#description' => t('If enabled the rendered output of the grouping field is used to group the rows.'),
+ '#dependency' => array(
+ 'edit-style-options-grouping-' . $i . '-field' => array_keys($field_labels),
+ )
+ );
+ $form['grouping'][$i]['rendered_strip'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remove tags from rendered output'),
+ '#default_value' => $grouping['rendered_strip'],
+ '#dependency' => array(
+ 'edit-style-options-grouping-' . $i . '-field' => array_keys($field_labels),
+ )
+ );
+ }
+ }
+ }
+
+ if ($this->uses_row_class()) {
+ $form['row_class'] = array(
+ '#title' => t('Row class'),
+ '#description' => t('The class to provide on each row.'),
+ '#type' => 'textfield',
+ '#default_value' => $this->options['row_class'],
+ );
+
+ if ($this->uses_fields()) {
+ $form['row_class']['#description'] .= ' ' . t('You may use field tokens from as per the "Replacement patterns" used in "Rewrite the output of this field" for all fields.');
+ }
+
+ $form['default_row_class'] = array(
+ '#title' => t('Add views row classes'),
+ '#description' => t('Add the default row classes like views-row-1 to the output. You can use this to quickly reduce the amount of markup the view provides by default, at the cost of making it more difficult to apply CSS.'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['default_row_class'],
+ );
+ $form['row_class_special'] = array(
+ '#title' => t('Add striping (odd/even), first/last row classes'),
+ '#description' => t('Add css classes to the first and last line, as well as odd/even classes for striping.'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->options['row_class_special'],
+ );
+ }
+
+ if (!$this->uses_fields() || !empty($this->options['uses_fields'])) {
+ $form['uses_fields'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Force using fields'),
+ '#description' => t('If neither the row nor the style plugin supports fields, this field allows to enable them, so you can for example use groupby.'),
+ '#default_value' => $this->options['uses_fields'],
+ );
+ }
+ }
+
+ function options_validate(&$form, &$form_state) {
+ // Don't run validation on style plugins without the grouping setting.
+ if (isset($form_state['values']['style_options']['grouping'])) {
+ // Don't save grouping if no field is specified.
+ foreach ($form_state['values']['style_options']['grouping'] as $index => $grouping) {
+ if (empty($grouping['field'])) {
+ unset($form_state['values']['style_options']['grouping'][$index]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by the view builder to see if this style handler wants to
+ * interfere with the sorts. If so it should build; if it returns
+ * any non-TRUE value, normal sorting will NOT be added to the query.
+ */
+ function build_sort() { return TRUE; }
+
+ /**
+ * Called by the view builder to let the style build a second set of
+ * sorts that will come after any other sorts in the view.
+ */
+ function build_sort_post() { }
+
+ /**
+ * Allow the style to do stuff before each row is rendered.
+ *
+ * @param $result
+ * The full array of results from the query.
+ */
+ function pre_render($result) {
+ if (!empty($this->row_plugin)) {
+ $this->row_plugin->pre_render($result);
+ }
+ }
+
+ /**
+ * Render the display in this style.
+ */
+ function render() {
+ if ($this->uses_row_plugin() && empty($this->row_plugin)) {
+ debug('views_plugin_style_default: Missing row plugin');
+ return;
+ }
+
+ // Group the rows according to the grouping instructions, if specified.
+ $sets = $this->render_grouping(
+ $this->view->result,
+ $this->options['grouping'],
+ TRUE
+ );
+
+ return $this->render_grouping_sets($sets);
+ }
+
+ /**
+ * Render the grouping sets.
+ *
+ * Plugins may override this method if they wish some other way of handling
+ * grouping.
+ *
+ * @param $sets
+ * Array containing the grouping sets to render.
+ * @param $level
+ * Integer indicating the hierarchical level of the grouping.
+ *
+ * @return string
+ * Rendered output of given grouping sets.
+ */
+ function render_grouping_sets($sets, $level = 0) {
+ $output = '';
+ foreach ($sets as $set) {
+ $row = reset($set['rows']);
+ // Render as a grouping set.
+ if (is_array($row) && isset($row['group'])) {
+ $output .= theme(views_theme_functions('views_view_grouping', $this->view, $this->display),
+ array(
+ 'view' => $this->view,
+ 'grouping' => $this->options['grouping'][$level],
+ 'grouping_level' => $level,
+ 'rows' => $set['rows'],
+ 'title' => $set['group'])
+ );
+ }
+ // Render as a record set.
+ else {
+ if ($this->uses_row_plugin()) {
+ foreach ($set['rows'] as $index => $row) {
+ $this->view->row_index = $index;
+ $set['rows'][$index] = $this->row_plugin->render($row);
+ }
+ }
+
+ $output .= theme($this->theme_functions(),
+ array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'grouping_level' => $level,
+ 'rows' => $set['rows'],
+ 'title' => $set['group'])
+ );
+ }
+ }
+ unset($this->view->row_index);
+ return $output;
+ }
+
+ /**
+ * Group records as needed for rendering.
+ *
+ * @param $records
+ * An array of records from the view to group.
+ * @param $groupings
+ * An array of grouping instructions on which fields to group. If empty, the
+ * result set will be given a single group with an empty string as a label.
+ * @param $group_rendered
+ * Boolean value whether to use the rendered or the raw field value for
+ * grouping. If set to NULL the return is structured as before
+ * Views 7.x-3.0-rc2. After Views 7.x-3.0 this boolean is only used if
+ * $groupings is an old-style string or if the rendered option is missing
+ * for a grouping instruction.
+ * @return
+ * The grouped record set.
+ * A nested set structure is generated if multiple grouping fields are used.
+ *
+ * @code
+ * array(
+ * 'grouping_field_1:grouping_1' => array(
+ * 'group' => 'grouping_field_1:content_1',
+ * 'rows' => array(
+ * 'grouping_field_2:grouping_a' => array(
+ * 'group' => 'grouping_field_2:content_a',
+ * 'rows' => array(
+ * $row_index_1 => $row_1,
+ * $row_index_2 => $row_2,
+ * // ...
+ * )
+ * ),
+ * ),
+ * ),
+ * 'grouping_field_1:grouping_2' => array(
+ * // ...
+ * ),
+ * )
+ * @endcode
+ */
+ function render_grouping($records, $groupings = array(), $group_rendered = NULL) {
+ // This is for backward compatibility, when $groupings was a string containing
+ // the ID of a single field.
+ if (is_string($groupings)) {
+ $rendered = $group_rendered === NULL ? TRUE : $group_rendered;
+ $groupings = array(array('field' => $groupings, 'rendered' => $rendered));
+ }
+
+ // Make sure fields are rendered
+ $this->render_fields($this->view->result);
+ $sets = array();
+ if ($groupings) {
+ foreach ($records as $index => $row) {
+ // Iterate through configured grouping fields to determine the
+ // hierarchically positioned set where the current row belongs to.
+ // While iterating, parent groups, that do not exist yet, are added.
+ $set = &$sets;
+ foreach ($groupings as $info) {
+ $field = $info['field'];
+ $rendered = isset($info['rendered']) ? $info['rendered'] : $group_rendered;
+ $rendered_strip = isset($info['rendered_strip']) ? $info['rendered_strip'] : FALSE;
+ $grouping = '';
+ $group_content = '';
+ // Group on the rendered version of the field, not the raw. That way,
+ // we can control any special formatting of the grouping field through
+ // the admin or theme layer or anywhere else we'd like.
+ if (isset($this->view->field[$field])) {
+ $group_content = $this->get_field($index, $field);
+ if ($this->view->field[$field]->options['label']) {
+ $group_content = $this->view->field[$field]->options['label'] . ': ' . $group_content;
+ }
+ if ($rendered) {
+ $grouping = $group_content;
+ if ($rendered_strip) {
+ $group_content = $grouping = strip_tags(htmlspecialchars_decode($group_content));
+ }
+ }
+ else {
+ $grouping = $this->get_field_value($index, $field);
+ // Not all field handlers return a scalar value,
+ // e.g. views_handler_field_field.
+ if (!is_scalar($grouping)) {
+ $grouping = md5(serialize($grouping));
+ }
+ }
+ }
+
+ // Create the group if it does not exist yet.
+ if (empty($set[$grouping])) {
+ $set[$grouping]['group'] = $group_content;
+ $set[$grouping]['rows'] = array();
+ }
+
+ // Move the set reference into the row set of the group we just determined.
+ $set = &$set[$grouping]['rows'];
+ }
+ // Add the row to the hierarchically positioned row set we just determined.
+ $set[$index] = $row;
+ }
+ }
+ else {
+ // Create a single group with an empty grouping field.
+ $sets[''] = array(
+ 'group' => '',
+ 'rows' => $records,
+ );
+ }
+
+ // If this parameter isn't explicitly set modify the output to be fully
+ // backward compatible to code before Views 7.x-3.0-rc2.
+ // @TODO Remove this as soon as possible e.g. October 2020
+ if ($group_rendered === NULL) {
+ $old_style_sets = array();
+ foreach ($sets as $group) {
+ $old_style_sets[$group['group']] = $group['rows'];
+ }
+ $sets = $old_style_sets;
+ }
+
+ return $sets;
+ }
+
+ /**
+ * Render all of the fields for a given style and store them on the object.
+ *
+ * @param $result
+ * The result array from $view->result
+ */
+ function render_fields($result) {
+ if (!$this->uses_fields()) {
+ return;
+ }
+
+ if (!isset($this->rendered_fields)) {
+ $this->rendered_fields = array();
+ $this->view->row_index = 0;
+ $keys = array_keys($this->view->field);
+
+ // If all fields have a field::access FALSE there might be no fields, so
+ // there is no reason to execute this code.
+ if (!empty($keys)) {
+ foreach ($result as $count => $row) {
+ $this->view->row_index = $count;
+ foreach ($keys as $id) {
+ $this->rendered_fields[$count][$id] = $this->view->field[$id]->theme($row);
+ }
+
+ $this->row_tokens[$count] = $this->view->field[$id]->get_render_tokens(array());
+ }
+ }
+ unset($this->view->row_index);
+ }
+
+ return $this->rendered_fields;
+ }
+
+ /**
+ * Get a rendered field.
+ *
+ * @param $index
+ * The index count of the row.
+ * @param $field
+ * The id of the field.
+ */
+ function get_field($index, $field) {
+ if (!isset($this->rendered_fields)) {
+ $this->render_fields($this->view->result);
+ }
+
+ if (isset($this->rendered_fields[$index][$field])) {
+ return $this->rendered_fields[$index][$field];
+ }
+ }
+
+ /**
+ * Get the raw field value.
+ *
+ * @param $index
+ * The index count of the row.
+ * @param $field
+ * The id of the field.
+ */
+ function get_field_value($index, $field) {
+ $this->view->row_index = $index;
+ $value = $this->view->field[$field]->get_value($this->view->result[$index]);
+ unset($this->view->row_index);
+ return $value;
+ }
+
+ function validate() {
+ $errors = parent::validate();
+
+ if ($this->uses_row_plugin()) {
+ $plugin = $this->display->handler->get_plugin('row');
+ if (empty($plugin)) {
+ $errors[] = t('Style @style requires a row style but the row plugin is invalid.', array('@style' => $this->definition['title']));
+ }
+ else {
+ $result = $plugin->validate();
+ if (!empty($result) && is_array($result)) {
+ $errors = array_merge($errors, $result);
+ }
+ }
+ }
+ return $errors;
+ }
+
+ function query() {
+ parent::query();
+ if (isset($this->row_plugin)) {
+ $this->row_plugin->query();
+ }
+ }
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/views/plugins/views_plugin_style_default.inc b/sites/all/modules/views/plugins/views_plugin_style_default.inc
new file mode 100644
index 000000000..a18f6ccfa
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_default.inc
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains the default style plugin.
+ */
+
+/**
+ * Default style plugin to render rows one after another with no
+ * decorations.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_default extends views_plugin_style {
+ /**
+ * Set default options
+ */
+ function options(&$options) {
+ parent::options($options);
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_grid.inc b/sites/all/modules/views/plugins/views_plugin_style_grid.inc
new file mode 100644
index 000000000..a2e437559
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_grid.inc
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains the grid style plugin.
+ */
+
+/**
+ * Style plugin to render each item in a grid cell.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_grid extends views_plugin_style {
+ /**
+ * Set default options
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['columns'] = array('default' => '4');
+ $options['alignment'] = array('default' => 'horizontal');
+ $options['fill_single_line'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['summary'] = array('default' => '');
+ $options['caption'] = array('default' => '');
+
+ return $options;
+ }
+
+ /**
+ * Render the given style.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['columns'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Number of columns'),
+ '#default_value' => $this->options['columns'],
+ '#required' => TRUE,
+ '#element_validate' => array('views_element_validate_integer'),
+ );
+ $form['alignment'] = array(
+ '#type' => 'radios',
+ '#title' => t('Alignment'),
+ '#options' => array('horizontal' => t('Horizontal'), 'vertical' => t('Vertical')),
+ '#default_value' => $this->options['alignment'],
+ '#description' => t('Horizontal alignment will place items starting in the upper left and moving right. Vertical alignment will place items starting in the upper left and moving down.'),
+ );
+
+ $form['fill_single_line'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Fill up single line'),
+ '#description' => t('If you disable this option, a grid with only one row will have the same number of table cells (<TD>) as items. Disabling it can cause problems with your CSS.'),
+ '#default_value' => !empty($this->options['fill_single_line']),
+ );
+
+ $form['caption'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Short description of table'),
+ '#description' => t('Include a caption for better accessibility of your table.'),
+ '#default_value' => $this->options['caption'],
+ );
+
+ $form['summary'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Table summary'),
+ '#description' => t('This value will be displayed as table-summary attribute in the html. Use this to give a summary of complex tables.'),
+ '#default_value' => $this->options['summary'],
+ );
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_jump_menu.inc b/sites/all/modules/views/plugins/views_plugin_style_jump_menu.inc
new file mode 100644
index 000000000..1de90f79e
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_jump_menu.inc
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * @file
+ * Contains the table style plugin.
+ */
+
+/**
+ * Style plugin to render each item as a row in a table.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_jump_menu extends views_plugin_style {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['hide'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['path'] = array('default' => '');
+ $options['text'] = array('default' => 'Go', 'translatable' => TRUE);
+ $options['label'] = array('default' => '', 'translatable' => TRUE);
+ $options['choose'] = array('default' => '- Choose -', 'translatable' => TRUE);
+ $options['inline'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['default_value'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Render the given style.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $handlers = $this->display->handler->get_handlers('field');
+ if (empty($handlers)) {
+ $form['error_markup'] = array(
+ '#markup' => t('You need at least one field before you can configure your jump menu settings'),
+ '#prefix' => '<div class="error messages">',
+ '#suffix' => '</div>',
+ );
+ return;
+ }
+
+ $form['markup'] = array(
+ '#markup' => t('To properly configure a jump menu, you must select one field that will represent the path to utilize. You should then set that field to exclude. All other displayed fields will be part of the menu. Please note that all HTML will be stripped from this output as select boxes cannot show HTML.'),
+ '#prefix' => '<div class="form-item description">',
+ '#suffix' => '</div>',
+ );
+
+ foreach ($handlers as $id => $handler) {
+ $options[$id] = $handler->ui_name();
+ }
+
+ $form['path'] = array(
+ '#type' => 'select',
+ '#title' => t('Path field'),
+ '#options' => $options,
+ '#default_value' => $this->options['path'],
+ );
+
+ $form['hide'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide the "Go" button'),
+ '#default_value' => !empty($this->options['hide']),
+ '#description' => t('If hidden, this button will only be hidden for users with javascript and the page will automatically jump when the select is changed.'),
+ );
+
+ $form['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Button text'),
+ '#default_value' => $this->options['text'],
+ );
+
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Selector label'),
+ '#default_value' => $this->options['label'],
+ '#description' => t('The text that will appear as the the label of the selector element. If blank no label tag will be used.'),
+ );
+
+ $form['choose'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Choose text'),
+ '#default_value' => $this->options['choose'],
+ '#description' => t('The text that will appear as the selected option in the jump menu.'),
+ );
+
+ $form['inline'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Set this field to display inline'),
+ '#default_value' => !empty($this->options['inline']),
+ );
+
+ $form['default_value'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Select the current contextual filter value'),
+ '#default_value' => !empty($this->options['default_value']),
+ '#description' => t('If checked, the current path will be displayed as the default option in the jump menu, if applicable.'),
+ );
+ }
+
+ /**
+ * Render the display in this style.
+ *
+ * This is overridden so that we can render our grouping specially.
+ */
+ function render() {
+ $sets = $this->render_grouping($this->view->result, $this->options['grouping']);
+
+ // Turn this all into an $options array for the jump menu.
+ $this->view->row_index = 0;
+ $options = array();
+ $paths = array();
+
+ foreach ($sets as $title => $records) {
+ foreach ($records as $row_index => $row) {
+ $this->view->row_index = $row_index;
+ $path = strip_tags(decode_entities($this->get_field($this->view->row_index, $this->options['path'])));
+ // Putting a '/' in front messes up url() so let's take that out
+ // so users don't shoot themselves in the foot.
+ $base_path = base_path();
+ if (strpos($path, $base_path) === 0) {
+ $path = drupal_substr($path, drupal_strlen($base_path));
+ }
+
+ // use drupal_parse_url() to preserve query and fragment in case the user
+ // wants to do fun tricks.
+ $url_options = drupal_parse_url($path);
+
+ $path = url($url_options['path'], $url_options);
+ $field = strip_tags(decode_entities($this->row_plugin->render($row)));
+ $key = md5($path . $field) . "::" . $path;
+ if ($title) {
+ $options[$title][$key] = $field;
+ }
+ else {
+ $options[$key] = $field;
+ }
+ $paths[$path] = $key;
+ $this->view->row_index++;
+ }
+ }
+ unset($this->view->row_index);
+
+ $default_value = '';
+ if ($this->options['default_value']) {
+ $lookup_options = array();
+ // We need to check if the path is absolute
+ // or else language is not taken in account.
+ if ($this->view->display[$this->view->current_display]->display_options['fields'][$this->options['path']]['absolute']) {
+ $lookup_options['absolute'] = TRUE;
+ }
+ $lookup_url = url($_GET['q'], $lookup_options);
+ if (!empty($paths[$lookup_url])) {
+ $default_value = $paths[$lookup_url];
+ }
+ }
+
+ ctools_include('jump-menu');
+ $settings = array(
+ 'hide' => $this->options['hide'],
+ 'button' => $this->options['text'],
+ 'title' => $this->options['label'],
+ 'choose' => $this->options['choose'],
+ 'inline' => $this->options['inline'],
+ 'default_value' => $default_value,
+ );
+
+ $form = drupal_get_form('ctools_jump_menu', $options, $settings);
+ return $form;
+ }
+
+ function render_set($title, $records) {
+ $options = array();
+ $fields = $this->rendered_fields;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_list.inc b/sites/all/modules/views/plugins/views_plugin_style_list.inc
new file mode 100644
index 000000000..2a1dadb88
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_list.inc
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains the list style plugin.
+ */
+
+/**
+ * Style plugin to render each item in an ordered or unordered list.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_list extends views_plugin_style {
+ /**
+ * Set default options
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['type'] = array('default' => 'ul');
+ $options['class'] = array('default' => '');
+ $options['wrapper_class'] = array('default' => 'item-list');
+
+ return $options;
+ }
+
+ /**
+ * Render the given style.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('List type'),
+ '#options' => array('ul' => t('Unordered list'), 'ol' => t('Ordered list')),
+ '#default_value' => $this->options['type'],
+ );
+ $form['wrapper_class'] = array(
+ '#title' => t('Wrapper class'),
+ '#description' => t('The class to provide on the wrapper, outside the list.'),
+ '#type' => 'textfield',
+ '#size' => '30',
+ '#default_value' => $this->options['wrapper_class'],
+ );
+ $form['class'] = array(
+ '#title' => t('List class'),
+ '#description' => t('The class to provide on the list element itself.'),
+ '#type' => 'textfield',
+ '#size' => '30',
+ '#default_value' => $this->options['class'],
+ );
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_mapping.inc b/sites/all/modules/views/plugins/views_plugin_style_mapping.inc
new file mode 100644
index 000000000..fb60a03f2
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_mapping.inc
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_style_mapping.
+ */
+
+/**
+ * Allows fields to be mapped to specific use cases.
+ */
+abstract class views_plugin_style_mapping extends views_plugin_style {
+
+ /**
+ * Builds the list of field mappings.
+ *
+ * @return array
+ * An associative array, keyed by the field name, containing the following
+ * key-value pairs:
+ * - #title: The human-readable label for this field.
+ * - #default_value: The default value for this field. If not provided, an
+ * empty string will be used.
+ * - #description: A description of this field.
+ * - #required: Whether this field is required.
+ * - #filter: (optional) A method on the plugin to filter field options.
+ * - #toggle: (optional) If this select should be toggled by a checkbox.
+ */
+ abstract protected function define_mapping();
+
+ /**
+ * Overrides views_plugin_style::option_definition().
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+
+ // Parse the mapping and add a default for each.
+ foreach ($this->define_mapping() as $key => $value) {
+ $default = !empty($value['#multiple']) ? array() : '';
+ $options['mapping']['contains'][$key] = array(
+ 'default' => isset($value['#default_value']) ? $value['#default_value'] : $default,
+ );
+ if (!empty($value['#toggle'])) {
+ $options['mapping']['contains']["toggle_$key"] = array(
+ 'default' => FALSE,
+ 'bool' => TRUE,
+ );
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Overrides views_plugin_style::options_form().
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ // Get the mapping.
+ $mapping = $this->define_mapping();
+
+ // Restrict the list of defaults to the mapping, in case they have changed.
+ $options = array_intersect_key($this->options['mapping'], $mapping);
+
+ // Get the labels of the fields added to this display.
+ $field_labels = $this->display->handler->get_field_labels();
+
+ // Provide some default values.
+ $defaults = array(
+ '#type' => 'select',
+ '#required' => FALSE,
+ '#multiple' => FALSE,
+ );
+
+ // For each mapping, add a select element to the form.
+ foreach ($options as $key => $value) {
+ // If the field is optional, add a 'None' value to the top of the options.
+ $field_options = array();
+ $required = !empty($mapping[$key]['#required']);
+ if (!$required && empty($mapping[$key]['#multiple'])) {
+ $field_options = array('' => t('- None -'));
+ }
+ $field_options += $field_labels;
+
+ // Optionally filter the available fields.
+ if (isset($mapping[$key]['#filter'])) {
+ $this->view->init_handlers();
+ $this::$mapping[$key]['#filter']($field_options);
+ unset($mapping[$key]['#filter']);
+ }
+
+ // These values must always be set.
+ $overrides = array(
+ '#options' => $field_options,
+ '#default_value' => $options[$key],
+ );
+
+ // Optionally allow the select to be toggleable.
+ if (!empty($mapping[$key]['#toggle'])) {
+ $form['mapping']["toggle_$key"] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use a custom %field_name', array('%field_name' => strtolower($mapping[$key]['#title']))),
+ '#default_value' => $this->options['mapping']["toggle_$key"],
+ );
+ $overrides['#states']['visible'][':input[name="style_options[mapping][' . "toggle_$key" . ']"]'] = array('checked' => TRUE);
+ }
+
+ $form['mapping'][$key] = $overrides + $mapping[$key] + $defaults;
+ }
+ }
+
+ /**
+ * Overrides views_plugin_style::render().
+ *
+ * Provides the mapping definition as an available variable.
+ */
+ function render() {
+ return theme($this->theme_functions(), array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'rows' => $this->view->result,
+ 'mapping' => $this->define_mapping(),
+ ));
+ }
+
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_rss.inc b/sites/all/modules/views/plugins/views_plugin_style_rss.inc
new file mode 100644
index 000000000..79fef3d00
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_rss.inc
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * @file
+ * Contains the RSS style plugin.
+ */
+
+/**
+ * Default style plugin to render an RSS feed.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_rss extends views_plugin_style {
+ function attach_to($display_id, $path, $title) {
+ $display = $this->view->display[$display_id]->handler;
+ $url_options = array();
+ $input = $this->view->get_exposed_input();
+ if ($input) {
+ $url_options['query'] = $input;
+ }
+ $url_options['absolute'] = TRUE;
+
+ $url = url($this->view->get_url(NULL, $path), $url_options);
+ if ($display->has_path()) {
+ if (empty($this->preview)) {
+ drupal_add_feed($url, $title);
+ }
+ }
+ else {
+ if (empty($this->view->feed_icon)) {
+ $this->view->feed_icon = '';
+ }
+
+ $this->view->feed_icon .= theme('feed_icon', array('url' => $url, 'title' => $title));
+ drupal_add_html_head_link(array(
+ 'rel' => 'alternate',
+ 'type' => 'application/rss+xml',
+ 'title' => $title,
+ 'href' => $url
+ ));
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['description'] = array('default' => '', 'translatable' => TRUE);
+
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['description'] = array(
+ '#type' => 'textfield',
+ '#title' => t('RSS description'),
+ '#default_value' => $this->options['description'],
+ '#description' => t('This will appear in the RSS feed itself.'),
+ '#maxlength' => 1024,
+ );
+ }
+
+ /**
+ * Return an array of additional XHTML elements to add to the channel.
+ *
+ * @return
+ * An array that can be passed to format_xml_elements().
+ */
+ function get_channel_elements() {
+ return array();
+ }
+
+ /**
+ * Return an atom:link XHTML element to add to the channel to comply with
+ * the RSS 2.0 specification.
+ *
+ * @see http://validator.w3.org/feed/docs/warning/MissingAtomSelfLink.html
+ *
+ * @return
+ * An array that can be passed to format_xml_elements().
+ */
+ function get_channel_elements_atom_link() {
+ $url_options = array('absolute' => TRUE);
+ $input = $this->view->get_exposed_input();
+ if ($input) {
+ $url_options['query'] = $input;
+ }
+ $url = url($this->view->get_url(), $url_options);
+
+ return array(
+ array(
+ 'namespace' => array('xmlns:atom' => 'http://www.w3.org/2005/Atom'),
+ 'key' => 'atom:link',
+ 'attributes' => array(
+ 'href' => $url,
+ 'rel' => 'self',
+ 'type' => 'application/rss+xml',
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Get RSS feed description.
+ *
+ * @return string
+ * The string containing the description with the tokens replaced.
+ */
+ function get_description() {
+ $description = $this->options['description'];
+
+ // Allow substitutions from the first row.
+ $description = $this->tokenize_value($description, 0);
+
+ return $description;
+ }
+
+ function render() {
+ if (empty($this->row_plugin)) {
+ vpr('views_plugin_style_default: Missing row plugin');
+ return;
+ }
+ $rows = '';
+
+ // This will be filled in by the row plugin and is used later on in the
+ // theming output.
+ $this->namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
+
+ // Fetch any additional elements for the channel and merge in their
+ // namespaces.
+ $this->channel_elements = array_merge(
+ $this->get_channel_elements(),
+ $this->get_channel_elements_atom_link()
+ );
+ foreach ($this->channel_elements as $element) {
+ if (isset($element['namespace'])) {
+ $this->namespaces = array_merge($this->namespaces, $element['namespace']);
+ }
+ }
+
+ foreach ($this->view->result as $row_index => $row) {
+ $this->view->row_index = $row_index;
+ $rows .= $this->row_plugin->render($row);
+ }
+
+ $output = theme($this->theme_functions(),
+ array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'rows' => $rows
+ ));
+ unset($this->view->row_index);
+ return $output;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_summary.inc b/sites/all/modules/views/plugins/views_plugin_style_summary.inc
new file mode 100644
index 000000000..5081dd62e
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_summary.inc
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains the default summary style plugin, which displays items in an HTML list.
+ */
+
+/**
+ * The default style plugin for summaries.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_summary extends views_plugin_style {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['base_path'] = array('default' => '');
+ $options['count'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['override'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['items_per_page'] = array('default' => 25);
+
+ return $options;
+ }
+
+ function query() {
+ if (!empty($this->options['override'])) {
+ $this->view->set_items_per_page(intval($this->options['items_per_page']));
+ }
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['base_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Base path'),
+ '#default_value' => $this->options['base_path'],
+ '#description' => t('Define the base path for links in this summary
+ view, i.e. http://example.com/<strong>your_view_path/archive</strong>.
+ Do not include beginning and ending forward slash. If this value
+ is empty, views will use the first path found as the base path,
+ in page displays, or / if no path could be found.'),
+ );
+ $form['count'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['count']),
+ '#title' => t('Display record count with link'),
+ );
+ $form['override'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['override']),
+ '#title' => t('Override number of items to display'),
+ );
+
+ $form['items_per_page'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Items to display'),
+ '#default_value' => $this->options['items_per_page'],
+ '#dependency' => array(
+ 'edit-options-summary-options-' . str_replace('_', '-', $this->definition['name']) . '-override' => array(1)
+ ),
+ );
+ }
+
+ function render() {
+ $rows = array();
+ foreach ($this->view->result as $row) {
+ // @todo: Include separator as an option.
+ $rows[] = $row;
+ }
+
+ return theme($this->theme_functions(), array(
+ 'view' => $this->view,
+ 'options' => $this->options,
+ 'rows' => $rows
+ ));
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_summary_jump_menu.inc b/sites/all/modules/views/plugins/views_plugin_style_summary_jump_menu.inc
new file mode 100644
index 000000000..a16a84b1a
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_summary_jump_menu.inc
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Contains the default summary style plugin, which displays items in an HTML list.
+ */
+
+/**
+ * The default style plugin for summaries.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_summary_jump_menu extends views_plugin_style {
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['base_path'] = array('default' => '');
+ $options['count'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['hide'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['text'] = array('default' => 'Go', 'translatable' => TRUE);
+ $options['label'] = array('default' => '', 'translatable' => TRUE);
+ $options['choose'] = array('default' => '- Choose -', 'translatable' => TRUE);
+ $options['inline'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['default_value'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function query() {
+ // Copy the offset option.
+ $pager = array(
+ 'type' => 'none',
+ 'options' => $this->display->handler->options['pager']['options'],
+ );
+ $this->display->handler->set_option('pager', $pager);
+ }
+
+ function options_form(&$form, &$form_state) {
+ $form['base_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Base path'),
+ '#default_value' => $this->options['base_path'],
+ '#description' => t('Define the base path for links in this summary
+ view, i.e. http://example.com/<strong>your_view_path/archive</strong>.
+ Do not include beginning and ending forward slash. If this value
+ is empty, views will use the first path found as the base path,
+ in page displays, or / if no path could be found.'),
+ );
+ $form['count'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['count']),
+ '#title' => t('Display record count with link'),
+ );
+
+ $form['hide'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Hide the "Go" button'),
+ '#default_value' => !empty($this->options['hide']),
+ '#description' => t('If hidden, this button will only be hidden for users with javascript and the page will automatically jump when the select is changed.'),
+ );
+
+ $form['text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Button text'),
+ '#default_value' => $this->options['text'],
+ );
+
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Selector label'),
+ '#default_value' => $this->options['label'],
+ '#description' => t('The text that will appear as the the label of the selector element. If blank no label tag will be used.'),
+ );
+
+ $form['choose'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Choose text'),
+ '#default_value' => $this->options['choose'],
+ '#description' => t('The text that will appear as the selected option in the jump menu.'),
+ );
+
+ $form['inline'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Set this field to display inline'),
+ '#default_value' => !empty($this->options['inline']),
+ );
+
+ $form['default_value'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Select the current contextual filter value'),
+ '#default_value' => !empty($this->options['default_value']),
+ '#description' => t('If checked, the current contextual filter value will be displayed as the default option in the jump menu, if applicable.'),
+ );
+ }
+
+ function render() {
+ $argument = $this->view->argument[$this->view->build_info['summary_level']];
+
+ $url_options = array();
+
+ if (!empty($this->view->exposed_raw_input)) {
+ $url_options['query'] = $this->view->exposed_raw_input;
+ }
+
+ $options = array();
+ $default_value = '';
+ $row_args = array();
+ foreach ($this->view->result as $id => $row) {
+ $row_args[$id] = $argument->summary_argument($row);
+ }
+ $argument->process_summary_arguments($row_args);
+
+ foreach ($this->view->result as $id => $row) {
+ $args = $this->view->args;
+ $args[$argument->position] = $row_args[$id];
+ $base_path = NULL;
+ if (!empty($argument->options['summary_options']['base_path'])) {
+ $base_path = $argument->options['summary_options']['base_path'];
+ }
+ $path = url($this->view->get_url($args, $base_path), $url_options);
+ $summary_value = strip_tags($argument->summary_name($row));
+ $key = md5($path . $summary_value) . "::" . $path;
+
+ $options[$key] = $summary_value;
+ if (!empty($this->options['count'])) {
+ $options[$key] .= ' (' . intval($row->{$argument->count_alias}) . ')';
+ }
+ if ($this->options['default_value'] && $_GET['q'] == $this->view->get_url($args)) {
+ $default_value = $key;
+ }
+ }
+
+ ctools_include('jump-menu');
+ $settings = array(
+ 'hide' => $this->options['hide'],
+ 'button' => $this->options['text'],
+ 'title' => $this->options['label'],
+ 'choose' => $this->options['choose'],
+ 'inline' => $this->options['inline'],
+ 'default_value' => $default_value,
+ );
+
+ $form = drupal_get_form('ctools_jump_menu', $options, $settings);
+ return drupal_render($form);
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_summary_unformatted.inc b/sites/all/modules/views/plugins/views_plugin_style_summary_unformatted.inc
new file mode 100644
index 000000000..fc4662456
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_summary_unformatted.inc
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains the unformatted summary style plugin.
+ */
+
+/**
+ * The default style plugin for summaries.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_summary_unformatted extends views_plugin_style_summary {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['inline'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['separator'] = array('default' => '');
+ return $options;
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $form['inline'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['inline']),
+ '#title' => t('Display items inline'),
+ );
+ $form['separator'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Separator'),
+ '#default_value' => $this->options['separator'],
+ );
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_plugin_style_table.inc b/sites/all/modules/views/plugins/views_plugin_style_table.inc
new file mode 100644
index 000000000..45ed97634
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_plugin_style_table.inc
@@ -0,0 +1,307 @@
+<?php
+
+/**
+ * @file
+ * Contains the table style plugin.
+ */
+
+/**
+ * Style plugin to render each item as a row in a table.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_plugin_style_table extends views_plugin_style {
+
+ /**
+ * Contains the current active sort column.
+ * @var string
+ */
+ public $active;
+
+ /**
+ * Contains the current active sort order, either desc or asc.
+ * @var string
+ */
+ public $order;
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['columns'] = array('default' => array());
+ $options['default'] = array('default' => '');
+ $options['info'] = array('default' => array());
+ $options['override'] = array('default' => TRUE, 'bool' => TRUE);
+ $options['sticky'] = array('default' => FALSE, 'bool' => TRUE);
+ $options['order'] = array('default' => 'asc');
+ $options['caption'] = array('default' => '', 'translatable' => TRUE);
+ $options['summary'] = array('default' => '', 'translatable' => TRUE);
+ $options['empty_table'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ /**
+ * Determine if we should provide sorting based upon $_GET inputs
+ *
+ * @return bool
+ */
+ function build_sort() {
+ if (!isset($_GET['order']) && ($this->options['default'] == -1 || empty($this->view->field[$this->options['default']]))) {
+ return TRUE;
+ }
+
+ // If a sort we don't know anything about gets through, exit gracefully.
+ if (isset($_GET['order']) && empty($this->view->field[$_GET['order']])) {
+ return TRUE;
+ }
+
+ // Let the builder know whether or not we're overriding the default sorts.
+ return empty($this->options['override']);
+ }
+
+ /**
+ * Add our actual sort criteria
+ */
+ function build_sort_post() {
+ if (!isset($_GET['order'])) {
+ // check for a 'default' clicksort. If there isn't one, exit gracefully.
+ if (empty($this->options['default'])) {
+ return;
+ }
+ $sort = $this->options['default'];
+ if (!empty($this->options['info'][$sort]['default_sort_order'])) {
+ $this->order = $this->options['info'][$sort]['default_sort_order'];
+ }
+ else {
+ $this->order = !empty($this->options['order']) ? $this->options['order'] : 'asc';
+ }
+ }
+ else {
+ $sort = $_GET['order'];
+ // Store the $order for later use.
+ $this->order = !empty($_GET['sort']) ? strtolower($_GET['sort']) : 'asc';
+ }
+
+ // If a sort we don't know anything about gets through, exit gracefully.
+ if (empty($this->view->field[$sort])) {
+ return;
+ }
+
+ // Ensure $this->order is valid.
+ if ($this->order != 'asc' && $this->order != 'desc') {
+ $this->order = 'asc';
+ }
+
+ // Store the $sort for later use.
+ $this->active = $sort;
+
+ // Tell the field to click sort.
+ $this->view->field[$sort]->click_sort($this->order);
+ }
+
+ /**
+ * Normalize a list of columns based upon the fields that are
+ * available. This compares the fields stored in the style handler
+ * to the list of fields actually in the view, removing fields that
+ * have been removed and adding new fields in their own column.
+ *
+ * - Each field must be in a column.
+ * - Each column must be based upon a field, and that field
+ * is somewhere in the column.
+ * - Any fields not currently represented must be added.
+ * - Columns must be re-ordered to match the fields.
+ *
+ * @param $columns
+ * An array of all fields; the key is the id of the field and the
+ * value is the id of the column the field should be in.
+ * @param $fields
+ * The fields to use for the columns. If not provided, they will
+ * be requested from the current display. The running render should
+ * send the fields through, as they may be different than what the
+ * display has listed due to access control or other changes.
+ *
+ * @return array
+ * An array of all the sanitized columns.
+ */
+ function sanitize_columns($columns, $fields = NULL) {
+ $sanitized = array();
+ if ($fields === NULL) {
+ $fields = $this->display->handler->get_option('fields');
+ }
+ // Preconfigure the sanitized array so that the order is retained.
+ foreach ($fields as $field => $info) {
+ // Set to itself so that if it isn't touched, it gets column
+ // status automatically.
+ $sanitized[$field] = $field;
+ }
+
+ foreach ($columns as $field => $column) {
+ // first, make sure the field still exists.
+ if (!isset($sanitized[$field])) {
+ continue;
+ }
+
+ // If the field is the column, mark it so, or the column
+ // it's set to is a column, that's ok
+ if ($field == $column || $columns[$column] == $column && !empty($sanitized[$column])) {
+ $sanitized[$field] = $column;
+ }
+ // Since we set the field to itself initially, ignoring
+ // the condition is ok; the field will get its column
+ // status back.
+ }
+
+ return $sanitized;
+ }
+
+ /**
+ * Render the given style.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ $handlers = $this->display->handler->get_handlers('field');
+ if (empty($handlers)) {
+ $form['error_markup'] = array(
+ '#markup' => '<div class="error messages">' . t('You need at least one field before you can configure your table settings') . '</div>',
+ );
+ return;
+ }
+
+ $form['override'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Override normal sorting if click sorting is used'),
+ '#default_value' => !empty($this->options['override']),
+ );
+
+ $form['sticky'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable Drupal style "sticky" table headers (Javascript)'),
+ '#default_value' => !empty($this->options['sticky']),
+ '#description' => t('(Sticky header effects will not be active for preview below, only on live output.)'),
+ );
+
+ $form['caption'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Short description of table'),
+ '#description' => t('Include a caption for better accessibility of your table.'),
+ '#default_value' => $this->options['caption'],
+ '#maxlength' => 255,
+ );
+
+ $form['summary'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Table summary'),
+ '#description' => t('This value will be displayed as table-summary attribute in the html. Use this to give a summary of complex tables.'),
+ '#default_value' => $this->options['summary'],
+ '#maxlength' => 255,
+ );
+
+ // Note: views UI registers this theme handler on our behalf. Your module
+ // will have to register your theme handlers if you do stuff like this.
+ $form['#theme'] = 'views_ui_style_plugin_table';
+
+ $columns = $this->sanitize_columns($this->options['columns']);
+
+ // Create an array of allowed columns from the data we know:
+ $field_names = $this->display->handler->get_field_labels();
+
+ if (isset($this->options['default'])) {
+ $default = $this->options['default'];
+ if (!isset($columns[$default])) {
+ $default = -1;
+ }
+ }
+ else {
+ $default = -1;
+ }
+
+ foreach ($columns as $field => $column) {
+ $safe = str_replace(array('][', '_', ' '), '-', $field);
+ // the $id of the column for dependency checking.
+ $id = 'edit-style-options-columns-' . $safe;
+
+ $form['columns'][$field] = array(
+ '#type' => 'select',
+ '#options' => $field_names,
+ '#default_value' => $column,
+ );
+ if ($handlers[$field]->click_sortable()) {
+ $form['info'][$field]['sortable'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => !empty($this->options['info'][$field]['sortable']),
+ '#dependency' => array($id => array($field)),
+ );
+ $form['info'][$field]['default_sort_order'] = array(
+ '#type' => 'select',
+ '#options' => array('asc' => t('Ascending'), 'desc' => t('Descending')),
+ '#default_value' => !empty($this->options['info'][$field]['default_sort_order']) ? $this->options['info'][$field]['default_sort_order'] : 'asc',
+ '#dependency_count' => 2,
+ '#dependency' => array($id => array($field), 'edit-style-options-info-' . $safe . '-sortable' => array(1)),
+ );
+ // Provide an ID so we can have such things.
+ $radio_id = drupal_html_id('edit-default-' . $field);
+ $form['default'][$field] = array(
+ '#type' => 'radio',
+ '#return_value' => $field,
+ '#parents' => array('style_options', 'default'),
+ '#id' => $radio_id,
+ // because 'radio' doesn't fully support '#id' =(
+ '#attributes' => array('id' => $radio_id),
+ '#default_value' => $default,
+ '#dependency' => array($id => array($field)),
+ );
+ }
+ $form['info'][$field]['align'] = array(
+ '#type' => 'select',
+ '#default_value' => !empty($this->options['info'][$field]['align']) ? $this->options['info'][$field]['align'] : '',
+ '#options' => array(
+ '' => t('None'),
+ 'views-align-left' => t('Left'),
+ 'views-align-center' => t('Center'),
+ 'views-align-right' => t('Right'),
+ ),
+ '#dependency' => array($id => array($field)),
+ );
+ $form['info'][$field]['separator'] = array(
+ '#type' => 'textfield',
+ '#size' => 10,
+ '#default_value' => isset($this->options['info'][$field]['separator']) ? $this->options['info'][$field]['separator'] : '',
+ '#dependency' => array($id => array($field)),
+ );
+ $form['info'][$field]['empty_column'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => isset($this->options['info'][$field]['empty_column']) ? $this->options['info'][$field]['empty_column'] : FALSE,
+ '#dependency' => array($id => array($field)),
+ );
+
+ // markup for the field name
+ $form['info'][$field]['name'] = array(
+ '#markup' => $field_names[$field],
+ );
+ }
+
+ // Provide a radio for no default sort
+ $form['default'][-1] = array(
+ '#type' => 'radio',
+ '#return_value' => -1,
+ '#parents' => array('style_options', 'default'),
+ '#id' => 'edit-default-0',
+ '#default_value' => $default,
+ );
+
+ $form['empty_table'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show the empty text in the table'),
+ '#default_value' => $this->options['empty_table'],
+ '#description' => t('Per default the table is hidden for an empty view. With this option it is posible to show an empty table with the text in it.'),
+ );
+
+ $form['description_markup'] = array(
+ '#markup' => '<div class="description form-item">' . t('Place fields into columns; you may combine multiple fields into the same column. If you do, the separator in the column specified will be used to separate the fields. Check the sortable box to make that column click sortable, and check the default sort radio to determine which column will be sorted by default, if any. You may control column order and field labels in the fields section.') . '</div>',
+ );
+ }
+
+ function even_empty() {
+ return parent::even_empty() || !empty($this->options['empty_table']);
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/comment.inc b/sites/all/modules/views/plugins/views_wizard/comment.inc
new file mode 100644
index 000000000..a91078849
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/comment.inc
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Views wizard for comment views.
+ */
+
+// Parent plugin.
+if (module_exists('comment')) {
+ $plugin = array(
+ 'name' => 'comment',
+ 'base_table' => 'comment',
+ 'created_column' => 'created',
+ 'form_wizard_class' => array(
+ 'file' => 'views_ui_comment_views_wizard.class.php',
+ 'class' => 'ViewsUiCommentViewsWizard',
+ ),
+ 'title' => t('Comments'),
+ 'filters' => array(
+ 'status' => array(
+ 'value' => COMMENT_PUBLISHED,
+ 'table' => 'comment',
+ 'field' => 'status',
+ ),
+ 'status_node' => array(
+ 'value' => NODE_PUBLISHED,
+ 'table' => 'node',
+ 'field' => 'status',
+ 'relationship' => 'nid',
+ ),
+ ),
+ 'path_field' => array(
+ 'id' => 'cid',
+ 'table' => 'comment',
+ 'field' => 'cid',
+ 'exclude' => TRUE,
+ 'link_to_comment' => FALSE,
+ 'alter' => array(
+ 'alter_text' => 1,
+ 'text' => 'comment/[cid]#comment-[cid]',
+ ),
+ ),
+ );
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/file_managed.inc b/sites/all/modules/views/plugins/views_wizard/file_managed.inc
new file mode 100644
index 000000000..049ce1b52
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/file_managed.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Views wizard for managed file views.
+ */
+
+$plugin = array(
+ 'name' => 'file_managed',
+ 'base_table' => 'file_managed',
+ 'created_column' => 'timestamp',
+ 'form_wizard_class' => array(
+ 'file' => 'views_ui_file_managed_views_wizard.class.php',
+ 'class' => 'ViewsUiFileManagedViewsWizard',
+ ),
+ 'title' => t('Files'),
+ 'filters' => array(
+ ),
+ 'path_field' => array(
+ 'id' => 'uri',
+ 'table' => 'file_managed',
+ 'field' => 'uri',
+ 'exclude' => TRUE,
+ 'file_download_path' => TRUE,
+ ),
+);
diff --git a/sites/all/modules/views/plugins/views_wizard/node.inc b/sites/all/modules/views/plugins/views_wizard/node.inc
new file mode 100644
index 000000000..ccca48dec
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/node.inc
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Views wizard for node views.
+ */
+
+$plugin = array(
+ 'name' => 'node',
+ 'base_table' => 'node',
+ 'created_column' => 'created',
+ 'available_sorts' => array(
+ 'title:DESC' => t('Title')
+ ),
+ 'form_wizard_class' => array(
+ 'file' => 'views_ui_node_views_wizard.class.php',
+ 'class' => 'ViewsUiNodeViewsWizard',
+ ),
+ 'title' => t('Content'),
+ 'filters' => array(
+ 'status' => array(
+ 'value' => NODE_PUBLISHED,
+ 'table' => 'node',
+ 'field' => 'status',
+ ),
+ ),
+ 'path_field' => array(
+ 'id' => 'nid',
+ 'table' => 'node',
+ 'field' => 'nid',
+ 'exclude' => TRUE,
+ 'link_to_node' => FALSE,
+ 'alter' => array(
+ 'alter_text' => 1,
+ 'text' => 'node/[nid]',
+ ),
+ ),
+);
+
+if (module_exists('statistics')) {
+ $plugin['available_sorts']['node_counter-totalcount:DESC'] = t('Number of hits');
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/node_revision.inc b/sites/all/modules/views/plugins/views_wizard/node_revision.inc
new file mode 100644
index 000000000..ddf1d61ce
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/node_revision.inc
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Views wizard for node revision views.
+ */
+
+$plugin = array(
+ 'name' => 'node_revision',
+ 'base_table' => 'node_revision',
+ 'created_column' => 'timestamp',
+ 'form_wizard_class' => array(
+ 'file' => 'views_ui_node_revision_views_wizard.class.php',
+ 'class' => 'ViewsUiNodeRevisionViewsWizard',
+ ),
+ 'title' => t('Content revisions'),
+ 'filters' => array(
+ 'status' => array(
+ 'value' => '1',
+ 'table' => 'node', // @todo - unclear if this should be node or node_revision
+ 'field' => 'status',
+ ),
+ ),
+ 'path_field' => array(
+ 'id' => 'vid',
+ 'table' => 'node_revision',
+ 'field' => 'vid',
+ 'exclude' => TRUE,
+ 'alter' => array(
+ 'alter_text' => 1,
+ 'text' => 'node/[nid]/revisions/[vid]/view',
+ ),
+ ),
+ 'path_fields_supplemental' => array(
+ array(
+ 'id' => 'nid',
+ 'table' => 'node',
+ 'field' => 'nid',
+ 'exclude' => TRUE,
+ 'link_to_node' => FALSE,
+ ),
+ ),
+);
diff --git a/sites/all/modules/views/plugins/views_wizard/taxonomy_term.inc b/sites/all/modules/views/plugins/views_wizard/taxonomy_term.inc
new file mode 100644
index 000000000..599e35436
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/taxonomy_term.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Views wizard for taxonomy term views.
+ */
+
+if (module_exists('taxonomy')) {
+ $plugin = array(
+ 'name' => 'taxonomy_term',
+ 'base_table' => 'taxonomy_term_data',
+ 'form_wizard_class' => array(
+ 'file' => 'views_ui_taxonomy_term_views_wizard.class.php',
+ 'class' => 'ViewsUiTaxonomyTermViewsWizard',
+ ),
+ 'title' => t('Taxonomy terms'),
+ 'filters' => array(
+ ),
+ 'path_field' => array(
+ 'id' => 'tid',
+ 'table' => 'taxonomy_term_data',
+ 'field' => 'tid',
+ 'exclude' => TRUE,
+ 'alter' => array(
+ 'alter_text' => 1,
+ 'text' => 'taxonomy/term/[tid]',
+ ),
+ ),
+ );
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/users.inc b/sites/all/modules/views/plugins/views_wizard/users.inc
new file mode 100644
index 000000000..176a9e1ee
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/users.inc
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Views wizard for user views.
+ */
+
+$plugin = array(
+ 'name' => 'users',
+ 'base_table' => 'users',
+ 'created_column' => 'created',
+ 'form_wizard_class' => array(
+ 'file' => 'views_ui_users_views_wizard.class.php',
+ 'class' => 'ViewsUiUsersViewsWizard',
+ ),
+ 'title' => t('Users'),
+ 'filters' => array(
+ 'status' => array(
+ 'value' => '1',
+ 'table' => 'users',
+ 'field' => 'status',
+ ),
+ ),
+ 'path_field' => array(
+ 'id' => 'uid',
+ 'table' => 'users',
+ 'field' => 'uid',
+ 'exclude' => TRUE,
+ 'link_to_user' => FALSE,
+ 'alter' => array(
+ 'alter_text' => 1,
+ 'text' => 'user/[uid]',
+ ),
+ ),
+);
diff --git a/sites/all/modules/views/plugins/views_wizard/views_ui_base_views_wizard.class.php b/sites/all/modules/views/plugins/views_wizard/views_ui_base_views_wizard.class.php
new file mode 100644
index 000000000..8893ab817
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/views_ui_base_views_wizard.class.php
@@ -0,0 +1,929 @@
+<?php
+
+/**
+ * @file
+ * Provides the interface and base class for Views Wizard plugins.
+ */
+
+/**
+ * Defines a common interface for Views Wizard plugins.
+ */
+interface ViewsWizardInterface {
+ function __construct($plugin);
+
+ /**
+ * For AJAX callbacks to build other elements in the "show" form.
+ */
+ function build_form($form, &$form_state);
+
+ /**
+ * Validate form and values.
+ *
+ * @return an array of form errors.
+ */
+ function validate($form, &$form_state);
+
+ /**
+ * Create a new View from form values.
+ *
+ * @return a view object.
+ *
+ * @throws ViewsWizardException in the event of a problem.
+ */
+ function create_view($form, &$form_state);
+}
+
+/**
+ * A custom exception class for our errors.
+ */
+class ViewsWizardException extends Exception {
+}
+
+/**
+ * A very generic Views Wizard class - can be constructed for any base table.
+ */
+class ViewsUiBaseViewsWizard implements ViewsWizardInterface {
+ protected $base_table;
+ protected $entity_type;
+ protected $entity_info = array();
+ protected $validated_views = array();
+ protected $plugin = array();
+ protected $filter_defaults = array(
+ 'id' => NULL,
+ 'expose' => array('operator' => FALSE),
+ 'group' => 1,
+ );
+
+ function __construct($plugin) {
+ $this->base_table = $plugin['base_table'];
+ $default = $this->filter_defaults;
+
+ if (isset($plugin['filters'])) {
+ foreach ($plugin['filters'] as $name => $info) {
+ $default['id'] = $name;
+ $plugin['filters'][$name] = $info + $default;
+ }
+ }
+
+ $this->plugin = $plugin;
+
+ $entities = entity_get_info();
+ foreach ($entities as $entity_type => $entity_info) {
+ if (isset($entity_info['base table']) && $this->base_table == $entity_info['base table']) {
+ $this->entity_info = $entity_info;
+ $this->entity_type = $entity_type;
+ }
+ }
+ }
+
+ function build_form($form, &$form_state) {
+ $style_options = views_fetch_plugin_names('style', 'normal', array($this->base_table));
+ $feed_row_options = views_fetch_plugin_names('row', 'feed', array($this->base_table));
+ $path_prefix = url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=');
+
+ // Add filters and sorts which apply to the view as a whole.
+ $this->build_filters($form, $form_state);
+ $this->build_sorts($form, $form_state);
+
+ $form['displays']['page'] = array(
+ '#type' => 'fieldset',
+ '#attributes' => array('class' => array('views-attachment', 'fieldset-no-legend'),),
+ '#tree' => TRUE,
+ );
+ $form['displays']['page']['create'] = array(
+ '#title' => t('Create a page'),
+ '#type' => 'checkbox',
+ '#attributes' => array('class' => array('strong')),
+ '#default_value' => TRUE,
+ '#id' => 'edit-page-create',
+ );
+
+ // All options for the page display are included in this container so they
+ // can be hidden en masse when the "Create a page" checkbox is unchecked.
+ $form['displays']['page']['options'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('options-set'),),
+ '#dependency' => array(
+ 'edit-page-create' => array(1),
+ ),
+ '#pre_render' => array('ctools_dependent_pre_render'),
+ '#prefix' => '<div><div id="edit-page-wrapper">',
+ '#suffix' => '</div></div>',
+ '#parents' => array('page'),
+ );
+
+ $form['displays']['page']['options']['title'] = array(
+ '#title' => t('Page title'),
+ '#type' => 'textfield',
+ );
+ $form['displays']['page']['options']['path'] = array(
+ '#title' => t('Path'),
+ '#type' => 'textfield',
+ '#field_prefix' => $path_prefix,
+ );
+ $form['displays']['page']['options']['style'] = array(
+ '#type' => 'fieldset',
+ '#attributes' => array('class' => array('container-inline', 'fieldset-no-legend')),
+ );
+
+ // Create the dropdown for choosing the display format.
+ $form['displays']['page']['options']['style']['style_plugin'] = array(
+ '#title' => t('Display format'),
+ '#help_topic' => 'style',
+ '#type' => 'select',
+ '#options' => $style_options,
+ );
+ $style_form = &$form['displays']['page']['options']['style'];
+ $style_form['style_plugin']['#default_value'] = views_ui_get_selected($form_state, array('page', 'style', 'style_plugin'), 'default', $style_form['style_plugin']);
+ // Changing this dropdown updates $form['displays']['page']['options'] via
+ // AJAX.
+ views_ui_add_ajax_trigger($style_form, 'style_plugin', array('displays', 'page', 'options'));
+
+ $this->build_form_style($form, $form_state, 'page');
+ $form['displays']['page']['options']['items_per_page'] = array(
+ '#title' => t('Items to display'),
+ '#type' => 'textfield',
+ '#default_value' => '10',
+ '#size' => 5,
+ '#element_validate' => array('views_element_validate_integer'),
+ );
+ $form['displays']['page']['options']['pager'] = array(
+ '#title' => t('Use a pager'),
+ '#type' => 'checkbox',
+ '#default_value' => TRUE,
+ );
+ $form['displays']['page']['options']['link'] = array(
+ '#title' => t('Create a menu link'),
+ '#type' => 'checkbox',
+ '#id' => 'edit-page-link',
+ );
+ $form['displays']['page']['options']['link_properties'] = array(
+ '#type' => 'container',
+ '#dependency' => array(
+ 'edit-page-link' => array(1),
+ ),
+ '#pre_render' => array('ctools_dependent_pre_render'),
+ '#prefix' => '<div id="edit-page-link-properties-wrapper">',
+ '#suffix' => '</div>',
+ );
+ if (module_exists('menu')) {
+ $menu_options = menu_get_menus();
+ }
+ else {
+ // These are not yet translated.
+ $menu_options = menu_list_system_menus();
+ foreach ($menu_options as $name => $title) {
+ $menu_options[$name] = t($title);
+ }
+ }
+ $form['displays']['page']['options']['link_properties']['menu_name'] = array(
+ '#title' => t('Menu'),
+ '#type' => 'select',
+ '#options' => $menu_options,
+ );
+ $form['displays']['page']['options']['link_properties']['title'] = array(
+ '#title' => t('Link text'),
+ '#type' => 'textfield',
+ );
+ // Only offer a feed if we have at least one available feed row style.
+ if ($feed_row_options) {
+ $form['displays']['page']['options']['feed'] = array(
+ '#title' => t('Include an RSS feed'),
+ '#type' => 'checkbox',
+ '#id' => 'edit-page-feed',
+ );
+ $form['displays']['page']['options']['feed_properties'] = array(
+ '#type' => 'container',
+ '#dependency' => array(
+ 'edit-page-feed' => array(1),
+ ),
+ '#pre_render' => array('ctools_dependent_pre_render'),
+ '#prefix' => '<div id="edit-page-feed-properties-wrapper">',
+ '#suffix' => '</div>',
+ );
+ $form['displays']['page']['options']['feed_properties']['path'] = array(
+ '#title' => t('Feed path'),
+ '#type' => 'textfield',
+ '#field_prefix' => $path_prefix,
+ );
+ // This will almost never be visible.
+ $form['displays']['page']['options']['feed_properties']['row_plugin'] = array(
+ '#title' => t('Feed row style'),
+ '#type' => 'select',
+ '#options' => $feed_row_options,
+ '#default_value' => key($feed_row_options),
+ '#access' => (count($feed_row_options) > 1),
+ '#dependency' => array(
+ 'edit-page-feed' => array(1),
+ ),
+ '#pre_render' => array('ctools_dependent_pre_render'),
+ '#prefix' => '<div id="edit-page-feed-properties-row-plugin-wrapper">',
+ '#suffix' => '</div>',
+ );
+ }
+
+ $form['displays']['block'] = array(
+ '#type' => 'fieldset',
+ '#attributes' => array('class' => array('views-attachment', 'fieldset-no-legend'),),
+ '#tree' => TRUE,
+ );
+ $form['displays']['block']['create'] = array(
+ '#title' => t('Create a block'),
+ '#type' => 'checkbox',
+ '#attributes' => array('class' => array('strong')),
+ '#id' => 'edit-block-create',
+ );
+
+ // All options for the block display are included in this container so they
+ // can be hidden en masse when the "Create a block" checkbox is unchecked.
+ $form['displays']['block']['options'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('options-set'),),
+ '#dependency' => array(
+ 'edit-block-create' => array(1),
+ ),
+ '#pre_render' => array('ctools_dependent_pre_render'),
+ '#prefix' => '<div id="edit-block-wrapper">',
+ '#suffix' => '</div>',
+ '#parents' => array('block'),
+ );
+
+ $form['displays']['block']['options']['title'] = array(
+ '#title' => t('Block title'),
+ '#type' => 'textfield',
+ );
+ $form['displays']['block']['options']['style'] = array(
+ '#type' => 'fieldset',
+ '#attributes' => array('class' => array('container-inline', 'fieldset-no-legend')),
+ );
+
+ // Create the dropdown for choosing the display format.
+ $form['displays']['block']['options']['style']['style_plugin'] = array(
+ '#title' => t('Display format'),
+ '#help_topic' => 'style',
+ '#type' => 'select',
+ '#options' => $style_options,
+ );
+ $style_form = &$form['displays']['block']['options']['style'];
+ $style_form['style_plugin']['#default_value'] = views_ui_get_selected($form_state, array('block', 'style', 'style_plugin'), 'default', $style_form['style_plugin']);
+ // Changing this dropdown updates $form['displays']['block']['options'] via
+ // AJAX.
+ views_ui_add_ajax_trigger($style_form, 'style_plugin', array('displays', 'block', 'options'));
+
+ $this->build_form_style($form, $form_state, 'block');
+ $form['displays']['block']['options']['items_per_page'] = array(
+ '#title' => t('Items per page'),
+ '#type' => 'textfield',
+ '#default_value' => '5',
+ '#size' => 5,
+ '#element_validate' => array('views_element_validate_integer'),
+ );
+ $form['displays']['block']['options']['pager'] = array(
+ '#title' => t('Use a pager'),
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ );
+
+ return $form;
+ }
+
+ /**
+ * Build the part of the form that builds the display format options.
+ */
+ protected function build_form_style(&$form, &$form_state, $type) {
+ $style_form =& $form['displays'][$type]['options']['style'];
+ $style = $style_form['style_plugin']['#default_value'];
+ $style_plugin = views_get_plugin('style', $style);
+ if (isset($style_plugin) && $style_plugin->uses_row_plugin()) {
+ $options = $this->row_style_options($type);
+ $style_form['row_plugin'] = array(
+ '#type' => 'select',
+ '#title' => t('of'),
+ '#options' => $options,
+ '#access' => count($options) > 1,
+ );
+ // For the block display, the default value should be "titles (linked)",
+ // if it's available (since that's the most common use case).
+ $block_with_linked_titles_available = ($type == 'block' && isset($options['titles_linked']));
+ $default_value = $block_with_linked_titles_available ? 'titles_linked' : key($options);
+ $style_form['row_plugin']['#default_value'] = views_ui_get_selected($form_state, array($type, 'style', 'row_plugin'), $default_value, $style_form['row_plugin']);
+ // Changing this dropdown updates the individual row options via AJAX.
+ views_ui_add_ajax_trigger($style_form, 'row_plugin', array('displays', $type, 'options', 'style', 'row_options'));
+
+ // This is the region that can be updated by AJAX. The base class doesn't
+ // add anything here, but child classes can.
+ $style_form['row_options'] = array(
+ '#theme_wrappers' => array('container'),
+ );
+ }
+ elseif ($style_plugin->uses_fields()) {
+ $style_form['row_plugin'] = array('#markup' => '<span>' . t('of fields') . '</span>');
+ }
+ }
+
+ /**
+ * Add possible row style options.
+ *
+ * Per default use fields with base field.
+ */
+ protected function row_style_options($type) {
+ $data = views_fetch_data($this->base_table);
+ // Get all available row plugins by default.
+ $options = views_fetch_plugin_names('row', 'normal', array($this->base_table));
+ return $options;
+ }
+
+ /**
+ * Build the part of the form that allows the user to select the view's filters.
+ *
+ * By default, this adds "of type" and "tagged with" filters (when they are
+ * available).
+ */
+ protected function build_filters(&$form, &$form_state) {
+ // Find all the fields we are allowed to filter by.
+ $fields = views_fetch_fields($this->base_table, 'filter');
+
+ $entity_info = $this->entity_info;
+ // If the current base table support bundles and has more than one (like user).
+ if (isset($entity_info['bundle keys']) && isset($entity_info['bundles'])) {
+ // Get all bundles and their human readable names.
+ $options = array('all' => t('All'));
+ foreach ($entity_info['bundles'] as $type => $bundle) {
+ $options[$type] = $bundle['label'];
+ }
+ $form['displays']['show']['type'] = array(
+ '#type' => 'select',
+ '#title' => t('of type'),
+ '#options' => $options,
+ );
+ $selected_bundle = views_ui_get_selected($form_state, array('show', 'type'), 'all', $form['displays']['show']['type']);
+ $form['displays']['show']['type']['#default_value'] = $selected_bundle;
+ // Changing this dropdown updates the entire content of $form['displays']
+ // via AJAX, since each bundle might have entirely different fields
+ // attached to it, etc.
+ views_ui_add_ajax_trigger($form['displays']['show'], 'type', array('displays'));
+ }
+
+ // Check if we are allowed to filter by taxonomy, and if so, add the
+ // "tagged with" filter to the view.
+ //
+ // We construct this filter using taxonomy_index.tid (which limits the
+ // filtering to a specific vocabulary) rather than taxonomy_term_data.name
+ // (which matches terms in any vocabulary). This is because it is a more
+ // commonly-used filter that works better with the autocomplete UI, and
+ // also to avoid confusion with other vocabularies on the site that may
+ // have terms with the same name but are not used for free tagging.
+ //
+ // The downside is that if there *is* more than one vocabulary on the site
+ // that is used for free tagging, the wizard will only be able to make the
+ // "tagged with" filter apply to one of them (see below for the method it
+ // uses to choose).
+ if (isset($fields['taxonomy_index.tid'])) {
+ // Check if this view will be displaying fieldable entities.
+ if (!empty($entity_info['fieldable'])) {
+ // Find all "tag-like" taxonomy fields associated with the view's
+ // entities. If a particular entity type (i.e., bundle) has been
+ // selected above, then we only search for taxonomy fields associated
+ // with that bundle. Otherwise, we use all bundles.
+ $bundles = array_keys($entity_info['bundles']);
+ // Double check that this is a real bundle before using it (since above
+ // we added a dummy option 'all' to the bundle list on the form).
+ if (isset($selected_bundle) && in_array($selected_bundle, $bundles)) {
+ $bundles = array($selected_bundle);
+ }
+ $tag_fields = array();
+ foreach ($bundles as $bundle) {
+ foreach (field_info_instances($this->entity_type, $bundle) as $instance) {
+ // We define "tag-like" taxonomy fields as ones that use the
+ // "Autocomplete term widget (tagging)" widget.
+ if ($instance['widget']['type'] == 'taxonomy_autocomplete') {
+ $tag_fields[] = $instance['field_name'];
+ }
+ }
+ }
+ $tag_fields = array_unique($tag_fields);
+ if (!empty($tag_fields)) {
+ // If there is more than one "tag-like" taxonomy field available to
+ // the view, we can only make our filter apply to one of them (as
+ // described above). We choose 'field_tags' if it is available, since
+ // that is created by the Standard install profile in core and also
+ // commonly used by contrib modules; thus, it is most likely to be
+ // associated with the "main" free-tagging vocabulary on the site.
+ if (in_array('field_tags', $tag_fields)) {
+ $tag_field_name = 'field_tags';
+ }
+ else {
+ $tag_field_name = reset($tag_fields);
+ }
+ // Add the autocomplete textfield to the wizard.
+ $form['displays']['show']['tagged_with'] = array(
+ '#type' => 'textfield',
+ '#title' => t('tagged with'),
+ '#autocomplete_path' => 'taxonomy/autocomplete/' . $tag_field_name,
+ '#size' => 30,
+ '#maxlength' => 1024,
+ '#field_name' => $tag_field_name,
+ '#element_validate' => array('views_ui_taxonomy_autocomplete_validate'),
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Build the part of the form that allows the user to select the view's sort order.
+ *
+ * By default, this adds a "sorted by [date]" filter (when it is available).
+ */
+ protected function build_sorts(&$form, &$form_state) {
+ $sorts = array(
+ 'none' => t('Unsorted'),
+ );
+ // Check if we are allowed to sort by creation date.
+ if (!empty($this->plugin['created_column'])) {
+ $sorts += array(
+ $this->plugin['created_column'] . ':DESC' => t('Newest first'),
+ $this->plugin['created_column'] . ':ASC' => t('Oldest first'),
+ );
+ }
+ if (isset($this->plugin['available_sorts'])) {
+ $sorts += $this->plugin['available_sorts'];
+ }
+
+ // If there is no sorts option available continue.
+ if (!empty($sorts)) {
+ $form['displays']['show']['sort'] = array(
+ '#type' => 'select',
+ '#title' => t('sorted by'),
+ '#options' => $sorts,
+ '#default_value' => isset($this->plugin['created_column']) ? $this->plugin['created_column'] . ':DESC' : 'none',
+ );
+ }
+ }
+
+ protected function instantiate_view($form, &$form_state) {
+ // Build the basic view properties.
+ $view = views_new_view();
+ $view->name = $form_state['values']['name'];
+ $view->human_name = $form_state['values']['human_name'];
+ $view->description = $form_state['values']['description'];
+ $view->tag = 'default';
+ $view->core = VERSION;
+ $view->base_table = $this->base_table;
+
+ // Build all display options for this view.
+ $display_options = $this->build_display_options($form, $form_state);
+
+ // Allow the fully built options to be altered. This happens before adding
+ // the options to the view, so that once they are eventually added we will
+ // be able to get all the overrides correct.
+ $this->alter_display_options($display_options, $form, $form_state);
+
+ $this->add_displays($view, $display_options, $form, $form_state);
+
+ return $view;
+ }
+
+ /**
+ * Build an array of display options for the view.
+ *
+ * @return
+ * An array whose keys are the names of each display and whose values are
+ * arrays of options for that display.
+ */
+ protected function build_display_options($form, $form_state) {
+ // Display: Master
+ $display_options['default'] = $this->default_display_options($form, $form_state);
+ $display_options['default'] += array(
+ 'filters' => array(),
+ 'sorts' => array(),
+ );
+ $display_options['default']['filters'] += $this->default_display_filters($form, $form_state);
+ $display_options['default']['sorts'] += $this->default_display_sorts($form, $form_state);
+
+ // Display: Page
+ if (!empty($form_state['values']['page']['create'])) {
+ $display_options['page'] = $this->page_display_options($form, $form_state);
+
+ // Display: Feed (attached to the page)
+ if (!empty($form_state['values']['page']['feed'])) {
+ $display_options['feed'] = $this->page_feed_display_options($form, $form_state);
+ }
+ }
+
+ // Display: Block
+ if (!empty($form_state['values']['block']['create'])) {
+ $display_options['block'] = $this->block_display_options($form, $form_state);
+ }
+
+ return $display_options;
+ }
+
+ /**
+ * Alter the full array of display options before they are added to the view.
+ */
+ protected function alter_display_options(&$display_options, $form, $form_state) {
+ // If any of the displays use jump menus, we want to add fields to the view
+ // that store the path that will be used in the jump menu. The fields to
+ // use for this are defined by the plugin.
+ if (isset($this->plugin['path_field'])) {
+ $path_field = $this->plugin['path_field'];
+ $path_fields_added = FALSE;
+ foreach ($display_options as $display_type => $options) {
+ if (!empty($options['style_plugin']) && $options['style_plugin'] == 'jump_menu') {
+ // Regardless of how many displays have jump menus, we only need to
+ // add a single set of path fields to the view.
+ if (!$path_fields_added) {
+ // The plugin might provide supplemental fields that it needs to
+ // generate the path (for example, node revisions need the node ID
+ // as well as the revision ID). We need to add these first so they
+ // are available as replacement patterns in the main path field.
+ $path_fields = !empty($this->plugin['path_fields_supplemental']) ? $this->plugin['path_fields_supplemental'] : array();
+ $path_fields[] = &$path_field;
+
+ // Generate a unique ID for each field so we don't overwrite
+ // existing ones.
+ foreach ($path_fields as &$field) {
+ $field['id'] = view::generate_item_id($field['id'], $display_options['default']['fields']);
+ $display_options['default']['fields'][$field['id']] = $field;
+ }
+
+ $path_fields_added = TRUE;
+ }
+
+ // Configure the style plugin to use the path field to generate the
+ // jump menu path.
+ $display_options[$display_type]['style_options']['path'] = $path_field['id'];
+ }
+ }
+ }
+
+ // If any of the displays use the table style, take sure that the fields
+ // always have a labels by unsetting the override.
+ foreach ($display_options as &$options) {
+ if ($options['style_plugin'] == 'table') {
+ foreach ($display_options['default']['fields'] as &$field) {
+ unset($field['label']);
+ }
+ }
+ }
+ }
+
+ /**
+ * Add the array of display options to the view, with appropriate overrides.
+ */
+ protected function add_displays($view, $display_options, $form, $form_state) {
+ // Display: Master
+ $default_display = $view->new_display('default', 'Master', 'default');
+ foreach ($display_options['default'] as $option => $value) {
+ $default_display->set_option($option, $value);
+ }
+
+ // Display: Page
+ if (isset($display_options['page'])) {
+ $display = $view->new_display('page', 'Page', 'page');
+ // The page display is usually the main one (from the user's point of
+ // view). Its options should therefore become the overall view defaults,
+ // so that new displays which are added later automatically inherit them.
+ $this->set_default_options($display_options['page'], $display, $default_display);
+
+ // Display: Feed (attached to the page)
+ if (isset($display_options['feed'])) {
+ $display = $view->new_display('feed', 'Feed', 'feed');
+ $this->set_override_options($display_options['feed'], $display, $default_display);
+ }
+ }
+
+ // Display: Block
+ if (isset($display_options['block'])) {
+ $display = $view->new_display('block', 'Block', 'block');
+ // When there is no page, the block display options should become the
+ // overall view defaults.
+ if (!isset($display_options['page'])) {
+ $this->set_default_options($display_options['block'], $display, $default_display);
+ }
+ else {
+ $this->set_override_options($display_options['block'], $display, $default_display);
+ }
+ }
+ }
+
+ /**
+ * Most subclasses will need to override this method to provide some fields
+ * or a different row plugin.
+ */
+ protected function default_display_options($form, $form_state) {
+ $display_options = array();
+ $display_options['access']['type'] = 'none';
+ $display_options['cache']['type'] = 'none';
+ $display_options['query']['type'] = 'views_query';
+ $display_options['exposed_form']['type'] = 'basic';
+ $display_options['pager']['type'] = 'full';
+ $display_options['style_plugin'] = 'default';
+ $display_options['row_plugin'] = 'fields';
+
+ // Add a least one field so the view validates and the user has already a preview.
+ // Therefore the basefield could provide 'defaults][field]' in it's base settings.
+ // If there is nothing like this choose the first field with a field handler.
+ $data = views_fetch_data($this->base_table);
+ if (isset($data['table']['base']['defaults']['field'])) {
+ $field = $data['table']['base']['defaults']['field'];
+ }
+ else {
+ foreach ($data as $field => $field_data) {
+ if (isset($field_data['field']['handler'])) {
+ break;
+ }
+ }
+ }
+ $display_options['fields'][$field] = array(
+ 'table' => $this->base_table,
+ 'field' => $field,
+ 'id' => $field,
+ );
+
+ return $display_options;
+ }
+
+ protected function default_display_filters($form, $form_state) {
+ $filters = array();
+
+ // Add any filters provided by the plugin.
+ if (isset($this->plugin['filters'])) {
+ foreach ($this->plugin['filters'] as $name => $info) {
+ $filters[$name] = $info;
+ }
+ }
+
+ // Add any filters specified by the user when filling out the wizard.
+ $filters = array_merge($filters, $this->default_display_filters_user($form, $form_state));
+
+ return $filters;
+ }
+
+ protected function default_display_filters_user($form, $form_state) {
+ $filters = array();
+
+ if (!empty($form_state['values']['show']['type']) && $form_state['values']['show']['type'] != 'all') {
+ $bundle_key = $this->entity_info['bundle keys']['bundle'];
+ // Figure out the table where $bundle_key lives. It may not be the same as
+ // the base table for the view; the taxonomy vocabulary machine_name, for
+ // example, is stored in taxonomy_vocabulary, not taxonomy_term_data.
+ $fields = views_fetch_fields($this->base_table, 'filter');
+ if (isset($fields[$this->base_table . '.' . $bundle_key])) {
+ $table = $this->base_table;
+ }
+ else {
+ foreach ($fields as $field_name => $value) {
+ if ($pos = strpos($field_name, '.' . $bundle_key)) {
+ $table = substr($field_name, 0, $pos);
+ break;
+ }
+ }
+ }
+ $table_data = views_fetch_data($table);
+ // Check whether the bundle key filter handler is or an child of it views_handler_filter_in_operator
+ // If it's not just use a single value instead of an array.
+ $handler = $table_data[$bundle_key]['filter']['handler'];
+ if ($handler == 'views_handler_filter_in_operator' || is_subclass_of($handler, 'views_handler_filter_in_operator')) {
+ $value = drupal_map_assoc(array($form_state['values']['show']['type']));
+ }
+ else {
+ $value = $form_state['values']['show']['type'];
+ }
+
+ $filters[$bundle_key] = array(
+ 'id' => $bundle_key,
+ 'table' => $table,
+ 'field' => $bundle_key,
+ 'value' => $value,
+ );
+ }
+
+ // @todo: Figure out why this isn't part of node_views_wizard.
+ if (!empty($form_state['values']['show']['tagged_with']['tids'])) {
+ $filters['tid'] = array(
+ 'id' => 'tid',
+ 'table' => 'taxonomy_index',
+ 'field' => 'tid',
+ 'value' => $form_state['values']['show']['tagged_with']['tids'],
+ 'vocabulary' => $form_state['values']['show']['tagged_with']['vocabulary'],
+ );
+ // If the user entered more than one valid term in the autocomplete
+ // field, they probably intended both of them to be applied.
+ if (count($form_state['values']['show']['tagged_with']['tids']) > 1) {
+ $filters['tid']['operator'] = 'and';
+ // Sort the terms so the filter will be displayed as it normally would
+ // on the edit screen.
+ sort($filters['tid']['value']);
+ }
+ }
+
+ return $filters;
+ }
+
+ protected function default_display_sorts($form, $form_state) {
+ $sorts = array();
+
+ // Add any sorts provided by the plugin.
+ if (isset($this->plugin['sorts'])) {
+ foreach ($this->plugin['sorts'] as $name => $info) {
+ $sorts[$name] = $info;
+ }
+ }
+
+ // Add any sorts specified by the user when filling out the wizard.
+ $sorts = array_merge($sorts, $this->default_display_sorts_user($form, $form_state));
+
+ return $sorts;
+ }
+
+ protected function default_display_sorts_user($form, $form_state) {
+ $sorts = array();
+
+ // Don't add a sort if there is no form value or the user selected none as sort.
+ if (!empty($form_state['values']['show']['sort']) && $form_state['values']['show']['sort'] != 'none') {
+ list($column, $sort) = explode(':', $form_state['values']['show']['sort']);
+ // Column either be a column-name or the table-columnn-ame.
+ $column = explode('-', $column);
+ if (count($column) > 1) {
+ $table = $column[0];
+ $column = $column[1];
+ }
+ else {
+ $table = $this->base_table;
+ $column = $column[0];
+ }
+
+ $sorts[$column] = array(
+ 'id' => $column,
+ 'table' => $table,
+ 'field' => $column,
+ 'order' => $sort,
+ );
+ }
+
+ return $sorts;
+ }
+
+ protected function page_display_options($form, $form_state) {
+ $display_options = array();
+ $page = $form_state['values']['page'];
+ $display_options['title'] = $page['title'];
+ $display_options['path'] = $page['path'];
+ $display_options['style_plugin'] = $page['style']['style_plugin'];
+ // Not every style plugin supports row style plugins.
+ $display_options['row_plugin'] = isset($page['style']['row_plugin']) ? $page['style']['row_plugin'] : 'fields';
+ if (empty($page['items_per_page'])) {
+ $display_options['pager']['type'] = 'none';
+ }
+ elseif ($page['pager']) {
+ $display_options['pager']['type'] = 'full';
+ }
+ else {
+ $display_options['pager']['type'] = 'some';
+ }
+ $display_options['pager']['options']['items_per_page'] = $page['items_per_page'];
+ if (!empty($page['link'])) {
+ $display_options['menu']['type'] = 'normal';
+ $display_options['menu']['title'] = $page['link_properties']['title'];
+ $display_options['menu']['name'] = $page['link_properties']['menu_name'];
+ }
+ return $display_options;
+ }
+
+ protected function block_display_options($form, $form_state) {
+ $display_options = array();
+ $block = $form_state['values']['block'];
+ $display_options['title'] = $block['title'];
+ $display_options['style_plugin'] = $block['style']['style_plugin'];
+ $display_options['row_plugin'] = isset($block['style']['row_plugin']) ? $block['style']['row_plugin'] : 'fields';
+ $display_options['pager']['type'] = $block['pager'] ? 'full' : (empty($block['items_per_page']) ? 'none' : 'some');
+ $display_options['pager']['options']['items_per_page'] = $block['items_per_page'];
+ return $display_options;
+ }
+
+ protected function page_feed_display_options($form, $form_state) {
+ $display_options = array();
+ $display_options['pager']['type'] = 'some';
+ $display_options['style_plugin'] = 'rss';
+ $display_options['row_plugin'] = $form_state['values']['page']['feed_properties']['row_plugin'];
+ $display_options['path'] = $form_state['values']['page']['feed_properties']['path'];
+ $display_options['title'] = $form_state['values']['page']['title'];
+ $display_options['displays'] = array(
+ 'default' => 'default',
+ 'page' => 'page',
+ );
+ return $display_options;
+ }
+
+ /**
+ * Sets options for a display and makes them the default options if possible.
+ *
+ * This function can be used to set options for a display when it is desired
+ * that the options also become the defaults for the view whenever possible.
+ * This should be done for the "primary" display created in the view wizard,
+ * so that new displays which the user adds later will be similar to this
+ * one.
+ *
+ * @param $options
+ * An array whose keys are the name of each option and whose values are the
+ * desired values to set.
+ * @param $display
+ * The display which the options will be applied to. The default display
+ * will actually be assigned the options (and this display will inherit
+ * them) when possible.
+ * @param $default_display
+ * The default display, which will store the options when possible.
+ */
+ protected function set_default_options($options, $display, $default_display) {
+ foreach ($options as $option => $value) {
+ // If the default display supports this option, set the value there.
+ // Otherwise, set it on the provided display.
+ $default_value = $default_display->get_option($option);
+ if (isset($default_value)) {
+ $default_display->set_option($option, $value);
+ }
+ else {
+ $display->set_option($option, $value);
+ }
+ }
+ }
+
+ /**
+ * Sets options for a display, inheriting from the defaults when possible.
+ *
+ * This function can be used to set options for a display when it is desired
+ * that the options inherit from the default display whenever possible. This
+ * avoids setting too many options as overrides, which will be harder for the
+ * user to modify later. For example, if $this->set_default_options() was
+ * previously called on a page display and then this function is called on a
+ * block display, and if the user entered the same title for both displays in
+ * the views wizard, then the view will wind up with the title stored as the
+ * default (with the page and block both inheriting from it).
+ *
+ * @param $options
+ * An array whose keys are the name of each option and whose values are the
+ * desired values.
+ * @param $display
+ * The display which the options will apply to. It will get the options by
+ * inheritance from the default display when possible.
+ * @param $default_display
+ * The default display, from which the options will be inherited when
+ * possible.
+ */
+ protected function set_override_options($options, $display, $default_display) {
+ foreach ($options as $option => $value) {
+ // Only override the default value if it is different from the value that
+ // was provided.
+ $default_value = $default_display->get_option($option);
+ if (!isset($default_value)) {
+ $display->set_option($option, $value);
+ }
+ elseif ($default_value !== $value) {
+ $display->override_option($option, $value);
+ }
+ }
+ }
+
+ protected function retrieve_validated_view($form, $form_state, $unset = TRUE) {
+ $key = hash('sha256', serialize($form_state['values']));
+ $view = (isset($this->validated_views[$key]) ? $this->validated_views[$key] : NULL);
+ if ($unset) {
+ unset($this->validated_views[$key]);
+ }
+ return $view;
+ }
+
+ protected function set_validated_view($form, $form_state, $view) {
+ $key = hash('sha256', serialize($form_state['values']));
+ $this->validated_views[$key] = $view;
+ }
+
+ /**
+ * Instantiates a view and validates values.
+ */
+ function validate($form, &$form_state) {
+ $view = $this->instantiate_view($form, $form_state);
+ $errors = $view->validate();
+ if (!is_array($errors) || empty($errors)) {
+ $this->set_validated_view($form, $form_state, $view);
+ return array();
+ }
+ return $errors;
+ }
+
+ /**
+ * Create a View from values that have been already submitted to validate().
+ *
+ * @throws ViewsWizardException if the values have not been validated.
+ */
+ function create_view($form, &$form_state) {
+ $view = $this->retrieve_validated_view($form, $form_state);
+ if (empty($view)) {
+ throw new ViewsWizardException(t('Attempted to create_view with values that have not been validated'));
+ }
+ return $view;
+ }
+
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/views_ui_comment_views_wizard.class.php b/sites/all/modules/views/plugins/views_wizard/views_ui_comment_views_wizard.class.php
new file mode 100644
index 000000000..fa26d3332
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/views_ui_comment_views_wizard.class.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUiCommentViewsWizard.
+ */
+
+/**
+ * Tests creating comment views with the wizard.
+ */
+class ViewsUiCommentViewsWizard extends ViewsUiBaseViewsWizard {
+
+ protected function row_style_options($type) {
+ $options = array();
+ $options['comment'] = t('comments');
+ $options['fields'] = t('fields');
+ return $options;
+ }
+
+ protected function build_form_style(&$form, &$form_state, $type) {
+ parent::build_form_style($form, $form_state, $type);
+ $style_form =& $form['displays'][$type]['options']['style'];
+ // Some style plugins don't support row plugins so stop here if that's the
+ // case.
+ if (!isset($style_form['row_plugin']['#default_value'])) {
+ return;
+ }
+ $row_plugin = $style_form['row_plugin']['#default_value'];
+ switch ($row_plugin) {
+ case 'comment':
+ $style_form['row_options']['links'] = array(
+ '#type' => 'select',
+ '#title_display' => 'invisible',
+ '#title' => t('Should links be displayed below each comment'),
+ '#options' => array(
+ 1 => t('with links (allow users to reply to the comment, etc.)'),
+ 0 => t('without links'),
+ ),
+ '#default_value' => 1,
+ );
+ break;
+ }
+ }
+
+ protected function page_display_options($form, $form_state) {
+ $display_options = parent::page_display_options($form, $form_state);
+ $row_plugin = isset($form_state['values']['page']['style']['row_plugin']) ? $form_state['values']['page']['style']['row_plugin'] : NULL;
+ $row_options = isset($form_state['values']['page']['style']['row_options']) ? $form_state['values']['page']['style']['row_options'] : array();
+ $this->display_options_row($display_options, $row_plugin, $row_options);
+ return $display_options;
+ }
+
+ protected function block_display_options($form, $form_state) {
+ $display_options = parent::block_display_options($form, $form_state);
+ $row_plugin = isset($form_state['values']['block']['style']['row_plugin']) ? $form_state['values']['block']['style']['row_plugin'] : NULL;
+ $row_options = isset($form_state['values']['block']['style']['row_options']) ? $form_state['values']['block']['style']['row_options'] : array();
+ $this->display_options_row($display_options, $row_plugin, $row_options);
+ return $display_options;
+ }
+
+ /**
+ * Set the row style and row style plugins to the display_options.
+ */
+ protected function display_options_row(&$display_options, $row_plugin, $row_options) {
+ switch ($row_plugin) {
+ case 'comment':
+ $display_options['row_plugin'] = 'comment';
+ $display_options['row_options']['links'] = !empty($row_options['links']);
+ break;
+ }
+ }
+
+ protected function default_display_options($form, $form_state) {
+ $display_options = parent::default_display_options($form, $form_state);
+
+ // Add permission-based access control.
+ $display_options['access']['type'] = 'perm';
+ $display_options['access']['perm'] = 'access comments';
+
+ // Add a relationship to nodes.
+ $display_options['relationships']['nid']['id'] = 'nid';
+ $display_options['relationships']['nid']['table'] = 'comment';
+ $display_options['relationships']['nid']['field'] = 'nid';
+ $display_options['relationships']['nid']['required'] = 1;
+
+ // Remove the default fields, since we are customizing them here.
+ unset($display_options['fields']);
+
+ /* Field: Comment: Title */
+ $display_options['fields']['subject']['id'] = 'subject';
+ $display_options['fields']['subject']['table'] = 'comment';
+ $display_options['fields']['subject']['field'] = 'subject';
+ $display_options['fields']['subject']['label'] = '';
+ $display_options['fields']['subject']['alter']['alter_text'] = 0;
+ $display_options['fields']['subject']['alter']['make_link'] = 0;
+ $display_options['fields']['subject']['alter']['absolute'] = 0;
+ $display_options['fields']['subject']['alter']['trim'] = 0;
+ $display_options['fields']['subject']['alter']['word_boundary'] = 0;
+ $display_options['fields']['subject']['alter']['ellipsis'] = 0;
+ $display_options['fields']['subject']['alter']['strip_tags'] = 0;
+ $display_options['fields']['subject']['alter']['html'] = 0;
+ $display_options['fields']['subject']['hide_empty'] = 0;
+ $display_options['fields']['subject']['empty_zero'] = 0;
+ $display_options['fields']['subject']['link_to_comment'] = 1;
+
+ return $display_options;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/views_ui_file_managed_views_wizard.class.php b/sites/all/modules/views/plugins/views_wizard/views_ui_file_managed_views_wizard.class.php
new file mode 100644
index 000000000..111b6315e
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/views_ui_file_managed_views_wizard.class.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUiFileManagedViewsWizard.
+ */
+
+/**
+ * Tests creating managed files views with the wizard.
+ */
+class ViewsUiFileManagedViewsWizard extends ViewsUiBaseViewsWizard {
+ protected function default_display_options($form, $form_state) {
+ $display_options = parent::default_display_options($form, $form_state);
+
+ // Add permission-based access control.
+ $display_options['access']['type'] = 'perm';
+
+ // Remove the default fields, since we are customizing them here.
+ unset($display_options['fields']);
+
+ /* Field: File: Name */
+ $display_options['fields']['filename']['id'] = 'filename';
+ $display_options['fields']['filename']['table'] = 'file_managed';
+ $display_options['fields']['filename']['field'] = 'filename';
+ $display_options['fields']['filename']['label'] = '';
+ $display_options['fields']['filename']['alter']['alter_text'] = 0;
+ $display_options['fields']['filename']['alter']['make_link'] = 0;
+ $display_options['fields']['filename']['alter']['absolute'] = 0;
+ $display_options['fields']['filename']['alter']['trim'] = 0;
+ $display_options['fields']['filename']['alter']['word_boundary'] = 0;
+ $display_options['fields']['filename']['alter']['ellipsis'] = 0;
+ $display_options['fields']['filename']['alter']['strip_tags'] = 0;
+ $display_options['fields']['filename']['alter']['html'] = 0;
+ $display_options['fields']['filename']['hide_empty'] = 0;
+ $display_options['fields']['filename']['empty_zero'] = 0;
+ $display_options['fields']['filename']['link_to_file'] = 1;
+
+ return $display_options;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/views_ui_node_revision_views_wizard.class.php b/sites/all/modules/views/plugins/views_wizard/views_ui_node_revision_views_wizard.class.php
new file mode 100644
index 000000000..3623f5361
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/views_ui_node_revision_views_wizard.class.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUiNodeRevisionViewsWizard.
+ */
+
+/**
+ * Tests creating node revision views with the wizard.
+ */
+class ViewsUiNodeRevisionViewsWizard extends ViewsUiNodeViewsWizard {
+
+ /**
+ * Node revisions do not support full posts or teasers, so remove them.
+ */
+ protected function row_style_options($type) {
+ $options = parent::row_style_options($type);
+ unset($options['teasers']);
+ unset($options['full_posts']);
+ return $options;
+ }
+
+ protected function default_display_options($form, $form_state) {
+ $display_options = parent::default_display_options($form, $form_state);
+
+ // Add permission-based access control.
+ $display_options['access']['type'] = 'perm';
+ $display_options['access']['perm'] = 'view revisions';
+
+ // Remove the default fields, since we are customizing them here.
+ unset($display_options['fields']);
+
+ /* Field: Content revision: Created date */
+ $display_options['fields']['timestamp']['id'] = 'timestamp';
+ $display_options['fields']['timestamp']['table'] = 'node_revision';
+ $display_options['fields']['timestamp']['field'] = 'timestamp';
+ $display_options['fields']['timestamp']['alter']['alter_text'] = 0;
+ $display_options['fields']['timestamp']['alter']['make_link'] = 0;
+ $display_options['fields']['timestamp']['alter']['absolute'] = 0;
+ $display_options['fields']['timestamp']['alter']['trim'] = 0;
+ $display_options['fields']['timestamp']['alter']['word_boundary'] = 0;
+ $display_options['fields']['timestamp']['alter']['ellipsis'] = 0;
+ $display_options['fields']['timestamp']['alter']['strip_tags'] = 0;
+ $display_options['fields']['timestamp']['alter']['html'] = 0;
+ $display_options['fields']['timestamp']['hide_empty'] = 0;
+ $display_options['fields']['timestamp']['empty_zero'] = 0;
+
+ /* Field: Content revision: Title */
+ $display_options['fields']['title']['id'] = 'title';
+ $display_options['fields']['title']['table'] = 'node_revision';
+ $display_options['fields']['title']['field'] = 'title';
+ $display_options['fields']['title']['label'] = '';
+ $display_options['fields']['title']['alter']['alter_text'] = 0;
+ $display_options['fields']['title']['alter']['make_link'] = 0;
+ $display_options['fields']['title']['alter']['absolute'] = 0;
+ $display_options['fields']['title']['alter']['trim'] = 0;
+ $display_options['fields']['title']['alter']['word_boundary'] = 0;
+ $display_options['fields']['title']['alter']['ellipsis'] = 0;
+ $display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $display_options['fields']['title']['alter']['html'] = 0;
+ $display_options['fields']['title']['hide_empty'] = 0;
+ $display_options['fields']['title']['empty_zero'] = 0;
+ $display_options['fields']['title']['link_to_node'] = 0;
+ $display_options['fields']['title']['link_to_node_revision'] = 1;
+
+ return $display_options;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/views_ui_node_views_wizard.class.php b/sites/all/modules/views/plugins/views_wizard/views_ui_node_views_wizard.class.php
new file mode 100644
index 000000000..07bb91dfd
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/views_ui_node_views_wizard.class.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUiNodeViewsWizard.
+ */
+
+/**
+ * Tests creating node views with the wizard.
+ */
+class ViewsUiNodeViewsWizard extends ViewsUiBaseViewsWizard {
+
+ protected function row_style_options($type) {
+ $options = array();
+ $options['teasers'] = t('teasers');
+ $options['full_posts'] = t('full posts');
+ $options['titles'] = t('titles');
+ $options['titles_linked'] = t('titles (linked)');
+ $options['fields'] = t('fields');
+ return $options;
+ }
+
+ protected function build_form_style(&$form, &$form_state, $type) {
+ parent::build_form_style($form, $form_state, $type);
+ $style_form =& $form['displays'][$type]['options']['style'];
+ // Some style plugins don't support row plugins so stop here if that's the
+ // case.
+ if (!isset($style_form['row_plugin']['#default_value'])) {
+ return;
+ }
+ $row_plugin = $style_form['row_plugin']['#default_value'];
+ switch ($row_plugin) {
+ case 'full_posts':
+ case 'teasers':
+ $style_form['row_options']['links'] = array(
+ '#type' => 'select',
+ '#title_display' => 'invisible',
+ '#title' => t('Should links be displayed below each node'),
+ '#options' => array(
+ 1 => t('with links (allow users to add comments, etc.)'),
+ 0 => t('without links'),
+ ),
+ '#default_value' => 1,
+ );
+ $style_form['row_options']['comments'] = array(
+ '#type' => 'select',
+ '#title_display' => 'invisible',
+ '#title' => t('Should comments be displayed below each node'),
+ '#options' => array(
+ 1 => t('with comments'),
+ 0 => t('without comments'),
+ ),
+ '#default_value' => 0,
+ );
+ break;
+ }
+ }
+
+ /**
+ * @override
+ */
+ protected function default_display_options($form, $form_state) {
+ $display_options = parent::default_display_options($form, $form_state);
+
+ // Add permission-based access control.
+ $display_options['access']['type'] = 'perm';
+ $display_options['access']['perm'] = 'access content';
+
+ // Remove the default fields, since we are customizing them here.
+ unset($display_options['fields']);
+
+ // Add the title field, so that the display has content if the user switches
+ // to a row style that uses fields.
+ /* Field: Content: Title */
+ $display_options['fields']['title']['id'] = 'title';
+ $display_options['fields']['title']['table'] = 'node';
+ $display_options['fields']['title']['field'] = 'title';
+ $display_options['fields']['title']['label'] = '';
+ $display_options['fields']['title']['alter']['alter_text'] = 0;
+ $display_options['fields']['title']['alter']['make_link'] = 0;
+ $display_options['fields']['title']['alter']['absolute'] = 0;
+ $display_options['fields']['title']['alter']['trim'] = 0;
+ $display_options['fields']['title']['alter']['word_boundary'] = 0;
+ $display_options['fields']['title']['alter']['ellipsis'] = 0;
+ $display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $display_options['fields']['title']['alter']['html'] = 0;
+ $display_options['fields']['title']['hide_empty'] = 0;
+ $display_options['fields']['title']['empty_zero'] = 0;
+ $display_options['fields']['title']['link_to_node'] = 1;
+
+ return $display_options;
+ }
+
+ protected function page_display_options($form, $form_state) {
+ $display_options = parent::page_display_options($form, $form_state);
+ $row_plugin = isset($form_state['values']['page']['style']['row_plugin']) ? $form_state['values']['page']['style']['row_plugin'] : NULL;
+ $row_options = isset($form_state['values']['page']['style']['row_options']) ? $form_state['values']['page']['style']['row_options'] : array();
+ $this->display_options_row($display_options, $row_plugin, $row_options);
+ return $display_options;
+ }
+
+ protected function block_display_options($form, $form_state) {
+ $display_options = parent::block_display_options($form, $form_state);
+ $row_plugin = isset($form_state['values']['block']['style']['row_plugin']) ? $form_state['values']['block']['style']['row_plugin'] : NULL;
+ $row_options = isset($form_state['values']['block']['style']['row_options']) ? $form_state['values']['block']['style']['row_options'] : array();
+ $this->display_options_row($display_options, $row_plugin, $row_options);
+ return $display_options;
+ }
+
+ /**
+ * Set the row style and row style plugins to the display_options.
+ */
+ protected function display_options_row(&$display_options, $row_plugin, $row_options) {
+ switch ($row_plugin) {
+ case 'full_posts':
+ $display_options['row_plugin'] = 'node';
+ $display_options['row_options']['build_mode'] = 'full';
+ $display_options['row_options']['links'] = !empty($row_options['links']);
+ $display_options['row_options']['comments'] = !empty($row_options['comments']);
+ break;
+ case 'teasers':
+ $display_options['row_plugin'] = 'node';
+ $display_options['row_options']['build_mode'] = 'teaser';
+ $display_options['row_options']['links'] = !empty($row_options['links']);
+ $display_options['row_options']['comments'] = !empty($row_options['comments']);
+ break;
+ case 'titles_linked':
+ $display_options['row_plugin'] = 'fields';
+ $display_options['field']['title']['link_to_node'] = 1;
+ break;
+ case 'titles':
+ $display_options['row_plugin'] = 'fields';
+ $display_options['field']['title']['link_to_node'] = 0;
+ break;
+ }
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/views_ui_taxonomy_term_views_wizard.class.php b/sites/all/modules/views/plugins/views_wizard/views_ui_taxonomy_term_views_wizard.class.php
new file mode 100644
index 000000000..8c1d6d564
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/views_ui_taxonomy_term_views_wizard.class.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUiTaxonomyTermViewsWizard.
+ */
+
+/**
+ * Tests creating taxonomy views with the wizard.
+ */
+class ViewsUiTaxonomyTermViewsWizard extends ViewsUiBaseViewsWizard {
+
+ protected function default_display_options($form, $form_state) {
+ $display_options = parent::default_display_options($form, $form_state);
+
+ // Add permission-based access control.
+ $display_options['access']['type'] = 'perm';
+ $display_options['access']['perm'] = 'access content';
+
+ // Remove the default fields, since we are customizing them here.
+ unset($display_options['fields']);
+
+ /* Field: Taxonomy: Term */
+ $display_options['fields']['name']['id'] = 'name';
+ $display_options['fields']['name']['table'] = 'taxonomy_term_data';
+ $display_options['fields']['name']['field'] = 'name';
+ $display_options['fields']['name']['label'] = '';
+ $display_options['fields']['name']['alter']['alter_text'] = 0;
+ $display_options['fields']['name']['alter']['make_link'] = 0;
+ $display_options['fields']['name']['alter']['absolute'] = 0;
+ $display_options['fields']['name']['alter']['trim'] = 0;
+ $display_options['fields']['name']['alter']['word_boundary'] = 0;
+ $display_options['fields']['name']['alter']['ellipsis'] = 0;
+ $display_options['fields']['name']['alter']['strip_tags'] = 0;
+ $display_options['fields']['name']['alter']['html'] = 0;
+ $display_options['fields']['name']['hide_empty'] = 0;
+ $display_options['fields']['name']['empty_zero'] = 0;
+ $display_options['fields']['name']['link_to_taxonomy'] = 1;
+
+ return $display_options;
+ }
+}
diff --git a/sites/all/modules/views/plugins/views_wizard/views_ui_users_views_wizard.class.php b/sites/all/modules/views/plugins/views_wizard/views_ui_users_views_wizard.class.php
new file mode 100644
index 000000000..73eff486a
--- /dev/null
+++ b/sites/all/modules/views/plugins/views_wizard/views_ui_users_views_wizard.class.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUiUsersViewsWizard.
+ */
+
+/**
+ * Tests creating user views with the wizard.
+ */
+class ViewsUiUsersViewsWizard extends ViewsUiBaseViewsWizard {
+ protected function default_display_options($form, $form_state) {
+ $display_options = parent::default_display_options($form, $form_state);
+
+ // Add permission-based access control.
+ $display_options['access']['type'] = 'perm';
+ $display_options['access']['perm'] = 'access user profiles';
+
+ // Remove the default fields, since we are customizing them here.
+ unset($display_options['fields']);
+
+ /* Field: User: Name */
+ $display_options['fields']['name']['id'] = 'name';
+ $display_options['fields']['name']['table'] = 'users';
+ $display_options['fields']['name']['field'] = 'name';
+ $display_options['fields']['name']['label'] = '';
+ $display_options['fields']['name']['alter']['alter_text'] = 0;
+ $display_options['fields']['name']['alter']['make_link'] = 0;
+ $display_options['fields']['name']['alter']['absolute'] = 0;
+ $display_options['fields']['name']['alter']['trim'] = 0;
+ $display_options['fields']['name']['alter']['word_boundary'] = 0;
+ $display_options['fields']['name']['alter']['ellipsis'] = 0;
+ $display_options['fields']['name']['alter']['strip_tags'] = 0;
+ $display_options['fields']['name']['alter']['html'] = 0;
+ $display_options['fields']['name']['hide_empty'] = 0;
+ $display_options['fields']['name']['empty_zero'] = 0;
+ $display_options['fields']['name']['link_to_user'] = 1;
+ $display_options['fields']['name']['overwrite_anonymous'] = 0;
+
+ return $display_options;
+ }
+}
diff --git a/sites/all/modules/views/test_templates/README.txt b/sites/all/modules/views/test_templates/README.txt
new file mode 100644
index 000000000..551f738f2
--- /dev/null
+++ b/sites/all/modules/views/test_templates/README.txt
@@ -0,0 +1,11 @@
+Workaround for:
+
+- https://www.drupal.org/node/2450447
+- https://www.drupal.org/node/2415991
+
+
+Files of this folder cannot be included inside the views/tests directory because
+they are included as tests cases and make testbot crash.
+
+This files could be moved to tests/templates once
+https://www.drupal.org/node/2415991 be properly fixed.
diff --git a/sites/all/modules/views/test_templates/views-view--frontpage.tpl.php b/sites/all/modules/views/test_templates/views-view--frontpage.tpl.php
new file mode 100644
index 000000000..eb4f58b7b
--- /dev/null
+++ b/sites/all/modules/views/test_templates/views-view--frontpage.tpl.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Main view template.
+ *
+ * Variables available:
+ * - $classes_array: An array of classes determined in
+ * template_preprocess_views_view(). Default classes are:
+ * .view
+ * .view-[css_name]
+ * .view-id-[view_name]
+ * .view-display-id-[display_name]
+ * .view-dom-id-[dom_id]
+ * - $classes: A string version of $classes_array for use in the class attribute
+ * - $css_name: A css-safe version of the view name.
+ * - $css_class: The user-specified classes names, if any
+ * - $header: The view header
+ * - $footer: The view footer
+ * - $rows: The results of the view query, if any
+ * - $empty: The empty text to display if the view is empty
+ * - $pager: The pager next/prev links to display, if any
+ * - $exposed: Exposed widget form/info to display
+ * - $feed_icon: Feed icon to display, if any
+ * - $more: A link to view more, if any
+ *
+ * @ingroup views_templates
+ */
+?>
+<div class="<?php print $classes; ?>">
+ <?php if ($header): ?>
+ <div class="view-header">
+ <?php print $header; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($exposed): ?>
+ <div class="view-filters">
+ <?php print $exposed; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($attachment_before): ?>
+ <div class="attachment attachment-before">
+ <?php print $attachment_before; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($rows): ?>
+ <div class="view-content">
+ <?php print $rows; ?>
+ </div>
+ <?php elseif ($empty): ?>
+ <div class="view-empty">
+ <?php print $empty; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($pager): ?>
+ <?php print $pager; ?>
+ <?php endif; ?>
+
+ <?php if ($attachment_after): ?>
+ <div class="attachment attachment-after">
+ <?php print $attachment_after; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($more): ?>
+ <?php print $more; ?>
+ <?php endif; ?>
+
+ <?php if ($footer): ?>
+ <div class="view-footer">
+ <?php print $footer; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($feed_icon): ?>
+ <div class="feed-icon">
+ <?php print $feed_icon; ?>
+ </div>
+ <?php endif; ?>
+
+</div> <?php /* class view */ ?>
diff --git a/sites/all/modules/views/tests/comment/views_handler_argument_comment_user_uid.test b/sites/all/modules/views/tests/comment/views_handler_argument_comment_user_uid.test
new file mode 100644
index 000000000..353d92978
--- /dev/null
+++ b/sites/all/modules/views/tests/comment/views_handler_argument_comment_user_uid.test
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @file
+ * Definition of viewsHandlerArgumentCommentUserUidTest.
+ */
+
+/**
+ * Tests the argument_comment_user_uid handler.
+ */
+class viewsHandlerArgumentCommentUserUidTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests handler argument_comment_user_uid',
+ 'description' => 'Tests the user posted or commented argument handler',
+ 'group' => 'Views Modules',
+ );
+ }
+
+ /**
+ * Post comment.
+ *
+ * @param $node
+ * Node to post comment on.
+ * @param $comment
+ * Comment to save
+ */
+ function postComment($node, $comment = array()) {
+ $comment += array(
+ 'uid' => $this->loggedInUser->uid,
+ 'nid' => $node->nid,
+ 'cid' => '',
+ 'pid' => '',
+ );
+ return comment_save((object) $comment);
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Add two users, create a node with the user1 as author and another node with user2 as author.
+ // For the second node add a comment from user1.
+ $this->account = $this->drupalCreateUser();
+ $this->account2 = $this->drupalCreateUser();
+ $this->drupalLogin($this->account);
+ $this->node_user_posted = $this->drupalCreateNode();
+ $this->node_user_commented = $this->drupalCreateNode(array('uid' => $this->account2->uid));
+ $this->postComment($this->node_user_commented);
+ }
+
+ function testCommentUserUidTest() {
+ $view = $this->view_comment_user_uid();
+
+
+ $this->executeView($view, array($this->account->uid));
+ $resultset = array(
+ array(
+ 'nid' => $this->node_user_posted->nid,
+ ),
+ array(
+ 'nid' => $this->node_user_commented->nid,
+ ),
+ );
+ $this->column_map = array('nid' => 'nid');
+ debug($view->result);
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function view_comment_user_uid() {
+ $view = new view;
+ $view->name = 'test_comment_user_uid';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'test_comment_user_uid';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ /* Field: Content: nid */
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'nid';
+ /* Contextual filter: Content: User posted or commented */
+ $handler->display->display_options['arguments']['uid_touch']['id'] = 'uid_touch';
+ $handler->display->display_options['arguments']['uid_touch']['table'] = 'node';
+ $handler->display->display_options['arguments']['uid_touch']['field'] = 'uid_touch';
+ $handler->display->display_options['arguments']['uid_touch']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['uid_touch']['default_argument_skip_url'] = 0;
+ $handler->display->display_options['arguments']['uid_touch']['summary']['number_of_records'] = '0';
+ $handler->display->display_options['arguments']['uid_touch']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['uid_touch']['summary_options']['items_per_page'] = '25';
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/comment/views_handler_filter_comment_user_uid.test b/sites/all/modules/views/tests/comment/views_handler_filter_comment_user_uid.test
new file mode 100644
index 000000000..1f3f75cdb
--- /dev/null
+++ b/sites/all/modules/views/tests/comment/views_handler_filter_comment_user_uid.test
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Definition of viewsHandlerFilterCommentUserUidTest.
+ */
+
+/**
+ * Tests the filter_comment_user_uid handler.
+ *
+ * The actual stuff is done in the parent class.
+ */
+class viewsHandlerFilterCommentUserUidTest extends viewsHandlerArgumentCommentUserUidTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests handler filter_comment_user_uid',
+ 'description' => 'Tests the user posted or commented filter handler',
+ 'group' => 'Views Modules',
+ );
+ }
+
+ /**
+ * Override the view from the argument test case to remove the argument and
+ * add filter with the uid as the value.
+ */
+ function view_comment_user_uid() {
+ $view = parent::view_comment_user_uid();
+ // Remove the argument.
+ $view->set_item('default', 'argument', 'uid_touch', NULL);
+
+ $options = array(
+ 'id' => 'uid_touch',
+ 'table' => 'node',
+ 'field' => 'uid_touch',
+ 'value' => array($this->loggedInUser->uid),
+ );
+ $view->add_item('default', 'filter', 'node', 'uid_touch', $options);
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/field/views_fieldapi.test b/sites/all/modules/views/tests/field/views_fieldapi.test
new file mode 100644
index 000000000..da4c27b37
--- /dev/null
+++ b/sites/all/modules/views/tests/field/views_fieldapi.test
@@ -0,0 +1,494 @@
+<?php
+
+/**
+ * @file
+ * Tests the fieldapi integration of viewsdata.
+ */
+
+/**
+ * @TODO
+ * - Test on a generic entity not on a node.
+ *
+ * What has to be tested:
+ * - Take sure that every wanted field is added to the according entity type.
+ * - Take sure the joins are done correct.
+ * - Use basic fields and take sure that the full wanted object is build.
+ * - Use relationships between different entity types, for example node and the node author(user).
+ */
+
+/**
+ * Provides some helper methods for testing fieldapi integration into views.
+ */
+class ViewsFieldApiTestHelper extends ViewsSqlTest {
+ /**
+ * Stores the field definitions used by the test.
+ * @var array
+ */
+ public $fields;
+ /**
+ * Stores the instances of the fields. They have
+ * the same keys as the fields.
+ * @var array
+ */
+ public $instances;
+
+ protected function CreateUser($extra_edit = array()) {
+ $permissions = array('access comments', 'access content', 'post comments', 'skip comment approval');
+ // Create a role with the given permission set.
+ if (!($rid = $this->drupalCreateRole($permissions))) {
+ return FALSE;
+ }
+
+ // Create a user assigned to that role.
+ $edit = array();
+ $edit['name'] = $this->randomName();
+ $edit['mail'] = $edit['name'] . '@example.com';
+ $edit['roles'] = array($rid => $rid);
+ $edit['pass'] = user_password();
+ $edit['status'] = 1;
+ $edit += $extra_edit;
+
+ $account = user_save(drupal_anonymous_user(), $edit);
+
+ $this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login'));
+ if (empty($account->uid)) {
+ return FALSE;
+ }
+
+ // Add the raw password so that we can log in as this user.
+ $account->pass_raw = $edit['pass'];
+ return $account;
+ }
+
+ function setUpFields($amount = 3) {
+ // Create three fields.
+ $field_names = array();
+ for ($i = 0; $i < $amount; $i++) {
+ $field_names[$i] = 'field_name_' . $i;
+ $field = array('field_name' => $field_names[$i], 'type' => 'text');
+
+ $this->fields[$i] = $field = field_create_field($field);
+ }
+ return $field_names;
+ }
+
+ function setUpInstances($bundle = 'page') {
+ foreach ($this->fields as $key => $field) {
+ $instance = array(
+ 'field_name' => $field['field_name'],
+ 'entity_type' => 'node',
+ 'bundle' => 'page',
+ );
+ $this->instances[$key] = field_create_instance($instance);
+ }
+ }
+
+ /**
+ * Clear all views caches and static caches which are required for the patch.
+ */
+ function clearViewsCaches() {
+ // Reset views data cache.
+ drupal_static_reset('_views_fetch_data_cache');
+ drupal_static_reset('_views_fetch_data_recursion_protected');
+ drupal_static_reset('_views_fetch_data_fully_loaded');
+ }
+}
+
+/**
+ * Test the produced views_data.
+ */
+class viewsFieldApiDataTest extends ViewsFieldApiTestHelper {
+ /**
+ * Stores the fields for this test case.
+ */
+ var $fields;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Fieldapi: Views Data',
+ 'description' => 'Tests the fieldapi views data.',
+ 'group' => 'Views Modules',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $langcode = LANGUAGE_NONE;
+
+
+ $field_names = $this->setUpFields();
+
+ // The first one will be attached to nodes only.
+ $instance = array(
+ 'field_name' => $field_names[0],
+ 'entity_type' => 'node',
+ 'bundle' => 'page',
+ );
+ field_create_instance($instance);
+
+ // The second one will be attached to users only.
+ $instance = array(
+ 'field_name' => $field_names[1],
+ 'entity_type' => 'user',
+ 'bundle' => 'user',
+ );
+ field_create_instance($instance);
+
+ // The third will be attached to both nodes and users.
+ $instance = array(
+ 'field_name' => $field_names[2],
+ 'entity_type' => 'node',
+ 'bundle' => 'page',
+ );
+ field_create_instance($instance);
+ $instance = array(
+ 'field_name' => $field_names[2],
+ 'entity_type' => 'user',
+ 'bundle' => 'user',
+ );
+ field_create_instance($instance);
+
+ // Now create some example nodes/users for the view result.
+ for ($i = 0; $i < 5; $i++) {
+ $edit = array(
+ // @TODO Write a helper method to create such values.
+ 'field_name_0' => array($langcode => array((array('value' => $this->randomName())))),
+ 'field_name_2' => array($langcode => array((array('value' => $this->randomName())))),
+ );
+ $this->nodes[] = $this->drupalCreateNode($edit);
+ }
+
+ for ($i = 0; $i < 5; $i++) {
+ $edit = array(
+ 'field_name_1' => array($langcode => array((array('value' => $this->randomName())))),
+ 'field_name_2' => array($langcode => array((array('value' => $this->randomName())))),
+ );
+ $this->users[] = $this->CreateUser($edit);
+ }
+
+ // Reset views data cache.
+ $this->clearViewsCaches();
+ }
+
+ /**
+ * Unit testing the views data structure.
+ *
+ * We check data structure for both node and node revision tables.
+ */
+ function testViewsData() {
+ $data = views_fetch_data();
+
+ // Check the table and the joins of the first field.
+ // Attached to node only.
+ $field = $this->fields[0];
+ $current_table = _field_sql_storage_tablename($field);
+ $revision_table = _field_sql_storage_revision_tablename($field);
+
+ $this->assertTrue(isset($data[$current_table]));
+ $this->assertTrue(isset($data[$revision_table]));
+ // The node field should join against node.
+ $this->assertTrue(isset($data[$current_table]['table']['join']['node']));
+ $this->assertTrue(isset($data[$revision_table]['table']['join']['node_revision']));
+
+ $expected_join = array(
+ 'left_field' => 'nid',
+ 'field' => 'entity_id',
+ 'extra' => array(
+ array('field' => 'entity_type', 'value' => 'node'),
+ array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
+ ),
+ );
+ $this->assertEqual($expected_join, $data[$current_table]['table']['join']['node']);
+ $expected_join = array(
+ 'left_field' => 'vid',
+ 'field' => 'revision_id',
+ 'extra' => array(
+ array('field' => 'entity_type', 'value' => 'node'),
+ array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
+ ),
+ );
+ $this->assertEqual($expected_join, $data[$revision_table]['table']['join']['node_revision']);
+
+
+ // Check the table and the joins of the second field.
+ // Attached to both node and user.
+ $field_2 = $this->fields[2];
+ $current_table_2 = _field_sql_storage_tablename($field_2);
+ $revision_table_2 = _field_sql_storage_revision_tablename($field_2);
+
+ $this->assertTrue(isset($data[$current_table_2]));
+ $this->assertTrue(isset($data[$revision_table_2]));
+ // The second field should join against both node and users.
+ $this->assertTrue(isset($data[$current_table_2]['table']['join']['node']));
+ $this->assertTrue(isset($data[$revision_table_2]['table']['join']['node_revision']));
+ $this->assertTrue(isset($data[$current_table_2]['table']['join']['users']));
+
+ $expected_join = array(
+ 'left_field' => 'nid',
+ 'field' => 'entity_id',
+ 'extra' => array(
+ array('field' => 'entity_type', 'value' => 'node'),
+ array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
+ )
+ );
+ $this->assertEqual($expected_join, $data[$current_table_2]['table']['join']['node']);
+ $expected_join = array(
+ 'left_field' => 'vid',
+ 'field' => 'revision_id',
+ 'extra' => array(
+ array('field' => 'entity_type', 'value' => 'node'),
+ array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
+ )
+ );
+ $this->assertEqual($expected_join, $data[$revision_table_2]['table']['join']['node_revision']);
+ $expected_join = array(
+ 'left_field' => 'uid',
+ 'field' => 'entity_id',
+ 'extra' => array(
+ array('field' => 'entity_type', 'value' => 'user'),
+ array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
+ )
+ );
+ $this->assertEqual($expected_join, $data[$current_table_2]['table']['join']['users']);
+
+ // Check the fields
+ // @todo
+
+ // Check the arguments
+ // @todo
+
+ // Check the sort criterias
+ // @todo
+
+ // Check the relationships
+ // @todo
+
+ }
+}
+
+/**
+ * Tests the field_field handler.
+ * @TODO
+ * Check a entity-type with bundles
+ * Check a entity-type without bundles
+ * Check locale:disabled, locale:enabled and locale:enabled with another language
+ * Check revisions
+ */
+class viewsHandlerFieldFieldTest extends ViewsFieldApiTestHelper {
+ public $nodes;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Fieldapi: Field handler',
+ 'description' => 'Tests the field itself of the fieldapi integration',
+ 'group' => 'Views Modules'
+ );
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ // Setup basic fields.
+ $this->setUpFields(3);
+
+ // Setup a field with cardinality > 1.
+ $this->fields[3] = $field = field_create_field(array('field_name' => 'field_name_3', 'type' => 'text', 'cardinality' => FIELD_CARDINALITY_UNLIMITED));
+ // Setup a field that will have no value.
+ $this->fields[4] = $field = field_create_field(array('field_name' => 'field_name_4', 'type' => 'text', 'cardinality' => FIELD_CARDINALITY_UNLIMITED));
+
+ $this->setUpInstances();
+
+ $this->clearViewsCaches();
+
+ // Create some nodes.
+ $this->nodes = array();
+ for ($i = 0; $i < 3; $i++) {
+ $edit = array('type' => 'page');
+
+ for ($key = 0; $key < 3; $key++) {
+ $field = $this->fields[$key];
+ $edit[$field['field_name']][LANGUAGE_NONE][0]['value'] = $this->randomName(8);
+ }
+ for ($j = 0; $j < 5; $j++) {
+ $edit[$this->fields[3]['field_name']][LANGUAGE_NONE][$j]['value'] = $this->randomName(8);
+ }
+ // Set this field to be empty.
+ $edit[$this->fields[4]['field_name']] = array();
+
+ $this->nodes[$i] = $this->drupalCreateNode($edit);
+ }
+ }
+
+ public function testFieldRender() {
+ $this->_testSimpleFieldRender();
+ $this->_testFormatterSimpleFieldRender();
+ $this->_testMultipleFieldRender();
+ }
+
+ public function _testSimpleFieldRender() {
+ $view = $this->getFieldView();
+ $this->executeView($view);
+
+ // Tests that the rendered fields match the actual value of the fields.
+ for ($i = 0; $i < 3; $i++) {
+ for ($key = 0; $key < 2; $key++) {
+ $field = $this->fields[$key];
+ $rendered_field = $view->style_plugin->get_field($i, $field['field_name']);
+ $expected_field = $this->nodes[$i]->{$field['field_name']}[LANGUAGE_NONE][0]['value'];
+ $this->assertEqual($rendered_field, $expected_field);
+ }
+ }
+ }
+
+ /**
+ * Tests that fields with formatters runs as expected.
+ */
+ public function _testFormatterSimpleFieldRender() {
+ $view = $this->getFieldView();
+ $view->display['default']->display_options['fields'][$this->fields[0]['field_name']]['type'] = 'text_trimmed';
+ $view->display['default']->display_options['fields'][$this->fields[0]['field_name']]['settings'] = array(
+ 'trim_length' => 3,
+ );
+ $this->executeView($view);
+
+ // Take sure that the formatter works as expected.
+ // @TODO: actually there should be a specific formatter.
+ for ($i = 0; $i < 2; $i++) {
+ $rendered_field = $view->style_plugin->get_field($i, $this->fields[0]['field_name']);
+ $this->assertEqual(strlen($rendered_field), 3);
+ }
+ }
+
+ public function _testMultipleFieldRender() {
+ $view = $this->getFieldView();
+
+ // Test delta limit.
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['group_rows'] = TRUE;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_limit'] = 3;
+ $this->executeView($view);
+
+ for ($i = 0; $i < 3; $i++) {
+ $rendered_field = $view->style_plugin->get_field($i, $this->fields[3]['field_name']);
+ $items = array();
+ $pure_items = $this->nodes[$i]->{$this->fields[3]['field_name']}[LANGUAGE_NONE];
+ $pure_items = array_splice($pure_items, 0, 3);
+ foreach ($pure_items as $j => $item) {
+ $items[] = $pure_items[$j]['value'];
+ }
+ $this->assertEqual($rendered_field, implode(', ', $items), 'Take sure that the amount of items are limited.');
+ }
+
+ // Test that an empty field is rendered without error.
+ $rendered_field = $view->style_plugin->get_field(4, $this->fields[4]['field_name']);
+
+ $view->destroy();
+
+ // Test delta limit + offset
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['group_rows'] = TRUE;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_limit'] = 3;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_offset'] = 1;
+ $this->executeView($view);
+
+ for ($i = 0; $i < 3; $i++) {
+ $rendered_field = $view->style_plugin->get_field($i, $this->fields[3]['field_name']);
+ $items = array();
+ $pure_items = $this->nodes[$i]->{$this->fields[3]['field_name']}[LANGUAGE_NONE];
+ $pure_items = array_splice($pure_items, 1, 3);
+ foreach ($pure_items as $j => $item) {
+ $items[] = $pure_items[$j]['value'];
+ }
+ $this->assertEqual($rendered_field, implode(', ', $items), 'Take sure that the amount of items are limited.');
+ }
+ $view->destroy();
+
+ // Test delta limit + reverse.
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_offset'] = 0;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['group_rows'] = TRUE;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_limit'] = 3;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_reversed'] = TRUE;
+ $this->executeView($view);
+
+ for ($i = 0; $i < 3; $i++) {
+ $rendered_field = $view->style_plugin->get_field($i, $this->fields[3]['field_name']);
+ $items = array();
+ $pure_items = $this->nodes[$i]->{$this->fields[3]['field_name']}[LANGUAGE_NONE];
+ array_splice($pure_items, 0, -3);
+ $pure_items = array_reverse($pure_items);
+ foreach ($pure_items as $j => $item) {
+ $items[] = $pure_items[$j]['value'];
+ }
+ $this->assertEqual($rendered_field, implode(', ', $items), 'Take sure that the amount of items are limited.');
+ }
+ $view->destroy();
+
+ // Test delta first last.
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['group_rows'] = TRUE;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_limit'] = 0;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_first_last'] = TRUE;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_reversed'] = FALSE;
+ $this->executeView($view);
+
+ for ($i = 0; $i < 3; $i++) {
+ $rendered_field = $view->style_plugin->get_field($i, $this->fields[3]['field_name']);
+ $items = array();
+ $pure_items = $this->nodes[$i]->{$this->fields[3]['field_name']}[LANGUAGE_NONE];
+ $items[] = $pure_items[0]['value'];
+ $items[] = $pure_items[4]['value'];
+ $this->assertEqual($rendered_field, implode(', ', $items), 'Take sure that the amount of items are limited.');
+ }
+ $view->destroy();
+
+ // Test delta limit + custom seperator.
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_first_last'] = FALSE;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['delta_limit'] = 3;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['group_rows'] = TRUE;
+ $view->display['default']->display_options['fields'][$this->fields[3]['field_name']]['separator'] = ':';
+ $this->executeView($view);
+
+ for ($i = 0; $i < 3; $i++) {
+ $rendered_field = $view->style_plugin->get_field($i, $this->fields[3]['field_name']);
+ $items = array();
+ $pure_items = $this->nodes[$i]->{$this->fields[3]['field_name']}[LANGUAGE_NONE];
+ $pure_items = array_splice($pure_items, 0, 3);
+ foreach ($pure_items as $j => $item) {
+ $items[] = $pure_items[$j]['value'];
+ }
+ $this->assertEqual($rendered_field, implode(':', $items), 'Take sure that the amount of items are limited.');
+ }
+ }
+
+ protected function getFieldView() {
+ $view = new view;
+ $view->name = 'view_fieldapi';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'view_fieldapi';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'nid';
+ foreach ($this->fields as $key => $field) {
+ $handler->display->display_options['fields'][$field['field_name']]['id'] = $field['field_name'];
+ $handler->display->display_options['fields'][$field['field_name']]['table'] = 'field_data_' . $field['field_name'];
+ $handler->display->display_options['fields'][$field['field_name']]['field'] = $field['field_name'];
+ }
+ return $view;
+ }
+
+}
+
diff --git a/sites/all/modules/views/tests/handlers/views_handler_area_text.test b/sites/all/modules/views/tests/handlers/views_handler_area_text.test
new file mode 100644
index 000000000..9f30e0c52
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_area_text.test
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerAreaTextTest.
+ */
+
+/**
+ * Tests the text area handler.
+ *
+ * @see views_handler_area_text
+ */
+class ViewsHandlerAreaTextTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Area: Text',
+ 'description' => 'Test the core views_handler_area_text handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ public function testAreaText() {
+ $view = $this->getBasicView();
+
+ // add a text header
+ $string = $this->randomName();
+ $view->display['default']->handler->override_option('header', array(
+ 'area' => array(
+ 'id' => 'area',
+ 'table' => 'views',
+ 'field' => 'area',
+ 'content' => $string,
+ ),
+ ));
+
+ // Execute the view.
+ $this->executeView($view);
+
+ $view->display_handler->handlers['header']['area']->options['format'] = $this->randomString();
+ $this->assertEqual(NULL, $view->display_handler->handlers['header']['area']->render(), 'Non existant format should return nothing');
+
+ $view->display_handler->handlers['header']['area']->options['format'] = filter_default_format();
+ $this->assertEqual(check_markup($string), $view->display_handler->handlers['header']['area']->render(), 'Existant format should return something');
+
+ // Empty results, and it shouldn't be displayed .
+ $this->assertEqual('', $view->display_handler->handlers['header']['area']->render(TRUE), 'No result should lead to no header');
+ // Empty results, and it should be displayed.
+ $view->display_handler->handlers['header']['area']->options['empty'] = TRUE;
+ $this->assertEqual(check_markup($string), $view->display_handler->handlers['header']['area']->render(TRUE), 'No result, but empty enabled lead to a full header');
+ }
+
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_argument_null.test b/sites/all/modules/views/tests/handlers/views_handler_argument_null.test
new file mode 100644
index 000000000..e8650a383
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_argument_null.test
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerArgumentNullTest.
+ */
+
+/**
+ * Tests the core views_handler_argument_null handler.
+ */
+class ViewsHandlerArgumentNullTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Argument: Null',
+ 'description' => 'Test the core views_handler_argument_null handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['id']['argument']['handler'] = 'views_handler_argument_null';
+
+ return $data;
+ }
+
+ public function testAreaText() {
+ // Test validation
+ $view = $this->getBasicView();
+
+ // Add a null argument.
+ $string = $this->randomString();
+ $view->display['default']->handler->override_option('arguments', array(
+ 'null' => array(
+ 'id' => 'null',
+ 'table' => 'views',
+ 'field' => 'null',
+ ),
+ ));
+
+ $this->executeView($view);
+
+ // Make sure that the argument is not validated yet.
+ unset($view->argument['null']->argument_validated);
+ $this->assertTrue($view->argument['null']->validate_arg(26));
+ // test must_not_be option.
+ unset($view->argument['null']->argument_validated);
+ $view->argument['null']->options['must_not_be'] = TRUE;
+ $this->assertFalse($view->argument['null']->validate_arg(26), 'must_not_be returns FALSE, if there is an argument');
+ unset($view->argument['null']->argument_validated);
+ $this->assertTrue($view->argument['null']->validate_arg(NULL), 'must_not_be returns TRUE, if there is no argument');
+
+ // Test execution.
+ $view = $this->getBasicView();
+
+ // Add a argument, which has null as handler.
+ $string = $this->randomString();
+ $view->display['default']->handler->override_option('arguments', array(
+ 'id' => array(
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ ),
+ ));
+
+ $this->executeView($view, array(26));
+
+ // The argument should be ignored, so every result should return.
+ $this->assertEqual(5, count($view->result));
+ }
+
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_argument_string.test b/sites/all/modules/views/tests/handlers/views_handler_argument_string.test
new file mode 100644
index 000000000..d078abfe2
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_argument_string.test
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @file
+ * Definition of ViewsHandlerArgumentNullTest.
+ */
+
+/**
+ * Tests the core views_handler_argument_string handler.
+ */
+class ViewsHandlerArgumentStringTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Argument: String',
+ 'description' => 'Test the core views_handler_argument_string handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ /**
+ * Tests the glossary feature.
+ */
+ function testGlossary() {
+ // Setup some nodes, one with a, two with b and three with c.
+ $counter = 1;
+ foreach (array('a', 'b', 'c') as $char) {
+ for ($i = 0; $i < $counter; $i++) {
+ $edit = array(
+ 'title' => $char . $this->randomName(),
+ );
+ $this->drupalCreateNode($edit);
+ }
+ }
+
+ $view = $this->viewGlossary();
+ $view->init_display();
+ $this->executeView($view);
+
+ $count_field = 'nid';
+ foreach ($view->result as &$row) {
+ if (strpos($row->node_title, 'a') === 0) {
+ $this->assertEqual(1, $row->{$count_field});
+ }
+ if (strpos($row->node_title, 'b') === 0) {
+ $this->assertEqual(2, $row->{$count_field});
+ }
+ if (strpos($row->node_title, 'c') === 0) {
+ $this->assertEqual(3, $row->{$count_field});
+ }
+ }
+ }
+
+ /**
+ * Provide a test view for testGlossary.
+ *
+ * @see testGlossary
+ * @return view
+ */
+ function viewGlossary() {
+ $view = new view();
+ $view->name = 'test_glossary';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'test_glossary';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ /* Contextual filter: Content: Title */
+ $handler->display->display_options['arguments']['title']['id'] = 'title';
+ $handler->display->display_options['arguments']['title']['table'] = 'node';
+ $handler->display->display_options['arguments']['title']['field'] = 'title';
+ $handler->display->display_options['arguments']['title']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['title']['summary']['number_of_records'] = '0';
+ $handler->display->display_options['arguments']['title']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['title']['summary_options']['items_per_page'] = '25';
+ $handler->display->display_options['arguments']['title']['glossary'] = TRUE;
+ $handler->display->display_options['arguments']['title']['limit'] = '1';
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field.test b/sites/all/modules/views/tests/handlers/views_handler_field.test
new file mode 100644
index 000000000..9e6dfca25
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field.test
@@ -0,0 +1,314 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFieldTest.
+ */
+
+/**
+ * Tests the generic field handler
+ *
+ * @see views_handler_field
+ */
+class ViewsHandlerFieldTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field',
+ 'description' => 'Test the core views_handler_field handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ protected function setUp() {
+ parent::setUp();
+ $this->column_map = array(
+ 'views_test_name' => 'name',
+ );
+ }
+
+ function testEmpty() {
+ $this->_testHideIfEmpty();
+ $this->_testEmptyText();
+ }
+
+ /**
+ * Tests the hide if empty functionality.
+ *
+ * This tests alters the result to get easier and less coupled results.
+ */
+ function _testHideIfEmpty() {
+ $view = $this->getBasicView();
+ $view->init_display();
+ $this->executeView($view);
+
+ $column_map_reversed = array_flip($this->column_map);
+ $view->row_index = 0;
+ $random_name = $this->randomName();
+ $random_value = $this->randomName();
+
+ // Test when results are not rewritten and empty values are not hidden.
+ $view->field['name']->options['hide_alter_empty'] = FALSE;
+ $view->field['name']->options['hide_empty'] = FALSE;
+ $view->field['name']->options['empty_zero'] = FALSE;
+
+ // Test a valid string.
+ $view->result[0]->{$column_map_reversed['name']} = $random_name;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_name, 'By default, a string should not be treated as empty.');
+
+ // Test an empty string.
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'By default, "" should not be treated as empty.');
+
+ // Test zero as an integer.
+ $view->result[0]->{$column_map_reversed['name']} = 0;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, '0', 'By default, 0 should not be treated as empty.');
+
+ // Test zero as a string.
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "0", 'By default, "0" should not be treated as empty.');
+
+ // Test when results are not rewritten and non-zero empty values are hidden.
+ $view->field['name']->options['hide_alter_empty'] = TRUE;
+ $view->field['name']->options['hide_empty'] = TRUE;
+ $view->field['name']->options['empty_zero'] = FALSE;
+
+ // Test a valid string.
+ $view->result[0]->{$column_map_reversed['name']} = $random_name;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
+
+ // Test an empty string.
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If hide_empty is checked, "" should be treated as empty.');
+
+ // Test zero as an integer.
+ $view->result[0]->{$column_map_reversed['name']} = 0;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
+
+ // Test zero as a string.
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
+
+ // Test when results are not rewritten and all empty values are hidden.
+ $view->field['name']->options['hide_alter_empty'] = TRUE;
+ $view->field['name']->options['hide_empty'] = TRUE;
+ $view->field['name']->options['empty_zero'] = TRUE;
+
+ // Test zero as an integer.
+ $view->result[0]->{$column_map_reversed['name']} = 0;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, 0 should be treated as empty.');
+
+ // Test zero as a string.
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, "0" should be treated as empty.');
+
+ // Test when results are rewritten to a valid string and non-zero empty
+ // results are hidden.
+ $view->field['name']->options['hide_alter_empty'] = FALSE;
+ $view->field['name']->options['hide_empty'] = TRUE;
+ $view->field['name']->options['empty_zero'] = FALSE;
+ $view->field['name']->options['alter']['alter_text'] = TRUE;
+ $view->field['name']->options['alter']['text'] = $random_name;
+
+ // Test a valid string.
+ $view->result[0]->{$column_map_reversed['name']} = $random_value;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
+
+ // Test an empty string.
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
+
+ // Test zero as an integer.
+ $view->result[0]->{$column_map_reversed['name']} = 0;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
+
+ // Test zero as a string.
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
+
+ // Test when results are rewritten to an empty string and non-zero empty results are hidden.
+ $view->field['name']->options['hide_alter_empty'] = TRUE;
+ $view->field['name']->options['hide_empty'] = TRUE;
+ $view->field['name']->options['empty_zero'] = FALSE;
+ $view->field['name']->options['alter']['alter_text'] = TRUE;
+ $view->field['name']->options['alter']['text'] = "";
+
+ // Test a valid string.
+ $view->result[0]->{$column_map_reversed['name']} = $random_name;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
+
+ // Test an empty string.
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If the rewritten string is empty, "" should be treated as empty.');
+
+ // Test zero as an integer.
+ $view->result[0]->{$column_map_reversed['name']} = 0;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
+
+ // Test zero as a string.
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
+
+ // Test when results are rewritten to zero as a string and non-zero empty
+ // results are hidden.
+ $view->field['name']->options['hide_alter_empty'] = FALSE;
+ $view->field['name']->options['hide_empty'] = TRUE;
+ $view->field['name']->options['empty_zero'] = FALSE;
+ $view->field['name']->options['alter']['alter_text'] = TRUE;
+ $view->field['name']->options['alter']['text'] = "0";
+
+ // Test a valid string.
+ $view->result[0]->{$column_map_reversed['name']} = $random_name;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
+
+ // Test an empty string.
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
+
+ // Test zero as an integer.
+ $view->result[0]->{$column_map_reversed['name']} = 0;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
+
+ // Test zero as a string.
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
+
+ // Test when results are rewritten to a valid string and non-zero empty
+ // results are hidden.
+ $view->field['name']->options['hide_alter_empty'] = TRUE;
+ $view->field['name']->options['hide_empty'] = TRUE;
+ $view->field['name']->options['empty_zero'] = FALSE;
+ $view->field['name']->options['alter']['alter_text'] = TRUE;
+ $view->field['name']->options['alter']['text'] = $random_value;
+
+ // Test a valid string.
+ $view->result[0]->{$column_map_reversed['name']} = $random_name;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
+
+ // Test an empty string.
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If either the original or rewritten string is invalid, "" should be treated as empty.');
+
+ // Test zero as an integer.
+ $view->result[0]->{$column_map_reversed['name']} = 0;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
+
+ // Test zero as a string.
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
+
+ // Test when results are rewritten to zero as a string and all empty
+ // original values and results are hidden.
+ $view->field['name']->options['hide_alter_empty'] = TRUE;
+ $view->field['name']->options['hide_empty'] = TRUE;
+ $view->field['name']->options['empty_zero'] = TRUE;
+ $view->field['name']->options['alter']['alter_text'] = TRUE;
+ $view->field['name']->options['alter']['text'] = "0";
+
+ // Test a valid string.
+ $view->result[0]->{$column_map_reversed['name']} = $random_name;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If the rewritten string is zero, it should be treated as empty.');
+
+ // Test an empty string.
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If the rewritten string is zero, "" should be treated as empty.');
+
+ // Test zero as an integer.
+ $view->result[0]->{$column_map_reversed['name']} = 0;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If the rewritten string is zero, 0 should not be treated as empty.');
+
+ // Test zero as a string.
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "", 'If the rewritten string is zero, "0" should not be treated as empty.');
+ }
+
+ /**
+ * Tests the usage of the empty text.
+ */
+ function _testEmptyText() {
+ $view = $this->getBasicView();
+ $view->init_display();
+ $this->executeView($view);
+
+ $column_map_reversed = array_flip($this->column_map);
+ $view->row_index = 0;
+
+ $empty_text = $view->field['name']->options['empty'] = $this->randomName();
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
+
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
+
+ $view->result[0]->{$column_map_reversed['name']} = "0";
+ $view->field['name']->options['empty_zero'] = TRUE;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
+
+ $view->result[0]->{$column_map_reversed['name']} = "";
+ $view->field['name']->options['alter']['alter_text'] = TRUE;
+ $alter_text = $view->field['name']->options['alter']['text'] = $this->randomName();
+ $view->field['name']->options['hide_alter_empty'] = FALSE;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
+
+ $view->field['name']->options['hide_alter_empty'] = TRUE;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
+ }
+
+ /**
+ * Tests views_handler_field::is_value_empty().
+ */
+ function testIsValueEmpty() {
+ $view = $this->getBasicView();
+ $view->init_display();
+ $view->init_handlers();
+ $field = $view->field['name'];
+
+ $this->assertFalse($field->is_value_empty("not empty", TRUE), 'A normal string is not empty.');
+ $this->assertTrue($field->is_value_empty("not empty", TRUE, FALSE), 'A normal string which skips empty() can be seen as empty.');
+
+ $this->assertTrue($field->is_value_empty("", TRUE), '"" is considered as empty.');
+
+ $this->assertTrue($field->is_value_empty('0', TRUE), '"0" is considered as empty if empty_zero is TRUE.');
+ $this->assertTrue($field->is_value_empty(0, TRUE), '0 is considered as empty if empty_zero is TRUE.');
+ $this->assertFalse($field->is_value_empty('0', FALSE), '"0" is considered not as empty if empty_zero is FALSE.');
+ $this->assertFalse($field->is_value_empty(0, FALSE), '0 is considered not as empty if empty_zero is FALSE.');
+
+ $this->assertTrue($field->is_value_empty(NULL, TRUE, TRUE), 'Null should be always seen as empty, regardless of no_skip_empty.');
+ $this->assertTrue($field->is_value_empty(NULL, TRUE, FALSE), 'Null should be always seen as empty, regardless of no_skip_empty.');
+ }
+
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_boolean.test b/sites/all/modules/views/tests/handlers/views_handler_field_boolean.test
new file mode 100644
index 000000000..286b9425c
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_boolean.test
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFieldBooleanTest.
+ */
+
+/**
+ * Tests the core views_handler_field_boolean handler.
+ */
+class ViewsHandlerFieldBooleanTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: Boolean',
+ 'description' => 'Test the core views_handler_field_boolean handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function dataSet() {
+ // Use default dataset but remove the age from john and paul
+ $data = parent::dataSet();
+ $data[0]['age'] = 0;
+ $data[3]['age'] = 0;
+ return $data;
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['age']['field']['handler'] = 'views_handler_field_boolean';
+ return $data;
+ }
+
+ public function testFieldBoolean() {
+ $view = $this->getBasicView();
+
+ $view->display['default']->handler->override_option('fields', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ $this->executeView($view);
+
+ // This is john, which has no age, there are no custom formats defined, yet.
+ $this->assertEqual(t('No'), $view->field['age']->advanced_render($view->result[0]));
+ $this->assertEqual(t('Yes'), $view->field['age']->advanced_render($view->result[1]));
+
+ // Reverse the output.
+ $view->field['age']->options['not'] = TRUE;
+ $this->assertEqual(t('Yes'), $view->field['age']->advanced_render($view->result[0]));
+ $this->assertEqual(t('No'), $view->field['age']->advanced_render($view->result[1]));
+
+ unset($view->field['age']->options['not']);
+
+ // Use another output format.
+ $view->field['age']->options['type'] = 'true-false';
+ $this->assertEqual(t('False'), $view->field['age']->advanced_render($view->result[0]));
+ $this->assertEqual(t('True'), $view->field['age']->advanced_render($view->result[1]));
+
+ // test awesome unicode.
+ $view->field['age']->options['type'] = 'unicode-yes-no';
+ $this->assertEqual('✖', $view->field['age']->advanced_render($view->result[0]));
+ $this->assertEqual('✔', $view->field['age']->advanced_render($view->result[1]));
+
+ // Set a custom output format programmatically.
+ $view->field['age']->formats['test'] = array(t('Test-True'), t('Test-False'));
+ $view->field['age']->options['type'] = 'test';
+ $this->assertEqual(t('Test-False'), $view->field['age']->advanced_render($view->result[0]));
+ $this->assertEqual(t('Test-True'), $view->field['age']->advanced_render($view->result[1]));
+
+ // Set a custom output format through the UI using plain-text inputs.
+ $view->field['age']->options['type'] = 'custom';
+ $values = array(
+ 'false' => 'Nay',
+ 'true' => 'Yay',
+ );
+ $view->field['age']->options['type_custom_false'] = $values['false'];
+ $view->field['age']->options['type_custom_true'] = $values['true'];
+ $this->assertEqual($values['false'], $view->field['age']->advanced_render($view->result[0]));
+ $this->assertEqual($values['true'], $view->field['age']->advanced_render($view->result[1]));
+
+ // Set a custom output format through the UI using valid HTML inputs.
+ $view->field['age']->options['type'] = 'custom';
+ $values = array(
+ 'false' => '<div class="bar">Nay</div>',
+ 'true' => '<div class="foo">Yay</div>',
+ );
+ $view->field['age']->options['type_custom_false'] = $values['false'];
+ $view->field['age']->options['type_custom_true'] = $values['true'];
+ $this->assertEqual($values['false'], $view->field['age']->advanced_render($view->result[0]));
+ $this->assertEqual($values['true'], $view->field['age']->advanced_render($view->result[1]));
+
+ // Set a custom output format through the UI using unsafe inputs.
+ $view->field['age']->options['type'] = 'custom';
+ $values = array(
+ 'false' => '<script>alert("Nay");</script>',
+ 'true' => '<script>alert("Yay");</script>',
+ );
+ $view->field['age']->options['type_custom_false'] = $values['false'];
+ $view->field['age']->options['type_custom_true'] = $values['true'];
+ $this->assertNotEqual($values['false'], $view->field['age']->advanced_render($view->result[0]));
+ $this->assertNotEqual($values['true'], $view->field['age']->advanced_render($view->result[1]));
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_counter.test b/sites/all/modules/views/tests/handlers/views_handler_field_counter.test
new file mode 100644
index 000000000..2ddcb6f65
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_counter.test
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFilterCounterTest.
+ */
+
+/**
+ * Tests the views_handler_field_counter handler.
+ */
+class ViewsHandlerFilterCounterTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: Counter',
+ 'description' => 'Tests the views_handler_field_counter handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function testSimple() {
+ $view = $this->getBasicView();
+ $view->display['default']->handler->override_option('fields', array(
+ 'counter' => array(
+ 'id' => 'counter',
+ 'table' => 'views',
+ 'field' => 'counter',
+ 'relationship' => 'none',
+ ),
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ ));
+ $view->preview();
+
+ $this->assertEqual(1, $view->style_plugin->rendered_fields[0]['counter']);
+ $this->assertEqual(2, $view->style_plugin->rendered_fields[1]['counter']);
+ $this->assertEqual(3, $view->style_plugin->rendered_fields[2]['counter']);
+ $view->destroy();
+
+ $view = $this->getBasicView();
+ $rand_start = rand(5, 10);
+ $view->display['default']->handler->override_option('fields', array(
+ 'counter' => array(
+ 'id' => 'counter',
+ 'table' => 'views',
+ 'field' => 'counter',
+ 'relationship' => 'none',
+ 'counter_start' => $rand_start
+ ),
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ ));
+ $view->preview();
+
+ $this->assertEqual(0 + $rand_start, $view->style_plugin->rendered_fields[0]['counter']);
+ $this->assertEqual(1 + $rand_start, $view->style_plugin->rendered_fields[1]['counter']);
+ $this->assertEqual(2 + $rand_start, $view->style_plugin->rendered_fields[2]['counter']);
+ }
+
+ // @TODO: Write tests for pager.
+ function testPager() {
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_custom.test b/sites/all/modules/views/tests/handlers/views_handler_field_custom.test
new file mode 100644
index 000000000..b45fd1770
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_custom.test
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFieldCustomTest.
+ */
+
+/**
+ * Tests the core views_handler_field_custom handler.
+ */
+class ViewsHandlerFieldCustomTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: Custom',
+ 'description' => 'Test the core views_handler_field_custom handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['name']['field']['handler'] = 'views_handler_field_custom';
+ return $data;
+ }
+
+ public function testFieldCustom() {
+ $view = $this->getBasicView();
+
+ // Alter the text of the field to a random string.
+ $random = $this->randomName();
+ $view->display['default']->handler->override_option('fields', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'alter' => array(
+ 'text' => $random,
+ ),
+ ),
+ ));
+
+ $this->executeView($view);
+
+ $this->assertEqual($random, $view->style_plugin->get_field(0, 'name'));
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_date.test b/sites/all/modules/views/tests/handlers/views_handler_field_date.test
new file mode 100644
index 000000000..794414294
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_date.test
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFieldDateTest.
+ */
+
+/**
+ * Tests the core views_handler_field_date handler.
+ */
+class ViewsHandlerFieldDateTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: Date',
+ 'description' => 'Test the core views_handler_field_date handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['created']['field']['handler'] = 'views_handler_field_date';
+ return $data;
+ }
+
+ public function testFieldDate() {
+ $view = $this->getBasicView();
+
+ $view->display['default']->handler->override_option('fields', array(
+ 'created' => array(
+ 'id' => 'created',
+ 'table' => 'views_test',
+ 'field' => 'created',
+ 'relationship' => 'none',
+ // c is iso 8601 date format @see http://php.net/manual/en/function.date.php
+ 'custom_date_format' => 'c',
+ 'second_date_format' => 'custom',
+ 'second_date_format_custom' => 'c',
+ ),
+ ));
+ $time = gmmktime(0, 0, 0, 1, 1, 2000);
+
+ $this->executeView($view);
+
+ $timezones = array(
+ NULL,
+ 'UTC',
+ 'America/New_York',
+ );
+ foreach ($timezones as $timezone) {
+ $dates = array(
+ 'small' => format_date($time, 'small', '', $timezone),
+ 'medium' => format_date($time, 'medium', '', $timezone),
+ 'large' => format_date($time, 'large', '', $timezone),
+ 'custom' => format_date($time, 'custom', 'c', $timezone),
+ 'today time ago custom' => format_date($time, 'custom', 'c', $timezone),
+ 'today time ago' => t('%time ago', array('%time' => format_interval(120, 2))),
+ );
+ $this->assertRenderedDatesEqual($view, $dates, $timezone);
+ }
+
+ $intervals = array(
+ 'raw time ago' => format_interval(REQUEST_TIME - $time, 2),
+ 'time ago' => t('%time ago', array('%time' => format_interval(REQUEST_TIME - $time, 2))),
+ // TODO write tests for them
+// 'raw time span' => format_interval(REQUEST_TIME - $time, 2),
+// 'time span' => t('%time hence', array('%time' => format_interval(REQUEST_TIME - $time, 2))),
+ );
+ $this->assertRenderedDatesEqual($view, $intervals);
+ }
+
+ protected function assertRenderedDatesEqual($view, $map, $timezone = NULL) {
+ foreach ($map as $date_format => $expected_result) {
+ $check_result_number = 0;
+
+ // If it's "today time ago" format we have to check the 6th element.
+ if ($date_format == 'today time ago') {
+ $check_result_number = 5;
+ }
+
+ // Correct the date format.
+ if ($date_format == 'today time ago custom') {
+ $date_format = 'today time ago';
+ }
+ $view->field['created']->options['date_format'] = $date_format;
+ $t_args = array(
+ '%value' => $expected_result,
+ '%format' => $date_format,
+ );
+ if (isset($timezone)) {
+ $t_args['%timezone'] = $timezone;
+ $message = t('Value %value in %format format for timezone %timezone matches.', $t_args);
+ $view->field['created']->options['timezone'] = $timezone;
+ }
+ else {
+ $message = t('Value %value in %format format matches.', $t_args);
+ }
+ $actual_result = $view->field['created']->advanced_render($view->result[$check_result_number]);
+ $this->assertEqual($expected_result, $actual_result, $message);
+ }
+ }
+
+ /**
+ * Appends dataSet() with a data row for "today time ago" format testing.
+ */
+ protected function dataSet() {
+ $data = parent::dataSet();
+ $data[] = array(
+ 'name' => 'David',
+ 'age' => 25,
+ 'job' => 'Singer',
+ 'created' => REQUEST_TIME - 120,
+ );
+
+ return $data;
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_file_extension.test b/sites/all/modules/views/tests/handlers/views_handler_field_file_extension.test
new file mode 100644
index 000000000..ab8b0a933
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_file_extension.test
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFileExtensionTest.
+ */
+
+/**
+ * Tests the views_handler_field_file_extension handler.
+ */
+class ViewsHandlerFileExtensionTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: File extension',
+ 'description' => 'Test the views_handler_field_file_extension handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function dataSet() {
+ $data = parent::dataSet();
+ $data[0]['name'] = 'file.png';
+ $data[1]['name'] = 'file.tar';
+ $data[2]['name'] = 'file.tar.gz';
+ $data[3]['name'] = 'file';
+
+ return $data;
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['name']['field']['handler'] = 'views_handler_field_file_extension';
+ $data['views_test']['name']['real field'] = 'name';
+
+ return $data;
+ }
+
+ /**
+ * Tests the 'extension_detect_tar' handler option.
+ */
+ public function testFileExtensionTarOption() {
+ $view = $this->getBasicView();
+
+ $view->display['default']->handler->override_option('fields', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ ),
+ ));
+
+ $this->executeView($view);
+
+ // Test without the tar option.
+ $this->assertEqual($view->field['name']->advanced_render($view->result[0]), 'png');
+ $this->assertEqual($view->field['name']->advanced_render($view->result[1]), 'tar');
+ $this->assertEqual($view->field['name']->advanced_render($view->result[2]), 'gz');
+ $this->assertEqual($view->field['name']->advanced_render($view->result[3]), '');
+ // Test with the tar option.
+ $view->field['name']->options['extension_detect_tar'] = TRUE;
+ $this->assertEqual($view->field['name']->advanced_render($view->result[0]), 'png');
+ $this->assertEqual($view->field['name']->advanced_render($view->result[1]), 'tar');
+ $this->assertEqual($view->field['name']->advanced_render($view->result[2]), 'tar.gz');
+ $this->assertEqual($view->field['name']->advanced_render($view->result[3]), '');
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_file_size.test b/sites/all/modules/views/tests/handlers/views_handler_field_file_size.test
new file mode 100644
index 000000000..8652754fe
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_file_size.test
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerTestFileSize.
+ */
+
+/**
+ * Tests the core views_handler_field_file_size handler.
+ *
+ * @see CommonXssUnitTest
+ */
+class ViewsHandlerTestFileSize extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: file_size',
+ 'description' => 'Test the core views_handler_field_file_size handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function dataSet() {
+ $data = parent::dataSet();
+ $data[0]['age'] = 0;
+ $data[1]['age'] = 10;
+ $data[2]['age'] = 1000;
+ $data[3]['age'] = 10000;
+
+ return $data;
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['age']['field']['handler'] = 'views_handler_field_file_size';
+
+ return $data;
+ }
+
+ public function testFieldFileSize() {
+ $view = $this->getBasicView();
+
+ $view->display['default']->handler->override_option('fields', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ ),
+ ));
+
+ $this->executeView($view);
+
+ // Test with the formatted option.
+ $this->assertEqual($view->field['age']->advanced_render($view->result[0]), '');
+ $this->assertEqual($view->field['age']->advanced_render($view->result[1]), '10 bytes');
+ $this->assertEqual($view->field['age']->advanced_render($view->result[2]), '1000 bytes');
+ $this->assertEqual($view->field['age']->advanced_render($view->result[3]), '9.77 KB');
+ // Test with the bytes option.
+ $view->field['age']->options['file_size_display'] = 'bytes';
+ $this->assertEqual($view->field['age']->advanced_render($view->result[0]), '');
+ $this->assertEqual($view->field['age']->advanced_render($view->result[1]), 10);
+ $this->assertEqual($view->field['age']->advanced_render($view->result[2]), 1000);
+ $this->assertEqual($view->field['age']->advanced_render($view->result[3]), 10000);
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_math.test b/sites/all/modules/views/tests/handlers/views_handler_field_math.test
new file mode 100644
index 000000000..ac33ac4a4
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_math.test
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFieldMath.
+ */
+
+/**
+ * Tests the core views_handler_field_math handler.
+ */
+class ViewsHandlerFieldMath extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: Math',
+ 'description' => 'Test the core views_handler_field_math handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ return $data;
+ }
+
+ public function testFieldCustom() {
+ $view = $this->getBasicView();
+
+ // Alter the text of the field to a random string.
+ $rand1 = rand(0, 100);
+ $rand2 = rand(0, 100);
+ $view->display['default']->handler->override_option('fields', array(
+ 'expression' => array(
+ 'id' => 'expression',
+ 'table' => 'views',
+ 'field' => 'expression',
+ 'relationship' => 'none',
+ 'expression' => $rand1 . ' + ' . $rand2,
+ ),
+ ));
+
+ $this->executeView($view);
+
+ $this->assertEqual($rand1 + $rand2, $view->style_plugin->get_field(0, 'expression'));
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_url.test b/sites/all/modules/views/tests/handlers/views_handler_field_url.test
new file mode 100644
index 000000000..527e94fb4
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_url.test
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFieldUrlTest.
+ */
+
+/**
+ * Tests the core views_handler_field_url handler.
+ */
+class ViewsHandlerFieldUrlTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: Url',
+ 'description' => 'Test the core views_handler_field_url handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['name']['field']['handler'] = 'views_handler_field_url';
+ return $data;
+ }
+
+ public function testFieldUrl() {
+ $view = $this->getBasicView();
+
+ $view->display['default']->handler->override_option('fields', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'display_as_link' => FALSE,
+ ),
+ ));
+
+ $this->executeView($view);
+
+ $this->assertEqual('John', $view->field['name']->advanced_render($view->result[0]));
+
+ // Make the url a link.
+ $view->delete();
+ $view = $this->getBasicView();
+
+ $view->display['default']->handler->override_option('fields', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ $this->executeView($view);
+
+ $this->assertEqual(l('John', 'John'), $view->field['name']->advanced_render($view->result[0]));
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_xss.test b/sites/all/modules/views/tests/handlers/views_handler_field_xss.test
new file mode 100644
index 000000000..65a1ce282
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_field_xss.test
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerTestXss.
+ */
+
+/**
+ * Tests the core views_handler_field_css handler.
+ *
+ * @see CommonXssUnitTest
+ */
+class ViewsHandlerTestXss extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field: Xss',
+ 'description' => 'Test the core views_handler_field_css handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function dataHelper() {
+ $map = array(
+ 'John' => 'John',
+ "Foo\xC0barbaz" => '',
+ 'Fooÿñ' => 'Fooÿñ'
+ );
+
+ return $map;
+ }
+
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['name']['field']['handler'] = 'views_handler_field_xss';
+
+ return $data;
+ }
+
+ public function testFieldXss() {
+ $view = $this->getBasicView();
+
+ $view->display['default']->handler->override_option('fields', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ ),
+ ));
+
+ $this->executeView($view);
+
+ $counter = 0;
+ foreach ($this->dataHelper() as $input => $expected_result) {
+ $view->result[$counter]->views_test_name = $input;
+ $this->assertEqual($view->field['name']->advanced_render($view->result[$counter]), $expected_result);
+ $counter++;
+ }
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_filter_combine.test b/sites/all/modules/views/tests/handlers/views_handler_filter_combine.test
new file mode 100644
index 000000000..99bf1eb55
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_filter_combine.test
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFilterCombineTest.
+ */
+
+/**
+ * Tests the combine filter handler.
+ */
+class ViewsHandlerFilterCombineTest extends ViewsSqlTest {
+ var $column_map = array();
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter: Combine',
+ 'description' => 'Tests the combine filter handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->column_map = array(
+ 'views_test_name' => 'name',
+ 'views_test_job' => 'job',
+ );
+ }
+
+ protected function getBasicView() {
+ $view = parent::getBasicView();
+ $fields = $view->display['default']->handler->options['fields'];
+ $view->display['default']->display_options['fields']['job'] = array(
+ 'id' => 'job',
+ 'table' => 'views_test',
+ 'field' => 'job',
+ 'relationship' => 'none',
+ );
+ return $view;
+ }
+
+ public function testFilterCombineContains() {
+ $view = $this->getBasicView();
+
+ // Change the filtering.
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'id' => 'combine',
+ 'table' => 'views',
+ 'field' => 'combine',
+ 'relationship' => 'none',
+ 'operator' => 'contains',
+ 'fields' => array(
+ 'name',
+ 'job',
+ ),
+ 'value' => 'ing',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ 'job' => 'Singer',
+ ),
+ array(
+ 'name' => 'George',
+ 'job' => 'Singer',
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'job' => 'Drummer',
+ ),
+ array(
+ 'name' => 'Ginger',
+ 'job' => NULL,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ /**
+ * Additional data to test the NULL issue.
+ */
+ protected function dataSet() {
+ $data_set = parent::dataSet();
+ $data_set[] = array(
+ 'name' => 'Ginger',
+ 'age' => 25,
+ 'job' => NULL,
+ 'created' => gmmktime(0, 0, 0, 1, 2, 2000),
+ );
+ return $data_set;
+ }
+
+ /**
+ * Allow {views_test}.job to be NULL.
+ */
+ protected function schemaDefinition() {
+ $schema = parent::schemaDefinition();
+ unset($schema['views_test']['fields']['job']['not null']);
+ return $schema;
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_filter_date.test b/sites/all/modules/views/tests/handlers/views_handler_filter_date.test
new file mode 100644
index 000000000..8b92ccb59
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_filter_date.test
@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFilterDateTest.
+ */
+
+/**
+ * Tests the core views_handler_filter_date handler.
+ */
+class ViewsHandlerFilterDateTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter: Date',
+ 'description' => 'Test the core views_handler_filter_date handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ // Add some basic test nodes.
+ $this->nodes = array();
+ $this->nodes[] = $this->drupalCreateNode(array('created' => 100000));
+ $this->nodes[] = $this->drupalCreateNode(array('created' => 200000));
+ $this->nodes[] = $this->drupalCreateNode(array('created' => 300000));
+ $this->nodes[] = $this->drupalCreateNode(array('created' => time() + 86400));
+
+ $this->map = array(
+ 'nid' => 'nid',
+ );
+ $this->enableViewsUi();
+ }
+
+ /**
+ * Test the general offset functionality.
+ */
+ function testOffset() {
+ $view = $this->views_test_offset();
+ // Test offset for simple operator.
+ $view->set_display('default');
+ $view->init_handlers();
+ $view->filter['created']->operator = '>';
+ $view->filter['created']->value['type'] = 'offset';
+ $view->filter['created']->value['value'] = '+1 hour';
+ $view->execute_display('default');
+ $expected_result = array(
+ array('nid' => $this->nodes[3]->nid),
+ );
+ $this->assertIdenticalResultset($view, $expected_result, $this->map);
+ $view->destroy();
+
+ // Test "first day of" type of relative dates for simple operator.
+ $view->set_display('default');
+ $view->init_handlers();
+ $view->filter['created']->operator = '<';
+ $view->filter['created']->value['type'] = 'offset';
+ $view->filter['created']->value['value'] = 'last day of January 1970';
+ $view->execute_display('default');
+ $expected_result = array(
+ array('nid' => $this->nodes[0]->nid),
+ array('nid' => $this->nodes[1]->nid),
+ array('nid' => $this->nodes[2]->nid),
+ );
+ $this->assertIdenticalResultset($view, $expected_result, $this->map);
+ $view->destroy();
+
+ // Test offset for between operator.
+ $view->set_display('default');
+ $view->init_handlers();
+ $view->filter['created']->operator = 'between';
+ $view->filter['created']->value['type'] = 'offset';
+ $view->filter['created']->value['max'] = '+2 days';
+ $view->filter['created']->value['min'] = '+1 hour';
+ $view->execute_display('default');
+ $expected_result = array(
+ array('nid' => $this->nodes[3]->nid),
+ );
+ $this->assertIdenticalResultset($view, $expected_result, $this->map);
+ $view->destroy();
+
+ // Test "first day of" type of relative dates for between operator.
+ $view->set_display('default');
+ $view->init_handlers();
+ $view->filter['created']->operator = 'between';
+ $view->filter['created']->value['type'] = 'offset';
+ $view->filter['created']->value['max'] = 'last day of January 1970';
+ $view->filter['created']->value['min'] = 'first day of January 1970';
+ $view->execute_display('default');
+ $expected_result = array(
+ array('nid' => $this->nodes[0]->nid),
+ array('nid' => $this->nodes[1]->nid),
+ array('nid' => $this->nodes[2]->nid),
+ );
+ $this->assertIdenticalResultset($view, $expected_result, $this->map);
+ $view->destroy();
+ }
+
+
+ /**
+ * Tests the filter operator between/not between.
+ */
+ function testBetween() {
+ // Test between with min and max.
+ $view = $this->views_test_between();
+ $view->set_display('default');
+ $view->init_handlers();
+ $view->filter['created']->operator = 'between';
+ $view->filter['created']->value['min'] = format_date(150000, 'custom', 'Y-m-d H:s');
+ $view->filter['created']->value['max'] = format_date(250000, 'custom', 'Y-m-d H:s');
+ $view->execute_display('default');
+ $expected_result = array(
+ array('nid' => $this->nodes[1]->nid),
+ );
+ $this->assertIdenticalResultset($view, $expected_result, $this->map);
+ $view->destroy();
+
+ // Test between with just max.
+ $view = $this->views_test_between();
+ $view->set_display('default');
+ $view->init_handlers();
+ $view->filter['created']->operator = 'between';
+ $view->filter['created']->value['max'] = format_date(250000, 'custom', 'Y-m-d H:s');
+ $view->execute_display('default');
+ $expected_result = array(
+ array('nid' => $this->nodes[0]->nid),
+ array('nid' => $this->nodes[1]->nid),
+ );
+ $this->assertIdenticalResultset($view, $expected_result, $this->map);
+ $view->destroy();
+
+ // Test not between with min and max.
+ $view = $this->views_test_between();
+ $view->set_display('default');
+ $view->init_handlers();
+ $view->filter['created']->operator = 'not between';
+ $view->filter['created']->value['min'] = format_date(150000, 'custom', 'Y-m-d H:s');
+ $view->filter['created']->value['max'] = format_date(250000, 'custom', 'Y-m-d H:s');
+ $view->execute_display('default');
+ $expected_result = array(
+ array('nid' => $this->nodes[0]->nid),
+ array('nid' => $this->nodes[2]->nid),
+ array('nid' => $this->nodes[3]->nid),
+ );
+ $this->assertIdenticalResultset($view, $expected_result, $this->map);
+ $view->destroy();
+
+ // Test not between with just max.
+ $view = $this->views_test_between();
+ $view->set_display('default');
+ $view->init_handlers();
+ $view->filter['created']->operator = 'not between';
+ $view->filter['created']->value['max'] = format_date(150000, 'custom', 'Y-m-d H:s');
+ $view->execute_display('default');
+ $expected_result = array(
+ array('nid' => $this->nodes[1]->nid),
+ array('nid' => $this->nodes[2]->nid),
+ array('nid' => $this->nodes[3]->nid),
+ );
+ $this->assertIdenticalResultset($view, $expected_result, $this->map);
+ $view->destroy();
+ }
+
+ /**
+ * Make sure the validation callbacks works.
+ */
+ function testUiValidation() {
+ $view = $this->views_test_between();
+ $view->save();
+
+ $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration'));
+ $this->drupalLogin($admin_user);
+ menu_rebuild();
+ $this->drupalGet('admin/structure/views/view/test_filter_date_between/edit');
+ $this->drupalGet('admin/structure/views/nojs/config-item/test_filter_date_between/default/filter/created');
+
+ $edit = array();
+ // Generate a definitive wrong value, which should be checked by validation.
+ $edit['options[value][value]'] = $this->randomString() . '-------';
+ $this->drupalPost(NULL, $edit, t('Apply'));
+ $this->assertText(t('Invalid date format.'), 'Make sure that validation is runned and the invalidate date format is identified.');
+ }
+
+ function views_test_between() {
+ $view = new view;
+ $view->name = 'test_filter_date_between';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->human_name = '';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Nid */
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'nid';
+ /* Filter criterion: Content: Post date */
+ $handler->display->display_options['filters']['created']['id'] = 'created';
+ $handler->display->display_options['filters']['created']['table'] = 'node';
+ $handler->display->display_options['filters']['created']['field'] = 'created';
+
+ return $view;
+ }
+
+ function views_test_offset() {
+ $view = $this->views_test_between();
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_filter_equality.test b/sites/all/modules/views/tests/handlers/views_handler_filter_equality.test
new file mode 100644
index 000000000..5bb48c8df
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_filter_equality.test
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFilterEqualityTest.
+ */
+
+/**
+ * Tests the core views_handler_filter_equality handler.
+ */
+class ViewsHandlerFilterEqualityTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter: Equality',
+ 'description' => 'Test the core views_handler_filter_equality handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->column_map = array(
+ 'views_test_name' => 'name',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['name']['filter']['handler'] = 'views_handler_filter_equality';
+
+ return $data;
+ }
+
+ function testEqual() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'operator' => '=',
+ 'value' => array('value' => 'Ringo'),
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ public function testEqualGroupedExposed() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: =, Value: Ringo
+ $filters['name']['group_info']['default_group'] = 1;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testNotEqual() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'operator' => '!=',
+ 'value' => array('value' => 'Ringo'),
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'George',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ array(
+ 'name' => 'Meredith',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ public function testEqualGroupedNotExposed() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: !=, Value: Ringo
+ $filters['name']['group_info']['default_group'] = 2;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'George',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ array(
+ 'name' => 'Meredith',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ protected function getGroupedExposedFilters() {
+ $filters = array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'exposed' => TRUE,
+ 'expose' => array(
+ 'operator' => 'name_op',
+ 'label' => 'name',
+ 'identifier' => 'name',
+ ),
+ 'is_grouped' => TRUE,
+ 'group_info' => array(
+ 'label' => 'name',
+ 'identifier' => 'name',
+ 'default_group' => 'All',
+ 'group_items' => array(
+ 1 => array(
+ 'title' => 'Name is equal to Ringo',
+ 'operator' => '=',
+ 'value' => array('value' => 'Ringo'),
+ ),
+ 2 => array(
+ 'title' => 'Name is not equal to Ringo',
+ 'operator' => '!=',
+ 'value' => array('value' => 'Ringo'),
+ ),
+ ),
+ ),
+ ),
+ );
+ return $filters;
+ }
+
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_filter_in_operator.test b/sites/all/modules/views/tests/handlers/views_handler_filter_in_operator.test
new file mode 100644
index 000000000..3a20f8cc9
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_filter_in_operator.test
@@ -0,0 +1,196 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFilterInOperator.
+ */
+
+/**
+ * Tests the core views_handler_filter_in_operator handler.
+ */
+class ViewsHandlerFilterInOperator extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter: in_operator',
+ 'description' => 'Test the core views_handler_filter_in_operator handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['age']['filter']['handler'] = 'views_handler_filter_in_operator';
+
+ return $data;
+ }
+
+ public function testFilterInOperatorSimple() {
+ $view = $this->getBasicView();
+
+ // Add a in_operator ordering.
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'field' => 'age',
+ 'table' => 'views_test',
+ 'value' => array(26, 30),
+ 'operator' => 'in',
+ ),
+ ));
+
+ $this->executeView($view);
+
+ $expected_result = array(
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ array(
+ 'name' => 'Meredith',
+ 'age' => 30,
+ ),
+ );
+
+ $this->assertEqual(2, count($view->result));
+ $this->assertIdenticalResultset($view, $expected_result, array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+
+ $view->delete();
+ $view = $this->getBasicView();
+
+ // Add a in_operator ordering.
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'field' => 'age',
+ 'table' => 'views_test',
+ 'value' => array(26, 30),
+ 'operator' => 'not in',
+ ),
+ ));
+
+ $this->executeView($view);
+
+ $expected_result = array(
+ array(
+ 'name' => 'John',
+ 'age' => 25,
+ ),
+ array(
+ 'name' => 'George',
+ 'age' => 27,
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ ),
+ );
+
+ $this->assertEqual(3, count($view->result));
+ $this->assertIdenticalResultset($view, $expected_result, array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+ }
+
+ public function testFilterInOperatorGroupedExposedSimple() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Age, Operator: in, Value: 26, 30
+ $filters['age']['group_info']['default_group'] = 1;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $expected_result = array(
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ array(
+ 'name' => 'Meredith',
+ 'age' => 30,
+ ),
+ );
+
+ $this->assertEqual(2, count($view->result));
+ $this->assertIdenticalResultset($view, $expected_result, array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+ }
+
+ public function testFilterNotInOperatorGroupedExposedSimple() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Age, Operator: in, Value: 26, 30
+ $filters['age']['group_info']['default_group'] = 2;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $expected_result = array(
+ array(
+ 'name' => 'John',
+ 'age' => 25,
+ ),
+ array(
+ 'name' => 'George',
+ 'age' => 27,
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ ),
+ );
+
+ $this->assertEqual(3, count($view->result));
+ $this->assertIdenticalResultset($view, $expected_result, array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+ }
+
+ protected function getGroupedExposedFilters() {
+ $filters = array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'exposed' => TRUE,
+ 'expose' => array(
+ 'operator' => 'age_op',
+ 'label' => 'age',
+ 'identifier' => 'age',
+ ),
+ 'is_grouped' => TRUE,
+ 'group_info' => array(
+ 'label' => 'age',
+ 'identifier' => 'age',
+ 'default_group' => 'All',
+ 'group_items' => array(
+ 1 => array(
+ 'title' => 'Age is one of 26, 30',
+ 'operator' => 'in',
+ 'value' => array(26, 30),
+ ),
+ 2 => array(
+ 'title' => 'Age is not one of 26, 30',
+ 'operator' => 'not in',
+ 'value' => array(26, 30),
+ ),
+ ),
+ ),
+ ),
+ );
+ return $filters;
+ }
+
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_filter_numeric.test b/sites/all/modules/views/tests/handlers/views_handler_filter_numeric.test
new file mode 100644
index 000000000..2ab1aea00
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_filter_numeric.test
@@ -0,0 +1,409 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFilterNumericTest.
+ */
+
+/**
+ * Tests the numeric filter handler.
+ */
+class ViewsHandlerFilterNumericTest extends ViewsSqlTest {
+ var $column_map = array();
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter: Numeric',
+ 'description' => 'Tests the numeric filter handler',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->column_map = array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['age']['filter']['allow empty'] = TRUE;
+ $data['views_test']['id']['filter']['allow empty'] = FALSE;
+
+ return $data;
+ }
+
+ public function testFilterNumericSimple() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'operator' => '=',
+ 'value' => array('value' => 28),
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ public function testFilterNumericExposedGroupedSimple() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Age, Operator: =, Value: 28
+ $filters['age']['group_info']['default_group'] = 1;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ public function testFilterNumericBetween() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'operator' => 'between',
+ 'value' => array(
+ 'min' => 26,
+ 'max' => 29,
+ ),
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'George',
+ 'age' => 27,
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ ),
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+
+ // test not between
+ $view->delete();
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'operator' => 'not between',
+ 'value' => array(
+ 'min' => 26,
+ 'max' => 29,
+ ),
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ 'age' => 25,
+ ),
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ array(
+ 'name' => 'Meredith',
+ 'age' => 30,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ public function testFilterNumericExposedGroupedBetween() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Age, Operator: between, Value: 26 and 29
+ $filters['age']['group_info']['default_group'] = 2;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'George',
+ 'age' => 27,
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ ),
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ public function testFilterNumericExposedGroupedNotBetween() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Age, Operator: between, Value: 26 and 29
+ $filters['age']['group_info']['default_group'] = 3;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ 'age' => 25,
+ ),
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ array(
+ 'name' => 'Meredith',
+ 'age' => 30,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ public function testFilterNumericEmpty() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'operator' => 'empty',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+
+ $view->delete();
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'operator' => 'not empty',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ 'age' => 25,
+ ),
+ array(
+ 'name' => 'George',
+ 'age' => 27,
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ ),
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ array(
+ 'name' => 'Meredith',
+ 'age' => 30,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ public function testFilterNumericExposedGroupedEmpty() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Age, Operator: empty, Value:
+ $filters['age']['group_info']['default_group'] = 4;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+
+ $this->executeView($view);
+ $resultset = array(
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ public function testFilterNumericExposedGroupedNotEmpty() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Age, Operator: empty, Value:
+ $filters['age']['group_info']['default_group'] = 5;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ 'age' => 25,
+ ),
+ array(
+ 'name' => 'George',
+ 'age' => 27,
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ ),
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ array(
+ 'name' => 'Meredith',
+ 'age' => 30,
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ public function testAllowEmpty() {
+ $view = $this->getBasicView();
+
+ $view->display['default']->handler->override_option('filters', array(
+ 'id' => array(
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ $view->set_display('default');
+ $view->init_handlers();
+
+ $id_operators = $view->filter['id']->operators();
+ $age_operators = $view->filter['age']->operators();
+
+ $this->assertFalse(isset($id_operators['empty']));
+ $this->assertFalse(isset($id_operators['not empty']));
+ $this->assertTrue(isset($age_operators['empty']));
+ $this->assertTrue(isset($age_operators['not empty']));
+ }
+
+ protected function getGroupedExposedFilters() {
+ $filters = array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'exposed' => TRUE,
+ 'expose' => array(
+ 'operator' => 'age_op',
+ 'label' => 'age',
+ 'identifier' => 'age',
+ ),
+ 'is_grouped' => TRUE,
+ 'group_info' => array(
+ 'label' => 'age',
+ 'identifier' => 'age',
+ 'default_group' => 'All',
+ 'group_items' => array(
+ 1 => array(
+ 'title' => 'Age is 28',
+ 'operator' => '=',
+ 'value' => array('value' => 28),
+ ),
+ 2 => array(
+ 'title' => 'Age is between 26 and 29',
+ 'operator' => 'between',
+ 'value' => array(
+ 'min' => 26,
+ 'max' => 29,
+ ),
+ ),
+ 3 => array(
+ 'title' => 'Age is not between 26 and 29',
+ 'operator' => 'not between',
+ 'value' => array(
+ 'min' => 26,
+ 'max' => 29,
+ ),
+ ),
+ 4 => array(
+ 'title' => 'Age is empty',
+ 'operator' => 'empty',
+ ),
+ 5 => array(
+ 'title' => 'Age is not empty',
+ 'operator' => 'not empty',
+ ),
+ ),
+ ),
+ ),
+ );
+ return $filters;
+ }
+
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_filter_string.test b/sites/all/modules/views/tests/handlers/views_handler_filter_string.test
new file mode 100644
index 000000000..ee74a2825
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_filter_string.test
@@ -0,0 +1,810 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerFilterStringTest.
+ */
+
+/**
+ * Tests the core views_handler_filter_string handler.
+ */
+class ViewsHandlerFilterStringTest extends ViewsSqlTest {
+ var $column_map = array();
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter: String',
+ 'description' => 'Tests the core views_handler_filter_string handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->column_map = array(
+ 'views_test_name' => 'name',
+ );
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['name']['filter']['allow empty'] = TRUE;
+ $data['views_test']['job']['filter']['allow empty'] = FALSE;
+ $data['views_test']['description'] = $data['views_test']['name'];
+
+ return $data;
+ }
+
+ protected function schemaDefinition() {
+ $schema = parent::schemaDefinition();
+ $schema['views_test']['fields']['description'] = array(
+ 'description' => "A person's description",
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ );
+
+ return $schema;
+ }
+
+ /**
+ * An extended test dataset.
+ */
+ protected function dataSet() {
+ $dataset = parent::dataSet();
+ $dataset[0]['description'] = 'John Winston Ono Lennon, MBE (9 October 1940 – 8 December 1980) was an English musician and singer-songwriter who rose to worldwide fame as one of the founding members of The Beatles, one of the most commercially successful and critically acclaimed acts in the history of popular music. Along with fellow Beatle Paul McCartney, he formed one of the most successful songwriting partnerships of the 20th century.';
+ $dataset[1]['description'] = 'George Harrison,[1] MBE (25 February 1943 – 29 November 2001)[2] was an English rock guitarist, singer-songwriter, actor and film producer who achieved international fame as lead guitarist of The Beatles.';
+ $dataset[2]['description'] = 'Richard Starkey, MBE (born 7 July 1940), better known by his stage name Ringo Starr, is an English musician, singer-songwriter, and actor who gained worldwide fame as the drummer for The Beatles.';
+ $dataset[3]['description'] = 'Sir James Paul McCartney, MBE (born 18 June 1942) is an English musician, singer-songwriter and composer. Formerly of The Beatles (1960–1970) and Wings (1971–1981), McCartney is the most commercially successful songwriter in the history of popular music, according to Guinness World Records.[1]';
+ $dataset[4]['description'] = NULL;
+
+ return $dataset;
+ }
+
+ protected function getBasicView() {
+ $view = parent::getBasicView();
+ $view->display['default']->options['fields']['description'] = array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ );
+ return $view;
+ }
+
+ function testFilterStringEqual() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'operator' => '=',
+ 'value' => 'Ringo',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedEqual() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: =, Value: Ringo
+ $filters['name']['group_info']['default_group'] = 1;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringNotEqual() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'operator' => '!=',
+ 'value' => array('value' => 'Ringo'),
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'George',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ array(
+ 'name' => 'Meredith',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedNotEqual() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: !=, Value: Ringo
+ $filters['name']['group_info']['default_group'] = '2';
+
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'George',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ array(
+ 'name' => 'Meredith',
+ ),
+ );
+
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringContains() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'operator' => 'contains',
+ 'value' => 'ing',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ function testFilterStringGroupedExposedContains() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: contains, Value: ing
+ $filters['name']['group_info']['default_group'] = '3';
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ function testFilterStringWord() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'operator' => 'word',
+ 'value' => 'actor',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'George',
+ ),
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ $view->destroy();
+
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'operator' => 'allwords',
+ 'value' => 'Richard Starkey',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ function testFilterStringGroupedExposedWord() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: contains, Value: ing
+ $filters['name']['group_info']['default_group'] = '3';
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ $view->destroy();
+
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Description, Operator: contains, Value: actor
+ $filters['description']['group_info']['default_group'] = '1';
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'George',
+ ),
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringStarts() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'operator' => 'starts',
+ 'value' => 'George',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'George',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedStarts() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: starts, Value: George
+ $filters['description']['group_info']['default_group'] = 2;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'George',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringNotStarts() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'operator' => 'not_starts',
+ 'value' => 'George',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'Ringo',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ // There is no Meredith returned because his description is empty
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedNotStarts() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: not_starts, Value: George
+ $filters['description']['group_info']['default_group'] = 3;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'Ringo',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ // There is no Meredith returned because his description is empty
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringEnds() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'operator' => 'ends',
+ 'value' => 'Beatles.',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'George',
+ ),
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedEnds() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Descriptino, Operator: ends, Value: Beatles
+ $filters['description']['group_info']['default_group'] = 4;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'George',
+ ),
+ array(
+ 'name' => 'Ringo',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringNotEnds() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'operator' => 'not_ends',
+ 'value' => 'Beatles.',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ // There is no Meredith returned because his description is empty
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedNotEnds() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Description, Operator: not_ends, Value: Beatles
+ $filters['description']['group_info']['default_group'] = 5;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ // There is no Meredith returned because his description is empty
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringNot() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'operator' => 'not',
+ 'value' => 'Beatles.',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ // There is no Meredith returned because his description is empty
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ function testFilterStringGroupedExposedNot() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Description, Operator: not (does not contains), Value: Beatles
+ $filters['description']['group_info']['default_group'] = 6;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ // There is no Meredith returned because his description is empty
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+
+ }
+
+ function testFilterStringShorter() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'operator' => 'shorterthan',
+ 'value' => 5,
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedShorter() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: shorterthan, Value: 5
+ $filters['name']['group_info']['default_group'] = 4;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'John',
+ ),
+ array(
+ 'name' => 'Paul',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringLonger() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'operator' => 'longerthan',
+ 'value' => 7,
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Meredith',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedLonger() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Name, Operator: longerthan, Value: 4
+ $filters['name']['group_info']['default_group'] = 5;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Meredith',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+
+ function testFilterStringEmpty() {
+ $view = $this->getBasicView();
+
+ // Change the filtering
+ $view->display['default']->handler->override_option('filters', array(
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'operator' => 'empty',
+ ),
+ ));
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Meredith',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function testFilterStringGroupedExposedEmpty() {
+ $filters = $this->getGroupedExposedFilters();
+ $view = $this->getBasicPageView();
+
+ // Filter: Description, Operator: empty, Value:
+ $filters['description']['group_info']['default_group'] = 7;
+ $view->set_display('page_1');
+ $view->display['page_1']->handler->override_option('filters', $filters);
+
+ $this->executeView($view);
+ $resultset = array(
+ array(
+ 'name' => 'Meredith',
+ ),
+ );
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ protected function getGroupedExposedFilters() {
+ $filters = array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'exposed' => TRUE,
+ 'expose' => array(
+ 'operator' => 'name_op',
+ 'label' => 'name',
+ 'identifier' => 'name',
+ ),
+ 'is_grouped' => TRUE,
+ 'group_info' => array(
+ 'label' => 'name',
+ 'identifier' => 'name',
+ 'default_group' => 'All',
+ 'group_items' => array(
+ 1 => array(
+ 'title' => 'Is Ringo',
+ 'operator' => '=',
+ 'value' => 'Ringo',
+ ),
+ 2 => array(
+ 'title' => 'Is not Ringo',
+ 'operator' => '!=',
+ 'value' => array('value' => 'Ringo'),
+ ),
+ 3 => array(
+ 'title' => 'Contains ing',
+ 'operator' => 'contains',
+ 'value' => 'ing',
+ ),
+ 4 => array(
+ 'title' => 'Shorter than 5 letters',
+ 'operator' => 'shorterthan',
+ 'value' => 5,
+ ),
+ 5 => array(
+ 'title' => 'Longer than 7 letters',
+ 'operator' => 'longerthan',
+ 'value' => 7,
+ ),
+ ),
+ ),
+ ),
+ 'description' => array(
+ 'id' => 'description',
+ 'table' => 'views_test',
+ 'field' => 'description',
+ 'relationship' => 'none',
+ 'exposed' => TRUE,
+ 'expose' => array(
+ 'operator' => 'description_op',
+ 'label' => 'description',
+ 'identifier' => 'description',
+ ),
+ 'is_grouped' => TRUE,
+ 'group_info' => array(
+ 'label' => 'description',
+ 'identifier' => 'description',
+ 'default_group' => 'All',
+ 'group_items' => array(
+ 1 => array(
+ 'title' => 'Contains the word: Actor',
+ 'operator' => 'word',
+ 'value' => 'actor',
+ ),
+ 2 => array(
+ 'title' => 'Starts with George',
+ 'operator' => 'starts',
+ 'value' => 'George',
+ ),
+ 3 => array(
+ 'title' => 'Not Starts with: George',
+ 'operator' => 'not_starts',
+ 'value' => 'George',
+ ),
+ 4 => array(
+ 'title' => 'Ends with: Beatles',
+ 'operator' => 'ends',
+ 'value' => 'Beatles.',
+ ),
+ 5 => array(
+ 'title' => 'Not Ends with: Beatles',
+ 'operator' => 'not_ends',
+ 'value' => 'Beatles.',
+ ),
+ 6 => array(
+ 'title' => 'Does not contain: Beatles',
+ 'operator' => 'not',
+ 'value' => 'Beatles.',
+ ),
+ 7 => array(
+ 'title' => 'Empty description',
+ 'operator' => 'empty',
+ 'value' => '',
+ ),
+ ),
+ ),
+ ),
+ );
+ return $filters;
+ }
+
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_sort.test b/sites/all/modules/views/tests/handlers/views_handler_sort.test
new file mode 100644
index 000000000..70f2aac4f
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_sort.test
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerSortTest.
+ */
+
+/**
+ * Tests for core views_handler_sort handler.
+ */
+class ViewsHandlerSortTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Sort: generic',
+ 'description' => 'Test the core views_handler_sort handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ /**
+ * Tests numeric ordering of the result set.
+ */
+ public function testNumericOrdering() {
+ $view = $this->getBasicView();
+
+ // Change the ordering
+ $view->display['default']->handler->override_option('sorts', array(
+ 'age' => array(
+ 'order' => 'ASC',
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Execute the view.
+ $this->executeView($view);
+
+ // Verify the result.
+ $this->assertEqual(count($this->dataSet()), count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultset($view, $this->orderResultSet($this->dataSet(), 'age'), array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+
+ $view = $this->getBasicView();
+
+ // Reverse the ordering
+ $view->display['default']->handler->override_option('sorts', array(
+ 'age' => array(
+ 'order' => 'DESC',
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Execute the view.
+ $this->executeView($view);
+
+ // Verify the result.
+ $this->assertEqual(count($this->dataSet()), count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultset($view, $this->orderResultSet($this->dataSet(), 'age', TRUE), array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+ }
+
+ /**
+ * Tests string ordering of the result set.
+ */
+ public function testStringOrdering() {
+ $view = $this->getBasicView();
+
+ // Change the ordering
+ $view->display['default']->handler->override_option('sorts', array(
+ 'name' => array(
+ 'order' => 'ASC',
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Execute the view.
+ $this->executeView($view);
+
+ // Verify the result.
+ $this->assertEqual(count($this->dataSet()), count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultset($view, $this->orderResultSet($this->dataSet(), 'name'), array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+
+ $view = $this->getBasicView();
+
+ // Reverse the ordering
+ $view->display['default']->handler->override_option('sorts', array(
+ 'name' => array(
+ 'order' => 'DESC',
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Execute the view.
+ $this->executeView($view);
+
+ // Verify the result.
+ $this->assertEqual(count($this->dataSet()), count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultset($view, $this->orderResultSet($this->dataSet(), 'name', TRUE), array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_sort_date.test b/sites/all/modules/views/tests/handlers/views_handler_sort_date.test
new file mode 100644
index 000000000..65a94e815
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_sort_date.test
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerSortDateTest.
+ */
+
+/**
+ * Tests for core views_handler_sort_date handler.
+ */
+class ViewsHandlerSortDateTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Sort: date',
+ 'description' => 'Test the core views_handler_sort_date handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ protected function expectedResultSet($granularity, $reverse = TRUE) {
+ $expected = array();
+ if (!$reverse) {
+ switch ($granularity) {
+ case 'second':
+ $expected = array(
+ array('name' => 'John'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ array('name' => 'Ringo'),
+ array('name' => 'George'),
+ );
+ break;
+ case 'minute':
+ $expected = array(
+ array('name' => 'John'),
+ array('name' => 'Paul'),
+ array('name' => 'Ringo'),
+ array('name' => 'Meredith'),
+ array('name' => 'George'),
+ );
+ break;
+ case 'hour':
+ $expected = array(
+ array('name' => 'John'),
+ array('name' => 'Ringo'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ array('name' => 'George'),
+ );
+ break;
+ case 'day':
+ $expected = array(
+ array('name' => 'John'),
+ array('name' => 'Ringo'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ array('name' => 'George'),
+ );
+ break;
+ case 'month':
+ $expected = array(
+ array('name' => 'John'),
+ array('name' => 'George'),
+ array('name' => 'Ringo'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ );
+ break;
+ case 'year':
+ $expected = array(
+ array('name' => 'John'),
+ array('name' => 'George'),
+ array('name' => 'Ringo'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ );
+ break;
+ }
+ }
+ else {
+ switch ($granularity) {
+ case 'second':
+ $expected = array(
+ array('name' => 'George'),
+ array('name' => 'Ringo'),
+ array('name' => 'Meredith'),
+ array('name' => 'Paul'),
+ array('name' => 'John'),
+ );
+ break;
+ case 'minute':
+ $expected = array(
+ array('name' => 'George'),
+ array('name' => 'Ringo'),
+ array('name' => 'Meredith'),
+ array('name' => 'Paul'),
+ array('name' => 'John'),
+ );
+ break;
+ case 'hour':
+ $expected = array(
+ array('name' => 'George'),
+ array('name' => 'Ringo'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ array('name' => 'John'),
+ );
+ break;
+ case 'day':
+ $expected = array(
+ array('name' => 'George'),
+ array('name' => 'John'),
+ array('name' => 'Ringo'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ );
+ break;
+ case 'month':
+ $expected = array(
+ array('name' => 'John'),
+ array('name' => 'George'),
+ array('name' => 'Ringo'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ );
+ break;
+ case 'year':
+ $expected = array(
+ array('name' => 'John'),
+ array('name' => 'George'),
+ array('name' => 'Ringo'),
+ array('name' => 'Paul'),
+ array('name' => 'Meredith'),
+ );
+ break;
+ }
+ }
+
+ return $expected;
+ }
+
+ /**
+ * Tests numeric ordering of the result set.
+ */
+ public function testDateOrdering() {
+ foreach (array('second', 'minute', 'hour', 'day', 'month', 'year') as $granularity) {
+ foreach (array(FALSE, TRUE) as $reverse) {
+ $view = $this->getBasicView();
+
+ // Change the fields.
+ $view->display['default']->handler->override_option('fields', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'created' => array(
+ 'id' => 'created',
+ 'table' => 'views_test',
+ 'field' => 'created',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Change the ordering
+ $view->display['default']->handler->override_option('sorts', array(
+ 'created' => array(
+ 'id' => 'created',
+ 'table' => 'views_test',
+ 'field' => 'created',
+ 'relationship' => 'none',
+ 'granularity' => $granularity,
+ 'order' => $reverse ? 'DESC' : 'ASC',
+ ),
+ 'id' => array(
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ 'order' => 'ASC',
+ ),
+ ));
+
+ // Execute the view.
+ $this->executeView($view);
+
+ // Verify the result.
+ $this->assertEqual(count($this->dataSet()), count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultset($view, $this->expectedResultSet($granularity, $reverse), array(
+ 'views_test_name' => 'name',
+ ), t('Result is returned correctly when ordering by granularity @granularity, @reverse.', array('@granularity' => $granularity, '@reverse' => $reverse ? t('reverse') : t('forward'))));
+ $view->destroy();
+ unset($view);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handler_sort_random.test b/sites/all/modules/views/tests/handlers/views_handler_sort_random.test
new file mode 100644
index 000000000..19db8a90d
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handler_sort_random.test
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerSortRandomTest.
+ */
+
+/**
+ * Tests for core views_handler_sort_random handler.
+ */
+class ViewsHandlerSortRandomTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Sort: random',
+ 'description' => 'Test the core views_handler_sort_random handler.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ /**
+ * Add more items to the test set, to make the order tests more robust.
+ */
+ protected function dataSet() {
+ $data = parent::dataSet();
+ for ($i = 0; $i < 50; $i++) {
+ $data[] = array(
+ 'name' => 'name_' . $i,
+ 'age' => $i,
+ 'job' => 'job_' . $i,
+ 'created' => rand(0, time()),
+ );
+ }
+ return $data;
+ }
+
+ /**
+ * Return a basic view with random ordering.
+ */
+ protected function getBasicRandomView() {
+ $view = $this->getBasicView();
+
+ // Add a random ordering.
+ $view->display['default']->handler->override_option('sorts', array(
+ 'random' => array(
+ 'id' => 'random',
+ 'field' => 'random',
+ 'table' => 'views',
+ ),
+ ));
+
+ return $view;
+ }
+
+ /**
+ * Tests random ordering of the result set.
+ *
+ * @see DatabaseSelectTestCase::testRandomOrder()
+ */
+ public function testRandomOrdering() {
+ // Execute a basic view first.
+ $view = $this->getBasicView();
+ $this->executeView($view);
+
+ // Verify the result.
+ $this->assertEqual(count($this->dataSet()), count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultset($view, $this->dataSet(), array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+
+ // Execute a random view, we expect the result set to be different.
+ $view_random = $this->getBasicRandomView();
+ $this->executeView($view_random);
+ $this->assertEqual(count($this->dataSet()), count($view_random->result), t('The number of returned rows match.'));
+ $this->assertNotIdenticalResultset($view_random, $view->result, array(
+ 'views_test_name' => 'views_test_name',
+ 'views_test_age' => 'views_test_name',
+ ));
+
+ // Execute a second random view, we expect the result set to be different again.
+ $view_random_2 = $this->getBasicRandomView();
+ $this->executeView($view_random_2);
+ $this->assertEqual(count($this->dataSet()), count($view_random_2->result), t('The number of returned rows match.'));
+ $this->assertNotIdenticalResultset($view_random, $view->result, array(
+ 'views_test_name' => 'views_test_name',
+ 'views_test_age' => 'views_test_name',
+ ));
+ }
+}
diff --git a/sites/all/modules/views/tests/handlers/views_handlers.test b/sites/all/modules/views/tests/handlers/views_handlers.test
new file mode 100644
index 000000000..f2faee3f7
--- /dev/null
+++ b/sites/all/modules/views/tests/handlers/views_handlers.test
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains ViewsHandlerTest.
+ */
+
+/**
+ * Tests generic handler functionality.
+ *
+ * @see view
+ */
+class ViewsHandlerTest extends ViewsSqlTest {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Handlers',
+ 'description' => 'Tests generic handler functionality.',
+ 'group' => 'Views Handlers',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function viewsData() {
+ $views_data = parent::viewsData();
+ $views_data['views']['test_access'] = array(
+ 'title' => 'test access',
+ 'help' => '',
+ 'area' => array(
+ 'handler' => 'views_test_area_access',
+ ),
+ );
+
+ return $views_data;
+ }
+
+ /**
+ * Tests access for handlers using an area handler.
+ */
+ public function testHandlerAccess() {
+ $view = $this->getBasicView();
+
+ // add a test area
+ $view->display['default']->handler->override_option('header', array(
+ 'test_access' => array(
+ 'id' => 'test_access',
+ 'table' => 'views',
+ 'field' => 'test_access',
+ 'custom_access' => FALSE,
+ ),
+ ));
+
+ $view->init_display();
+ $view->init_handlers();
+ $handlers = $view->display_handler->get_handlers('header');
+ $this->assertEqual(0, count($handlers));
+
+ $view->destroy();
+
+ $view = $this->getBasicView();
+
+ // add a test area
+ $view->display['default']->handler->override_option('header', array(
+ 'test_access' => array(
+ 'id' => 'test_access',
+ 'table' => 'views',
+ 'field' => 'test_access',
+ 'custom_access' => TRUE,
+ ),
+ ));
+
+ $view->init_display();
+ $view->init_handlers();
+ $handlers = $view->display_handler->get_handlers('header');
+ $this->assertEqual(1, count($handlers));
+ $this->assertTrue(isset($handlers['test_access']));
+ }
+
+}
diff --git a/sites/all/modules/views/tests/node/views_node_revision_relations.test b/sites/all/modules/views/tests/node/views_node_revision_relations.test
new file mode 100644
index 000000000..6b38396f8
--- /dev/null
+++ b/sites/all/modules/views/tests/node/views_node_revision_relations.test
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsNodeRevisionRelationsTestCase.
+ */
+
+/**
+ * Tests basic node_revision table integration into views.
+ */
+class ViewsNodeRevisionRelationsTestCase extends ViewsSqlTest {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests basic node_revision integration',
+ 'description' => 'Tests the integration of node_revision table of node module',
+ 'group' => 'Views Modules',
+ );
+ }
+
+ /**
+ * Create a node with revision and rest result count for both views.
+ */
+ public function testNodeRevisionRelationship() {
+ $node = $this->drupalCreateNode();
+ // Create revision of the node.
+ $node_revision = clone $node;
+ $node_revision->revision = 1;
+ node_save($node_revision);
+ $column_map = array(
+ 'vid' => 'vid',
+ 'node_revision_nid' => 'node_revision_nid',
+ 'node_node_revision_nid' => 'node_node_revision_nid',
+ );
+
+ // Here should be two rows.
+ $view_nid = $this->test_view_node_revision_nid();
+ $this->executeView($view_nid, array($node->nid));
+ $resultset_nid = array(
+ array(
+ 'vid' => '1',
+ 'node_revision_nid' => '1',
+ 'node_node_revision_nid' => '1',
+ ),
+ array(
+ 'vid' => '2',
+ 'node_revision_nid' => '1',
+ 'node_node_revision_nid' => '1',
+ ),
+ );
+ $this->assertIdenticalResultset($view_nid, $resultset_nid, $column_map);
+
+ // There should be only one row with active revision 2.
+ $view_vid = $this->test_view_node_revision_vid();
+ $this->executeView($view_vid, array($node->nid));
+ $resultset_vid = array(
+ array(
+ 'vid' => '2',
+ 'node_revision_nid' => '1',
+ 'node_node_revision_nid' => '1',
+ ),
+ );
+ $this->assertIdenticalResultset($view_vid, $resultset_vid, $column_map);
+ }
+
+ /**
+ * Test view with default join on node.nid.
+ */
+ function test_view_node_revision_nid() {
+ $view = new view();
+ $view->name = 'test_node_revision_nid';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node_revision';
+ $view->human_name = 'Test node revision nid';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'view revisions';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Relationship: Content revision: Content */
+ $handler->display->display_options['relationships']['nid']['id'] = 'nid';
+ $handler->display->display_options['relationships']['nid']['table'] = 'node_revision';
+ $handler->display->display_options['relationships']['nid']['field'] = 'nid';
+ $handler->display->display_options['relationships']['nid']['label'] = 'NID';
+ $handler->display->display_options['relationships']['nid']['required'] = TRUE;
+ /* Field: Content revision: Vid */
+ $handler->display->display_options['fields']['vid']['id'] = 'vid';
+ $handler->display->display_options['fields']['vid']['table'] = 'node_revision';
+ $handler->display->display_options['fields']['vid']['field'] = 'vid';
+ /* Field: Content revision: Nid */
+ $handler->display->display_options['fields']['nid_1']['id'] = 'nid_1';
+ $handler->display->display_options['fields']['nid_1']['table'] = 'node_revision';
+ $handler->display->display_options['fields']['nid_1']['field'] = 'nid';
+ /* Field: Content: Nid */
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'nid';
+ $handler->display->display_options['fields']['nid']['relationship'] = 'nid';
+ /* Contextual filter: Content revision: Nid */
+ $handler->display->display_options['arguments']['nid']['id'] = 'nid';
+ $handler->display->display_options['arguments']['nid']['table'] = 'node_revision';
+ $handler->display->display_options['arguments']['nid']['field'] = 'nid';
+ $handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0';
+ $handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25';
+
+ return $view;
+ }
+
+ /**
+ * Test view with default join on node.vid.
+ */
+ function test_view_node_revision_vid() {
+ $view = new view();
+ $view->name = 'test_node_revision_vid';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node_revision';
+ $view->human_name = 'Test node revision vid';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'view revisions';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Relationship: Content revision: Content */
+ $handler->display->display_options['relationships']['vid']['id'] = 'vid';
+ $handler->display->display_options['relationships']['vid']['table'] = 'node_revision';
+ $handler->display->display_options['relationships']['vid']['field'] = 'vid';
+ $handler->display->display_options['relationships']['vid']['label'] = 'VID';
+ $handler->display->display_options['relationships']['vid']['required'] = TRUE;
+ /* Field: Content revision: Vid */
+ $handler->display->display_options['fields']['vid']['id'] = 'vid';
+ $handler->display->display_options['fields']['vid']['table'] = 'node_revision';
+ $handler->display->display_options['fields']['vid']['field'] = 'vid';
+ /* Field: Content revision: Nid */
+ $handler->display->display_options['fields']['nid_1']['id'] = 'nid_1';
+ $handler->display->display_options['fields']['nid_1']['table'] = 'node_revision';
+ $handler->display->display_options['fields']['nid_1']['field'] = 'nid';
+ /* Field: Content: Nid */
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'nid';
+ $handler->display->display_options['fields']['nid']['relationship'] = 'vid';
+ /* Contextual filter: Content revision: Nid */
+ $handler->display->display_options['arguments']['nid']['id'] = 'nid';
+ $handler->display->display_options['arguments']['nid']['table'] = 'node_revision';
+ $handler->display->display_options['arguments']['nid']['field'] = 'nid';
+ $handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0';
+ $handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25';
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/plugins/views_plugin_display.test b/sites/all/modules/views/tests/plugins/views_plugin_display.test
new file mode 100644
index 000000000..df33a3417
--- /dev/null
+++ b/sites/all/modules/views/tests/plugins/views_plugin_display.test
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsPluginDisplayTestCase.
+ */
+
+/**
+ *
+ */
+class ViewsPluginDisplayTestCase extends ViewsSqlTest {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Display plugin',
+ 'description' => 'Tests the basic display plugin.',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+ /**
+ * Tests the overriding of filter_groups.
+ */
+ function testFilterGroupsOverriding() {
+ $view = $this->viewFilterGroupsUpdating();
+ $view->init_display();
+
+ // mark is as overridden, yes FALSE, means overridden.
+ $view->display['page']->handler->set_override('filter_groups', FALSE);
+ $this->assertFalse($view->display['page']->handler->is_defaulted('filter_groups'), "Take sure that 'filter_groups' is marked as overridden.");
+ $this->assertFalse($view->display['page']->handler->is_defaulted('filters'), "Take sure that 'filters'' is marked as overridden.");
+ }
+
+ /**
+ * Returns a test view for testFilterGroupsOverriding.
+ *
+ * @see testFilterGroupsOverriding
+ * @return view
+ */
+ function viewFilterGroupsOverriding() {
+ $view = new view();
+ $view->name = 'test_filter_group_override';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'test_filter_group_override';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page_1');
+ $handler->display->display_options['path'] = 'test';
+
+ return $view;
+ }
+
+ /**
+ * Based on a bug some filter_groups landed in the overridden display, even the filters weren't overridden.
+ * This caused multiple issues.
+ * Take sure that the value from the default display are used.
+ *
+ * @see http://drupal.org/node/1259608
+ * @see views_plugin_display::init()
+ */
+ function testFilterGroupsUpdating() {
+ $view = $this->viewFilterGroupsUpdating();
+ $view->init_display();
+
+ $this->assertFalse($view->display['page']->handler->options['defaults']['filter_groups']);
+ $this->assertEqual($view->display['default']->handler->options['filter_groups'], $view->display['page']->handler->options['filter_groups'], 'Take sure the default options are used for the filter_groups');
+ }
+
+ /**
+ * Returns a test view for testFilterGroupUpdating.
+ *
+ * @see testFilterGroupUpdating
+ *
+ * @return view
+ */
+ function viewFilterGroupsUpdating() {
+ $view = new view();
+ $view->name = 'test_filter_groups';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'test_filter_groups';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'test_filter_groups';
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
+ /* Sort criterion: Content: Post date */
+ $handler->display->display_options['sorts']['created']['id'] = 'created';
+ $handler->display->display_options['sorts']['created']['table'] = 'node';
+ $handler->display->display_options['sorts']['created']['field'] = 'created';
+ $handler->display->display_options['sorts']['created']['order'] = 'DESC';
+ $handler->display->display_options['filter_groups']['groups'] = array(
+ 1 => 'AND',
+ 2 => 'AND',
+ );
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Nid */
+ $handler->display->display_options['filters']['nid']['id'] = 'nid';
+ $handler->display->display_options['filters']['nid']['table'] = 'node';
+ $handler->display->display_options['filters']['nid']['field'] = 'nid';
+ $handler->display->display_options['filters']['nid']['value']['value'] = '1';
+ $handler->display->display_options['filters']['nid']['group'] = 2;
+ /* Filter criterion: Content: Nid */
+ $handler->display->display_options['filters']['nid_1']['id'] = 'nid_1';
+ $handler->display->display_options['filters']['nid_1']['table'] = 'node';
+ $handler->display->display_options['filters']['nid_1']['field'] = 'nid';
+ $handler->display->display_options['filters']['nid_1']['value']['value'] = '2';
+ $handler->display->display_options['filters']['nid_1']['group'] = 2;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['filter_groups']['operator'] = 'OR';
+ $handler->display->display_options['filter_groups']['groups'] = array(
+ 1 => 'OR',
+ 2 => 'OR',
+ );
+ $handler->display->display_options['defaults']['filters'] = FALSE;
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Nid */
+ $handler->display->display_options['filters']['nid']['id'] = 'nid';
+ $handler->display->display_options['filters']['nid']['table'] = 'node';
+ $handler->display->display_options['filters']['nid']['field'] = 'nid';
+ $handler->display->display_options['filters']['nid']['value']['value'] = '1';
+ $handler->display->display_options['filters']['nid']['group'] = 2;
+ /* Filter criterion: Content: Nid */
+ $handler->display->display_options['filters']['nid_1']['id'] = 'nid_1';
+ $handler->display->display_options['filters']['nid_1']['table'] = 'node';
+ $handler->display->display_options['filters']['nid_1']['field'] = 'nid';
+ $handler->display->display_options['filters']['nid_1']['value']['value'] = '2';
+ $handler->display->display_options['filters']['nid_1']['group'] = 2;
+ $handler->display->display_options['path'] = 'test-filter-groups';
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/styles/views_plugin_style.test b/sites/all/modules/views/tests/styles/views_plugin_style.test
new file mode 100644
index 000000000..dfc5413ac
--- /dev/null
+++ b/sites/all/modules/views/tests/styles/views_plugin_style.test
@@ -0,0 +1,264 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsPluginStyleTestCase.
+ */
+
+/**
+ * Tests some general style plugin related functionality.
+ */
+class ViewsPluginStyleTestCase extends ViewsPluginStyleTestBase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Styles',
+ 'description' => 'Test general style functionality.',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+ /**
+ * Tests the grouping legacy features of styles.
+ */
+ function testGroupingLegacy() {
+ $view = $this->getBasicView();
+ // Setup grouping by the job.
+ $view->init_display();
+ $view->init_style();
+ $view->style_plugin->options['grouping'] = 'job';
+
+ // Reduce the amount of items to make the test a bit easier.
+ // Set up the pager.
+ $view->display['default']->handler->override_option('pager', array(
+ 'type' => 'some',
+ 'options' => array('items_per_page' => 3),
+ ));
+
+ // Add the job field .
+ $view->display['default']->handler->override_option('fields', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'job' => array(
+ 'id' => 'job',
+ 'table' => 'views_test',
+ 'field' => 'job',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Now run the query and groupby the result.
+ $this->executeView($view);
+
+ // This is the old way to call it.
+ $sets = $view->style_plugin->render_grouping($view->result, $view->style_plugin->options['grouping']);
+
+ $expected = array();
+ // Use Job: as label, so be sure that the label is used for groupby as well.
+ $expected['Job: Singer'] = array();
+ $expected['Job: Singer'][0] = new StdClass();
+ $expected['Job: Singer'][0]->views_test_name = 'John';
+ $expected['Job: Singer'][0]->views_test_job = 'Singer';
+ $expected['Job: Singer'][0]->views_test_id = '1';
+ $expected['Job: Singer'][1] = new StdClass();
+ $expected['Job: Singer'][1]->views_test_name = 'George';
+ $expected['Job: Singer'][1]->views_test_job = 'Singer';
+ $expected['Job: Singer'][1]->views_test_id = '2';
+ $expected['Job: Drummer'] = array();
+ $expected['Job: Drummer'][2] = new StdClass();
+ $expected['Job: Drummer'][2]->views_test_name = 'Ringo';
+ $expected['Job: Drummer'][2]->views_test_job = 'Drummer';
+ $expected['Job: Drummer'][2]->views_test_id = '3';
+
+ $this->assertEqual($sets, $expected, t('The style plugin should proper group the results with grouping by the rendered output.'));
+
+ $expected = array();
+ $expected['Job: Singer'] = array();
+ $expected['Job: Singer']['group'] = 'Job: Singer';
+ $expected['Job: Singer']['rows'][0] = new StdClass();
+ $expected['Job: Singer']['rows'][0]->views_test_name = 'John';
+ $expected['Job: Singer']['rows'][0]->views_test_job = 'Singer';
+ $expected['Job: Singer']['rows'][0]->views_test_id = '1';
+ $expected['Job: Singer']['rows'][1] = new StdClass();
+ $expected['Job: Singer']['rows'][1]->views_test_name = 'George';
+ $expected['Job: Singer']['rows'][1]->views_test_job = 'Singer';
+ $expected['Job: Singer']['rows'][1]->views_test_id = '2';
+ $expected['Job: Drummer'] = array();
+ $expected['Job: Drummer']['group'] = 'Job: Drummer';
+ $expected['Job: Drummer']['rows'][2] = new StdClass();
+ $expected['Job: Drummer']['rows'][2]->views_test_name = 'Ringo';
+ $expected['Job: Drummer']['rows'][2]->views_test_job = 'Drummer';
+ $expected['Job: Drummer']['rows'][2]->views_test_id = '3';
+
+ // The newer api passes the value of the grouping as well.
+ $sets_new_rendered = $view->style_plugin->render_grouping($view->result, $view->style_plugin->options['grouping'], TRUE);
+ $sets_new_value = $view->style_plugin->render_grouping($view->result, $view->style_plugin->options['grouping'], FALSE);
+
+ $this->assertEqual($sets_new_rendered, $expected, t('The style plugins should proper group the results with grouping by the rendered output.'));
+
+ // Reorder the group structure to group by value.
+ $expected['Singer'] = $expected['Job: Singer'];
+ $expected['Drummer'] = $expected['Job: Drummer'];
+ unset($expected['Job: Singer']);
+ unset($expected['Job: Drummer']);
+
+ $this->assertEqual($sets_new_value, $expected, t('The style plugins should proper group the results with grouping by the value.'));
+ }
+
+ function testGrouping() {
+ $this->_testGrouping(FALSE);
+ $this->_testGrouping(TRUE);
+ }
+
+ /**
+ * Tests the grouping features of styles.
+ */
+ function _testGrouping($stripped = FALSE) {
+ $view = $this->getBasicView();
+ // Setup grouping by the job and the age field.
+ $view->init_display();
+ $view->init_style();
+ $view->style_plugin->options['grouping'] = array(
+ array('field' => 'job'),
+ array('field' => 'age'),
+ );
+
+ // Reduce the amount of items to make the test a bit easier.
+ // Set up the pager.
+ $view->display['default']->handler->override_option('pager', array(
+ 'type' => 'some',
+ 'options' => array('items_per_page' => 3),
+ ));
+
+ // Add the job and age field.
+ $view->display['default']->handler->override_option('fields', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'job' => array(
+ 'id' => 'job',
+ 'table' => 'views_test',
+ 'field' => 'job',
+ 'relationship' => 'none',
+ ),
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Now run the query and groupby the result.
+ $this->executeView($view);
+
+ $expected = array();
+ $expected['Job: Singer'] = array();
+ $expected['Job: Singer']['group'] = 'Job: Singer';
+ $expected['Job: Singer']['rows']['Age: 25'] = array();
+ $expected['Job: Singer']['rows']['Age: 25']['group'] = 'Age: 25';
+ $expected['Job: Singer']['rows']['Age: 25']['rows'][0] = new StdClass();
+ $expected['Job: Singer']['rows']['Age: 25']['rows'][0]->views_test_name = 'John';
+ $expected['Job: Singer']['rows']['Age: 25']['rows'][0]->views_test_job = 'Singer';
+ $expected['Job: Singer']['rows']['Age: 25']['rows'][0]->views_test_age = '25';
+ $expected['Job: Singer']['rows']['Age: 25']['rows'][0]->views_test_id = '1';
+ $expected['Job: Singer']['rows']['Age: 27'] = array();
+ $expected['Job: Singer']['rows']['Age: 27']['group'] = 'Age: 27';
+ $expected['Job: Singer']['rows']['Age: 27']['rows'][1] = new StdClass();
+ $expected['Job: Singer']['rows']['Age: 27']['rows'][1]->views_test_name = 'George';
+ $expected['Job: Singer']['rows']['Age: 27']['rows'][1]->views_test_job = 'Singer';
+ $expected['Job: Singer']['rows']['Age: 27']['rows'][1]->views_test_age = '27';
+ $expected['Job: Singer']['rows']['Age: 27']['rows'][1]->views_test_id = '2';
+ $expected['Job: Drummer'] = array();
+ $expected['Job: Drummer']['group'] = 'Job: Drummer';
+ $expected['Job: Drummer']['rows']['Age: 28'] = array();
+ $expected['Job: Drummer']['rows']['Age: 28']['group'] = 'Age: 28';
+ $expected['Job: Drummer']['rows']['Age: 28']['rows'][2] = new StdClass();
+ $expected['Job: Drummer']['rows']['Age: 28']['rows'][2]->views_test_name = 'Ringo';
+ $expected['Job: Drummer']['rows']['Age: 28']['rows'][2]->views_test_job = 'Drummer';
+ $expected['Job: Drummer']['rows']['Age: 28']['rows'][2]->views_test_age = '28';
+ $expected['Job: Drummer']['rows']['Age: 28']['rows'][2]->views_test_id = '3';
+
+
+ // Alter the results to support the stripped case.
+ if ($stripped) {
+
+ // Add some html to the result and expected value.
+ $rand = '<a data="' . $this->randomName() . '" />';
+ $view->result[0]->views_test_job .= $rand;
+ $expected['Job: Singer']['rows']['Age: 25']['rows'][0]->views_test_job = 'Singer' . $rand;
+ $expected['Job: Singer']['group'] = 'Job: Singer';
+ $rand = '<a data="' . $this->randomName() . '" />';
+ $view->result[1]->views_test_job .= $rand;
+ $expected['Job: Singer']['rows']['Age: 27']['rows'][1]->views_test_job = 'Singer' . $rand;
+ $rand = '<a data="' . $this->randomName() . '" />';
+ $view->result[2]->views_test_job .= $rand;
+ $expected['Job: Drummer']['rows']['Age: 28']['rows'][2]->views_test_job = 'Drummer' . $rand;
+ $expected['Job: Drummer']['group'] = 'Job: Drummer';
+
+ $view->style_plugin->options['grouping'][0] = array('field' => 'job', 'rendered' => TRUE, 'rendered_strip' => TRUE);
+ $view->style_plugin->options['grouping'][1] = array('field' => 'age', 'rendered' => TRUE, 'rendered_strip' => TRUE);
+ }
+
+
+ // The newer api passes the value of the grouping as well.
+ $sets_new_rendered = $view->style_plugin->render_grouping($view->result, $view->style_plugin->options['grouping'], TRUE);
+
+ $this->assertEqual($sets_new_rendered, $expected, t('The style plugins should proper group the results with grouping by the rendered output.'));
+
+ // Don't test stripped case, because the actual value is not stripped.
+ if (!$stripped) {
+ $sets_new_value = $view->style_plugin->render_grouping($view->result, $view->style_plugin->options['grouping'], FALSE);
+
+ // Reorder the group structure to grouping by value.
+ $expected['Singer'] = $expected['Job: Singer'];
+ $expected['Singer']['rows']['25'] = $expected['Job: Singer']['rows']['Age: 25'];
+ $expected['Singer']['rows']['27'] = $expected['Job: Singer']['rows']['Age: 27'];
+ $expected['Drummer'] = $expected['Job: Drummer'];
+ $expected['Drummer']['rows']['28'] = $expected['Job: Drummer']['rows']['Age: 28'];
+ unset($expected['Job: Singer']);
+ unset($expected['Singer']['rows']['Age: 25']);
+ unset($expected['Singer']['rows']['Age: 27']);
+ unset($expected['Job: Drummer']);
+ unset($expected['Drummer']['rows']['Age: 28']);
+
+ $this->assertEqual($sets_new_value, $expected, t('The style plugins should proper group the results with grouping by the value.'));
+ }
+ }
+
+ /**
+ * Tests custom css classes.
+ */
+ function testCustomRowClasses() {
+ $view = $this->getBasicView();
+
+ // Setup some random css class.
+ $view->init_display();
+ $view->init_style();
+ $random_name = $this->randomName();
+ $view->style_plugin->options['row_class'] = $random_name . " test-token-[name]";
+
+ $rendered_output = $view->preview();
+ $this->storeViewPreview($rendered_output);
+
+ $rows = $this->elements->body->div->div->div;
+ $count = 0;
+ foreach ($rows as $row) {
+ $attributes = $row->attributes();
+ $class = (string) $attributes['class'][0];
+ $this->assertTrue(strpos($class, $random_name) !== FALSE, 'Take sure that a custom css class is added to the output.');
+
+ // Check token replacement.
+ $name = $view->field['name']->get_value($view->result[$count]);
+ $this->assertTrue(strpos($class, "test-token-$name") !== FALSE, 'Take sure that a token in custom css class is replaced.');
+
+ $count++;
+ }
+ }
+}
diff --git a/sites/all/modules/views/tests/styles/views_plugin_style_base.test b/sites/all/modules/views/tests/styles/views_plugin_style_base.test
new file mode 100644
index 000000000..514077dbe
--- /dev/null
+++ b/sites/all/modules/views/tests/styles/views_plugin_style_base.test
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsPluginStyleTestBase.
+ */
+
+/**
+ * Provides a base foundation for testing style plugins.
+ */
+abstract class ViewsPluginStyleTestBase extends ViewsSqlTest {
+
+ /**
+ * Stores the SimpleXML representation of the output.
+ *
+ * @var SimpleXMLElement
+ */
+ protected $elements;
+
+ /**
+ * Stores a view output in the elements.
+ */
+ function storeViewPreview($output) {
+ $htmlDom = new DOMDocument();
+ @$htmlDom->loadHTML($output);
+ if ($htmlDom) {
+ // It's much easier to work with simplexml than DOM, luckily enough
+ // we can just simply import our DOM tree.
+ $this->elements = simplexml_import_dom($htmlDom);
+ }
+ }
+
+}
diff --git a/sites/all/modules/views/tests/styles/views_plugin_style_jump_menu.test b/sites/all/modules/views/tests/styles/views_plugin_style_jump_menu.test
new file mode 100644
index 000000000..dd4eca0b3
--- /dev/null
+++ b/sites/all/modules/views/tests/styles/views_plugin_style_jump_menu.test
@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * @file
+ * Definition of viewsPluginStyleJumpMenuTest.
+ */
+
+/**
+ * Tests jump menu style functionality.
+ */
+class viewsPluginStyleJumpMenuTest extends ViewsSqlTest {
+
+ /**
+ * Stores all created nodes.
+ *
+ * @var array
+ */
+ var $nodes;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Jump menu',
+ 'description' => 'Test jump menu style functionality.',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+
+ public function setUp() {
+ parent::setUp();
+ $this->nodes = array();
+ $this->nodes['page'][] = $this->drupalCreateNode(array('type' => 'page'));
+ $this->nodes['page'][] = $this->drupalCreateNode(array('type' => 'page'));
+ $this->nodes['story'][] = $this->drupalCreateNode(array('type' => 'story'));
+ $this->nodes['story'][] = $this->drupalCreateNode(array('type' => 'story'));
+
+ $this->nodeTitles = array($this->nodes['page'][0]->title, $this->nodes['page'][1]->title, $this->nodes['story'][0]->title, $this->nodes['story'][1]->title);
+ }
+
+
+ /**
+ * Tests jump menues with more then one same path but maybe differnet titles.
+ */
+ function testDuplicatePaths() {
+ $view = $this->getJumpMenuView();
+ $view->set_display();
+ $view->init_handlers();
+
+ // Setup a [path] which would leed to "duplicate" paths, but still the shouldn't be used for grouping.
+ $view->field['nothing']->options['alter']['text'] = '[path]';
+ $view->preview();
+ $form = $view->style_plugin->render($view->result);
+
+ // As there is no grouping setup it should be 4 elements.
+ $this->assertEqual(count($form['jump']['#options']), 4 + 1);
+
+ // Check that all titles are part of the form as well.
+ $options = array_values($form['jump']['#options']);
+ foreach ($options as $key => $title) {
+ // The first one is the choose label.
+ if ($key == 0) {
+ continue;
+ }
+ $this->assertTrue($this->nodeTitles[$key - 1] == trim($title), t('Title @title should appear on the jump list, as we do not filter', array('@title' => $title)));
+ }
+ }
+
+ function getJumpMenuView() {
+ $view = new view;
+ $view->name = 'test_jump_menu';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'test_jump_menu';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'jump_menu';
+ $handler->display->display_options['style_options']['hide'] = 0;
+ $handler->display->display_options['style_options']['path'] = 'nothing';
+ $handler->display->display_options['style_options']['default_value'] = 0;
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+ /* Field: Content: Type */
+ $handler->display->display_options['fields']['type']['id'] = 'type';
+ $handler->display->display_options['fields']['type']['table'] = 'node';
+ $handler->display->display_options['fields']['type']['field'] = 'type';
+ $handler->display->display_options['fields']['type']['exclude'] = 1;
+ /* Field: Global: Custom text */
+ $handler->display->display_options['fields']['nothing']['id'] = 'nothing';
+ $handler->display->display_options['fields']['nothing']['table'] = 'views';
+ $handler->display->display_options['fields']['nothing']['field'] = 'nothing';
+ $handler->display->display_options['fields']['nothing']['alter']['text'] = '[type]';
+ $handler->display->display_options['fields']['nothing']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['nothing']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['nothing']['alter']['external'] = 0;
+ $handler->display->display_options['fields']['nothing']['alter']['replace_spaces'] = 0;
+ $handler->display->display_options['fields']['nothing']['alter']['trim_whitespace'] = 0;
+ $handler->display->display_options['fields']['nothing']['alter']['nl2br'] = 0;
+ $handler->display->display_options['fields']['nothing']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['nothing']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['nothing']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['nothing']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['nothing']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['nothing']['element_label_colon'] = 1;
+ $handler->display->display_options['fields']['nothing']['element_default_classes'] = 1;
+ $handler->display->display_options['fields']['nothing']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['nothing']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['nothing']['hide_alter_empty'] = 0;
+ $handler->display->display_options['fields']['nothing']['exclude'] = 1;
+
+ /* Sort criterion: Content: Post date */
+ $handler->display->display_options['sorts']['created']['id'] = 'created';
+ $handler->display->display_options['sorts']['created']['table'] = 'node';
+ $handler->display->display_options['sorts']['created']['field'] = 'created';
+ $handler->display->display_options['sorts']['created']['order'] = 'DESC';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/styles/views_plugin_style_mapping.test b/sites/all/modules/views/tests/styles/views_plugin_style_mapping.test
new file mode 100644
index 000000000..5785075b2
--- /dev/null
+++ b/sites/all/modules/views/tests/styles/views_plugin_style_mapping.test
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsPluginStyleMappingTest.
+ */
+
+/**
+ * Tests the default/mapping row style.
+ */
+class ViewsPluginStyleMappingTest extends ViewsPluginStyleTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Style: Mapping',
+ 'description' => 'Test mapping style functionality.',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp();
+
+ // Reset the plugin data.
+ views_fetch_plugin_data(NULL, NULL, TRUE);
+ }
+
+ protected function viewsPlugins() {
+ return array(
+ 'style' => array(
+ 'test_mapping' => array(
+ 'title' => t('Field mapping'),
+ 'help' => t('Maps specific fields to specific purposes.'),
+ 'handler' => 'views_test_plugin_style_test_mapping',
+ 'path' => drupal_get_path('module', 'views_test') . '/test_plugins',
+ 'theme' => 'views_view_mapping_test',
+ 'theme path' => drupal_get_path('module', 'views_test'),
+ 'theme file' => 'views_test.module',
+ 'uses row plugin' => FALSE,
+ 'uses fields' => TRUE,
+ 'uses options' => TRUE,
+ 'uses grouping' => FALSE,
+ 'type' => 'normal',
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Overrides ViewsTestCase::getBasicView().
+ */
+ protected function getBasicView() {
+ $view = parent::getBasicView();
+ $view->display['default']->handler->override_option('style_plugin', 'test_mapping');
+ $view->display['default']->handler->override_option('style_options', array(
+ 'mapping' => array(
+ 'name_field' => 'name',
+ 'numeric_field' => array(
+ 'age',
+ ),
+ 'title_field' => 'name',
+ 'toggle_numeric_field' => TRUE,
+ 'toggle_title_field' => TRUE,
+ ),
+ ));
+ $view->display['default']->handler->override_option('fields', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'job' => array(
+ 'id' => 'job',
+ 'table' => 'views_test',
+ 'field' => 'job',
+ 'relationship' => 'none',
+ ),
+ ));
+ return $view;
+ }
+
+ /**
+ * Verifies that the fields were mapped correctly.
+ */
+ public function testMappedOutput() {
+ $view = $this->getBasicView();
+ $output = $this->mappedOutputHelper($view);
+ $this->assertTrue(strpos($output, 'job') === FALSE, 'The job field is added to the view but not in the mapping.');
+
+ $view = $this->getBasicView();
+ $view->display['default']->display_options['style_options']['mapping']['name_field'] = 'job';
+ $output = $this->mappedOutputHelper($view);
+ $this->assertTrue(strpos($output, 'job') !== FALSE, 'The job field is added to the view and is in the mapping.');
+ }
+
+ /**
+ * Tests the mapping of fields.
+ *
+ * @param view $view
+ * The view to test.
+ *
+ * @return string
+ * The view rendered as HTML.
+ */
+ protected function mappedOutputHelper($view) {
+ $rendered_output = $view->preview();
+ $this->storeViewPreview($rendered_output);
+ $rows = $this->elements->body->div->div->div;
+ $data_set = $this->dataSet();
+
+ $count = 0;
+ foreach ($rows as $row) {
+ $attributes = $row->attributes();
+ $class = (string) $attributes['class'][0];
+ $this->assertTrue(strpos($class, 'views-row-mapping-test') !== FALSE, 'Make sure that each row has the correct CSS class.');
+
+ foreach ($row->div as $field) {
+ // Split up the field-level class, the first part is the mapping name
+ // and the second is the field ID.
+ $field_attributes = $field->attributes();
+ $name = strtok((string) $field_attributes['class'][0], '-');
+ $field_id = strtok('-');
+
+ // The expected result is the mapping name and the field value,
+ // separated by ':'.
+ $expected_result = $name . ':' . $data_set[$count][$field_id];
+ $actual_result = (string) $field;
+ $this->assertIdentical($expected_result, $actual_result, format_string('The fields were mapped successfully: %name => %field_id', array('%name' => $name, '%field_id' => $field_id)));
+ }
+
+ $count++;
+ }
+
+ return $rendered_output;
+ }
+
+}
diff --git a/sites/all/modules/views/tests/styles/views_plugin_style_unformatted.test b/sites/all/modules/views/tests/styles/views_plugin_style_unformatted.test
new file mode 100644
index 000000000..0c0e88254
--- /dev/null
+++ b/sites/all/modules/views/tests/styles/views_plugin_style_unformatted.test
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsPluginStyleUnformattedTestCase.
+ */
+
+/**
+ * Tests the default/unformatted row style.
+ */
+class ViewsPluginStyleUnformattedTestCase extends ViewsPluginStyleTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Style: unformatted',
+ 'description' => 'Test unformatted style functionality.',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+ /**
+ * Take sure that the default css classes works as expected.
+ */
+ function testDefaultRowClasses() {
+ $view = $this->getBasicView();
+ $rendered_output = $view->preview();
+ $this->storeViewPreview($rendered_output);
+
+ $rows = $this->elements->body->div->div->div;
+ $count = 0;
+ $count_result = count($view->result);
+ foreach ($rows as $row) {
+ $count++;
+ $attributes = $row->attributes();
+ $class = (string) $attributes['class'][0];
+ // Take sure that each row has a row css class.
+ $this->assertTrue(strpos($class, "views-row-$count") !== FALSE, 'Take sure that each row has a row css class.');
+ // Take sure that the odd/even classes are set right.
+ $odd_even = $count % 2 == 0 ? 'even' : 'odd';
+ $this->assertTrue(strpos($class, "views-row-$odd_even") !== FALSE, 'Take sure that the odd/even classes are set right.');
+
+ if ($count == 1) {
+ $this->assertTrue(strpos($class, "views-row-first") !== FALSE, 'Take sure that the first class is set right.');
+ }
+ else if ($count == $count_result) {
+ $this->assertTrue(strpos($class, "views-row-last") !== FALSE, 'Take sure that the last class is set right.');
+
+ }
+ $this->assertTrue(strpos($class, 'views-row') !== FALSE, 'Take sure that the views row class is set right.');
+ }
+ }
+
+}
diff --git a/sites/all/modules/views/tests/taxonomy/views_handler_relationship_node_term_data.test b/sites/all/modules/views/tests/taxonomy/views_handler_relationship_node_term_data.test
new file mode 100644
index 000000000..37e8aa6c6
--- /dev/null
+++ b/sites/all/modules/views/tests/taxonomy/views_handler_relationship_node_term_data.test
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlerRelationshipNodeTermDataTest.
+ */
+
+/**
+ * Tests the relationship_node_term_data handler.
+ */
+class ViewsHandlerRelationshipNodeTermDataTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests handler relationship_node_term_data',
+ 'description' => 'Tests the taxonomy term on node relationship handler',
+ 'group' => 'Views Modules',
+ );
+ }
+
+ /**
+ * Returns a new term with random properties in vocabulary $vid.
+ */
+ function createTerm($vocabulary) {
+ $term = new stdClass();
+ $term->name = $this->randomName();
+ $term->description = $this->randomName();
+ // Use the first available text format.
+ $term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField();
+ $term->vid = $vocabulary->vid;
+ taxonomy_term_save($term);
+ return $term;
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ //$web_user = $this->drupalCreateUser(array('create article content'));
+ //$this->drupalLogin($web_user);
+
+ $vocabulary = taxonomy_vocabulary_machine_name_load('tags');
+ $this->term_1 = $this->createTerm($vocabulary);
+ $this->term_2 = $this->createTerm($vocabulary);
+
+ $node = array();
+ $node['type'] = 'article';
+ $node['field_tags'][LANGUAGE_NONE][]['tid'] = $this->term_1->tid;
+ $node['field_tags'][LANGUAGE_NONE][]['tid'] = $this->term_2->tid;
+ $this->node = $this->drupalCreateNode($node);
+ }
+
+ function testViewsHandlerRelationshipNodeTermData() {
+ $view = $this->view_taxonomy_node_term_data();
+
+ $this->executeView($view, array($this->term_1->tid, $this->term_2->tid));
+ $resultset = array(
+ array(
+ 'nid' => $this->node->nid,
+ ),
+ );
+ $this->column_map = array('nid' => 'nid');
+ debug($view->result);
+ $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+ }
+
+ function view_taxonomy_node_term_data() {
+ $view = new view();
+ $view->name = 'test_taxonomy_node_term_data';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->human_name = 'test_taxonomy_node_term_data';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ /* Relationship: Content: Taxonomy terms on node */
+ $handler->display->display_options['relationships']['term_node_tid']['id'] = 'term_node_tid';
+ $handler->display->display_options['relationships']['term_node_tid']['table'] = 'node';
+ $handler->display->display_options['relationships']['term_node_tid']['field'] = 'term_node_tid';
+ $handler->display->display_options['relationships']['term_node_tid']['label'] = 'Term #1';
+ $handler->display->display_options['relationships']['term_node_tid']['vocabularies'] = array(
+ 'tags' => 0,
+ );
+ /* Relationship: Content: Taxonomy terms on node */
+ $handler->display->display_options['relationships']['term_node_tid_1']['id'] = 'term_node_tid_1';
+ $handler->display->display_options['relationships']['term_node_tid_1']['table'] = 'node';
+ $handler->display->display_options['relationships']['term_node_tid_1']['field'] = 'term_node_tid';
+ $handler->display->display_options['relationships']['term_node_tid_1']['label'] = 'Term #2';
+ $handler->display->display_options['relationships']['term_node_tid_1']['vocabularies'] = array(
+ 'tags' => 0,
+ );
+ /* Contextual filter: Taxonomy term: Term ID */
+ $handler->display->display_options['arguments']['tid']['id'] = 'tid';
+ $handler->display->display_options['arguments']['tid']['table'] = 'taxonomy_term_data';
+ $handler->display->display_options['arguments']['tid']['field'] = 'tid';
+ $handler->display->display_options['arguments']['tid']['relationship'] = 'term_node_tid';
+ $handler->display->display_options['arguments']['tid']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['tid']['summary']['number_of_records'] = '0';
+ $handler->display->display_options['arguments']['tid']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['tid']['summary_options']['items_per_page'] = '25';
+ /* Contextual filter: Taxonomy term: Term ID */
+ $handler->display->display_options['arguments']['tid_1']['id'] = 'tid_1';
+ $handler->display->display_options['arguments']['tid_1']['table'] = 'taxonomy_term_data';
+ $handler->display->display_options['arguments']['tid_1']['field'] = 'tid';
+ $handler->display->display_options['arguments']['tid_1']['relationship'] = 'term_node_tid_1';
+ $handler->display->display_options['arguments']['tid_1']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['tid_1']['summary']['number_of_records'] = '0';
+ $handler->display->display_options['arguments']['tid_1']['summary']['format'] = 'default_summary';
+ $handler->display->display_options['arguments']['tid_1']['summary_options']['items_per_page'] = '25';
+
+ return $view;
+ }
+} \ No newline at end of file
diff --git a/sites/all/modules/views/tests/test_handlers/views_test_area_access.inc b/sites/all/modules/views/tests/test_handlers/views_test_area_access.inc
new file mode 100644
index 000000000..57a0014e1
--- /dev/null
+++ b/sites/all/modules/views/tests/test_handlers/views_test_area_access.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains views_test_area_access
+ */
+
+class views_test_area_access extends views_handler_area {
+
+ /**
+ * {@inheritdoc}
+ */
+ function access() {
+ return $this->options['custom_access'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['custom_access'] = array('default' => TRUE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+}
diff --git a/sites/all/modules/views/tests/test_plugins/views_test_plugin_access_test_dynamic.inc b/sites/all/modules/views/tests/test_plugins/views_test_plugin_access_test_dynamic.inc
new file mode 100644
index 000000000..cecec2f96
--- /dev/null
+++ b/sites/all/modules/views/tests/test_plugins/views_test_plugin_access_test_dynamic.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_test_plugin_access_test_dynamic.
+ */
+
+/**
+ * Tests a dynamic access plugin.
+ */
+class views_test_plugin_access_test_dynamic extends views_plugin_access {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['access'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function access($account) {
+ return !empty($this->options['access']) && isset($this->view->args[0]) && $this->view->args[0] == variable_get('test_dynamic_access_argument1', NULL) && isset($this->view->args[1]) && $this->view->args[1] == variable_get('test_dynamic_access_argument2', NULL);
+ }
+
+ function get_access_callback() {
+ return array('views_test_test_dynamic_access_callback', array(!empty($options['access']), 1, 2));
+ }
+}
diff --git a/sites/all/modules/views/tests/test_plugins/views_test_plugin_access_test_static.inc b/sites/all/modules/views/tests/test_plugins/views_test_plugin_access_test_static.inc
new file mode 100644
index 000000000..187d6ea28
--- /dev/null
+++ b/sites/all/modules/views/tests/test_plugins/views_test_plugin_access_test_static.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_test_plugin_access_test_static.
+ */
+
+/**
+ * Tests a static access plugin.
+ */
+class views_test_plugin_access_test_static extends views_plugin_access {
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['access'] = array('default' => FALSE, 'bool' => TRUE);
+
+ return $options;
+ }
+
+ function access($account) {
+ return !empty($this->options['access']);
+ }
+
+ function get_access_callback() {
+ return array('views_test_test_static_access_callback', array(!empty($options['access'])));
+ }
+}
diff --git a/sites/all/modules/views/tests/test_plugins/views_test_plugin_style_test_mapping.inc b/sites/all/modules/views/tests/test_plugins/views_test_plugin_style_test_mapping.inc
new file mode 100644
index 000000000..b92678734
--- /dev/null
+++ b/sites/all/modules/views/tests/test_plugins/views_test_plugin_style_test_mapping.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_test_plugin_style_test_mapping.
+ */
+
+/**
+ * Provides a test mapping style plugin.
+ */
+class views_test_plugin_style_test_mapping extends views_plugin_style_mapping {
+
+ /**
+ * Overrides views_plugin_style_mapping::define_mapping().
+ */
+ protected function define_mapping() {
+ return array(
+ 'title_field' => array(
+ '#title' => t('Title field'),
+ '#description' => t('Choose the field with the custom title.'),
+ '#toggle' => TRUE,
+ '#required' => TRUE,
+ ),
+ 'name_field' => array(
+ '#title' => t('Name field'),
+ '#description' => t('Choose the field with the custom name.'),
+ ),
+ 'numeric_field' => array(
+ '#title' => t('Numeric field'),
+ '#description' => t('Select one or more numeric fields.'),
+ '#multiple' => TRUE,
+ '#toggle' => TRUE,
+ '#filter' => 'filter_numeric_fields',
+ '#required' => TRUE,
+ ),
+ );
+ }
+
+ /**
+ * Restricts the allowed fields to only numeric fields.
+ *
+ * @param array $fields
+ * An array of field labels, keyed by the field ID.
+ */
+ protected function filter_numeric_fields(&$fields) {
+ foreach ($this->view->field as $id => $field) {
+ if (!($field instanceof views_handler_field_numeric)) {
+ unset($fields[$id]);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views/tests/user/views_handler_field_user_name.test b/sites/all/modules/views/tests/user/views_handler_field_user_name.test
new file mode 100644
index 000000000..6ace47165
--- /dev/null
+++ b/sites/all/modules/views/tests/user/views_handler_field_user_name.test
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Definition of viewsHandlerFieldUserNameTest.
+ */
+
+/**
+ * Tests the field username handler.
+ *
+ * @see views_handler_field_user_name
+ */
+class viewsHandlerFieldUserNameTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests user: name field',
+ 'description' => 'Tests the handler of the user: name field',
+ 'group' => 'Views Modules',
+ );
+ }
+
+ function testUserName() {
+ $view = $this->view_user_name();
+ $view->init_display();
+ $this->executeView($view);
+
+ $view->row_index = 0;
+
+ $view->field['name']->options['link_to_user'] = TRUE;
+ $username = $view->result[0]->users_name = $this->randomName();
+ $view->result[0]->uid = 1;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertTrue(strpos($render, $username) !== FALSE, 'If link to user is checked the username should be part of the output.');
+ $this->assertTrue(strpos($render, 'user/1') !== FALSE, 'If link to user is checked the link to the user should appear as well.');
+
+ $view->field['name']->options['link_to_user'] = FALSE;
+ $username = $view->result[0]->users_name = $this->randomName();
+ $view->result[0]->uid = 1;
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $username, 'If the user is not linked the username should be printed out for a normal user.');
+
+ $view->result[0]->uid = 0;
+ $anon_name = variable_get('anonymous', t('Anonymous'));
+ $view->result[0]->users_name = '';
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $anon_name , 'For user0 it should use the default anonymous name by default.');
+
+ $view->field['name']->options['overwrite_anonymous'] = TRUE;
+ $anon_name = $view->field['name']->options['anonymous_text'] = $this->randomName();
+ $render = $view->field['name']->advanced_render($view->result[0]);
+ $this->assertIdentical($render, $anon_name , 'For user0 it should use the configured anonymous text if overwrite_anonymous is checked.');
+
+
+ }
+ function view_user_name() {
+ $view = new view;
+ $view->name = 'test_views_handler_field_user_name';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'users';
+ $view->human_name = 'test_views_handler_field_user_name';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: User: Name */
+ $handler->display->display_options['fields']['name']['id'] = 'name';
+ $handler->display->display_options['fields']['name']['table'] = 'users';
+ $handler->display->display_options['fields']['name']['field'] = 'name';
+ $handler->display->display_options['fields']['name']['label'] = '';
+ $handler->display->display_options['fields']['name']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['word_boundary'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['ellipsis'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['name']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['name']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['name']['link_to_user'] = 1;
+ $handler->display->display_options['fields']['name']['overwrite_anonymous'] = 0;
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/user/views_user.test b/sites/all/modules/views/tests/user/views_user.test
new file mode 100644
index 000000000..52c502673
--- /dev/null
+++ b/sites/all/modules/views/tests/user/views_user.test
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUserTestCase.
+ */
+
+/**
+ * Tests basic user module integration into views.
+ */
+class ViewsUserTestCase extends ViewsSqlTest {
+ var $users = array();
+ var $nodes = array();
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests basic user integration',
+ 'description' => 'Tests the integration of user module',
+ 'group' => 'Views Modules',
+ );
+ }
+
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->users[] = $this->drupalCreateUser();
+ $this->users[] = user_load(1);
+ $this->nodes[] = $this->drupalCreateNode(array('uid' => $this->users[0]->uid));
+ $this->nodes[] = $this->drupalCreateNode(array('uid' => 1));
+ }
+
+ /**
+ * Add a view which has no explicit relationship to the author and check the result.
+ *
+ * @todo: Remove the following comment once the relationship is required.
+ * One day a view will require the relationship so it should still work
+ */
+ public function testRelationship() {
+ $view = $this->test_view_user_relationship();
+
+ $view->execute_display();
+ $expected = array();
+ for ($i = 0; $i <= 1; $i++) {
+ $expected[$i] = array(
+ 'node_title' => $this->nodes[$i]->title,
+ 'users_uid' => $this->nodes[$i]->uid,
+ 'users_name' => $this->users[$i]->name,
+ );
+ }
+ $this->assertIdenticalResultset($view, $expected);
+ }
+
+ function test_view_user_relationship() {
+ $view = new view;
+ $view->name = 'test_user_relationship';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'test_user_relationship';
+ $view->core = 7;
+ $view->api_version = '3.0-alpha1';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'test_user_relationship';
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ $handler->display->display_options['row_options']['hide_empty'] = 0;
+ $handler->display->display_options['row_options']['default_field_elements'] = 1;
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+ /* Field: User: Name */
+ $handler->display->display_options['fields']['name']['id'] = 'name';
+ $handler->display->display_options['fields']['name']['table'] = 'users';
+ $handler->display->display_options['fields']['name']['field'] = 'name';
+ $handler->display->display_options['fields']['name']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['external'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['replace_spaces'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['trim_whitespace'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['nl2br'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['name']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['name']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['name']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['name']['element_label_colon'] = 1;
+ $handler->display->display_options['fields']['name']['element_default_classes'] = 1;
+ $handler->display->display_options['fields']['name']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['name']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['name']['hide_alter_empty'] = 0;
+ $handler->display->display_options['fields']['name']['link_to_user'] = 1;
+ $handler->display->display_options['fields']['name']['overwrite_anonymous'] = 0;
+ /* Field: User: Uid */
+ $handler->display->display_options['fields']['uid']['id'] = 'uid';
+ $handler->display->display_options['fields']['uid']['table'] = 'users';
+ $handler->display->display_options['fields']['uid']['field'] = 'uid';
+ $handler->display->display_options['fields']['uid']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['absolute'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['external'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['replace_spaces'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['trim_whitespace'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['nl2br'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['uid']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['uid']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['uid']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['uid']['element_label_colon'] = 1;
+ $handler->display->display_options['fields']['uid']['element_default_classes'] = 1;
+ $handler->display->display_options['fields']['uid']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['uid']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['uid']['hide_alter_empty'] = 0;
+ $handler->display->display_options['fields']['uid']['link_to_user'] = 1;
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/user/views_user_argument_default.test b/sites/all/modules/views/tests/user/views_user_argument_default.test
new file mode 100644
index 000000000..afb24d1e0
--- /dev/null
+++ b/sites/all/modules/views/tests/user/views_user_argument_default.test
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUserArgumentDefault.
+ */
+
+/**
+ * Tests views user argument default plugin.
+ */
+class ViewsUserArgumentDefault extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests user argument default plugin',
+ 'description' => 'Tests user argument default plugin',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+ public function test_plugin_argument_default_current_user() {
+ // Create a user to test.
+ $account = $this->drupalCreateUser();
+
+ // Switch the user, we have to check the global user too, because drupalLogin is only for the simpletest browser.
+ $this->drupalLogin($account);
+ global $user;
+ $admin = $user;
+ drupal_save_session(FALSE);
+ $user = $account;
+
+ $view = $this->view_plugin_argument_default_current_user();
+
+ $view->set_display('default');
+ $view->pre_execute();
+ $view->init_handlers();
+
+ $this->assertEqual($view->argument['null']->get_default_argument(), $account->uid, 'Uid of the current user is used.');
+ // Switch back.
+ $user = $admin;
+ drupal_save_session(TRUE);
+ }
+
+ function view_plugin_argument_default_current_user() {
+ $view = new view;
+ $view->name = 'test_plugin_argument_default_current_user';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = '3.0-alpha1';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['title']['link_to_node'] = 0;
+ /* Argument: Global: Null */
+ $handler->display->display_options['arguments']['null']['id'] = 'null';
+ $handler->display->display_options['arguments']['null']['table'] = 'views';
+ $handler->display->display_options['arguments']['null']['field'] = 'null';
+ $handler->display->display_options['arguments']['null']['default_action'] = 'default';
+ $handler->display->display_options['arguments']['null']['style_plugin'] = 'default_summary';
+ $handler->display->display_options['arguments']['null']['default_argument_type'] = 'current_user';
+ $handler->display->display_options['arguments']['null']['must_not_be'] = 0;
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/user/views_user_argument_validate.test b/sites/all/modules/views/tests/user/views_user_argument_validate.test
new file mode 100644
index 000000000..6e157bf5e
--- /dev/null
+++ b/sites/all/modules/views/tests/user/views_user_argument_validate.test
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUserArgumentValidate.
+ */
+
+/**
+ * Tests views user argument argument handler.
+ */
+class ViewsUserArgumentValidate extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests user argument validator',
+ 'description' => 'Tests user argument validator',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('views');
+ $this->account = $this->drupalCreateUser();
+ }
+
+ function testArgumentValidateUserUid() {
+ $account = $this->account;
+ // test 'uid' case
+ $view = $this->view_argument_validate_user('uid');
+ $view->set_display('default');
+ $view->pre_execute();
+ $view->init_handlers();
+ $this->assertTrue($view->argument['null']->validate_arg($account->uid));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ // Fail for a string variable since type is 'uid'
+ $this->assertFalse($view->argument['null']->validate_arg($account->name));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ // Fail for a valid numeric, but for a user that doesn't exist
+ $this->assertFalse($view->argument['null']->validate_arg(32));
+ }
+
+ function testArgumentValidateUserName() {
+ $account = $this->account;
+ // test 'name' case
+ $view = $this->view_argument_validate_user('name');
+ $view->set_display('default');
+ $view->pre_execute();
+ $view->init_handlers();
+ $this->assertTrue($view->argument['null']->validate_arg($account->name));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ // Fail for a uid variable since type is 'name'
+ $this->assertFalse($view->argument['null']->validate_arg($account->uid));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ // Fail for a valid string, but for a user that doesn't exist
+ $this->assertFalse($view->argument['null']->validate_arg($this->randomName()));
+ }
+
+ function testArgumentValidateUserEither() {
+ $account = $this->account;
+ // test 'either' case
+ $view = $this->view_argument_validate_user('either');
+ $view->set_display('default');
+ $view->pre_execute();
+ $view->init_handlers();
+ $this->assertTrue($view->argument['null']->validate_arg($account->name));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ // Fail for a uid variable since type is 'name'
+ $this->assertTrue($view->argument['null']->validate_arg($account->uid));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ // Fail for a valid string, but for a user that doesn't exist
+ $this->assertFalse($view->argument['null']->validate_arg($this->randomName()));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ // Fail for a valid uid, but for a user that doesn't exist
+ $this->assertFalse($view->argument['null']->validate_arg(32));
+ }
+
+ function view_argument_validate_user($argtype) {
+ $view = new view;
+ $view->name = 'view_argument_validate_user';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Argument: Global: Null */
+ $handler->display->display_options['arguments']['null']['id'] = 'null';
+ $handler->display->display_options['arguments']['null']['table'] = 'views';
+ $handler->display->display_options['arguments']['null']['field'] = 'null';
+ $handler->display->display_options['arguments']['null']['style_plugin'] = 'default_summary';
+ $handler->display->display_options['arguments']['null']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['null']['validate']['type'] = 'user';
+ $handler->display->display_options['arguments']['null']['validate_options']['type'] = $argtype;
+ $handler->display->display_options['arguments']['null']['must_not_be'] = 0;
+
+ return $view;
+ }
+
+}
diff --git a/sites/all/modules/views/tests/views_access.test b/sites/all/modules/views/tests/views_access.test
new file mode 100644
index 000000000..f02eca994
--- /dev/null
+++ b/sites/all/modules/views/tests/views_access.test
@@ -0,0 +1,285 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsAccessTest.
+ */
+
+/**
+ * Basic test for pluggable access.
+ */
+class ViewsAccessTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Access',
+ 'description' => 'Tests pluggable access for views.',
+ 'group' => 'Views Plugins'
+ );
+ }
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->admin_user = $this->drupalCreateUser(array('access all views'));
+ $this->web_user = $this->drupalCreateUser();
+ $this->web_role = current($this->web_user->roles);
+
+ $this->normal_role = $this->drupalCreateRole(array());
+ $this->normal_user = $this->drupalCreateUser(array('views_test test permission'));
+ $this->normal_user->roles[$this->normal_role] = $this->normal_role;
+ // Reset the plugin data.
+ views_fetch_plugin_data(NULL, NULL, TRUE);
+ }
+
+ function viewsPlugins() {
+ $plugins = array(
+ 'access' => array(
+ 'test_static' => array(
+ 'title' => t('Static test access plugin'),
+ 'help' => t('Provides a static test access plugin.'),
+ 'handler' => 'views_test_plugin_access_test_static',
+ 'path' => drupal_get_path('module', 'views_test') . '/test_plugins',
+ ),
+ 'test_dynamic' => array(
+ 'title' => t('Dynamic test access plugin'),
+ 'help' => t('Provides a dynamic test access plugin.'),
+ 'handler' => 'views_test_plugin_access_test_dynamic',
+ 'path' => drupal_get_path('module', 'views_test') . '/test_plugins',
+ ),
+ ),
+ );
+
+ return $plugins;
+ }
+
+ /**
+ * Tests none access plugin.
+ */
+ function testAccessNone() {
+ $view = $this->view_access_none();
+
+ $view->set_display('default');
+
+ $this->assertTrue($view->display_handler->access($this->admin_user), t('Admin-Account should be able to access the view everytime'));
+ $this->assertTrue($view->display_handler->access($this->web_user));
+ $this->assertTrue($view->display_handler->access($this->normal_user));
+ }
+
+ /**
+ * Tests perm access plugin.
+ */
+ function testAccessPerm() {
+ $view = $this->view_access_perm();
+
+ $view->set_display('default');
+ $access_plugin = $view->display_handler->get_plugin('access');
+
+ $this->assertTrue($view->display_handler->access($this->admin_user), t('Admin-Account should be able to access the view everytime'));
+ $this->assertFalse($view->display_handler->access($this->web_user));
+ $this->assertTrue($view->display_handler->access($this->normal_user));
+ }
+
+ /**
+ * Tests role access plugin.
+ */
+ function testAccessRole() {
+ $view = $this->view_access_role();
+
+ $view->set_display('default');
+ $access_plugin = $view->display_handler->get_plugin('access');
+
+ $this->assertTrue($view->display_handler->access($this->admin_user), t('Admin-Account should be able to access the view everytime'));
+ $this->assertFalse($view->display_handler->access($this->web_user));
+ $this->assertTrue($view->display_handler->access($this->normal_user));
+ }
+
+ /**
+ * @todo Test abstract access plugin.
+ */
+
+ /**
+ * Tests static access check.
+ */
+ function testStaticAccessPlugin() {
+ $view = $this->view_access_static();
+
+ $view->set_display('default');
+ $access_plugin = $view->display_handler->get_plugin('access');
+
+ $this->assertFalse($access_plugin->access($this->normal_user));
+
+ $access_plugin->options['access'] = TRUE;
+ $this->assertTrue($access_plugin->access($this->normal_user));
+
+ // FALSE comes from hook_menu caching.
+ $expected_hook_menu = array(
+ 'views_test_test_static_access_callback', array(FALSE)
+ );
+ $hook_menu = $view->execute_hook_menu('page_1');
+ $this->assertEqual($expected_hook_menu, $hook_menu['test_access_static']['access arguments'][0]);
+
+ $expected_hook_menu = array(
+ 'views_test_test_static_access_callback', array(TRUE)
+ );
+ $this->assertTrue(views_access($expected_hook_menu));
+ }
+
+ /**
+ * Tests dynamic access plugin.
+ */
+ function testDynamicAccessPlugin() {
+ $view = $this->view_access_dynamic();
+ $argument1 = $this->randomName();
+ $argument2 = $this->randomName();
+ variable_set('test_dynamic_access_argument1', $argument1);
+ variable_set('test_dynamic_access_argument2', $argument2);
+
+ $view->set_display('default');
+ $access_plugin = $view->display_handler->get_plugin('access');
+
+ $this->assertFalse($access_plugin->access($this->normal_user));
+
+ $access_plugin->options['access'] = TRUE;
+ $this->assertFalse($access_plugin->access($this->normal_user));
+
+ $view->set_arguments(array($argument1, $argument2));
+ $this->assertTrue($access_plugin->access($this->normal_user));
+
+ // FALSE comes from hook_menu caching.
+ $expected_hook_menu = array(
+ 'views_test_test_dynamic_access_callback', array(FALSE, 1, 2)
+ );
+ $hook_menu = $view->execute_hook_menu('page_1');
+ $this->assertEqual($expected_hook_menu, $hook_menu['test_access_dynamic']['access arguments'][0]);
+
+ $expected_hook_menu = array(
+ 'views_test_test_dynamic_access_callback', array(TRUE, 1, 2)
+ );
+ $this->assertTrue(views_access($expected_hook_menu, $argument1, $argument2));
+ }
+
+ function view_access_none() {
+ $view = new view;
+ $view->name = 'test_access_none';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+
+ return $view;
+ }
+
+ function view_access_perm() {
+ $view = new view;
+ $view->name = 'test_access_perm';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['access']['perm'] = 'views_test test permission';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+
+ return $view;
+ }
+
+ function view_access_role() {
+ $view = new view;
+ $view->name = 'test_access_role';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'role';
+ $handler->display->display_options['access']['role'] = array(
+ $this->normal_role => $this->normal_role,
+ );
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+
+ return $view;
+ }
+
+ function view_access_dynamic() {
+ $view = new view;
+ $view->name = 'test_access_dynamic';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'test_dynamic';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+
+ $handler = $view->new_display('page', 'Page', 'page_1');
+ $handler->display->display_options['path'] = 'test_access_dynamic';
+
+ return $view;
+ }
+
+ function view_access_static() {
+ $view = new view;
+ $view->name = 'test_access_static';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'test_static';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+
+ $handler = $view->new_display('page', 'Page', 'page_1');
+ $handler->display->display_options['path'] = 'test_access_static';
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/views_analyze.test b/sites/all/modules/views/tests/views_analyze.test
new file mode 100644
index 000000000..c21a4bb62
--- /dev/null
+++ b/sites/all/modules/views/tests/views_analyze.test
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsAnalyzeTest.
+ */
+
+/**
+ * Tests the views analyze system.
+ */
+class ViewsAnalyzeTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views Analyze',
+ 'description' => 'Test the views analyze system.',
+ 'group' => 'Views',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('views_ui');
+ module_enable(array('views_ui'));
+ // @TODO Figure out why it's required to clear the cache here.
+ views_module_include('views_default', TRUE);
+ views_get_all_views(TRUE);
+ menu_rebuild();
+
+ // Add an admin user will full rights;
+ $this->admin = $this->drupalCreateUser(array('administer views'));
+ }
+
+ /**
+ * Tests that analyze works in general.
+ */
+ function testAnalyzeBasic() {
+ $this->drupalLogin($this->admin);
+ // Enable the frontpage view and click the analyse button.
+ $view = views_get_view('frontpage');
+ $view->save();
+
+ $this->drupalGet('admin/structure/views/view/frontpage/edit');
+ $this->assertLink(t('analyze view'));
+
+ // This redirects the user to the form.
+ $this->clickLink(t('analyze view'));
+ $this->assertText(t('View analysis'));
+
+ // This redirects the user back to the main views edit page.
+ $this->drupalPost(NULL, array(), t('Ok'));
+ }
+}
diff --git a/sites/all/modules/views/tests/views_argument_default.test b/sites/all/modules/views/tests/views_argument_default.test
new file mode 100644
index 000000000..9c0a7ebf4
--- /dev/null
+++ b/sites/all/modules/views/tests/views_argument_default.test
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsArgumentDefaultTest.
+ */
+
+/**
+ * Basic test for pluggable argument default.
+ */
+class ViewsArgumentDefaultTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Argument_default',
+ 'description' => 'Tests pluggable argument_default for views.',
+ 'group' => 'Views Plugins'
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('views');
+
+ $this->random = $this->randomString();
+ }
+
+ /**
+ * Tests the use of a default argument plugin that provides no options.
+ */
+ function testArgumentDefaultNoOptions() {
+ module_enable(array('views_ui', 'views_test'));
+ $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration'));
+ $this->drupalLogin($admin_user);
+
+ // The current_user plugin has no options form, and should pass validation.
+ $argument_type = 'current_user';
+ $edit = array(
+ 'options[default_argument_type]' => $argument_type,
+ );
+ $this->drupalPost('admin/structure/views/nojs/config-item/test_argument_default_current_user/default/argument/uid', $edit, t('Apply'));
+
+ // Note, the undefined index error has two spaces after it.
+ $error = array(
+ '%type' => 'Notice',
+ '!message' => 'Undefined index: ' . $argument_type,
+ '%function' => 'views_handler_argument->options_validate()',
+ );
+ $message = t('%type: !message in %function', $error);
+ $this->assertNoRaw($message, t('Did not find error message: !message.', array('!message' => $message)));
+ }
+
+ /**
+ * Tests fixed default argument.
+ */
+ function testArgumentDefaultFixed() {
+ $view = $this->view_argument_default_fixed();
+
+ $view->set_display('default');
+ $view->pre_execute();
+ $view->init_handlers();
+
+ $this->assertEqual($view->argument['null']->get_default_argument(), $this->random, 'Fixed argument should be used by default.');
+
+ $view->destroy();
+
+ // Make sure that a normal argument provided is used
+ $view = $this->view_argument_default_fixed();
+
+ $view->set_display('default');
+ $random_string = $this->randomString();
+ $view->execute_display('default', array($random_string));
+
+ $this->assertEqual($view->args[0], $random_string, 'Provided argument should be used.');
+ }
+
+ /**
+ * @todo Test php default argument.
+ */
+ function testArgumentDefaultPhp() {
+
+ }
+
+ /**
+ * @todo Test node default argument.
+ */
+ function testArgumentDefaultNode() {
+
+ }
+
+ function view_argument_default_fixed() {
+ $view = new view;
+ $view->name = 'test_argument_default_fixed';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['title']['link_to_node'] = 0;
+ /* Argument: Global: Null */
+ $handler->display->display_options['arguments']['null']['id'] = 'null';
+ $handler->display->display_options['arguments']['null']['table'] = 'views';
+ $handler->display->display_options['arguments']['null']['field'] = 'null';
+ $handler->display->display_options['arguments']['null']['default_action'] = 'default';
+ $handler->display->display_options['arguments']['null']['style_plugin'] = 'default_summary';
+ $handler->display->display_options['arguments']['null']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['null']['default_argument_options']['argument'] = $this->random;
+ $handler->display->display_options['arguments']['null']['must_not_be'] = 0;
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/views_argument_validator.test b/sites/all/modules/views/tests/views_argument_validator.test
new file mode 100644
index 000000000..fb2e4f2b1
--- /dev/null
+++ b/sites/all/modules/views/tests/views_argument_validator.test
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsArgumentValidatorTest.
+ */
+
+/**
+ * Tests Views argument validators.
+ */
+class ViewsArgumentValidatorTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Argument validator',
+ 'group' => 'Views Plugins',
+ 'description' => 'Test argument validator tests.',
+ );
+ }
+
+ function testArgumentValidatePhp() {
+ $string = $this->randomName();
+ $view = $this->view_test_argument_validate_php($string);
+ $view->set_display('default');
+ $view->pre_execute();
+ $view->init_handlers();
+ $this->assertTrue($view->argument['null']->validate_arg($string));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ $this->assertFalse($view->argument['null']->validate_arg($this->randomName()));
+ }
+
+ function testArgumentValidateNumeric() {
+ $view = $this->view_argument_validate_numeric();
+ $view->set_display('default');
+ $view->pre_execute();
+ $view->init_handlers();
+ $this->assertFalse($view->argument['null']->validate_arg($this->randomString()));
+ // Reset safed argument validation.
+ $view->argument['null']->argument_validated = NULL;
+ $this->assertTrue($view->argument['null']->validate_arg(12));
+ }
+
+ function view_test_argument_validate_php($string) {
+ $code = 'return $argument == \''. $string .'\';';
+ $view = new view;
+ $view->name = 'view_argument_validate_numeric';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Argument: Global: Null */
+ $handler->display->display_options['arguments']['null']['id'] = 'null';
+ $handler->display->display_options['arguments']['null']['table'] = 'views';
+ $handler->display->display_options['arguments']['null']['field'] = 'null';
+ $handler->display->display_options['arguments']['null']['style_plugin'] = 'default_summary';
+ $handler->display->display_options['arguments']['null']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['null']['validate_type'] = 'php';
+ $handler->display->display_options['arguments']['null']['validate_options']['code'] = $code;
+ $handler->display->display_options['arguments']['null']['must_not_be'] = 0;
+
+ return $view;
+ }
+
+ function view_argument_validate_numeric() {
+ $view = new view;
+ $view->name = 'view_argument_validate_numeric';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Argument: Global: Null */
+ $handler->display->display_options['arguments']['null']['id'] = 'null';
+ $handler->display->display_options['arguments']['null']['table'] = 'views';
+ $handler->display->display_options['arguments']['null']['field'] = 'null';
+ $handler->display->display_options['arguments']['null']['style_plugin'] = 'default_summary';
+ $handler->display->display_options['arguments']['null']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['null']['validate_type'] = 'numeric';
+ $handler->display->display_options['arguments']['null']['must_not_be'] = 0;
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/views_basic.test b/sites/all/modules/views/tests/views_basic.test
new file mode 100644
index 000000000..5fc60d8e3
--- /dev/null
+++ b/sites/all/modules/views/tests/views_basic.test
@@ -0,0 +1,178 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsBasicTest.
+ */
+
+/**
+ * Basic test class for Views query builder tests.
+ */
+class ViewsBasicTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Basic query test',
+ 'description' => 'A basic query test for Views.',
+ 'group' => 'Views'
+ );
+ }
+
+ /**
+ * Tests a trivial result set.
+ */
+ public function testSimpleResultSet() {
+ $view = $this->getBasicView();
+
+ // Execute the view.
+ $this->executeView($view);
+
+ // Verify the result.
+ $this->assertEqual(5, count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultset($view, $this->dataSet(), array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+ }
+
+ /**
+ * Tests filtering of the result set.
+ */
+ public function testSimpleFiltering() {
+ $view = $this->getBasicView();
+
+ // Add a filter.
+ $view->display['default']->handler->override_option('filters', array(
+ 'age' => array(
+ 'operator' => '<',
+ 'value' => array(
+ 'value' => '28',
+ 'min' => '',
+ 'max' => '',
+ ),
+ 'group' => '0',
+ 'exposed' => FALSE,
+ 'expose' => array(
+ 'operator' => FALSE,
+ 'label' => '',
+ ),
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Execute the view.
+ $this->executeView($view);
+
+ // Build the expected result.
+ $dataset = array(
+ array(
+ 'id' => 1,
+ 'name' => 'John',
+ 'age' => 25,
+ ),
+ array(
+ 'id' => 2,
+ 'name' => 'George',
+ 'age' => 27,
+ ),
+ array(
+ 'id' => 4,
+ 'name' => 'Paul',
+ 'age' => 26,
+ ),
+ );
+
+ // Verify the result.
+ $this->assertEqual(3, count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultSet($view, $dataset, array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+ }
+
+ /**
+ * Tests simple argument.
+ */
+ public function testSimpleArgument() {
+ $view = $this->getBasicView();
+
+ // Add a argument.
+ $view->display['default']->handler->override_option('arguments', array(
+ 'age' => array(
+ 'default_action' => 'ignore',
+ 'style_plugin' => 'default_summary',
+ 'style_options' => array(),
+ 'wildcard' => 'all',
+ 'wildcard_substitution' => 'All',
+ 'title' => '',
+ 'breadcrumb' => '',
+ 'default_argument_type' => 'fixed',
+ 'default_argument' => '',
+ 'validate_type' => 'none',
+ 'validate_fail' => 'not found',
+ 'break_phrase' => 0,
+ 'not' => 0,
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'validate_user_argument_type' => 'uid',
+ 'validate_user_roles' => array(
+ '2' => 0,
+ ),
+ 'relationship' => 'none',
+ 'default_options_div_prefix' => '',
+ 'default_argument_user' => 0,
+ 'default_argument_fixed' => '',
+ 'default_argument_php' => '',
+ 'validate_argument_node_type' => array(
+ 'page' => 0,
+ 'story' => 0,
+ ),
+ 'validate_argument_node_access' => 0,
+ 'validate_argument_nid_type' => 'nid',
+ 'validate_argument_vocabulary' => array(),
+ 'validate_argument_type' => 'tid',
+ 'validate_argument_transform' => 0,
+ 'validate_user_restrict_roles' => 0,
+ 'validate_argument_php' => '',
+ )
+ ));
+
+ $saved_view = clone $view;
+
+ // Execute with a view
+ $view->set_arguments(array(27));
+ $this->executeView($view);
+
+ // Build the expected result.
+ $dataset = array(
+ array(
+ 'id' => 2,
+ 'name' => 'George',
+ 'age' => 27,
+ ),
+ );
+
+ // Verify the result.
+ $this->assertEqual(1, count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultSet($view, $dataset, array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+
+ // Test "show all" if no argument is present.
+ $view = $saved_view;
+ $this->executeView($view);
+
+ // Build the expected result.
+ $dataset = $this->dataSet();
+
+ $this->assertEqual(5, count($view->result), t('The number of returned rows match.'));
+ $this->assertIdenticalResultSet($view, $dataset, array(
+ 'views_test_name' => 'name',
+ 'views_test_age' => 'age',
+ ));
+ }
+}
diff --git a/sites/all/modules/views/tests/views_cache.test b/sites/all/modules/views/tests/views_cache.test
new file mode 100644
index 000000000..28534a2f3
--- /dev/null
+++ b/sites/all/modules/views/tests/views_cache.test
@@ -0,0 +1,308 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsCacheTest.
+ */
+
+/**
+ * Basic test for pluggable caching.
+ *
+ * @see views_plugin_cache
+ */
+class ViewsCacheTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Cache',
+ 'description' => 'Tests pluggable caching for views.',
+ 'group' => 'Views Plugins'
+ );
+ }
+
+ /**
+ * Build and return a basic view of the views_test table.
+ *
+ * @return view
+ */
+ protected function getBasicView() {
+ views_include('view');
+
+ // Create the basic view.
+ $view = new view();
+ $view->name = 'test_view';
+ $view->add_display('default');
+ $view->base_table = 'views_test';
+
+ // Set up the fields we need.
+ $display = $view->new_display('default', 'Master', 'default');
+ $display->override_option('fields', array(
+ 'id' => array(
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Set up the sort order.
+ $display->override_option('sorts', array(
+ 'id' => array(
+ 'order' => 'ASC',
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ return $view;
+ }
+
+ /**
+ * Tests time based caching.
+ *
+ * @see views_plugin_cache_time
+ */
+ function testTimeCaching() {
+ // Create a basic result which just 2 results.
+ $view = $this->getBasicView();
+ $view->set_display();
+ $view->display_handler->override_option('cache', array(
+ 'type' => 'time',
+ 'results_lifespan' => '3600',
+ 'output_lifespan' => '3600',
+ ));
+
+ $this->executeView($view);
+ // Verify the result.
+ $this->assertEqual(5, count($view->result), t('The number of returned rows match.'));
+
+ // Add another man to the beatles.
+ $record = array(
+ 'name' => 'Rod Davis',
+ 'age' => 29,
+ 'job' => 'Banjo',
+ );
+ drupal_write_record('views_test', $record);
+
+ // The Result should be the same as before, because of the caching.
+ $view = $this->getBasicView();
+ $view->set_display();
+ $view->display_handler->override_option('cache', array(
+ 'type' => 'time',
+ 'results_lifespan' => '3600',
+ 'output_lifespan' => '3600',
+ ));
+
+ $this->executeView($view);
+ // Verify the result.
+ $this->assertEqual(5, count($view->result), t('The number of returned rows match.'));
+ }
+
+ /**
+ * Tests no caching.
+ *
+ * @see views_plugin_cache_time
+ */
+ function testNoneCaching() {
+ // Create a basic result which just 2 results.
+ $view = $this->getBasicView();
+ $view->set_display();
+ $view->display_handler->override_option('cache', array(
+ 'type' => 'none',
+ ));
+
+ $this->executeView($view);
+ // Verify the result.
+ $this->assertEqual(5, count($view->result), t('The number of returned rows match.'));
+
+ // Add another man to the beatles.
+ $record = array(
+ 'name' => 'Rod Davis',
+ 'age' => 29,
+ 'job' => 'Banjo',
+ );
+
+ drupal_write_record('views_test', $record);
+
+ // The Result changes, because the view is not cached.
+ $view = $this->getBasicView();
+ $view->set_display();
+ $view->display_handler->override_option('cache', array(
+ 'type' => 'none',
+ ));
+
+ $this->executeView($view);
+ // Verify the result.
+ $this->assertEqual(6, count($view->result), t('The number of returned rows match.'));
+ }
+
+ /**
+ * Tests css/js storage and restoring mechanism.
+ */
+ function testHeaderStorage() {
+ // Create a view with output caching enabled.
+ // Some hook_views_pre_render in views_test.module adds the test css/js file.
+ // so they should be added to the css/js storage.
+ $view = $this->getBasicView();
+ $view->init_display();
+ $view->name = 'test_cache_header_storage';
+ $view->display_handler->override_option('cache', array(
+ 'type' => 'time',
+ 'output_lifespan' => '3600',
+ ));
+
+ $view->preview();
+ $view->destroy();
+ unset($view->pre_render_called);
+ drupal_static_reset('drupal_add_css');
+ drupal_static_reset('drupal_add_js');
+
+ $view->init_display();
+ $view->preview();
+ $css = drupal_add_css();
+ $css_path = drupal_get_path('module', 'views_test') . '/views_cache.test.css';
+ $js_path = drupal_get_path('module', 'views_test') . '/views_cache.test.js';
+ $js = drupal_add_js();
+
+ $this->assertTrue(isset($css[$css_path]), 'Make sure the css is added for cached views.');
+ $this->assertTrue(isset($js[$js_path]), 'Make sure the js is added for cached views.');
+ $this->assertFalse(!empty($view->pre_render_called), 'Make sure hook_views_pre_render is not called for the cached view.');
+ $view->destroy();
+
+ // Now add some css/jss before running the view.
+ // Make sure that this css is not added when running the cached view.
+ $view->name = 'test_cache_header_storage_2';
+
+ $system_css_path = drupal_get_path('module', 'system') . '/system.maintenance.css';
+ drupal_add_css($system_css_path);
+ $system_js_path = drupal_get_path('module', 'system') . '/system.cron.js';
+ drupal_add_js($system_js_path);
+
+ $view->init_display();
+ $view->preview();
+ $view->destroy();
+ drupal_static_reset('drupal_add_css');
+ drupal_static_reset('drupal_add_js');
+
+ $view->init_display();
+ $view->preview();
+
+ $css = drupal_add_css();
+ $js = drupal_add_js();
+
+ $this->assertFalse(isset($css[$system_css_path]), 'Make sure that unrelated css is not added.');
+ $this->assertFalse(isset($js[$system_js_path]), 'Make sure that unrelated js is not added.');
+
+ }
+
+ /**
+ * Check that HTTP headers are cached for views.
+ */
+ function testHttpHeadersCaching() {
+ // Create a few nodes to appear in RSS feed.
+ for ($i = 0; $i < 5; $i++) {
+ $this->drupalCreateNode();
+ }
+
+ // Make the first request and check returned content type.
+ $this->drupalGet('test_feed_http_headers_caching');
+ $first_content = $this->drupalGetContent();
+ $first_content_type = $this->drupalGetHeader('content-type');
+ $expected_type = 'application/rss+xml';
+ $this->assertIdentical(0, strpos(trim($first_content_type), $expected_type), t('Expected content type returned.'));
+
+ // Check that we have 5 items in RSS feed returned by the first request.
+ $xml = simplexml_load_string($first_content);
+ $items = $xml->xpath('/rss/channel/item');
+ $this->assertEqual(5, count($items), t('The number of RSS feed items matched.'));
+
+ // Create another node to be sure we get cached results on the second
+ // request.
+ $this->drupalCreateNode();
+
+ // Make the second request, check content type and content matching.
+ $this->drupalGet('test_feed_http_headers_caching');
+ $second_content = $this->drupalGetContent();
+ $this->assertEqual($first_content, $second_content, t('The second result fetched from cache.'));
+ $second_content_type = $this->drupalGetHeader('content-type');
+ $this->assertEqual($first_content_type, $second_content_type, t('Content types of responses are equal.'));
+ }
+
+ /**
+ * Test caching of different exposed filter values with the same view result.
+ *
+ * Make sure the output is different.
+ */
+ function testExposedFilterSameResultsCaching() {
+ // Create the view with time-based cache with hour lifetimes and add exposed
+ // filter to it with "Starts with" operator.
+ $view = $this->getBasicView();
+ $view->set_display();
+ $view->display_handler->override_option('cache', array(
+ 'type' => 'time',
+ 'results_lifespan' => '3600',
+ 'output_lifespan' => '3600',
+ ));
+ $view->display_handler->override_option('filters', array(
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ 'operator' => 'starts',
+ 'exposed' => TRUE,
+ 'expose' => array(
+ 'operator_id' => 'name_op',
+ 'operator' => 'name_op',
+ 'identifier' => 'name',
+ ),
+ ),
+ ));
+
+ // Clone the view before setting exposed input.
+ $clone = $view->copy();
+
+ // Pass "Rin" to the exposed filter and check that only one row returned.
+ $view->set_exposed_input(array(
+ 'name' => 'Rin',
+ ));
+ $this->executeView($view);
+ $first_result = $view->result;
+ $first_output = $view->render();
+ $this->assertEqual(1, count($first_result), t('The number of rows returned by the first view match.'));
+
+ // Pass full "Ringo" to the exposed filter at the second time and make sure
+ // results are the same.
+ $clone->set_exposed_input(array(
+ 'name' => 'Ringo',
+ ));
+ $this->executeView($clone);
+ $second_result = $clone->result;
+ $second_output = $clone->render();
+ $this->assertEqual($first_result, $second_result, t('Results of both views are the same.'));
+
+ // Check that output is not the same and it contains full "Ringo" word in
+ // default value of exposed input.
+ $this->assertNotEqual($first_output, $second_output, t('Output of the second view is different.'));
+ $this->drupalSetContent($second_output);
+ $element = $this->xpath('//input[@name="name" and @value="Ringo"]');
+ $this->assertTrue(!empty($element), t('Input field of exposed filter has the second value.'));
+
+ $view->destroy();
+ $clone->destroy();
+ }
+
+}
diff --git a/sites/all/modules/views/tests/views_cache.test.css b/sites/all/modules/views/tests/views_cache.test.css
new file mode 100644
index 000000000..8dd17c148
--- /dev/null
+++ b/sites/all/modules/views/tests/views_cache.test.css
@@ -0,0 +1,5 @@
+/**
+ * @file
+ * Just a placeholder file for the test.
+ * @see ViewsCacheTest::testHeaderStorage
+ */
diff --git a/sites/all/modules/views/tests/views_cache.test.js b/sites/all/modules/views/tests/views_cache.test.js
new file mode 100644
index 000000000..8dd17c148
--- /dev/null
+++ b/sites/all/modules/views/tests/views_cache.test.js
@@ -0,0 +1,5 @@
+/**
+ * @file
+ * Just a placeholder file for the test.
+ * @see ViewsCacheTest::testHeaderStorage
+ */
diff --git a/sites/all/modules/views/tests/views_exposed_form.test b/sites/all/modules/views/tests/views_exposed_form.test
new file mode 100644
index 000000000..72baf2cb4
--- /dev/null
+++ b/sites/all/modules/views/tests/views_exposed_form.test
@@ -0,0 +1,170 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsExposedFormTest.
+ */
+
+/**
+ * Tests exposed forms.
+ */
+class ViewsExposedFormTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Exposed forms',
+ 'description' => 'Test exposed forms functionality.',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('views_ui');
+ module_enable(array('views_ui'));
+ // @TODO Figure out why it's required to clear the cache here.
+ views_module_include('views_default', TRUE);
+ views_get_all_views(TRUE);
+ menu_rebuild();
+ }
+
+ /**
+ * Tests, whether and how the reset button can be renamed.
+ */
+ public function testRenameResetButton() {
+ $account = $this->drupalCreateUser();
+ $this->drupalLogin($account);
+ // Create some random nodes.
+ for ($i = 0; $i < 5; $i++) {
+ $this->drupalCreateNode();
+ }
+ // Look at the page and check the label "reset".
+ $this->drupalGet('test_rename_reset_button');
+ // Rename the label of the reset button.
+ $view = views_get_view('test_rename_reset_button');
+ $view->set_display('default');
+
+ $exposed_form = $view->display_handler->get_option('exposed_form');
+ $exposed_form['options']['reset_button_label'] = $expected_label = $this->randomName();
+ $exposed_form['options']['reset_button'] = TRUE;
+ $view->display_handler->set_option('exposed_form', $exposed_form);
+ $view->save();
+
+ views_invalidate_cache();
+
+ // Look whether ther reset button label changed.
+ $this->drupalGet('test_rename_reset_button');
+
+ $this->helperButtonHasLabel('edit-reset', $expected_label);
+ }
+
+ /**
+ * Tests the admin interface of exposed filter and sort items.
+ */
+ function testExposedAdminUi() {
+ $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration'));
+ $this->drupalLogin($admin_user);
+ menu_rebuild();
+ $edit = array();
+
+ $this->drupalGet('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/filter/type');
+ // Be sure that the button is called exposed.
+ $this->helperButtonHasLabel('edit-options-expose-button-button', t('Expose filter'));
+
+ // The first time the filter UI is displayed, the operator and the
+ // value forms should be shown.
+ $this->assertFieldById('edit-options-operator-in', '', 'Operator In exists');
+ $this->assertFieldById('edit-options-operator-not-in', '', 'Operator Not In exists');
+ $this->assertFieldById('edit-options-value-page', '', 'Checkbox for Page exists');
+ $this->assertFieldById('edit-options-value-article', '', 'Checkbox for Article exists');
+
+ // Click the Expose filter button.
+ $this->drupalPost('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/filter/type', $edit, t('Expose filter'));
+ // Check the label of the expose button.
+ $this->helperButtonHasLabel('edit-options-expose-button-button', t('Hide filter'));
+ // Check the label of the grouped exposed button
+ $this->helperButtonHasLabel('edit-options-group-button-button', t('Grouped filters'));
+
+ // After Expose the filter, Operator and Value should be still here
+ $this->assertFieldById('edit-options-operator-in', '', 'Operator In exists');
+ $this->assertFieldById('edit-options-operator-not-in', '', 'Operator Not In exists');
+ $this->assertFieldById('edit-options-value-page', '', 'Checkbox for Page exists');
+ $this->assertFieldById('edit-options-value-article', '', 'Checkbox for Article exists');
+
+ // Check the validations of the filter handler.
+ $edit = array();
+ $edit['options[expose][identifier]'] = '';
+ $this->drupalPost(NULL, $edit, t('Apply'));
+ $this->assertText(t('The identifier is required if the filter is exposed.'));
+
+ $edit = array();
+ $edit['options[expose][identifier]'] = 'value';
+ $this->drupalPost(NULL, $edit, t('Apply'));
+ $this->assertText(t('This identifier is not allowed.'));
+
+ // Now check the sort criteria.
+ $this->drupalGet('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/sort/created');
+ $this->helperButtonHasLabel('edit-options-expose-button-button', t('Expose sort'));
+ $this->assertNoFieldById('edit-options-expose-label', '', t('Make sure no label field is shown'));
+
+ // Click the Grouped Filters button.
+ $this->drupalGet('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/filter/type');
+ $this->drupalPost(NULL, array(), t('Grouped filters'));
+
+ // After click on 'Grouped Filters' standard operator and value should not be displayed
+ $this->assertNoFieldById('edit-options-operator-in', '', 'Operator In not exists');
+ $this->assertNoFieldById('edit-options-operator-not-in', '', 'Operator Not In not exists');
+ $this->assertNoFieldById('edit-options-value-page', '', 'Checkbox for Page not exists');
+ $this->assertNoFieldById('edit-options-value-article', '', 'Checkbox for Article not exists');
+
+
+ // Check that after click on 'Grouped Filters', a new button is shown to
+ // add more items to the list.
+ $this->helperButtonHasLabel('edit-options-group-info-add-group', t('Add another item'));
+
+ // Create a grouped filter
+ $this->drupalGet('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/filter/type');
+ $edit = array();
+ $edit["options[group_info][group_items][1][title]"] = 'Is Article';
+ $edit["options[group_info][group_items][1][value][article]"] = 'article';
+
+ $edit["options[group_info][group_items][2][title]"] = 'Is Page';
+ $edit["options[group_info][group_items][2][value][page]"] = TRUE;
+
+ $edit["options[group_info][group_items][3][title]"] = 'Is Page and Article';
+ $edit["options[group_info][group_items][3][value][article]"] = TRUE;
+ $edit["options[group_info][group_items][3][value][page]"] = TRUE;
+ $this->drupalPost(NULL, $edit, t('Apply'));
+
+ // Validate that all the titles are defined for each group
+ $this->drupalGet('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/filter/type');
+ $edit = array();
+ $edit["options[group_info][group_items][1][title]"] = 'Is Article';
+ $edit["options[group_info][group_items][1][value][article]"] = TRUE;
+
+ // This should trigger an error
+ $edit["options[group_info][group_items][2][title]"] = '';
+ $edit["options[group_info][group_items][2][value][page]"] = TRUE;
+
+ $edit["options[group_info][group_items][3][title]"] = 'Is Page and Article';
+ $edit["options[group_info][group_items][3][value][article]"] = TRUE;
+ $edit["options[group_info][group_items][3][value][page]"] = TRUE;
+ $this->drupalPost(NULL, $edit, t('Apply'));
+ $this->assertRaw(t('The title is required if value for this item is defined.'), t('Group items should have a title'));
+
+ // Un-Expose the filter
+ $this->drupalGet('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/filter/type');
+ $this->drupalPost(NULL, array(), t('Hide filter'));
+
+ // After Un-Expose the filter, Operator and Value should be shown again
+ $this->assertFieldById('edit-options-operator-in', '', 'Operator In exists after hide filter');
+ $this->assertFieldById('edit-options-operator-not-in', '', 'Operator Not In exists after hide filter');
+ $this->assertFieldById('edit-options-value-page', '', 'Checkbox for Page exists after hide filter');
+ $this->assertFieldById('edit-options-value-article', '', 'Checkbox for Article exists after hide filter');
+
+ // Click the Expose sort button.
+ $edit = array();
+ $this->drupalPost('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/sort/created', $edit, t('Expose sort'));
+ // Check the label of the expose button.
+ $this->helperButtonHasLabel('edit-options-expose-button-button', t('Hide sort'));
+ $this->assertFieldById('edit-options-expose-label', '', t('Make sure a label field is shown'));
+ }
+}
diff --git a/sites/all/modules/views/tests/views_glossary.test b/sites/all/modules/views/tests/views_glossary.test
new file mode 100644
index 000000000..0fe0fbae6
--- /dev/null
+++ b/sites/all/modules/views/tests/views_glossary.test
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsGlossaryTestCase.
+ */
+
+/**
+ * Tests glossary view ( summary of arguments ).
+ */
+class ViewsGlossaryTestCase extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Glossary Test',
+ 'description' => 'Tests glossary functionality of views.',
+ 'group' => 'Views',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('views');
+ }
+
+ /**
+ * Tests the default glossary view.
+ */
+ public function testGlossaryView() {
+ // create a contentype and add some nodes, with a non random title.
+ $type = $this->drupalCreateContentType();
+ $nodes_per_char = array(
+ 'd' => 1,
+ 'r' => 4,
+ 'u' => 10,
+ 'p' => 2,
+ 'a' => 3,
+ 'l' => 6,
+ );
+ foreach ($nodes_per_char as $char => $count) {
+ $setting = array(
+ 'type' => $type->type
+ );
+ for ($i = 0; $i < $count; $i++) {
+ $node = $setting;
+ $node['title'] = $char . $this->randomString(3);
+ $this->drupalCreateNode($node);
+ }
+ }
+
+ // Execute glossary view
+ $view = views_get_view('glossary');
+ $view->set_display('attachment');
+ $view->execute_display('attachment');
+
+ // Check that the amount of nodes per char.
+ $result_nodes_per_char = array();
+ foreach ($view->result as $item) {
+ $this->assertEqual($nodes_per_char[$item->title_truncated], $item->num_records);
+ }
+ }
+}
diff --git a/sites/all/modules/views/tests/views_groupby.test b/sites/all/modules/views/tests/views_groupby.test
new file mode 100644
index 000000000..4e0925658
--- /dev/null
+++ b/sites/all/modules/views/tests/views_groupby.test
@@ -0,0 +1,352 @@
+<?php
+
+/**
+ * @file
+ * Tests aggregate functionality of Views.
+ */
+
+/**
+ * Tests aggregate functionality of views, for example count.
+ */
+class ViewsQueryGroupByTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Groupby',
+ 'description' => 'Tests aggregate functionality of views, for example count.',
+ 'group' => 'Views',
+ );
+
+ }
+
+ /**
+ * Tests aggregate count feature.
+ */
+ public function testAggregateCount() {
+ // Create 2 nodes of type1 and 3 nodes of type2
+ $type1 = $this->drupalCreateContentType();
+ $type2 = $this->drupalCreateContentType();
+
+ $node_1 = array(
+ 'type' => $type1->type,
+ );
+ $this->drupalCreateNode($node_1);
+ $this->drupalCreateNode($node_1);
+ $this->drupalCreateNode($node_1);
+ $this->drupalCreateNode($node_1);
+
+ $node_2 = array(
+ 'type' => $type2->type,
+ );
+ $this->drupalCreateNode($node_2);
+ $this->drupalCreateNode($node_2);
+ $this->drupalCreateNode($node_2);
+
+ $view = $this->viewsAggregateCountView();
+ $output = $view->execute_display();
+
+ $this->assertEqual(count($view->result), 2, 'Make sure the count of items is right.');
+
+ $types = array();
+ foreach ($view->result as $item) {
+ // num_records is a alias for nid.
+ $types[$item->node_type] = $item->num_records;
+ }
+
+ $this->assertEqual($types[$type1->type], 4);
+ $this->assertEqual($types[$type2->type], 3);
+ }
+
+ //public function testAggregateSum() {
+ //}
+
+ public function viewsAggregateCountView() {
+ $view = new view;
+ $view->name = 'aggregate_count';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->human_name = '';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['group_by'] = TRUE;
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'title';
+ $handler->display->display_options['fields']['nid']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['nid']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['nid']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['nid']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['nid']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['nid']['link_to_node'] = 0;
+ /* Contextual filter: Content: Type */
+ $handler->display->display_options['arguments']['type']['id'] = 'type';
+ $handler->display->display_options['arguments']['type']['table'] = 'node';
+ $handler->display->display_options['arguments']['type']['field'] = 'type';
+ $handler->display->display_options['arguments']['type']['default_action'] = 'summary';
+ $handler->display->display_options['arguments']['type']['default_argument_type'] = 'fixed';
+ $handler->display->display_options['arguments']['type']['summary']['format'] = 'default_summary';
+
+
+ return $view;
+ }
+
+ /**
+ * @param string|null $group_by
+ * (optional) Which group_by function should be used, for example sum or
+ * count. If omitted, the aggregation is tested with no group function.
+ * @param array|null $values
+ * (optional) Expected values.
+ */
+ function GroupByTestHelper($group_by = NULL, $values = NULL) {
+ // Create 4 nodes of type1 and 3 nodes of type2
+ $type1 = $this->drupalCreateContentType();
+ $type2 = $this->drupalCreateContentType();
+
+ $node_1 = array(
+ 'type' => $type1->type,
+ );
+ // Nids from 1 to 4.
+ $this->drupalCreateNode($node_1);
+ $this->drupalCreateNode($node_1);
+ $this->drupalCreateNode($node_1);
+ $this->drupalCreateNode($node_1);
+ $node_2 = array(
+ 'type' => $type2->type,
+ );
+ // Nids from 5 to 7.
+ $this->drupalCreateNode($node_2);
+ $this->drupalCreateNode($node_2);
+ $this->drupalCreateNode($node_2);
+
+ $view = $this->viewsGroupByViewHelper($group_by);
+ $output = $view->execute_display();
+
+ $this->assertEqual(count($view->result), 2, 'Make sure the count of items is right.');
+
+ $results = array();
+ // There's no need for a function in order to have aggregation.
+ if (empty($group_by)) {
+ $types = array($type1->type, $type2->type);
+ $results = array_map(function ($item) { return $item->node_type; }, $view->result);
+ sort($types);
+ sort($results);
+ $this->assertIdentical($results, $types);
+ // Exit here with no aggregation function.
+ return;
+ }
+
+ // Group by nodetype to identify the right count.
+ foreach ($view->result as $item) {
+ $results[$item->node_type] = $item->nid;
+ }
+ $this->assertEqual($results[$type1->type], $values[0]);
+ $this->assertEqual($results[$type2->type], $values[1]);
+ }
+
+ function viewsGroupByViewHelper($group_by = NULL) {
+ $view = new view;
+ $view->name = 'group_by_count';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['group_by'] = TRUE;
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+
+ // The test view has 2 fields ('nid' and 'type'). Don't add 'nid' when
+ // having no aggregation function. We just want to aggregate on node type.
+ if (!empty($group_by)) {
+ /* Field: Content: Nid */
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'nid';
+ $handler->display->display_options['fields']['nid']['group_type'] = $group_by;
+ $handler->display->display_options['fields']['nid']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['nid']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['nid']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['nid']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['nid']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['nid']['link_to_node'] = 0;
+ }
+
+ /* Field: Content: Type */
+ $handler->display->display_options['fields']['type']['id'] = 'type';
+ $handler->display->display_options['fields']['type']['table'] = 'node';
+ $handler->display->display_options['fields']['type']['field'] = 'type';
+ $handler->display->display_options['fields']['type']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['type']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['type']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['type']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['type']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['type']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['type']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['type']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['type']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['type']['link_to_node'] = 0;
+
+ return $view;
+ }
+
+ public function testGroupByCount() {
+ $this->GroupByTestHelper('count', array(4, 3));
+ }
+
+ function testGroupBySum() {
+ $this->GroupByTestHelper('sum', array(10, 18));
+ }
+
+
+ function testGroupByAverage() {
+ $this->GroupByTestHelper('avg', array(2.5, 6));
+ }
+
+ function testGroupByMin() {
+ $this->GroupByTestHelper('min', array(1, 5));
+ }
+
+ function testGroupByMax() {
+ $this->GroupByTestHelper('max', array(4, 7));
+ }
+
+ function testGroupByNone() {
+ $this->GroupByTestHelper();
+ }
+
+ public function testGroupByCountOnlyFilters() {
+ // Check if GROUP BY and HAVING are included when a view
+ // Doesn't display SUM, COUNT, MAX... functions in SELECT statment
+
+ $type1 = $this->drupalCreateContentType();
+
+ $node_1 = array(
+ 'type' => $type1->type,
+ );
+ for ($x = 0; $x < 10; $x++) {
+ $this->drupalCreateNode($node_1);
+ }
+
+ $view = $this->viewsGroupByCountViewOnlyFilters();
+ $output = $view->execute_display();
+
+ $this->assertTrue(strpos($view->build_info['query'], 'GROUP BY'), t('Make sure that GROUP BY is in the query'));
+ $this->assertTrue(strpos($view->build_info['query'], 'HAVING'), t('Make sure that HAVING is in the query'));
+ }
+
+ function viewsGroupByCountViewOnlyFilters() {
+ $view = new view;
+ $view->name = 'group_by_in_filters';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['group_by'] = TRUE;
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Nodo: Tipo */
+ $handler->display->display_options['fields']['type']['id'] = 'type';
+ $handler->display->display_options['fields']['type']['table'] = 'node';
+ $handler->display->display_options['fields']['type']['field'] = 'type';
+ $handler->display->display_options['fields']['type']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['type']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['type']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['type']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['type']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['type']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['type']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['type']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['type']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['type']['link_to_node'] = 0;
+ /* Filtrar: Nodo: Nid */
+ $handler->display->display_options['filters']['nid']['id'] = 'nid';
+ $handler->display->display_options['filters']['nid']['table'] = 'node';
+ $handler->display->display_options['filters']['nid']['field'] = 'nid';
+ $handler->display->display_options['filters']['nid']['group_type'] = 'count';
+ $handler->display->display_options['filters']['nid']['operator'] = '>';
+ $handler->display->display_options['filters']['nid']['value']['value'] = '3';
+
+ return $view;
+ }
+}
+
+/**
+ * Tests UI of aggregate functionality..
+ */
+class viewsUiGroupbyTestCase extends DrupalWebTestCase {
+ function setUp() {
+ // Enable views_ui.
+ parent::setUp('views_ui', 'views_test');
+
+ // Create and log in a user with administer views permission.
+ $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view revisions'));
+ $this->drupalLogin($views_admin);
+ }
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Groupby UI',
+ 'description' => 'Tests UI of aggregate functionality.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ /**
+ * Tests whether basic saving works.
+ *
+ * @todo: this should check the change of the settings as well.
+ */
+ function testGroupBySave() {
+ $this->drupalGet('admin/structure/views/view/test_views_groupby_save/edit');
+
+ $edit = array(
+ 'group_by' => TRUE,
+ );
+ $this->drupalPost('admin/structure/views/nojs/display/test_views_groupby_save/default/group_by', $edit, t('Apply'));
+
+ $this->drupalGet('admin/structure/views/view/test_views_groupby_save/edit');
+ $this->drupalPost('admin/structure/views/view/test_views_groupby_save/edit', array(), t('Save'));
+
+ $this->drupalGet('admin/structure/views/nojs/display/test_views_groupby_save/default/group_by');
+ }
+}
diff --git a/sites/all/modules/views/tests/views_handlers.test b/sites/all/modules/views/tests/views_handlers.test
new file mode 100644
index 000000000..d54a7dff1
--- /dev/null
+++ b/sites/all/modules/views/tests/views_handlers.test
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsHandlersTest.
+ */
+
+/**
+ * Tests abstract handlers of views.
+ */
+class ViewsHandlersTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Handlers test',
+ 'description' => 'test abstract handler definitions',
+ 'group' => 'Views',
+ );
+ }
+
+ protected function setUp() {
+ parent::setUp('views', 'views_ui');
+ module_enable(array('views_ui'));
+ }
+
+ function testFilterInOperatorUi() {
+ $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration'));
+ $this->drupalLogin($admin_user);
+ menu_rebuild();
+
+ $path = 'admin/structure/views/nojs/config-item/test_filter_in_operator_ui/default/filter/type';
+ $this->drupalGet($path);
+ $this->assertFieldByName('options[expose][reduce]', FALSE);
+
+ $edit = array(
+ 'options[expose][reduce]' => TRUE,
+ );
+ $this->drupalPost($path, $edit, t('Apply'));
+ $this->drupalGet($path);
+ $this->assertFieldByName('options[expose][reduce]', TRUE);
+ }
+
+ /**
+ * Tests views_break_phrase_string function.
+ */
+ function test_views_break_phrase_string() {
+ $empty_stdclass = new stdClass();
+ $empty_stdclass->operator = 'or';
+ $empty_stdclass->value = array();
+
+ $null = NULL;
+ // check defaults
+ $this->assertEqual($empty_stdclass, views_break_phrase_string('', $null));
+
+ $handler = views_get_handler('node', 'title', 'argument');
+ $this->assertEqual($handler, views_break_phrase_string('', $handler));
+
+ // test ors
+ $handler = views_break_phrase_string('word1 word2+word');
+ $this->assertEqualValue(array('word1', 'word2', 'word'), $handler);
+ $this->assertEqual('or', $handler->operator);
+ $handler = views_break_phrase_string('word1+word2+word');
+ $this->assertEqualValue(array('word1', 'word2', 'word'), $handler);
+ $this->assertEqual('or', $handler->operator);
+ $handler = views_break_phrase_string('word1 word2 word');
+ $this->assertEqualValue(array('word1', 'word2', 'word'), $handler);
+ $this->assertEqual('or', $handler->operator);
+ $handler = views_break_phrase_string('word-1+word-2+word');
+ $this->assertEqualValue(array('word-1', 'word-2', 'word'), $handler);
+ $this->assertEqual('or', $handler->operator);
+ $handler = views_break_phrase_string('wõrd1+wõrd2+wõrd');
+ $this->assertEqualValue(array('wõrd1', 'wõrd2', 'wõrd'), $handler);
+ $this->assertEqual('or', $handler->operator);
+
+ // test ands.
+ $handler = views_break_phrase_string('word1,word2,word');
+ $this->assertEqualValue(array('word1', 'word2', 'word'), $handler);
+ $this->assertEqual('and', $handler->operator);
+ $handler = views_break_phrase_string('word1 word2,word');
+ $this->assertEqualValue(array('word1 word2', 'word'), $handler);
+ $this->assertEqual('and', $handler->operator);
+ $handler = views_break_phrase_string('word1,word2 word');
+ $this->assertEqualValue(array('word1', 'word2 word'), $handler);
+ $this->assertEqual('and', $handler->operator);
+ $handler = views_break_phrase_string('word-1,word-2,word');
+ $this->assertEqualValue(array('word-1', 'word-2', 'word'), $handler);
+ $this->assertEqual('and', $handler->operator);
+ $handler = views_break_phrase_string('wõrd1,wõrd2,wõrd');
+ $this->assertEqualValue(array('wõrd1', 'wõrd2', 'wõrd'), $handler);
+ $this->assertEqual('and', $handler->operator);
+
+ // test a single word
+ $handler = views_break_phrase_string('word');
+ $this->assertEqualValue(array('word'), $handler);
+ $this->assertEqual('and', $handler->operator);
+ }
+
+ /**
+ * Tests views_break_phrase function.
+ */
+ function test_views_break_phrase() {
+ $empty_stdclass = new stdClass();
+ $empty_stdclass->operator = 'or';
+ $empty_stdclass->value = array();
+
+ $null = NULL;
+ // check defaults
+ $this->assertEqual($empty_stdclass, views_break_phrase('', $null));
+
+ $handler = views_get_handler('node', 'title', 'argument');
+ $this->assertEqual($handler, views_break_phrase('', $handler));
+
+ // Generate three random numbers which can be used below;
+ $n1 = rand(0, 100);
+ $n2 = rand(0, 100);
+ $n3 = rand(0, 100);
+ // test ors
+ $this->assertEqualValue(array($n1, $n2, $n3), views_break_phrase("$n1 $n2+$n3", $handler));
+ $this->assertEqual('or', $handler->operator);
+ $this->assertEqualValue(array($n1, $n2, $n3), views_break_phrase("$n1+$n2+$n3", $handler));
+ $this->assertEqual('or', $handler->operator);
+ $this->assertEqualValue(array($n1, $n2, $n3), views_break_phrase("$n1 $n2 $n3", $handler));
+ $this->assertEqual('or', $handler->operator);
+ $this->assertEqualValue(array($n1, $n2, $n3), views_break_phrase("$n1 $n2++$n3", $handler));
+ $this->assertEqual('or', $handler->operator);
+
+ // test ands.
+ $this->assertEqualValue(array($n1, $n2, $n3), views_break_phrase("$n1,$n2,$n3", $handler));
+ $this->assertEqual('and', $handler->operator);
+ $this->assertEqualValue(array($n1, $n2, $n3), views_break_phrase("$n1,,$n2,$n3", $handler));
+ $this->assertEqual('and', $handler->operator);
+ }
+
+ /**
+ * Check to see if two values are equal.
+ *
+ * @param $first
+ * The first value to check.
+ * @param views_handler $handler
+ * @param string $message
+ * The message to display along with the assertion.
+ * @param string $group
+ * The type of assertion - examples are "Browser", "PHP".
+ *
+ * @return bool
+ * TRUE if the assertion succeeded, FALSE otherwise.
+ */
+ protected function assertEqualValue($first, $handler, $message = '', $group = 'Other') {
+ return $this->assert($first == $handler->value, $message ? $message : t('First value is equal to second value'), $group);
+ }
+}
diff --git a/sites/all/modules/views/tests/views_module.test b/sites/all/modules/views/tests/views_module.test
new file mode 100644
index 000000000..e160964a9
--- /dev/null
+++ b/sites/all/modules/views/tests/views_module.test
@@ -0,0 +1,241 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsModuleTest.
+ */
+
+/**
+ * Tests basic functions from the Views module.
+ */
+class ViewsModuleTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Tests views.module',
+ 'description' => 'Tests some basic functions of views.module',
+ 'group' => 'Views',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp();
+ drupal_theme_rebuild();
+ }
+
+ public function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test_previous'] = array();
+ $data['views_test_previous']['id']['field']['moved to'] = array('views_test', 'id');
+ $data['views_test_previous']['id']['filter']['moved to'] = array('views_test', 'id');
+ $data['views_test']['age_previous']['field']['moved to'] = array('views_test', 'age');
+ $data['views_test']['age_previous']['sort']['moved to'] = array('views_test', 'age');
+ $data['views_test_previous']['name_previous']['field']['moved to'] = array('views_test', 'name');
+ $data['views_test_previous']['name_previous']['argument']['moved to'] = array('views_test', 'name');
+
+ return $data;
+ }
+
+ public function test_views_trim_text() {
+ // Test unicode, @see http://drupal.org/node/513396#comment-2839416
+ $text = array(
+ 'Tuy nhiên, những hi vọng',
+ 'Giả sử chúng tôi có 3 Apple',
+ 'siêu nhỏ này là bộ xử lý',
+ 'Di động của nhà sản xuất Phần Lan',
+ 'khoảng cách từ đại lí đến',
+ 'của hãng bao gồm ba dòng',
+ 'сд асд асд ас',
+ 'асд асд асд ас'
+ );
+ // Just test maxlength without word boundry.
+ $alter = array(
+ 'max_length' => 10,
+ );
+ $expect = array(
+ 'Tuy nhiên,',
+ 'Giả sử chú',
+ 'siêu nhỏ n',
+ 'Di động củ',
+ 'khoảng các',
+ 'của hãng b',
+ 'сд асд асд',
+ 'асд асд ас',
+ );
+
+ foreach ($text as $key => $line) {
+ $result_text = views_trim_text($alter, $line);
+ $this->assertEqual($result_text, $expect[$key]);
+ }
+
+ // Test also word_boundary
+ $alter['word_boundary'] = TRUE;
+ $expect = array(
+ 'Tuy nhiên',
+ 'Giả sử',
+ 'siêu nhỏ',
+ 'Di động',
+ 'khoảng',
+ 'của hãng',
+ 'сд асд',
+ 'асд асд',
+ );
+
+ foreach ($text as $key => $line) {
+ $result_text = views_trim_text($alter, $line);
+ $this->assertEqual($result_text, $expect[$key]);
+ }
+ }
+
+ /**
+ * Tests the dynamic includes of templates via module feature.
+ */
+ function testModuleTemplates() {
+ $views_status = variable_get('views_defaults', array());
+ $views_status['frontpage'] = FALSE; // false is enabled
+ variable_set('views_defaults', $views_status);
+
+ $existing = array();
+ $type = array();
+ $theme = array();
+ $path = array();
+ $registry = views_theme($existing, $type, $theme, $path);
+ $this->assertTrue(isset($registry['views_view__frontpage']));
+ }
+
+ /**
+ * Tests the views_get_handler method.
+ */
+ function testviews_get_handler() {
+ $types = array('field', 'area', 'filter');
+ foreach ($types as $type) {
+ $handler = views_get_handler($this->randomName(), $this->randomName(), $type);
+ $this->assertEqual('views_handler_' . $type . '_broken', get_class($handler), t('Make sure that a broken handler of type: @type are created', array('@type' => $type)));
+ }
+
+ $views_data = $this->viewsData();
+ $test_tables = array('views_test' => array('id', 'name'));
+ foreach ($test_tables as $table => $fields) {
+ foreach ($fields as $field) {
+ $data = $views_data[$table][$field];
+ foreach ($data as $id => $field_data) {
+ if (!in_array($id, array('title', 'help'))) {
+ $handler = views_get_handler($table, $field, $id);
+ $this->assertInstanceHandler($handler, $table, $field, $id);
+ }
+ }
+ }
+ }
+
+ // Test the automatic conversion feature.
+
+ // Test the automatic table renaming.
+ $handler = views_get_handler('views_test_previous', 'id', 'field');
+ $this->assertInstanceHandler($handler, 'views_test', 'id', 'field');
+ $handler = views_get_handler('views_test_previous', 'id', 'filter');
+ $this->assertInstanceHandler($handler, 'views_test', 'id', 'filter');
+
+ // Test the automatic field renaming.
+ $handler = views_get_handler('views_test', 'age_previous', 'field');
+ $this->assertInstanceHandler($handler, 'views_test', 'age', 'field');
+ $handler = views_get_handler('views_test', 'age_previous', 'sort');
+ $this->assertInstanceHandler($handler, 'views_test', 'age', 'sort');
+
+ // Test the automatic table and field renaming.
+ $handler = views_get_handler('views_test_previous', 'name_previous', 'field');
+ $this->assertInstanceHandler($handler, 'views_test', 'name', 'field');
+ $handler = views_get_handler('views_test_previous', 'name_previous', 'argument');
+ $this->assertInstanceHandler($handler, 'views_test', 'name', 'argument');
+
+ // Test the override handler feature.
+ $handler = views_get_handler('views_test', 'job', 'filter', 'views_handler_filter');
+ $this->assertEqual('views_handler_filter', get_class($handler));
+ }
+
+ /**
+ * Tests views_fetch_data().
+ */
+ function testFetchData() {
+
+ // Make sure we start with a empty cache.
+ $this->resetStaticViewsDataCache();
+ cache_clear_all('*', 'cache_views', TRUE);
+ variable_set('views_test_views_data_count', 0);
+
+ // Request info about an existing table.
+ $this->assertTrue(views_fetch_data('views_test'), 'Data about existing table returned');
+ // This should have triggered a views data rebuild, and written a cache
+ // entry for all tables and the requested table but no other tables.
+ $this->assertEqual(variable_get('views_test_views_data_count', 0), 1, 'Views data rebuilt once');
+ $this->assertTrue(cache_get('views_data:en', 'cache_views'), 'Cache for all tables written.');
+ $this->assertTrue(cache_get('views_data:views_test:en', 'cache_views'), 'Cache for requested table written.');
+ $this->assertFalse(cache_get('views_data:views_test_previous:en', 'cache_views'), 'No Cache written for not requested table.');
+ $this->assertTrue(drupal_static('_views_fetch_data_fully_loaded'), 'Views data is fully loaded');
+
+ $this->resetStaticViewsDataCache();
+
+ // Request the same table again.
+ $this->assertTrue(views_fetch_data('views_test'), 'Data about existing table returned');
+ $this->assertEqual(variable_get('views_test_views_data_count', 0), 1, 'Views data rebuilt once');
+ $this->assertFalse(drupal_static('_views_fetch_data_fully_loaded'), 'Views data is not fully loaded');
+
+ $this->resetStaticViewsDataCache();
+
+ // Request a missing table, this should load the full cache from cache but
+ // not rebuilt.
+ $this->assertFalse(views_fetch_data('views_test_missing'), 'No data about missing table returned');
+ $this->assertEqual(variable_get('views_test_views_data_count', 0), 1, 'Views data rebuilt once');
+ $this->assertTrue(drupal_static('_views_fetch_data_fully_loaded'), 'Views data is fully loaded');
+
+ $this->resetStaticViewsDataCache();
+
+ // Request the same empty table again, this should load only that (empty)
+ // cache for that table.
+ $this->assertFalse(views_fetch_data('views_test_missing'), 'No data about missing table returned');
+ $this->assertEqual(variable_get('views_test_views_data_count', 0), 1, 'Views data rebuilt once');
+ $this->assertFalse(drupal_static('_views_fetch_data_fully_loaded'), 'Views data is not fully loaded');
+
+
+ // Test if the cache consistency is ensured. There was an issue where
+ // calling _views_fetch_data() first with a table would prevent the function
+ // from properly rebuilt a missing the general cache entry.
+ // See https://www.drupal.org/node/2475669 for details.
+ // Make sure we start with a empty cache.
+ $this->resetStaticViewsDataCache();
+ cache_clear_all('*', 'cache_views', TRUE);
+
+ // Prime the static cache of _views_fetch_data() by calling it with a table
+ // first.
+ views_fetch_data('views_test');
+ // Now remove the general cache.
+ cache_clear_all('views_data:en', 'cache_views');
+ // Reset the static cache to see if fetches from the persistent cache
+ // properly rebuild the static cache.
+ $this->resetStaticViewsDataCache();
+ // Prime the static cache of _views_fetch_data() by calling it with a table
+ // first.
+ views_fetch_data('views_test');
+ // Fetch the general cache, which was deleted, an see if it is rebuild
+ // properly.
+ views_fetch_data();
+ $this->assertTrue(cache_get('views_data:en', 'cache_views'), 'Cache for all tables was properly rebuild.');
+ }
+
+ /**
+ * Ensure that a certain handler is a instance of a certain table/field.
+ */
+ function assertInstanceHandler($handler, $table, $field, $id) {
+ $table_data = views_fetch_data($table);
+ $field_data = $table_data[$field][$id];
+
+ $this->assertEqual($field_data['handler'], get_class($handler));
+ }
+
+ /**
+ * Resets the views data cache.
+ */
+ protected function resetStaticViewsDataCache() {
+ drupal_static_reset('_views_fetch_data_cache');
+ drupal_static_reset('_views_fetch_data_recursion_protected');
+ drupal_static_reset('_views_fetch_data_fully_loaded');
+ }
+}
diff --git a/sites/all/modules/views/tests/views_pager.test b/sites/all/modules/views/tests/views_pager.test
new file mode 100644
index 000000000..fef491537
--- /dev/null
+++ b/sites/all/modules/views/tests/views_pager.test
@@ -0,0 +1,496 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsPagerTest.
+ */
+
+/**
+ * Tests the pluggable pager system.
+ */
+class ViewsPagerTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Pager',
+ 'description' => 'Test the pluggable pager system',
+ 'group' => 'Views Plugins',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('views', 'views_ui', 'views_test');
+ }
+
+ /**
+ * Pagers was sometimes not stored.
+ *
+ * @see http://drupal.org/node/652712
+ */
+ public function testStorePagerSettings() {
+ $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration'));
+ $this->drupalLogin($admin_user);
+ // Test behaviour described in http://drupal.org/node/652712#comment-2354918.
+
+ $this->drupalGet('admin/structure/views/view/frontpage/edit');
+
+
+ $edit = array(
+ 'pager_options[items_per_page]' => 20,
+ );
+ $this->drupalPost('admin/structure/views/nojs/display/frontpage/default/pager_options', $edit, t('Apply'));
+ $this->assertText('20 items');
+
+ // Change type and check whether the type is new type is stored.
+ $edit = array();
+ $edit = array(
+ 'pager[type]' => 'mini',
+ );
+ $this->drupalPost('admin/structure/views/nojs/display/frontpage/default/pager', $edit, t('Apply'));
+ $this->drupalGet('admin/structure/views/view/frontpage/edit');
+ $this->assertText('Mini', 'Changed pager plugin, should change some text');
+
+ // Test behaviour described in http://drupal.org/node/652712#comment-2354400
+ $view = $this->viewsStorePagerSettings();
+ // Make it editable in the admin interface.
+ $view->save();
+
+ $this->drupalGet('admin/structure/views/view/test_store_pager_settings/edit');
+
+ $edit = array();
+ $edit = array(
+ 'pager[type]' => 'full',
+ );
+ $this->drupalPost('admin/structure/views/nojs/display/test_store_pager_settings/default/pager', $edit, t('Apply'));
+ $this->drupalGet('admin/structure/views/view/test_store_pager_settings/edit');
+ $this->assertText('Full');
+
+ $edit = array(
+ 'pager_options[items_per_page]' => 20,
+ );
+ $this->drupalPost('admin/structure/views/nojs/display/test_store_pager_settings/default/pager_options', $edit, t('Apply'));
+ $this->assertText('20 items');
+
+ // add new display and test the settings again, by override it.
+ $edit = array( );
+ // Add a display and override the pager settings.
+ $this->drupalPost('admin/structure/views/view/test_store_pager_settings/edit', $edit, t('Add Page'));
+ $edit = array(
+ 'override[dropdown]' => 'page_1',
+ );
+ $this->drupalPost('admin/structure/views/nojs/display/test_store_pager_settings/page_1/pager', $edit, t('Apply'));
+
+ $edit = array(
+ 'pager[type]' => 'mini',
+ );
+ $this->drupalPost('admin/structure/views/nojs/display/test_store_pager_settings/page_1/pager', $edit, t('Apply'));
+ $this->drupalGet('admin/structure/views/view/test_store_pager_settings/edit');
+ $this->assertText('Mini', 'Changed pager plugin, should change some text');
+
+ $edit = array(
+ 'pager_options[items_per_page]' => 10,
+ );
+ $this->drupalPost('admin/structure/views/nojs/display/test_store_pager_settings/default/pager_options', $edit, t('Apply'));
+ $this->assertText('20 items');
+
+ }
+
+ public function viewsStorePagerSettings() {
+ $view = new view;
+ $view->name = 'test_store_pager_settings';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 3;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'none';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ return $view;
+ }
+
+ /**
+ * Tests the none-pager-query.
+ */
+ public function testNoLimit() {
+ // Create 11 nodes and make sure that everyone is returned.
+ // We create 11 nodes, because the default pager plugin had 10 items per page.
+ for ($i = 0; $i < 11; $i++) {
+ $this->drupalCreateNode();
+ }
+ $view = $this->viewsPagerNoLimit();
+ $view->set_display('default');
+ $this->executeView($view);
+ $this->assertEqual(count($view->result), 11, 'Make sure that every item is returned in the result');
+
+ $view->destroy();
+
+ // Setup and test a offset.
+ $view = $this->viewsPagerNoLimit();
+ $view->set_display('default');
+
+ $pager = array(
+ 'type' => 'none',
+ 'options' => array(
+ 'offset' => 3,
+ ),
+ );
+ $view->display_handler->set_option('pager', $pager);
+ $this->executeView($view);
+
+ $this->assertEqual(count($view->result), 8, 'Make sure that every item beside the first three is returned in the result');
+
+ // Check some public functions.
+ $this->assertFalse($view->query->pager->use_pager());
+ $this->assertFalse($view->query->pager->use_count_query());
+ $this->assertEqual($view->query->pager->get_items_per_page(), 0);
+ }
+
+ public function viewsPagerNoLimit() {
+ $view = new view;
+ $view->name = 'test_pager_none';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version =3;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'none';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ return $view;
+ }
+
+ public function testViewTotalRowsWithoutPager() {
+ $this->createNodes(23);
+
+ $view = $this->viewsPagerNoLimit();
+ $view->get_total_rows = TRUE;
+ $view->set_display('default');
+ $this->executeView($view);
+
+ $this->assertEqual($view->total_rows, 23, "'total_rows' is calculated when pager type is 'none' and 'get_total_rows' is TRUE.");
+ }
+
+ public function createNodes($count) {
+ if ($count >= 0) {
+ for ($i = 0; $i < $count; $i++) {
+ $this->drupalCreateNode();
+ }
+ }
+ }
+
+ /**
+ * Tests the some pager plugin.
+ */
+ public function testLimit() {
+ // Create 11 nodes and make sure that everyone is returned.
+ // We create 11 nodes, because the default pager plugin had 10 items per page.
+ for ($i = 0; $i < 11; $i++) {
+ $this->drupalCreateNode();
+ }
+ $view = $this->viewsPagerLimit();
+ $view->set_display('default');
+ $this->executeView($view);
+ $this->assertEqual(count($view->result), 5, 'Make sure that only a certain count of items is returned');
+ $view->destroy();
+
+ // Setup and test a offset.
+ $view = $this->viewsPagerLimit();
+ $view->set_display('default');
+
+ $pager = array(
+ 'type' => 'none',
+ 'options' => array(
+ 'offset' => 8,
+ 'items_per_page' => 5,
+ ),
+ );
+ $view->display_handler->set_option('pager', $pager);
+ $this->executeView($view);
+ $this->assertEqual(count($view->result), 3, 'Make sure that only a certain count of items is returned');
+
+ // Check some public functions.
+ $this->assertFalse($view->query->pager->use_pager());
+ $this->assertFalse($view->query->pager->use_count_query());
+ }
+
+ public function viewsPagerLimit() {
+ $view = new view;
+ $view->name = 'test_pager_some';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 3;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['pager']['options']['offset'] = 0;
+ $handler->display->display_options['pager']['options']['items_per_page'] = 5;
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ return $view;
+ }
+
+ /**
+ * Tests the normal pager.
+ */
+ public function testNormalPager() {
+ // Create 11 nodes and make sure that everyone is returned.
+ // We create 11 nodes, because the default pager plugin had 10 items per page.
+ for ($i = 0; $i < 11; $i++) {
+ $this->drupalCreateNode();
+ }
+ $view = $this->viewsPagerFull();
+ $view->set_display('default');
+ $this->executeView($view);
+ $this->assertEqual(count($view->result), 5, 'Make sure that only a certain count of items is returned');
+ $view->destroy();
+
+ // Setup and test a offset.
+ $view = $this->viewsPagerFull();
+ $view->set_display('default');
+
+ $pager = array(
+ 'type' => 'full',
+ 'options' => array(
+ 'offset' => 8,
+ 'items_per_page' => 5,
+ ),
+ );
+ $view->display_handler->set_option('pager', $pager);
+ $this->executeView($view);
+ $this->assertEqual(count($view->result), 3, 'Make sure that only a certain count of items is returned');
+
+ // Test items per page = 0
+ $view = $this->viewPagerFullZeroItemsPerPage();
+ $view->set_display('default');
+ $this->executeView($view);
+
+ $this->assertEqual(count($view->result), 11, 'All items are return');
+
+ // TODO test number of pages.
+
+ // Test items per page = 0.
+ $view->destroy();
+
+ // Setup and test a offset.
+ $view = $this->viewsPagerFull();
+ $view->set_display('default');
+
+ $pager = array(
+ 'type' => 'full',
+ 'options' => array(
+ 'offset' => 0,
+ 'items_per_page' => 0,
+ ),
+ );
+
+ $view->display_handler->set_option('pager', $pager);
+ $this->executeView($view);
+ $this->assertEqual($view->query->pager->get_items_per_page(), 0);
+ $this->assertEqual(count($view->result), 11);
+ }
+
+ function viewPagerFullZeroItemsPerPage() {
+ $view = new view;
+ $view->name = 'view_pager_full_zero_items_per_page';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 3;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '0';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['title']['link_to_node'] = 0;
+
+ return $view;
+ }
+
+ function viewsPagerFull() {
+ $view = new view;
+ $view->name = 'test_pager_full';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 3;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '5';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+
+ return $view;
+ }
+
+ function viewsPagerFullFields() {
+ $view = new view;
+ $view->name = 'test_pager_full';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 3;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '5';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['title']['link_to_node'] = 0;
+ return $view;
+ }
+
+ /**
+ * Tests the minipager.
+ */
+ public function testMiniPager() {
+ // the functionality is the same as the normal pager, so i don't know what to test here.
+ }
+
+ /**
+ * Tests rendering with NULL pager.
+ */
+ public function testRenderNullPager() {
+ // Create 11 nodes and make sure that everyone is returned.
+ // We create 11 nodes, because the default pager plugin had 10 items per page.
+ for ($i = 0; $i < 11; $i++) {
+ $this->drupalCreateNode();
+ }
+ $view = $this->viewsPagerFullFields();
+ $view->set_display('default');
+ $this->executeView($view);
+ $view->use_ajax = TRUE; // force the value again here
+ $view->query->pager = NULL;
+ $output = $view->render();
+ $this->assertEqual(preg_match('/<ul class="pager">/', $output), 0, t('The pager is not rendered.'));
+ }
+
+ /**
+ * Test the api functions on the view object.
+ */
+ function testPagerApi() {
+ $view = $this->viewsPagerFull();
+ // On the first round don't initialize the pager.
+
+ $this->assertEqual($view->get_items_per_page(), NULL, 'If the pager is not initialized and no manual override there is no items per page.');
+ $rand_number = rand(1, 5);
+ $view->set_items_per_page($rand_number);
+ $this->assertEqual($view->get_items_per_page(), $rand_number, 'Make sure get_items_per_page uses the settings of set_items_per_page.');
+
+ $this->assertEqual($view->get_offset(), NULL, 'If the pager is not initialized and no manual override there is no offset.');
+ $rand_number = rand(1, 5);
+ $view->set_offset($rand_number);
+ $this->assertEqual($view->get_offset(), $rand_number, 'Make sure get_offset uses the settings of set_offset.');
+
+ $this->assertEqual($view->get_current_page(), NULL, 'If the pager is not initialized and no manual override there is no current page.');
+ $rand_number = rand(1, 5);
+ $view->set_current_page($rand_number);
+ $this->assertEqual($view->get_current_page(), $rand_number, 'Make sure get_current_page uses the settings of set_current_page.');
+
+ $view->destroy();
+
+ // On this round enable the pager.
+ $view->init_display();
+ $view->init_query();
+ $view->init_pager();
+
+ $this->assertEqual($view->get_items_per_page(), 5, 'Per default the view has 5 items per page.');
+ $rand_number = rand(1, 5);
+ $view->set_items_per_page($rand_number);
+ $rand_number = rand(6, 11);
+ $view->query->pager->set_items_per_page($rand_number);
+ $this->assertEqual($view->get_items_per_page(), $rand_number, 'Make sure get_items_per_page uses the settings of set_items_per_page.');
+
+ $this->assertEqual($view->get_offset(), 0, 'Per default a view has a 0 offset.');
+ $rand_number = rand(1, 5);
+ $view->set_offset($rand_number);
+ $rand_number = rand(6, 11);
+ $view->query->pager->set_offset($rand_number);
+ $this->assertEqual($view->get_offset(), $rand_number, 'Make sure get_offset uses the settings of set_offset.');
+
+ $this->assertEqual($view->get_current_page(), 0, 'Per default the current page is 0.');
+ $rand_number = rand(1, 5);
+ $view->set_current_page($rand_number);
+ $rand_number = rand(6, 11);
+ $view->query->pager->set_current_page($rand_number);
+ $this->assertEqual($view->get_current_page(), $rand_number, 'Make sure get_current_page uses the settings of set_current_page.');
+
+ }
+}
diff --git a/sites/all/modules/views/tests/views_plugin_localization_test.inc b/sites/all/modules/views/tests/views_plugin_localization_test.inc
new file mode 100644
index 000000000..1987fd809
--- /dev/null
+++ b/sites/all/modules/views/tests/views_plugin_localization_test.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of views_plugin_localization_test.
+ */
+
+/**
+ * A stump localisation plugin which has static variables to cache the input.
+ */
+class views_plugin_localization_test extends views_plugin_localization {
+ /**
+ * Store the strings which was translated.
+ */
+ var $translated_strings;
+ /**
+ * Return the string and take sure that the test can find out whether the
+ * string got translated.
+ */
+ function translate_string($string, $keys = array(), $format = '') {
+ $this->translated_strings[] = $string;
+ return $string . "-translated";
+ }
+
+ /**
+ * Store the export strings.
+ */
+ function export($source) {
+ if (!empty($source['value'])) {
+ $this->export_strings[] = $source['value'];
+ }
+ }
+
+ /**
+ * Return the stored strings for the simpletest.
+ */
+ function get_export_strings() {
+ return $this->export_strings;
+ }
+}
diff --git a/sites/all/modules/views/tests/views_query.test b/sites/all/modules/views/tests/views_query.test
new file mode 100644
index 000000000..735df0ea4
--- /dev/null
+++ b/sites/all/modules/views/tests/views_query.test
@@ -0,0 +1,433 @@
+<?php
+
+/**
+ * @file
+ * Tests for Views query features.
+ */
+
+/**
+ * Abstract class for views testing.
+ */
+abstract class ViewsTestCase extends DrupalWebTestCase {
+ /**
+ * Helper function: verify a result set returned by view.
+ *
+ * The comparison is done on the string representation of the columns of the
+ * column map, taking the order of the rows into account, but not the order
+ * of the columns.
+ *
+ * @param $view
+ * An executed View.
+ * @param $expected_result
+ * An expected result set.
+ * @param $column_map
+ * An associative array mapping the columns of the result set from the view
+ * (as keys) and the expected result set (as values).
+ */
+ protected function assertIdenticalResultset($view, $expected_result, $column_map = array(), $message = 'Identical result set') {
+ return $this->assertIdenticalResultsetHelper($view, $expected_result, $column_map, $message, 'assertIdentical');
+ }
+
+ /**
+ * Helper function: verify a result set returned by view..
+ *
+ * Inverse of ViewsTestCase::assertIdenticalResultset().
+ *
+ * @param $view
+ * An executed View.
+ * @param $expected_result
+ * An expected result set.
+ * @param $column_map
+ * An associative array mapping the columns of the result set from the view
+ * (as keys) and the expected result set (as values).
+ */
+ protected function assertNotIdenticalResultset($view, $expected_result, $column_map = array(), $message = 'Identical result set') {
+ return $this->assertIdenticalResultsetHelper($view, $expected_result, $column_map, $message, 'assertNotIdentical');
+ }
+
+ protected function assertIdenticalResultsetHelper($view, $expected_result, $column_map, $message, $assert_method) {
+ // Convert $view->result to an array of arrays.
+ $result = array();
+ foreach ($view->result as $key => $value) {
+ $row = array();
+ foreach ($column_map as $view_column => $expected_column) {
+ // The comparison will be done on the string representation of the value.
+ $row[$expected_column] = (string) $value->$view_column;
+ }
+ $result[$key] = $row;
+ }
+
+ // Remove the columns we don't need from the expected result.
+ foreach ($expected_result as $key => $value) {
+ $row = array();
+ foreach ($column_map as $expected_column) {
+ // The comparison will be done on the string representation of the value.
+ $row[$expected_column] = (string) (is_object($value) ? $value->$expected_column : $value[$expected_column]);
+ }
+ $expected_result[$key] = $row;
+ }
+
+ // Reset the numbering of the arrays.
+ $result = array_values($result);
+ $expected_result = array_values($expected_result);
+
+ $this->verbose('<pre>Returned data set: ' . print_r($result, TRUE) . "\n\nExpected: ". print_r($expected_result, TRUE));
+
+ // Do the actual comparison.
+ return $this->$assert_method($result, $expected_result, $message);
+ }
+
+ /**
+ * Helper function: order an array of array based on a column.
+ */
+ protected function orderResultSet($result_set, $column, $reverse = FALSE) {
+ $this->sort_column = $column;
+ $this->sort_order = $reverse ? -1 : 1;
+ usort($result_set, array($this, 'helperCompareFunction'));
+ return $result_set;
+ }
+
+ protected $sort_column = NULL;
+ protected $sort_order = 1;
+
+ /**
+ * Helper comparison function for orderResultSet().
+ */
+ protected function helperCompareFunction($a, $b) {
+ $value1 = $a[$this->sort_column];
+ $value2 = $b[$this->sort_column];
+ if ($value1 == $value2) {
+ return 0;
+ }
+ return $this->sort_order * (($value1 < $value2) ? -1 : 1);
+ }
+
+ /**
+ * Helper function to check whether a button with a certain id exists and has a certain label.
+ */
+ protected function helperButtonHasLabel($id, $expected_label, $message = 'Label has the expected value: %label.') {
+ return $this->assertFieldById($id, $expected_label, t($message, array('%label' => $expected_label)));
+ }
+
+ /**
+ * Helper function to execute a view with debugging.
+ *
+ * @param view $view
+ * @param array $args
+ */
+ protected function executeView($view, $args = array()) {
+ $view->set_display();
+ $view->pre_execute($args);
+ $view->execute();
+ $this->verbose('<pre>Executed view: ' . ((string) $view->build_info['query']) . '</pre>');
+ }
+}
+
+abstract class ViewsSqlTest extends ViewsTestCase {
+
+ protected function setUp() {
+ parent::setUp('views', 'views_ui');
+
+ // Define the schema and views data variable before enabling the test module.
+ variable_set('views_test_schema', $this->schemaDefinition());
+ variable_set('views_test_views_data', $this->viewsData());
+ variable_set('views_test_views_plugins', $this->viewsPlugins());
+
+ module_enable(array('views_test'));
+ $this->resetAll();
+
+ // Load the test dataset.
+ $data_set = $this->dataSet();
+ $query = db_insert('views_test')
+ ->fields(array_keys($data_set[0]));
+ foreach ($data_set as $record) {
+ $query->values($record);
+ }
+ $query->execute();
+ $this->checkPermissions(array(), TRUE);
+ }
+
+ /**
+ * This function allows to enable views ui from a higher class which can't change the setup function anymore.
+ *
+ * @TODO
+ * Convert existing setUp functions.
+ */
+ function enableViewsUi() {
+ module_enable(array('views_ui'));
+ // @TODO Figure out why it's required to clear the cache here.
+ views_module_include('views_default', TRUE);
+ views_get_all_views(TRUE);
+ menu_rebuild();
+ }
+
+ /**
+ * The schema definition.
+ */
+ protected function schemaDefinition() {
+ $schema['views_test'] = array(
+ 'description' => 'Basic test table for Views tests.',
+ 'fields' => array(
+ 'id' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'name' => array(
+ 'description' => "A person's name",
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'age' => array(
+ 'description' => "The person's age",
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0),
+ 'job' => array(
+ 'description' => "The person's job",
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => 'Undefined',
+ ),
+ 'created' => array(
+ 'description' => "The creation date of this record",
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('id'),
+ 'unique keys' => array(
+ 'name' => array('name')
+ ),
+ 'indexes' => array(
+ 'ages' => array('age'),
+ ),
+ );
+ return $schema;
+ }
+
+ /**
+ * The views data definition.
+ */
+ protected function viewsData() {
+ // Declaration of the base table.
+ $data['views_test']['table'] = array(
+ 'group' => t('Views test'),
+ 'base' => array(
+ 'field' => 'id',
+ 'title' => t('Views test'),
+ 'help' => t('Users who have created accounts on your site.'),
+ ),
+ );
+
+ // Declaration of fields.
+ $data['views_test']['id'] = array(
+ 'title' => t('ID'),
+ 'help' => t('The test data ID'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['views_test']['name'] = array(
+ 'title' => t('Name'),
+ 'help' => t('The name of the person'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['views_test']['age'] = array(
+ 'title' => t('Age'),
+ 'help' => t('The age of the person'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_numeric',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['views_test']['job'] = array(
+ 'title' => t('Job'),
+ 'help' => t('The job of the person'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+ $data['views_test']['created'] = array(
+ 'title' => t('Created'),
+ 'help' => t('The creation date of this record'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ );
+ return $data;
+ }
+
+ protected function viewsPlugins() {
+ return array();
+ }
+
+ /**
+ * A very simple test dataset.
+ */
+ protected function dataSet() {
+ return array(
+ array(
+ 'name' => 'John',
+ 'age' => 25,
+ 'job' => 'Singer',
+ 'created' => gmmktime(0, 0, 0, 1, 1, 2000),
+ ),
+ array(
+ 'name' => 'George',
+ 'age' => 27,
+ 'job' => 'Singer',
+ 'created' => gmmktime(0, 0, 0, 1, 2, 2000),
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ 'job' => 'Drummer',
+ 'created' => gmmktime(6, 30, 30, 1, 1, 2000),
+ ),
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ 'job' => 'Songwriter',
+ 'created' => gmmktime(6, 0, 0, 1, 1, 2000),
+ ),
+ array(
+ 'name' => 'Meredith',
+ 'age' => 30,
+ 'job' => 'Speaker',
+ 'created' => gmmktime(6, 30, 10, 1, 1, 2000),
+ ),
+ );
+ }
+
+ /**
+ * Build and return a basic view of the views_test table.
+ *
+ * @return view
+ */
+ protected function getBasicView() {
+ views_include('view');
+
+ // Create the basic view.
+ $view = new view();
+ $view->name = 'test_view';
+ $view->add_display('default');
+ $view->base_table = 'views_test';
+
+ // Set up the fields we need.
+ $display = $view->new_display('default', 'Master', 'default');
+ $display->override_option('fields', array(
+ 'id' => array(
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Set up the sort order.
+ $display->override_option('sorts', array(
+ 'id' => array(
+ 'order' => 'ASC',
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Set up the pager.
+ $display->override_option('pager', array(
+ 'type' => 'none',
+ 'options' => array('offset' => 0),
+ ));
+
+ return $view;
+ }
+
+ /**
+ * Build and return a Page view of the views_test table.
+ *
+ * @return view
+ */
+ protected function getBasicPageView() {
+ views_include('view');
+ $view = $this->getBasicView();
+
+ // In order to test exposed filters, we have to disable
+ // the exposed forms cache.
+ drupal_static_reset('views_exposed_form_cache');
+
+ $display = $view->new_display('page', 'Page', 'page_1');
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/views_test.info b/sites/all/modules/views/tests/views_test.info
new file mode 100644
index 000000000..33c89d804
--- /dev/null
+++ b/sites/all/modules/views/tests/views_test.info
@@ -0,0 +1,13 @@
+name = Views Test
+description = Test module for Views.
+package = Views
+core = 7.x
+dependencies[] = views
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2015-11-06
+version = "7.x-3.13"
+core = "7.x"
+project = "views"
+datestamp = "1446804876"
+
diff --git a/sites/all/modules/views/tests/views_test.install b/sites/all/modules/views/tests/views_test.install
new file mode 100644
index 000000000..b0ccd8b6c
--- /dev/null
+++ b/sites/all/modules/views/tests/views_test.install
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Install, update, and uninstall functions for the Views Test module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function views_test_schema() {
+ return variable_get('views_test_schema', array());
+}
diff --git a/sites/all/modules/views/tests/views_test.module b/sites/all/modules/views/tests/views_test.module
new file mode 100644
index 000000000..be533e0ce
--- /dev/null
+++ b/sites/all/modules/views/tests/views_test.module
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Helper module for Views tests.
+ */
+
+/**
+ * Implements hook_permission().
+ */
+function views_test_permission() {
+ return array(
+ 'views_test test permission' => array(
+ 'title' => t('Test permission'),
+ 'description' => t('views_test test permission'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function views_test_views_api() {
+ return array(
+ 'api' => 3.0,
+ 'template path' => drupal_get_path('module', 'views') . '/test_templates',
+ );
+}
+
+/**
+ * Implements hook_views_data().
+ */
+function views_test_views_data() {
+ // Count how often this hook is called.
+ $count = variable_get('views_test_views_data_count', 0);
+ $count++;
+ variable_set('views_test_views_data_count', $count);
+ return variable_get('views_test_views_data', array());
+}
+
+/**
+ * Implements hook_views_plugins().
+ */
+function views_test_views_plugins() {
+ return variable_get('views_test_views_plugins', array());
+}
+
+function views_test_test_static_access_callback($access) {
+ return $access;
+}
+
+function views_test_test_dynamic_access_callback($access, $argument1, $argument2) {
+ return $access && $argument1 == variable_get('test_dynamic_access_argument1', NULL) && $argument2 == variable_get('test_dynamic_access_argument2', NULL);
+}
+
+/**
+ * Implements hook_views_pre_render().
+ */
+function views_test_views_pre_render(&$view) {
+ if ($view->name == 'test_cache_header_storage') {
+ drupal_add_js(drupal_get_path('module', 'views_test') . '/views_cache.test.js');
+ drupal_add_css(drupal_get_path('module', 'views_test') . '/views_cache.test.css');
+ $view->pre_render_called = TRUE;
+ }
+}
+
+/**
+ * Implements hook_preprocess_HOOK() for theme_views_view_mapping_test().
+ */
+function template_preprocess_views_view_mapping_test(&$variables) {
+ $variables['element'] = array();
+
+ foreach ($variables['rows'] as $delta => $row) {
+ $fields = array();
+ foreach ($variables['options']['mapping'] as $type => $field_names) {
+ if (!is_array($field_names)) {
+ $field_names = array($field_names);
+ }
+ foreach ($field_names as $field_name) {
+ if ($value = $variables['view']->style_plugin->get_field($delta, $field_name)) {
+ $fields[$type . '-' . $field_name] = $type . ':' . $value;
+ }
+ }
+ }
+
+ // If there are no fields in this row, skip to the next one.
+ if (empty($fields)) {
+ continue;
+ }
+
+ // Build a container for the row.
+ $variables['element'][$delta] = array(
+ '#type' => 'container',
+ '#attributes' => array(
+ 'class' => array(
+ 'views-row-mapping-test',
+ ),
+ ),
+ );
+
+ // Add each field to the row.
+ foreach ($fields as $key => $render) {
+ $variables['element'][$delta][$key] = array(
+ '#children' => $render,
+ '#type' => 'container',
+ '#attributes' => array(
+ 'class' => array(
+ $key,
+ ),
+ ),
+ );
+ }
+ }
+}
+
+/**
+ * Returns HTML for the Mapping Test style.
+ */
+function theme_views_view_mapping_test($variables) {
+ return drupal_render($variables['element']);
+}
diff --git a/sites/all/modules/views/tests/views_test.views_default.inc b/sites/all/modules/views/tests/views_test.views_default.inc
new file mode 100644
index 000000000..044870506
--- /dev/null
+++ b/sites/all/modules/views/tests/views_test.views_default.inc
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * @file
+ * Tests views.
+ */
+
+/**
+ * Implements hook_views_default_views().
+ */
+function views_test_views_default_views() {
+ $view = new view;
+ $view->name = 'test_views_groupby_save';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->version = 7;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'none';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+
+ $views[$view->name] = $view;
+
+ $view = new view;
+ $view->name = 'test_rename_reset_button';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->human_name = '';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['exposed_form']['options']['reset_button'] = TRUE;
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ $handler->display->display_options['row_options']['links'] = 1;
+ $handler->display->display_options['row_options']['comments'] = 0;
+ /* Filter criterion: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['label'] = 'Content: Type';
+ $handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+ $handler->display->display_options['filters']['type']['expose']['reduce'] = 0;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page_1');
+ $handler->display->display_options['path'] = 'test_rename_reset_button';
+
+ $views[$view->name] = $view;
+
+ $view = new view;
+ $view->name = 'test_exposed_admin_ui';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->version = 7;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['exposed_form']['options']['reset_button'] = TRUE;
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ $handler->display->display_options['row_options']['links'] = 1;
+ $handler->display->display_options['row_options']['comments'] = 0;
+ /* Sort criterion: Content: Post date */
+ $handler->display->display_options['sorts']['created']['id'] = 'created';
+ $handler->display->display_options['sorts']['created']['table'] = 'node';
+ $handler->display->display_options['sorts']['created']['field'] = 'created';
+ /* Filter: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['label'] = 'Content: Type';
+ $handler->display->display_options['filters']['type']['expose']['use_operator'] = TRUE;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page_1');
+ $handler->display->display_options['path'] = 'test_exposed_admin_ui';
+
+ $views[$view->name] = $view;
+
+ $view = new view;
+ $view->name = 'test_filter_in_operator_ui';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->version = 7;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Filter: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+ $handler->display->display_options['filters']['type']['expose']['label'] = 'Content: Type';
+ $handler->display->display_options['filters']['type']['expose']['use_operator'] = 0;
+ $handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+ $handler->display->display_options['filters']['type']['expose']['reduce'] = 0;
+
+ $views[$view->name] = $view;
+
+ $view = new view;
+ $view->name = 'test_argument_default_current_user';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'node';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 3;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['title']['link_to_node'] = 0;
+ /* Contextual filter: Content: Author uid */
+ $handler->display->display_options['arguments']['uid']['id'] = 'uid';
+ $handler->display->display_options['arguments']['uid']['table'] = 'node';
+ $handler->display->display_options['arguments']['uid']['field'] = 'uid';
+ $handler->display->display_options['arguments']['uid']['default_action'] = 'default';
+
+ $views[$view->name] = $view;
+
+ $view = new view();
+ $view->name = 'test_feed_http_headers_caching';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE;
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'time';
+ $handler->display->display_options['cache']['results_lifespan'] = '3600';
+ $handler->display->display_options['cache']['results_lifespan_custom'] = '0';
+ $handler->display->display_options['cache']['output_lifespan'] = '3600';
+ $handler->display->display_options['cache']['output_lifespan_custom'] = '0';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+
+ /* Display: Feed */
+ $handler = $view->new_display('feed', 'Feed', 'feed_1');
+ $handler->display->display_options['pager']['type'] = 'none';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['style_plugin'] = 'rss';
+ $handler->display->display_options['row_plugin'] = 'node_rss';
+ $handler->display->display_options['path'] = 'test_feed_http_headers_caching';
+
+ $views[$view->name] = $view;
+
+ return $views;
+}
diff --git a/sites/all/modules/views/tests/views_translatable.test b/sites/all/modules/views/tests/views_translatable.test
new file mode 100644
index 000000000..983a97e00
--- /dev/null
+++ b/sites/all/modules/views/tests/views_translatable.test
@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsTranslatableTest.
+ */
+
+/**
+ * Tests Views pluggable translations.
+ */
+class ViewsTranslatableTest extends ViewsSqlTest {
+ var $strings;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views Translatable Test',
+ 'description' => 'Tests the pluggable translations',
+ 'group' => 'Views',
+ );
+ }
+
+ /**
+ * The views plugin definition. Override it if you test provides a plugin.
+ */
+ public function viewsPlugins() {
+ return array(
+ 'localization' => array(
+ 'test' => array(
+ 'no ui' => TRUE,
+ 'title' => t('Test'),
+ 'help' => t('This is a test description.'),
+ 'handler' => 'views_plugin_localization_test',
+ 'parent' => 'parent',
+ 'path' => drupal_get_path('module', 'views') .'/tests',
+ ),
+ ),
+ );
+ }
+
+ public function setUp() {
+ parent::setUp();
+
+ variable_set('views_localization_plugin', 'test');
+ // Reset the plugin data.
+ views_fetch_plugin_data(NULL, NULL, TRUE);
+ $this->strings = array('Master1', 'Apply1', 'Sort By1', 'Asc1', 'Desc1', 'more1', 'Reset1', 'Offset1', 'Master1', 'title1', 'Items per page1', 'fieldlabel1', 'filterlabel1');
+ $this->enableViewsUi();
+ }
+
+ /**
+ * Tests the unpack translation funtionality.
+ */
+ public function testUnpackTranslatable() {
+ $view = $this->view_unpack_translatable();
+ $view->init_localization();
+
+ $this->assertEqual('views_plugin_localization_test', get_class($view->localization_plugin), 'Make sure that init_localization initializes the right translation plugin');
+
+ $view->export_locale_strings();
+
+ $expected_strings = $this->strings;
+ $result_strings = $view->localization_plugin->get_export_strings();
+ $this->assertEqual(sort($expected_strings), sort($result_strings), 'Make sure that the localization plugin got every translatable string.');
+ }
+
+ public function testUi() {
+ // Make sure that the string is not translated in the UI.
+ $view = $this->view_unpack_translatable();
+ $view->save();
+ views_invalidate_cache();
+
+ $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration'));
+ $this->drupalLogin($admin_user);
+
+ $this->drupalGet("admin/structure/views/view/$view->name/edit");
+ $this->assertNoText('-translated', 'Make sure that no strings get translated in the UI.');
+ }
+
+ /**
+ * Make sure that the translations get into the loaded view.
+ */
+ public function testTranslation() {
+ $view = $this->view_unpack_translatable();
+ $view->set_display('default');
+ $this->executeView($view);
+
+ $expected_strings = array();
+ foreach ($this->strings as $string) {
+ $expected_strings[] = $string .= '-translated';
+ }
+ $this->assertEqual(sort($expected_strings), sort($view->localization_plugin->translated_strings), 'Make sure that every string got loaded translated');
+ }
+
+ /**
+ * Make sure that the different things have the right translation keys.
+ */
+ public function testTranslationKey() {
+ $view = $this->view_unpack_translatable();
+ $view->editing = TRUE;
+ $view->init_display();
+
+ // Don't run translation. We just want to get the right keys.
+
+ foreach ($view->display as $display_id => $display) {
+ $translatables = array();
+ $display->handler->unpack_translatables($translatables);
+
+ $this->string_keys = array(
+ 'Master1' => array('title'),
+ 'Apply1' => array('exposed_form', 'submit_button'),
+ 'Sort By1' => array('exposed_form', 'exposed_sorts_label'),
+ 'Asc1' => array('exposed_form', 'sort_asc_label'),
+ 'Desc1' => array('exposed_form', 'sort_desc_label'),
+ 'more1' => array('use_more_text'),
+ 'Reset1' => array('exposed_form', 'reset_button_label'),
+ 'Offset1' => array('pager', 'expose', 'offset_label'),
+ 'title1' => array('title'),
+ 'Tag first1' => array('pager', 'tags', 'first'),
+ 'Tag prev1' => array('pager', 'tags', 'previous'),
+ 'Tag next1' => array('pager', 'tags', 'next'),
+ 'Tag last1' => array('pager', 'tags', 'last'),
+ 'Items per page1' => array('pager', 'expose', 'items_per_page_label'),
+ 'fieldlabel1' => array('field', 'node', 'nid', 'label'),
+ 'filterlabel1' => array('filter', 'node', 'nid', 'expose', 'label'),
+ '- All -' => array('pager', 'expose', 'items_per_page_options_all_label'),
+ 'Header1' => array('header', 'views', 'area', 'content'),
+ );
+
+ $formats = array(
+ 'Header1' => 'filtered_html',
+ );
+
+ foreach ($translatables as $translatable) {
+ $this->assertEqual($translatable['keys'], $this->string_keys[$translatable['value']]);
+
+ // Make sure the format is correct.
+ if (isset($formats[$translatable['value']])) {
+ $this->assertEqual($translatable['format'], $formats[$translatable['value']]);
+ }
+ else {
+ $this->assertNull($translatable['format'], 'No format defined');
+ }
+ }
+ }
+ }
+
+ public function view_unpack_translatable() {
+ $view = new view;
+ $view->name = 'view_unpack_translatable';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->api_version = '3.0-alpha1';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master1', 'default');
+ $handler->display->display_options['title'] = 'title1';
+ $handler->display->display_options['use_more_text'] = 'more1';
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['exposed_form']['options']['submit_button'] = 'Apply1';
+ $handler->display->display_options['exposed_form']['options']['reset_button'] = TRUE;
+ $handler->display->display_options['exposed_form']['options']['reset_button_label'] = 'Reset1';
+ $handler->display->display_options['exposed_form']['options']['exposed_sorts_label'] = 'Sort By1';
+ $handler->display->display_options['exposed_form']['options']['sort_asc_label'] = 'Asc1';
+ $handler->display->display_options['exposed_form']['options']['sort_desc_label'] = 'Desc1';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['pager']['options']['quantity'] = '9';
+ $handler->display->display_options['pager']['options']['tags']['first'] = 'Tag first1';
+ $handler->display->display_options['pager']['options']['tags']['previous'] = 'Tag prev1';
+ $handler->display->display_options['pager']['options']['tags']['next'] = 'Tag next1';
+ $handler->display->display_options['pager']['options']['tags']['last'] = 'Tag last1';
+ $handler->display->display_options['pager']['options']['expose']['items_per_page'] = TRUE;
+ $handler->display->display_options['pager']['options']['expose']['items_per_page_label'] = 'Items per page1';
+ $handler->display->display_options['pager']['options']['expose']['offset'] = TRUE;
+ $handler->display->display_options['pager']['options']['expose']['offset_label'] = 'Offset1';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Global: Header */
+ $handler->display->display_options['header']['area']['id'] = 'area';
+ $handler->display->display_options['header']['area']['table'] = 'views';
+ $handler->display->display_options['header']['area']['field'] = 'area';
+ $handler->display->display_options['header']['area']['empty'] = FALSE;
+ $handler->display->display_options['header']['area']['content'] = 'Header1';
+ $handler->display->display_options['header']['area']['format'] = 'filtered_html';
+ $handler->display->display_options['header']['area']['tokenize'] = 0;
+ /* Field: Content: Nid */
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'nid';
+ $handler->display->display_options['fields']['nid']['label'] = 'fieldlabel1';
+ $handler->display->display_options['fields']['nid']['alter']['alter_text'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['make_link'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['trim'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['word_boundary'] = 1;
+ $handler->display->display_options['fields']['nid']['alter']['ellipsis'] = 1;
+ $handler->display->display_options['fields']['nid']['alter']['strip_tags'] = 0;
+ $handler->display->display_options['fields']['nid']['alter']['html'] = 0;
+ $handler->display->display_options['fields']['nid']['hide_empty'] = 0;
+ $handler->display->display_options['fields']['nid']['empty_zero'] = 0;
+ $handler->display->display_options['fields']['nid']['link_to_node'] = 0;
+ /* Filter: Content: Nid */
+ $handler->display->display_options['filters']['nid']['id'] = 'nid';
+ $handler->display->display_options['filters']['nid']['table'] = 'node';
+ $handler->display->display_options['filters']['nid']['field'] = 'nid';
+ $handler->display->display_options['filters']['nid']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['nid']['expose']['operator_id'] = 'nid_op';
+ $handler->display->display_options['filters']['nid']['expose']['label'] = 'filterlabel1';
+ $handler->display->display_options['filters']['nid']['expose']['identifier'] = 'nid';
+ $handler->display->display_options['filters']['nid']['expose']['multiple'] = 1;
+ $handler->display->display_options['filters']['nid']['expose']['reduce'] = 0;
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/tests/views_ui.test b/sites/all/modules/views/tests/views_ui.test
new file mode 100644
index 000000000..8785539be
--- /dev/null
+++ b/sites/all/modules/views/tests/views_ui.test
@@ -0,0 +1,973 @@
+<?php
+
+/**
+ * @file
+ * Tests Views UI Wizard.
+ */
+
+/**
+ * Views UI wizard tests.
+ */
+class ViewsUIWizardHelper extends DrupalWebTestCase {
+ function setUp() {
+ // Enable views_ui.
+ parent::setUp('views_ui');
+
+ // Create and log in a user with administer views permission.
+ $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view revisions'));
+ $this->drupalLogin($views_admin);
+ }
+}
+
+/**
+ * Tests creating views with the wizard and viewing them on the listing page.
+ */
+class ViewsUIWizardBasicTestCase extends ViewsUIWizardHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views UI wizard basic functionality',
+ 'description' => 'Test creating basic views with the wizard and viewing them on the listing page.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ function testViewsWizardAndListing() {
+ // Check if we can access the main views admin page.
+ $this->drupalGet('admin/structure/views');
+ $this->assertText(t('Add new view'));
+
+ // Create a simple and not at all useful view.
+ $view1 = array();
+ $view1['human_name'] = $this->randomName(16);
+ $view1['name'] = strtolower($this->randomName(16));
+ $view1['description'] = $this->randomName(16);
+ $view1['page[create]'] = FALSE;
+ $this->drupalPost('admin/structure/views/add', $view1, t('Save & exit'));
+ $this->assertText(t('Your view was saved. You may edit it from the list below.'));
+ $this->assertText($view1['human_name']);
+ $this->assertText($view1['description']);
+ foreach(array('delete', 'clone', 'edit') as $operation) {
+ $this->assertLinkByHref(url('admin/structure/views/view/' . $view1['name'] . '/' . $operation));
+ }
+
+ // This view should not have a block.
+ $this->drupalGet('admin/structure/block');
+ $this->assertNoText('View: ' . $view1['human_name']);
+
+ // Create two nodes.
+ $node1 = $this->drupalCreateNode(array('type' => 'page'));
+ $node2 = $this->drupalCreateNode(array('type' => 'article'));
+
+ // Now create a page with simple node listing and an attached feed.
+ $view2 = array();
+ $view2['human_name'] = $this->randomName(16);
+ $view2['name'] = strtolower($this->randomName(16));
+ $view2['description'] = $this->randomName(16);
+ $view2['page[create]'] = 1;
+ $view2['page[title]'] = $this->randomName(16);
+ $view2['page[path]'] = $this->randomName(16);
+ $view2['page[feed]'] = 1;
+ $view2['page[feed_properties][path]'] = $this->randomName(16);
+ $this->drupalPost('admin/structure/views/add', $view2, t('Save & exit'));
+
+ // Since the view has a page, we expect to be automatically redirected to
+ // it.
+ $this->assertUrl($view2['page[path]']);
+ $this->assertText($view2['page[title]']);
+ $this->assertText($node1->title);
+ $this->assertText($node2->title);
+
+ // Check if we have the feed.
+ $this->assertLinkByHref(url($view2['page[feed_properties][path]']));
+ $this->drupalGet($view2['page[feed_properties][path]']);
+ $this->assertRaw('<rss version="2.0"');
+ // The feed should have the same title and nodes as the page.
+ $this->assertText($view2['page[title]']);
+ $this->assertRaw(url('node/' . $node1->nid, array('absolute' => TRUE)));
+ $this->assertText($node1->title);
+ $this->assertRaw(url('node/' . $node2->nid, array('absolute' => TRUE)));
+ $this->assertText($node2->title);
+
+ // Go back to the views page and check if this view is there.
+ $this->drupalGet('admin/structure/views');
+ $this->assertText($view2['human_name']);
+ $this->assertText($view2['description']);
+ $this->assertLinkByHref(url($view2['page[path]']));
+
+ // This view should not have a block.
+ $this->drupalGet('admin/structure/block');
+ $this->assertNoText('View: ' . $view2['human_name']);
+
+ // Create a view with a page and a block, and filter the listing.
+ $view3 = array();
+ $view3['human_name'] = $this->randomName(16);
+ $view3['name'] = strtolower($this->randomName(16));
+ $view3['description'] = $this->randomName(16);
+ $view3['show[wizard_key]'] = 'node';
+ $view3['show[type]'] = 'page';
+ $view3['page[create]'] = 1;
+ $view3['page[title]'] = $this->randomName(16);
+ $view3['page[path]'] = $this->randomName(16);
+ $view3['block[create]'] = 1;
+ $view3['block[title]'] = $this->randomName(16);
+ $this->drupalPost('admin/structure/views/add', $view3, t('Save & exit'));
+
+ // Make sure the view only displays the node we expect.
+ $this->assertUrl($view3['page[path]']);
+ $this->assertText($view3['page[title]']);
+ $this->assertText($node1->title);
+ $this->assertNoText($node2->title);
+
+ // Go back to the views page and check if this view is there.
+ $this->drupalGet('admin/structure/views');
+ $this->assertText($view3['human_name']);
+ $this->assertText($view3['description']);
+ $this->assertLinkByHref(url($view3['page[path]']));
+
+ // Put the block into the first sidebar region.
+ $this->drupalGet('admin/structure/block');
+ $this->assertText('View: ' . $view3['human_name']);
+ $edit = array();
+ $edit["blocks[views_{$view3['name']}-block][region]"] = 'sidebar_first';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+ // Visit a random page (not the one that displays the view itself) and look
+ // for the expected node title in the block.
+ $this->drupalGet('user');
+ $this->assertText($node1->title);
+ $this->assertNoText($node2->title);
+
+ // Check if the export screen works.
+ $this->drupalGet('admin/structure/views/view/' . $view3['name'] . '/export');
+ $this->assertRaw('$view = new view();');
+ $this->assertRaw($view3['human_name']);
+ $this->assertRaw($view3['description']);
+
+ // Make sure the listing page doesn't show disabled default views.
+ $this->assertNoText('tracker', t('Default tracker view does not show on the listing page.'));
+ }
+}
+
+/**
+ * Tests enabling, disabling, and reverting default views via the listing page.
+ */
+class ViewsUIWizardDefaultViewsTestCase extends ViewsUIWizardHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views UI default views functionality',
+ 'description' => 'Test enabling, disabling, and reverting default views via the listing page.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ /**
+ * Tests default views.
+ */
+ function testDefaultViews() {
+ // Make sure the front page view starts off as disabled (does not appear on
+ // the listing page).
+ $edit_href = 'admin/structure/views/view/frontpage/edit';
+ $this->drupalGet('admin/structure/views');
+ // TODO: Disabled default views do now appear on the front page. Test this
+ // behavior with templates instead.
+ // $this->assertNoLinkByHref($edit_href);
+
+ // Enable the front page view, and make sure it is now visible on the main
+ // listing page.
+ $this->drupalGet('admin/structure/views/templates');
+ $this->clickViewsOperationLink(t('Enable'), '/frontpage/');
+ $this->assertUrl('admin/structure/views');
+ $this->assertLinkByHref($edit_href);
+
+ // It should not be possible to revert the view yet.
+ $this->assertNoLink(t('Revert'));
+ $revert_href = 'admin/structure/views/view/frontpage/revert';
+ $this->assertNoLinkByHref($revert_href);
+
+ // Edit the view and change the title. Make sure that the new title is
+ // displayed.
+ $new_title = $this->randomName(16);
+ $edit = array('title' => $new_title);
+ $this->drupalPost('admin/structure/views/nojs/display/frontpage/page/title', $edit, t('Apply'));
+ $this->drupalPost('admin/structure/views/view/frontpage/edit/page', array(), t('Save'));
+ $this->drupalGet('frontpage');
+ $this->assertText($new_title);
+
+ // It should now be possible to revert the view. Do that, and make sure the
+ // view title we added above no longer is displayed.
+ $this->drupalGet('admin/structure/views');
+ $this->assertLink(t('Revert'));
+ $this->assertLinkByHref($revert_href);
+ $this->drupalPost($revert_href, array(), t('Revert'));
+ $this->drupalGet('frontpage');
+ $this->assertNoText($new_title);
+
+ // Now disable the view, and make sure it stops appearing on the main view
+ // listing page but instead goes back to displaying on the disabled views
+ // listing page.
+ // TODO: Test this behavior with templates instead.
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Disable'), '/frontpage/');
+ // $this->assertUrl('admin/structure/views');
+ // $this->assertNoLinkByHref($edit_href);
+ // The easiest way to verify it appears on the disabled views listing page
+ // is to try to click the "enable" link from there again.
+ $this->drupalGet('admin/structure/views/templates');
+ $this->clickViewsOperationLink(t('Enable'), '/frontpage/');
+ $this->assertUrl('admin/structure/views');
+ $this->assertLinkByHref($edit_href);
+ }
+
+ /**
+ * Click a link to perform an operation on a view.
+ *
+ * In general, we expect lots of links titled "enable" or "disable" on the
+ * various views listing pages, and they might have tokens in them. So we
+ * need special code to find the correct one to click.
+ *
+ * @param $label
+ * Text between the anchor tags of the desired link.
+ * @param $unique_href_part
+ * A unique string that is expected to occur within the href of the desired
+ * link. For example, if the link URL is expected to look like
+ * "admin/structure/views/view/frontpage/...", then "/frontpage/" could be
+ * passed as the expected unique string.
+ *
+ * @return
+ * The page content that results from clicking on the link, or FALSE on
+ * failure. Failure also results in a failed assertion.
+ */
+ function clickViewsOperationLink($label, $unique_href_part) {
+ $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
+ foreach ($links as $link_index => $link) {
+ $position = strpos($link['href'], $unique_href_part);
+ if ($position !== FALSE) {
+ $index = $link_index;
+ break;
+ }
+ }
+ $this->assertTrue(isset($index), t('Link to "@label" containing @part found.', array('@label' => $label, '@part' => $unique_href_part)));
+ if (isset($index)) {
+ return $this->clickLink($label, $index);
+ }
+ else {
+ return FALSE;
+ }
+ }
+}
+
+/**
+ * Tests the ability of the views wizard to create views filtered by taxonomy.
+ */
+class ViewsUIWizardTaggedWithTestCase extends ViewsUIWizardHelper {
+ protected $node_type_with_tags;
+ protected $node_type_without_tags;
+ protected $tag_vocabulary;
+ protected $tag_field;
+ protected $tag_instance;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views UI wizard taxonomy functionality',
+ 'description' => 'Test the ability of the views wizard to create views filtered by taxonomy.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create two content types. One will have an autocomplete tagging field,
+ // and one won't.
+ $this->node_type_with_tags = $this->drupalCreateContentType();
+ $this->node_type_without_tags = $this->drupalCreateContentType();
+
+ // Create the vocabulary for the tag field.
+ $this->tag_vocabulary = new stdClass();
+ $this->tag_vocabulary->name = 'Views testing tags';
+ $this->tag_vocabulary->machine_name = 'views_testing_tags';
+ taxonomy_vocabulary_save($this->tag_vocabulary);
+
+ // Create the tag field itself.
+ $this->tag_field = array(
+ 'field_name' => 'field_views_testing_tags',
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->tag_vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->tag_field);
+
+ // Create an instance of the tag field on one of the content types, and
+ // configure it to display an autocomplete widget.
+ $this->tag_instance = array(
+ 'field_name' => 'field_views_testing_tags',
+ 'entity_type' => 'node',
+ 'bundle' => $this->node_type_with_tags->type,
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ 'teaser' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ ),
+ );
+ field_create_instance($this->tag_instance);
+ }
+
+ /**
+ * Tests the "tagged with" functionality.
+ */
+ function testTaggedWith() {
+ // In this test we will only create nodes that have an instance of the tag
+ // field.
+ $node_add_path = 'node/add/' . $this->node_type_with_tags->type;
+
+ // Create three nodes, with different tags.
+ $tag_field = $this->tag_field['field_name'] . '[' . LANGUAGE_NONE . ']';
+ $edit = array();
+ $edit['title'] = $node_tag1_title = $this->randomName();
+ $edit[$tag_field] = 'tag1';
+ $this->drupalPost($node_add_path, $edit, t('Save'));
+ $edit = array();
+ $edit['title'] = $node_tag1_tag2_title = $this->randomName();
+ $edit[$tag_field] = 'tag1, tag2';
+ $this->drupalPost($node_add_path, $edit, t('Save'));
+ $edit = array();
+ $edit['title'] = $node_no_tags_title = $this->randomName();
+ $this->drupalPost($node_add_path, $edit, t('Save'));
+
+ // Create a view that filters by taxonomy term "tag1". It should show only
+ // the two nodes from above that are tagged with "tag1".
+ $view1 = array();
+ // First select the node type and update the form so the correct tag field
+ // is used.
+ $view1['show[type]'] = $this->node_type_with_tags->type;
+ $this->drupalPost('admin/structure/views/add', $view1, t('Update "of type" choice'));
+ // Now resubmit the entire form to the same URL.
+ $view1['human_name'] = $this->randomName(16);
+ $view1['name'] = strtolower($this->randomName(16));
+ $view1['description'] = $this->randomName(16);
+ $view1['show[tagged_with]'] = 'tag1';
+ $view1['page[create]'] = 1;
+ $view1['page[title]'] = $this->randomName(16);
+ $view1['page[path]'] = $this->randomName(16);
+ $this->drupalPost(NULL, $view1, t('Save & exit'));
+ // Visit the page and check that the nodes we expect are present and the
+ // ones we don't expect are absent.
+ $this->drupalGet($view1['page[path]']);
+ $this->assertText($node_tag1_title);
+ $this->assertText($node_tag1_tag2_title);
+ $this->assertNoText($node_no_tags_title);
+
+ // Create a view that filters by taxonomy term "tag2". It should show only
+ // the one node from above that is tagged with "tag2".
+ $view2 = array();
+ $view2['show[type]'] = $this->node_type_with_tags->type;
+ $this->drupalPost('admin/structure/views/add', $view2, t('Update "of type" choice'));
+ $view2['human_name'] = $this->randomName(16);
+ $view2['name'] = strtolower($this->randomName(16));
+ $view2['description'] = $this->randomName(16);
+ $view2['show[tagged_with]'] = 'tag2';
+ $view2['page[create]'] = 1;
+ $view2['page[title]'] = $this->randomName(16);
+ $view2['page[path]'] = $this->randomName(16);
+ $this->drupalPost(NULL, $view2, t('Save & exit'));
+ $this->drupalGet($view2['page[path]']);
+ $this->assertNoText($node_tag1_title);
+ $this->assertText($node_tag1_tag2_title);
+ $this->assertNoText($node_no_tags_title);
+ }
+
+ /**
+ * Tests that the "tagged with" form element only shows for node types that support it.
+ */
+ function testTaggedWithByNodeType() {
+ // The tagging field is associated with one of our node types only. So the
+ // "tagged with" form element on the view wizard should appear on the form
+ // by default (when the wizard is configured to display all content) and
+ // also when the node type that has the tagging field is selected, but not
+ // when the node type that doesn't have the tagging field is selected.
+ $tags_xpath = '//input[@name="show[tagged_with]"]';
+ $this->drupalGet('admin/structure/views/add');
+ $this->assertFieldByXpath($tags_xpath);
+ $view['show[type]'] = $this->node_type_with_tags->type;
+ $this->drupalPost('admin/structure/views/add', $view, t('Update "of type" choice'));
+ $this->assertFieldByXpath($tags_xpath);
+ $view['show[type]'] = $this->node_type_without_tags->type;
+ $this->drupalPost(NULL, $view, t('Update "of type" choice'));
+ $this->assertNoFieldByXpath($tags_xpath);
+
+ // If we add an instance of the tagging field to the second node type, the
+ // "tagged with" form element should not appear for it too.
+ $instance = $this->tag_instance;
+ $instance['bundle'] = $this->node_type_without_tags->type;
+ field_create_instance($instance);
+ $view['show[type]'] = $this->node_type_with_tags->type;
+ $this->drupalPost('admin/structure/views/add', $view, t('Update "of type" choice'));
+ $this->assertFieldByXpath($tags_xpath);
+ $view['show[type]'] = $this->node_type_without_tags->type;
+ $this->drupalPost(NULL, $view, t('Update "of type" choice'));
+ $this->assertFieldByXpath($tags_xpath);
+ }
+}
+
+/**
+ * Tests the ability of the views wizard to create views with sorts.
+ */
+class ViewsUIWizardSortingTestCase extends ViewsUIWizardHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views UI wizard sorting functionality',
+ 'description' => 'Test the ability of the views wizard to create views with sorts.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ /**
+ * Tests the sorting functionality.
+ */
+ function testSorting() {
+ // Create nodes, each with a different creation time so that we can do a
+ // meaningful sort.
+ $node1 = $this->drupalCreateNode(array('created' => REQUEST_TIME));
+ $node2 = $this->drupalCreateNode(array('created' => REQUEST_TIME + 1));
+ $node3 = $this->drupalCreateNode(array('created' => REQUEST_TIME + 2));
+
+ // Create a view that sorts oldest first.
+ $view1 = array();
+ $view1['human_name'] = $this->randomName(16);
+ $view1['name'] = strtolower($this->randomName(16));
+ $view1['description'] = $this->randomName(16);
+ $view1['show[sort]'] = 'created:ASC';
+ $view1['page[create]'] = 1;
+ $view1['page[title]'] = $this->randomName(16);
+ $view1['page[path]'] = $this->randomName(16);
+ $this->drupalPost('admin/structure/views/add', $view1, t('Save & exit'));
+
+ // Make sure the view shows the nodes in the expected order.
+ $this->assertUrl($view1['page[path]']);
+ $this->assertText($view1['page[title]']);
+ $content = $this->drupalGetContent();
+ $this->assertText($node1->title);
+ $this->assertText($node2->title);
+ $this->assertText($node3->title);
+ $pos1 = strpos($content, $node1->title);
+ $pos2 = strpos($content, $node2->title);
+ $pos3 = strpos($content, $node3->title);
+ $this->assertTrue($pos1 < $pos2 && $pos2 < $pos3, t('The nodes appear in the expected order in a view that sorts by oldest first.'));
+
+ // Create a view that sorts newest first.
+ $view2 = array();
+ $view2['human_name'] = $this->randomName(16);
+ $view2['name'] = strtolower($this->randomName(16));
+ $view2['description'] = $this->randomName(16);
+ $view2['show[sort]'] = 'created:DESC';
+ $view2['page[create]'] = 1;
+ $view2['page[title]'] = $this->randomName(16);
+ $view2['page[path]'] = $this->randomName(16);
+ $this->drupalPost('admin/structure/views/add', $view2, t('Save & exit'));
+
+ // Make sure the view shows the nodes in the expected order.
+ $this->assertUrl($view2['page[path]']);
+ $this->assertText($view2['page[title]']);
+ $content = $this->drupalGetContent();
+ $this->assertText($node3->title);
+ $this->assertText($node2->title);
+ $this->assertText($node1->title);
+ $pos3 = strpos($content, $node3->title);
+ $pos2 = strpos($content, $node2->title);
+ $pos1 = strpos($content, $node1->title);
+ $this->assertTrue($pos3 < $pos2 && $pos2 < $pos1, t('The nodes appear in the expected order in a view that sorts by newest first.'));
+ }
+}
+
+/**
+ * Tests the ability of the views wizard to specify the number of items per page.
+ */
+class ViewsUIWizardItemsPerPageTestCase extends ViewsUIWizardHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views UI wizard items per page functionality',
+ 'description' => 'Test the ability of the views wizard to specify the number of items per page.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ /**
+ * Tests the number of items per page.
+ */
+ function testItemsPerPage() {
+ // Create articles, each with a different creation time so that we can do a
+ // meaningful sort.
+ $node1 = $this->drupalCreateNode(array('type' => 'article', 'created' => REQUEST_TIME));
+ $node2 = $this->drupalCreateNode(array('type' => 'article', 'created' => REQUEST_TIME + 1));
+ $node3 = $this->drupalCreateNode(array('type' => 'article', 'created' => REQUEST_TIME + 2));
+ $node4 = $this->drupalCreateNode(array('type' => 'article', 'created' => REQUEST_TIME + 3));
+ $node5 = $this->drupalCreateNode(array('type' => 'article', 'created' => REQUEST_TIME + 4));
+
+ // Create a page. This should never appear in the view created below.
+ $page_node = $this->drupalCreateNode(array('type' => 'page', 'created' => REQUEST_TIME + 2));
+
+ // Create a view that sorts newest first, and shows 4 items in the page and
+ // 3 in the block.
+ $view = array();
+ $view['human_name'] = $this->randomName(16);
+ $view['name'] = strtolower($this->randomName(16));
+ $view['description'] = $this->randomName(16);
+ $view['show[wizard_key]'] = 'node';
+ $view['show[type]'] = 'article';
+ $view['show[sort]'] = 'created:DESC';
+ $view['page[create]'] = 1;
+ $view['page[title]'] = $this->randomName(16);
+ $view['page[path]'] = $this->randomName(16);
+ $view['page[items_per_page]'] = 4;
+ $view['block[create]'] = 1;
+ $view['block[title]'] = $this->randomName(16);
+ $view['block[items_per_page]'] = 3;
+ $this->drupalPost('admin/structure/views/add', $view, t('Save & exit'));
+
+ // Make sure the page display shows the nodes we expect, and that they
+ // appear in the expected order.
+ $this->assertUrl($view['page[path]']);
+ $this->assertText($view['page[title]']);
+ $content = $this->drupalGetContent();
+ $this->assertText($node5->title);
+ $this->assertText($node4->title);
+ $this->assertText($node3->title);
+ $this->assertText($node2->title);
+ $this->assertNoText($node1->title);
+ $this->assertNoText($page_node->title);
+ $pos5 = strpos($content, $node5->title);
+ $pos4 = strpos($content, $node4->title);
+ $pos3 = strpos($content, $node3->title);
+ $pos2 = strpos($content, $node2->title);
+ $this->assertTrue($pos5 < $pos4 && $pos4 < $pos3 && $pos3 < $pos2, t('The nodes appear in the expected order in the page display.'));
+
+ // Put the block into the first sidebar region, visit a page that displays
+ // the block, and check that the nodes we expect appear in the correct
+ // order.
+ $this->drupalGet('admin/structure/block');
+ $this->assertText('View: ' . $view['human_name']);
+ $edit = array();
+ $edit["blocks[views_{$view['name']}-block][region]"] = 'sidebar_first';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $this->drupalGet('user');
+ $content = $this->drupalGetContent();
+ $this->assertText($node5->title);
+ $this->assertText($node4->title);
+ $this->assertText($node3->title);
+ $this->assertNoText($node2->title);
+ $this->assertNoText($node1->title);
+ $this->assertNoText($page_node->title);
+ $pos5 = strpos($content, $node5->title);
+ $pos4 = strpos($content, $node4->title);
+ $pos3 = strpos($content, $node3->title);
+ $this->assertTrue($pos5 < $pos4 && $pos4 < $pos3, t('The nodes appear in the expected order in the block display.'));
+ }
+}
+
+/**
+ * Tests the ability of the views wizard to put views in a menu.
+ */
+class ViewsUIWizardMenuTestCase extends ViewsUIWizardHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views UI wizard menu functionality',
+ 'description' => 'Test the ability of the views wizard to put views in a menu.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ /**
+ * Tests the menu functionality.
+ */
+ function testMenus() {
+ // Create a view with a page display and a menu link in the Main Menu.
+ $view = array();
+ $view['human_name'] = $this->randomName(16);
+ $view['name'] = strtolower($this->randomName(16));
+ $view['description'] = $this->randomName(16);
+ $view['page[create]'] = 1;
+ $view['page[title]'] = $this->randomName(16);
+ $view['page[path]'] = $this->randomName(16);
+ $view['page[link]'] = 1;
+ $view['page[link_properties][menu_name]'] = 'main-menu';
+ $view['page[link_properties][title]'] = $this->randomName(16);
+ $this->drupalPost('admin/structure/views/add', $view, t('Save & exit'));
+
+ // Make sure there is a link to the view from the front page (where we
+ // expect the main menu to display).
+ $this->drupalGet('');
+ $this->assertLink($view['page[link_properties][title]']);
+ $this->assertLinkByHref(url($view['page[path]']));
+
+ // Make sure the link is associated with the main menu.
+ $links = menu_load_links('main-menu');
+ $found = FALSE;
+ foreach ($links as $link) {
+ if ($link['link_path'] == $view['page[path]']) {
+ $found = TRUE;
+ break;
+ }
+ }
+ $this->assertTrue($found, t('Found a link to %path in the main menu', array('%path' => $view['page[path]'])));
+ }
+}
+
+/**
+ * Tests the ability of the views wizard to create views with a jump menu style plugin.
+ */
+class ViewsUIWizardJumpMenuTestCase extends ViewsUIWizardHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views UI wizard jump menu functionality',
+ 'description' => 'Test the ability of the views wizard to create views with a jump menu style plugin.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ /**
+ * Tests the jump menu style plugin.
+ */
+ function testJumpMenus() {
+ // We'll run this test for several different base tables that appear in the
+ // wizard.
+ $base_table_methods = array(
+ 'node' => 'createNodeAndGetPath',
+ 'users' => 'createUserAndGetPath',
+ 'comment' => 'createCommentAndGetPath',
+ 'taxonomy_term' => 'createTaxonomyTermAndGetPath',
+ 'file_managed' => 'createFileAndGetPath',
+ 'node_revision' => 'createNodeRevisionAndGetPath',
+ );
+
+ foreach ($base_table_methods as $base_table => $method) {
+ // For each base table, find the path that we expect the jump menu to
+ // redirect us to.
+ $path_info = $this->{$method}();
+ if (is_array($path_info)) {
+ $path = $path_info['path'];
+ $options = isset($path_info['options']) ? $path_info['options'] : array();
+ }
+ else {
+ $path = $path_info;
+ $options = array();
+ }
+
+ // Create a page view for the specified base table that uses the jump
+ // menu style plugin.
+ $view = array();
+ $view['human_name'] = $this->randomName(16);
+ $view['name'] = strtolower($this->randomName(16));
+ $view['description'] = $this->randomName(16);
+ $view['show[wizard_key]'] = $base_table;
+ $view['page[create]'] = 1;
+ $view['page[title]'] = $this->randomName(16);
+ $view['page[path]'] = $this->randomName(16);
+ $view['page[style][style_plugin]'] = 'jump_menu';
+ $view['page[style][row_plugin]'] = 'fields';
+ $this->drupalPost('admin/structure/views/add', $view, t('Save & exit'));
+
+ // Submit the jump menu form, and check that we are redirected to the
+ // expected URL.
+
+ $edit = array();
+ $edit['jump'] = url($path, $options);
+
+ // The urls are built with :: to be able to have a unique path all the time,
+ // so try to find out the real path of $edit.
+ $view_object = views_get_view($view['name']);
+ $view_object->preview('page');
+ $form = $view_object->style_plugin->render();
+ $jump_options = $form['jump']['#options'];
+ foreach ($jump_options as $key => $title) {
+ if (strpos($key, $edit['jump']) !== FALSE) {
+ $edit['jump'] = $key;
+ }
+ }
+
+ $this->drupalPost($view['page[path]'], $edit, t('Go'));
+ $this->assertResponse(200);
+ $this->assertUrl($path, $options);
+ }
+ }
+
+ /**
+ * Helper function to create a node and return its expected path.
+ */
+ function createNodeAndGetPath() {
+ $node = $this->drupalCreateNode();
+ return entity_uri('node', $node);
+ }
+
+ /**
+ * Helper function to create a user and return its expected path.
+ */
+ function createUserAndGetPath() {
+ $account = $this->drupalCreateUser();
+ return entity_uri('user', $account);
+ }
+
+ /**
+ * Helper function to create a comment and return its expected path.
+ */
+ function createCommentAndGetPath() {
+ $node = $this->drupalCreateNode();
+ $comment = (object) array(
+ 'cid' => NULL,
+ 'nid' => $node->nid,
+ 'pid' => 0,
+ 'uid' => 0,
+ 'status' => COMMENT_PUBLISHED,
+ 'subject' => $this->randomName(),
+ 'language' => LANGUAGE_NONE,
+ 'comment_body' => array(LANGUAGE_NONE => array($this->randomName())),
+ );
+ comment_save($comment);
+ return entity_uri('comment', $comment);
+ }
+
+ /**
+ * Helper function to create a taxonomy term and return its expected path.
+ */
+ function createTaxonomyTermAndGetPath() {
+ $vocabulary = new stdClass();
+ $vocabulary->name = $this->randomName();
+ $vocabulary->machine_name = drupal_strtolower($this->randomName());
+ taxonomy_vocabulary_save($vocabulary);
+
+ $term = new stdClass();
+ $term->name = $this->randomName();
+ $term->vid = $vocabulary->vid;
+ taxonomy_term_save($term);
+
+ return entity_uri('taxonomy_term', $term);
+ }
+
+ /**
+ * Helper function to create a file and return its expected path.
+ */
+ function createFileAndGetPath() {
+ $file = (object) array(
+ 'uid' => 1,
+ 'filename' => 'views-ui-jump-menu-test.txt',
+ 'uri' => 'public://views-ui-jump-menu-test.txt',
+ 'filemime' => 'text/plain',
+ 'timestamp' => 1,
+ 'status' => FILE_STATUS_PERMANENT,
+ );
+ file_put_contents($file->uri, 'test content');
+ $file = file_save($file);
+ return file_create_url($file->uri);
+ }
+
+ /**
+ * Helper function to create a node revision and return its expected path.
+ */
+ function createNodeRevisionAndGetPath() {
+ // The node needs at least two revisions in order for Drupal to allow
+ // access to the revision path.
+ $settings = array('revision' => TRUE);
+ $node = $this->drupalCreateNode($settings);
+ $node->vid = NULL;
+ node_save($node);
+ return 'node/' . $node->nid . '/revisions/' . $node->vid . '/view';
+ }
+}
+
+/**
+ * Tests that displays can be correctly overridden via the user interface.
+ */
+class ViewsUIWizardOverrideDisplaysTestCase extends ViewsUIWizardHelper {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views UI overridden displays',
+ 'description' => 'Test that displays can be correctly overridden via the user interface.',
+ 'group' => 'Views UI',
+ );
+ }
+
+ /**
+ * Tests that displays can be overridden via the UI.
+ */
+ function testOverrideDisplays() {
+ // Create a basic view that shows all content, with a page and a block
+ // display.
+ $view['human_name'] = $this->randomName(16);
+ $view['name'] = strtolower($this->randomName(16));
+ $view['page[create]'] = 1;
+ $view['page[path]'] = $this->randomName(16);
+ $view['block[create]'] = 1;
+ $view_path = $view['page[path]'];
+ $this->drupalPost('admin/structure/views/add', $view, t('Save & exit'));
+
+ // Configure its title. Since the page and block both started off with the
+ // same (empty) title in the views wizard, we expect the wizard to have set
+ // things up so that they both inherit from the default display, and we
+ // therefore only need to change that to have it take effect for both.
+ $edit = array();
+ $edit['title'] = $original_title = $this->randomName(16);
+ $edit['override[dropdown]'] = 'default';
+ $this->drupalPost("admin/structure/views/nojs/display/{$view['name']}/page/title", $edit, t('Apply'));
+ $this->drupalPost("admin/structure/views/view/{$view['name']}/edit/page", array(), t('Save'));
+
+ // Put the block into the first sidebar region, and make sure it will not
+ // display on the view's page display (since we will be searching for the
+ // presence/absence of the view's title in both the page and the block).
+ $this->drupalGet('admin/structure/block');
+ $edit = array();
+ $edit["blocks[views_{$view['name']}-block][region]"] = 'sidebar_first';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $edit = array();
+ $edit['visibility'] = BLOCK_VISIBILITY_NOTLISTED;
+ $edit['pages'] = $view_path;
+ $this->drupalPost("admin/structure/block/manage/views/{$view['name']}-block/configure", $edit, t('Save block'));
+
+ // Add a node that will appear in the view, so that the block will actually
+ // be displayed.
+ $this->drupalCreateNode();
+
+ // Make sure the title appears in both the page and the block.
+ $this->drupalGet($view_path);
+ $this->assertText($original_title);
+ $this->drupalGet('');
+ $this->assertText($original_title);
+
+ // Change the title for the page display only, and make sure that is the
+ // only one that is changed.
+ $edit = array();
+ $edit['title'] = $new_title = $this->randomName(16);
+ $edit['override[dropdown]'] = 'page';
+ $this->drupalPost("admin/structure/views/nojs/display/{$view['name']}/page/title", $edit, t('Apply'));
+ $this->drupalPost("admin/structure/views/view/{$view['name']}/edit/page", array(), t('Save'));
+ $this->drupalGet($view_path);
+ $this->assertText($new_title);
+ $this->assertNoText($original_title);
+ $this->drupalGet('');
+ $this->assertText($original_title);
+ $this->assertNoText($new_title);
+ }
+
+ /**
+ * Tests that the wizard correctly sets up default and overridden displays.
+ */
+ function testWizardMixedDefaultOverriddenDisplays() {
+ // Create a basic view with a page, block, and feed. Give the page and feed
+ // identical titles, but give the block a different one, so we expect the
+ // page and feed to inherit their titles from the default display, but the
+ // block to override it.
+ $view['human_name'] = $this->randomName(16);
+ $view['name'] = strtolower($this->randomName(16));
+ $view['page[create]'] = 1;
+ $view['page[title]'] = $this->randomName(16);
+ $view['page[path]'] = $this->randomName(16);
+ $view['page[feed]'] = 1;
+ $view['page[feed_properties][path]'] = $this->randomName(16);
+ $view['block[create]'] = 1;
+ $view['block[title]'] = $this->randomName(16);
+ $this->drupalPost('admin/structure/views/add', $view, t('Save & exit'));
+
+ // Put the block into the first sidebar region, and make sure it will not
+ // display on the view's page display (since we will be searching for the
+ // presence/absence of the view's title in both the page and the block).
+ $this->drupalGet('admin/structure/block');
+ $edit = array();
+ $edit["blocks[views_{$view['name']}-block][region]"] = 'sidebar_first';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $edit = array();
+ $edit['visibility'] = BLOCK_VISIBILITY_NOTLISTED;
+ $edit['pages'] = $view['page[path]'];
+ $this->drupalPost("admin/structure/block/manage/views/{$view['name']}-block/configure", $edit, t('Save block'));
+
+ // Add a node that will appear in the view, so that the block will actually
+ // be displayed.
+ $this->drupalCreateNode();
+
+ // Make sure that the feed, page and block all start off with the correct
+ // titles.
+ $this->drupalGet($view['page[path]']);
+ $this->assertText($view['page[title]']);
+ $this->assertNoText($view['block[title]']);
+ $this->drupalGet($view['page[feed_properties][path]']);
+ $this->assertText($view['page[title]']);
+ $this->assertNoText($view['block[title]']);
+ $this->drupalGet('');
+ $this->assertText($view['block[title]']);
+ $this->assertNoText($view['page[title]']);
+
+ // Edit the page and change the title. This should automatically change
+ // the feed's title also, but not the block.
+ $edit = array();
+ $edit['title'] = $new_default_title = $this->randomName(16);
+ $this->drupalPost("admin/structure/views/nojs/display/{$view['name']}/page/title", $edit, t('Apply'));
+ $this->drupalPost("admin/structure/views/view/{$view['name']}/edit/page", array(), t('Save'));
+ $this->drupalGet($view['page[path]']);
+ $this->assertText($new_default_title);
+ $this->assertNoText($view['page[title]']);
+ $this->assertNoText($view['block[title]']);
+ $this->drupalGet($view['page[feed_properties][path]']);
+ $this->assertText($new_default_title);
+ $this->assertNoText($view['page[title]']);
+ $this->assertNoText($view['block[title]']);
+ $this->drupalGet('');
+ $this->assertText($view['block[title]']);
+ $this->assertNoText($new_default_title);
+ $this->assertNoText($view['page[title]']);
+
+ // Edit the block and change the title. This should automatically change
+ // the block title only, and leave the defaults alone.
+ $edit = array();
+ $edit['title'] = $new_block_title = $this->randomName(16);
+ $this->drupalPost("admin/structure/views/nojs/display/{$view['name']}/block/title", $edit, t('Apply'));
+ $this->drupalPost("admin/structure/views/view/{$view['name']}/edit/block", array(), t('Save'));
+ $this->drupalGet($view['page[path]']);
+ $this->assertText($new_default_title);
+ $this->assertNoText($new_block_title);
+ $this->drupalGet($view['page[feed_properties][path]']);
+ $this->assertText($new_default_title);
+ $this->assertNoText($new_block_title);
+ $this->drupalGet('');
+ $this->assertText($new_block_title);
+ $this->assertNoText($view['block[title]']);
+ }
+
+ /**
+ * Tests that the revert to all displays select-option works as expected.
+ */
+ function testRevertAllDisplays() {
+ // Create a basic view with a page, block.
+ // Because there is both a title on page and block we expect the title on
+ // the block be overriden.
+ $view['human_name'] = $this->randomName(16);
+ $view['name'] = strtolower($this->randomName(16));
+ $view['page[create]'] = 1;
+ $view['page[title]'] = $this->randomName(16);
+ $view['page[path]'] = $this->randomName(16);
+ $view['block[create]'] = 1;
+ $view['block[title]'] = $this->randomName(16);
+ $this->drupalPost('admin/structure/views/add', $view, t('Continue & edit'));
+
+ // Revert the title of the block back to the default ones, but submit some
+ // new values to be sure that the new value is not stored.
+ $edit = array();
+ $edit['title'] = $new_block_title = $this->randomName();
+ $edit['override[dropdown]'] = 'default_revert';
+
+ $this->drupalPost("admin/structure/views/nojs/display/{$view['name']}/block/title", $edit, t('Apply'));
+ $this->drupalPost("admin/structure/views/view/{$view['name']}/edit/block", array(), t('Save'));
+ $this->assertText($view['page[title]']);
+ }
+}
diff --git a/sites/all/modules/views/tests/views_upgrade.test b/sites/all/modules/views/tests/views_upgrade.test
new file mode 100644
index 000000000..3f453dbb9
--- /dev/null
+++ b/sites/all/modules/views/tests/views_upgrade.test
@@ -0,0 +1,277 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsUpgradeTestCase.
+ */
+
+/**
+ * Try to test the upgrade path of all conversions.
+ *
+ * You can find all conversions by searching for "moved to".
+ */
+class ViewsUpgradeTestCase extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Views Upgrade test',
+ 'description' => 'Try to test the upgrade path of modules which were changed.',
+ 'group' => 'Views',
+ );
+ }
+
+ protected function setUp() {
+// // To import a view the user needs use PHP for settings rights, so enable php module.
+ parent::setUp();
+
+ module_enable(array('php'));
+ $this->resetAll();
+ }
+
+ function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test']['old_field_1']['moved to'] = array('views_test', 'id');
+ $data['views_test']['old_field_2']['field']['moved to'] = array('views_test', 'name');
+ $data['views_test']['old_field_3']['filter']['moved to'] = array('views_test', 'age');
+
+ // @todo Test this scenario, too.
+ $data['views_old_table_2']['old_field']['moved to'] = array('views_test', 'job');
+
+ $data['views_old_table']['moved to'] = 'views_test';
+
+ return $data;
+ }
+
+ function debugField($field) {
+ $keys = array('id', 'table', 'field', 'actual_field', 'original_field', 'real_field');
+ $info = array();
+ foreach ($keys as $key) {
+ $info[$key] = $field->{$key};
+ }
+ debug($info, NULL, TRUE);
+ }
+
+ /**
+ * Tests the moved to parameter in general.
+ */
+ public function testMovedTo() {
+ // Test moving on field lavel.
+ $view = $this->viewsMovedToField();
+ $view->update();
+ $view->build();
+
+// $this->assertEqual('old_field_1', $view->field['old_field_1']->options['id'], "Id shouldn't change during conversion");
+// $this->assertEqual('id', $view->field['old_field_1']->field, 'The field should change during conversion');
+ $this->assertEqual('id', $view->field['old_field_1']->real_field);
+ $this->assertEqual('views_test', $view->field['old_field_1']->table);
+ $this->assertEqual('old_field_1', $view->field['old_field_1']->original_field, 'The field should have stored the original_field');
+
+ // Test moving on handler lavel.
+ $view = $this->viewsMovedToHandler();
+ $view->update();
+ $view->build();
+
+// $this->assertEqual('old_field_2', $view->field['old_field_2']->options['id']);
+ $this->assertEqual('name', $view->field['old_field_2']->real_field);
+ $this->assertEqual('views_test', $view->field['old_field_2']->table);
+
+// $this->assertEqual('old_field_3', $view->filter['old_field_3']->options['id']);
+ $this->assertEqual('age', $view->filter['old_field_3']->real_field);
+ $this->assertEqual('views_test', $view->filter['old_field_3']->table);
+
+ // Test moving on table level.
+ $view = $this->viewsMovedToTable();
+ $view->update();
+ $view->build();
+
+ $this->assertEqual('views_test', $view->base_table, 'Make sure that view->base_table gets automatically converted.');
+// $this->assertEqual('id', $view->field['id']->field, 'If we move a whole table fields of this table should work, too.');
+ $this->assertEqual('id', $view->field['id']->real_field, 'To run the query right the real_field has to be set right.');
+ $this->assertEqual('views_test', $view->field['id']->table);
+ }
+
+ /**
+ * Tests a import via ui.
+ *
+ * To ensure the general functionality, the recent comments view from drupal6
+ * is used.
+ */
+ public function testUpgradeImport() {
+ $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration', 'use PHP for settings'));
+ $this->drupalLogin($admin_user);
+ $edit = array(
+ 'view' => $this->viewUpgradeImport(),
+ );
+ $this->drupalPost('admin/structure/views/import', $edit, t('Import'));
+
+ $this->assertText('Recent comments');
+ }
+
+ public function viewsMovedToField() {
+ $view = new view;
+ $view->name = 'test_views_move_to_field';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'views_test';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+
+ $handler->display->display_options['fields']['old_field_1']['id'] = 'old_field_1';
+ $handler->display->display_options['fields']['old_field_1']['table'] = 'views_test';
+ $handler->display->display_options['fields']['old_field_1']['field'] = 'old_field_1';
+
+ return $view;
+ }
+
+ public function viewsMovedToHandler() {
+ $view = new view;
+ $view->name = 'test_views_move_to_handler';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'views_test';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+
+ $handler->display->display_options['fields']['old_field_2']['id'] = 'old_field_2';
+ $handler->display->display_options['fields']['old_field_2']['table'] = 'views_test';
+ $handler->display->display_options['fields']['old_field_2']['field'] = 'old_field_2';
+
+ $handler->display->display_options['filters']['old_field_3']['id'] = 'old_field_3';
+ $handler->display->display_options['filters']['old_field_3']['table'] = 'views_test';
+ $handler->display->display_options['filters']['old_field_3']['field'] = 'old_field_3';
+
+ return $view;
+ }
+
+ public function viewsMovedToTable() {
+ $view = new view;
+ $view->name = 'test_views_move_to_table';
+ $view->description = '';
+ $view->tag = '';
+ $view->view_php = '';
+ $view->base_table = 'views_old_table';
+ $view->is_cacheable = FALSE;
+ $view->api_version = 2;
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+
+ $handler->display->display_options['fields']['id']['id'] = 'id';
+ $handler->display->display_options['fields']['id']['table'] = 'views_old_table';
+ $handler->display->display_options['fields']['id']['field'] = 'id';
+
+ return $view;
+ }
+
+ protected function viewUpgradeImport() {
+ $import = '
+ $view = new view;
+ $view->name = "comments_recent";
+ $view->description = "Contains a block and a page to list recent comments; the block will automatically link to the page, which displays the comment body as well as a link to the node.";
+ $view->tag = "default";
+ $view->base_table = "comments";
+ $view->human_name = "";
+ $view->core = 0;
+ $view->api_version = "3.0-alpha1";
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Defaults */
+ $handler = $view->new_display("default", "Defaults", "default");
+ $handler->display->display_options["title"] = "Recent comments";
+ $handler->display->display_options["use_more"] = TRUE;
+ $handler->display->display_options["access"]["type"] = "none";
+ $handler->display->display_options["cache"]["type"] = "none";
+ $handler->display->display_options["query"]["type"] = "views_query";
+ $handler->display->display_options["exposed_form"]["type"] = "basic";
+ $handler->display->display_options["pager"]["type"] = "some";
+ $handler->display->display_options["pager"]["options"]["items_per_page"] = 5;
+ $handler->display->display_options["style_plugin"] = "list";
+ $handler->display->display_options["row_plugin"] = "fields";
+ /* Relationship: Comment: Node */
+ $handler->display->display_options["relationships"]["nid"]["id"] = "nid";
+ $handler->display->display_options["relationships"]["nid"]["table"] = "comments";
+ $handler->display->display_options["relationships"]["nid"]["field"] = "nid";
+ /* Field: Comment: Title */
+ $handler->display->display_options["fields"]["subject"]["id"] = "subject";
+ $handler->display->display_options["fields"]["subject"]["table"] = "comments";
+ $handler->display->display_options["fields"]["subject"]["field"] = "subject";
+ $handler->display->display_options["fields"]["subject"]["label"] = "";
+ $handler->display->display_options["fields"]["subject"]["link_to_comment"] = 1;
+ /* Field: Comment: Post date */
+ $handler->display->display_options["fields"]["timestamp"]["id"] = "timestamp";
+ $handler->display->display_options["fields"]["timestamp"]["table"] = "comments";
+ $handler->display->display_options["fields"]["timestamp"]["field"] = "timestamp";
+ $handler->display->display_options["fields"]["timestamp"]["label"] = "";
+ $handler->display->display_options["fields"]["timestamp"]["date_format"] = "time ago";
+ /* Sort criterion: Comment: Post date */
+ $handler->display->display_options["sorts"]["timestamp"]["id"] = "timestamp";
+ $handler->display->display_options["sorts"]["timestamp"]["table"] = "comments";
+ $handler->display->display_options["sorts"]["timestamp"]["field"] = "timestamp";
+ $handler->display->display_options["sorts"]["timestamp"]["order"] = "DESC";
+ /* Filter: Node: Published or admin */
+ $handler->display->display_options["filters"]["status_extra"]["id"] = "status_extra";
+ $handler->display->display_options["filters"]["status_extra"]["table"] = "node";
+ $handler->display->display_options["filters"]["status_extra"]["field"] = "status_extra";
+ $handler->display->display_options["filters"]["status_extra"]["relationship"] = "nid";
+ $handler->display->display_options["filters"]["status_extra"]["group"] = 0;
+ $handler->display->display_options["filters"]["status_extra"]["expose"]["operator"] = FALSE;
+
+ /* Display: Page */
+ $handler = $view->new_display("page", "Page", "page");
+ $handler->display->display_options["defaults"]["items_per_page"] = FALSE;
+ $handler->display->display_options["defaults"]["style_plugin"] = FALSE;
+ $handler->display->display_options["style_plugin"] = "list";
+ $handler->display->display_options["defaults"]["style_options"] = FALSE;
+ $handler->display->display_options["defaults"]["row_plugin"] = FALSE;
+ $handler->display->display_options["row_plugin"] = "fields";
+ $handler->display->display_options["row_options"]["inline"] = array(
+ "title" => "title",
+ "timestamp" => "timestamp",
+ );
+ $handler->display->display_options["row_options"]["separator"] = "&nbsp;";
+ $handler->display->display_options["defaults"]["row_options"] = FALSE;
+ $handler->display->display_options["defaults"]["fields"] = FALSE;
+ /* Field: Node: Title */
+ $handler->display->display_options["fields"]["title"]["id"] = "title";
+ $handler->display->display_options["fields"]["title"]["table"] = "node";
+ $handler->display->display_options["fields"]["title"]["field"] = "title";
+ $handler->display->display_options["fields"]["title"]["relationship"] = "nid";
+ $handler->display->display_options["fields"]["title"]["label"] = "Reply to";
+ $handler->display->display_options["fields"]["title"]["link_to_node"] = 1;
+ /* Field: Comment: Post date */
+ $handler->display->display_options["fields"]["timestamp"]["id"] = "timestamp";
+ $handler->display->display_options["fields"]["timestamp"]["table"] = "comments";
+ $handler->display->display_options["fields"]["timestamp"]["field"] = "timestamp";
+ $handler->display->display_options["fields"]["timestamp"]["label"] = "";
+ $handler->display->display_options["fields"]["timestamp"]["date_format"] = "time ago";
+ /* Field: Comment: Title */
+ $handler->display->display_options["fields"]["subject"]["id"] = "subject";
+ $handler->display->display_options["fields"]["subject"]["table"] = "comments";
+ $handler->display->display_options["fields"]["subject"]["field"] = "subject";
+ $handler->display->display_options["fields"]["subject"]["label"] = "";
+ $handler->display->display_options["fields"]["subject"]["link_to_comment"] = 1;
+ /* Field: Comment: Body */
+ $handler->display->display_options["fields"]["comment"]["id"] = "comment";
+ $handler->display->display_options["fields"]["comment"]["table"] = "comments";
+ $handler->display->display_options["fields"]["comment"]["field"] = "comment";
+ $handler->display->display_options["fields"]["comment"]["label"] = "";
+ $handler->display->display_options["path"] = "comments/recent";
+
+ /* Display: Block */
+ $handler = $view->new_display("block", "Block", "block");
+ $handler->display->display_options["block_description"] = "Recent comments view"
+;';
+
+ return $import;
+ }
+}
diff --git a/sites/all/modules/views/tests/views_view.test b/sites/all/modules/views/tests/views_view.test
new file mode 100644
index 000000000..d268a0c12
--- /dev/null
+++ b/sites/all/modules/views/tests/views_view.test
@@ -0,0 +1,290 @@
+<?php
+
+/**
+ * @file
+ * Definition of ViewsViewTest.
+ */
+
+/**
+ * Views class tests.
+ */
+class ViewsViewTest extends ViewsSqlTest {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Test the view class',
+ 'description' => 'Tests some functionality of the view class',
+ 'group' => 'Views',
+ );
+ }
+
+ /**
+ * Tests the deconstructor to be sure that every kind of heavy objects are removed.
+ */
+ function testDestroy() {
+ $view = $this->view_test_destroy();
+
+ $view->preview();
+ $view->destroy();
+
+ $this->assertViewDestroy($view);
+
+ // Manipulate the display variable to test a previous bug.
+ $view = $this->view_test_destroy();
+ $view->preview();
+
+ $view->destroy();
+ $this->assertViewDestroy($view);
+ }
+
+ function assertViewDestroy($view) {
+ $this->assertFalse(isset($view->display['default']->handler), 'Make sure all displays are destroyed.');
+ $this->assertFalse(isset($view->display['attachment_1']->handler), 'Make sure all displays are destroyed.');
+
+ $this->assertFalse(isset($view->filter), 'Make sure all handlers are destroyed');
+ $this->assertFalse(isset($view->field), 'Make sure all handlers are destroyed');
+ $this->assertFalse(isset($view->argument), 'Make sure all handlers are destroyed');
+ $this->assertFalse(isset($view->relationship), 'Make sure all handlers are destroyed');
+ $this->assertFalse(isset($view->sort), 'Make sure all handlers are destroyed');
+ $this->assertFalse(isset($view->area), 'Make sure all handlers are destroyed');
+
+ $keys = array('current_display', 'display_handler', 'field', 'argument', 'filter', 'sort', 'relationship', 'header', 'footer', 'empty', 'query', 'result', 'inited', 'style_plugin', 'plugin_name', 'exposed_data', 'exposed_input', 'many_to_one_tables');
+ foreach ($keys as $key) {
+ $this->assertFalse(isset($view->{$key}), $key);
+ }
+ $this->assertEqual($view->built, FALSE);
+ $this->assertEqual($view->executed, FALSE);
+ $this->assertEqual($view->build_info, array());
+ $this->assertEqual($view->attachment_before, '');
+ $this->assertEqual($view->attachment_after, '');
+ }
+
+ function testDelete() {
+ // Delete a database view
+ $view = $this->view_test_delete();
+ $view->save();
+ $view = views_get_view($view->name);
+ $view->delete();
+
+ $view = views_get_view($view->name);
+ $this->assertNotNull($view, 'Make sure that the old view is still in the static cache.');
+
+ $view = views_get_view($view->name, TRUE);
+ $this->assertNull($view, "Make sure that the old view gets cleared by the reset parameter.");
+ }
+
+ function testValidate() {
+ // Test a view with multiple displays.
+ // Validating a view shouldn't change the active display.
+ // @todo: Create an extra validation view.
+ $view = $this->view_test_destroy();
+ $view->set_display('page_1');
+
+ $view->validate();
+
+ $this->assertEqual('page_1', $view->current_display, "The display should be constant while validating");
+
+ // @todo: Write real tests for the validation.
+ // In general the following things could be tested:
+ // - Deleted displays shouldn't be validated
+ // - Multiple displays are validating and the errors are merged together.
+ }
+
+ /**
+ * This view provides some filters, fields, arguments, relationships, sorts, areas and attachments.
+ */
+ function view_test_destroy() {
+ $view = new view;
+ $view->name = 'test_destroy';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->human_name = '';
+ $view->api_version = '3.0-alpha1';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ /* Header: Global: Text area */
+ $handler->display->display_options['header']['area']['id'] = 'area';
+ $handler->display->display_options['header']['area']['table'] = 'views';
+ $handler->display->display_options['header']['area']['field'] = 'area';
+ $handler->display->display_options['header']['area']['empty'] = FALSE;
+ /* Header: Global: Text area */
+ $handler->display->display_options['header']['area_1']['id'] = 'area_1';
+ $handler->display->display_options['header']['area_1']['table'] = 'views';
+ $handler->display->display_options['header']['area_1']['field'] = 'area';
+ $handler->display->display_options['header']['area_1']['empty'] = FALSE;
+ /* Footer: Global: Text area */
+ $handler->display->display_options['footer']['area']['id'] = 'area';
+ $handler->display->display_options['footer']['area']['table'] = 'views';
+ $handler->display->display_options['footer']['area']['field'] = 'area';
+ $handler->display->display_options['footer']['area']['empty'] = FALSE;
+ /* Footer: Global: Text area */
+ $handler->display->display_options['footer']['area_1']['id'] = 'area_1';
+ $handler->display->display_options['footer']['area_1']['table'] = 'views';
+ $handler->display->display_options['footer']['area_1']['field'] = 'area';
+ $handler->display->display_options['footer']['area_1']['empty'] = FALSE;
+ /* Empty text: Global: Text area */
+ $handler->display->display_options['empty']['area']['id'] = 'area';
+ $handler->display->display_options['empty']['area']['table'] = 'views';
+ $handler->display->display_options['empty']['area']['field'] = 'area';
+ $handler->display->display_options['empty']['area']['empty'] = FALSE;
+ /* Empty text: Global: Text area */
+ $handler->display->display_options['empty']['area_1']['id'] = 'area_1';
+ $handler->display->display_options['empty']['area_1']['table'] = 'views';
+ $handler->display->display_options['empty']['area_1']['field'] = 'area';
+ $handler->display->display_options['empty']['area_1']['empty'] = FALSE;
+ /* Relationship: Comment: Node */
+ $handler->display->display_options['relationships']['nid']['id'] = 'nid';
+ $handler->display->display_options['relationships']['nid']['table'] = 'comments';
+ $handler->display->display_options['relationships']['nid']['field'] = 'nid';
+ /* Relationship: Comment: Parent comment */
+ $handler->display->display_options['relationships']['pid']['id'] = 'pid';
+ $handler->display->display_options['relationships']['pid']['table'] = 'comments';
+ $handler->display->display_options['relationships']['pid']['field'] = 'pid';
+ /* Relationship: Comment: User */
+ $handler->display->display_options['relationships']['uid']['id'] = 'uid';
+ $handler->display->display_options['relationships']['uid']['table'] = 'comments';
+ $handler->display->display_options['relationships']['uid']['field'] = 'uid';
+ /* Field: Content: Nid */
+ $handler->display->display_options['fields']['nid']['id'] = 'nid';
+ $handler->display->display_options['fields']['nid']['table'] = 'node';
+ $handler->display->display_options['fields']['nid']['field'] = 'nid';
+ /* Field: Content: Path */
+ $handler->display->display_options['fields']['path']['id'] = 'path';
+ $handler->display->display_options['fields']['path']['table'] = 'node';
+ $handler->display->display_options['fields']['path']['field'] = 'path';
+ /* Field: Content: Post date */
+ $handler->display->display_options['fields']['created']['id'] = 'created';
+ $handler->display->display_options['fields']['created']['table'] = 'node';
+ $handler->display->display_options['fields']['created']['field'] = 'created';
+ /* Sort criterion: Content: In moderation */
+ $handler->display->display_options['sorts']['moderate']['id'] = 'moderate';
+ $handler->display->display_options['sorts']['moderate']['table'] = 'node';
+ $handler->display->display_options['sorts']['moderate']['field'] = 'moderate';
+ /* Sort criterion: Content: Last comment author */
+ $handler->display->display_options['sorts']['last_comment_name']['id'] = 'last_comment_name';
+ $handler->display->display_options['sorts']['last_comment_name']['table'] = 'node_comment_statistics';
+ $handler->display->display_options['sorts']['last_comment_name']['field'] = 'last_comment_name';
+ /* Sort criterion: Content: Last comment time */
+ $handler->display->display_options['sorts']['last_comment_timestamp']['id'] = 'last_comment_timestamp';
+ $handler->display->display_options['sorts']['last_comment_timestamp']['table'] = 'node_comment_statistics';
+ $handler->display->display_options['sorts']['last_comment_timestamp']['field'] = 'last_comment_timestamp';
+ /* Argument: Content: Created date */
+ $handler->display->display_options['arguments']['created_fulldate']['id'] = 'created_fulldate';
+ $handler->display->display_options['arguments']['created_fulldate']['table'] = 'node';
+ $handler->display->display_options['arguments']['created_fulldate']['field'] = 'created_fulldate';
+ $handler->display->display_options['arguments']['created_fulldate']['style_plugin'] = 'default_summary';
+ $handler->display->display_options['arguments']['created_fulldate']['default_argument_type'] = 'fixed';
+ /* Argument: Content: Created day */
+ $handler->display->display_options['arguments']['created_day']['id'] = 'created_day';
+ $handler->display->display_options['arguments']['created_day']['table'] = 'node';
+ $handler->display->display_options['arguments']['created_day']['field'] = 'created_day';
+ $handler->display->display_options['arguments']['created_day']['style_plugin'] = 'default_summary';
+ $handler->display->display_options['arguments']['created_day']['default_argument_type'] = 'fixed';
+ /* Argument: Content: Created month */
+ $handler->display->display_options['arguments']['created_month']['id'] = 'created_month';
+ $handler->display->display_options['arguments']['created_month']['table'] = 'node';
+ $handler->display->display_options['arguments']['created_month']['field'] = 'created_month';
+ $handler->display->display_options['arguments']['created_month']['style_plugin'] = 'default_summary';
+ $handler->display->display_options['arguments']['created_month']['default_argument_type'] = 'fixed';
+ /* Filter: Content: Nid */
+ $handler->display->display_options['filters']['nid']['id'] = 'nid';
+ $handler->display->display_options['filters']['nid']['table'] = 'node';
+ $handler->display->display_options['filters']['nid']['field'] = 'nid';
+ /* Filter: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ /* Filter: Content: Title */
+ $handler->display->display_options['filters']['title']['id'] = 'title';
+ $handler->display->display_options['filters']['title']['table'] = 'node';
+ $handler->display->display_options['filters']['title']['field'] = 'title';
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page_1');
+ $handler->display->display_options['path'] = 'test_destroy';
+
+ /* Display: Attachment */
+ $handler = $view->new_display('attachment', 'Attachment', 'attachment_1');
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['displays'] = array(
+ 'default' => 'default',
+ 'page_1' => 'page_1',
+ );
+
+ /* Display: Attachment */
+ $handler = $view->new_display('attachment', 'Attachment', 'attachment_2');
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['displays'] = array(
+ 'default' => 'default',
+ 'page_1' => 'page_1',
+ );
+ $translatables['test_destroy'] = array(
+ t('Master'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort By'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ t('Text area'),
+ t('Content'),
+ t('Parent comment'),
+ t('User'),
+ t('Nid'),
+ t('Path'),
+ t('Post date'),
+ t('All'),
+ t('Page'),
+ t('Attachment'),
+ );
+
+ return $view;
+ }
+ function view_test_delete() {
+ $view = new view;
+ $view->name = 'test_view_delete';
+ $view->description = '';
+ $view->tag = '';
+ $view->base_table = 'node';
+ $view->human_name = 'test_view_delete';
+ $view->core = 7;
+ $view->api_version = '3.0-alpha1';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Defaults */
+ $handler = $view->new_display('default', 'Defaults', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'fields';
+ $translatables['test_view_delete'] = array(
+ t('Defaults'),
+ t('more'),
+ t('Apply'),
+ t('Reset'),
+ t('Sort By'),
+ t('Asc'),
+ t('Desc'),
+ t('Items per page'),
+ t('- All -'),
+ t('Offset'),
+ );
+
+ return $view;
+ }
+}
diff --git a/sites/all/modules/views/theme/theme.inc b/sites/all/modules/views/theme/theme.inc
new file mode 100644
index 000000000..e7f7a1589
--- /dev/null
+++ b/sites/all/modules/views/theme/theme.inc
@@ -0,0 +1,1155 @@
+<?php
+
+/**
+ * @file
+ * Preprocessors and helper functions to make theming easier.
+ */
+
+/**
+ * Provide a full array of possible themes to try for a given hook.
+ *
+ * @param $hook
+ * The hook to use. This is the base theme/template name.
+ * @param $view
+ * The view being rendered.
+ * @param $display
+ * The display being rendered, if applicable.
+ */
+function _views_theme_functions($hook, $view, $display = NULL) {
+ $themes = array();
+
+ if ($display) {
+ $themes[] = $hook . '__' . $view->name . '__' . $display->id;
+ $themes[] = $hook . '__' . $display->id;
+ $themes[] = $hook . '__' . preg_replace('/[^a-z0-9]/', '_', strtolower($view->tag));
+
+ // Add theme suggestions foreach single tag.
+ foreach (drupal_explode_tags($view->tag) as $tag) {
+ $themes[] = $hook . '__' . preg_replace('/[^a-z0-9]/', '_', strtolower($tag));
+ }
+
+ if ($display->id != $display->display_plugin) {
+ $themes[] = $hook . '__' . $view->name . '__' . $display->display_plugin;
+ $themes[] = $hook . '__' . $display->display_plugin;
+ }
+ }
+ $themes[] = $hook . '__' . $view->name;
+ $themes[] = $hook;
+ return $themes;
+}
+
+/**
+ * Preprocess the primary theme implementation for a view.
+ */
+function template_preprocess_views_view(&$vars) {
+ global $base_path;
+
+ $view = $vars['view'];
+
+ $vars['rows'] = (!empty($view->result) || $view->style_plugin->even_empty()) ? $view->style_plugin->render($view->result) : '';
+
+ $vars['css_name'] = drupal_clean_css_identifier($view->name);
+ $vars['name'] = $view->name;
+ $vars['display_id'] = $view->current_display;
+
+ // Basic classes
+ $vars['css_class'] = '';
+
+ $vars['classes_array'] = array();
+ $vars['classes_array'][] = 'view';
+ $vars['classes_array'][] = 'view-' . drupal_clean_css_identifier($vars['name']);
+ $vars['classes_array'][] = 'view-id-' . $vars['name'];
+ $vars['classes_array'][] = 'view-display-id-' . $vars['display_id'];
+
+ $css_class = $view->display_handler->get_option('css_class');
+ if (!empty($css_class)) {
+ $vars['css_class'] = preg_replace('/[^a-zA-Z0-9- ]/', '-', $css_class);
+ $vars['classes_array'][] = $vars['css_class'];
+ }
+
+ $empty = empty($vars['rows']);
+
+ $vars['header'] = $view->display_handler->render_area('header', $empty);
+ $vars['footer'] = $view->display_handler->render_area('footer', $empty);
+ if ($empty) {
+ $vars['empty'] = $view->display_handler->render_area('empty', $empty);
+ }
+
+ $vars['exposed'] = !empty($view->exposed_widgets) ? $view->exposed_widgets : '';
+ $vars['more'] = $view->display_handler->render_more_link();
+ $vars['feed_icon'] = !empty($view->feed_icon) ? $view->feed_icon : '';
+
+ $vars['pager'] = '';
+
+ // @todo: Figure out whether this belongs into views_ui_preprocess_views_view.
+ // Render title for the admin preview.
+ $vars['title'] = !empty($view->views_ui_context) ? filter_xss_admin($view->get_title()) : '';
+
+ if ($view->display_handler->render_pager()) {
+ $exposed_input = isset($view->exposed_raw_input) ? $view->exposed_raw_input : NULL;
+ $vars['pager'] = $view->query->render_pager($exposed_input);
+ }
+
+ $vars['attachment_before'] = !empty($view->attachment_before) ? $view->attachment_before : '';
+ $vars['attachment_after'] = !empty($view->attachment_after) ? $view->attachment_after : '';
+
+ // Add contextual links to the view. We need to attach them to the dummy
+ // $view_array variable, since contextual_preprocess() requires that they be
+ // attached to an array (not an object) in order to process them. For our
+ // purposes, it doesn't matter what we attach them to, since once they are
+ // processed by contextual_preprocess() they will appear in the $title_suffix
+ // variable (which we will then render in views-view.tpl.php).
+ views_add_contextual_links($vars['view_array'], 'view', $view, $view->current_display);
+
+ // Attachments are always updated with the outer view, never by themselves,
+ // so they do not have dom ids.
+ if (empty($view->is_attachment)) {
+ // Our JavaScript needs to have some means to find the HTML belonging to this
+ // view.
+ //
+ // It is true that the DIV wrapper has classes denoting the name of the view
+ // and its display ID, but this is not enough to unequivocally match a view
+ // with its HTML, because one view may appear several times on the page. So
+ // we set up a hash with the current time, $dom_id, to issue a "unique" identifier for
+ // each view. This identifier is written to both Drupal.settings and the DIV
+ // wrapper.
+ $vars['dom_id'] = $view->dom_id;
+ $vars['classes_array'][] = 'view-dom-id-' . $vars['dom_id'];
+ }
+
+ // If using AJAX, send identifying data about this view.
+ if ($view->use_ajax && empty($view->is_attachment) && empty($view->live_preview)) {
+ $settings = array(
+ 'views' => array(
+ 'ajax_path' => url('views/ajax'),
+ 'ajaxViews' => array(
+ 'views_dom_id:' . $vars['dom_id'] => array(
+ 'view_name' => $view->name,
+ 'view_display_id' => $view->current_display,
+ 'view_args' => check_plain(implode('/', $view->args)),
+ 'view_path' => check_plain($_GET['q']),
+ // Pass through URL to ensure we get e.g. language prefixes.
+// 'view_base_path' => isset($view->display['page']) ? substr(url($view->display['page']->display_options['path']), strlen($base_path)) : '',
+ 'view_base_path' => $view->get_path(),
+ 'view_dom_id' => $vars['dom_id'],
+ // To fit multiple views on a page, the programmer may have
+ // overridden the display's pager_element.
+ 'pager_element' => isset($view->query->pager) ? $view->query->pager->get_pager_id() : 0,
+ ),
+ ),
+ ),
+ );
+
+ drupal_add_js($settings, 'setting');
+ views_add_js('ajax_view');
+ }
+
+ // If form fields were found in the View, reformat the View output as a form.
+ if (views_view_has_form_elements($view)) {
+ $output = !empty($vars['rows']) ? $vars['rows'] : $vars['empty'];
+ $form = drupal_get_form(views_form_id($view), $view, $output);
+ // The form is requesting that all non-essential views elements be hidden,
+ // usually because the rendered step is not a view result.
+ if ($form['show_view_elements']['#value'] == FALSE) {
+ $vars['header'] = '';
+ $vars['exposed'] = '';
+ $vars['pager'] = '';
+ $vars['footer'] = '';
+ $vars['more'] = '';
+ $vars['feed_icon'] = '';
+ }
+ $vars['rows'] = $form;
+ }
+}
+
+/**
+ * Process function to render certain elements into the view.
+ */
+function template_process_views_view(&$vars) {
+ if (is_array($vars['rows'])) {
+ $vars['rows'] = drupal_render($vars['rows']);
+ }
+
+ // Flatten the classes to a string for the template file.
+ $vars['classes'] = implode(' ', $vars['classes_array']);
+}
+
+/**
+ * Preprocess theme function to print a single record from a row, with fields
+ */
+function template_preprocess_views_view_fields(&$vars) {
+ $view = $vars['view'];
+
+ // Loop through the fields for this view.
+ $previous_inline = FALSE;
+ $vars['fields'] = array(); // ensure it's at least an empty array.
+ foreach ($view->field as $id => $field) {
+ // render this even if set to exclude so it can be used elsewhere.
+ $field_output = $view->style_plugin->get_field($view->row_index, $id);
+ $empty = $field->is_value_empty($field_output, $field->options['empty_zero']);
+ if (empty($field->options['exclude']) && (!$empty || (empty($field->options['hide_empty']) && empty($vars['options']['hide_empty'])))) {
+ $object = new stdClass();
+ $object->handler = &$view->field[$id];
+ $object->inline = !empty($vars['options']['inline'][$id]);
+
+ $object->element_type = $object->handler->element_type(TRUE, !$vars['options']['default_field_elements'], $object->inline);
+ if ($object->element_type) {
+ $class = '';
+ if ($object->handler->options['element_default_classes']) {
+ $class = 'field-content';
+ }
+
+ if ($classes = $object->handler->element_classes($view->row_index)) {
+ if ($class) {
+ $class .= ' ';
+ }
+ $class .= $classes;
+ }
+
+ $pre = '<' . $object->element_type;
+ if ($class) {
+ $pre .= ' class="' . $class . '"';
+ }
+ $field_output = $pre . '>' . $field_output . '</' . $object->element_type . '>';
+ }
+
+ // Protect ourself somewhat for backward compatibility. This will prevent
+ // old templates from producing invalid HTML when no element type is selected.
+ if (empty($object->element_type)) {
+ $object->element_type = 'span';
+ }
+
+ $object->content = $field_output;
+ if (isset($view->field[$id]->field_alias) && isset($vars['row']->{$view->field[$id]->field_alias})) {
+ $object->raw = $vars['row']->{$view->field[$id]->field_alias};
+ }
+ else {
+ $object->raw = NULL; // make sure it exists to reduce NOTICE
+ }
+
+ if (!empty($vars['options']['separator']) && $previous_inline && $object->inline && $object->content) {
+ $object->separator = filter_xss_admin($vars['options']['separator']);
+ }
+
+ $object->class = drupal_clean_css_identifier($id);
+
+ $previous_inline = $object->inline;
+ $object->inline_html = $object->handler->element_wrapper_type(TRUE, TRUE);
+ if ($object->inline_html === '' && $vars['options']['default_field_elements']) {
+ $object->inline_html = $object->inline ? 'span' : 'div';
+ }
+
+ // Set up the wrapper HTML.
+ $object->wrapper_prefix = '';
+ $object->wrapper_suffix = '';
+
+ if ($object->inline_html) {
+ $class = '';
+ if ($object->handler->options['element_default_classes']) {
+ $class = "views-field views-field-" . $object->class;
+ }
+
+ if ($classes = $object->handler->element_wrapper_classes($view->row_index)) {
+ if ($class) {
+ $class .= ' ';
+ }
+ $class .= $classes;
+ }
+
+ $object->wrapper_prefix = '<' . $object->inline_html;
+ if ($class) {
+ $object->wrapper_prefix .= ' class="' . $class . '"';
+ }
+ $object->wrapper_prefix .= '>';
+ $object->wrapper_suffix = '</' . $object->inline_html . '>';
+ }
+
+ // Set up the label for the value and the HTML to make it easier
+ // on the template.
+ $object->label = check_plain($view->field[$id]->label());
+ $object->label_html = '';
+ if ($object->label) {
+ $object->label_html .= $object->label;
+ if ($object->handler->options['element_label_colon']) {
+ $object->label_html .= ': ';
+ }
+
+ $object->element_label_type = $object->handler->element_label_type(TRUE, !$vars['options']['default_field_elements']);
+ if ($object->element_label_type) {
+ $class = '';
+ if ($object->handler->options['element_default_classes']) {
+ $class = 'views-label views-label-' . $object->class;
+ }
+
+ $element_label_class = $object->handler->element_label_classes($view->row_index);
+ if ($element_label_class) {
+ if ($class) {
+ $class .= ' ';
+ }
+
+ $class .= $element_label_class;
+ }
+
+ $pre = '<' . $object->element_label_type;
+ if ($class) {
+ $pre .= ' class="' . $class . '"';
+ }
+ $pre .= '>';
+
+ $object->label_html = $pre . $object->label_html . '</' . $object->element_label_type . '>';
+ }
+ }
+
+ $vars['fields'][$id] = $object;
+ }
+ }
+
+}
+
+/**
+ * Display a single views grouping.
+ */
+function theme_views_view_grouping($vars) {
+ $view = $vars['view'];
+ $title = $vars['title'];
+ $content = $vars['content'];
+
+ $output = '<div class="view-grouping">';
+ $output .= '<div class="view-grouping-header">' . $title . '</div>';
+ $output .= '<div class="view-grouping-content">' . $content . '</div>' ;
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Process a single grouping within a view.
+ */
+function template_preprocess_views_view_grouping(&$vars) {
+ $vars['content'] = $vars['view']->style_plugin->render_grouping_sets($vars['rows'], $vars['grouping_level']);
+}
+
+/**
+ * Display a single views field.
+ *
+ * Interesting bits of info:
+ * $field->field_alias says what the raw value in $row will be. Reach it like
+ * this: @code { $row->{$field->field_alias} @endcode
+ */
+function theme_views_view_field($vars) {
+ $view = $vars['view'];
+ $field = $vars['field'];
+ $row = $vars['row'];
+ return $vars['output'];
+}
+
+/**
+ * Process a single field within a view.
+ *
+ * This preprocess function isn't normally run, as a function is used by
+ * default, for performance. However, by creating a template, this
+ * preprocess should get picked up.
+ */
+function template_preprocess_views_view_field(&$vars) {
+ $vars['output'] = $vars['field']->advanced_render($vars['row']);
+}
+
+/**
+ * Preprocess theme function to print a single record from a row, with fields
+ */
+function template_preprocess_views_view_summary(&$vars) {
+ $view = $vars['view'];
+ $argument = $view->argument[$view->build_info['summary_level']];
+ $vars['row_classes'] = array();
+
+ $url_options = array();
+
+ if (!empty($view->exposed_raw_input)) {
+ $url_options['query'] = $view->exposed_raw_input;
+ }
+
+ $active_urls = drupal_map_assoc(array(
+ url($_GET['q'], array('alias' => TRUE)), // force system path
+ url($_GET['q']), // could be an alias
+ ));
+
+ // Collect all arguments foreach row, to be able to alter them for example by the validator.
+ // This is not done per single argument value, because this could cause performance problems.
+ $row_args = array();
+
+ foreach ($vars['rows'] as $id => $row) {
+ $row_args[$id] = $argument->summary_argument($row);
+ }
+ $argument->process_summary_arguments($row_args);
+
+ foreach ($vars['rows'] as $id => $row) {
+ $vars['row_classes'][$id] = '';
+
+ $vars['rows'][$id]->link = $argument->summary_name($row);
+ $args = $view->args;
+ $args[$argument->position] = $row_args[$id];
+
+ $base_path = NULL;
+ if (!empty($argument->options['summary_options']['base_path'])) {
+ $base_path = $argument->options['summary_options']['base_path'];
+ }
+ $vars['rows'][$id]->url = url($view->get_url($args, $base_path), $url_options);
+ $vars['rows'][$id]->count = intval($row->{$argument->count_alias});
+ if (isset($active_urls[$vars['rows'][$id]->url])) {
+ $vars['row_classes'][$id] = 'active';
+ }
+ }
+}
+
+/**
+ * Template preprocess theme function to print summary basically
+ * unformatted.
+ */
+function template_preprocess_views_view_summary_unformatted(&$vars) {
+ $view = $vars['view'];
+ $argument = $view->argument[$view->build_info['summary_level']];
+ $vars['row_classes'] = array();
+
+ $url_options = array();
+
+ if (!empty($view->exposed_raw_input)) {
+ $url_options['query'] = $view->exposed_raw_input;
+ }
+
+ $count = 0;
+ $active_urls = drupal_map_assoc(array(
+ url($_GET['q'], array('alias' => TRUE)), // force system path
+ url($_GET['q']), // could be an alias
+ ));
+
+ // Collect all arguments foreach row, to be able to alter them for example by the validator.
+ // This is not done per single argument value, because this could cause performance problems.
+ $row_args = array();
+ foreach ($vars['rows'] as $id => $row) {
+ $row_args[$id] = $argument->summary_argument($row);
+ }
+ $argument->process_summary_arguments($row_args);
+
+ foreach ($vars['rows'] as $id => $row) {
+ // only false on first time:
+ if ($count++) {
+ $vars['rows'][$id]->separator = filter_xss_admin($vars['options']['separator']);
+ }
+ $vars['rows'][$id]->link = $argument->summary_name($row);
+ $args = $view->args;
+ $args[$argument->position] = $row_args[$id];
+
+ $base_path = NULL;
+ if (!empty($argument->options['summary_options']['base_path'])) {
+ $base_path = $argument->options['summary_options']['base_path'];
+ }
+ $vars['rows'][$id]->url = url($view->get_url($args, $base_path), $url_options);
+ $vars['rows'][$id]->count = intval($row->{$argument->count_alias});
+ if (isset($active_urls[$vars['rows'][$id]->url])) {
+ $vars['row_classes'][$id] = 'active';
+ }
+ }
+}
+
+/**
+ * Display a view as a table style.
+ */
+function template_preprocess_views_view_table(&$vars) {
+ $view = $vars['view'];
+
+ // We need the raw data for this grouping, which is passed in as $vars['rows'].
+ // However, the template also needs to use for the rendered fields. We
+ // therefore swap the raw data out to a new variable and reset $vars['rows']
+ // so that it can get rebuilt.
+ // Store rows so that they may be used by further preprocess functions.
+ $result = $vars['result'] = $vars['rows'];
+ $vars['rows'] = array();
+ $vars['field_classes'] = array();
+ $vars['header'] = array();
+
+ $options = $view->style_plugin->options;
+ $handler = $view->style_plugin;
+
+ $default_row_class = isset($options['default_row_class']) ? $options['default_row_class'] : TRUE;
+ $row_class_special = isset($options['row_class_special']) ? $options['row_class_special'] : TRUE;
+
+ $fields = &$view->field;
+ $columns = $handler->sanitize_columns($options['columns'], $fields);
+
+ $active = !empty($handler->active) ? $handler->active : '';
+ $order = !empty($handler->order) ? $handler->order : 'asc';
+
+ $query = tablesort_get_query_parameters();
+ if (isset($view->exposed_raw_input)) {
+ $query += $view->exposed_raw_input;
+ }
+
+ // Fields must be rendered in order as of Views 2.3, so we will pre-render
+ // everything.
+ $renders = $handler->render_fields($result);
+
+ foreach ($columns as $field => $column) {
+ // Create a second variable so we can easily find what fields we have and what the
+ // CSS classes should be.
+ $vars['fields'][$field] = drupal_clean_css_identifier($field);
+ if ($active == $field) {
+ $vars['fields'][$field] .= ' active';
+ }
+
+ // render the header labels
+ if ($field == $column && empty($fields[$field]->options['exclude'])) {
+ $label = check_plain(!empty($fields[$field]) ? $fields[$field]->label() : '');
+ if (empty($options['info'][$field]['sortable']) || !$fields[$field]->click_sortable()) {
+ $vars['header'][$field] = $label;
+ }
+ else {
+ $initial = !empty($options['info'][$field]['default_sort_order']) ? $options['info'][$field]['default_sort_order'] : 'asc';
+
+ if ($active == $field) {
+ $initial = ($order == 'asc') ? 'desc' : 'asc';
+ }
+
+ $title = t('sort by @s', array('@s' => $label));
+ if ($active == $field) {
+ $label .= theme('tablesort_indicator', array('style' => $initial));
+ }
+
+ $query['order'] = $field;
+ $query['sort'] = $initial;
+ $link_options = array(
+ 'html' => TRUE,
+ 'attributes' => array('title' => $title),
+ 'query' => $query,
+ );
+ $vars['header'][$field] = l($label, $_GET['q'], $link_options);
+ }
+
+ $vars['header_classes'][$field] = '';
+ // Set up the header label class.
+ if ($fields[$field]->options['element_default_classes']) {
+ $vars['header_classes'][$field] .= "views-field views-field-" . $vars['fields'][$field];
+ }
+ $class = $fields[$field]->element_label_classes(0);
+ if ($class) {
+ if ($vars['header_classes'][$field]) {
+ $vars['header_classes'][$field] .= ' ';
+ }
+ $vars['header_classes'][$field] .= $class;
+ }
+ // Add a CSS align class to each field if one was set
+ if (!empty($options['info'][$field]['align'])) {
+ $vars['header_classes'][$field] .= ' ' . drupal_clean_css_identifier($options['info'][$field]['align']);
+ }
+
+ // Add a header label wrapper if one was selected.
+ if ($vars['header'][$field]) {
+ $element_label_type = $fields[$field]->element_label_type(TRUE, TRUE);
+ if ($element_label_type) {
+ $vars['header'][$field] = '<' . $element_label_type . '>' . $vars['header'][$field] . '</' . $element_label_type . '>';
+ }
+ }
+
+ }
+
+ // Add a CSS align class to each field if one was set
+ if (!empty($options['info'][$field]['align'])) {
+ $vars['fields'][$field] .= ' ' . drupal_clean_css_identifier($options['info'][$field]['align']);
+ }
+
+ // Render each field into its appropriate column.
+ foreach ($result as $num => $row) {
+ // Add field classes
+ $vars['field_classes'][$field][$num] = '';
+ if ($fields[$field]->options['element_default_classes']) {
+ $vars['field_classes'][$field][$num] = "views-field views-field-" . $vars['fields'][$field];
+ }
+ if ($classes = $fields[$field]->element_classes($num)) {
+ if ($vars['field_classes'][$field][$num]) {
+ $vars['field_classes'][$field][$num] .= ' ';
+ }
+
+ $vars['field_classes'][$field][$num] .= $classes;
+ }
+ $vars['field_attributes'][$field][$num] = array();
+
+ if (!empty($fields[$field]) && empty($fields[$field]->options['exclude'])) {
+ $field_output = $renders[$num][$field];
+ $element_type = $fields[$field]->element_type(TRUE, TRUE);
+ if ($element_type) {
+ $field_output = '<' . $element_type . '>' . $field_output . '</' . $element_type . '>';
+ }
+
+ // Don't bother with separators and stuff if the field does not show up.
+ if (empty($field_output) && !empty($vars['rows'][$num][$column])) {
+ continue;
+ }
+
+ // Place the field into the column, along with an optional separator.
+ if (!empty($vars['rows'][$num][$column])) {
+ if (!empty($options['info'][$column]['separator'])) {
+ $vars['rows'][$num][$column] .= filter_xss_admin($options['info'][$column]['separator']);
+ }
+ }
+ else {
+ $vars['rows'][$num][$column] = '';
+ }
+
+ $vars['rows'][$num][$column] .= $field_output;
+ }
+ }
+
+ // Remove columns if the option is hide empty column is checked and the field is not empty.
+ if (!empty($options['info'][$field]['empty_column'])) {
+ $empty = TRUE;
+ foreach ($vars['rows'] as $num => $columns) {
+ $empty &= empty($columns[$column]);
+ }
+ if ($empty) {
+ foreach ($vars['rows'] as $num => &$column_items) {
+ unset($column_items[$column]);
+ unset($vars['header'][$column]);
+ }
+ }
+ }
+ }
+
+ // Hide table header if all labels are empty.
+ if (!array_filter($vars['header'])) {
+ $vars['header'] = array();
+ }
+
+ $count = 0;
+ foreach ($vars['rows'] as $num => $row) {
+ $vars['row_classes'][$num] = array();
+ if ($row_class_special) {
+ $vars['row_classes'][$num][] = ($count++ % 2 == 0) ? 'odd' : 'even';
+ }
+ if ($row_class = $handler->get_row_class($num)) {
+ $vars['row_classes'][$num][] = $row_class;
+ }
+ }
+
+ if ($row_class_special) {
+ $vars['row_classes'][0][] = 'views-row-first';
+ $vars['row_classes'][count($vars['row_classes']) - 1][] = 'views-row-last';
+ }
+
+ $vars['classes_array'] = array('views-table');
+ if (empty($vars['rows']) && !empty($options['empty_table'])) {
+ $vars['rows'][0][0] = $view->display_handler->render_area('empty');
+ // Calculate the amounts of rows with output.
+ $vars['field_attributes'][0][0]['colspan'] = count($vars['header']);
+ $vars['field_classes'][0][0] = 'views-empty';
+ }
+
+
+ if (!empty($options['sticky'])) {
+ drupal_add_js('misc/tableheader.js');
+ $vars['classes_array'][] = "sticky-enabled";
+ }
+ $vars['classes_array'][] = 'cols-'. count($vars['header']);
+
+ // Add the summary to the list if set.
+ if (!empty($handler->options['summary'])) {
+ $vars['attributes_array'] = array('summary' => filter_xss_admin($handler->options['summary']));
+ }
+
+ // Add the caption to the list if set.
+ if (!empty($handler->options['caption'])) {
+ $vars['caption'] = filter_xss_admin($handler->options['caption']);
+ }
+ else {
+ $vars['caption'] = '';
+ }
+}
+
+/**
+ * Display a view as a grid style.
+ */
+function template_preprocess_views_view_grid(&$vars) {
+ $view = $vars['view'];
+ $result = $view->result;
+ $options = $view->style_plugin->options;
+ $handler = $view->style_plugin;
+ $default_row_class = isset($options['default_row_class']) ? $options['default_row_class'] : TRUE;
+ $row_class_special = isset($options['row_class_special']) ? $options['row_class_special'] : TRUE;
+
+ $columns = $options['columns'];
+
+ $rows = array();
+ $row_indexes = array();
+
+ if ($options['alignment'] == 'horizontal') {
+ $row = array();
+ $col_count = 0;
+ $row_count = 0;
+ $count = 0;
+ foreach ($vars['rows'] as $row_index => $item) {
+ $count++;
+ $row[] = $item;
+ $row_indexes[$row_count][$col_count] = $row_index;
+ $col_count++;
+ if ($count % $columns == 0) {
+ $rows[] = $row;
+ $row = array();
+ $col_count = 0;
+ $row_count++;
+ }
+ }
+ if ($row) {
+ // Fill up the last line only if it's configured, but this is default.
+ if (!empty($handler->options['fill_single_line']) && count($rows)) {
+ for ($i = 0; $i < ($columns - $col_count); $i++) {
+ $row[] = '';
+ }
+ }
+ $rows[] = $row;
+ }
+ }
+ else {
+ $num_rows = floor(count($vars['rows']) / $columns);
+ // The remainders are the 'odd' columns that are slightly longer.
+ $remainders = count($vars['rows']) % $columns;
+ $row = 0;
+ $col = 0;
+ foreach ($vars['rows'] as $count => $item) {
+ $rows[$row][$col] = $item;
+ $row_indexes[$row][$col] = $count;
+ $row++;
+
+ if (!$remainders && $row == $num_rows) {
+ $row = 0;
+ $col++;
+ }
+ elseif ($remainders && $row == $num_rows + 1) {
+ $row = 0;
+ $col++;
+ $remainders--;
+ }
+ }
+ for ($i = 0; $i < count($rows[0]); $i++) {
+ // This should be string so that's okay :)
+ if (!isset($rows[count($rows) - 1][$i])) {
+ $rows[count($rows) - 1][$i] = '';
+ }
+ }
+ }
+
+ // Apply the row classes
+ foreach ($rows as $row_number => $row) {
+ $row_classes = array();
+ if ($default_row_class) {
+ $row_classes[] = 'row-' . ($row_number + 1);
+ }
+ if ($row_class_special) {
+ if ($row_number == 0) {
+ $row_classes[] = 'row-first';
+ }
+ if (count($rows) == ($row_number + 1)) {
+ $row_classes[] = 'row-last';
+ }
+ }
+ $vars['row_classes'][$row_number] = implode(' ', $row_classes);
+ foreach ($rows[$row_number] as $column_number => $item) {
+ $column_classes = array();
+ if ($default_row_class) {
+ $column_classes[] = 'col-'. ($column_number + 1);
+ }
+ if ($row_class_special) {
+ if ($column_number == 0) {
+ $column_classes[] = 'col-first';
+ }
+ elseif (count($rows[$row_number]) == ($column_number + 1)) {
+ $column_classes[] = 'col-last';
+ }
+ }
+ if (isset($row_indexes[$row_number][$column_number]) && $column_class = $view->style_plugin->get_row_class($row_indexes[$row_number][$column_number])) {
+ $column_classes[] = $column_class;
+ }
+ $vars['column_classes'][$row_number][$column_number] = implode(' ', $column_classes);
+ }
+ }
+ $vars['rows'] = $rows;
+ $vars['class'] = 'views-view-grid cols-' . $columns;
+
+ // Add the summary to the list if set.
+ if (!empty($handler->options['summary'])) {
+ $vars['attributes_array'] = array('summary' => filter_xss_admin($handler->options['summary']));
+ }
+
+ // Add the caption to the list if set.
+ if (!empty($handler->options['caption'])) {
+ $vars['caption'] = filter_xss_admin($handler->options['caption']);
+ }
+ else {
+ $vars['caption'] = '';
+ }
+}
+
+/**
+ * Display the simple view of rows one after another
+ */
+function template_preprocess_views_view_unformatted(&$vars) {
+ $view = $vars['view'];
+ $rows = $vars['rows'];
+ $style = $view->style_plugin;
+ $options = $style->options;
+
+ $vars['classes_array'] = array();
+ $vars['classes'] = array();
+ $default_row_class = isset($options['default_row_class']) ? $options['default_row_class'] : FALSE;
+ $row_class_special = isset($options['row_class_special']) ? $options['row_class_special'] : FALSE;
+ // Set up striping values.
+ $count = 0;
+ $max = count($rows);
+ foreach ($rows as $id => $row) {
+ $count++;
+ if ($default_row_class) {
+ $vars['classes'][$id][] = 'views-row';
+ $vars['classes'][$id][] = 'views-row-' . $count;
+ }
+ if ($row_class_special) {
+ $vars['classes'][$id][] = 'views-row-' . ($count % 2 ? 'odd' : 'even');
+ if ($count == 1) {
+ $vars['classes'][$id][] = 'views-row-first';
+ }
+ if ($count == $max) {
+ $vars['classes'][$id][] = 'views-row-last';
+ }
+ }
+
+ if ($row_class = $view->style_plugin->get_row_class($id)) {
+ $vars['classes'][$id][] = $row_class;
+ }
+
+ // Flatten the classes to a string for each row for the template file.
+ $vars['classes_array'][$id] = isset($vars['classes'][$id]) ? implode(' ', $vars['classes'][$id]) : '';
+ }
+}
+
+/**
+ * Display the view as an HTML list element
+ */
+function template_preprocess_views_view_list(&$vars) {
+ $handler = $vars['view']->style_plugin;
+
+ $class = explode(' ', $handler->options['class']);
+ $class = array_map('views_clean_css_identifier', $class);
+
+ $wrapper_class = explode(' ', $handler->options['wrapper_class']);
+ $wrapper_class = array_map('views_clean_css_identifier', $wrapper_class);
+
+ $vars['class'] = implode(' ', $class);
+ $vars['wrapper_class'] = implode(' ', $wrapper_class);
+ $vars['wrapper_prefix'] = '';
+ $vars['wrapper_suffix'] = '';
+ $vars['list_type_prefix'] = '<' . $handler->options['type'] . '>';
+ $vars['list_type_suffix'] = '</' . $handler->options['type'] . '>';
+ if ($vars['wrapper_class']) {
+ $vars['wrapper_prefix'] = '<div class="' . $vars['wrapper_class'] . '">';
+ $vars['wrapper_suffix'] = '</div>';
+ }
+
+ if ($vars['class']) {
+ $vars['list_type_prefix'] = '<' . $handler->options['type'] . ' class="' . $vars['class'] . '">';
+ }
+ template_preprocess_views_view_unformatted($vars);
+}
+
+/**
+ * Preprocess an RSS feed
+ */
+function template_preprocess_views_view_rss(&$vars) {
+ global $base_url;
+ global $language;
+
+ $view = &$vars['view'];
+ $options = &$vars['options'];
+ $items = &$vars['rows'];
+
+ $style = &$view->style_plugin;
+
+ // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
+ // We strip all HTML tags, but need to prevent double encoding from properly
+ // escaped source data (such as &amp becoming &amp;amp;).
+ $vars['description'] = check_plain(decode_entities(strip_tags($style->get_description())));
+
+ if ($view->display_handler->get_option('sitename_title')) {
+ $title = variable_get('site_name', 'Drupal');
+ if ($slogan = variable_get('site_slogan', '')) {
+ $title .= ' - ' . $slogan;
+ }
+ }
+ else {
+ $title = $view->get_title();
+ }
+ $vars['title'] = check_plain($title);
+
+ // Figure out which display which has a path we're using for this feed. If there isn't
+ // one, use the global $base_url
+ $link_display_id = $view->display_handler->get_link_display();
+ if ($link_display_id && !empty($view->display[$link_display_id])) {
+ $path = $view->display[$link_display_id]->handler->get_path();
+ }
+
+ if ($path) {
+ $path = $view->get_url(NULL, $path);
+ $url_options = array('absolute' => TRUE);
+ if (!empty($view->exposed_raw_input)) {
+ $url_options['query'] = $view->exposed_raw_input;
+ }
+
+ // Compare the link to the default home page; if it's the default home page, just use $base_url.
+ if ($path == variable_get('site_frontpage', 'node')) {
+ $path = '';
+ }
+
+ $vars['link'] = check_url(url($path, $url_options));
+ }
+
+ $vars['langcode'] = check_plain($language->language);
+ $vars['namespaces'] = drupal_attributes($style->namespaces);
+ $vars['items'] = $items;
+ $vars['channel_elements'] = format_xml_elements($style->channel_elements);
+
+ // During live preview we don't want to output the header since the contents
+ // of the feed are being displayed inside a normal HTML page.
+ if (empty($vars['view']->live_preview)) {
+ drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8');
+ }
+}
+
+/**
+ * Default theme function for all RSS rows.
+ */
+function template_preprocess_views_view_row_rss(&$vars) {
+ $view = &$vars['view'];
+ $options = &$vars['options'];
+ $item = &$vars['row'];
+
+ $vars['title'] = check_plain($item->title);
+ $vars['link'] = check_url($item->link);
+ $vars['description'] = check_plain($item->description);
+ $vars['item_elements'] = empty($item->elements) ? '' : format_xml_elements($item->elements);
+}
+
+/**
+ * Default theme function for all filter forms.
+ */
+function template_preprocess_views_exposed_form(&$vars) {
+ $form = &$vars['form'];
+
+ // Put all single checkboxes together in the last spot.
+ $checkboxes = '';
+
+ if (!empty($form['q'])) {
+ $vars['q'] = drupal_render($form['q']);
+ }
+
+ $vars['widgets'] = array();
+ foreach ($form['#info'] as $id => $info) {
+ // Set aside checkboxes.
+ if (isset($form[$info['value']]['#type']) && $form[$info['value']]['#type'] == 'checkbox') {
+ $checkboxes .= drupal_render($form[$info['value']]);
+ continue;
+ }
+ $widget = new stdClass;
+ // set up defaults so that there's always something there.
+ $widget->label = $widget->operator = $widget->widget = $widget->description = NULL;
+
+ $widget->id = isset($form[$info['value']]['#id']) ? $form[$info['value']]['#id'] : '';
+
+ if (!empty($info['label'])) {
+ $widget->label = check_plain($info['label']);
+ }
+ if (!empty($info['operator'])) {
+ $widget->operator = drupal_render($form[$info['operator']]);
+ }
+
+ $widget->widget = drupal_render($form[$info['value']]);
+
+ if (!empty($info['description'])) {
+ $widget->description = check_plain($info['description']);
+ }
+
+ $vars['widgets'][$id] = $widget;
+ }
+
+ // Wrap up all the checkboxes we set aside into a widget.
+ if ($checkboxes) {
+ $widget = new stdClass;
+ // set up defaults so that there's always something there.
+ $widget->label = $widget->operator = $widget->widget = NULL;
+ $widget->id = 'checkboxes';
+ $widget->widget = $checkboxes;
+ $vars['widgets']['checkboxes'] = $widget;
+ }
+
+ if (isset($form['sort_by'])) {
+ $vars['sort_by'] = drupal_render($form['sort_by']);
+ $vars['sort_order'] = drupal_render($form['sort_order']);
+ }
+ if (isset($form['items_per_page'])) {
+ $vars['items_per_page'] = drupal_render($form['items_per_page']);
+ }
+ if (isset($form['offset'])) {
+ $vars['offset'] = drupal_render($form['offset']);
+ }
+ if (isset($form['reset'])) {
+ $vars['reset_button'] = drupal_render($form['reset']);
+ }
+ // This includes the submit button.
+ $vars['button'] = drupal_render_children($form);
+}
+
+/**
+ * Theme function for a View with form elements: replace the placeholders.
+ */
+function theme_views_form_views_form($variables) {
+ $form = $variables['form'];
+
+ // Placeholders and their substitutions (usually rendered form elements).
+ $search = array();
+ $replace = array();
+
+ // Add in substitutions provided by the form.
+ foreach ($form['#substitutions']['#value'] as $substitution) {
+ $field_name = $substitution['field_name'];
+ $row_id = $substitution['row_id'];
+
+ $search[] = $substitution['placeholder'];
+ $replace[] = isset($form[$field_name][$row_id]) ? drupal_render($form[$field_name][$row_id]) : '';
+ }
+ // Add in substitutions from hook_views_form_substitutions().
+ $substitutions = module_invoke_all('views_form_substitutions');
+ foreach ($substitutions as $placeholder => $substitution) {
+ $search[] = $placeholder;
+ $replace[] = $substitution;
+ }
+
+ // Apply substitutions to the rendered output.
+ $form['output']['#markup'] = str_replace($search, $replace, $form['output']['#markup']);
+
+ // Render and add remaining form fields.
+ return drupal_render_children($form);
+}
+
+function theme_views_mini_pager($vars) {
+ global $pager_page_array, $pager_total;
+
+ $tags = $vars['tags'];
+ $element = $vars['element'];
+ $parameters = $vars['parameters'];
+
+ // current is the page we are currently paged to
+ $pager_current = $pager_page_array[$element] + 1;
+ // max is the maximum page number
+ $pager_max = $pager_total[$element];
+ // End of marker calculations.
+
+ if ($pager_total[$element] > 1) {
+
+ $li_previous = theme('pager_previous',
+ array(
+ 'text' => (isset($tags[1]) ? $tags[1] : t('‹‹')),
+ 'element' => $element,
+ 'interval' => 1,
+ 'parameters' => $parameters,
+ )
+ );
+ if (empty($li_previous)) {
+ $li_previous = "&nbsp;";
+ }
+
+ $li_next = theme('pager_next',
+ array(
+ 'text' => (isset($tags[3]) ? $tags[3] : t('››')),
+ 'element' => $element,
+ 'interval' => 1,
+ 'parameters' => $parameters,
+ )
+ );
+
+ if (empty($li_next)) {
+ $li_next = "&nbsp;";
+ }
+
+ $items[] = array(
+ 'class' => array('pager-previous'),
+ 'data' => $li_previous,
+ );
+
+ $items[] = array(
+ 'class' => array('pager-current'),
+ 'data' => t('@current of @max', array('@current' => $pager_current, '@max' => $pager_max)),
+ );
+
+ $items[] = array(
+ 'class' => array('pager-next'),
+ 'data' => $li_next,
+ );
+ return theme('item_list',
+ array(
+ 'items' => $items,
+ 'title' => NULL,
+ 'type' => 'ul',
+ 'attributes' => array('class' => array('pager')),
+ )
+ );
+ }
+}
+
+/**
+ * Generic <div> container function.
+ */
+function theme_views_container($variables) {
+ $element = $variables['element'];
+ return '<div' . drupal_attributes($element['#attributes']) . '>' . $element['#children'] . '</div>';
+}
+
+/**
+ * @defgroup views_templates Views template files
+ * @{
+ * All views templates can be overridden with a variety of names, using
+ * the view, the display ID of the view, the display type of the view,
+ * or some combination thereof.
+ *
+ * For each view, there will be a minimum of two templates used. The first
+ * is used for all views: views-view.tpl.php.
+ *
+ * The second template is determined by the style selected for the view. Note
+ * that certain aspects of the view can also change which style is used; for
+ * example, arguments which provide a summary view might change the style to
+ * one of the special summary styles.
+ *
+ * The default style for all views is views-view-unformatted.tpl.php
+ *
+ * Many styles will then farm out the actual display of each row to a row
+ * style; the default row style is views-view-fields.tpl.php.
+ *
+ * Here is an example of all the templates that will be tried in the following
+ * case:
+ *
+ * View, named foobar. Style: unformatted. Row style: Fields. Display: Page.
+ *
+ * - views-view--foobar--page.tpl.php
+ * - views-view--page.tpl.php
+ * - views-view--foobar.tpl.php
+ * - views-view.tpl.php
+ *
+ * - views-view-unformatted--foobar--page.tpl.php
+ * - views-view-unformatted--page.tpl.php
+ * - views-view-unformatted--foobar.tpl.php
+ * - views-view-unformatted.tpl.php
+ *
+ * - views-view-fields--foobar--page.tpl.php
+ * - views-view-fields--page.tpl.php
+ * - views-view-fields--foobar.tpl.php
+ * - views-view-fields.tpl.php
+ *
+ * Important! When adding a new template to your theme, be sure to flush the
+ * theme registry cache!
+ *
+ * @see _views_theme_functions()
+ * @}
+ */
diff --git a/sites/all/modules/views/theme/views-exposed-form.tpl.php b/sites/all/modules/views/theme/views-exposed-form.tpl.php
new file mode 100644
index 000000000..bdd570c22
--- /dev/null
+++ b/sites/all/modules/views/theme/views-exposed-form.tpl.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * This template handles the layout of the views exposed filter form.
+ *
+ * Variables available:
+ * - $widgets: An array of exposed form widgets. Each widget contains:
+ * - $widget->label: The visible label to print. May be optional.
+ * - $widget->operator: The operator for the widget. May be optional.
+ * - $widget->widget: The widget itself.
+ * - $sort_by: The select box to sort the view using an exposed form.
+ * - $sort_order: The select box with the ASC, DESC options to define order. May be optional.
+ * - $items_per_page: The select box with the available items per page. May be optional.
+ * - $offset: A textfield to define the offset of the view. May be optional.
+ * - $reset_button: A button to reset the exposed filter applied. May be optional.
+ * - $button: The submit button for the form.
+ *
+ * @ingroup views_templates
+ */
+?>
+<?php if (!empty($q)): ?>
+ <?php
+ // This ensures that, if clean URLs are off, the 'q' is added first so that
+ // it shows up first in the URL.
+ print $q;
+ ?>
+<?php endif; ?>
+<div class="views-exposed-form">
+ <div class="views-exposed-widgets clearfix">
+ <?php foreach ($widgets as $id => $widget): ?>
+ <div id="<?php print $widget->id; ?>-wrapper" class="views-exposed-widget views-widget-<?php print $id; ?>">
+ <?php if (!empty($widget->label)): ?>
+ <label for="<?php print $widget->id; ?>">
+ <?php print $widget->label; ?>
+ </label>
+ <?php endif; ?>
+ <?php if (!empty($widget->operator)): ?>
+ <div class="views-operator">
+ <?php print $widget->operator; ?>
+ </div>
+ <?php endif; ?>
+ <div class="views-widget">
+ <?php print $widget->widget; ?>
+ </div>
+ <?php if (!empty($widget->description)): ?>
+ <div class="description">
+ <?php print $widget->description; ?>
+ </div>
+ <?php endif; ?>
+ </div>
+ <?php endforeach; ?>
+ <?php if (!empty($sort_by)): ?>
+ <div class="views-exposed-widget views-widget-sort-by">
+ <?php print $sort_by; ?>
+ </div>
+ <div class="views-exposed-widget views-widget-sort-order">
+ <?php print $sort_order; ?>
+ </div>
+ <?php endif; ?>
+ <?php if (!empty($items_per_page)): ?>
+ <div class="views-exposed-widget views-widget-per-page">
+ <?php print $items_per_page; ?>
+ </div>
+ <?php endif; ?>
+ <?php if (!empty($offset)): ?>
+ <div class="views-exposed-widget views-widget-offset">
+ <?php print $offset; ?>
+ </div>
+ <?php endif; ?>
+ <div class="views-exposed-widget views-submit-button">
+ <?php print $button; ?>
+ </div>
+ <?php if (!empty($reset_button)): ?>
+ <div class="views-exposed-widget views-reset-button">
+ <?php print $reset_button; ?>
+ </div>
+ <?php endif; ?>
+ </div>
+</div>
diff --git a/sites/all/modules/views/theme/views-more.tpl.php b/sites/all/modules/views/theme/views-more.tpl.php
new file mode 100644
index 000000000..0b7080bc5
--- /dev/null
+++ b/sites/all/modules/views/theme/views-more.tpl.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Theme the more link.
+ *
+ * - $view: The view object.
+ * - $more_url: the url for the more link.
+ * - $link_text: the text for the more link.
+ *
+ * @ingroup views_templates
+ */
+?>
+
+<div class="more-link">
+ <a href="<?php print $more_url ?>">
+ <?php print $link_text; ?>
+ </a>
+</div>
diff --git a/sites/all/modules/views/theme/views-ui-display-tab-bucket.tpl.php b/sites/all/modules/views/theme/views-ui-display-tab-bucket.tpl.php
new file mode 100644
index 000000000..6d51a1d4f
--- /dev/null
+++ b/sites/all/modules/views/theme/views-ui-display-tab-bucket.tpl.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Template for each "box" on the display query edit screen.
+ */
+?>
+<div class="<?php print $classes; ?>" <?php print $attributes; ?>>
+ <?php print $item_help_icon; ?>
+ <?php if(!empty($actions)) : ?>
+ <?php print $actions; ?>
+ <?php endif; ?>
+ <?php if (!empty($title)) : ?>
+ <h3><?php print $title; ?></h3>
+ <?php endif; ?>
+ <?php print $content; ?>
+</div>
diff --git a/sites/all/modules/views/theme/views-ui-display-tab-setting.tpl.php b/sites/all/modules/views/theme/views-ui-display-tab-setting.tpl.php
new file mode 100644
index 000000000..3d13e12fd
--- /dev/null
+++ b/sites/all/modules/views/theme/views-ui-display-tab-setting.tpl.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Template for each row inside the "boxes" on the display query edit screen.
+ */
+?>
+<div class="views-display-setting <?php print $classes; ?> <?php print $zebra; ?> clearfix" <?php print $attributes; ?>>
+ <?php if ($description): ?>
+ <span class="label"><?php print $description; ?></span>
+ <?php endif; ?>
+ <?php if ($settings_links): ?>
+ <?php print $settings_links; ?>
+ <?php endif; ?>
+</div>
diff --git a/sites/all/modules/views/theme/views-ui-edit-item.tpl.php b/sites/all/modules/views/theme/views-ui-edit-item.tpl.php
new file mode 100644
index 000000000..bf90ec172
--- /dev/null
+++ b/sites/all/modules/views/theme/views-ui-edit-item.tpl.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * This template handles the printing of fields/filters/sort criteria/arguments or relationships.
+ */
+?>
+<?php print $rearrange; ?>
+<?php print $add; ?>
+<div class="views-category-title<?php
+ if ($overridden) {
+ print ' overridden';
+ }
+ if ($defaulted) {
+ print ' defaulted';
+ }
+ ?>">
+ <?php print $item_help_icon; ?>
+ <?php print $title; ?>
+</div>
+
+<div class="views-category-content<?php
+ if ($overridden) {
+ print ' overridden';
+ }
+ if ($defaulted) {
+ print ' defaulted';
+ }
+ ?>">
+ <?php if (!empty($no_fields)): ?>
+ <div><?php print t('The style selected does not utilize fields.'); ?></div>
+ <?php elseif (empty($fields)): ?>
+ <div><?php print t('None defined'); ?></div>
+ <?php else: ?>
+ <?php foreach ($fields as $pid => $field): ?>
+ <?php if (!empty($field['links'])): ?>
+ <?php print $field['links']; ?>
+ <?php endif; ?>
+ <div class="<?php print $field['class']; if (!empty($field['changed'])) { print ' changed'; } ?>">
+ <?php print $field['title']; ?>
+ <?php print $field['info']; ?>
+ </div>
+ <?php endforeach; ?>
+ <?php endif; ?>
+</div>
diff --git a/sites/all/modules/views/theme/views-ui-edit-view.tpl.php b/sites/all/modules/views/theme/views-ui-edit-view.tpl.php
new file mode 100644
index 000000000..5c3732ca5
--- /dev/null
+++ b/sites/all/modules/views/theme/views-ui-edit-view.tpl.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Template for the primary view editing window.
+ */
+?>
+<div class="views-edit-view">
+ <?php if ($locked): ?>
+ <div class="view-locked">
+ <?php print t('This view is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => $locked, '!age' => $lock_age, '!break' => $break)); ?>
+ </div>
+ <?php endif; ?>
+ <div class="views-basic-info clearfix<?php if (!empty($view->changed)) { print " changed"; }?>">
+ <?php if (!is_numeric($view->vid)): ?>
+ <div class="view-changed view-new"><?php print t('New view'); ?></div>
+ <?php else: ?>
+ <div class="view-changed"><?php print t('Changed view'); ?></div>
+ <?php endif; ?>
+ <div class="views-quick-links">
+ <?php print $quick_links ?>
+ </div>
+ <?php print t('View %name, displaying items of type <strong>@base</strong>.',
+ array('%name' => $view->name, '@base' => $base_table)); ?>
+ </div>
+
+ <?php print $tabs; ?>
+
+ <div id="views-ajax-form">
+ <div id="views-ajax-title">
+ <?php // This is initially empty ?>
+ </div>
+ <div id="views-ajax-pad">
+ <?php /* This is sent in because it is also sent out through settings and
+ needs to be consistent. */ ?>
+ <?php print $message; ?>
+ </div>
+ </div>
+
+ <?php print $save_button ?>
+
+ <h2><?php print t('Live preview'); ?></h2>
+ <div id='views-live-preview'>
+ <?php print $preview ?>
+ </div>
+</div>
diff --git a/sites/all/modules/views/theme/views-view-field.tpl.php b/sites/all/modules/views/theme/views-view-field.tpl.php
new file mode 100644
index 000000000..91d92eee9
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-field.tpl.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * This template is used to print a single field in a view.
+ *
+ * It is not actually used in default Views, as this is registered as a theme
+ * function which has better performance. For single overrides, the template is
+ * perfectly okay.
+ *
+ * Variables available:
+ * - $view: The view object
+ * - $field: The field handler object that can process the input
+ * - $row: The raw SQL result that can be used
+ * - $output: The processed output that will normally be used.
+ *
+ * When fetching output from the $row, this construct should be used:
+ * $data = $row->{$field->field_alias}
+ *
+ * The above will guarantee that you'll always get the correct data,
+ * regardless of any changes in the aliasing that might happen if
+ * the view is modified.
+ */
+?>
+<?php print $output; ?>
diff --git a/sites/all/modules/views/theme/views-view-fields.tpl.php b/sites/all/modules/views/theme/views-view-fields.tpl.php
new file mode 100644
index 000000000..ae3a4c68c
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-fields.tpl.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Default simple view template to all the fields as a row.
+ *
+ * - $view: The view in use.
+ * - $fields: an array of $field objects. Each one contains:
+ * - $field->content: The output of the field.
+ * - $field->raw: The raw data for the field, if it exists. This is NOT output safe.
+ * - $field->class: The safe class id to use.
+ * - $field->handler: The Views field handler object controlling this field. Do not use
+ * var_export to dump this object, as it can't handle the recursion.
+ * - $field->inline: Whether or not the field should be inline.
+ * - $field->inline_html: either div or span based on the above flag.
+ * - $field->wrapper_prefix: A complete wrapper containing the inline_html to use.
+ * - $field->wrapper_suffix: The closing tag for the wrapper.
+ * - $field->separator: an optional separator that may appear before a field.
+ * - $field->label: The wrap label text to use.
+ * - $field->label_html: The full HTML of the label to use including
+ * configured element type.
+ * - $row: The raw result object from the query, with all data it fetched.
+ *
+ * @ingroup views_templates
+ */
+?>
+<?php foreach ($fields as $id => $field): ?>
+ <?php if (!empty($field->separator)): ?>
+ <?php print $field->separator; ?>
+ <?php endif; ?>
+
+ <?php print $field->wrapper_prefix; ?>
+ <?php print $field->label_html; ?>
+ <?php print $field->content; ?>
+ <?php print $field->wrapper_suffix; ?>
+<?php endforeach; ?>
diff --git a/sites/all/modules/views/theme/views-view-grid.tpl.php b/sites/all/modules/views/theme/views-view-grid.tpl.php
new file mode 100644
index 000000000..09f807a7c
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-grid.tpl.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Default simple view template to display a rows in a grid.
+ *
+ * - $rows contains a nested array of rows. Each row contains an array of
+ * columns.
+ *
+ * @ingroup views_templates
+ */
+?>
+<?php if (!empty($title)) : ?>
+ <h3><?php print $title; ?></h3>
+<?php endif; ?>
+<table class="<?php print $class; ?>"<?php print $attributes; ?>>
+ <?php if (!empty($caption)) : ?>
+ <caption><?php print $caption; ?></caption>
+ <?php endif; ?>
+
+ <tbody>
+ <?php foreach ($rows as $row_number => $columns): ?>
+ <tr <?php if ($row_classes[$row_number]) { print 'class="' . $row_classes[$row_number] .'"'; } ?>>
+ <?php foreach ($columns as $column_number => $item): ?>
+ <td <?php if ($column_classes[$row_number][$column_number]) { print 'class="' . $column_classes[$row_number][$column_number] .'"'; } ?>>
+ <?php print $item; ?>
+ </td>
+ <?php endforeach; ?>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+</table>
diff --git a/sites/all/modules/views/theme/views-view-grouping.tpl.php b/sites/all/modules/views/theme/views-view-grouping.tpl.php
new file mode 100644
index 000000000..ebf7bc2e3
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-grouping.tpl.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * This template is used to print a single grouping in a view.
+ *
+ * It is not actually used in default Views, as this is registered as a theme
+ * function which has better performance. For single overrides, the template is
+ * perfectly okay.
+ *
+ * Variables available:
+ * - $view: The view object
+ * - $grouping: The grouping instruction.
+ * - $grouping_level: Integer indicating the hierarchical level of the grouping.
+ * - $rows: The rows contained in this grouping.
+ * - $title: The title of this grouping.
+ * - $content: The processed content output that will normally be used.
+ */
+?>
+<div class="view-grouping">
+ <div class="view-grouping-header"><?php print $title; ?></div>
+ <div class="view-grouping-content">
+ <?php print $content; ?>
+ </div>
+</div>
diff --git a/sites/all/modules/views/theme/views-view-list.tpl.php b/sites/all/modules/views/theme/views-view-list.tpl.php
new file mode 100644
index 000000000..601279ae6
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-list.tpl.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Default simple view template to display a list of rows.
+ *
+ * - $title : The title of this group of rows. May be empty.
+ * - $options['type'] will either be ul or ol.
+ * @ingroup views_templates
+ */
+?>
+<?php print $wrapper_prefix; ?>
+ <?php if (!empty($title)) : ?>
+ <h3><?php print $title; ?></h3>
+ <?php endif; ?>
+ <?php print $list_type_prefix; ?>
+ <?php foreach ($rows as $id => $row): ?>
+ <li class="<?php print $classes_array[$id]; ?>"><?php print $row; ?></li>
+ <?php endforeach; ?>
+ <?php print $list_type_suffix; ?>
+<?php print $wrapper_suffix; ?>
diff --git a/sites/all/modules/views/theme/views-view-row-comment.tpl.php b/sites/all/modules/views/theme/views-view-row-comment.tpl.php
new file mode 100644
index 000000000..7fe2e81fe
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-row-comment.tpl.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Default simple view template to display a single comment.
+ *
+ * Rather than doing anything with this particular template, it is more
+ * efficient to use a variant of the comment.tpl.php based upon the view,
+ * which will be named comment-view-VIEWNAME.tpl.php. This isn't actually
+ * a views template, which is why it's not used here, but is a template
+ * 'suggestion' given to the comment template, and is used exactly
+ * the same as any other variant of the comment template file, such as
+ * node-nodeTYPE.tpl.php
+ *
+ * @ingroup views_templates
+ */
+?>
+<?php print $comment; ?>
diff --git a/sites/all/modules/views/theme/views-view-row-rss.tpl.php b/sites/all/modules/views/theme/views-view-row-rss.tpl.php
new file mode 100644
index 000000000..01e0696dc
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-row-rss.tpl.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Default view template to display a item in an RSS feed.
+ *
+ * @ingroup views_templates
+ */
+?>
+ <item>
+ <title><?php print $title; ?></title>
+ <link><?php print $link; ?></link>
+ <description><?php print $description; ?></description>
+ <?php print $item_elements; ?>
+ </item>
diff --git a/sites/all/modules/views/theme/views-view-rss.tpl.php b/sites/all/modules/views/theme/views-view-rss.tpl.php
new file mode 100644
index 000000000..18ca73eaf
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-rss.tpl.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Default template for feed displays that use the RSS style.
+ *
+ * @ingroup views_templates
+ */
+?>
+<?php print "<?xml"; ?> version="1.0" encoding="utf-8" <?php print "?>"; ?>
+<rss version="2.0" xml:base="<?php print $link; ?>"<?php print $namespaces; ?>>
+ <channel>
+ <title><?php print $title; ?></title>
+ <link><?php print $link; ?></link>
+ <description><?php print $description; ?></description>
+ <language><?php print $langcode; ?></language>
+ <?php print $channel_elements; ?>
+ <?php print $items; ?>
+ </channel>
+</rss>
diff --git a/sites/all/modules/views/theme/views-view-summary-unformatted.tpl.php b/sites/all/modules/views/theme/views-view-summary-unformatted.tpl.php
new file mode 100644
index 000000000..306d76fef
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-summary-unformatted.tpl.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Default simple view template to display a group of summary lines.
+ *
+ * This wraps items in a span if set to inline, or a div if not.
+ *
+ * @ingroup views_templates
+ */
+?>
+<?php foreach ($rows as $id => $row): ?>
+ <?php print (!empty($options['inline']) ? '<span' : '<div') . ' class="views-summary views-summary-unformatted">'; ?>
+ <?php if (!empty($row->separator)) { print $row->separator; } ?>
+ <a href="<?php print $row->url; ?>"<?php print !empty($row_classes[$id]) ? ' class="' . $row_classes[$id] . '"' : ''; ?>><?php print $row->link; ?></a>
+ <?php if (!empty($options['count'])): ?>
+ (<?php print $row->count; ?>)
+ <?php endif; ?>
+ <?php print !empty($options['inline']) ? '</span>' : '</div>'; ?>
+<?php endforeach; ?>
diff --git a/sites/all/modules/views/theme/views-view-summary.tpl.php b/sites/all/modules/views/theme/views-view-summary.tpl.php
new file mode 100644
index 000000000..22969eb48
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-summary.tpl.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Default simple view template to display a list of summary lines.
+ *
+ * @ingroup views_templates
+ */
+?>
+<div class="item-list">
+ <ul class="views-summary">
+ <?php foreach ($rows as $id => $row): ?>
+ <li><a href="<?php print $row->url; ?>"<?php print !empty($row_classes[$id]) ? ' class="'. $row_classes[$id] .'"' : ''; ?>><?php print $row->link; ?></a>
+ <?php if (!empty($options['count'])): ?>
+ (<?php print $row->count?>)
+ <?php endif; ?>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+</div>
diff --git a/sites/all/modules/views/theme/views-view-table.tpl.php b/sites/all/modules/views/theme/views-view-table.tpl.php
new file mode 100644
index 000000000..b3443fcc4
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-table.tpl.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Template to display a view as a table.
+ *
+ * - $title : The title of this group of rows. May be empty.
+ * - $header: An array of header labels keyed by field id.
+ * - $caption: The caption for this table. May be empty.
+ * - $header_classes: An array of header classes keyed by field id.
+ * - $fields: An array of CSS IDs to use for each field id.
+ * - $classes: A class or classes to apply to the table, based on settings.
+ * - $row_classes: An array of classes to apply to each row, indexed by row
+ * number. This matches the index in $rows.
+ * - $rows: An array of row items. Each row is an array of content.
+ * $rows are keyed by row number, fields within rows are keyed by field ID.
+ * - $field_classes: An array of classes to apply to each field, indexed by
+ * field id, then row number. This matches the index in $rows.
+ * @ingroup views_templates
+ */
+?>
+<table <?php if ($classes) { print 'class="'. $classes . '" '; } ?><?php print $attributes; ?>>
+ <?php if (!empty($title) || !empty($caption)) : ?>
+ <caption><?php print $caption . $title; ?></caption>
+ <?php endif; ?>
+ <?php if (!empty($header)) : ?>
+ <thead>
+ <tr>
+ <?php foreach ($header as $field => $label): ?>
+ <th <?php if ($header_classes[$field]) { print 'class="'. $header_classes[$field] . '" '; } ?> scope="col">
+ <?php print $label; ?>
+ </th>
+ <?php endforeach; ?>
+ </tr>
+ </thead>
+ <?php endif; ?>
+ <tbody>
+ <?php foreach ($rows as $row_count => $row): ?>
+ <tr <?php if ($row_classes[$row_count]) { print 'class="' . implode(' ', $row_classes[$row_count]) .'"'; } ?>>
+ <?php foreach ($row as $field => $content): ?>
+ <td <?php if ($field_classes[$field][$row_count]) { print 'class="'. $field_classes[$field][$row_count] . '" '; } ?><?php print drupal_attributes($field_attributes[$field][$row_count]); ?>>
+ <?php print $content; ?>
+ </td>
+ <?php endforeach; ?>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+</table>
diff --git a/sites/all/modules/views/theme/views-view-unformatted.tpl.php b/sites/all/modules/views/theme/views-view-unformatted.tpl.php
new file mode 100644
index 000000000..f1cccb888
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view-unformatted.tpl.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Default simple view template to display a list of rows.
+ *
+ * @ingroup views_templates
+ */
+?>
+<?php if (!empty($title)): ?>
+ <h3><?php print $title; ?></h3>
+<?php endif; ?>
+<?php foreach ($rows as $id => $row): ?>
+ <div<?php if ($classes_array[$id]) { print ' class="' . $classes_array[$id] .'"'; } ?>>
+ <?php print $row; ?>
+ </div>
+<?php endforeach; ?>
diff --git a/sites/all/modules/views/theme/views-view.tpl.php b/sites/all/modules/views/theme/views-view.tpl.php
new file mode 100644
index 000000000..579cf12a8
--- /dev/null
+++ b/sites/all/modules/views/theme/views-view.tpl.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Main view template.
+ *
+ * Variables available:
+ * - $classes_array: An array of classes determined in
+ * template_preprocess_views_view(). Default classes are:
+ * .view
+ * .view-[css_name]
+ * .view-id-[view_name]
+ * .view-display-id-[display_name]
+ * .view-dom-id-[dom_id]
+ * - $classes: A string version of $classes_array for use in the class attribute
+ * - $css_name: A css-safe version of the view name.
+ * - $css_class: The user-specified classes names, if any
+ * - $header: The view header
+ * - $footer: The view footer
+ * - $rows: The results of the view query, if any
+ * - $empty: The empty text to display if the view is empty
+ * - $pager: The pager next/prev links to display, if any
+ * - $exposed: Exposed widget form/info to display
+ * - $feed_icon: Feed icon to display, if any
+ * - $more: A link to view more, if any
+ *
+ * @ingroup views_templates
+ */
+?>
+<div class="<?php print $classes; ?>">
+ <?php print render($title_prefix); ?>
+ <?php if ($title): ?>
+ <?php print $title; ?>
+ <?php endif; ?>
+ <?php print render($title_suffix); ?>
+ <?php if ($header): ?>
+ <div class="view-header">
+ <?php print $header; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($exposed): ?>
+ <div class="view-filters">
+ <?php print $exposed; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($attachment_before): ?>
+ <div class="attachment attachment-before">
+ <?php print $attachment_before; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($rows): ?>
+ <div class="view-content">
+ <?php print $rows; ?>
+ </div>
+ <?php elseif ($empty): ?>
+ <div class="view-empty">
+ <?php print $empty; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($pager): ?>
+ <?php print $pager; ?>
+ <?php endif; ?>
+
+ <?php if ($attachment_after): ?>
+ <div class="attachment attachment-after">
+ <?php print $attachment_after; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($more): ?>
+ <?php print $more; ?>
+ <?php endif; ?>
+
+ <?php if ($footer): ?>
+ <div class="view-footer">
+ <?php print $footer; ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($feed_icon): ?>
+ <div class="feed-icon">
+ <?php print $feed_icon; ?>
+ </div>
+ <?php endif; ?>
+
+</div><?php /* class view */ ?>
diff --git a/sites/all/modules/views/views.api.php b/sites/all/modules/views/views.api.php
new file mode 100644
index 000000000..16d1f6739
--- /dev/null
+++ b/sites/all/modules/views/views.api.php
@@ -0,0 +1,1183 @@
+<?php
+
+/**
+ * @file
+ * Describe hooks provided by the Views module.
+ */
+
+/**
+ * @mainpage Views 3 API Manual
+ *
+ * Much of this information is actually stored in the advanced help; please
+ * check the API topic. This help will primarily be aimed at documenting
+ * classes and function calls.
+ *
+ * Topics:
+ * - @link views_lifetime The life of a view @endlink
+ * - @link views_hooks Views hooks @endlink
+ * - @link views_handlers About Views handlers @endlink
+ * - @link views_plugins About Views plugins @endlink
+ * - @link views_templates Views template files @endlink
+ * - @link views_module_handlers Views module handlers @endlink
+ */
+
+/**
+ * @defgroup views_lifetime The life of a view
+ * @{
+ * This page explains the basic cycle of a view and what processes happen.
+ *
+ * @todo.
+ * @}
+ */
+
+/**
+ * @defgroup views_handlers About Views handlers
+ * @{
+ * In Views, a handler is an object that is part of the view and is part of the
+ * query building flow.
+ *
+ * Handlers are objects; much of the time, the base handlers will work, but
+ * often you'll need to override the handler to achieve something meaningful.
+ * One typical handler override will be views_handler_filter_operator_in which
+ * allows you to have a filter select from a list of options; you'll need to
+ * override this to provide your list.
+ *
+ * Handlers have two distinct code flows; the UI flow and the view building
+ * flow.
+ *
+ * For the query flow:
+ * - handler->construct()
+ * - Create the initial handler; at this time it is not yet attached to a
+ * view. It is here that you can set basic defaults if needed, but there
+ * will be no knowledge of the environment yet.
+ * - handler->set_definition()
+ * - Set the data from hook_views_data() relevant to the handler.
+ * - handler->init()
+ * - Attach the handler to a view, and usually provides the options from the
+ * display.
+ * - handler->pre_query()
+ * - Run prior to the query() stage to do early processing.
+ * - handler->query()
+ * - Do the bulk of the work this handler needs to do to add itself to the
+ * query.
+ *
+ * Fields, being the only handlers concerned with output, also have an extended
+ * piece of the flow:
+ *
+ * - handler->pre_render(&$values)
+ * - Called prior to the actual rendering, this allows handlers to query for
+ * extra data; the entire resultset is available here, and this is where
+ * items that have "multiple values" per record can do their extra query for
+ * all of the records available. There are several examples of this at work
+ * in the code, see for example views_handler_field_user_roles.
+ * - handler->render()
+ * - This does the actual work of rendering the field.
+ *
+ * Most handlers are just extensions of existing classes with a few tweaks that
+ * are specific to the field in question. For example,
+ * views_handler_filter_in_operator provides a simple mechanism to set a
+ * multiple-value list for setting filter values. Below,
+ * views_handler_filter_node_type overrides the list options, but inherits
+ * everything else.
+ *
+ * @code
+ * class views_handler_filter_node_type extends views_handler_filter_in_operator {
+ * function get_value_options() {
+ * if (!isset($this->value_options)) {
+ * $this->value_title = t('Node type');
+ * $types = node_get_types();
+ * foreach ($types as $type => $info) {
+ * $options[$type] = $info-&gt;name;
+ * }
+ * $this->value_options = $options;
+ * }
+ * }
+ * }
+ * @endcode
+ *
+ * Handlers are stored in their own files and loaded on demand. Like all other
+ * module files, they must first be registered through the module's info file.
+ * For example:
+ *
+ * @code
+ * name = Example module
+ * description = "Gives an example of a module."
+ * core = 7.x
+ * files[] = example.module
+ * files[] = example.install
+ *
+ * ; Views handlers
+ * files[] = includes/views/handlers/example_handler_argument_string.inc
+ * @endcode
+ *
+ * The best place to learn more about handlers and how they work is to explore
+ * @link views_handlers Views' handlers @endlink and use existing handlers as a
+ * guide and a model. Understanding how views_handler and its child classes work
+ * is handy but you can do a lot just following these models. You can also
+ * explore the views module directory, particularly node.views.inc.
+ *
+ * Please note that while all handler names in views are prefixed with views_,
+ * you should use your own module's name to prefix your handler names in order
+ * to ensure namespace safety. Note that the basic pattern for handler naming
+ * goes like this:
+ *
+ * [module]_handler_[type]_[tablename]_[fieldname].
+ *
+ * Sometimes table and fieldname are not appropriate, but something that
+ * resembles what the table/field would be can be used.
+ *
+ * See also:
+ * - @link views_field_handlers Views field handlers @endlink
+ * - @link views_sort_handlers Views sort handlers @endlink
+ * - @link views_filter_handlers Views filter handlers @endlink
+ * - @link views_argument_handlers Views argument handlers @endlink
+ * - @link views_relationship_handlers Views relationship handlers @endlink
+ * - @link views_area_handlers Views area handlers @endlink
+ * @}
+ */
+
+/**
+ * @defgroup views_plugins About Views plugins
+ *
+ * In Views, a plugin is a bit like a handler, but plugins are not directly
+ * responsible for building the query. Instead, they are objects that are used
+ * to display the view or make other modifications.
+ *
+ * There are several types of plugins in Views:
+ * - Display: Display plugins are responsible for controlling *where* a view
+ * lives; that is, how they are being exposed to other parts of Drupal. Page
+ * and block are the most common displays, as well as the ubiquitous 'master'
+ * (or 'default') display.
+ * - Style: Style plugins control how a view is displayed. For the most part
+ * they are object wrappers around theme templates. Styles could for example
+ * be HTML lists or tables.
+ * - Row style: Row styles handle each individual record from the main view
+ * table. The two included by default render the entire entity (nodes only),
+ * or selected fields.
+ * - Argument default: Argument default plugins allow pluggable ways of
+ * providing default values for contextual filters (previously 'arguments').
+ * This is useful for blocks and other display types lacking a natural
+ * argument input. Examples are plugins to extract node and user IDs from the
+ * URL.
+ * - Argument validator: Validator plugins can ensure arguments are valid, and
+ * even do transformations on the arguments. They can also provide replacement
+ * patterns for the view title. For example, the 'content' validator
+ * verifies verifies that the argument value corresponds to a node, loads
+ * that node and provides the node title as a replacement pattern.
+ * - Access: Access plugins are responsible for controlling access to the view.
+ * Views includes plugins for checking user roles and individual permissions.
+ * - Query: Query plugins generate and execute a query, so they can be seen as
+ * a data backend. The default implementation is using SQL. There are
+ * contributed modules reading data from other sources, see for example the
+ * Views XML Backend module.
+ * - Cache: Cache plugins control the storage and loading of caches. Currently
+ * they can do both result and render caching, but maybe one day cache the
+ * generated query.
+ * - Pager plugins: Pager plugins take care of everything regarding pagers.
+ * From getting and setting the total amount of items to render the pager and
+ * setting the global pager arrays.
+ * - Exposed form plugins: Exposed form plugins are responsible for building,
+ * rendering and controlling exposed forms. They can expose new parts of the
+ * view to the user and more.
+ * - Localization plugins: Localization plugins take care how the view options
+ * are translated. There are example implementations for t(), 'no
+ * translation' and i18n.
+ * - Display extenders: Display extender plugins allow scaling of views options
+ * horizontally. This means that you can add options and do stuff on all
+ * views displays. One theoretical example is metatags for views.
+ *
+ * Plugins are registered by implementing hook_views_plugins() in your
+ * modulename.views.inc file and returning an array of data.
+ * For examples please look at views_views_plugins() in
+ * views/includes/plugins.inc as it has examples for all of them.
+ *
+ * Similar to handlers, make sure that you add your plugin files to the
+ * module.info file.
+ *
+ * The array defining plugins will look something like this:
+ * @code
+ * return array(
+ * 'display' => array(
+ * // ... list of display plugins,
+ * ),
+ * 'style' => array(
+ * // ... list of style plugins,
+ * ),
+ * 'row' => array(
+ * // ... list of row style plugins,
+ * ),
+ * 'argument default' => array(
+ * // ... list of argument default plugins,
+ * ),
+ * 'argument validator' => array(
+ * // ... list of argument validator plugins,
+ * ),
+ * 'access' => array(
+ * // ... list of access plugins,
+ * ),
+ * 'query' => array(
+ * // ... list of query plugins,
+ * ),,
+ * 'cache' => array(
+ * // ... list of cache plugins,
+ * ),,
+ * 'pager' => array(
+ * // ... list of pager plugins,
+ * ),,
+ * 'exposed_form' => array(
+ * // ... list of exposed_form plugins,
+ * ),,
+ * 'localization' => array(
+ * // ... list of localization plugins,
+ * ),
+ * 'display_extender' => array(
+ * // ... list of display extender plugins,
+ * ),
+ * );
+ * @endcode
+ *
+ * Each plugin will be registered with an identifier for the plugin, plus a
+ * fairly lengthy list of items that can define how and where the plugin is
+ * used. Here is an example of a row style plugin from Views core:
+ * @code
+ * 'node' => array(
+ * 'title' => t('Node'),
+ * 'help' => t('Display the node with standard node view.'),
+ * 'handler' => 'views_plugin_row_node_view',
+ * 'path' => drupal_get_path('module', 'views') . '/modules/node', // not necessary for most modules
+ * 'theme' => 'views_view_row_node',
+ * 'base' => array('node'), // only works with 'node' as base.
+ * 'uses options' => TRUE,
+ * 'type' => 'normal',
+ * ),
+ * @endcode
+ *
+ * Of particular interest is the *path* directive, which works a little
+ * differently from handler registration; each plugin must define its own path,
+ * rather than relying on a global info for the paths. For example:
+ * @code
+ * 'feed' => array(
+ * 'title' => t('Feed'),
+ * 'help' => t('Display the view as a feed, such as an RSS feed.'),
+ * 'handler' => 'views_plugin_display_feed',
+ * 'uses hook menu' => TRUE,
+ * 'use ajax' => FALSE,
+ * 'use pager' => FALSE,
+ * 'accept attachments' => FALSE,
+ * 'admin' => t('Feed'),
+ * 'help topic' => 'display-feed',
+ * ),
+ * @endcode
+ *
+ * Please be sure to prefix your plugin identifiers with your module name to
+ * ensure namespace safety; after all, two different modules could try to
+ * implement the 'grid2' plugin, and that would cause one plugin to completely
+ * fail.
+ *
+ * @todo Finish this document.
+ *
+ * See also:
+ * - @link views_display_plugins Views display plugins @endlink
+ * - @link views_style_plugins Views style plugins @endlink
+ * - @link views_row_plugins Views row plugins @endlink
+ */
+
+/**
+ * @defgroup views_hooks Views hooks
+ * @{
+ * Hooks that can be implemented by other modules in order to implement the
+ * Views API.
+ */
+
+/**
+ * Describes data tables (or the equivalent) to Views.
+ *
+ * This hook should be placed in MODULENAME.views.inc and it will be
+ * auto-loaded. MODULENAME.views.inc must be in the directory specified by the
+ * 'path' key returned by MODULENAME_views_api(), or the same directory as the
+ * .module file, if 'path' is unspecified.
+ *
+ * @return
+ * An associative array describing the data structure. Primary key is the
+ * name used internally by Views for the table(s) – usually the actual table
+ * name. The values for the key entries are described in detail below.
+ */
+function hook_views_data() {
+ // This example describes how to write hook_views_data() for the following
+ // table:
+ //
+ // CREATE TABLE example_table (
+ // nid INT(11) NOT NULL COMMENT 'Primary key; refers to {node}.nid.',
+ // plain_text_field VARCHAR(32) COMMENT 'Just a plain text field.',
+ // numeric_field INT(11) COMMENT 'Just a numeric field.',
+ // boolean_field INT(1) COMMENT 'Just an on/off field.',
+ // timestamp_field INT(8) COMMENT 'Just a timestamp field.',
+ // PRIMARY KEY(nid)
+ // );
+
+ // First, the entry $data['example_table']['table'] describes properties of
+ // the actual table – not its content.
+
+ // The 'group' index will be used as a prefix in the UI for any of this
+ // table's fields, sort criteria, etc. so it's easy to tell where they came
+ // from.
+ $data['example_table']['table']['group'] = t('Example table');
+
+ // Define this as a base table – a table that can be described in itself by
+ // views (and not just being brought in as a relationship). In reality this
+ // is not very useful for this table, as it isn't really a distinct object of
+ // its own, but it makes a good example.
+ $data['example_table']['table']['base'] = array(
+ 'field' => 'nid', // This is the identifier field for the view.
+ 'title' => t('Example table'),
+ 'help' => t('Example table contains example content and can be related to nodes.'),
+ 'weight' => -10,
+ );
+
+ // This table references the {node} table. The declaration below creates an
+ // 'implicit' relationship to the node table, so that when 'node' is the base
+ // table, the fields are automatically available.
+ $data['example_table']['table']['join'] = array(
+ // Index this array by the table name to which this table refers.
+ // 'left_field' is the primary key in the referenced table.
+ // 'field' is the foreign key in this table.
+ 'node' => array(
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ );
+
+ // Next, describe each of the individual fields in this table to Views. This
+ // is done by describing $data['example_table']['FIELD_NAME']. This part of
+ // the array may then have further entries:
+ // - title: The label for the table field, as presented in Views.
+ // - help: The description text for the table field.
+ // - relationship: A description of any relationship handler for the table
+ // field.
+ // - field: A description of any field handler for the table field.
+ // - sort: A description of any sort handler for the table field.
+ // - filter: A description of any filter handler for the table field.
+ // - argument: A description of any argument handler for the table field.
+ // - area: A description of any handler for adding content to header,
+ // footer or as no result behaviour.
+ //
+ // The handler descriptions are described with examples below.
+
+ // Node ID table field.
+ $data['example_table']['nid'] = array(
+ 'title' => t('Example content'),
+ 'help' => t('Some example content that references a node.'),
+ // Define a relationship to the {node} table, so example_table views can
+ // add a relationship to nodes. If you want to define a relationship the
+ // other direction, use hook_views_data_alter(), or use the 'implicit' join
+ // method described above.
+ 'relationship' => array(
+ 'base' => 'node', // The name of the table to join with.
+ 'base field' => 'nid', // The name of the field on the joined table.
+ // 'field' => 'nid' -- see hook_views_data_alter(); not needed here.
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Default label for the relationship'),
+ 'title' => t('Title shown when adding the relationship'),
+ 'help' => t('More information on this relationship'),
+ ),
+ );
+
+ // Example plain text field.
+ $data['example_table']['plain_text_field'] = array(
+ 'title' => t('Plain text field'),
+ 'help' => t('Just a plain text field.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field',
+ 'click sortable' => TRUE, // This is use by the table display plugin.
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_string',
+ ),
+ 'argument' => array(
+ 'handler' => 'views_handler_argument_string',
+ ),
+ );
+
+ // Example numeric text field.
+ $data['example_table']['numeric_field'] = array(
+ 'title' => t('Numeric field'),
+ 'help' => t('Just a numeric field.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_numeric',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_numeric',
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // Example boolean field.
+ $data['example_table']['boolean_field'] = array(
+ 'title' => t('Boolean field'),
+ 'help' => t('Just an on/off field.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_boolean',
+ 'click sortable' => TRUE,
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_boolean_operator',
+ // Note that you can override the field-wide label:
+ 'label' => t('Published'),
+ // This setting is used by the boolean filter handler, as possible option.
+ 'type' => 'yes-no',
+ // use boolean_field = 1 instead of boolean_field <> 0 in WHERE statement.
+ 'use equal' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort',
+ ),
+ );
+
+ // Example timestamp field.
+ $data['example_table']['timestamp_field'] = array(
+ 'title' => t('Timestamp field'),
+ 'help' => t('Just a timestamp field.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ return $data;
+}
+
+/**
+ * Alter table structure.
+ *
+ * You can add/edit/remove existing tables defined by hook_views_data().
+ *
+ * This hook should be placed in MODULENAME.views.inc and it will be
+ * auto-loaded. MODULENAME.views.inc must be in the directory specified by the
+ * 'path' key returned by MODULENAME_views_api(), or the same directory as the
+ * .module file, if 'path' is unspecified.
+ *
+ * @param $data
+ * An array of all Views data, passed by reference. See hook_views_data() for
+ * structure.
+ *
+ * @see hook_views_data()
+ */
+function hook_views_data_alter(&$data) {
+ // This example alters the title of the node:nid field in the Views UI.
+ $data['node']['nid']['title'] = t('Node-Nid');
+
+ // This example adds an example field to the users table.
+ $data['users']['example_field'] = array(
+ 'title' => t('Example field'),
+ 'help' => t('Some example content that references a user'),
+ 'field' => array(
+ 'handler' => 'modulename_handler_field_example_field',
+ ),
+ );
+
+ // This example changes the handler of the node title field.
+ // In this handler you could do stuff, like preview of the node when clicking
+ // the node title.
+ $data['node']['title']['field']['handler'] = 'modulename_handler_field_node_title';
+
+ // This example adds a relationship to table {foo}, so that 'foo' views can
+ // add this table using a relationship. Because we don't want to write over
+ // the primary key field definition for the {foo}.fid field, we use a dummy
+ // field name as the key.
+ $data['foo']['dummy_name'] = array(
+ 'title' => t('Example relationship'),
+ 'help' => t('Example help'),
+ 'relationship' => array(
+ 'base' => 'example_table', // Table we're joining to.
+ 'base field' => 'eid', // Field on the joined table.
+ 'field' => 'fid', // Real field name on the 'foo' table.
+ 'handler' => 'views_handler_relationship',
+ 'label' => t('Default label for relationship'),
+ 'title' => t('Title seen when adding relationship'),
+ 'help' => t('More information about relationship.'),
+ ),
+ );
+
+ // Note that the $data array is not returned – it is modified by reference.
+}
+
+/**
+ * Override the default data for a Field API field.
+ *
+ * Field module's implementation of hook_views_data() invokes this for each
+ * field in the module that defines the field type (as declared in the field
+ * array). It is not invoked in other modules.
+ *
+ * If no hook implementation exists, hook_views_data() falls back to
+ * field_views_field_default_views_data().
+ *
+ * @see field_views_data()
+ * @see hook_field_views_data_alter()
+ * @see hook_field_views_data_views_data_alter()
+ *
+ * @param $field
+ * A field definition array, as returned by field_info_fields().
+ *
+ * @return
+ * An array of views data, in the same format as the return value of
+ * hook_views_data().
+ */
+function hook_field_views_data($field) {
+
+}
+
+/**
+ * Alter the views data for a single Field API field.
+ *
+ * This is called even if there is no hook_field_views_data() implementation for
+ * the field, and therefore may be used to alter the default data that
+ * field_views_field_default_views_data() supplies for the field.
+ *
+ * @param $result
+ * An array of views table data provided for a single field. This has the same
+ * format as the return value of hook_views_data().
+ * @param $field
+ * A field definition array, as returned by field_info_fields().
+ * @param $module
+ * The module that defines the field type.
+ *
+ * @see field_views_data()
+ * @see hook_field_views_data()
+ * @see hook_field_views_data_views_data_alter()
+ */
+function hook_field_views_data_alter(&$result, $field, $module) {
+
+}
+
+/**
+ * Alter the views data on a per field basis.
+ *
+ * Field module's implementation of hook_views_data_alter() invokes this for
+ * each field in the module that defines the field type (as declared in the
+ * field array). It is not invoked in other modules.
+ *
+ * Unlike hook_field_views_data_alter(), this operates on the whole of the views
+ * data. This allows a field module to add data that concerns its fields to
+ * other tables, which would not yet be defined at the point when
+ * hook_field_views_data() and hook_field_views_data_alter() are invoked. For
+ * example, entityreference adds reverse relationships on the tables for the
+ * entities which are referenced by entityreference fields.
+ *
+ * (Note: this is weirdly named so as not to conflict with
+ * hook_field_views_data_alter().)
+ *
+ * @see hook_field_views_data()
+ * @see hook_field_views_data_alter()
+ * @see field_views_data_alter()
+ */
+function hook_field_views_data_views_data_alter(&$data, $field) {
+ $field_name = $field['field_name'];
+ $data_key = 'field_data_' . $field_name;
+ // Views data for this field is in $data[$data_key]
+}
+
+/**
+ * Describes plugins defined by the module.
+ *
+ * This hook should be placed in MODULENAME.views.inc and it will be
+ * auto-loaded. MODULENAME.views.inc must be in the directory specified by the
+ * 'path' key returned by MODULENAME_views_api(), or the same directory as the
+ * .module file, if 'path' is unspecified. All plugin files need to be
+ * referenced in MODULENAME.info with the files[] directive.
+ *
+ * @return
+ * An array on the form $plugins['PLUGIN TYPE']['PLUGIN NAME']. The plugin
+ * must be one of row, display, display_extender, style, argument default,
+ * argument validator, access, query, cache, pager, exposed_form or
+ * localization. The plugin name should be prefixed with your module name.
+ * The value for each entry is an associative array that may contain the
+ * following entries:
+ * - Used by all plugin types:
+ * - title (required): The name of the plugin, as shown in Views. Wrap in
+ * t().
+ * - handler (required): The name of the file containing the class
+ * describing the handler, which must also be the name of the handler's
+ * class.
+ * - path: Path to the handler. Only required if the handler is not placed
+ * in the same folder as the .module file or in the subfolder 'views'.
+ * - parent: The name of the plugin this plugin extends. Since Drupal 7 this
+ * is no longer required, but may still be useful from a code readability
+ * perspective.
+ * - no ui: Set to TRUE to denote that the plugin doesn't appear to be
+ * selectable in the ui, though on the api side they still exists.
+ * - uses options: Set to TRUE to denote that the plugin has an additional
+ * options form.
+ * - help: A short help text, wrapped in t() used as description on the plugin settings form.
+ * - help topic: The name of an entry by advanced help for the plugin.
+ * - theme: The name of a theme suggestion to use for the display.
+ * - js: An array with paths to js files that should be included for the
+ * display. Note that the path should be relative Drupal root, not module
+ * root.
+ * - type: Each plugin can specify a type parameter to group certain
+ * plugins together. For example all row plugins related to feeds are
+ * grouped together, because a rss style plugin only accepts feed row
+ * plugins.
+ *
+ * - Used by display plugins:
+ * - admin: The administrative name of the display, as displayed on the
+ * Views overview and also used as default name for new displays. Wrap in
+ * t().
+ * - no remove: Set to TRUE to make the display non-removable. (Basically
+ * only used for the master/default display.)
+ * - use ajax: Set to TRUE to allow AJAX loads in the display. If it's
+ * disabled there will be no ajax option in the ui.
+ * - use pager: Set to TRUE to allow paging in the display.
+ * - use more: Set to TRUE to allow the 'use more' setting in the display.
+ * - accept attachments: Set to TRUE to allow attachment displays to be
+ * attached to this display type.
+ * - contextual links locations: An array with places where contextual links
+ * should be added. Can for example be 'page' or 'block'. If you don't
+ * specify it there will be contextual links around the rendered view. If
+ * this is not set or regions have been specified, views will display an
+ * option to 'hide contextual links'. Use an empty array if you do not want
+ * this.
+ * - uses hook menu: Set to TRUE to have the display included by
+ * views_menu_alter(). views_menu_alter executes then execute_hook_menu
+ * on the display object.
+ * - uses hook block: Set to TRUE to have the display included by
+ * views_block_info().
+ * - theme: The name of a theme suggestion to use for the display.
+ * - js: An array with paths to js files that should be included for the
+ * display. Note that the path should be relative Drupal root, not module
+ * root.
+ *
+ * - Used by style plugins:
+ * - uses row plugin: Set to TRUE to allow row plugins for this style.
+ * - uses row class: Set to TRUE to allow the CSS class settings for rows.
+ * - uses fields: Set to TRUE to have the style plugin accept field
+ * handlers.
+ * - uses grouping: Set to TRUE to allow the grouping settings for rows.
+ * - even empty: May have the value 'even empty' to tell Views that the style
+ * should be rendered even if there are no results.
+ *
+ * - Used by row plugins:
+ * - uses fields: Set to TRUE to have the row plugin accept field handlers.
+ */
+function hook_views_plugins() {
+ $plugins = array();
+ $plugins['argument validator'] = array(
+ 'taxonomy_term' => array(
+ 'title' => t('Taxonomy term'),
+ 'handler' => 'views_plugin_argument_validate_taxonomy_term',
+ // Declaring path explicitly not necessary for most modules.
+ 'path' => drupal_get_path('module', 'views') . '/modules/taxonomy',
+ ),
+ );
+
+ return array(
+ 'module' => 'views', // This just tells our themes are elsewhere.
+ 'argument validator' => array(
+ 'taxonomy_term' => array(
+ 'title' => t('Taxonomy term'),
+ 'handler' => 'views_plugin_argument_validate_taxonomy_term',
+ 'path' => drupal_get_path('module', 'views') . '/modules/taxonomy', // not necessary for most modules
+ ),
+ ),
+ 'argument default' => array(
+ 'taxonomy_tid' => array(
+ 'title' => t('Taxonomy term ID from URL'),
+ 'handler' => 'views_plugin_argument_default_taxonomy_tid',
+ 'path' => drupal_get_path('module', 'views') . '/modules/taxonomy',
+ 'parent' => 'fixed',
+ ),
+ ),
+ );
+}
+
+/**
+ * Alter existing plugins data, defined by modules.
+ *
+ * @see hook_views_plugins()
+ */
+function hook_views_plugins_alter(&$plugins) {
+ // Add apachesolr to the base of the node row plugin.
+ $plugins['row']['node']['base'][] = 'apachesolr';
+}
+
+/**
+ * Register View API information.
+ *
+ * This is required for your module to have its include files loaded; for
+ * example, when implementing hook_views_default_views().
+ *
+ * @return
+ * An array with the following possible keys:
+ * - api: (required) The version of the Views API the module implements.
+ * - path: (optional) If includes are stored somewhere other than within the
+ * root module directory, specify its path here.
+ * - template path: (optional) A path where the module has stored it's views
+ * template files. When you have specified this key views automatically
+ * uses the template files for the views. You can use the same naming
+ * conventions like for normal views template files.
+ */
+function hook_views_api() {
+ return array(
+ 'api' => 3,
+ 'path' => drupal_get_path('module', 'example') . '/includes/views',
+ 'template path' => drupal_get_path('module', 'example') . '/themes',
+ );
+}
+
+/**
+ * This hook allows modules to provide their own views which can either be used
+ * as-is or as a "starter" for users to build from.
+ *
+ * This hook should be placed in MODULENAME.views_default.inc and it will be
+ * auto-loaded. MODULENAME.views_default.inc must be in the directory specified
+ * by the 'path' key returned by MODULENAME_views_api(), or the same directory
+ * as the .module file, if 'path' is unspecified.
+ *
+ * The $view->disabled boolean flag indicates whether the View should be
+ * enabled (FALSE) or disabled (TRUE) by default.
+ *
+ * @return
+ * An associative array containing the structures of views, as generated from
+ * the Export tab, keyed by the view name. A best practice is to go through
+ * and add t() to all title and label strings, with the exception of menu
+ * strings.
+ */
+function hook_views_default_views() {
+ // Begin copy and paste of output from the Export tab of a view.
+ $view = new view;
+ $view->name = 'frontpage';
+ $view->description = 'Emulates the default Drupal front page; you may set the default home page path to this view to make it your front page.';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Front page';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ /* Sort criterion: Content: Sticky */
+ $handler->display->display_options['sorts']['sticky']['id'] = 'sticky';
+ $handler->display->display_options['sorts']['sticky']['table'] = 'node';
+ $handler->display->display_options['sorts']['sticky']['field'] = 'sticky';
+ $handler->display->display_options['sorts']['sticky']['order'] = 'DESC';
+ /* Sort criterion: Content: Post date */
+ $handler->display->display_options['sorts']['created']['id'] = 'created';
+ $handler->display->display_options['sorts']['created']['table'] = 'node';
+ $handler->display->display_options['sorts']['created']['field'] = 'created';
+ $handler->display->display_options['sorts']['created']['order'] = 'DESC';
+ /* Filter criterion: Content: Promoted to front page */
+ $handler->display->display_options['filters']['promote']['id'] = 'promote';
+ $handler->display->display_options['filters']['promote']['table'] = 'node';
+ $handler->display->display_options['filters']['promote']['field'] = 'promote';
+ $handler->display->display_options['filters']['promote']['value'] = '1';
+ $handler->display->display_options['filters']['promote']['group'] = 0;
+ $handler->display->display_options['filters']['promote']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = '1';
+ $handler->display->display_options['filters']['status']['group'] = 0;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'frontpage';
+
+ /* Display: Feed */
+ $handler = $view->new_display('feed', 'Feed', 'feed');
+ $handler->display->display_options['defaults']['title'] = FALSE;
+ $handler->display->display_options['title'] = 'Front page feed';
+ $handler->display->display_options['pager']['type'] = 'some';
+ $handler->display->display_options['style_plugin'] = 'rss';
+ $handler->display->display_options['row_plugin'] = 'node_rss';
+ $handler->display->display_options['path'] = 'rss.xml';
+ $handler->display->display_options['displays'] = array(
+ 'default' => 'default',
+ 'page' => 'page',
+ );
+ $handler->display->display_options['sitename_title'] = '1';
+
+ // (Export ends here.)
+
+ // Add view to list of views to provide.
+ $views[$view->name] = $view;
+
+ // ...Repeat all of the above for each view the module should provide.
+
+ // At the end, return array of default views.
+ return $views;
+}
+
+/**
+ * Alter default views defined by other modules.
+ *
+ * This hook is called right before all default views are cached to the
+ * database. It takes a keyed array of views by reference.
+ *
+ * Example usage to add a field to a view:
+ * @code
+ * $handler =& $view->display['DISPLAY_ID']->handler;
+ * // Add the user name field to the view.
+ * $handler->display->display_options['fields']['name']['id'] = 'name';
+ * $handler->display->display_options['fields']['name']['table'] = 'users';
+ * $handler->display->display_options['fields']['name']['field'] = 'name';
+ * $handler->display->display_options['fields']['name']['label'] = 'Author';
+ * $handler->display->display_options['fields']['name']['link_to_user'] = 1;
+ * @endcode
+ */
+function hook_views_default_views_alter(&$views) {
+ if (isset($views['taxonomy_term'])) {
+ $views['taxonomy_term']->display['default']->display_options['title'] = 'Categories';
+ }
+}
+
+/**
+ * Performs replacements in the query before being performed.
+ *
+ * @param $view
+ * The View being executed.
+ * @return
+ * An array with keys being the strings to replace, and the values the strings
+ * to replace them with. The strings to replace are often surrounded with
+ * '***', as illustrated in the example implementation.
+ */
+function hook_views_query_substitutions($view) {
+ // Example from views_views_query_substitutions().
+ global $language_content;
+ return array(
+ '***CURRENT_VERSION***' => VERSION,
+ '***CURRENT_TIME***' => REQUEST_TIME,
+ '***CURRENT_LANGUAGE***' => $language_content->language,
+ '***DEFAULT_LANGUAGE***' => language_default('language'),
+ );
+}
+
+/**
+ * This hook is called to get a list of placeholders and their substitutions,
+ * used when preprocessing a View with form elements.
+ *
+ * @return
+ * An array with keys being the strings to replace, and the values the strings
+ * to replace them with.
+ */
+function hook_views_form_substitutions() {
+ return array(
+ '<!--views-form-example-substitutions-->' => 'Example Substitution',
+ );
+}
+
+/**
+ * Allows altering a view at the very beginning of views processing, before
+ * anything is done.
+ *
+ * Adding output to the view can be accomplished by placing text on
+ * $view->attachment_before and $view->attachment_after.
+ * @param $view
+ * The view object about to be processed.
+ * @param $display_id
+ * The machine name of the active display.
+ * @param $args
+ * An array of arguments passed into the view.
+ */
+function hook_views_pre_view(&$view, &$display_id, &$args) {
+ // Change the display if the acting user has 'administer site configuration'
+ // permission, to display something radically different.
+ // (Note that this is not necessarily the best way to solve that task. Feel
+ // free to contribute another example!)
+ if (
+ $view->name == 'my_special_view' &&
+ user_access('administer site configuration') &&
+ $display_id == 'public_display'
+ ) {
+ $view->set_display('private_display');
+ }
+}
+
+/**
+ * This hook is called right before the build process, but after displays
+ * are attached and the display performs its pre_execute phase.
+ *
+ * Adding output to the view can be accomplished by placing text on
+ * $view->attachment_before and $view->attachment_after.
+ * @param $view
+ * The view object about to be processed.
+ */
+function hook_views_pre_build(&$view) {
+ // Because of some inexplicable business logic, we should remove all
+ // attachments from all views on Mondays.
+ // (This alter could be done later in the execution process as well.)
+ if (date('D') == 'Mon') {
+ unset($view->attachment_before);
+ unset($view->attachment_after);
+ }
+}
+
+/**
+ * This hook is called right after the build process. The query is now fully
+ * built, but it has not yet been run through db_rewrite_sql.
+ *
+ * Adding output to the view can be accomplished by placing text on
+ * $view->attachment_before and $view->attachment_after.
+ * @param $view
+ * The view object about to be processed.
+ */
+function hook_views_post_build(&$view) {
+ // If the exposed field 'type' is set, hide the column containing the content
+ // type. (Note that this is a solution for a particular view, and makes
+ // assumptions about both exposed filter settings and the fields in the view.
+ // Also note that this alter could be done at any point before the view being
+ // rendered.)
+ if ($view->name == 'my_view' && isset($view->exposed_raw_input['type']) && $view->exposed_raw_input['type'] != 'All') {
+ // 'Type' should be interpreted as content type.
+ if (isset($view->field['type'])) {
+ $view->field['type']->options['exclude'] = TRUE;
+ }
+ }
+}
+
+/**
+ * This hook is called right before the execute process. The query is now fully
+ * built, but it has not yet been run through db_rewrite_sql.
+ *
+ * Adding output to the view can be accomplished by placing text on
+ * $view->attachment_before and $view->attachment_after.
+ * @param $view
+ * The view object about to be processed.
+ */
+function hook_views_pre_execute(&$view) {
+ // Whenever a view queries more than two tables, show a message that notifies
+ // view administrators that the query might be heavy.
+ // (This action could be performed later in the execution process, but not
+ // earlier.)
+ if (count($view->query->tables) > 2 && user_access('administer views')) {
+ drupal_set_message(t('The view %view may be heavy to execute.', array('%view' => $view->name)), 'warning');
+ }
+}
+
+/**
+ * This hook is called right after the execute process. The query has
+ * been executed, but the pre_render() phase has not yet happened for
+ * handlers.
+ *
+ * Adding output to the view can be accomplished by placing text on
+ * $view->attachment_before and $view->attachment_after. Altering the
+ * content can be achieved by editing the items of $view->result.
+ * @param $view
+ * The view object about to be processed.
+ */
+function hook_views_post_execute(&$view) {
+ // If there are more than 100 results, show a message that encourages the user
+ // to change the filter settings.
+ // (This action could be performed later in the execution process, but not
+ // earlier.)
+ if ($view->total_rows > 100) {
+ drupal_set_message(t('You have more than 100 hits. Use the filter settings to narrow down your list.'));
+ }
+}
+
+/**
+ * This hook is called right before the render process. The query has been
+ * executed, and the pre_render() phase has already happened for handlers, so
+ * all data should be available.
+ *
+ * Adding output to the view can be accomplished by placing text on
+ * $view->attachment_before and $view->attachment_after. Altering the content
+ * can be achieved by editing the items of $view->result.
+ *
+ * This hook can be utilized by themes.
+ * @param $view
+ * The view object about to be processed.
+ */
+function hook_views_pre_render(&$view) {
+ // Scramble the order of the rows shown on this result page.
+ // Note that this could be done earlier, but not later in the view execution
+ // process.
+ shuffle($view->result);
+}
+
+/**
+ * Post process any rendered data.
+ *
+ * This can be valuable to be able to cache a view and still have some level of
+ * dynamic output. In an ideal world, the actual output will include HTML
+ * comment based tokens, and then the post process can replace those tokens.
+ *
+ * Example usage. If it is known that the view is a node view and that the
+ * primary field will be a nid, you can do something like this:
+ *
+ * <!--post-FIELD-NID-->
+ *
+ * And then in the post render, create an array with the text that should
+ * go there:
+ *
+ * strtr($output, array('<!--post-FIELD-1-->' => 'output for FIELD of nid 1');
+ *
+ * All of the cached result data will be available in $view->result, as well,
+ * so all ids used in the query should be discoverable.
+ *
+ * This hook can be utilized by themes.
+ * @param $view
+ * The view object about to be processed.
+ * @param $output
+ * A flat string with the rendered output of the view.
+ * @param $cache
+ * The cache settings.
+ */
+function hook_views_post_render(&$view, &$output, &$cache) {
+ // When using full pager, disable any time-based caching if there are less
+ // then 10 results.
+ if ($view->query->pager instanceof views_plugin_pager_full && $cache->options['type'] == 'time' && count($view->result) < 10) {
+ $cache['options']['results_lifespan'] = 0;
+ $cache['options']['output_lifespan'] = 0;
+ }
+}
+
+/**
+ * Alter the query before executing the query.
+ *
+ * This hook should be placed in MODULENAME.views.inc and it will be
+ * auto-loaded. MODULENAME.views.inc must be in the directory specified by the
+ * 'path' key returned by MODULENAME_views_api(), or the same directory as the
+ * .module file, if 'path' is unspecified.
+ *
+ * @param $view
+ * The view object about to be processed.
+ * @param $query
+ * An object describing the query.
+ * @see hook_views_query_substitutions()
+ */
+function hook_views_query_alter(&$view, &$query) {
+ // (Example assuming a view with an exposed filter on node title.)
+ // If the input for the title filter is a positive integer, filter against
+ // node ID instead of node title.
+ if ($view->name == 'my_view' && is_numeric($view->exposed_raw_input['title']) && $view->exposed_raw_input['title'] > 0) {
+ // Traverse through the 'where' part of the query.
+ foreach ($query->where as &$condition_group) {
+ foreach ($condition_group['conditions'] as &$condition) {
+ // If this is the part of the query filtering on title, change the
+ // condition to filter on node ID.
+ if ($condition['field'] == 'node.title') {
+ $condition = array(
+ 'field' => 'node.nid',
+ 'value' => $view->exposed_raw_input['title'],
+ 'operator' => '=',
+ );
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Alter the information box that (optionally) appears with a view preview,
+ * including query and performance statistics.
+ *
+ * This hook should be placed in MODULENAME.views.inc and it will be
+ * auto-loaded. MODULENAME.views.inc must be in the directory specified by the
+ * 'path' key returned by MODULENAME_views_api(), or the same directory as the
+ * .module file, if 'path' is unspecified.
+ *
+ * Warning: $view is not a reference in PHP4 and cannot be modified here. But it
+ * IS a reference in PHP5, and can be modified. Please be careful with it.
+ *
+ * @param $rows
+ * An associative array with two keys:
+ * - query: An array of rows suitable for theme('table'), containing
+ * information about the query and the display title and path.
+ * - statistics: An array of rows suitable for theme('table'), containing
+ * performance statistics.
+ * @param $view
+ * The view object.
+ * @see theme_table()
+ */
+function hook_views_preview_info_alter(&$rows, $view) {
+ // Adds information about the tables being queried by the view to the query
+ // part of the info box.
+ $rows['query'][] = array(
+ t('<strong>Table queue</strong>'),
+ count($view->query->table_queue) . ': (' . implode(', ', array_keys($view->query->table_queue)) . ')',
+ );
+}
+
+/**
+ * This hooks allows to alter the links at the top of the view edit form. Some
+ * modules might want to add links there.
+ *
+ * @param $links
+ * An array of links which will be displayed at the top of the view edit form.
+ * Each entry should be on a form suitable for theme('link').
+ * @param view $view
+ * The full view object which is currently edited.
+ * @param $display_id
+ * The current display id which is edited. For example that's 'default' or
+ * 'page_1'.
+ */
+function hook_views_ui_display_top_links_alter(&$links, $view, $display_id) {
+ // Put the export link first in the list.
+ if (isset($links['export'])) {
+ $links = array('export' => $links['export']) + $links;
+ }
+}
+
+/**
+ * This hook allows to alter the commands which are used on a views ajax
+ * request.
+ *
+ * @param $commands
+ * An array of ajax commands
+ * @param $view view
+ * The view which is requested.
+ */
+function hook_views_ajax_data_alter(&$commands, $view) {
+ // Replace Views' method for scrolling to the top of the element with your
+ // custom scrolling method.
+ foreach ($commands as &$command) {
+ if ($command['command'] == 'viewsScrollTop') {
+ $command['command'] .= 'myScrollTop';
+ }
+ }
+}
+
+/**
+ * Allow modules to respond to the Views cache being invalidated.
+ *
+ * This hook should fire whenever a view is enabled, disabled, created,
+ * updated, or deleted.
+ *
+ * @see views_invalidate_cache()
+ */
+function hook_views_invalidate_cache() {
+ cache_clear_all('views:*', 'cache_mymodule', TRUE);
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup views_module_handlers Views module handlers
+ * @{
+ * Handlers exposed by various modules to Views.
+ * @}
+ */
diff --git a/sites/all/modules/views/views.info b/sites/all/modules/views/views.info
new file mode 100644
index 000000000..27500c172
--- /dev/null
+++ b/sites/all/modules/views/views.info
@@ -0,0 +1,326 @@
+name = Views
+description = Create customized lists and queries from your database.
+package = Views
+core = 7.x
+php = 5.2
+
+; Always available CSS
+stylesheets[all][] = css/views.css
+
+dependencies[] = ctools
+; Handlers
+files[] = handlers/views_handler_area.inc
+files[] = handlers/views_handler_area_messages.inc
+files[] = handlers/views_handler_area_result.inc
+files[] = handlers/views_handler_area_text.inc
+files[] = handlers/views_handler_area_text_custom.inc
+files[] = handlers/views_handler_area_view.inc
+files[] = handlers/views_handler_argument.inc
+files[] = handlers/views_handler_argument_date.inc
+files[] = handlers/views_handler_argument_formula.inc
+files[] = handlers/views_handler_argument_many_to_one.inc
+files[] = handlers/views_handler_argument_null.inc
+files[] = handlers/views_handler_argument_numeric.inc
+files[] = handlers/views_handler_argument_string.inc
+files[] = handlers/views_handler_argument_group_by_numeric.inc
+files[] = handlers/views_handler_field.inc
+files[] = handlers/views_handler_field_counter.inc
+files[] = handlers/views_handler_field_boolean.inc
+files[] = handlers/views_handler_field_contextual_links.inc
+files[] = handlers/views_handler_field_custom.inc
+files[] = handlers/views_handler_field_date.inc
+files[] = handlers/views_handler_field_entity.inc
+files[] = handlers/views_handler_field_markup.inc
+files[] = handlers/views_handler_field_math.inc
+files[] = handlers/views_handler_field_numeric.inc
+files[] = handlers/views_handler_field_prerender_list.inc
+files[] = handlers/views_handler_field_time_interval.inc
+files[] = handlers/views_handler_field_serialized.inc
+files[] = handlers/views_handler_field_machine_name.inc
+files[] = handlers/views_handler_field_url.inc
+files[] = handlers/views_handler_filter.inc
+files[] = handlers/views_handler_filter_boolean_operator.inc
+files[] = handlers/views_handler_filter_boolean_operator_string.inc
+files[] = handlers/views_handler_filter_combine.inc
+files[] = handlers/views_handler_filter_date.inc
+files[] = handlers/views_handler_filter_equality.inc
+files[] = handlers/views_handler_filter_entity_bundle.inc
+files[] = handlers/views_handler_filter_group_by_numeric.inc
+files[] = handlers/views_handler_filter_in_operator.inc
+files[] = handlers/views_handler_filter_many_to_one.inc
+files[] = handlers/views_handler_filter_numeric.inc
+files[] = handlers/views_handler_filter_string.inc
+files[] = handlers/views_handler_filter_fields_compare.inc
+files[] = handlers/views_handler_relationship.inc
+files[] = handlers/views_handler_relationship_groupwise_max.inc
+files[] = handlers/views_handler_sort.inc
+files[] = handlers/views_handler_sort_date.inc
+files[] = handlers/views_handler_sort_formula.inc
+files[] = handlers/views_handler_sort_group_by_numeric.inc
+files[] = handlers/views_handler_sort_menu_hierarchy.inc
+files[] = handlers/views_handler_sort_random.inc
+; Includes
+files[] = includes/base.inc
+files[] = includes/handlers.inc
+files[] = includes/plugins.inc
+files[] = includes/view.inc
+; Modules
+files[] = modules/aggregator/views_handler_argument_aggregator_fid.inc
+files[] = modules/aggregator/views_handler_argument_aggregator_iid.inc
+files[] = modules/aggregator/views_handler_argument_aggregator_category_cid.inc
+files[] = modules/aggregator/views_handler_field_aggregator_title_link.inc
+files[] = modules/aggregator/views_handler_field_aggregator_category.inc
+files[] = modules/aggregator/views_handler_field_aggregator_item_description.inc
+files[] = modules/aggregator/views_handler_field_aggregator_xss.inc
+files[] = modules/aggregator/views_handler_filter_aggregator_category_cid.inc
+files[] = modules/aggregator/views_plugin_row_aggregator_rss.inc
+files[] = modules/book/views_plugin_argument_default_book_root.inc
+files[] = modules/comment/views_handler_argument_comment_user_uid.inc
+files[] = modules/comment/views_handler_field_comment.inc
+files[] = modules/comment/views_handler_field_comment_depth.inc
+files[] = modules/comment/views_handler_field_comment_link.inc
+files[] = modules/comment/views_handler_field_comment_link_approve.inc
+files[] = modules/comment/views_handler_field_comment_link_delete.inc
+files[] = modules/comment/views_handler_field_comment_link_edit.inc
+files[] = modules/comment/views_handler_field_comment_link_reply.inc
+files[] = modules/comment/views_handler_field_comment_node_link.inc
+files[] = modules/comment/views_handler_field_comment_username.inc
+files[] = modules/comment/views_handler_field_ncs_last_comment_name.inc
+files[] = modules/comment/views_handler_field_ncs_last_updated.inc
+files[] = modules/comment/views_handler_field_node_comment.inc
+files[] = modules/comment/views_handler_field_node_new_comments.inc
+files[] = modules/comment/views_handler_field_last_comment_timestamp.inc
+files[] = modules/comment/views_handler_filter_comment_user_uid.inc
+files[] = modules/comment/views_handler_filter_ncs_last_updated.inc
+files[] = modules/comment/views_handler_filter_node_comment.inc
+files[] = modules/comment/views_handler_sort_comment_thread.inc
+files[] = modules/comment/views_handler_sort_ncs_last_comment_name.inc
+files[] = modules/comment/views_handler_sort_ncs_last_updated.inc
+files[] = modules/comment/views_plugin_row_comment_rss.inc
+files[] = modules/comment/views_plugin_row_comment_view.inc
+files[] = modules/contact/views_handler_field_contact_link.inc
+files[] = modules/field/views_handler_field_field.inc
+files[] = modules/field/views_handler_relationship_entity_reverse.inc
+files[] = modules/field/views_handler_argument_field_list.inc
+files[] = modules/field/views_handler_filter_field_list_boolean.inc
+files[] = modules/field/views_handler_argument_field_list_string.inc
+files[] = modules/field/views_handler_filter_field_list.inc
+files[] = modules/filter/views_handler_field_filter_format_name.inc
+files[] = modules/locale/views_handler_field_node_language.inc
+files[] = modules/locale/views_handler_filter_node_language.inc
+files[] = modules/locale/views_handler_argument_locale_group.inc
+files[] = modules/locale/views_handler_argument_locale_language.inc
+files[] = modules/locale/views_handler_field_locale_group.inc
+files[] = modules/locale/views_handler_field_locale_language.inc
+files[] = modules/locale/views_handler_field_locale_link_edit.inc
+files[] = modules/locale/views_handler_filter_locale_group.inc
+files[] = modules/locale/views_handler_filter_locale_language.inc
+files[] = modules/locale/views_handler_filter_locale_version.inc
+files[] = modules/node/views_handler_argument_dates_various.inc
+files[] = modules/node/views_handler_argument_node_language.inc
+files[] = modules/node/views_handler_argument_node_nid.inc
+files[] = modules/node/views_handler_argument_node_type.inc
+files[] = modules/node/views_handler_argument_node_vid.inc
+files[] = modules/node/views_handler_argument_node_uid_revision.inc
+files[] = modules/node/views_handler_field_history_user_timestamp.inc
+files[] = modules/node/views_handler_field_node.inc
+files[] = modules/node/views_handler_field_node_link.inc
+files[] = modules/node/views_handler_field_node_link_delete.inc
+files[] = modules/node/views_handler_field_node_link_edit.inc
+files[] = modules/node/views_handler_field_node_revision.inc
+files[] = modules/node/views_handler_field_node_revision_link.inc
+files[] = modules/node/views_handler_field_node_revision_link_delete.inc
+files[] = modules/node/views_handler_field_node_revision_link_revert.inc
+files[] = modules/node/views_handler_field_node_path.inc
+files[] = modules/node/views_handler_field_node_type.inc
+files[] = modules/node/views_handler_filter_history_user_timestamp.inc
+files[] = modules/node/views_handler_filter_node_access.inc
+files[] = modules/node/views_handler_filter_node_status.inc
+files[] = modules/node/views_handler_filter_node_type.inc
+files[] = modules/node/views_handler_filter_node_uid_revision.inc
+files[] = modules/node/views_plugin_argument_default_node.inc
+files[] = modules/node/views_plugin_argument_validate_node.inc
+files[] = modules/node/views_plugin_row_node_rss.inc
+files[] = modules/node/views_plugin_row_node_view.inc
+files[] = modules/profile/views_handler_field_profile_date.inc
+files[] = modules/profile/views_handler_field_profile_list.inc
+files[] = modules/profile/views_handler_filter_profile_selection.inc
+files[] = modules/search/views_handler_argument_search.inc
+files[] = modules/search/views_handler_field_search_score.inc
+files[] = modules/search/views_handler_filter_search.inc
+files[] = modules/search/views_handler_sort_search_score.inc
+files[] = modules/search/views_plugin_row_search_view.inc
+files[] = modules/statistics/views_handler_field_accesslog_path.inc
+files[] = modules/system/views_handler_argument_file_fid.inc
+files[] = modules/system/views_handler_field_file.inc
+files[] = modules/system/views_handler_field_file_extension.inc
+files[] = modules/system/views_handler_field_file_filemime.inc
+files[] = modules/system/views_handler_field_file_uri.inc
+files[] = modules/system/views_handler_field_file_status.inc
+files[] = modules/system/views_handler_filter_file_status.inc
+files[] = modules/taxonomy/views_handler_argument_taxonomy.inc
+files[] = modules/taxonomy/views_handler_argument_term_node_tid.inc
+files[] = modules/taxonomy/views_handler_argument_term_node_tid_depth.inc
+files[] = modules/taxonomy/views_handler_argument_term_node_tid_depth_modifier.inc
+files[] = modules/taxonomy/views_handler_argument_vocabulary_vid.inc
+files[] = modules/taxonomy/views_handler_argument_vocabulary_machine_name.inc
+files[] = modules/taxonomy/views_handler_field_taxonomy.inc
+files[] = modules/taxonomy/views_handler_field_term_node_tid.inc
+files[] = modules/taxonomy/views_handler_field_term_link_edit.inc
+files[] = modules/taxonomy/views_handler_filter_term_node_tid.inc
+files[] = modules/taxonomy/views_handler_filter_term_node_tid_depth.inc
+files[] = modules/taxonomy/views_handler_filter_vocabulary_vid.inc
+files[] = modules/taxonomy/views_handler_filter_vocabulary_machine_name.inc
+files[] = modules/taxonomy/views_handler_relationship_node_term_data.inc
+files[] = modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc
+files[] = modules/taxonomy/views_plugin_argument_default_taxonomy_tid.inc
+files[] = modules/tracker/views_handler_argument_tracker_comment_user_uid.inc
+files[] = modules/tracker/views_handler_filter_tracker_comment_user_uid.inc
+files[] = modules/tracker/views_handler_filter_tracker_boolean_operator.inc
+files[] = modules/system/views_handler_filter_system_type.inc
+files[] = modules/translation/views_handler_argument_node_tnid.inc
+files[] = modules/translation/views_handler_field_node_link_translate.inc
+files[] = modules/translation/views_handler_field_node_translation_link.inc
+files[] = modules/translation/views_handler_filter_node_tnid.inc
+files[] = modules/translation/views_handler_filter_node_tnid_child.inc
+files[] = modules/translation/views_handler_relationship_translation.inc
+files[] = modules/user/views_handler_argument_user_uid.inc
+files[] = modules/user/views_handler_argument_users_roles_rid.inc
+files[] = modules/user/views_handler_field_user.inc
+files[] = modules/user/views_handler_field_user_language.inc
+files[] = modules/user/views_handler_field_user_link.inc
+files[] = modules/user/views_handler_field_user_link_cancel.inc
+files[] = modules/user/views_handler_field_user_link_edit.inc
+files[] = modules/user/views_handler_field_user_mail.inc
+files[] = modules/user/views_handler_field_user_name.inc
+files[] = modules/user/views_handler_field_user_permissions.inc
+files[] = modules/user/views_handler_field_user_picture.inc
+files[] = modules/user/views_handler_field_user_roles.inc
+files[] = modules/user/views_handler_filter_user_current.inc
+files[] = modules/user/views_handler_filter_user_name.inc
+files[] = modules/user/views_handler_filter_user_permissions.inc
+files[] = modules/user/views_handler_filter_user_roles.inc
+files[] = modules/user/views_plugin_argument_default_current_user.inc
+files[] = modules/user/views_plugin_argument_default_user.inc
+files[] = modules/user/views_plugin_argument_validate_user.inc
+files[] = modules/user/views_plugin_row_user_view.inc
+; Plugins
+files[] = plugins/views_plugin_access.inc
+files[] = plugins/views_plugin_access_none.inc
+files[] = plugins/views_plugin_access_perm.inc
+files[] = plugins/views_plugin_access_role.inc
+files[] = plugins/views_plugin_argument_default.inc
+files[] = plugins/views_plugin_argument_default_php.inc
+files[] = plugins/views_plugin_argument_default_fixed.inc
+files[] = plugins/views_plugin_argument_default_raw.inc
+files[] = plugins/views_plugin_argument_validate.inc
+files[] = plugins/views_plugin_argument_validate_numeric.inc
+files[] = plugins/views_plugin_argument_validate_php.inc
+files[] = plugins/views_plugin_cache.inc
+files[] = plugins/views_plugin_cache_none.inc
+files[] = plugins/views_plugin_cache_time.inc
+files[] = plugins/views_plugin_display.inc
+files[] = plugins/views_plugin_display_attachment.inc
+files[] = plugins/views_plugin_display_block.inc
+files[] = plugins/views_plugin_display_default.inc
+files[] = plugins/views_plugin_display_embed.inc
+files[] = plugins/views_plugin_display_extender.inc
+files[] = plugins/views_plugin_display_feed.inc
+files[] = plugins/views_plugin_display_page.inc
+files[] = plugins/views_plugin_exposed_form_basic.inc
+files[] = plugins/views_plugin_exposed_form.inc
+files[] = plugins/views_plugin_exposed_form_input_required.inc
+files[] = plugins/views_plugin_localization_core.inc
+files[] = plugins/views_plugin_localization.inc
+files[] = plugins/views_plugin_localization_none.inc
+files[] = plugins/views_plugin_pager.inc
+files[] = plugins/views_plugin_pager_full.inc
+files[] = plugins/views_plugin_pager_mini.inc
+files[] = plugins/views_plugin_pager_none.inc
+files[] = plugins/views_plugin_pager_some.inc
+files[] = plugins/views_plugin_query.inc
+files[] = plugins/views_plugin_query_default.inc
+files[] = plugins/views_plugin_row.inc
+files[] = plugins/views_plugin_row_fields.inc
+files[] = plugins/views_plugin_row_rss_fields.inc
+files[] = plugins/views_plugin_style.inc
+files[] = plugins/views_plugin_style_default.inc
+files[] = plugins/views_plugin_style_grid.inc
+files[] = plugins/views_plugin_style_list.inc
+files[] = plugins/views_plugin_style_jump_menu.inc
+files[] = plugins/views_plugin_style_mapping.inc
+files[] = plugins/views_plugin_style_rss.inc
+files[] = plugins/views_plugin_style_summary.inc
+files[] = plugins/views_plugin_style_summary_jump_menu.inc
+files[] = plugins/views_plugin_style_summary_unformatted.inc
+files[] = plugins/views_plugin_style_table.inc
+
+; Tests
+files[] = tests/handlers/views_handlers.test
+files[] = tests/handlers/views_handler_area_text.test
+files[] = tests/handlers/views_handler_argument_null.test
+files[] = tests/handlers/views_handler_argument_string.test
+files[] = tests/handlers/views_handler_field.test
+files[] = tests/handlers/views_handler_field_boolean.test
+files[] = tests/handlers/views_handler_field_custom.test
+files[] = tests/handlers/views_handler_field_counter.test
+files[] = tests/handlers/views_handler_field_date.test
+files[] = tests/handlers/views_handler_field_file_extension.test
+files[] = tests/handlers/views_handler_field_file_size.test
+files[] = tests/handlers/views_handler_field_math.test
+files[] = tests/handlers/views_handler_field_url.test
+files[] = tests/handlers/views_handler_field_xss.test
+files[] = tests/handlers/views_handler_filter_combine.test
+files[] = tests/handlers/views_handler_filter_date.test
+files[] = tests/handlers/views_handler_filter_equality.test
+files[] = tests/handlers/views_handler_filter_in_operator.test
+files[] = tests/handlers/views_handler_filter_numeric.test
+files[] = tests/handlers/views_handler_filter_string.test
+files[] = tests/handlers/views_handler_sort_random.test
+files[] = tests/handlers/views_handler_sort_date.test
+files[] = tests/handlers/views_handler_sort.test
+files[] = tests/test_handlers/views_test_area_access.inc
+files[] = tests/test_plugins/views_test_plugin_access_test_dynamic.inc
+files[] = tests/test_plugins/views_test_plugin_access_test_static.inc
+files[] = tests/test_plugins/views_test_plugin_style_test_mapping.inc
+files[] = tests/plugins/views_plugin_display.test
+files[] = tests/styles/views_plugin_style_jump_menu.test
+files[] = tests/styles/views_plugin_style.test
+files[] = tests/styles/views_plugin_style_base.test
+files[] = tests/styles/views_plugin_style_mapping.test
+files[] = tests/styles/views_plugin_style_unformatted.test
+files[] = tests/views_access.test
+files[] = tests/views_analyze.test
+files[] = tests/views_basic.test
+files[] = tests/views_argument_default.test
+files[] = tests/views_argument_validator.test
+files[] = tests/views_exposed_form.test
+files[] = tests/field/views_fieldapi.test
+files[] = tests/views_glossary.test
+files[] = tests/views_groupby.test
+files[] = tests/views_handlers.test
+files[] = tests/views_module.test
+files[] = tests/views_pager.test
+files[] = tests/views_plugin_localization_test.inc
+files[] = tests/views_translatable.test
+files[] = tests/views_query.test
+files[] = tests/views_upgrade.test
+files[] = tests/views_test.views_default.inc
+files[] = tests/comment/views_handler_argument_comment_user_uid.test
+files[] = tests/comment/views_handler_filter_comment_user_uid.test
+files[] = tests/node/views_node_revision_relations.test
+files[] = tests/taxonomy/views_handler_relationship_node_term_data.test
+files[] = tests/user/views_handler_field_user_name.test
+files[] = tests/user/views_user_argument_default.test
+files[] = tests/user/views_user_argument_validate.test
+files[] = tests/user/views_user.test
+files[] = tests/views_cache.test
+files[] = tests/views_view.test
+files[] = tests/views_ui.test
+
+; Information added by Drupal.org packaging script on 2015-11-06
+version = "7.x-3.13"
+core = "7.x"
+project = "views"
+datestamp = "1446804876"
+
diff --git a/sites/all/modules/views/views.install b/sites/all/modules/views/views.install
new file mode 100644
index 000000000..ca10d69af
--- /dev/null
+++ b/sites/all/modules/views/views.install
@@ -0,0 +1,647 @@
+<?php
+
+/**
+ * @file
+ * Contains install and update functions for Views.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function views_install() {
+ if (Database::getConnection()->databaseType() == 'pgsql') {
+ db_query('CREATE OR REPLACE FUNCTION first(anyelement, anyelement) RETURNS anyelement AS \'SELECT COALESCE($1, $2);\' LANGUAGE \'sql\';');
+ db_query("DROP AGGREGATE IF EXISTS first(anyelement)");
+ db_query("CREATE AGGREGATE first(sfunc = first, basetype = anyelement, stype = anyelement);");
+ }
+ db_query("UPDATE {system} SET weight = 10 WHERE name = 'views'");
+}
+
+/**
+ * Implements hook_schema().
+ *
+ * Generate the current version of the database schema from
+ * the sequence of schema update functions. Uses a similar
+ * method to install.inc's drupal_get_schema_versions() to
+ * establish the update sequence.
+ *
+ * To change the schema, add a new views_schema_N()
+ * function to match the associated views_update_N()
+ *
+ * @param $caller_function
+ * The name of the function that called us.
+ * Used internally, if requesting a specific schema version.
+ */
+function views_schema($caller_function = FALSE) {
+ static $get_current;
+ static $schemas = array();
+
+ // If called with no arguments, get the latest version of the schema.
+ if (!isset($get_current)) {
+ $get_current = $caller_function ? FALSE : TRUE;
+ }
+
+ // Generate a sorted list of available schema update functions.
+ if ($get_current || empty($schemas)) {
+ $get_current = FALSE;
+ $functions = get_defined_functions();
+ foreach ($functions['user'] as $function) {
+ if (strpos($function, 'views_schema_') === 0) {
+ $version = substr($function, strlen('views_schema_'));
+ if (is_numeric($version)) {
+ $schemas[] = $version;
+ }
+ }
+ }
+ if ($schemas) {
+ sort($schemas, SORT_NUMERIC);
+
+ // If a specific version was requested, drop any later
+ // updates from the sequence.
+ if ($caller_function) {
+ do {
+ $schema = array_pop($schemas);
+ } while ($schemas && $caller_function != 'views_schema_'. $schema);
+ }
+ }
+ }
+
+ // Call views_schema_<n>, for the highest available <n>.
+ if ($schema = array_pop($schemas)) {
+ $function = 'views_schema_'. $schema;
+ return $function();
+ }
+
+ return array();
+}
+
+/**
+ * Views 2's initial schema.
+ * Called directly by views_update_6000() for updates from Drupal 5.
+ *
+ * Important: Do not edit this schema!
+ *
+ * Updates to the views schema must be provided as views_schema_6xxx() functions,
+ * which views_schema() automatically sees and applies. See below for examples.
+ *
+ * Please do document updates with comments in this function, however.
+ */
+function views_schema_6000() {
+ $schema['views_view'] = array(
+ 'description' => 'Stores the general data for a view.',
+ 'export' => array(
+ 'identifier' => 'view',
+ 'bulk export' => TRUE,
+ 'primary key' => 'vid',
+ 'default hook' => 'views_default_views',
+ 'admin_title' => 'human_name',
+ 'admin_description' => 'description',
+ 'api' => array(
+ 'owner' => 'views',
+ 'api' => 'views_default',
+ 'minimum_version' => '2',
+ 'current_version' => '3.0',
+ ),
+ 'object' => 'view',
+ // the callback to load the displays
+ 'subrecords callback' => 'views_load_display_records',
+ // the variable that holds enabled/disabled status
+ 'status' => 'views_defaults',
+ // CRUD callbacks
+ 'create callback' => 'views_new_view',
+ 'save callback' => 'views_save_view',
+ 'delete callback' => 'views_delete_view',
+ 'export callback' => 'views_export_view',
+ 'status callback' => 'views_export_status',
+ 'cache defaults' => TRUE,
+ 'default cache bin' => 'cache_views',
+ ),
+ 'fields' => array(
+ 'vid' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The view ID of the field, defined by the database.',
+ 'no export' => TRUE,
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '32',
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'The unique name of the view. This is the primary field views are loaded from, and is used so that views may be internal and not necessarily in the database. May only be alphanumeric characters plus underscores.',
+ ),
+ 'description' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'default' => '',
+ 'description' => 'A description of the view for the admin interface.',
+ ),
+ 'tag' => array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'default' => '',
+ 'description' => 'A tag used to group/sort views in the admin interface',
+ ),
+ 'view_php' => array(
+ 'type' => 'blob',
+ 'description' => 'A chunk of PHP code that can be used to provide modifications to the view prior to building.',
+ ),
+ 'base_table' => array(
+ 'type' => 'varchar',
+ 'length' => '32', // Updated to '64' in views_schema_6005()
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'What table this view is based on, such as node, user, comment, or term.',
+ ),
+ 'is_cacheable' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => 'A boolean to indicate whether or not this view may have its query cached.',
+ ),
+ ),
+ 'primary key' => array('vid'),
+ 'unique key' => array('name' => array('name')), // Updated to 'unique keys' in views_schema_6003()
+ );
+
+ $schema['views_display'] = array(
+ 'description' => 'Stores information about each display attached to a view.',
+ 'fields' => array(
+ 'vid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The view this display is attached to.',
+ 'no export' => TRUE,
+ ),
+ 'id' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'An identifier for this display; usually generated from the display_plugin, so should be something like page or page_1 or block_2, etc.',
+ ),
+ 'display_title' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'The title of the display, viewable by the administrator.',
+ ),
+ 'display_plugin' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'The type of the display. Usually page, block or embed, but is pluggable so may be other things.',
+ ),
+ 'position' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ 'description' => 'The order in which this display is loaded.',
+ ),
+ 'display_options' => array(
+ // Type corrected in update 6009
+ 'type' => 'blob',
+ 'description' => 'A serialized array of options for this display; it contains options that are generally only pertinent to that display plugin type.',
+ 'serialize' => TRUE,
+ 'serialized default' => 'a:0:{}',
+ ),
+ ),
+ // Added primary keys in views_schema_6008()
+ 'indexes' => array('vid' => array('vid', 'position')),
+ );
+
+ $schema['cache_views'] = drupal_get_schema_unprocessed('system', 'cache');
+
+ $schema['views_object_cache'] = array(
+ 'description' => 'A special cache used to store objects that are being edited; it serves to save state in an ordinarily stateless environment.',
+ 'fields' => array(
+ 'sid' => array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'description' => 'The session ID this cache object belongs to.',
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => '32',
+ 'description' => 'The name of the view this cache is attached to.',
+ ),
+ 'obj' => array(
+ 'type' => 'varchar',
+ 'length' => '32',
+ 'description' => 'The name of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.',
+ ),
+ 'updated' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The time this cache was created or updated.',
+ ),
+ 'data' => array(
+ 'type' => 'blob', // Updated to 'text' (with size => 'big') in views_schema_6004()
+ 'description' => 'Serialized data being stored.',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'indexes' => array(
+ 'sid_obj_name' => array('sid', 'obj', 'name'),
+ 'updated' => array('updated'),
+ ),
+ );
+
+ // $schema['cache_views_data'] added in views_schema_6006()
+
+ return $schema;
+}
+
+/**
+ * Update a site to Drupal 6! Contains a bit of special code to detect
+ * if you've been running a beta version or something.
+ */
+function views_update_6000() {
+ if (db_table_exists('views_view')) {
+ return;
+ }
+
+ // This has the beneficial effect of wiping out any Views 1 cache at the
+ // same time; not wiping that cache could easily cause problems with Views 2.
+ if (db_table_exists('cache_views')) {
+ db_drop_table('cache_views');
+ }
+
+ // This is mostly the same as drupal_install_schema, but it forces
+ // views_schema_6000() rather than the default views_schema().
+ // This is important for processing subsequent table updates.
+ $schema = views_schema_6000();
+ _drupal_schema_initialize($schema, 'views');
+
+ foreach ($schema as $name => $table) {
+ db_create_table($name, $table);
+ }
+}
+
+/**
+ * Remove '$' symbol in special blocks, as it is invalid for theming.
+ */
+function views_update_6001() {
+ $result = db_query("SELECT * FROM {blocks} WHERE module = 'views' AND delta LIKE '\$exp%'");
+ foreach ($result as $block) {
+ $new = strtr($block->delta, '$', '-');
+ update_sql("UPDATE {blocks} SET delta = '" . db_escape_string($new) . "' WHERE module = 'views' AND delta = '" . db_escape_string($block->delta) . "'");
+ }
+ update_sql("UPDATE {blocks} SET delta = CONCAT(delta, '-block_1') WHERE module = 'views'");
+}
+
+// NOTE: Update 6002 removed because it did not always work.
+// Update 6004 implements the change correctly.
+
+/**
+ * Add missing unique key.
+ */
+function views_schema_6003() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['views_view']['unique keys'] = array('name' => array('name'));
+ unset($schema['views_view']['unique key']);
+ return $schema;
+}
+function views_update_6003() {
+ db_add_unique_key('views_view', 'name', array('name'));
+}
+
+/**
+ * Enlarge the views_object_cache.data column to prevent truncation and JS
+ * errors.
+ */
+function views_schema_6004() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['views_object_cache']['fields']['data']['type'] = 'text';
+ $schema['views_object_cache']['fields']['data']['size'] = 'big';
+ return $schema;
+}
+function views_update_6004() {
+ $new_field = array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'Serialized data being stored.',
+ 'serialize' => TRUE,
+ );
+
+ // Drop and re-add this field because there is a bug in
+ // db_change_field that causes this to fail when trying to cast the data.
+ db_drop_field('views_object_cache', 'data');
+ db_add_field('views_object_cache', 'data', $new_field);
+}
+
+/**
+ * Enlarge the base_table column
+ */
+function views_schema_6005() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['views_view']['fields']['base_table']['length'] = 64;
+ return $schema;
+}
+function views_update_6005() {
+ $new_field = array(
+ 'type' => 'varchar',
+ 'length' => '64',
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'What table this view is based on, such as node, user, comment, or term.',
+ );
+ db_change_field('views_view', 'base_table', 'base_table', $new_field);
+}
+
+/**
+ * Add the cache_views_data table to support standard caching.
+ */
+function views_schema_6006() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['cache_views_data'] = drupal_get_schema_unprocessed('system', 'cache');
+ $schema['cache_views_data']['description'] = 'Cache table for views to store pre-rendered queries, results, and display output.';
+ $schema['cache_views_data']['fields']['serialized']['default'] = 1;
+ return $schema;
+}
+function views_update_6006() {
+ $table = drupal_get_schema_unprocessed('system', 'cache');
+ $table['description'] = 'Cache table for views to store pre-rendered queries, results, and display output.';
+ $table['fields']['serialized']['default'] = 1;
+
+ db_create_table('cache_views_data', $table);
+}
+
+/**
+ * Add aggregate function to PostgreSQL so GROUP BY can be used to force only
+ * one result to be returned for each item.
+ */
+function views_update_6007() {
+ if (Database::getConnection()->databaseType() == 'pgsql') {
+ db_query('CREATE OR REPLACE FUNCTION first(anyelement, anyelement) RETURNS anyelement AS \'SELECT COALESCE($1, $2);\' LANGUAGE \'sql\';');
+ db_query("DROP AGGREGATE IF EXISTS first(anyelement)");
+ db_query("CREATE AGGREGATE first(sfunc = first, basetype = anyelement, stype = anyelement);");
+ }
+}
+
+/**
+ * Add the primary key to views_display table.
+ */
+function views_schema_6008() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['views_display']['primary key'] = array('vid', 'id');
+ return $schema;
+}
+
+/**
+ * Add the primary key to the views_display table.
+ */
+function views_update_6008() {
+ db_add_primary_key('views_display', array('vid', 'id'));
+}
+
+/**
+ * Enlarge the views_display.display_options field to accommodate a larger set
+ * of configurations (e. g. fields, filters, etc.) on a display.
+ */
+function views_schema_6009() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['views_display']['fields']['display_options'] = array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'description' => 'A serialized array of options for this display; it contains options that are generally only pertinent to that display plugin type.',
+ 'serialize' => TRUE,
+ 'serialized default' => 'a:0:{}',
+ );
+ return $schema;
+}
+
+function views_update_6009() {
+ $schema = views_schema_6009();
+
+ if (Database::getConnection()->databaseType() == 'pgsql') {
+ db_query('ALTER TABLE {views_display} RENAME "display_options" TO "display_options_old"');
+ db_add_field('views_display', 'display_options', $schema['views_display']['fields']['display_options']);
+
+ $sql = "SELECT vid, id, display_options_old FROM {views_display}";
+ $result = db_query($sql);
+ foreach ($result as $row) {
+ $row['display_options_old'] = $row['display_options_old'];
+ $sql = "UPDATE {views_display} SET display_options = :display_optons WHERE vid = :vid AND id = :id";
+ db_query($sql, array(
+ ':display_optons' => $row['display_options_old'],
+ ':vid' => $row['vid'],
+ ':id' => $row['id'],
+ ));
+ }
+
+ db_drop_field('views_display', 'display_options_old');
+ }
+ else {
+ db_change_field('views_display', 'display_options', 'display_options', $schema['views_display']['fields']['display_options']);
+ }
+}
+
+/**
+ * Remove the view_php field
+ */
+function views_schema_6010() {
+ $schema = views_schema(__FUNCTION__);
+ unset($schema['views_view']['fields']['view_php']);
+ unset($schema['views_view']['fields']['is_cacheable']);
+ return $schema;
+}
+
+/**
+ * Remove the view_php and is_cacheable field
+ */
+function views_update_6010() {
+ db_drop_field('views_view', 'view_php');
+ db_drop_field('views_view', 'is_cacheable');
+}
+
+/**
+ * Remove views_object_cache table and move the data to ctools_object_cache.
+ */
+function views_schema_6011() {
+ $schema = views_schema(__FUNCTION__);
+ unset($schema['views_object_cache']);
+ return $schema;
+}
+
+/**
+ * Remove views_object_cache table and move the data to ctools_object_cache.
+ */
+function views_update_6011() {
+ $caches = db_query("SELECT * FROM {views_object_cache}");
+ foreach ($caches as $item) {
+ drupal_write_record('ctools_object_cache', $item);
+ }
+ db_drop_table('views_object_cache');
+}
+
+/**
+ * Correct the cache setting for exposed filter blocks.
+ *
+ * @see http://drupal.org/node/910864
+ */
+function views_update_6012() {
+ // There is only one simple query to run.
+ $update = db_update('blocks')
+ ->condition('module', 'views')
+ ->condition('delta', db_like('-exp-') . '%', 'LIKE')
+ ->fields(array('cache' => DRUPAL_NO_CACHE));
+}
+
+
+/**
+ * Add a human readable name.
+ */
+function views_schema_6013() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['views_view']['fields']['human_name'] = array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'default' => '',
+ 'description' => 'A human readable name used to be displayed in the admin interface',
+ );
+ return $schema;
+}
+
+function views_update_6013() {
+ // Check because D6 installs may already have added this.
+ if (!db_field_exists('views_view', 'human_name')) {
+
+ $new_field = array(
+ 'type' => 'varchar',
+ 'length' => '255',
+ 'default' => '',
+ 'description' => 'A human readable name used to be displayed in the admin interface',
+ );
+
+ db_add_field('views_view', 'human_name', $new_field);
+ }
+}
+
+function views_schema_6014() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['views_view']['fields']['core'] = array(
+ 'type' => 'int',
+ 'default' => 0,
+ 'description' => 'Stores the drupal core version of the view.',
+ );
+ return $schema;
+}
+
+/**
+ * Add a drupal core version field.
+ */
+function views_update_6014() {
+ // Check because D6 installs may already have added this.
+ if (!db_field_exists('views_view', 'core')) {
+ $new_field = array(
+ 'type' => 'int',
+ 'default' => 0,
+ 'description' => 'Stores the drupal core version of the view.',
+ );
+ db_add_field('views_view', 'core', $new_field);
+ }
+}
+
+/**
+ * Rename some system variables.
+ */
+function views_update_7000() {
+ // Views now lets users turn off query details on live preview.
+ $query_on_top = variable_get('views_ui_query_on_top');
+ if (isset($query_on_top)) {
+ variable_set('views_ui_show_sql_query', TRUE);
+ if ($query_on_top) {
+ variable_set('views_ui_show_sql_query_where', 'above');
+ }
+ else {
+ variable_set('views_ui_show_sql_query_where', 'below');
+ }
+ variable_del('views_ui_query_on_top');
+ }
+
+ // Rename the views_hide_help_message variable from negative to positive.
+ $hide_help = variable_get('views_hide_help_message');
+ if (isset($hide_help)) {
+ variable_set('views_ui_show_advanced_help_warning', !$hide_help);
+ variable_del('views_hide_help_message');
+ }
+
+ // Rename the unused views_no_hover_links variable.
+ variable_del('views_no_hover_links');
+}
+
+/**
+ * Fix missing items from Views administrative breadcrumb
+ */
+function views_update_7001() {
+ $depth = db_select('menu_links')
+ ->fields('menu_links', array('depth'))
+ ->condition('link_path', 'admin/structure/views/view/%')
+ ->execute()
+ ->fetchField();
+
+ if ($depth == 3) {
+ db_delete('menu_links')
+ ->condition('link_path', 'admin/structure/views/%', 'LIKE')
+ ->execute();
+ cache_clear_all(NULL, 'cache_menu');
+ menu_rebuild();
+ }
+}
+
+function views_schema_7300() {
+ return views_schema_6013();
+}
+/**
+ * Make sure the human_name field is added to the views_view table.
+ *
+ * If you updated from 6.x-2.x-dev to 7.x-3.x you already had schema
+ * version 6014, so update 6013 never was nor will be run. As a result,
+ * the human_name field is missing from the database.
+ *
+ * This will add the human_name field if it doesn't already exist.
+ */
+function views_update_7300() {
+ views_update_6013();
+}
+
+function views_schema_7301() {
+ $schema = views_schema(__FUNCTION__);
+ $schema['views_view']['fields']['name']['length'] = 128;
+ return $schema;
+}
+
+/**
+ * Enlarge the name column
+ */
+function views_update_7301() {
+ $new_field = array(
+ 'type' => 'varchar',
+ 'length' => '128',
+ 'default' => '',
+ 'not null' => TRUE,
+ 'description' => 'The unique name of the view. This is the primary field views are loaded from, and is used so that views may be internal and not necessarily in the database. May only be alphanumeric characters plus underscores.',
+ );
+ db_change_field('views_view', 'name', 'name', $new_field);
+}
+
+/**
+ * Remove headers field from cache tables
+ *
+ * @see system_update_7054().
+ */
+function views_update_7302() {
+ if (db_field_exists('cache_views', 'headers')) {
+ db_drop_field('cache_views', 'headers');
+ }
+ if (db_field_exists('cache_views_data', 'headers')) {
+ db_drop_field('cache_views_data', 'headers');
+ }
+}
diff --git a/sites/all/modules/views/views.module b/sites/all/modules/views/views.module
new file mode 100644
index 000000000..aab3812d4
--- /dev/null
+++ b/sites/all/modules/views/views.module
@@ -0,0 +1,2580 @@
+<?php
+
+/**
+ * @file
+ * Primarily Drupal hooks and global API functions to manipulate views.
+ *
+ * This is the main module file for Views. The main entry points into
+ * this module are views_page() and views_block(), where it handles
+ * incoming page and block requests.
+ */
+
+/**
+ * Advertise the current views api version
+ */
+function views_api_version() {
+ return '3.0';
+}
+
+/**
+ * Implements hook_forms().
+ *
+ * To provide distinct form IDs for Views forms, the View name and
+ * specific display name are appended to the base ID,
+ * views_form_views_form. When such a form is built or submitted, this
+ * function will return the proper callback function to use for the given form.
+ */
+function views_forms($form_id, $args) {
+ if (strpos($form_id, 'views_form_') === 0) {
+ return array(
+ $form_id => array(
+ 'callback' => 'views_form',
+ ),
+ );
+ }
+}
+
+/**
+ * Returns a form ID for a Views form using the name and display of the View.
+ */
+function views_form_id($view) {
+ $parts = array(
+ 'views_form',
+ $view->name,
+ $view->current_display,
+ );
+
+ return implode('_', $parts);
+}
+
+/**
+ * Views will not load plugins advertising a version older than this.
+ */
+function views_api_minimum_version() {
+ return '2';
+}
+
+/**
+ * Implement hook_theme(). Register views theming functions.
+ */
+function views_theme($existing, $type, $theme, $path) {
+ $path = drupal_get_path('module', 'views');
+ ctools_include('theme', 'views', 'theme');
+
+ // Some quasi clever array merging here.
+ $base = array(
+ 'file' => 'theme.inc',
+ 'path' => $path . '/theme',
+ );
+
+ // Our extra version of pager from pager.inc
+ $hooks['views_mini_pager'] = $base + array(
+ 'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array()),
+ 'pattern' => 'views_mini_pager__',
+ );
+
+ $variables = array(
+ // For displays, we pass in a dummy array as the first parameter, since
+ // $view is an object but the core contextual_preprocess() function only
+ // attaches contextual links when the primary theme argument is an array.
+ 'display' => array('view_array' => array(), 'view' => NULL),
+ 'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
+ 'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
+ 'exposed_form' => array('view' => NULL, 'options' => NULL),
+ 'pager' => array(
+ 'view' => NULL, 'options' => NULL,
+ 'tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()
+ ),
+ );
+
+ // Default view themes
+ $hooks['views_view_field'] = $base + array(
+ 'pattern' => 'views_view_field__',
+ 'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
+ );
+ $hooks['views_view_grouping'] = $base + array(
+ 'pattern' => 'views_view_grouping__',
+ 'variables' => array('view' => NULL, 'grouping' => NULL, 'grouping_level' => NULL, 'rows' => NULL, 'title' => NULL),
+ );
+
+ $plugins = views_fetch_plugin_data();
+
+ // Register theme functions for all style plugins
+ foreach ($plugins as $type => $info) {
+ foreach ($info as $plugin => $def) {
+ if (isset($def['theme']) && (!isset($def['register theme']) || !empty($def['register theme']))) {
+ $hooks[$def['theme']] = array(
+ 'pattern' => $def['theme'] . '__',
+ 'file' => $def['theme file'],
+ 'path' => $def['theme path'],
+ 'variables' => $variables[$type],
+ );
+
+ $include = DRUPAL_ROOT . '/' . $def['theme path'] . '/' . $def['theme file'];
+ if (file_exists($include)) {
+ require_once $include;
+ }
+
+ if (!function_exists('theme_' . $def['theme'])) {
+ $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
+ }
+ }
+ if (isset($def['additional themes'])) {
+ foreach ($def['additional themes'] as $theme => $theme_type) {
+ if (empty($theme_type)) {
+ $theme = $theme_type;
+ $theme_type = $type;
+ }
+
+ $hooks[$theme] = array(
+ 'pattern' => $theme . '__',
+ 'file' => $def['theme file'],
+ 'path' => $def['theme path'],
+ 'variables' => $variables[$theme_type],
+ );
+
+ if (!function_exists('theme_' . $theme)) {
+ $hooks[$theme]['template'] = drupal_clean_css_identifier($theme);
+ }
+ }
+ }
+ }
+ }
+
+ $hooks['views_form_views_form'] = $base + array(
+ 'render element' => 'form',
+ );
+
+ $hooks['views_exposed_form'] = $base + array(
+ 'template' => 'views-exposed-form',
+ 'pattern' => 'views_exposed_form__',
+ 'render element' => 'form',
+ );
+
+ $hooks['views_more'] = $base + array(
+ 'template' => 'views-more',
+ 'pattern' => 'views_more__',
+ 'variables' => array('more_url' => NULL, 'link_text' => 'more', 'view' => NULL),
+ );
+
+ // Add theme suggestions which are part of modules.
+ foreach (views_get_module_apis() as $info) {
+ if (isset($info['template path'])) {
+ $hooks += _views_find_module_templates($hooks, $info['template path']);
+ }
+ }
+ return $hooks;
+}
+
+/**
+ * Scans a directory of a module for template files.
+ *
+ * @param $cache
+ * The existing cache of theme hooks to test against.
+ * @param $path
+ * The path to search.
+ *
+ * @see drupal_find_theme_templates()
+ */
+function _views_find_module_templates($cache, $path) {
+ $templates = array();
+ $regex = '/' . '\.tpl\.php' . '$' . '/';
+
+ // Because drupal_system_listing works the way it does, we check for real
+ // templates separately from checking for patterns.
+ $files = drupal_system_listing($regex, $path, 'name', 0);
+ foreach ($files as $template => $file) {
+ // Chop off the remaining extensions if there are any. $template already
+ // has the rightmost extension removed, but there might still be more,
+ // such as with .tpl.php, which still has .tpl in $template at this point.
+ if (($pos = strpos($template, '.')) !== FALSE) {
+ $template = substr($template, 0, $pos);
+ }
+ // Transform - in filenames to _ to match function naming scheme
+ // for the purposes of searching.
+ $hook = strtr($template, '-', '_');
+ if (isset($cache[$hook])) {
+ $templates[$hook] = array(
+ 'template' => $template,
+ 'path' => dirname($file->filename),
+ 'includes' => isset($cache[$hook]['includes']) ? $cache[$hook]['includes'] : NULL,
+ );
+ }
+ // Ensure that the pattern is maintained from base themes to its sub-themes.
+ // Each sub-theme will have their templates scanned so the pattern must be
+ // held for subsequent runs.
+ if (isset($cache[$hook]['pattern'])) {
+ $templates[$hook]['pattern'] = $cache[$hook]['pattern'];
+ }
+ }
+
+ $patterns = array_keys($files);
+
+ foreach ($cache as $hook => $info) {
+ if (!empty($info['pattern'])) {
+ // Transform _ in pattern to - to match file naming scheme
+ // for the purposes of searching.
+ $pattern = strtr($info['pattern'], '_', '-');
+
+ $matches = preg_grep('/^'. $pattern .'/', $patterns);
+ if ($matches) {
+ foreach ($matches as $match) {
+ $file = substr($match, 0, strpos($match, '.'));
+ // Put the underscores back in for the hook name and register this pattern.
+ $templates[strtr($file, '-', '_')] = array(
+ 'template' => $file,
+ 'path' => dirname($files[$match]->uri),
+ 'variables' => isset($info['variables']) ? $info['variables'] : NULL,
+ 'render element' => isset($info['render element']) ? $info['render element'] : NULL,
+ 'base hook' => $hook,
+ 'includes' => isset($info['includes']) ? $info['includes'] : NULL,
+ );
+ }
+ }
+ }
+ }
+
+ return $templates;
+}
+
+/**
+ * Returns a list of plugins and metadata about them.
+ *
+ * @return array
+ * An array keyed by PLUGIN_TYPE:PLUGIN_NAME, like 'display:page' or
+ * 'pager:full', containing an array with the following keys:
+ * - title: The plugin's title.
+ * - type: The plugin type.
+ * - module: The module providing the plugin.
+ * - views: An array of enabled Views that are currently using this plugin,
+ * keyed by machine name.
+ */
+function views_plugin_list() {
+ $plugin_data = views_fetch_plugin_data();
+ $plugins = array();
+ foreach (views_get_enabled_views() as $view) {
+ foreach ($view->display as $display_id => $display) {
+ foreach ($plugin_data as $type => $info) {
+ if ($type == 'display' && isset($display->display_plugin)) {
+ $name = $display->display_plugin;
+ }
+ elseif (isset($display->display_options["{$type}_plugin"])) {
+ $name = $display->display_options["{$type}_plugin"];
+ }
+ elseif (isset($display->display_options[$type]['type'])) {
+ $name = $display->display_options[$type]['type'];
+ }
+ else {
+ continue;
+ }
+
+ // Key first by the plugin type, then the name.
+ $key = $type . ':' . $name;
+ // Add info for this plugin.
+ if (!isset($plugins[$key])) {
+ $plugins[$key] = array(
+ 'type' => $type,
+ 'title' => check_plain($info[$name]['title']),
+ 'module' => check_plain($info[$name]['module']),
+ 'views' => array(),
+ );
+ }
+
+ // Add this view to the list for this plugin.
+ $plugins[$key]['views'][$view->name] = $view->name;
+ }
+ }
+ }
+ return $plugins;
+}
+
+/**
+ * A theme preprocess function to automatically allow view-based node
+ * templates if called from a view.
+ *
+ * The 'modules/node.views.inc' file is a better place for this, but
+ * we haven't got a chance to load that file before Drupal builds the
+ * node portion of the theme registry.
+ */
+function views_preprocess_node(&$vars) {
+ // The 'view' attribute of the node is added in views_preprocess_node()
+ if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
+ $vars['view'] = $vars['node']->view;
+ $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name;
+ if (!empty($vars['node']->view->current_display)) {
+ $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
+
+ // If a node is being rendered in a view, and the view does not have a path,
+ // prevent drupal from accidentally setting the $page variable:
+ if ($vars['page'] && $vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) {
+ $vars['page'] = FALSE;
+ }
+ }
+ }
+
+ // Allow to alter comments and links based on the settings in the row plugin.
+ if (!empty($vars['view']->style_plugin->row_plugin) && get_class($vars['view']->style_plugin->row_plugin) == 'views_plugin_row_node_view') {
+ node_row_node_view_preprocess_node($vars);
+ }
+}
+
+/**
+ * A theme preprocess function to automatically allow view-based node
+ * templates if called from a view.
+ */
+function views_preprocess_comment(&$vars) {
+ // The 'view' attribute of the node is added in template_preprocess_views_view_row_comment()
+ if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
+ $vars['view'] = &$vars['node']->view;
+ $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name;
+ if (!empty($vars['node']->view->current_display)) {
+ $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
+ }
+ }
+}
+
+/**
+ * Implement hook_permission().
+ */
+function views_permission() {
+ return array(
+ 'administer views' => array(
+ 'title' => t('Administer views'),
+ 'description' => t('Access the views administration pages.'),
+ 'restrict access' => TRUE,
+ ),
+ 'access all views' => array(
+ 'title' => t('Bypass views access control'),
+ 'description' => t('Bypass access control when accessing views.'),
+ 'restrict access' => TRUE,
+ ),
+ );
+}
+
+/**
+ * Implement hook_menu().
+ */
+function views_menu() {
+ $items = array();
+ $items['views/ajax'] = array(
+ 'title' => 'Views',
+ 'page callback' => 'views_ajax',
+ 'theme callback' => 'ajax_base_page_theme',
+ 'delivery callback' => 'ajax_deliver',
+ 'access callback' => TRUE,
+ 'description' => 'Ajax callback for view loading.',
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/ajax.inc',
+ );
+ // Path is not admin/structure/views due to menu complications with the wildcards from
+ // the generic ajax callback.
+ $items['admin/views/ajax/autocomplete/user'] = array(
+ 'page callback' => 'views_ajax_autocomplete_user',
+ 'theme callback' => 'ajax_base_page_theme',
+ 'access callback' => 'user_access',
+ 'access arguments' => array('access user profiles'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/ajax.inc',
+ );
+ // Define another taxonomy autocomplete because the default one of drupal
+ // does not support a vid a argument anymore
+ $items['admin/views/ajax/autocomplete/taxonomy'] = array(
+ 'page callback' => 'views_ajax_autocomplete_taxonomy',
+ 'theme callback' => 'ajax_base_page_theme',
+ 'access callback' => 'user_access',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/ajax.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implement hook_menu_alter().
+ */
+function views_menu_alter(&$callbacks) {
+ $our_paths = array();
+ $views = views_get_applicable_views('uses hook menu');
+ foreach ($views as $data) {
+ list($view, $display_id) = $data;
+ $result = $view->execute_hook_menu($display_id, $callbacks);
+ if (is_array($result)) {
+ // The menu system doesn't support having two otherwise
+ // identical paths with different placeholders. So we
+ // want to remove the existing items from the menu whose
+ // paths would conflict with ours.
+
+ // First, we must find any existing menu items that may
+ // conflict. We use a regular expression because we don't
+ // know what placeholders they might use. Note that we
+ // first construct the regex itself by replacing %views_arg
+ // in the display path, then we use this constructed regex
+ // (which will be something like '#^(foo/%[^/]*/bar)$#') to
+ // search through the existing paths.
+ $regex = '#^(' . preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) . ')$#';
+ $matches = preg_grep($regex, array_keys($callbacks));
+
+ // Remove any conflicting items that were found.
+ foreach ($matches as $path) {
+ // Don't remove the paths we just added!
+ if (!isset($our_paths[$path])) {
+ unset($callbacks[$path]);
+ }
+ }
+ foreach ($result as $path => $item) {
+ if (!isset($callbacks[$path])) {
+ // Add a new item, possibly replacing (and thus effectively
+ // overriding) one that we removed above.
+ $callbacks[$path] = $item;
+ }
+ else {
+ // This item already exists, so it must be one that we added.
+ // We change the various callback arguments to pass an array
+ // of possible display IDs instead of a single ID.
+ $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
+ $callbacks[$path]['page arguments'][1][] = $display_id;
+ $callbacks[$path]['access arguments'][] = $item['access arguments'][0];
+ $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
+ $callbacks[$path]['load arguments'][1][] = $display_id;
+ }
+ $our_paths[$path] = TRUE;
+ }
+ }
+ }
+
+ // Save memory: Destroy those views.
+ foreach ($views as $data) {
+ list($view, $display_id) = $data;
+ $view->destroy();
+ }
+}
+
+/**
+ * Helper function for menu loading. This will automatically be
+ * called in order to 'load' a views argument; primarily it
+ * will be used to perform validation.
+ *
+ * @param $value
+ * The actual value passed.
+ * @param $name
+ * The name of the view. This needs to be specified in the 'load function'
+ * of the menu entry.
+ * @param $display_id
+ * The display id that will be loaded for this menu item.
+ * @param $index
+ * The menu argument index. This counts from 1.
+ */
+function views_arg_load($value, $name, $display_id, $index) {
+ static $views = array();
+
+ $display_ids = is_array($display_id) ? $display_id : array($display_id);
+ $display_id = reset($display_ids);
+
+ foreach ($display_ids as $id) {
+ // Make sure we haven't already loaded this views argument for a similar
+ // menu item elsewhere. Since access is always checked for the current user,
+ // we are sure that the static cache contains valid entries.
+ $key = $name . ':' . $id . ':' . $value . ':' . $index;
+ if (isset($views[$key])) {
+ return $views[$key];
+ }
+ // Lazy load the view object to avoid unnecessary work.
+ if (!isset($view)) {
+ $view = views_get_view($name);
+ }
+ // Pick the first display we have access to.
+ if ($view && count($display_ids) > 1 && $view->access($id)) {
+ $display_id = $id;
+ break;
+ }
+ }
+
+ if ($view) {
+ $view->set_display($display_id);
+ $view->init_handlers();
+
+ $ids = array_keys($view->argument);
+
+ $indexes = array();
+ $path = explode('/', $view->get_path());
+
+ foreach ($path as $id => $piece) {
+ if ($piece == '%' && !empty($ids)) {
+ $indexes[$id] = array_shift($ids);
+ }
+ }
+
+ if (isset($indexes[$index])) {
+ if (isset($view->argument[$indexes[$index]])) {
+ $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
+ $view->destroy();
+
+ // Store the output in case we load this same menu item again.
+ $views[$key] = $arg;
+ return $arg;
+ }
+ }
+ $view->destroy();
+ }
+}
+
+/**
+ * Page callback: Displays a page view, given a name and display id.
+ *
+ * @param $name
+ * The name of a view.
+ * @param $display_id
+ * The display id of a view.
+ *
+ * @return
+ * Either the HTML of a fully-executed view, or MENU_NOT_FOUND.
+ */
+function views_page($name, $display_id) {
+ $args = func_get_args();
+ // Remove $name and $display_id from the arguments.
+ array_shift($args);
+ array_shift($args);
+
+ // Load the view and render it.
+ if ($view = views_get_view($name)) {
+ return $view->execute_display($display_id, $args);
+ }
+
+ // Fallback; if we get here no view was found or handler was not valid.
+ return MENU_NOT_FOUND;
+}
+
+/**
+ * Implements hook_page_alter().
+ */
+function views_page_alter(&$page) {
+ // If the main content of this page contains a view, attach its contextual
+ // links to the overall page array. This allows them to be rendered directly
+ // next to the page title.
+ $view = views_get_page_view();
+ if (!empty($view)) {
+ // If a module is still putting in the display like we used to, catch that.
+ if (is_subclass_of($view, 'views_plugin_display')) {
+ $view = $view->view;
+ }
+
+ views_add_contextual_links($page, 'page', $view, $view->current_display);
+ }
+}
+
+/**
+ * Implements MODULE_preprocess_HOOK() for html.tpl.php.
+ */
+function views_preprocess_html(&$variables) {
+ // If the page contains a view as its main content, contextual links may have
+ // been attached to the page as a whole; for example, by views_page_alter().
+ // This allows them to be associated with the page and rendered by default
+ // next to the page title (which we want). However, it also causes the
+ // Contextual Links module to treat the wrapper for the entire page (i.e.,
+ // the <body> tag) as the HTML element that these contextual links are
+ // associated with. This we don't want; for better visual highlighting, we
+ // prefer a smaller region to be chosen. The region we prefer differs from
+ // theme to theme and depends on the details of the theme's markup in
+ // page.tpl.php, so we can only find it using JavaScript. We therefore remove
+ // the "contextual-links-region" class from the <body> tag here and add
+ // JavaScript that will insert it back in the correct place.
+ if (!empty($variables['page']['#views_contextual_links_info'])) {
+ $key = array_search('contextual-links-region', $variables['classes_array']);
+ if ($key !== FALSE) {
+ $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
+ // Add the JavaScript, with a group and weight such that it will run
+ // before modules/contextual/contextual.js.
+ drupal_add_js(drupal_get_path('module', 'views') . '/js/views-contextual.js', array('group' => JS_LIBRARY, 'weight' => -1));
+ }
+ }
+}
+
+/**
+* Implements hook_preprocess_HOOK() for page.tpl.php.
+*/
+function views_preprocess_page(&$variables) {
+ // If the page contains a view as its main content, contextual links may have
+ // been attached to the page as a whole; for example, by views_page_alter().
+ // This allows them to be associated with the page and rendered by default
+ // next to the page title (which we want). However, it also causes the
+ // Contextual Links module to treat the wrapper for the entire page (i.e.,
+ // the <body> tag) as the HTML element that these contextual links are
+ // associated with. This we don't want; for better visual highlighting, we
+ // prefer a smaller region to be chosen. The region we prefer differs from
+ // theme to theme and depends on the details of the theme's markup in
+ // page.tpl.php, so we can only find it using JavaScript. We therefore remove
+ // the "contextual-links-region" class from the <body> tag here and add
+ // JavaScript that will insert it back in the correct place.
+ if (!empty($variables['page']['#views_contextual_links_info'])) {
+ $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
+ }
+}
+
+/**
+ * Implements hook_contextual_links_view_alter().
+ */
+function views_contextual_links_view_alter(&$element, $items) {
+ // If we are rendering views-related contextual links attached to the overall
+ // page array, add a class to the list of contextual links. This will be used
+ // by the JavaScript added in views_preprocess_html().
+ if (!empty($element['#element']['#views_contextual_links_info']) && !empty($element['#element']['#type']) && $element['#element']['#type'] == 'page') {
+ $element['#attributes']['class'][] = 'views-contextual-links-page';
+ }
+}
+
+/**
+ * Implement hook_block_info().
+ */
+function views_block_info() {
+ // Try to avoid instantiating all the views just to get the blocks info.
+ views_include('cache');
+ $cache = views_cache_get('views_block_items', TRUE);
+ if ($cache && is_array($cache->data)) {
+ return $cache->data;
+ }
+
+ $items = array();
+ $views = views_get_all_views();
+ foreach ($views as $view) {
+ // disabled views get nothing.
+ if (!empty($view->disabled)) {
+ continue;
+ }
+
+ $view->init_display();
+ foreach ($view->display as $display_id => $display) {
+
+ if (isset($display->handler) && !empty($display->handler->definition['uses hook block'])) {
+ $result = $display->handler->execute_hook_block_list();
+ if (is_array($result)) {
+ $items = array_merge($items, $result);
+ }
+ }
+
+ if (isset($display->handler) && $display->handler->get_option('exposed_block')) {
+ $result = $display->handler->get_special_blocks();
+ if (is_array($result)) {
+ $items = array_merge($items, $result);
+ }
+ }
+ }
+ }
+
+ // block.module has a delta length limit of 32, but our deltas can
+ // unfortunately be longer because view names can be 32 and display IDs
+ // can also be 32. So for very long deltas, change to md5 hashes.
+ $hashes = array();
+
+ // get the keys because we're modifying the array and we don't want to
+ // confuse PHP too much.
+ $keys = array_keys($items);
+ foreach ($keys as $delta) {
+ if (strlen($delta) >= 32) {
+ $hash = md5($delta);
+ $hashes[$hash] = $delta;
+ $items[$hash] = $items[$delta];
+ unset($items[$delta]);
+ }
+ }
+
+ // Only save hashes if they have changed.
+ $old_hashes = variable_get('views_block_hashes', array());
+ if ($hashes != $old_hashes) {
+ variable_set('views_block_hashes', $hashes);
+ }
+ // Save memory: Destroy those views.
+ foreach ($views as $view) {
+ $view->destroy();
+ }
+
+ views_cache_set('views_block_items', $items, TRUE);
+
+ return $items;
+}
+
+/**
+ * Implement hook_block_view().
+ */
+function views_block_view($delta) {
+ $start = microtime(TRUE);
+ // if this is 32, this should be an md5 hash.
+ if (strlen($delta) == 32) {
+ $hashes = variable_get('views_block_hashes', array());
+ if (!empty($hashes[$delta])) {
+ $delta = $hashes[$delta];
+ }
+ }
+
+ // This indicates it's a special one.
+ if (substr($delta, 0, 1) == '-') {
+ list($nothing, $type, $name, $display_id) = explode('-', $delta);
+ // Put the - back on.
+ $type = '-' . $type;
+ if ($view = views_get_view($name)) {
+ if ($view->access($display_id)) {
+ $view->set_display($display_id);
+ if (isset($view->display_handler)) {
+ $output = $view->display_handler->view_special_blocks($type);
+ // Before returning the block output, convert it to a renderable
+ // array with contextual links.
+ views_add_block_contextual_links($output, $view, $display_id, 'special_block_' . $type);
+ $view->destroy();
+ return $output;
+ }
+ }
+ $view->destroy();
+ }
+ }
+
+ // If the delta doesn't contain valid data return nothing.
+ $explode = explode('-', $delta);
+ if (count($explode) != 2) {
+ return;
+ }
+ list($name, $display_id) = $explode;
+ // Load the view
+ if ($view = views_get_view($name)) {
+ if ($view->access($display_id)) {
+ $output = $view->execute_display($display_id);
+ // Before returning the block output, convert it to a renderable array
+ // with contextual links.
+ views_add_block_contextual_links($output, $view, $display_id);
+ $view->destroy();
+ return $output;
+ }
+ $view->destroy();
+ }
+}
+
+/**
+ * Converts Views block content to a renderable array with contextual links.
+ *
+ * @param $block
+ * An array representing the block, with the same structure as the return
+ * value of hook_block_view(). This will be modified so as to force
+ * $block['content'] to be a renderable array, containing the optional
+ * '#contextual_links' property (if there are any contextual links associated
+ * with the block).
+ * @param $view
+ * The view that was used to generate the block content.
+ * @param $display_id
+ * The ID of the display within the view that was used to generate the block
+ * content.
+ * @param $block_type
+ * The type of the block. If it's block it's a regular views display,
+ * but 'special_block_-exp' exist as well.
+ */
+function views_add_block_contextual_links(&$block, $view, $display_id, $block_type = 'block') {
+ // Do not add contextual links to an empty block.
+ if (!empty($block['content'])) {
+ // Contextual links only work on blocks whose content is a renderable
+ // array, so if the block contains a string of already-rendered markup,
+ // convert it to an array.
+ if (is_string($block['content'])) {
+ $block['content'] = array('#markup' => $block['content']);
+ }
+ // Add the contextual links.
+ views_add_contextual_links($block['content'], $block_type, $view, $display_id);
+ }
+}
+
+/**
+ * Adds contextual links associated with a view display to a renderable array.
+ *
+ * This function should be called when a view is being rendered in a particular
+ * location and you want to attach the appropriate contextual links (e.g.,
+ * links for editing the view) to it.
+ *
+ * The function operates by checking the view's display plugin to see if it has
+ * defined any contextual links that are intended to be displayed in the
+ * requested location; if so, it attaches them. The contextual links intended
+ * for a particular location are defined by the 'contextual links' and
+ * 'contextual links locations' properties in hook_views_plugins() and
+ * hook_views_plugins_alter(); as a result, these hook implementations have
+ * full control over where and how contextual links are rendered for each
+ * display.
+ *
+ * In addition to attaching the contextual links to the passed-in array (via
+ * the standard #contextual_links property), this function also attaches
+ * additional information via the #views_contextual_links_info property. This
+ * stores an array whose keys are the names of each module that provided
+ * views-related contextual links (same as the keys of the #contextual_links
+ * array itself) and whose values are themselves arrays whose keys ('location',
+ * 'view_name', and 'view_display_id') store the location, name of the view,
+ * and display ID that were passed in to this function. This allows you to
+ * access information about the contextual links and how they were generated in
+ * a variety of contexts where you might be manipulating the renderable array
+ * later on (for example, alter hooks which run later during the same page
+ * request).
+ *
+ * @param $render_element
+ * The renderable array to which contextual links will be added. This array
+ * should be suitable for passing in to drupal_render() and will normally
+ * contain a representation of the view display whose contextual links are
+ * being requested.
+ * @param $location
+ * The location in which the calling function intends to render the view and
+ * its contextual links. The core system supports three options for this
+ * parameter:
+ * - 'block': Used when rendering a block which contains a view. This
+ * retrieves any contextual links intended to be attached to the block
+ * itself.
+ * - 'page': Used when rendering the main content of a page which contains a
+ * view. This retrieves any contextual links intended to be attached to the
+ * page itself (for example, links which are displayed directly next to the
+ * page title).
+ * - 'view': Used when rendering the view itself, in any context. This
+ * retrieves any contextual links intended to be attached directly to the
+ * view.
+ * If you are rendering a view and its contextual links in another location,
+ * you can pass in a different value for this parameter. However, you will
+ * also need to use hook_views_plugins() or hook_views_plugins_alter() to
+ * declare, via the 'contextual links locations' array key, which view
+ * displays support having their contextual links rendered in the location
+ * you have defined.
+ * @param $view
+ * The view whose contextual links will be added.
+ * @param $display_id
+ * The ID of the display within $view whose contextual links will be added.
+ *
+ * @see hook_views_plugins()
+ * @see views_block_view()
+ * @see views_page_alter()
+ * @see template_preprocess_views_view()
+ */
+function views_add_contextual_links(&$render_element, $location, $view, $display_id) {
+ // Do not do anything if the view is configured to hide its administrative
+ // links.
+ if (empty($view->hide_admin_links)) {
+ // Also do not do anything if the display plugin has not defined any
+ // contextual links that are intended to be displayed in the requested
+ // location.
+ $plugin = views_fetch_plugin_data('display', $view->display[$display_id]->display_plugin);
+ // If contextual links locations are not set, provide a sane default. (To
+ // avoid displaying any contextual links at all, a display plugin can still
+ // set 'contextual links locations' to, e.g., an empty array.)
+ $plugin += array('contextual links locations' => array('view'));
+ // On exposed_forms blocks contextual links should always be visible.
+ $plugin['contextual links locations'][] = 'special_block_-exp';
+ $has_links = !empty($plugin['contextual links']) && !empty($plugin['contextual links locations']);
+ if ($has_links && in_array($location, $plugin['contextual links locations'])) {
+ foreach ($plugin['contextual links'] as $module => $link) {
+ $args = array();
+ $valid = TRUE;
+ if (!empty($link['argument properties'])) {
+ foreach ($link['argument properties'] as $property) {
+ // If the plugin is trying to create an invalid contextual link
+ // (for example, "path/to/{$view->property}", where $view->property
+ // does not exist), we cannot construct the link, so we skip it.
+ if (!property_exists($view, $property)) {
+ $valid = FALSE;
+ break;
+ }
+ else {
+ $args[] = $view->{$property};
+ }
+ }
+ }
+ // If the link was valid, attach information about it to the renderable
+ // array.
+ if ($valid) {
+ $render_element['#contextual_links'][$module] = array($link['parent path'], $args);
+ $render_element['#views_contextual_links_info'][$module] = array(
+ 'location' => $location,
+ 'view' => $view,
+ 'view_name' => $view->name,
+ 'view_display_id' => $display_id,
+ );
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Returns an array of language names.
+ *
+ * This is a one to one copy of locale_language_list because we can't rely on enabled locale module.
+ *
+ * @param $field
+ * 'name' => names in current language, localized
+ * 'native' => native names
+ * @param $all
+ * Boolean to return all languages or only enabled ones
+ *
+ * @see locale_language_list()
+ */
+function views_language_list($field = 'name', $all = FALSE) {
+ if ($all) {
+ $languages = language_list();
+ }
+ else {
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+ }
+ $list = array();
+ foreach ($languages as $language) {
+ $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
+ }
+ return $list;
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function views_flush_caches() {
+ return array('cache_views', 'cache_views_data');
+}
+
+/**
+ * Implements hook_field_create_instance.
+ */
+function views_field_create_instance($instance) {
+ cache_clear_all('*', 'cache_views', TRUE);
+ cache_clear_all('*', 'cache_views_data', TRUE);
+}
+
+/**
+ * Implements hook_field_update_instance.
+ */
+function views_field_update_instance($instance, $prior_instance) {
+ cache_clear_all('*', 'cache_views', TRUE);
+ cache_clear_all('*', 'cache_views_data', TRUE);
+}
+
+/**
+ * Implements hook_field_delete_instance.
+ */
+function views_field_delete_instance($instance) {
+ cache_clear_all('*', 'cache_views', TRUE);
+ cache_clear_all('*', 'cache_views_data', TRUE);
+}
+
+/**
+ * Invalidate the views cache, forcing a rebuild on the next grab of table data.
+ */
+function views_invalidate_cache() {
+ // Clear the views cache.
+ cache_clear_all('*', 'cache_views', TRUE);
+
+ // Clear the page and block cache.
+ cache_clear_all();
+
+ // Set the menu as needed to be rebuilt.
+ variable_set('menu_rebuild_needed', TRUE);
+
+ // Allow modules to respond to the Views cache being cleared.
+ module_invoke_all('views_invalidate_cache');
+}
+
+/**
+ * Access callback to determine if the user can import Views.
+ *
+ * View imports require an additional access check because they are PHP
+ * code and PHP is more locked down than administer views.
+ */
+function views_import_access() {
+ return user_access('administer views') && user_access('use PHP for settings');
+}
+
+/**
+ * Determine if the logged in user has access to a view.
+ *
+ * This function should only be called from a menu hook or some other
+ * embedded source. Each argument is the result of a call to
+ * views_plugin_access::get_access_callback() which is then used
+ * to determine if that display is accessible. If *any* argument
+ * is accessible, then the view is accessible.
+ */
+function views_access() {
+ $args = func_get_args();
+ foreach ($args as $arg) {
+ if ($arg === TRUE) {
+ return TRUE;
+ }
+
+ if (!is_array($arg)) {
+ continue;
+ }
+
+ list($callback, $arguments) = $arg;
+ $arguments = $arguments ? $arguments : array();
+ // Bring dynamic arguments to the access callback.
+ foreach ($arguments as $key => $value) {
+ if (is_int($value) && isset($args[$value])) {
+ $arguments[$key] = $args[$value];
+ }
+ }
+ if (function_exists($callback) && call_user_func_array($callback, $arguments)) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Access callback for the views_plugin_access_perm access plugin.
+ *
+ * Determine if the specified user has access to a view on the basis of
+ * permissions. If the $account argument is omitted, the current user
+ * is used.
+ */
+function views_check_perm($perms, $account = NULL) {
+ // Backward compatibility to ensure also a single permission string is
+ // properly processed.
+ $perms = is_array($perms) ? $perms : array($perms);
+ if (user_access('access all views', $account)) {
+ return TRUE;
+ }
+ // Perms are handled as OR, as soon one permission allows access TRUE is
+ // returned.
+ foreach ($perms as $perm) {
+ if (user_access($perm, $account)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the views_plugin_access_role access plugin.
+
+ * Determine if the specified user has access to a view on the basis of any of
+ * the requested roles. If the $account argument is omitted, the current user
+ * is used.
+ */
+function views_check_roles($rids, $account = NULL) {
+ global $user;
+ $account = isset($account) ? $account : $user;
+ $roles = array_keys($account->roles);
+ $roles[] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
+ return user_access('access all views', $account) || array_intersect(array_filter($rids), $roles);
+}
+// ------------------------------------------------------------------
+// Functions to help identify views that are running or ran
+
+/**
+ * Set the current 'page view' that is being displayed so that it is easy
+ * for other modules or the theme to identify.
+ */
+function &views_set_page_view($view = NULL) {
+ static $cache = NULL;
+ if (isset($view)) {
+ $cache = $view;
+ }
+
+ return $cache;
+}
+
+/**
+ * Find out what, if any, page view is currently in use. Please note that
+ * this returns a reference, so be careful! You can unintentionally modify the
+ * $view object.
+ *
+ * @return view
+ * A fully formed, empty $view object.
+ */
+function &views_get_page_view() {
+ return views_set_page_view();
+}
+
+/**
+ * Set the current 'current view' that is being built/rendered so that it is
+ * easy for other modules or items in drupal_eval to identify
+ *
+ * @return view
+ */
+function &views_set_current_view($view = NULL) {
+ static $cache = NULL;
+ if (isset($view)) {
+ $cache = $view;
+ }
+
+ return $cache;
+}
+
+/**
+ * Find out what, if any, current view is currently in use. Please note that
+ * this returns a reference, so be careful! You can unintentionally modify the
+ * $view object.
+ *
+ * @return view
+ */
+function &views_get_current_view() {
+ return views_set_current_view();
+}
+
+// ------------------------------------------------------------------
+// Include file helpers
+
+/**
+ * Include views .inc files as necessary.
+ */
+function views_include($file) {
+ ctools_include($file, 'views');
+}
+
+/**
+ * Load views files on behalf of modules.
+ */
+function views_module_include($api, $reset = FALSE) {
+ if ($reset) {
+ $cache = &drupal_static('ctools_plugin_api_info');
+ if (isset($cache['views']['views'])) {
+ unset($cache['views']['views']);
+ }
+ }
+ ctools_include('plugins');
+ return ctools_plugin_api_include('views', $api, views_api_minimum_version(), views_api_version());
+}
+
+/**
+ * Get a list of modules that support the current views API.
+ */
+function views_get_module_apis($api = 'views', $reset = FALSE) {
+ if ($reset) {
+ $cache = &drupal_static('ctools_plugin_api_info');
+ if (isset($cache['views']['views'])) {
+ unset($cache['views']['views']);
+ }
+ }
+ ctools_include('plugins');
+ return ctools_plugin_api_info('views', $api, views_api_minimum_version(), views_api_version());
+}
+
+/**
+ * Include views .css files.
+ */
+function views_add_css($file) {
+ // We set preprocess to FALSE because we are adding the files conditionally,
+ // and we don't want to generate duplicate cache files.
+ // TODO: at some point investigate adding some files unconditionally and
+ // allowing preprocess.
+ drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", array('preprocess' => FALSE));
+}
+
+/**
+ * Include views .js files.
+ */
+function views_add_js($file) {
+ // If javascript has been disabled by the user, never add js files.
+ if (variable_get('views_no_javascript', FALSE)) {
+ return;
+ }
+ static $base = TRUE, $ajax = TRUE;
+ if ($base) {
+ drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
+ $base = FALSE;
+ }
+ if ($ajax && in_array($file, array('ajax', 'ajax_view'))) {
+ drupal_add_library('system', 'drupal.ajax');
+ drupal_add_library('system', 'jquery.form');
+ $ajax = FALSE;
+ }
+ ctools_add_js($file, 'views');
+}
+
+/**
+ * Load views files on behalf of modules.
+ */
+function views_include_handlers($reset = FALSE) {
+ static $finished = FALSE;
+ // Ensure this only gets run once.
+ if ($finished && !$reset) {
+ return;
+ }
+
+ views_include('base');
+ views_include('handlers');
+ views_include('cache');
+ views_include('plugins');
+ views_module_include('views', $reset);
+ $finished = TRUE;
+}
+
+// -----------------------------------------------------------------------
+// Views handler functions
+
+/**
+ * Fetch a handler from the data cache.
+ *
+ * @param $table
+ * The name of the table this handler is from.
+ * @param $field
+ * The name of the field this handler is from.
+ * @param $key
+ * The type of handler. i.e, sort, field, argument, filter, relationship
+ * @param $override
+ * Override the actual handler object with this class. Used for
+ * aggregation when the handler is redirected to the aggregation
+ * handler.
+ *
+ * @return views_handler
+ * An instance of a handler object. May be views_handler_broken.
+ */
+function views_get_handler($table, $field, $key, $override = NULL) {
+ static $recursion_protection = array();
+
+ $data = views_fetch_data($table, FALSE);
+ $handler = NULL;
+ views_include('handlers');
+
+ // Support old views_data entries conversion.
+
+ // Support conversion on table level.
+ if (isset($data['moved to'])) {
+ $moved = array($data['moved to'], $field);
+ }
+ // Support conversion on datafield level.
+ if (isset($data[$field]['moved to'])) {
+ $moved = $data[$field]['moved to'];
+ }
+ // Support conversion on handler level.
+ if (isset($data[$field][$key]['moved to'])) {
+ $moved = $data[$field][$key]['moved to'];
+ }
+
+ if (isset($data[$field][$key]) || !empty($moved)) {
+ if (!empty($moved)) {
+ list($moved_table, $moved_field) = $moved;
+ if (!empty($recursion_protection[$moved_table][$moved_field])) {
+ // recursion detected!
+ return NULL;
+ }
+
+ $recursion_protection[$moved_table][$moved_field] = TRUE;
+ $handler = views_get_handler($moved_table, $moved_field, $key, $override);
+ $recursion_protection = array();
+ if ($handler) {
+ // store these values so we know what we were originally called.
+ $handler->original_table = $table;
+ $handler->original_field = $field;
+ if (empty($handler->actual_table)) {
+ $handler->actual_table = $moved_table;
+ $handler->actual_field = $moved_field;
+ }
+ }
+ return $handler;
+ }
+
+ // Set up a default handler:
+ if (empty($data[$field][$key]['handler'])) {
+ $data[$field][$key]['handler'] = 'views_handler_' . $key;
+ }
+
+ if ($override) {
+ $data[$field][$key]['override handler'] = $override;
+ }
+
+ $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key);
+ }
+
+ if ($handler) {
+ return $handler;
+ }
+
+ // DEBUG -- identify missing handlers
+ vpr("Missing handler: @table @field @key", array('@table' => $table, '@field' => $field, '@key' => $key));
+ $broken = array(
+ 'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)),
+ 'handler' => 'views_handler_' . $key . '_broken',
+ 'table' => $table,
+ 'field' => $field,
+ );
+ return _views_create_handler($broken, 'handler', $key);
+}
+
+/**
+ * Fetch Views' data from the cache
+ */
+function views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
+ views_include('cache');
+ return _views_fetch_data($table, $move, $reset);
+}
+
+// -----------------------------------------------------------------------
+// Views plugin functions
+
+/**
+ * Fetch the plugin data from cache.
+ */
+function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
+ views_include('cache');
+ return _views_fetch_plugin_data($type, $plugin, $reset);
+}
+
+/**
+ * Fetch a list of all base tables available
+ *
+ * @param $type
+ * Either 'display', 'style' or 'row'
+ * @param $key
+ * For style plugins, this is an optional type to restrict to. May be 'normal',
+ * 'summary', 'feed' or others based on the needs of the display.
+ * @param $base
+ * An array of possible base tables.
+ *
+ * @return
+ * A keyed array of in the form of 'base_table' => 'Description'.
+ */
+function views_fetch_plugin_names($type, $key = NULL, $base = array()) {
+ $data = views_fetch_plugin_data();
+
+ $plugins[$type] = array();
+
+ foreach ($data[$type] as $id => $plugin) {
+ // Skip plugins that don't conform to our key.
+ if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) {
+ continue;
+ }
+ if (empty($plugin['no ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) {
+ $plugins[$type][$id] = $plugin['title'];
+ }
+ }
+
+ if (!empty($plugins[$type])) {
+ asort($plugins[$type]);
+ return $plugins[$type];
+ }
+ // fall-through
+ return array();
+}
+
+/**
+ * Get a handler for a plugin
+ *
+ * @return views_plugin
+ *
+ * The created plugin object.
+ */
+function views_get_plugin($type, $plugin, $reset = FALSE) {
+ views_include('handlers');
+ $definition = views_fetch_plugin_data($type, $plugin, $reset);
+ if (!empty($definition)) {
+ return _views_create_handler($definition, $type);
+ }
+}
+
+/**
+ * Load the current enabled localization plugin.
+ *
+ * @return The name of the localization plugin.
+ */
+function views_get_localization_plugin() {
+ $plugin = variable_get('views_localization_plugin', '');
+ // Provide sane default values for the localization plugin.
+ if (empty($plugin)) {
+ if (module_exists('locale')) {
+ $plugin = 'core';
+ }
+ else {
+ $plugin = 'none';
+ }
+ }
+
+ return $plugin;
+}
+
+// -----------------------------------------------------------------------
+// Views database functions
+
+/**
+ * Get all view templates.
+ *
+ * Templates are special in-code views that are never active, but exist only
+ * to be cloned into real views as though they were templates.
+ */
+function views_get_all_templates() {
+ $templates = array();
+ $modules = views_module_include('views_template');
+
+ foreach ($modules as $module => $info) {
+ $function = $module . '_views_templates';
+ if (function_exists($function)) {
+ $new = $function();
+ if ($new && is_array($new)) {
+ $templates = array_merge($new, $templates);
+ }
+ }
+ }
+
+ return $templates;
+}
+
+/**
+ * Create an empty view to work with.
+ *
+ * @return view
+ * A fully formed, empty $view object. This object must be populated before
+ * it can be successfully saved.
+ */
+function views_new_view() {
+ views_include('view');
+ $view = new view();
+ $view->vid = 'new';
+ $view->add_display('default');
+
+ return $view;
+}
+
+/**
+ * Return a list of all views and display IDs that have a particular
+ * setting in their display's plugin settings.
+ *
+ * @return
+ * @code
+ * array(
+ * array($view, $display_id),
+ * array($view, $display_id),
+ * );
+ * @endcode
+ */
+function views_get_applicable_views($type) {
+ // @todo: Use a smarter flagging system so that we don't have to
+ // load every view for this.
+ $result = array();
+ $views = views_get_all_views();
+
+ foreach ($views as $view) {
+ // Skip disabled views.
+ if (!empty($view->disabled)) {
+ continue;
+ }
+
+ if (empty($view->display)) {
+ // Skip this view as it is broken.
+ vsm(t("Skipping broken view @view", array('@view' => $view->name)));
+ continue;
+ }
+
+ // Loop on array keys because something seems to muck with $view->display
+ // a bit in PHP4.
+ foreach (array_keys($view->display) as $id) {
+ $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
+ if (!empty($plugin[$type])) {
+ // This view uses hook menu. Clone it so that different handlers
+ // don't trip over each other, and add it to the list.
+ $v = $view->clone_view();
+ if ($v->set_display($id) && $v->display_handler->get_option('enabled')) {
+ $result[] = array($v, $id);
+ }
+ // In PHP 4.4.7 and presumably earlier, if we do not unset $v
+ // here, we will find that it actually overwrites references
+ // possibly due to shallow copying issues.
+ unset($v);
+ }
+ }
+ }
+ return $result;
+}
+
+/**
+ * Return an array of all views as fully loaded $view objects.
+ *
+ * @param $reset
+ * If TRUE, reset the static cache forcing views to be reloaded.
+ */
+function views_get_all_views($reset = FALSE) {
+ ctools_include('export');
+ return ctools_export_crud_load_all('views_view', $reset);
+}
+
+/**
+ * Returns an array of all enabled views, as fully loaded $view objects.
+ */
+function views_get_enabled_views() {
+ $views = views_get_all_views();
+ return array_filter($views, 'views_view_is_enabled');
+}
+
+/**
+ * Returns an array of all disabled views, as fully loaded $view objects.
+ */
+function views_get_disabled_views() {
+ $views = views_get_all_views();
+ return array_filter($views, 'views_view_is_disabled');
+}
+
+/**
+ * Return an array of view as options array, that can be used by select,
+ * checkboxes and radios as #options.
+ *
+ * @param bool $views_only
+ * If TRUE, only return views, not displays.
+ * @param string $filter
+ * Filters the views on status. Can either be 'all' (default), 'enabled' or
+ * 'disabled'
+ * @param mixed $exclude_view
+ * view or current display to exclude
+ * either a
+ * - views object (containing $exclude_view->name and $exclude_view->current_display)
+ * - views name as string: e.g. my_view
+ * - views name and display id (separated by ':'): e.g. my_view:default
+ * @param bool $optgroup
+ * If TRUE, returns an array with optgroups for each view (will be ignored for
+ * $views_only = TRUE). Can be used by select
+ * @param bool $sort
+ * If TRUE, the list of views is sorted ascending.
+ *
+ * @return array
+ * an associative array for use in select.
+ * - key: view name and display id separated by ':', or the view name only
+ */
+function views_get_views_as_options($views_only = FALSE, $filter = 'all', $exclude_view = NULL, $optgroup = FALSE, $sort = FALSE) {
+
+ // Filter the big views array.
+ switch ($filter) {
+ case 'all':
+ case 'disabled':
+ case 'enabled':
+ $func = "views_get_{$filter}_views";
+ $views = $func();
+ break;
+ default:
+ return array();
+ }
+
+ // Prepare exclude view strings for comparison.
+ if (empty($exclude_view)) {
+ $exclude_view_name = '';
+ $exclude_view_display = '';
+ }
+ elseif (is_object($exclude_view)) {
+ $exclude_view_name = $exclude_view->name;
+ $exclude_view_display = $exclude_view->current_display;
+ }
+ else {
+ list($exclude_view_name, $exclude_view_display) = explode(':', $exclude_view);
+ }
+
+ $options = array();
+ foreach ($views as $view) {
+ // Return only views.
+ if ($views_only && $view->name != $exclude_view_name) {
+ $options[$view->name] = $view->get_human_name();
+ }
+ // Return views with display ids.
+ else {
+ foreach ($view->display as $display_id => $display) {
+ if (!($view->name == $exclude_view_name && $display_id == $exclude_view_display)) {
+ if ($optgroup) {
+ $options[$view->name][$view->name . ':' . $display->id] = t('@view : @display', array('@view' => $view->name, '@display' => $display->id));
+ }
+ else {
+ $options[$view->name . ':' . $display->id] = t('View: @view - Display: @display', array('@view' => $view->name, '@display' => $display->id));
+ }
+ }
+ }
+ }
+ }
+ if ($sort) {
+ ksort($options);
+ }
+ return $options;
+}
+
+/**
+ * Returns TRUE if a view is enabled, FALSE otherwise.
+ */
+function views_view_is_enabled($view) {
+ return empty($view->disabled);
+}
+
+/**
+ * Returns TRUE if a view is disabled, FALSE otherwise.
+ */
+function views_view_is_disabled($view) {
+ return !empty($view->disabled);
+}
+
+/**
+ * Get a view from the database or from default views.
+ *
+ * This function is just a static wrapper around views::load(). This function
+ * isn't called 'views_load()' primarily because it might get a view
+ * from the default views which aren't technically loaded from the database.
+ *
+ * @param $name
+ * The name of the view.
+ * @param $reset
+ * If TRUE, reset this entry in the load cache.
+ * @return view
+ * A reference to the $view object. Use $reset if you're sure you want
+ * a fresh one.
+ */
+function views_get_view($name, $reset = FALSE) {
+ if ($reset) {
+ $cache = &drupal_static('ctools_export_load_object');
+ if (isset($cache['views_view'][$name])) {
+ unset($cache['views_view'][$name]);
+ }
+ }
+
+ ctools_include('export');
+ $view = ctools_export_crud_load('views_view', $name);
+ if ($view) {
+ $view->update();
+ return $view->clone_view();
+ }
+}
+
+/**
+ * Find the real location of a table.
+ *
+ * If a table has moved, find the new name of the table so that we can
+ * change its name directly in options where necessary.
+ */
+function views_move_table($table) {
+ $data = views_fetch_data($table, FALSE);
+ if (isset($data['moved to'])) {
+ $table = $data['moved to'];
+ }
+
+ return $table;
+}
+
+/**
+ * Export callback to load the view subrecords, which are the displays.
+ */
+function views_load_display_records(&$views) {
+ // Get vids from the views.
+ $names = array();
+ foreach ($views as $view) {
+ if (empty($view->display)) {
+ $names[$view->vid] = $view->name;
+ }
+ }
+
+ if (empty($names)) {
+ return;
+ }
+
+ foreach (view::db_objects() as $key) {
+ $object_name = "views_$key";
+ $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
+ array(':vids' => array_keys($names)));
+
+ foreach ($result as $data) {
+ $object = new $object_name(FALSE);
+ $object->load_row($data);
+
+ // Because it can get complicated with this much indirection,
+ // make a shortcut reference.
+ $location = &$views[$names[$object->vid]]->$key;
+
+ // If we have a basic id field, load the item onto the view based on
+ // this ID, otherwise push it on.
+ if (!empty($object->id)) {
+ $location[$object->id] = $object;
+ }
+ else {
+ $location[] = $object;
+ }
+ }
+ }
+}
+
+/**
+ * Export CRUD callback to save a view.
+ */
+function views_save_view(&$view) {
+ return $view->save();
+}
+
+/**
+ * Export CRUD callback to delete a view.
+ */
+function views_delete_view(&$view) {
+ return $view->delete(TRUE);
+}
+
+/**
+ * Export CRUD callback to export a view.
+ */
+function views_export_view(&$view, $indent = '') {
+ return $view->export($indent);
+}
+
+/**
+ * Export callback to change view status.
+ */
+function views_export_status($view, $status) {
+ ctools_export_set_object_status($view, $status);
+ views_invalidate_cache();
+}
+
+// ------------------------------------------------------------------
+// Views debug helper functions
+
+/**
+ * Provide debug output for Views.
+ *
+ * This relies on devel.module
+ * or on the debug() function if you use a simpletest.
+ *
+ * @param $message
+ * The message/variable which should be debugged.
+ * This either could be
+ * * an array/object which is converted to pretty output
+ * * a translation source string which is used together with the parameter placeholders.
+ *
+ * @param $placeholder
+ * The placeholders which are used for the translation source string.
+ */
+function views_debug($message, $placeholders = array()) {
+ if (!is_string($message)) {
+ $output = '<pre>' . var_export($message, TRUE) . '</pre>';
+ }
+ if (module_exists('devel') && variable_get('views_devel_output', FALSE) && user_access('access devel information')) {
+ $devel_region = variable_get('views_devel_region', 'footer');
+ if ($devel_region == 'watchdog') {
+ $output = $message;
+ watchdog('views_logging', $output, $placeholders);
+ }
+ else if ($devel_region == 'drupal_debug') {
+ $output = empty($output) ? t($message, $placeholders) : $output;
+ dd($output);
+ }
+ else {
+ $output = empty($output) ? t($message, $placeholders) : $output;
+ dpm($output);
+ }
+ }
+ elseif (isset($GLOBALS['drupal_test_info'])) {
+ $output = empty($output) ? t($message, $placeholders) : $output;
+ debug($output);
+ }
+}
+
+/**
+ * Shortcut to views_debug()
+ */
+function vpr($message, $placeholders = array()) {
+ views_debug($message, $placeholders);
+}
+
+/**
+ * Debug messages
+ */
+function vsm($message) {
+ if (module_exists('devel')) {
+ dpm($message);
+ }
+}
+
+function views_trace() {
+ $message = '';
+ foreach (debug_backtrace() as $item) {
+ if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
+ $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
+ }
+ }
+ return $message;
+}
+
+function vsm_trace() {
+ vsm(views_trace());
+}
+
+function vpr_trace() {
+ dpr(views_trace());
+}
+
+// ------------------------------------------------------------------
+// Views form (View with form elements)
+
+/**
+ * Returns TRUE if the passed-in view contains handlers with views form
+ * implementations, FALSE otherwise.
+ */
+function views_view_has_form_elements($view) {
+ foreach ($view->field as $field) {
+ if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
+ return TRUE;
+ }
+ }
+ $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
+ $empty = empty($view->result);
+ foreach ($area_handlers as $area) {
+ if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * This is the entry function. Just gets the form for the current step.
+ * The form is always assumed to be multistep, even if it has only one
+ * step (the default 'views_form_views_form' step). That way it is actually
+ * possible for modules to have a multistep form if they need to.
+ */
+function views_form($form, &$form_state, $view, $output) {
+ $form_state['step'] = isset($form_state['step']) ? $form_state['step'] : 'views_form_views_form';
+ // Cache the built form to prevent it from being rebuilt prior to validation
+ // and submission, which could lead to data being processed incorrectly,
+ // because the views rows (and thus, the form elements as well) have changed
+ // in the meantime.
+ $form_state['cache'] = TRUE;
+
+ $form = array();
+ $query = drupal_get_query_parameters($_GET, array('q'));
+ $form['#action'] = url($view->get_url(), array('query' => $query));
+ // Tell the preprocessor whether it should hide the header, footer, pager...
+ $form['show_view_elements'] = array(
+ '#type' => 'value',
+ '#value' => ($form_state['step'] == 'views_form_views_form') ? TRUE : FALSE,
+ );
+
+ $form = $form_state['step']($form, $form_state, $view, $output);
+ return $form;
+}
+
+/**
+ * Callback for the main step of a Views form.
+ * Invoked by views_form().
+ */
+function views_form_views_form($form, &$form_state, $view, $output) {
+ $form['#prefix'] = '<div class="views-form">';
+ $form['#suffix'] = '</div>';
+ $form['#theme'] = 'views_form_views_form';
+ $form['#validate'][] = 'views_form_views_form_validate';
+ $form['#submit'][] = 'views_form_views_form_submit';
+
+ // Add the output markup to the form array so that it's included when the form
+ // array is passed to the theme function.
+ $form['output'] = array(
+ '#type' => 'markup',
+ '#markup' => $output,
+ // This way any additional form elements will go before the view
+ // (below the exposed widgets).
+ '#weight' => 50,
+ );
+
+ $substitutions = array();
+ foreach ($view->field as $field_name => $field) {
+ $form_element_name = $field_name;
+ if (method_exists($field, 'form_element_name')) {
+ $form_element_name = $field->form_element_name();
+ }
+ $method_form_element_row_id_exists = FALSE;
+ if (method_exists($field, 'form_element_row_id')) {
+ $method_form_element_row_id_exists = TRUE;
+ }
+
+ // If the field provides a views form, allow it to modify the $form array.
+ $has_form = FALSE;
+ if (property_exists($field, 'views_form_callback')) {
+ $callback = $field->views_form_callback;
+ $callback($view, $field, $form, $form_state);
+ $has_form = TRUE;
+ }
+ elseif (method_exists($field, 'views_form')) {
+ $field->views_form($form, $form_state);
+ $has_form = TRUE;
+ }
+
+ // Build the substitutions array for use in the theme function.
+ if ($has_form) {
+ foreach ($view->result as $row_id => $row) {
+ if ($method_form_element_row_id_exists) {
+ $form_element_row_id = $field->form_element_row_id($row_id);
+ }
+ else {
+ $form_element_row_id = $row_id;
+ }
+
+ $substitutions[] = array(
+ 'placeholder' => '<!--form-item-' . $form_element_name . '--' . $form_element_row_id . '-->',
+ 'field_name' => $form_element_name,
+ 'row_id' => $form_element_row_id,
+ );
+ }
+ }
+ }
+
+ // Give the area handlers a chance to extend the form.
+ $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
+ $empty = empty($view->result);
+ foreach ($area_handlers as $area) {
+ if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
+ $area->views_form($form, $form_state);
+ }
+ }
+
+ $form['#substitutions'] = array(
+ '#type' => 'value',
+ '#value' => $substitutions,
+ );
+ $form['actions'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('form-actions')),
+ '#weight' => 100,
+ );
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate handler for the first step of the views form.
+ * Calls any existing views_form_validate functions located
+ * on the views fields.
+ */
+function views_form_views_form_validate($form, &$form_state) {
+ $view = $form_state['build_info']['args'][0];
+
+ // Call the validation method on every field handler that has it.
+ foreach ($view->field as $field_name => $field) {
+ if (method_exists($field, 'views_form_validate')) {
+ $field->views_form_validate($form, $form_state);
+ }
+ }
+
+ // Call the validate method on every area handler that has it.
+ foreach (array('header', 'footer') as $area) {
+ foreach ($view->{$area} as $area_name => $area_handler) {
+ if (method_exists($area_handler, 'views_form_validate')) {
+ $area_handler->views_form_validate($form, $form_state);
+ }
+ }
+ }
+}
+
+/**
+ * Submit handler for the first step of the views form.
+ * Calls any existing views_form_submit functions located
+ * on the views fields.
+ */
+function views_form_views_form_submit($form, &$form_state) {
+ $view = $form_state['build_info']['args'][0];
+
+ // Call the submit method on every field handler that has it.
+ foreach ($view->field as $field_name => $field) {
+ if (method_exists($field, 'views_form_submit')) {
+ $field->views_form_submit($form, $form_state);
+ }
+ }
+
+ // Call the submit method on every area handler that has it.
+ foreach (array('header', 'footer') as $area) {
+ foreach ($view->{$area} as $area_name => $area_handler) {
+ if (method_exists($area_handler, 'views_form_submit')) {
+ $area_handler->views_form_submit($form, $form_state);
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------
+// Exposed widgets form
+
+/**
+ * Form builder for the exposed widgets form.
+ *
+ * Be sure that $view and $display are references.
+ */
+function views_exposed_form($form, &$form_state) {
+ // Don't show the form when batch operations are in progress.
+ if ($batch = batch_get() && isset($batch['current_set'])) {
+ return array(
+ // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
+ '#theme' => '',
+ );
+ }
+
+ // Make sure that we validate because this form might be submitted
+ // multiple times per page.
+ $form_state['must_validate'] = TRUE;
+ $view = &$form_state['view'];
+ $display = &$form_state['display'];
+
+ $form_state['input'] = $view->get_exposed_input();
+
+ // Let form plugins know this is for exposed widgets.
+ $form_state['exposed'] = TRUE;
+ // Check if the form was already created
+ if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
+ return $cache;
+ }
+
+ $form['#info'] = array();
+
+ if (!variable_get('clean_url', FALSE)) {
+ $form['q'] = array(
+ '#type' => 'hidden',
+ '#value' => $view->get_url(),
+ );
+ }
+
+ // Go through each handler and let it generate its exposed widget.
+ foreach ($view->display_handler->handlers as $type => $value) {
+ foreach ($view->$type as $id => $handler) {
+ if ($handler->can_expose() && $handler->is_exposed()) {
+ // Grouped exposed filters have their own forms.
+ // Instead of render the standard exposed form, a new Select or
+ // Radio form field is rendered with the available groups.
+ // When an user choose an option the selected value is split
+ // into the operator and value that the item represents.
+ if ($handler->is_a_group()) {
+ $handler->group_form($form, $form_state);
+ $id = $handler->options['group_info']['identifier'];
+ }
+ else {
+ $handler->exposed_form($form, $form_state);
+ }
+ if ($info = $handler->exposed_info()) {
+ $form['#info']["$type-$id"] = $info;
+ }
+ }
+ }
+ }
+
+ $form['submit'] = array(
+ '#name' => '', // prevent from showing up in $_GET.
+ '#type' => 'submit',
+ '#value' => t('Apply'),
+ '#id' => drupal_html_id('edit-submit-' . $view->name),
+ );
+
+ $form['#action'] = url($view->display_handler->get_url());
+ $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
+ $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
+// $form['#attributes']['class'] = array('views-exposed-form');
+
+ // If using AJAX, we need the form plugin.
+ if ($view->use_ajax) {
+ drupal_add_library('system', 'jquery.form');
+ }
+ ctools_include('dependent');
+
+ $exposed_form_plugin = $form_state['exposed_form_plugin'];
+ $exposed_form_plugin->exposed_form_alter($form, $form_state);
+
+ // Save the form
+ views_exposed_form_cache($view->name, $view->current_display, $form);
+
+ return $form;
+}
+
+/**
+ * Implement hook_form_alter for the exposed form.
+ *
+ * Since the exposed form is a GET form, we don't want it to send a wide
+ * variety of information.
+ */
+function views_form_views_exposed_form_alter(&$form, &$form_state) {
+ $form['form_build_id']['#access'] = FALSE;
+ $form['form_token']['#access'] = FALSE;
+ $form['form_id']['#access'] = FALSE;
+}
+
+/**
+ * Validate handler for exposed filters
+ */
+function views_exposed_form_validate(&$form, &$form_state) {
+ foreach (array('field', 'filter') as $type) {
+ $handlers = &$form_state['view']->$type;
+ foreach ($handlers as $key => $handler) {
+ $handlers[$key]->exposed_validate($form, $form_state);
+ }
+ }
+ $exposed_form_plugin = $form_state['exposed_form_plugin'];
+ $exposed_form_plugin->exposed_form_validate($form, $form_state);
+}
+
+/**
+ * Submit handler for exposed filters
+ */
+function views_exposed_form_submit(&$form, &$form_state) {
+ foreach (array('field', 'filter') as $type) {
+ $handlers = &$form_state['view']->$type;
+ foreach ($handlers as $key => $info) {
+ $handlers[$key]->exposed_submit($form, $form_state);
+ }
+ }
+ $form_state['view']->exposed_data = $form_state['values'];
+ $form_state['view']->exposed_raw_input = array();
+
+
+ $exclude = array('q', 'submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
+ $exposed_form_plugin = $form_state['exposed_form_plugin'];
+ $exposed_form_plugin->exposed_form_submit($form, $form_state, $exclude);
+
+ foreach ($form_state['values'] as $key => $value) {
+ if (!in_array($key, $exclude)) {
+ $form_state['view']->exposed_raw_input[$key] = $value;
+ }
+ }
+}
+
+/**
+ * Save the Views exposed form for later use.
+ *
+ * @param $views_name
+ * String. The views name.
+ * @param $display_name
+ * String. The current view display name.
+ * @param $form_output
+ * Array (optional). The form structure. Only needed when inserting the value.
+ * @return
+ * Array. The form structure, if any. Otherwise, return FALSE.
+ */
+function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
+ // When running tests for exposed filters, this cache should
+ // be cleared between each test.
+ $views_exposed = &drupal_static(__FUNCTION__);
+
+ // Save the form output
+ if (!empty($form_output)) {
+ $views_exposed[$views_name][$display_name] = $form_output;
+ return;
+ }
+
+ // Return the form output, if any
+ return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
+}
+
+// ------------------------------------------------------------------
+// Misc helpers
+
+/**
+ * Build a list of theme function names for use most everywhere.
+ */
+function views_theme_functions($hook, $view, $display = NULL) {
+ require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'views') . "/theme/theme.inc";
+ return _views_theme_functions($hook, $view, $display);
+}
+
+/**
+ * Substitute current time; this works with cached queries.
+ */
+function views_views_query_substitutions($view) {
+ global $language_content;
+ return array(
+ '***CURRENT_VERSION***' => VERSION,
+ '***CURRENT_TIME***' => REQUEST_TIME,
+ '***CURRENT_LANGUAGE***' => $language_content->language,
+ '***DEFAULT_LANGUAGE***' => language_default('language'),
+ );
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * This is the hook_query_alter() for queries tagged by Views and is used to
+ * add in substitutions from hook_views_query_substitutions().
+ */
+function views_query_views_alter(QueryAlterableInterface $query) {
+ $substitutions = $query->getMetaData('views_substitutions');
+ $tables =& $query->getTables();
+ $where =& $query->conditions();
+
+ // Replaces substitions in tables.
+ foreach ($tables as $table_name => $table_metadata) {
+ foreach ($table_metadata['arguments'] as $replacement_key => $value) {
+ if (isset($substitutions[$value])) {
+ $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value];
+ }
+ }
+ }
+
+ // Replaces substitions in filter criterias.
+ _views_query_tag_alter_condition($query, $where, $substitutions);
+}
+
+/**
+ * Replaces the substitutions recursive foreach condition.
+ */
+function _views_query_tag_alter_condition(QueryAlterableInterface $query, &$conditions, $substitutions) {
+ foreach ($conditions as $condition_id => &$condition) {
+ if (is_numeric($condition_id)) {
+ if (is_string($condition['field'])) {
+ $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
+ }
+ elseif (is_object($condition['field'])) {
+ $sub_conditions =& $condition['field']->conditions();
+ _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
+ }
+ // $condition['value'] is a subquery so alter the subquery recursive.
+ // Therefore take sure to get the metadata of the main query.
+ if (is_object($condition['value'])) {
+ $subquery = $condition['value'];
+ $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
+ views_query_views_alter($condition['value']);
+ }
+ elseif (isset($condition['value'])) {
+ $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
+ }
+ }
+ }
+}
+
+/**
+ * Embed a view using a PHP snippet.
+ *
+ * This function is meant to be called from PHP snippets, should one wish to
+ * embed a view in a node or something. It's meant to provide the simplest
+ * solution and doesn't really offer a lot of options, but breaking the function
+ * apart is pretty easy, and this provides a worthwhile guide to doing so.
+ *
+ * Note that this function does NOT display the title of the view. If you want
+ * to do that, you will need to do what this function does manually, by
+ * loading the view, getting the preview and then getting $view->get_title().
+ *
+ * @param $name
+ * The name of the view to embed.
+ * @param $display_id
+ * The display id to embed. If unsure, use 'default', as it will always be
+ * valid. But things like 'page' or 'block' should work here.
+ * @param ...
+ * Any additional parameters will be passed as arguments.
+ */
+function views_embed_view($name, $display_id = 'default') {
+ $args = func_get_args();
+ array_shift($args); // remove $name
+ if (count($args)) {
+ array_shift($args); // remove $display_id
+ }
+
+ $view = views_get_view($name);
+ if (!$view || !$view->access($display_id)) {
+ return;
+ }
+
+ return $view->preview($display_id, $args);
+}
+
+/**
+ * Get the result of a view.
+ *
+ * @param string $name
+ * The name of the view to retrieve the data from.
+ * @param string $display_id
+ * The display id. On the edit page for the view in question, you'll find
+ * a list of displays at the left side of the control area. "Master"
+ * will be at the top of that list. Hover your cursor over the name of the
+ * display you want to use. An URL will appear in the status bar of your
+ * browser. This is usually at the bottom of the window, in the chrome.
+ * Everything after #views-tab- is the display ID, e.g. page_1.
+ * @param ...
+ * Any additional parameters will be passed as arguments.
+ * @return array
+ * An array containing an object for each view item.
+ */
+function views_get_view_result($name, $display_id = NULL) {
+ $args = func_get_args();
+ array_shift($args); // remove $name
+ if (count($args)) {
+ array_shift($args); // remove $display_id
+ }
+
+ $view = views_get_view($name);
+ if (is_object($view)) {
+ if (is_array($args)) {
+ $view->set_arguments($args);
+ }
+ if (is_string($display_id)) {
+ $view->set_display($display_id);
+ }
+ else {
+ $view->init_display();
+ }
+ $view->pre_execute();
+ $view->execute();
+ return $view->result;
+ }
+ else {
+ return array();
+ }
+}
+
+/**
+ * Export a field.
+ */
+function views_var_export($var, $prefix = '', $init = TRUE) {
+ if (is_array($var)) {
+ if (empty($var)) {
+ $output = 'array()';
+ }
+ else {
+ $output = "array(\n";
+ foreach ($var as $key => $value) {
+ $output .= " " . views_var_export($key, '', FALSE) . " => " . views_var_export($value, ' ', FALSE) . ",\n";
+ }
+ $output .= ')';
+ }
+ }
+ elseif (is_bool($var)) {
+ $output = $var ? 'TRUE' : 'FALSE';
+ }
+ elseif (is_string($var) && strpos($var, "\n") !== FALSE) {
+ // Replace line breaks in strings with a token for replacement
+ // at the very end. This protects multi-line strings from
+ // unintentional indentation.
+ $var = str_replace("\n", "***BREAK***", $var);
+ $output = var_export($var, TRUE);
+ }
+ else {
+ $output = var_export($var, TRUE);
+ }
+
+ if ($prefix) {
+ $output = str_replace("\n", "\n$prefix", $output);
+ }
+
+ if ($init) {
+ $output = str_replace("***BREAK***", "\n", $output);
+ }
+
+ return $output;
+}
+
+/**
+ * Prepare a string for use as a valid CSS identifier (element, class or ID name).
+ * This function is similar to a core version but with more sane filter values.
+ *
+ * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
+ * CSS identifiers (including element names, classes, and IDs in selectors.)
+ *
+ * @param $identifier
+ * The identifier to clean.
+ * @param $filter
+ * An array of string replacements to use on the identifier.
+ * @return
+ * The cleaned identifier.
+ *
+ * @see drupal_clean_css_identifier()
+ */
+function views_clean_css_identifier($identifier, $filter = array(' ' => '-', '/' => '-', '[' => '-', ']' => '')) {
+ // By default, we filter using Drupal's coding standards.
+ $identifier = strtr($identifier, $filter);
+
+ // Valid characters in a CSS identifier are:
+ // - the hyphen (U+002D)
+ // - a-z (U+0030 - U+0039)
+ // - A-Z (U+0041 - U+005A)
+ // - the underscore (U+005F)
+ // - 0-9 (U+0061 - U+007A)
+ // - ISO 10646 characters U+00A1 and higher
+ // We strip out any character not in the above list.
+ $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);
+
+ return $identifier;
+}
+
+/**
+ * Implement hook_views_exportables().
+ */
+function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
+ $all_views = views_get_all_views();
+ if ($op == 'list') {
+
+ foreach ($all_views as $name => $view) {
+ // in list, $views is a list of tags.
+ if (empty($views) || in_array($view->tag, $views)) {
+ $return[$name] = array(
+ 'name' => check_plain($name),
+ 'desc' => check_plain($view->description),
+ 'tag' => check_plain($view->tag)
+ );
+ }
+ }
+ return $return;
+ }
+
+ if ($op == 'export') {
+ $code = "/**\n";
+ $code .= " * Implement hook_views_default_views().\n";
+ $code .= " */\n";
+ $code .= "function " . $name . "_views_default_views() {\n";
+ foreach ($views as $view => $truth) {
+ $code .= " /*\n";
+ $code .= " * View " . var_export($all_views[$view]->name, TRUE) . "\n";
+ $code .= " */\n";
+ $code .= $all_views[$view]->export(' ');
+ $code .= ' $views[$view->name] = $view;' . "\n\n";
+ }
+ $code .= " return \$views;\n";
+ $code .= "}\n";
+
+ return $code;
+ }
+}
+
+/**
+ * #process callback to see if we need to check_plain() the options.
+ *
+ * Since FAPI is inconsistent, the #options are sanitized for you in all cases
+ * _except_ checkboxes. We have form elements that are sometimes 'select' and
+ * sometimes 'checkboxes', so we need decide late in the form rendering cycle
+ * if the options need to be sanitized before they're rendered. This callback
+ * inspects the type, and if it's still 'checkboxes', does the sanitation.
+ */
+function views_process_check_options($element, &$form_state) {
+ if ($element['#type'] == 'checkboxes' || $element['#type'] == 'checkbox') {
+ $element['#options'] = array_map('check_plain', $element['#options']);
+ }
+ return $element;
+}
+
+/**
+ * Trim the field down to the specified length.
+ *
+ * @param $alter
+ * - max_length: Maximum length of the string, the rest gets truncated.
+ * - word_boundary: Trim only on a word boundary.
+ * - ellipsis: Show an ellipsis (...) at the end of the trimmed string.
+ * - html: Take sure that the html is correct.
+ *
+ * @param $value
+ * The string which should be trimmed.
+ */
+function views_trim_text($alter, $value) {
+ if (drupal_strlen($value) > $alter['max_length']) {
+ $value = drupal_substr($value, 0, $alter['max_length']);
+ // TODO: replace this with cleanstring of ctools
+ if (!empty($alter['word_boundary'])) {
+ $regex = "(.*)\b.+";
+ if (function_exists('mb_ereg')) {
+ mb_regex_encoding('UTF-8');
+ $found = mb_ereg($regex, $value, $matches);
+ }
+ else {
+ $found = preg_match("/$regex/us", $value, $matches);
+ }
+ if ($found) {
+ $value = $matches[1];
+ }
+ }
+ // Remove scraps of HTML entities from the end of a strings
+ $value = rtrim(preg_replace('/(?:<(?!.+>)|&(?!.+;)).*$/us', '', $value));
+
+ if (!empty($alter['ellipsis'])) {
+ $value .= t('...');
+ }
+ }
+ if (!empty($alter['html'])) {
+ $value = _filter_htmlcorrector($value);
+ }
+
+ return $value;
+}
+
+/**
+ * Adds one to each key of the array.
+ *
+ * For example array(0 => 'foo') would be array(1 => 'foo').
+ */
+function views_array_key_plus($array) {
+ $keys = array_keys($array);
+ rsort($keys);
+ foreach ($keys as $key) {
+ $array[$key+1] = $array[$key];
+ unset($array[$key]);
+ }
+ asort($array);
+ return $array;
+}
+
+/**
+ * Report to CTools that we use hook_views_api instead of hook_ctools_plugin_api()
+ */
+function views_ctools_plugin_api_hook_name() {
+ return 'views_api';
+}
+
+// Declare API compatibility on behalf of core modules:
+
+/**
+ * Implements hook_views_api().
+ *
+ * This one is used as the base to reduce errors when updating.
+ */
+function views_views_api() {
+ return array(
+ // in your modules do *not* use views_api_version()!!!
+ 'api' => views_api_version(),
+ 'path' => drupal_get_path('module', 'views') . '/modules',
+ );
+}
+
+if (!function_exists('aggregator_views_api')) {
+ function aggregator_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('book_views_api')) {
+ function book_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('comment_views_api')) {
+ function comment_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('field_views_api')) {
+ function field_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('file_views_api')) {
+ function file_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('filter_views_api')) {
+ function filter_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('image_views_api')) {
+ function image_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('locale_views_api')) {
+ function locale_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('node_views_api')) {
+ function node_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('poll_views_api')) {
+ function poll_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('profile_views_api')) {
+ function profile_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('search_views_api')) {
+ function search_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('statistics_views_api')) {
+ function statistics_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('system_views_api')) {
+ function system_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('tracker_views_api')) {
+ function tracker_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('taxonomy_views_api')) {
+ function taxonomy_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('translation_views_api')) {
+ function translation_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('user_views_api')) {
+ function user_views_api() { return views_views_api(); }
+}
+
+if (!function_exists('contact_views_api')) {
+ function contact_views_api() { return views_views_api(); }
+}
diff --git a/sites/all/modules/views/views.tokens.inc b/sites/all/modules/views/views.tokens.inc
new file mode 100644
index 000000000..cc45b5cba
--- /dev/null
+++ b/sites/all/modules/views/views.tokens.inc
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Token integration for the views module.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function views_token_info() {
+ $info['types']['view'] = array(
+ 'name' => t('View'),
+ 'description' => t('Tokens related to views.'),
+ 'needs-data' => 'view',
+ );
+ $info['tokens']['view']['name'] = array(
+ 'name' => t('Name'),
+ 'description' => t('The human-readable name of the view.'),
+ );
+ $info['tokens']['view']['description'] = array(
+ 'name' => t('Description'),
+ 'description' => t('The description of the view.'),
+ );
+ $info['tokens']['view']['machine-name'] = array(
+ 'name' => t('Machine name'),
+ 'description' => t('The machine-readable name of the view.'),
+ );
+ $info['tokens']['view']['title'] = array(
+ 'name' => t('Title'),
+ 'description' => t('The title of current display of the view.'),
+ );
+ $info['tokens']['view']['url'] = array(
+ 'name' => t('URL'),
+ 'description' => t('The URL of the view.'),
+ 'type' => 'url',
+ );
+
+ return $info;
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function views_tokens($type, $tokens, array $data = array(), array $options = array()) {
+ $url_options = array('absolute' => TRUE);
+ if (isset($options['language'])) {
+ $url_options['language'] = $options['language'];
+ }
+ $sanitize = !empty($options['sanitize']);
+ $langcode = isset($options['language']) ? $options['language']->language : NULL;
+
+ $replacements = array();
+
+ if ($type == 'view' && !empty($data['view'])) {
+ $view = $data['view'];
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ case 'name':
+ $replacements[$original] = $sanitize ? check_plain($view->human_name) : $view->human_name;
+ break;
+
+ case 'description':
+ $replacements[$original] = $sanitize ? check_plain($view->description) : $view->description;
+ break;
+
+ case 'machine-name':
+ $replacements[$original] = $view->name;
+ break;
+
+ case 'title':
+ $title = $view->get_title();
+ $replacements[$original] = $sanitize ? check_plain($title) : $title;
+ break;
+
+ case 'url':
+ if ($path = $view->get_url()) {
+ $replacements[$original] = url($path, $url_options);
+ }
+ break;
+ }
+ }
+
+ // [view:url:*] nested tokens. This only works if Token module is installed.
+ if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
+ if ($path = $view->get_url()) {
+ $replacements += token_generate('url', $url_tokens, array('path' => $path), $options);
+ }
+ }
+ }
+
+ return $replacements;
+}
diff --git a/sites/all/modules/views/views_export/views_export.module b/sites/all/modules/views/views_export/views_export.module
new file mode 100644
index 000000000..bcc3c7cf3
--- /dev/null
+++ b/sites/all/modules/views/views_export/views_export.module
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @file
+ * The Views Export module has been removed.
+ *
+ * This note is left here to ensure that users who don't delete their directory
+ * before upgrading have the code removed and to let people looking for it to
+ * know to use the CTools bulk export module instead.
+ */
diff --git a/sites/all/modules/views/views_ui.info b/sites/all/modules/views/views_ui.info
new file mode 100644
index 000000000..ebee3875b
--- /dev/null
+++ b/sites/all/modules/views/views_ui.info
@@ -0,0 +1,15 @@
+name = Views UI
+description = Administrative interface to views. Without this module, you cannot create or edit your views.
+package = Views
+core = 7.x
+configure = admin/structure/views
+dependencies[] = views
+files[] = views_ui.module
+files[] = plugins/views_wizard/views_ui_base_views_wizard.class.php
+
+; Information added by Drupal.org packaging script on 2015-11-06
+version = "7.x-3.13"
+core = "7.x"
+project = "views"
+datestamp = "1446804876"
+
diff --git a/sites/all/modules/views/views_ui.module b/sites/all/modules/views/views_ui.module
new file mode 100644
index 000000000..f35d099f7
--- /dev/null
+++ b/sites/all/modules/views/views_ui.module
@@ -0,0 +1,867 @@
+<?php
+
+/**
+ * @file
+ * Provide structure for the administrative interface to Views.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function views_ui_menu() {
+ $items = array();
+
+ // Minor code reduction technique.
+ $base = array(
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer views'),
+ 'file' => 'includes/admin.inc',
+ );
+
+ // Top-level Views module pages (not tied to a particular View).
+ $items['admin/structure/views/add'] = array(
+ 'title' => 'Add new view',
+ 'page callback' => 'views_ui_add_page',
+ 'type' => MENU_LOCAL_ACTION,
+ ) + $base;
+
+ // Top-level Views module pages (not tied to a particular View).
+ $items['admin/structure/views/add-template'] = array(
+ 'title' => 'Add view from template',
+ 'page callback' => 'views_ui_add_template_page',
+ // Don't show a local action link if there aren't any templates.
+ 'type' => views_get_all_templates() ? MENU_LOCAL_ACTION : MENU_VISIBLE_IN_BREADCRUMB,
+ ) + $base;
+
+ $items['admin/structure/views/import'] = array(
+ 'title' => 'Import',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('views_ui_import_page'),
+ 'access callback' => 'views_import_access',
+ 'type' => MENU_LOCAL_ACTION,
+ ) + $base;
+
+ $items['admin/structure/views/settings'] = array(
+ 'title' => 'Settings',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('views_ui_admin_settings_basic'),
+ 'type' => MENU_LOCAL_TASK,
+ ) + $base;
+ $items['admin/structure/views/settings/basic'] = array(
+ 'title' => 'Basic',
+ 'page arguments' => array('views_ui_admin_settings_basic'),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ ) + $base;
+ $items['admin/structure/views/settings/advanced'] = array(
+ 'title' => 'Advanced',
+ 'page arguments' => array('views_ui_admin_settings_advanced'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 1,
+ ) + $base;
+
+ // The primary Edit View page. Secondary tabs for each Display are added in
+ // views_ui_menu_local_tasks_alter().
+ $items['admin/structure/views/view/%views_ui_cache'] = array(
+ 'title callback' => 'views_ui_edit_page_title',
+ 'title arguments' => array(4),
+ 'page callback' => 'views_ui_edit_page',
+ 'page arguments' => array(4),
+ ) + $base;
+ $items['admin/structure/views/view/%views_ui_cache/edit'] = array(
+ 'title' => 'Edit view',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ 'weight' => -10,
+ 'theme callback' => 'ajax_base_page_theme',
+ ) + $base;
+ $items['admin/structure/views/view/%views_ui_cache/edit/%/ajax'] = array(
+ 'page callback' => 'views_ui_ajax_get_form',
+ 'page arguments' => array('views_ui_edit_form', 4, 6),
+ 'delivery callback' => 'ajax_deliver',
+ 'theme callback' => 'ajax_base_page_theme',
+ 'type' => MENU_CALLBACK,
+ ) + $base;
+ $items['admin/structure/views/view/%views_ui_cache/preview/%'] = array(
+ 'page callback' => 'views_ui_build_preview',
+ 'page arguments' => array(4, 6),
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ ) + $base;
+ $items['admin/structure/views/view/%views_ui_cache/preview/%/ajax'] = array(
+ 'page callback' => 'views_ui_build_preview',
+ 'page arguments' => array(4, 6),
+ 'delivery callback' => 'ajax_deliver',
+ 'theme callback' => 'ajax_base_page_theme',
+ 'type' => MENU_CALLBACK,
+ ) + $base;
+
+ // Additional pages for acting on a View.
+
+ $items['admin/structure/views/view/%views_ui_cache/break-lock'] = array(
+ 'title' => 'Break lock',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('views_ui_break_lock_confirm', 4),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ ) + $base;
+
+ // NoJS/AJAX callbacks that can use the default Views AJAX form system.
+ $items['admin/structure/views/nojs/%/%views_ui_cache'] = array(
+ 'page callback' => 'views_ui_ajax_form',
+ 'page arguments' => array(FALSE, 4, 5),
+ 'type' => MENU_CALLBACK,
+ ) + $base;
+ $items['admin/structure/views/ajax/%/%views_ui_cache'] = array(
+ 'page callback' => 'views_ui_ajax_form',
+ 'page arguments' => array(TRUE, 4, 5),
+ 'delivery callback' => 'ajax_deliver',
+ 'type' => MENU_CALLBACK,
+ ) + $base;
+
+ // NoJS/AJAX callbacks that require custom page callbacks.
+ $ajax_callbacks = array(
+ 'preview' => 'views_ui_preview',
+ );
+ foreach ($ajax_callbacks as $menu => $menu_callback) {
+ $items['admin/structure/views/nojs/' . $menu . '/%views_ui_cache/%'] = array(
+ 'page callback' => $menu_callback,
+ 'page arguments' => array(5, 6),
+ ) + $base;
+ $items['admin/structure/views/ajax/' . $menu . '/%views_ui_cache/%'] = array(
+ 'page callback' => $menu_callback,
+ 'page arguments' => array(5, 6),
+ 'delivery callback' => 'ajax_deliver',
+ ) + $base;
+ }
+
+ // Autocomplete callback for tagging a View.
+ // Views module uses admin/views/... instead of admin/structure/views/... for
+ // autocomplete paths, so be consistent with that.
+ // @todo Change to admin/structure/views/... when the change can be made to
+ // Views module as well.
+ $items['admin/views/ajax/autocomplete/tag'] = array(
+ 'page callback' => 'views_ui_autocomplete_tag',
+ 'type' => MENU_CALLBACK,
+ ) + $base;
+
+ // A page in the Reports section to show usage of fields in all views
+ $items['admin/reports/fields/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/reports/fields/views-fields'] = array(
+ 'title' => 'Used in views',
+ 'description' => 'Overview of fields used in all views.',
+ 'page callback' => 'views_ui_field_list',
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 0,
+ ) + $base;
+
+ // A page in the Reports section to show usage of plugins in all views.
+ $items['admin/reports/views-plugins'] = array(
+ 'title' => 'Views plugins',
+ 'description' => 'Overview of plugins used in all views.',
+ 'page callback' => 'views_ui_plugin_list',
+ ) + $base;
+
+ return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function views_ui_theme() {
+ $path = drupal_get_path('module', 'views');
+ require_once DRUPAL_ROOT . "/$path/includes/admin.inc";
+
+ return array(
+ // edit a view
+ 'views_ui_display_tab_setting' => array(
+ 'variables' => array('description' => '', 'link' => '', 'settings_links' => array(), 'overridden' => FALSE, 'defaulted' => FALSE, 'description_separator' => TRUE, 'class' => array()),
+ 'template' => 'views-ui-display-tab-setting',
+ 'path' => "$path/theme",
+ ),
+ 'views_ui_display_tab_bucket' => array(
+ 'render element' => 'element',
+ 'template' => 'views-ui-display-tab-bucket',
+ 'path' => "$path/theme",
+ ),
+ 'views_ui_edit_item' => array(
+ 'variables' => array('type' => NULL, 'view' => NULL, 'display' => NULL, 'no_fields' => FALSE),
+ 'template' => 'views-ui-edit-item',
+ 'path' => "$path/theme",
+ ),
+ 'views_ui_rearrange_form' => array(
+ 'render element' => 'form',
+ ),
+ 'views_ui_rearrange_filter_form' => array(
+ 'render element' => 'form',
+ 'file' => 'includes/admin.inc',
+ ),
+ 'views_ui_expose_filter_form' => array(
+ 'render element' => 'form',
+ 'file' => 'includes/admin.inc',
+ ),
+
+ // list views
+ 'views_ui_view_info' => array(
+ 'variables' => array('view' => NULL, 'base' => NULL),
+ 'file' => "includes/admin.inc",
+ ),
+
+ // Group of filters.
+ 'views_ui_build_group_filter_form' => array(
+ 'render element' => 'form',
+ 'file' => 'includes/admin.inc',
+ ),
+
+ // tab themes
+ 'views_tabset' => array(
+ 'variables' => array('tabs' => NULL),
+ ),
+ 'views_tab' => array(
+ 'variables' => array('body' => NULL),
+ ),
+ 'views_ui_reorder_displays_form' => array(
+ 'render element' => 'form',
+ 'file' => 'includes/admin.inc',
+ ),
+
+
+ // On behalf of a plugin
+ 'views_ui_style_plugin_table' => array(
+ 'render element' => 'form',
+ ),
+
+ // When previewing a view.
+ 'views_ui_view_preview_section' => array(
+ 'variables' => array('view' => NULL, 'section' => NULL, 'content' => NULL, 'links' => ''),
+ ),
+
+ // Generic container wrapper, to use instead of theme_container when an id
+ // is not desired.
+ 'views_container' => array(
+ 'render element' => 'element',
+ 'file' => 'theme/theme.inc',
+ ),
+ );
+}
+
+/**
+ * Implements hook_custom_theme().
+ */
+function views_ui_custom_theme() {
+ $theme = variable_get('views_ui_custom_theme', '_default');
+
+ if ($theme != '_default') {
+ $available = list_themes();
+
+ if (isset($available[$theme]) && $available[$theme]->status && preg_match('/^admin\/structure\/views/', current_path())) {
+ return $theme;
+ }
+ }
+}
+
+/**
+ * Page title callback for the Edit View page.
+ */
+function views_ui_edit_page_title($view) {
+ module_load_include('inc', 'views_ui', 'includes/admin');
+ $bases = views_fetch_base_tables();
+ $name = $view->get_human_name();
+ if (isset($bases[$view->base_table])) {
+ $name .= ' (' . $bases[$view->base_table]['title'] . ')';
+ }
+
+ return $name;
+}
+
+/**
+ * Specialized menu callback to load a view and check its locked status.
+ *
+ * @param $name
+ * The machine name of the view.
+ *
+ * @return
+ * The view object, with a "locked" property indicating whether or not
+ * someone else is already editing the view.
+ */
+function views_ui_cache_load($name) {
+ ctools_include('object-cache');
+ views_include('view');
+ $view = ctools_object_cache_get('view', $name);
+ $original_view = views_get_view($name);
+
+ if (empty($view)) {
+ $view = $original_view;
+ if (!empty($view)) {
+ // Check to see if someone else is already editing this view.
+ $view->locked = ctools_object_cache_test('view', $view->name);
+ // Set a flag to indicate that this view is being edited.
+ // This flag will be used e.g. to determine whether strings
+ // should be localized.
+ $view->editing = TRUE;
+ }
+ }
+ else {
+ // Keep disabled/enabled status real.
+ if ($original_view) {
+ $view->disabled = !empty($original_view->disabled);
+ }
+ }
+
+ if (empty($view)) {
+ return FALSE;
+ }
+
+ else {
+ return $view;
+ }
+}
+
+/**
+ * Specialized cache function to add a flag to our view, include an appropriate
+ * include, and cache more easily.
+ */
+function views_ui_cache_set(&$view) {
+ if (!empty($view->locked)) {
+ drupal_set_message(t('Changes cannot be made to a locked view.'), 'error');
+ return;
+ }
+ ctools_include('object-cache');
+ $view->changed = TRUE; // let any future object know that this view has changed.
+
+ if (isset($view->current_display)) {
+ // Add the knowledge of the changed display, too.
+ $view->changed_display[$view->current_display] = TRUE;
+ unset($view->current_display);
+ }
+
+ // Unset handlers; we don't want to write these into the cache
+ unset($view->display_handler);
+ unset($view->default_display);
+ $view->query = NULL;
+ foreach (array_keys($view->display) as $id) {
+ unset($view->display[$id]->handler);
+ unset($view->display[$id]->default_display);
+ }
+ ctools_object_cache_set('view', $view->name, $view);
+}
+
+
+/**
+ * Specialized menu callback to load a view that is only a default
+ * view.
+ */
+function views_ui_default_load($name) {
+ $view = views_get_view($name);
+ if ($view->type == t('Default')) {
+ return $view;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Theme preprocess for views-view.tpl.php.
+ */
+function views_ui_preprocess_views_view(&$vars) {
+ $view = $vars['view'];
+ if (!empty($view->views_ui_context) && module_exists('contextual')) {
+ $view->hide_admin_links = TRUE;
+ foreach (array('title', 'header', 'exposed', 'rows', 'pager', 'more', 'footer', 'empty', 'attachment_after', 'attachment_before') as $section) {
+ if (!empty($vars[$section])) {
+ $vars[$section] = array(
+ '#theme' => 'views_ui_view_preview_section',
+ '#view' => $view,
+ '#section' => $section,
+ '#content' => is_array($vars[$section]) ? drupal_render($vars[$section]) : $vars[$section],
+ '#theme_wrappers' => array('views_container'),
+ '#attributes' => array('class' => 'contextual-links-region'),
+ );
+ $vars[$section] = drupal_render($vars[$section]);
+ }
+ }
+ }
+}
+
+/**
+ * Theme preprocess for theme_views_ui_view_preview_section().
+ *
+ * @TODO
+ * Perhaps move this to includes/admin.inc or theme/theme.inc
+ */
+function template_preprocess_views_ui_view_preview_section(&$vars) {
+ switch ($vars['section']) {
+ case 'title':
+ $vars['title'] = t('Title');
+ $links = views_ui_view_preview_section_display_category_links($vars['view'], 'title', $vars['title']);
+ break;
+ case 'header':
+ $vars['title'] = t('Header');
+ $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']);
+ break;
+ case 'empty':
+ $vars['title'] = t('No results behavior');
+ $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']);
+ break;
+ case 'exposed':
+ // @todo Sorts can be exposed too, so we may need a better title.
+ $vars['title'] = t('Exposed Filters');
+ $links = views_ui_view_preview_section_display_category_links($vars['view'], 'exposed_form_options', $vars['title']);
+ break;
+ case 'rows':
+ // @todo The title needs to depend on what is being viewed.
+ $vars['title'] = t('Content');
+ $links = views_ui_view_preview_section_rows_links($vars['view']);
+ break;
+ case 'pager':
+ $vars['title'] = t('Pager');
+ $links = views_ui_view_preview_section_display_category_links($vars['view'], 'pager_options', $vars['title']);
+ break;
+ case 'more':
+ $vars['title'] = t('More');
+ $links = views_ui_view_preview_section_display_category_links($vars['view'], 'use_more', $vars['title']);
+ break;
+ case 'footer':
+ $vars['title'] = t('Footer');
+ $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']);
+ break;
+ case 'attachment_before':
+ // @todo: Add links to the attachment configuration page.
+ $vars['title'] = t('Attachment before');
+ break;
+ case 'attachment_after':
+ // @todo: Add links to the attachment configuration page.
+ $vars['title'] = t('Attachment after');
+ break;
+ }
+
+ if (isset($links)) {
+ $build = array(
+ '#prefix' => '<div class="contextual-links-wrapper">',
+ '#suffix' => '</div>',
+ '#theme' => 'links__contextual',
+ '#links' => $links,
+ '#attributes' => array('class' => array('contextual-links')),
+ '#attached' => array(
+ 'library' => array(array('contextual', 'contextual-links')),
+ ),
+ );
+ $vars['links'] = drupal_render($build);
+ }
+ $vars['theme_hook_suggestions'][] = 'views_ui_view_preview_section__' . $vars['section'];
+}
+
+/**
+ * Returns the HTML for a section of a View being previewed within the Views UI.
+ */
+function theme_views_ui_view_preview_section($vars) {
+ return '<h1 class="section-title">' . $vars['title'] . '</h1>'
+ . $vars['links']
+ . '<div class="preview-section">'. $vars['content'] . '</div>';
+}
+
+/**
+ * Returns contextual links for each handler of a certain section.
+ *
+ * @TODO
+ * Bring in relationships
+ * Refactor this function to use much stuff of views_ui_edit_form_get_bucket.
+ *
+ * @param $title
+ * Add a bolded title of this section.
+ */
+function views_ui_view_preview_section_handler_links($view, $type, $title = FALSE) {
+ $display = $view->display_handler->display;
+ $handlers = $view->display_handler->get_handlers($type);
+ $links = array();
+
+ $types = views_object_types();
+ if ($title) {
+ $links[$type . '-title'] = array(
+ 'title' => $types[$type]['title'],
+ );
+ }
+
+ foreach ($handlers as $id => $handler) {
+ $field_name = $handler->ui_name(TRUE);
+ $links[$type . '-edit-' . $id] = array(
+ 'title' => t('Edit @section', array('@section' => $field_name)),
+ 'href' => "admin/structure/views/nojs/config-item/$view->name/$display->id/$type/$id",
+ 'attributes' => array('class' => array('views-ajax-link')),
+ );
+ }
+ $links[$type . '-add'] = array(
+ 'title' => t('Add new'),
+ 'href' => "admin/structure/views/nojs/add-item/$view->name/$display->id/$type",
+ 'attributes' => array('class' => array('views-ajax-link')),
+ );
+
+ return $links;
+}
+
+/**
+ * Returns a link to editing a certain display setting.
+ */
+function views_ui_view_preview_section_display_category_links($view, $type, $title) {
+ $display = $view->display_handler->display;
+ $links = array(
+ $type . '-edit' => array(
+ 'title' => t('Edit @section', array('@section' => $title)),
+ 'href' => "admin/structure/views/nojs/display/$view->name/$display->id/$type",
+ 'attributes' => array('class' => array('views-ajax-link')),
+ ),
+ );
+
+ return $links;
+}
+
+/**
+ * Returns all contextual links for the main content part of the view.
+ */
+function views_ui_view_preview_section_rows_links($view) {
+ $display = $view->display_handler->display;
+ $links = array();
+ $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'filter', TRUE));
+ $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'field', TRUE));
+ $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'sort', TRUE));
+ $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'argument', TRUE));
+ $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'relationship', TRUE));
+
+ return $links;
+}
+
+
+/**
+ * Implments hook_ctools_plugin_directory().
+ *
+ * Views UI provides wizard plugins on behalf of core base tables.
+ */
+function views_ui_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'views_ui' || ($module == 'ctools' && $plugin == 'export_ui')) {
+ return 'plugins/' . $plugin;
+ }
+}
+
+/**
+ * Fetch metadata on a specific views ui wizard plugin.
+ *
+ * @param $wizard_type
+ * Name of a wizard, or name of a base table.
+ *
+ * @return
+ * An array with information about the requested wizard type.
+ */
+function views_ui_get_wizard($wizard_type) {
+ ctools_include('plugins');
+ $wizard = ctools_get_plugins('views_ui', 'views_wizard', $wizard_type);
+ // @todo - handle this via an alter hook instead.
+ if (!$wizard) {
+ // Must be a base table using the default wizard plugin.
+ $base_tables = views_fetch_base_tables();
+ if (!empty($base_tables[$wizard_type])) {
+ $wizard = views_ui_views_wizard_defaults();
+ $wizard['base_table'] = $wizard_type;
+ $wizard['title'] = $base_tables[$wizard_type]['title'];
+ }
+ // The plugin is neither a base table nor an existing wizard.
+ else {
+ vpr('Views Wizard: @wizard does not exist. Be sure to implement hook_ctools_plugin_directory.', array('@wizard' => $wizard_type));
+ }
+ }
+ return $wizard;
+}
+
+/**
+ * Fetch metadata for all content_type plugins.
+ *
+ * @return
+ * An array of arrays with information about all available views wizards.
+ */
+function views_ui_get_wizards() {
+ ctools_include('plugins');
+ $wizard_plugins = ctools_get_plugins('views_ui', 'views_wizard');
+ $wizard_tables = array();
+ foreach ($wizard_plugins as $name => $info) {
+ $wizard_tables[$info['base_table']] = TRUE;
+ }
+ $base_tables = views_fetch_base_tables();
+ $default_wizard = views_ui_views_wizard_defaults();
+ // Find base tables with no wizard.
+ // @todo - handle this via an alter hook for plugins?
+ foreach ($base_tables as $table => $info) {
+ if (!isset($wizard_tables[$table])) {
+ $wizard = $default_wizard;
+ $wizard['title'] = $info['title'];
+ $wizard['base_table'] = $table;
+ $wizard_plugins[$table] = $wizard;
+ }
+ }
+ return $wizard_plugins;
+}
+
+/**
+ * Helper function to define the default values for a Views wizard plugin.
+ *
+ * @return
+ * An array of defaults for a views wizard.
+ */
+function views_ui_views_wizard_defaults() {
+ return array(
+ // The children may, for example, be a different variant for each node type.
+ 'get children' => NULL,
+ 'get child' => NULL,
+ // title and base table must be populated. They are empty here just
+ // so they are documented.
+ 'title' => '',
+ 'base_table' => NULL,
+ // This is a callback that takes the wizard as argument and returns
+ // an instantiazed Views UI form wizard object.
+ 'get_instance' => 'views_ui_get_form_wizard_instance',
+ 'form_wizard_class' => array(
+ 'file' => 'views_ui_base_views_wizard',
+ 'class' => 'ViewsUiBaseViewsWizard',
+ ),
+ );
+}
+
+/**
+ * Inform CTools that the Views wizard plugin can have child plugins.
+ */
+function views_ui_ctools_plugin_type() {
+ return array(
+ 'views_wizard' => array(
+ 'child plugins' => TRUE,
+ 'classes' => array(
+ 'form_wizard_class',
+ ),
+ 'defaults' => views_ui_views_wizard_defaults(),
+ ),
+ );
+}
+
+function views_ui_get_form_wizard_instance($wizard) {
+ if (isset($wizard['form_wizard_class']['class'])) {
+ $class = $wizard['form_wizard_class']['class'];
+ return new $class($wizard);
+ }
+ else {
+ return new ViewsUiBaseViewsWizard($wizard);
+ }
+}
+
+/**
+ * Implements hook_views_plugins_alter().
+ */
+function views_ui_views_plugins_alter(&$plugins) {
+ // Attach contextual links to each display plugin. The links will point to
+ // paths underneath "admin/structure/views/view/{$view->name}" (i.e., paths
+ // for editing and performing other contextual actions on the view).
+ foreach ($plugins['display'] as &$display) {
+ $display['contextual links']['views_ui'] = array(
+ 'parent path' => 'admin/structure/views/view',
+ 'argument properties' => array('name'),
+ );
+ }
+}
+
+/**
+ * Implements hook_contextual_links_view_alter().
+ */
+function views_ui_contextual_links_view_alter(&$element, $items) {
+ // Remove contextual links from being rendered, when so desired, such as
+ // within a View preview.
+ if (views_ui_contextual_links_suppress()) {
+ $element['#links'] = array();
+ }
+ // Append the display ID to the Views UI edit links, so that clicking on the
+ // contextual link takes you directly to the correct display tab on the edit
+ // screen.
+ elseif (!empty($element['#links']['views-ui-edit']) && !empty($element['#element']['#views_contextual_links_info']['views_ui']['view_display_id'])) {
+ $display_id = $element['#element']['#views_contextual_links_info']['views_ui']['view_display_id'];
+ $element['#links']['views-ui-edit']['href'] .= '/' . $display_id;
+ }
+}
+
+/**
+ * Sets a static variable for controlling whether contextual links are rendered.
+ *
+ * @see views_ui_contextual_links_view_alter()
+ */
+function views_ui_contextual_links_suppress($set = NULL) {
+ $suppress = &drupal_static(__FUNCTION__);
+ if (isset($set)) {
+ $suppress = $set;
+ }
+ return $suppress;
+}
+
+/**
+ * Increments the views_ui_contextual_links_suppress() static variable.
+ *
+ * When this function is added to the #pre_render of an element, and
+ * 'views_ui_contextual_links_suppress_pop' is added to the #post_render of the
+ * same element, then all contextual links within the element and its
+ * descendants are suppressed from being rendered. This is used, for example,
+ * during a View preview, when it is not desired for nodes in the Views result
+ * to have contextual links.
+ *
+ * @see views_ui_contextual_links_suppress_pop()
+ */
+function views_ui_contextual_links_suppress_push() {
+ views_ui_contextual_links_suppress(((int) views_ui_contextual_links_suppress())+1);
+}
+
+/**
+ * Decrements the views_ui_contextual_links_suppress() static variable.
+ *
+ * @see views_ui_contextual_links_suppress_push()
+ */
+function views_ui_contextual_links_suppress_pop() {
+ views_ui_contextual_links_suppress(((int) views_ui_contextual_links_suppress())-1);
+}
+
+/**
+ * Menu callback; handles AJAX form submissions similar to ajax_form_callback(), but can be used for uncached forms.
+ *
+ * ajax_form_callback(), the menu callback for the system/ajax path, requires
+ * the form to be retrievable from the form cache, because it lacks a trusted
+ * $form_id argument with which to call drupal_retrieve_form(). When AJAX is
+ * wanted on a non-cacheable form, #ajax['path'] can be set to a path whose
+ * menu router item's 'page callback' is this function, and whose
+ * 'page arguments' is the form id, optionally followed by additional build
+ * arguments, as expected by drupal_get_form().
+ *
+ * The same caution must be used when defining a hook_menu() entry with this
+ * page callback as is used when defining a hook_menu() entry with the
+ * 'drupal_get_form' page callback: a 'page arguments' must be specified with a
+ * literal value as the first argument, because $form_id determines which form
+ * builder function gets called, so must be safe from user tampering.
+ *
+ * @see drupal_get_form()
+ * @see ajax_form_callback()
+ * @see http://drupal.org/node/774876
+ */
+function views_ui_ajax_get_form($form_id) {
+ // @see ajax_get_form()
+ $form_state = array(
+ 'no_redirect' => TRUE,
+ );
+ $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
+ $form_state['rebuild_info']['copy']['#action'] = TRUE;
+
+ // @see drupal_get_form()
+ $args = func_get_args();
+ array_shift($args);
+ $form_state['build_info']['args'] = $args;
+ $form = drupal_build_form($form_id, $form_state);
+
+ // @see ajax_form_callback()
+ if (!empty($form_state['triggering_element'])) {
+ $callback = $form_state['triggering_element']['#ajax']['callback'];
+ }
+ if (!empty($callback) && function_exists($callback)) {
+ return $callback($form, $form_state);
+ }
+}
+// @todo move these when we can
+
+
+/**
+ * Helper function to get a list of paths assigned to a view.
+ *
+ * @param $view
+ * The view.
+ *
+ * @return
+ * An array of links to this view's display paths.
+ */
+function _views_ui_get_paths($view) {
+ $all_paths = array();
+ if (empty($view->display)) {
+ $all_paths[] = t('Edit this view to add a display.');
+ }
+ else {
+ $view->init_display(); // Make sure all the handlers are set up
+ foreach ($view->display as $display) {
+ if (!empty($display->handler) && $display->handler->has_path()) {
+ $one_path = $display->handler->get_option('path');
+ if (empty($path_sort)) {
+ $path_sort = strtolower($one_path);
+ }
+ if (empty($view->disabled) && strpos($one_path, '%') === FALSE) {
+ $all_paths[] = l('/' . $one_path, $one_path);
+ }
+ else {
+ $all_paths[] = check_plain('/' . $one_path);
+ }
+ }
+ }
+ }
+
+ return array_unique($all_paths);
+}
+
+/**
+ * Helper function to get a list of displays included in a view.
+ *
+ * @param $view
+ * The view.
+ *
+ * @return
+ * An array of display types that this view includes.
+ */
+function _views_ui_get_displays_list($view) {
+ $displays = array();
+ foreach ($view->display as $display) {
+ if (!empty($display->handler->definition['admin'])) {
+ $displays[$display->handler->definition['admin']] = TRUE;
+ }
+ }
+
+ if ($displays) {
+ ksort($displays);
+ $displays = array_keys($displays);
+ }
+ return $displays;
+}
+
+/**
+ * This is part of a patch to address a jQueryUI bug. The bug is responsible
+ * for the inability to scroll a page when a modal dialog is active. If the content
+ * of the dialog extends beyond the bottom of the viewport, the user is only able
+ * to scroll with a mousewheel or up/down keyboard keys.
+ *
+ * @see http://bugs.jqueryui.com/ticket/4671
+ * @see https://bugs.webkit.org/show_bug.cgi?id=19033
+ * @see /js/jquery.ui.dialog.patch.js
+ * @see /js/jquery.ui.dialog.min.js
+ *
+ * The javascript patch overwrites the $.ui.dialog.overlay.events object to remove
+ * the mousedown, mouseup and click events from the list of events that are bound
+ * in $.ui.dialog.overlay.create.
+ */
+
+function views_ui_library_alter(&$libraries, $module) {
+ if ($module == 'system' && isset($libraries['ui.dialog'])) {
+ // Only apply the fix, if we don't have an up to date jQueryUI version.
+ if (version_compare($libraries['ui.dialog']['version'], '1.7.2', '>=') && version_compare($libraries['ui.dialog']['version'], '1.10.0', '<')) {
+ $libraries['ui.dialog']['js'][drupal_get_path('module', 'views') . '/js/jquery.ui.dialog.patch.js'] = array();
+ }
+ }
+}
+
+/**
+ * Truncate strings to a set length and provide a ... if they truncated.
+ *
+ * This is often used in the UI to ensure long strings fit.
+ */
+function views_ui_truncate($string, $length) {
+ if (drupal_strlen($string) > $length) {
+ $string = drupal_substr($string, 0, $length);
+ $string .= '...';
+ }
+
+ return $string;
+}
diff --git a/sites/all/modules/views_bulk_operations/LICENSE.txt b/sites/all/modules/views_bulk_operations/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/views_bulk_operations/README.txt b/sites/all/modules/views_bulk_operations/README.txt
new file mode 100644
index 000000000..ea8484fce
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/README.txt
@@ -0,0 +1,13 @@
+Views Bulk Operations augments Views by allowing bulk operations
+(provided by Drupal core or Rules) to be executed on the displayed rows.
+It does so by showing a checkbox in front of each displayed row, and adding a
+select box on top of the View containing operations that can be applied.
+
+Getting started
+-----------------
+1. Create a View.
+2. Add a "Bulk operations" field, available to all entity types.
+3. Configure the field by selecting at least one operation.
+4. Go to the View page. VBO functionality should be present.
+
+Read the full documentation at http://drupal.org/node/1591342.
diff --git a/sites/all/modules/views_bulk_operations/actions/archive.action.inc b/sites/all/modules/views_bulk_operations/actions/archive.action.inc
new file mode 100644
index 000000000..f00552743
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions/archive.action.inc
@@ -0,0 +1,193 @@
+<?php
+
+/**
+ * @file
+ * Provides an action for creating a zip archive of selected files.
+ * An entry in the {file_managed} table is created for the newly created archive,
+ * and it is marked as permanent or temporary based on the operation settings.
+ */
+
+function views_bulk_operations_archive_action_info() {
+ $actions = array();
+ if (function_exists('zip_open')) {
+ $actions['views_bulk_operations_archive_action'] = array(
+ 'type' => 'file',
+ 'label' => t('Create an archive of selected files'),
+ // This action only works when invoked through VBO. That's why it's
+ // declared as non-configurable to prevent it from being shown in the
+ // "Create an advanced action" dropdown on admin/config/system/actions.
+ 'configurable' => FALSE,
+ 'vbo_configurable' => TRUE,
+ 'behavior' => array('views_property'),
+ 'triggers' => array('any'),
+ );
+ }
+ return $actions;
+}
+
+/**
+ * Since Drupal's Archiver doesn't abstract properly the archivers it implements
+ * (Archive_Tar and ZipArchive), it can't be used here.
+ */
+function views_bulk_operations_archive_action($file, $context) {
+ global $user;
+ static $archive_contents = array();
+
+ // Adding a non-existent file to the archive crashes ZipArchive on close().
+ if (file_exists($file->uri)) {
+ $destination = $context['destination'];
+ $zip = new ZipArchive();
+ // If the archive already exists, open it. If not, create it.
+ if (file_exists($destination)) {
+ $opened = $zip->open(drupal_realpath($destination));
+ }
+ else {
+ $opened = $zip->open(drupal_realpath($destination), ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
+ }
+
+ if ($opened) {
+ // Create a list of all files in the archive. Used for duplicate checking.
+ if (empty($archive_contents)) {
+ for ($i = 0; $i < $zip->numFiles; $i++) {
+ $archive_contents[] = $zip->getNameIndex($i);
+ }
+ }
+ // Make sure that the target filename is unique.
+ $filename = _views_bulk_operations_archive_action_create_filename(basename($file->uri), $archive_contents);
+ // Note that the actual addition happens on close(), hence the need
+ // to open / close the archive each time the action runs.
+ $zip->addFile(drupal_realpath($file->uri), $filename);
+ $zip->close();
+ $archive_contents[] = $filename;
+ }
+ }
+
+ // The operation is complete, create a file entity and provide a download
+ // link to the user.
+ if ($context['progress']['current'] == $context['progress']['total']) {
+ $archive_file = new stdClass();
+ $archive_file->uri = $destination;
+ $archive_file->filename = basename($destination);
+ $archive_file->filemime = file_get_mimetype($destination);
+ $archive_file->uid = $user->uid;
+ $archive_file->status = $context['settings']['temporary'] ? FALSE : FILE_STATUS_PERMANENT;
+ file_save($archive_file);
+
+ $url = file_create_url($archive_file->uri);
+ $url = l($url, $url, array('absolute' => TRUE));
+ _views_bulk_operations_log(t('An archive has been created and can be downloaded from: !url', array('!url' => $url)));
+ }
+}
+
+/**
+ * Configuration form shown to the user before the action gets executed.
+ */
+function views_bulk_operations_archive_action_form($context) {
+ // Pass the scheme as a value, so that the submit callback can access it.
+ $form['scheme'] = array(
+ '#type' => 'value',
+ '#value' => $context['settings']['scheme'],
+ );
+
+ $form['filename'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Filename'),
+ '#default_value' => 'vbo_archive_' . date('Ymd'),
+ '#field_suffix' => '.zip',
+ '#description' => t('The name of the archive file.'),
+ );
+ return $form;
+}
+
+/**
+ * Assembles a sanitized and unique URI for the archive, and returns it for
+ * usage by the action callback (views_bulk_operations_archive_action).
+ */
+function views_bulk_operations_archive_action_submit($form, $form_state) {
+ // Validate the scheme, fallback to public if it's somehow invalid.
+ $scheme = $form_state['values']['scheme'];
+ if (!file_stream_wrapper_valid_scheme($scheme)) {
+ $scheme = 'public';
+ }
+ $destination = $scheme . '://' . basename($form_state['values']['filename']) . '.zip';
+ // If the chosen filename already exists, file_destination() will append
+ // an integer to it in order to make it unique.
+ $destination = file_destination($destination, FILE_EXISTS_RENAME);
+
+ return array(
+ 'destination' => $destination,
+ );
+}
+
+/**
+ * Settings form (embedded into the VBO field settings in the Views UI).
+ */
+function views_bulk_operations_archive_action_views_bulk_operations_form($options) {
+ $scheme_options = array();
+ foreach (file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL) as $scheme => $stream_wrapper) {
+ $scheme_options[$scheme] = $stream_wrapper['name'];
+ }
+ if (count($scheme_options) > 1) {
+ $form['scheme'] = array(
+ '#type' => 'radios',
+ '#title' => t('Storage'),
+ '#options' => $scheme_options,
+ '#default_value' => !empty($options['scheme']) ? $options['scheme'] : variable_get('file_default_scheme', 'public'),
+ '#description' => t('Select where the archive should be stored. Private file storage has significantly more overhead than public files, but allows restricted access.'),
+ );
+ }
+ else {
+ $scheme_option_keys = array_keys($scheme_options);
+ $form['scheme'] = array(
+ '#type' => 'value',
+ '#value' => reset($scheme_option_keys),
+ );
+ }
+
+ $form['temporary'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Temporary'),
+ '#default_value' => isset($options['temporary']) ? $options['temporary'] : TRUE,
+ '#description' => t('Temporary files older than 6 hours are removed when cron runs.'),
+ );
+ return $form;
+}
+
+/**
+ * Create a sanitized and unique version of the provided filename.
+ *
+ * @param $filename
+ * String filename
+ *
+ * @return
+ * The new filename.
+ */
+function _views_bulk_operations_archive_action_create_filename($filename, $archive_list) {
+ // Strip control characters (ASCII value < 32). Though these are allowed in
+ // some filesystems, not many applications handle them well.
+ $filename = preg_replace('/[\x00-\x1F]/u', '_', $filename);
+ if (substr(PHP_OS, 0, 3) == 'WIN') {
+ // These characters are not allowed in Windows filenames
+ $filename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $filename);
+ }
+
+ if (in_array($filename, $archive_list)) {
+ // Destination file already exists, generate an alternative.
+ $pos = strrpos($filename, '.');
+ if ($pos !== FALSE) {
+ $name = substr($filename, 0, $pos);
+ $ext = substr($filename, $pos);
+ }
+ else {
+ $name = $filename;
+ $ext = '';
+ }
+
+ $counter = 0;
+ do {
+ $filename = $name . '_' . $counter++ . $ext;
+ } while (in_array($filename, $archive_list));
+ }
+
+ return $filename;
+}
diff --git a/sites/all/modules/views_bulk_operations/actions/argument_selector.action.inc b/sites/all/modules/views_bulk_operations/actions/argument_selector.action.inc
new file mode 100644
index 000000000..15a1b9ec9
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions/argument_selector.action.inc
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Passes selected ids as arguments to a page.
+ * The ids might be entity ids or revision ids, depending on the type of the
+ * VBO field.
+ */
+
+/**
+ * Implementation of hook_action_info().
+ */
+function views_bulk_operations_argument_selector_action_info() {
+ return array(
+ 'views_bulk_operations_argument_selector_action' => array(
+ 'label' => t('Pass ids as arguments to a page'),
+ 'type' => 'entity',
+ 'aggregate' => TRUE,
+ 'configurable' => FALSE,
+ 'hooks' => array(),
+ 'triggers' => array('any'),
+ ),
+ );
+}
+
+/**
+* Implementation of a Drupal action.
+* Passes selected ids as arguments to a view.
+*/
+function views_bulk_operations_argument_selector_action($entities, $context = array()) {
+ $base_url = $context['settings']['url'];
+ $arguments = implode(',', array_keys($entities));
+ // Add a trailing slash if missing.
+ if (substr($base_url, -1, 1) != '/') {
+ $base_url .= '/';
+ }
+ drupal_goto($base_url . $arguments);
+}
+
+function views_bulk_operations_argument_selector_action_views_bulk_operations_form($options) {
+ $form['url'] = array(
+ '#title' => t('URL'),
+ '#type' => 'textfield',
+ '#description' => t('Enter a URL that the user will be sent to. A comma-separated list of selected ids will be appended.'),
+ '#default_value' => isset($options['url']) ? $options['url'] : '',
+ '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
+ );
+ return $form;
+}
diff --git a/sites/all/modules/views_bulk_operations/actions/book.action.inc b/sites/all/modules/views_bulk_operations/actions/book.action.inc
new file mode 100644
index 000000000..ad8fa9c41
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions/book.action.inc
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Implements actions for managing books (book.module).
+ */
+
+function views_bulk_operations_book_action_info() {
+ $actions = array();
+ if (module_exists('book')) {
+ $actions['views_bulk_operations_move_to_book_action'] = array(
+ 'type' => 'node',
+ 'label' => t('Move to book'),
+ 'configurable' => TRUE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('any'),
+ );
+ $actions['views_bulk_operations_remove_from_book_action'] = array(
+ 'type' => 'node',
+ 'label' => t('Remove from book'),
+ 'configurable' => FALSE,
+ 'triggers' => array('any'),
+ );
+ }
+
+ return $actions;
+}
+
+function views_bulk_operations_move_to_book_action_form($context) {
+ $form = array();
+ if (!isset($context['book'])) {
+ $context['book'] = '';
+ }
+ $options = array();
+ $books = book_get_books();
+ foreach ($books as $value) {
+ $options[$value['nid']] = $value['title'];
+ }
+
+ if (empty($options)) {
+ drupal_set_message(t('You have no books.'), 'error');
+ return array();
+ }
+
+ $form['book'] = array(
+ '#type' => 'select',
+ '#title' => t('Choose a parent book'),
+ '#options' => $options,
+ '#description' => t('Select the parent book page you wish to move the book page into'),
+ );
+ return $form;
+}
+
+function views_bulk_operations_move_to_book_action_submit($form, $form_state) {
+ return array('book' => $form_state['values']['book']);
+}
+
+function views_bulk_operations_move_to_book_action($node, $context = array()) {
+ if (isset($context['book'])) {
+ $book_node = node_load($context['book']);
+ $mlid = db_select('menu_links' , 'ml')
+ ->condition('ml.link_path' , 'node/' . $node->nid)
+ ->fields('ml' , array('mlid'))
+ ->execute()
+ ->fetchField();
+ $node->book['mlid'] = $mlid;
+ $node->book['bid'] = $book_node->nid;
+ $node->book['plid'] = $book_node->book['mlid'];
+ $node->book['module'] = 'book';
+ }
+}
+
+/**
+ * Adds the action 'Remove node from a parent book'
+ */
+function views_bulk_operations_remove_from_book_action($node, $context) {
+ $book = $node->book['mlid'];
+ book_node_delete($node);
+}
diff --git a/sites/all/modules/views_bulk_operations/actions/delete.action.inc b/sites/all/modules/views_bulk_operations/actions/delete.action.inc
new file mode 100644
index 000000000..52c72d2fb
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions/delete.action.inc
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Implements a generic entity delete action. Uses Entity API if available.
+ */
+
+function views_bulk_operations_delete_action_info() {
+ return array(
+ 'views_bulk_operations_delete_item' => array(
+ 'type' => 'entity',
+ 'label' => t('Delete item'),
+ 'configurable' => FALSE,
+ 'behavior' => array('deletes_property'),
+ 'triggers' => array('any'),
+ ),
+ 'views_bulk_operations_delete_revision' => array(
+ 'type' => 'entity',
+ 'label' => t('Delete revision'),
+ 'configurable' => FALSE,
+ 'behavior' => array('deletes_property'),
+ 'triggers' => array('any'),
+ ),
+ );
+}
+
+function views_bulk_operations_delete_item($entity, $context) {
+ $info = entity_get_info($context['entity_type']);
+ $entity_id = $entity->{$info['entity keys']['id']};
+
+ entity_delete($context['entity_type'], $entity_id);
+}
+
+function views_bulk_operations_delete_revision($entity, $context) {
+ $info = entity_get_info($context['entity_type']);
+ $revision_id = $entity->{$info['entity keys']['revision']};
+ entity_revision_delete($context['entity_type'], $revision_id);
+}
diff --git a/sites/all/modules/views_bulk_operations/actions/modify.action.inc b/sites/all/modules/views_bulk_operations/actions/modify.action.inc
new file mode 100644
index 000000000..301b17b2c
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions/modify.action.inc
@@ -0,0 +1,622 @@
+<?php
+
+/**
+ * @file VBO action to modify entity values (properties and fields).
+ */
+
+// Specifies that all available values should be shown to the user for editing.
+define('VBO_MODIFY_ACTION_ALL', '_all_');
+
+function views_bulk_operations_modify_action_info() {
+ return array('views_bulk_operations_modify_action' => array(
+ 'type' => 'entity',
+ 'label' => t('Modify entity values'),
+ 'behavior' => array('changes_property'),
+ // This action only works when invoked through VBO. That's why it's
+ // declared as non-configurable to prevent it from being shown in the
+ // "Create an advanced action" dropdown on admin/config/system/actions.
+ 'configurable' => FALSE,
+ 'vbo_configurable' => TRUE,
+ 'triggers' => array('any'),
+ ));
+}
+
+/**
+ * Action function.
+ *
+ * Goes through new values and uses them to modify the passed entity by either
+ * replacing the existing values, or appending to them (based on user input).
+ */
+function views_bulk_operations_modify_action($entity, $context) {
+ list(,,$bundle_name) = entity_extract_ids($context['entity_type'], $entity);
+ // Handle Field API fields.
+ if (!empty($context['selected']['bundle_' . $bundle_name])) {
+ // The pseudo entity is cloned so that changes to it don't get carried
+ // over to the next execution.
+ $pseudo_entity = clone $context['entities'][$bundle_name];
+ foreach ($context['selected']['bundle_' . $bundle_name] as $key) {
+ // Get this field's language. We can just pull it from the pseudo entity
+ // as it was created using field_attach_form and entity_language so it's
+ // already been figured out if this field is translatable or not and
+ // applied the appropriate language code to the field
+ $language = key($pseudo_entity->{$key});
+ // Replace any tokens that might exist in the field columns.
+ foreach ($pseudo_entity->{$key}[$language] as $delta => &$item) {
+ foreach ($item as $column => $value) {
+ if (is_string($value)) {
+ $item[$column] = token_replace($value, array($context['entity_type'] => $entity), array('sanitize' => FALSE));
+ }
+ }
+ }
+
+ if (in_array($key, $context['append']['bundle_' . $bundle_name]) && !empty($entity->$key)) {
+ $entity->{$key}[$language] = array_merge($entity->{$key}[$language], $pseudo_entity->{$key}[$language]);
+
+ // Check if we breached cardinality, and notify the user.
+ $field_info = field_info_field($key);
+ $field_count = count($entity->{$key}[$language]);
+ if ($field_info['cardinality'] != FIELD_CARDINALITY_UNLIMITED && $field_count > $field_info['cardinality']) {
+ $entity_label = entity_label($context['entity_type'], $entity);
+ $warning = t('Tried to set !field_count values for field !field_name that supports a maximum of !cardinality.',
+ array('!field_count' => $field_count,
+ '!field_name' => $field_info['field_name'],
+ '!cardinality' => $field_info['cardinality']));
+ drupal_set_message($warning, 'warning', FALSE);
+ }
+
+ // Prevent storing duplicate references.
+ if (strpos($field_info['type'], 'reference') !== FALSE) {
+ $entity->{$key}[$language] = array_unique($entity->{$key}[LANGUAGE_NONE], SORT_REGULAR);
+ }
+ }
+ else {
+ $entity->{$key}[$language] = $pseudo_entity->{$key}[$language];
+ }
+ }
+ }
+
+ // Handle properties.
+ if (!empty($context['selected']['properties'])) {
+ // Use the wrapper to set property values, since some properties need
+ // additional massaging by their setter callbacks.
+ // The wrapper will automatically modify $entity itself.
+ $wrapper = entity_metadata_wrapper($context['entity_type'], $entity);
+ foreach ($context['selected']['properties'] as $key) {
+ if (!$wrapper->$key->access('update')) {
+ // No access.
+ continue;
+ }
+
+ if (in_array($key, $context['append']['properties'])) {
+ $old_values = $wrapper->$key->value();
+ $wrapper->$key->set($context['properties'][$key]);
+ $new_values = $wrapper->{$key}->value();
+ $all_values = array_merge($old_values, $new_values);
+ $wrapper->$key->set($all_values);
+ }
+ else {
+ $value = $context['properties'][$key];
+ if (is_string($value)) {
+ $value = token_replace($value, array($context['entity_type'] => $entity), array('sanitize' => FALSE));
+ }
+ $wrapper->$key->set($value);
+ }
+ }
+ }
+}
+
+/**
+ * Action form function.
+ *
+ * Displays form elements for properties acquired through Entity Metadata
+ * (hook_entity_property_info()), as well as field widgets for each
+ * entity bundle, as provided by field_attach_form().
+ */
+function views_bulk_operations_modify_action_form($context, &$form_state) {
+ // This action form uses admin-provided settings. If they were not set, pull the defaults now.
+ if (!isset($context['settings'])) {
+ $context['settings'] = views_bulk_operations_modify_action_views_bulk_operations_form_options();
+ }
+
+ $form_state['entity_type'] = $entity_type = $context['entity_type'];
+ // For Field API integration to work, a pseudo-entity is constructed for each
+ // bundle that has fields available for editing.
+ // The entities then get passed to Field API functions
+ // (field_attach_form(), field_attach_form_validate(), field_attach_submit()),
+ // and filled with form data.
+ // After submit, the pseudo-entities get passed to the actual action
+ // (views_bulk_operations_modify_action()) which copies the data from the
+ // relevant pseudo-entity constructed here to the actual entity being modified.
+ $form_state['entities'] = array();
+
+ $info = entity_get_info($entity_type);
+ $properties = _views_bulk_operations_modify_action_get_properties($entity_type, $context['settings']['display_values']);
+ $bundles = _views_bulk_operations_modify_action_get_bundles($entity_type, $context);
+
+ $form['#attached']['css'][] = drupal_get_path('module', 'views_bulk_operations') . '/css/modify.action.css';
+ $form['#tree'] = TRUE;
+
+ if (!empty($properties)) {
+ $form['properties'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Properties'),
+ );
+ $form['properties']['show_value'] = array(
+ '#suffix' => '<div class="clearfix"></div>',
+ );
+
+ foreach ($properties as $key => $property) {
+ $form['properties']['show_value'][$key] = array(
+ '#type' => 'checkbox',
+ '#title' => $property['label'],
+ );
+
+ $determined_type = ($property['type'] == 'boolean') ? 'checkbox' : 'textfield';
+ $form['properties'][$key] = array(
+ '#type' => $determined_type,
+ '#title' => $property['label'],
+ '#description' => $property['description'],
+ '#states' => array(
+ 'visible' => array(
+ '#edit-properties-show-value-' . str_replace('_', '-', $key) => array('checked' => TRUE),
+ ),
+ ),
+ );
+ // The default #maxlength for textfields is 128, while most varchar
+ // columns hold 255 characters, which makes it a saner default here.
+ if ($determined_type == 'textfield') {
+ $form['properties'][$key]['#maxlength'] = 255;
+ }
+
+ if (!empty($property['options list'])) {
+ $form['properties'][$key]['#type'] = 'select';
+ $form['properties'][$key]['#options'] = $property['options list']($key, array());
+
+ if ($property['type'] == 'list') {
+ $form['properties'][$key]['#type'] = 'checkboxes';
+
+ $form['properties']['_append::' . $key] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add new value(s) to %label, instead of overwriting the existing values.', array('%label' => $property['label'])),
+ '#states' => array(
+ 'visible' => array(
+ '#edit-properties-show-value-' . $key => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+ }
+ }
+ }
+
+ // Going to need this for multilingual nodes
+ global $language;
+ foreach ($bundles as $bundle_name => $bundle) {
+ $bundle_key = $info['entity keys']['bundle'];
+ $default_values = array();
+ // If the bundle key exists, it must always be set on an entity.
+ if (!empty($bundle_key)) {
+ $default_values[$bundle_key] = $bundle_name;
+ }
+ $default_values['language'] = $language->language;
+ $entity = entity_create($context['entity_type'], $default_values);
+ $form_state['entities'][$bundle_name] = $entity;
+
+ // Show the more detailed label only if the entity type has multiple bundles.
+ // Otherwise, it would just be confusing.
+ if (count($info['bundles']) > 1) {
+ $label = t('Fields for @bundle_key @label', array('@bundle_key' => $bundle_key, '@label' => $bundle['label']));
+ }
+ else {
+ $label = t('Fields');
+ }
+
+ $form_key = 'bundle_' . $bundle_name;
+ $form[$form_key] = array(
+ '#type' => 'fieldset',
+ '#title' => $label,
+ '#parents' => array($form_key),
+ );
+ field_attach_form($context['entity_type'], $entity, $form[$form_key], $form_state, entity_language($context['entity_type'], $entity));
+ // Now that all the widgets have been added, sort them by #weight.
+ // This ensures that they will stay in the correct order when they get
+ // assigned new weights.
+ uasort($form[$form_key], 'element_sort');
+
+ $display_values = $context['settings']['display_values'];
+ $instances = field_info_instances($entity_type, $bundle_name);
+ $weight = 0;
+ foreach (element_get_visible_children($form[$form_key]) as $field_name) {
+ // For our use case it makes no sense for any field widget to be required.
+ if (isset($form[$form_key][$field_name]['#language'])) {
+ $field_language = $form[$form_key][$field_name]['#language'];
+ _views_bulk_operations_modify_action_unset_required($form[$form_key][$field_name][$field_language]);
+ }
+
+ // The admin has specified which fields to display, but this field didn't
+ // make the cut. Hide it with #access => FALSE and move on.
+ if (empty($display_values[VBO_MODIFY_ACTION_ALL]) && empty($display_values[$bundle_name . '::' . $field_name])) {
+ $form[$form_key][$field_name]['#access'] = FALSE;
+ continue;
+ }
+
+ if (isset($instances[$field_name])) {
+ $field = $instances[$field_name];
+ $form[$form_key]['show_value'][$field_name] = array(
+ '#type' => 'checkbox',
+ '#title' => $field['label'],
+ );
+ $form[$form_key][$field_name]['#states'] = array(
+ 'visible' => array(
+ '#edit-bundle-' . str_replace('_', '-', $bundle_name) . '-show-value-' . str_replace('_', '-', $field_name) => array('checked' => TRUE),
+ ),
+ );
+ // All field widgets get reassigned weights so that additional elements
+ // added between them (such as "_append") can be properly ordered.
+ $form[$form_key][$field_name]['#weight'] = $weight++;
+
+ $field_info = field_info_field($field_name);
+ if ($field_info['cardinality'] != 1) {
+ $form[$form_key]['_append::' . $field_name] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Add new value(s) to %label, instead of overwriting the existing values.', array('%label' => $field['label'])),
+ '#states' => array(
+ 'visible' => array(
+ '#edit-bundle-' . str_replace('_', '-', $bundle_name) . '-show-value-' . str_replace('_', '-', $field_name) => array('checked' => TRUE),
+ ),
+ ),
+ '#weight' => $weight++,
+ );
+ }
+ }
+ }
+
+ // Add a clearfix below the checkboxes so that the widgets are not floated.
+ $form[$form_key]['show_value']['#suffix'] = '<div class="clearfix"></div>';
+ $form[$form_key]['show_value']['#weight'] = -1;
+ }
+
+ // If the form has only one group (for example, "Properties"), remove the
+ // title and the fieldset, since there's no need to visually group values.
+ $form_elements = element_get_visible_children($form);
+ if (count($form_elements) == 1) {
+ $element_key = reset($form_elements);
+ unset($form[$element_key]['#type']);
+ unset($form[$element_key]['#title']);
+
+ // Get a list of all elements in the group, and filter out the non-values.
+ $values = element_get_visible_children($form[$element_key]);
+ foreach ($values as $index => $key) {
+ if ($key == 'show_value' || substr($key, 0, 1) == '_') {
+ unset($values[$index]);
+ }
+ }
+ // If the group has only one value, no need to hide it through #states.
+ if (count($values) == 1) {
+ $value_key = reset($values);
+ $form[$element_key]['show_value'][$value_key]['#type'] = 'value';
+ $form[$element_key]['show_value'][$value_key]['#value'] = TRUE;
+ }
+ }
+
+ if (module_exists('token') && $context['settings']['show_all_tokens']) {
+ $token_type = str_replace('_', '-', $entity_type);
+ $form['tokens'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Available tokens'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#weight' => 998,
+ );
+ $form['tokens']['tree'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => array($token_type, 'site'),
+ '#global_types' => array(),
+ '#dialog' => TRUE,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Action form validate function.
+ *
+ * Checks that the user selected at least one value to modify, validates
+ * properties and calls Field API to validate fields for each bundle.
+ */
+function views_bulk_operations_modify_action_validate($form, &$form_state) {
+ // The form structure for "Show" checkboxes is a bit bumpy.
+ $search = array('properties');
+ foreach ($form_state['entities'] as $bundle => $entity) {
+ $search[] = 'bundle_' . $bundle;
+ }
+
+ $has_selected = FALSE;
+ foreach ($search as $group) {
+ // Store names of selected and appended entity values in a nicer format.
+ $form_state['selected'][$group] = array();
+ $form_state['append'][$group] = array();
+
+ // This group has no values, move on.
+ if (!isset($form_state['values'][$group])) {
+ continue;
+ }
+
+ foreach ($form_state['values'][$group]['show_value'] as $key => $value) {
+ if ($value) {
+ $has_selected = TRUE;
+ $form_state['selected'][$group][] = $key;
+ }
+ if (!empty($form_state['values'][$group]['_append::' . $key])) {
+ $form_state['append'][$group][] = $key;
+ unset($form_state['values'][$group]['_append::' . $key]);
+ }
+ }
+ unset($form_state['values'][$group]['show_value']);
+ }
+
+ if (!$has_selected) {
+ form_set_error('', t('You must select at least one value to modify.'));
+ return;
+ }
+
+ // Use the wrapper to validate property values.
+ if (!empty($form_state['selected']['properties'])) {
+ // The entity used is irrelevant, and we can't rely on
+ // $form_state['entities'] being non-empty, so a new one is created.
+ $info = entity_get_info($form_state['entity_type']);
+ $bundle_key = $info['entity keys']['bundle'];
+ $default_values = array();
+ // If the bundle key exists, it must always be set on an entity.
+ if (!empty($bundle_key)) {
+ $bundle_names = array_keys($info['bundles']);
+ $bundle_name = reset($bundle_names);
+ $default_values[$bundle_key] = $bundle_name;
+ }
+ $entity = entity_create($form_state['entity_type'], $default_values);
+ $wrapper = entity_metadata_wrapper($form_state['entity_type'], $entity);
+
+ $properties = _views_bulk_operations_modify_action_get_properties($form_state['entity_type']);
+ foreach ($form_state['selected']['properties'] as $key) {
+ $value = $form_state['values']['properties'][$key];
+ if (!$wrapper->$key->validate($value)) {
+ $label = $properties[$key]['label'];
+ form_set_error('properties][' . $key, t('%label contains an invalid value.', array('%label' => $label)));
+ }
+ }
+ }
+
+ foreach ($form_state['entities'] as $bundle_name => $entity) {
+ field_attach_form_validate($form_state['entity_type'], $entity, $form['bundle_' . $bundle_name], $form_state);
+ }
+}
+
+/**
+ * Action form submit function.
+ *
+ * Fills each constructed entity with property and field values, then
+ * passes them to views_bulk_operations_modify_action().
+ */
+function views_bulk_operations_modify_action_submit($form, $form_state) {
+ foreach ($form_state['entities'] as $bundle_name => $entity) {
+ field_attach_submit($form_state['entity_type'], $entity, $form['bundle_' . $bundle_name], $form_state);
+ }
+
+ return array(
+ 'append' => $form_state['append'],
+ 'selected' => $form_state['selected'],
+ 'entities' => $form_state['entities'],
+ 'properties' => isset($form_state['values']['properties']) ? $form_state['values']['properties'] : array(),
+ );
+}
+
+/**
+ * Returns all properties that can be modified.
+ *
+ * Properties that can't be changed are entity keys, timestamps, and the ones
+ * without a setter callback.
+ *
+ * @param $entity_type
+ * The entity type whose properties will be fetched.
+ * @param $display_values
+ * An optional, admin-provided list of properties and fields that should be
+ * displayed for editing, used to filter the returned list of properties.
+ */
+function _views_bulk_operations_modify_action_get_properties($entity_type, $display_values = NULL) {
+ $properties = array();
+ $info = entity_get_info($entity_type);
+
+ // List of properties that can't be modified.
+ $disabled_properties = array('created', 'changed');
+ foreach (array('id', 'bundle', 'revision') as $key) {
+ if (!empty($info['entity keys'][$key])) {
+ $disabled_properties[] = $info['entity keys'][$key];
+ }
+ }
+ // List of supported types.
+ $supported_types = array('text', 'token', 'integer', 'decimal', 'date', 'duration',
+ 'boolean', 'uri', 'list');
+ $property_info = entity_get_property_info($entity_type);
+ if (empty($property_info['properties'])) {
+ // Stop here if no properties were found.
+ return array();
+ }
+
+ foreach ($property_info['properties'] as $key => $property) {
+ if (in_array($key, $disabled_properties)) {
+ continue;
+ }
+ // Filter out properties that can't be set (they are usually generated by a
+ // getter callback based on other properties, and not stored in the DB).
+ if (empty($property['setter callback'])) {
+ continue;
+ }
+ // Determine the property type. If it's empty (permitted), default to text.
+ // If it's a list type such as list<boolean>, extract the "boolean" part.
+ $property['type'] = empty($property['type']) ? 'text' : $property['type'];
+ $type = $property['type'];
+ if ($list_type = entity_property_list_extract_type($type)) {
+ $type = $list_type;
+ $property['type'] = 'list';
+ }
+ // Filter out non-supported types (such as the Field API fields that
+ // Commerce adds to its entities so that they show up in tokens).
+ if (!in_array($type, $supported_types)) {
+ continue;
+ }
+
+ $properties[$key] = $property;
+ }
+
+ if (isset($display_values) && empty($display_values[VBO_MODIFY_ACTION_ALL])) {
+ // Return only the properties that the admin specified.
+ return array_intersect_key($properties, $display_values);
+ }
+
+ return $properties;
+}
+
+/**
+ * Returns all bundles for which field widgets should be displayed.
+ *
+ * If the admin decided to limit the modify form to certain properties / fields
+ * (through the action settings) then only bundles that have at least one field
+ * selected are returned.
+ *
+ * @param $entity_type
+ * The entity type whose bundles will be fetched.
+ * @param $context
+ * The VBO context variable.
+ */
+function _views_bulk_operations_modify_action_get_bundles($entity_type, $context) {
+ $bundles = array();
+
+ $view = $context['view'];
+ $vbo = _views_bulk_operations_get_field($view);
+ $display_values = $context['settings']['display_values'];
+ $info = entity_get_info($entity_type);
+ $bundle_key = $info['entity keys']['bundle'];
+
+ // Check if this View has a filter on the bundle key and assemble a list
+ // of allowed bundles according to the filter.
+ $filtered_bundles = array_keys($info['bundles']);
+
+ // Go over all the filters and find any relevant ones.
+ foreach ($view->filter as $key => $filter) {
+ // Check it's the right field on the right table.
+ if ($filter->table == $vbo->table && $filter->field == $bundle_key) {
+ // Exposed filters may have no bundles, so check that there is a value.
+ if (empty($filter->value)) {
+ continue;
+ }
+
+ $operator = $filter->operator;
+ if ($operator == 'in') {
+ $filtered_bundles = array_intersect($filtered_bundles, $filter->value);
+ }
+ elseif ($operator == 'not in') {
+ $filtered_bundles = array_diff($filtered_bundles, $filter->value);
+ }
+ }
+ }
+
+ foreach ($info['bundles'] as $bundle_name => $bundle) {
+ // The view is limited to specific bundles, but this bundle isn't one of
+ // them. Ignore it.
+ if (!in_array($bundle_name, $filtered_bundles)) {
+ continue;
+ }
+
+ $instances = field_info_instances($entity_type, $bundle_name);
+ // Ignore bundles that don't have any field instances attached.
+ if (empty($instances)) {
+ continue;
+ }
+
+ $has_enabled_fields = FALSE;
+ foreach ($display_values as $key) {
+ if (strpos($key, $bundle_name . '::') !== FALSE) {
+ $has_enabled_fields = TRUE;
+ }
+ }
+ // The admin has either specified that all values should be modifiable, or
+ // selected at least one field belonging to this bundle.
+ if (!empty($display_values[VBO_MODIFY_ACTION_ALL]) || $has_enabled_fields) {
+ $bundles[$bundle_name] = $bundle;
+ }
+ }
+
+ return $bundles;
+}
+
+/**
+ * Helper function that recursively strips #required from field widgets.
+ */
+function _views_bulk_operations_modify_action_unset_required(&$element) {
+ unset($element['#required']);
+ foreach (element_children($element) as $key) {
+ _views_bulk_operations_modify_action_unset_required($element[$key]);
+ }
+}
+
+/**
+ * VBO settings form function.
+ */
+function views_bulk_operations_modify_action_views_bulk_operations_form_options() {
+ $options['show_all_tokens'] = TRUE;
+ $options['display_values'] = array(VBO_MODIFY_ACTION_ALL);
+ return $options;
+}
+
+/**
+ * The settings form for this action.
+ */
+function views_bulk_operations_modify_action_views_bulk_operations_form($options, $entity_type, $dom_id) {
+ // Initialize default values.
+ if (empty($options)) {
+ $options = views_bulk_operations_modify_action_views_bulk_operations_form_options();
+ }
+
+ $form['show_all_tokens'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show available tokens'),
+ '#description' => t('Check this to show a list of all available tokens in the bottom of the form. Requires the token module.'),
+ '#default_value' => $options['show_all_tokens'],
+ );
+
+ $info = entity_get_info($entity_type);
+ $properties = _views_bulk_operations_modify_action_get_properties($entity_type);
+ $values = array(VBO_MODIFY_ACTION_ALL => t('- All -'));
+ foreach ($properties as $key => $property) {
+ $label = t('Properties');
+ $values[$label][$key] = $property['label'];
+ }
+ foreach ($info['bundles'] as $bundle_name => $bundle) {
+ $bundle_key = $info['entity keys']['bundle'];
+ // Show the more detailed label only if the entity type has multiple bundles.
+ // Otherwise, it would just be confusing.
+ if (count($info['bundles']) > 1) {
+ $label = t('Fields for @bundle_key @label', array('@bundle_key' => $bundle_key, '@label' => $bundle['label']));
+ }
+ else {
+ $label = t('Fields');
+ }
+
+ $instances = field_info_instances($entity_type, $bundle_name);
+ foreach ($instances as $field_name => $field) {
+ $values[$label][$bundle_name . '::' . $field_name] = $field['label'];
+ }
+ }
+
+ $form['display_values'] = array(
+ '#type' => 'select',
+ '#title' => t('Display values'),
+ '#options' => $values,
+ '#multiple' => TRUE,
+ '#description' => t('Select which values the action form should present to the user.'),
+ '#default_value' => $options['display_values'],
+ '#size' => 10,
+ );
+ return $form;
+}
diff --git a/sites/all/modules/views_bulk_operations/actions/script.action.inc b/sites/all/modules/views_bulk_operations/actions/script.action.inc
new file mode 100644
index 000000000..b98b2acf7
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions/script.action.inc
@@ -0,0 +1,92 @@
+<?php
+
+function views_bulk_operations_script_action_info() {
+ $actions = array();
+ $actions['views_bulk_operations_script_action'] = array(
+ 'type' => 'entity',
+ 'label' => t('Execute arbitrary PHP script'),
+ 'configurable' => TRUE,
+ 'triggers' => array('any'),
+ );
+ // Provide a strict default permission if actions_permissions is disabled.
+ if (!module_exists('actions_permissions')) {
+ $actions['views_bulk_operations_script_action']['permissions'] = array('administer site configuration');
+ }
+
+ return $actions;
+}
+
+function views_bulk_operations_script_action($entity, $context) {
+ $return = eval($context['script']);
+ if ($return === FALSE) {
+ $msg = 'Error in script.';
+ $arg = array();
+ $error = error_get_last();
+ if ($error) {
+ $msg = '!err in script: !msg in line \'%line\'.';
+ $arg = array(
+ '!msg' => $error['message'],
+ '%line' => _views_bulk_operations_script_action_error_line($context['script'], $error['line']),
+ '!err' => _views_bulk_operations_script_action_error_type($error['type']),
+ );
+ }
+ drupal_set_message(t($msg, $arg), 'error', FALSE);
+ watchdog('actions', $msg, $arg, WATCHDOG_ERROR);
+ }
+}
+
+function views_bulk_operations_script_action_form($context) {
+ $form['script'] = array(
+ '#type' => 'textarea',
+ '#title' => t('PHP script'),
+ '#description' => t('Type the PHP snippet that will run upon execution of this action. You can use variables <code>$entity</code> and <code>$context</code> in your snippet.
+ Note that it is up to the script to save the $entity once it\'s done modifying it.'),
+ '#default_value' => @$context['script'],
+ );
+ return $form;
+}
+
+function views_bulk_operations_script_action_validate($form, $form_state) {
+}
+
+function views_bulk_operations_script_action_submit($form, $form_state) {
+ return array(
+ 'script' => $form_state['values']['script'],
+ );
+}
+
+function _views_bulk_operations_script_action_error_line($script, $line) {
+ $lines = preg_split("/(\r?\n)/", $script);
+ if (isset($lines[$line-1])) {
+ return $lines[$line-1];
+ }
+ else {
+ return t('Line !line', array('!line' => $line));
+ }
+}
+
+function _views_bulk_operations_script_action_error_type($type) {
+ $types = array(
+ E_ERROR => 'Error',
+ E_WARNING => 'Warning',
+ E_PARSE => 'Parsing Error',
+ E_NOTICE => 'Notice',
+ E_CORE_ERROR => 'Core Error',
+ E_CORE_WARNING => 'Core Warning',
+ E_COMPILE_ERROR => 'Compile Error',
+ E_COMPILE_WARNING => 'Compile Warning',
+ E_USER_ERROR => 'User Error',
+ E_USER_WARNING => 'User Warning',
+ E_USER_NOTICE => 'User Notice',
+ E_STRICT => 'Runtime Notice',
+ E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
+ );
+ if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
+ $types += array(
+ E_DEPRECATED => 'Deprecated Notice',
+ E_USER_DEPRECATED => 'User Deprecated Notice',
+ );
+ }
+
+ return t($types[$type]);
+}
diff --git a/sites/all/modules/views_bulk_operations/actions/user_cancel.action.inc b/sites/all/modules/views_bulk_operations/actions/user_cancel.action.inc
new file mode 100644
index 000000000..147d29208
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions/user_cancel.action.inc
@@ -0,0 +1,82 @@
+<?php
+/**
+ * @file
+ * VBO action to cancel user accounts.
+ */
+
+function views_bulk_operations_user_cancel_action_info() {
+ return array('views_bulk_operations_user_cancel_action' => array(
+ 'type' => 'user',
+ 'label' => t('Cancel user account'),
+ 'configurable' => TRUE,
+ 'behavior' => array('deletes_property'),
+ 'triggers' => array('any'),
+ ));
+}
+
+function views_bulk_operations_user_cancel_action_form($context) {
+ module_load_include('inc', 'user', 'user.pages');
+ $form['user_cancel_method'] = array(
+ '#type' => 'item',
+ '#title' => t('When cancelling these accounts'),
+ );
+ $form['user_cancel_method'] += user_cancel_methods();
+ // Remove method descriptions.
+ foreach (element_children($form['user_cancel_method']) as $element) {
+ unset($form['user_cancel_method'][$element]['#description']);
+ }
+ $admin_access = user_access('administer users');
+ $default_notify = variable_get('user_mail_status_canceled_notify', FALSE);
+ $form['user_cancel_notify'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Notify user when account is canceled.'),
+ '#default_value' => ($admin_access ? FALSE : $default_notify),
+ '#access' => $admin_access && $default_notify,
+ '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'),
+ );
+
+ return $form;
+}
+
+function views_bulk_operations_user_cancel_action_submit($form, $form_state) {
+ return array(
+ 'user_cancel_method' => $form_state['values']['user_cancel_method'],
+ 'user_cancel_notify' => $form_state['values']['user_cancel_notify'],
+ );
+}
+
+function views_bulk_operations_user_cancel_action($account, $context) {
+ global $user;
+ // Prevent the user from cancelling itself.
+ if ($account->uid == $user->uid) {
+ return;
+ }
+
+ // Allow other modules to react on the cancellation.
+ if ($context['user_cancel_method'] != 'user_cancel_delete') {
+ module_invoke_all('user_cancel', array(), $account, $context['user_cancel_method']);
+ }
+
+ switch ($context['user_cancel_method']) {
+ case 'user_cancel_block':
+ case 'user_cancel_block_unpublish':
+ default:
+ // Send account blocked notification if option was checked.
+ if (!empty($context['user_cancel_notify'])) {
+ _user_mail_notify('status_blocked', $account);
+ }
+ user_save($account, array('status' => 0));
+ watchdog('user', 'Blocked user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
+ break;
+
+ case 'user_cancel_reassign':
+ case 'user_cancel_delete':
+ // Send account canceled notification if option was checked.
+ if (!empty($context['user_cancel_notify'])) {
+ _user_mail_notify('status_canceled', $account);
+ }
+ user_delete($account->uid);
+ watchdog('user', 'Deleted user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
+ break;
+ }
+}
diff --git a/sites/all/modules/views_bulk_operations/actions/user_roles.action.inc b/sites/all/modules/views_bulk_operations/actions/user_roles.action.inc
new file mode 100644
index 000000000..616f13ceb
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions/user_roles.action.inc
@@ -0,0 +1,63 @@
+<?php
+
+function views_bulk_operations_user_roles_action_info() {
+ return array('views_bulk_operations_user_roles_action' => array(
+ 'type' => 'user',
+ 'label' => t('Modify user roles'),
+ 'configurable' => TRUE,
+ 'triggers' => array('any'),
+ ));
+}
+
+function views_bulk_operations_user_roles_action_form($context) {
+ $roles = user_roles(TRUE);
+ unset($roles[DRUPAL_AUTHENTICATED_RID]); // Can't edit authenticated role.
+
+ $form['add_roles'] = array(
+ '#type' => 'select',
+ '#multiple' => TRUE,
+ '#title' => t('Add roles'),
+ '#description' => t('Choose one or more roles you would like to assign to the selected users.'),
+ '#options' => $roles,
+ '#size' => 5
+ );
+ $form['remove_roles'] = array(
+ '#type' => 'select',
+ '#multiple' => TRUE,
+ '#title' => t('Remove roles'),
+ '#description' => t('Choose one or more roles you would like to remove from the selected users.'),
+ '#options' => $roles,
+ '#size' => 5
+ );
+ return $form;
+}
+
+function views_bulk_operations_user_roles_action_validate($form, $form_state) {
+ if (!$form_state['values']['add_roles'] && !$form_state['values']['remove_roles']) {
+ form_set_error('add_roles', t('You have not chosen any role to add or remove. Please select something to do.'));
+ }
+}
+
+function views_bulk_operations_user_roles_action_submit($form, $form_state) {
+ return array(
+ 'add_roles' => array_filter($form_state['values']['add_roles']),
+ 'remove_roles' => array_filter($form_state['values']['remove_roles']),
+ );
+}
+
+function views_bulk_operations_user_roles_action($user, $context) {
+ $wrapper = entity_metadata_wrapper('user', $user);
+ if (!$wrapper->roles->access("update")) {
+ // No access.
+ return;
+ }
+ $roles = $wrapper->roles->value();
+ if (is_array($context['add_roles'])) {
+ $roles = array_merge($roles, $context['add_roles']);
+ }
+ if (is_array($context['remove_roles'])) {
+ $roles = array_diff($roles, $context['remove_roles']);
+ }
+ $wrapper->roles->set($roles);
+ $wrapper->save();
+}
diff --git a/sites/all/modules/views_bulk_operations/actions_permissions.info b/sites/all/modules/views_bulk_operations/actions_permissions.info
new file mode 100644
index 000000000..658d1adc1
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions_permissions.info
@@ -0,0 +1,11 @@
+name = Actions permissions (VBO)
+description = Provides permission-based access control for actions. Used by Views Bulk Operations.
+package = Administration
+core = 7.x
+
+; Information added by Drupal.org packaging script on 2015-07-01
+version = "7.x-3.3"
+core = "7.x"
+project = "views_bulk_operations"
+datestamp = "1435764542"
+
diff --git a/sites/all/modules/views_bulk_operations/actions_permissions.module b/sites/all/modules/views_bulk_operations/actions_permissions.module
new file mode 100644
index 000000000..3c43ea827
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/actions_permissions.module
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Implements hook_permission().
+ */
+function actions_permissions_permission() {
+ $permissions = array();
+ $actions = actions_list() + _actions_permissions_advanced_actions_list();
+ foreach ($actions as $key => $action) {
+ $permission = actions_permissions_get_perm($action['label'], $key);
+
+ $permissions[$permission] = array(
+ 'title' => t('Execute %label', array('%label' => $action['label'])),
+ );
+ }
+ return $permissions;
+}
+
+/**
+ * Get a list of advanced actions (created through the Action UI).
+ */
+function _actions_permissions_advanced_actions_list() {
+ $actions = array();
+ // Actions provided by Drupal that aren't usable in a VBO context.
+ $hidden_actions = array(
+ 'system_block_ip_action',
+ 'system_goto_action',
+ 'system_message_action',
+ );
+
+ $result = db_query("SELECT * FROM {actions} WHERE parameters > ''");
+ foreach ($result as $action) {
+ if (in_array($action->callback, $hidden_actions)) {
+ continue;
+ }
+
+ $parameters = unserialize($action->parameters);
+ $actions[$action->aid] = array(
+ 'label' => $action->label,
+ 'type' => $action->type,
+ );
+ }
+ return $actions;
+}
+
+/**
+ * Returns the permission name used in user_access().
+ */
+function actions_permissions_get_perm($label, $key) {
+ return "execute $key";
+}
diff --git a/sites/all/modules/views_bulk_operations/css/modify.action.css b/sites/all/modules/views_bulk_operations/css/modify.action.css
new file mode 100644
index 000000000..e4f24af43
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/css/modify.action.css
@@ -0,0 +1,4 @@
+div[class*="show-value"] {
+ float: left;
+ margin-right: 20px;
+}
diff --git a/sites/all/modules/views_bulk_operations/css/views_bulk_operations.css b/sites/all/modules/views_bulk_operations/css/views_bulk_operations.css
new file mode 100644
index 000000000..a93cf22a6
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/css/views_bulk_operations.css
@@ -0,0 +1,35 @@
+.vbo-select-all-markup, .vbo-table-select-all-markup {
+ display: none;
+}
+
+/* "Select all" for table styles. */
+.vbo-views-form thead .form-type-checkbox {
+ margin: 0;
+}
+.vbo-table-select-all {
+ display: none;
+}
+.views-table-row-select-all {
+ display: none;
+}
+.views-table-row-select-all td {
+ text-align: center;
+}
+.vbo-table-select-all-pages, .vbo-table-select-this-page {
+ margin: 0 !important;
+ padding: 2px 5px !important;
+}
+
+/* Generic "select all" */
+.vbo-fieldset-select-all {
+ text-align: center;
+ width: 210px;
+ padding: 0.6em 0;
+}
+.vbo-fieldset-select-all .form-item {
+ margin-bottom: 0;
+}
+.vbo-fieldset-select-all div {
+ padding: 0 !important;
+ margin: 0 !important;
+}
diff --git a/sites/all/modules/views_bulk_operations/js/views_bulk_operations.js b/sites/all/modules/views_bulk_operations/js/views_bulk_operations.js
new file mode 100644
index 000000000..ca76df863
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/js/views_bulk_operations.js
@@ -0,0 +1,127 @@
+(function ($) {
+ Drupal.behaviors.vbo = {
+ attach: function(context) {
+ $('.vbo-views-form', context).each(function() {
+ Drupal.vbo.initTableBehaviors(this);
+ Drupal.vbo.initGenericBehaviors(this);
+ });
+ }
+ }
+
+ Drupal.vbo = Drupal.vbo || {};
+ Drupal.vbo.initTableBehaviors = function(form) {
+ // If the table is not grouped, "Select all on this page / all pages"
+ // markup gets inserted below the table header.
+ var selectAllMarkup = $('.vbo-table-select-all-markup', form);
+ if (selectAllMarkup.length) {
+ $('.views-table > tbody', form).prepend('<tr class="views-table-row-select-all even">></tr>');
+ var colspan = $('table th', form).length;
+ $('.views-table-row-select-all', form).html('<td colspan="' + colspan + '">' + selectAllMarkup.html() + '</td>');
+
+ $('.vbo-table-select-all-pages', form).click(function() {
+ Drupal.vbo.tableSelectAllPages(form);
+ return false;
+ });
+ $('.vbo-table-select-this-page', form).click(function() {
+ Drupal.vbo.tableSelectThisPage(form);
+ return false;
+ });
+ }
+
+ $('.vbo-table-select-all', form).show();
+ // This is the "select all" checkbox in (each) table header.
+ $('.vbo-table-select-all', form).click(function() {
+ var table = $(this).closest('table')[0];
+ $('input[id^="edit-views-bulk-operations"]:not(:disabled)', table).attr('checked', this.checked);
+
+ // Toggle the visibility of the "select all" row (if any).
+ if (this.checked) {
+ $('.views-table-row-select-all', table).show();
+ }
+ else {
+ $('.views-table-row-select-all', table).hide();
+ // Disable "select all across pages".
+ Drupal.vbo.tableSelectThisPage(form);
+ }
+ });
+
+ // Set up the ability to click anywhere on the row to select it.
+ if (Drupal.settings.vbo.row_clickable) {
+ $('.views-table tbody tr', form).click(function(event) {
+ if (event.target.tagName.toLowerCase() != 'input' && event.target.tagName.toLowerCase() != 'a') {
+ $('input[id^="edit-views-bulk-operations"]:not(:disabled)', this).each(function() {
+ var checked = this.checked;
+ // trigger() toggles the checkmark *after* the event is set,
+ // whereas manually clicking the checkbox toggles it *beforehand*.
+ // that's why we manually set the checkmark first, then trigger the
+ // event (so that listeners get notified), then re-set the checkmark
+ // which the trigger will have toggled. yuck!
+ this.checked = !checked;
+ $(this).trigger('click');
+ this.checked = !checked;
+ });
+ }
+ });
+ }
+ }
+
+ Drupal.vbo.tableSelectAllPages = function(form) {
+ $('.vbo-table-this-page', form).hide();
+ $('.vbo-table-all-pages', form).show();
+ // Modify the value of the hidden form field.
+ $('.select-all-rows', form).val('1');
+ }
+ Drupal.vbo.tableSelectThisPage = function(form) {
+ $('.vbo-table-all-pages', form).hide();
+ $('.vbo-table-this-page', form).show();
+ // Modify the value of the hidden form field.
+ $('.select-all-rows', form).val('0');
+ }
+
+ Drupal.vbo.initGenericBehaviors = function(form) {
+ // Show the "select all" fieldset.
+ $('.vbo-select-all-markup', form).show();
+
+ $('.vbo-select-this-page', form).click(function() {
+ $('input[id^="edit-views-bulk-operations"]', form).attr('checked', this.checked);
+ $('.vbo-select-all-pages', form).attr('checked', false);
+
+ // Toggle the "select all" checkbox in grouped tables (if any).
+ $('.vbo-table-select-all', form).attr('checked', this.checked);
+ });
+ $('.vbo-select-all-pages', form).click(function() {
+ $('input[id^="edit-views-bulk-operations"]', form).attr('checked', this.checked);
+ $('.vbo-select-this-page', form).attr('checked', false);
+
+ // Toggle the "select all" checkbox in grouped tables (if any).
+ $('.vbo-table-select-all', form).attr('checked', this.checked);
+
+ // Modify the value of the hidden form field.
+ $('.select-all-rows', form).val(this.checked);
+ });
+
+ $('.vbo-select', form).click(function() {
+ // If a checkbox was deselected, uncheck any "select all" checkboxes.
+ if (!this.checked) {
+ $('.vbo-select-this-page', form).attr('checked', false);
+ $('.vbo-select-all-pages', form).attr('checked', false);
+ // Modify the value of the hidden form field.
+ $('.select-all-rows', form).val('0')
+
+ var table = $(this).closest('table')[0];
+ if (table) {
+ // Uncheck the "select all" checkbox in the table header.
+ $('.vbo-table-select-all', table).attr('checked', false);
+
+ // If there's a "select all" row, hide it.
+ if ($('.vbo-table-select-this-page', table).length) {
+ $('.views-table-row-select-all', table).hide();
+ // Disable "select all across pages".
+ Drupal.vbo.tableSelectThisPage(form);
+ }
+ }
+ }
+ });
+ }
+
+})(jQuery);
diff --git a/sites/all/modules/views_bulk_operations/plugins/operation_types/action.class.php b/sites/all/modules/views_bulk_operations/plugins/operation_types/action.class.php
new file mode 100644
index 000000000..f3e60a305
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/plugins/operation_types/action.class.php
@@ -0,0 +1,261 @@
+<?php
+
+/**
+ * @file
+ * Defines the class for core actions.
+ * Belongs to the "action" operation type plugin.
+ */
+
+class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation {
+
+ /**
+ * Contains the options provided by the user in the configuration form.
+ *
+ * @var array
+ */
+ public $formOptions = array();
+
+ /**
+ * Returns the access bitmask for the operation, used for entity access checks.
+ */
+ public function getAccessMask() {
+ // Assume edit by default.
+ if (empty($this->operationInfo['behavior'])) {
+ $this->operationInfo['behavior'] = array('changes_property');
+ }
+
+ $mask = 0;
+ if (in_array('views_property', $this->operationInfo['behavior'])) {
+ $mask |= VBO_ACCESS_OP_VIEW;
+ }
+ if (in_array('changes_property', $this->operationInfo['behavior'])) {
+ $mask |= VBO_ACCESS_OP_UPDATE;
+ }
+ if (in_array('creates_property', $this->operationInfo['behavior'])) {
+ $mask |= VBO_ACCESS_OP_CREATE;
+ }
+ if (in_array('deletes_property', $this->operationInfo['behavior'])) {
+ $mask |= VBO_ACCESS_OP_DELETE;
+ }
+ return $mask;
+ }
+
+ /**
+ * Returns whether the provided account has access to execute the operation.
+ *
+ * @param $account
+ */
+ public function access($account) {
+ // Use actions_permissions if enabled.
+ if (module_exists('actions_permissions')) {
+ $perm = actions_permissions_get_perm($this->operationInfo['label'], $this->operationInfo['key']);
+ if (!user_access($perm, $account)) {
+ return FALSE;
+ }
+ }
+ // Check against additional permissions.
+ if (!empty($this->operationInfo['permissions'])) {
+ foreach ($this->operationInfo['permissions'] as $perm) {
+ if (!user_access($perm, $account)) {
+ return FALSE;
+ }
+ }
+ }
+ // Access granted.
+ return TRUE;
+ }
+
+ /**
+ * Returns the configuration form for the operation.
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ * @param $context
+ * An array of related data provided by the caller.
+ */
+ public function form($form, &$form_state, array $context) {
+ // Some modules (including this one) place their action callbacks
+ // into separate files. At this point those files might no longer be
+ // included due to an #ajax rebuild, so we call actions_list() to trigger
+ // inclusion. The same thing is done by actions_do() on execute.
+ actions_list();
+
+ $context['settings'] = $this->getAdminOption('settings', array());
+ $form_callback = $this->operationInfo['callback'] . '_form';
+ return $form_callback($context, $form_state);
+ }
+
+ /**
+ * Validates the configuration form.
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ */
+ public function formValidate($form, &$form_state) {
+ // Some modules (including this one) place their action callbacks
+ // into separate files. At this point those files might no longer be
+ // included due to a page reload, so we call actions_list() to trigger
+ // inclusion. The same thing is done by actions_do() on execute.
+ actions_list();
+
+ $validation_callback = $this->operationInfo['callback'] . '_validate';
+ if (function_exists($validation_callback)) {
+ $validation_callback($form, $form_state);
+ }
+ }
+
+ /**
+ * Handles the submitted configuration form.
+ * This is where the operation can transform and store the submitted data.
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ */
+ public function formSubmit($form, &$form_state) {
+ // Some modules (including this one) place their action callbacks
+ // into separate files. At this point those files might no longer be
+ // included due to a page reload, so we call actions_list() to trigger
+ // inclusion. The same thing is done by actions_do() on execute.
+ actions_list();
+
+ $submit_callback = $this->operationInfo['callback'] . '_submit';
+ $this->formOptions = $submit_callback($form, $form_state);
+ }
+
+ /**
+ * Returns the admin options form for the operation.
+ *
+ * The admin options form is embedded into the VBO field settings and used
+ * to configure operation behavior. The options can later be fetched
+ * through the getAdminOption() method.
+ *
+ * @param $dom_id
+ * The dom path to the level where the admin options form is embedded.
+ * Needed for #dependency.
+ * @param $field_handler
+ * The Views field handler object for the VBO field.
+ */
+ public function adminOptionsForm($dom_id, $field_handler) {
+ $form = parent::adminOptionsForm($dom_id, $field_handler);
+
+ $settings_form_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form';
+ if (function_exists($settings_form_callback)) {
+ $settings = $this->getAdminOption('settings', array());
+
+ $form['settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Operation settings'),
+ '#collapsible' => TRUE,
+ '#dependency' => array(
+ $dom_id . '-selected' => array(1),
+ ),
+ );
+ $settings_dom_id = $dom_id . '-settings';
+ $form['settings'] += $settings_form_callback($settings, $this->entityType, $settings_dom_id);
+ }
+
+ return $form;
+ }
+
+ /**
+ * Validates the admin options form.
+ *
+ * @param $form
+ * The admin options form.
+ * @param $form_state
+ * An array containing the current state of the form. Note that this array
+ * is constructed by the VBO views field handler, so it's not a real form
+ * state, it contains only the 'values' key.
+ * @param $error_element_base
+ * The base to prepend to field names when using form_set_error().
+ * Needed because the admin settings form is embedded into a bigger form.
+ */
+ public function adminOptionsFormValidate($form, &$form_state, $error_element_base) {
+ parent::adminOptionsFormValidate($form, $form_state, $error_element_base);
+
+ if (!empty($form['settings'])) {
+ $settings_validation_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form_validate';
+ if (function_exists($settings_validation_callback)) {
+ $fake_form = $form['settings'];
+ $fake_form_state = array('values' => &$form_state['values']['settings']);
+ $error_element_base .= 'settings][';
+
+ $settings_validation_callback($fake_form, $fake_form_state, $error_element_base);
+ }
+ }
+ }
+
+ /**
+ * Handles the submitted admin options form.
+ * Note that there is no need to handle saving the options, that is done
+ * by the VBO views field handler, which also injects the options into the
+ * operation object upon instantiation.
+ *
+ * @param $form
+ * The admin options form.
+ * @param $form_state
+ * An array containing the current state of the form. Note that this array
+ * is constructed by the VBO views field handler, so it's not a real form
+ * state, it contains only the 'values' key.
+ */
+ public function adminOptionsFormSubmit($form, &$form_state) {
+ parent::adminOptionsFormSubmit($form, $form_state);
+
+ if (!empty($form['settings'])) {
+ $settings_submit_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form_submit';
+ if (function_exists($settings_submit_callback)) {
+ $fake_form = $form['settings'];
+ $fake_form_state = array('values' => &$form_state['values']['settings']);
+
+ $settings_submit_callback($form, $form_state);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the operation needs the full selected views rows to be
+ * passed to execute() as a part of $context.
+ */
+ public function needsRows() {
+ return !empty($this->operationInfo['pass rows']);
+ }
+
+ /**
+ * Executes the selected operation on the provided data.
+ *
+ * @param $data
+ * The data to to operate on. An entity or an array of entities.
+ * @param $context
+ * An array of related data (selected views rows, etc).
+ */
+ public function execute($data, array $context) {
+ $context['entity_type'] = $this->entityType;
+ $context['settings'] = $this->getAdminOption('settings', array());
+ $context += $this->formOptions;
+ $context += $this->operationInfo['parameters'];
+ // Actions provided by the Drupal system module require the entity to be
+ // present in $context, keyed by entity type.
+ if (is_object($data)) {
+ $context[$this->entityType] = $data;
+ }
+
+ actions_do($this->operationInfo['callback'], $data, $context);
+
+ // The action might need to have its entities saved after execution.
+ if (in_array('changes_property', $this->operationInfo['behavior'])) {
+ $data = is_array($data) ? $data : array($data);
+ foreach ($data as $entity) {
+ entity_save($this->entityType, $entity);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/views_bulk_operations/plugins/operation_types/action.inc b/sites/all/modules/views_bulk_operations/plugins/operation_types/action.inc
new file mode 100644
index 000000000..2cdd6a121
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/plugins/operation_types/action.inc
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * CTools plugin. Provides support for core actions.
+ */
+
+$plugin = array(
+ 'title' => t('Action'),
+ 'list callback' => 'views_bulk_operations_operation_action_list',
+ 'handler' => array(
+ 'file' => 'action.class.php',
+ 'class' => 'ViewsBulkOperationsAction',
+ ),
+);
+
+/**
+ * Returns a prepared list of available actions.
+ *
+ * Actions are fetched by invoking hook_action_info() and by loading
+ * advanced actions from the database.
+ *
+ * @param $operation_id
+ * The full, prefixed operation_id of the operation (in this case, action)
+ * to return, or NULL to return an array with all operations.
+ */
+function views_bulk_operations_operation_action_list($operation_id = NULL) {
+ $operations = &drupal_static(__FUNCTION__);
+
+ if (!isset($operations)) {
+ // Combined list of all actions and advanced actions.
+ $actions_list = actions_list() + views_bulk_operations_operation_advanced_action_list();
+ // Actions provided by Drupal that aren't usable in a VBO context.
+ $hidden_actions = array(
+ 'system_block_ip_action',
+ 'system_goto_action',
+ 'system_message_action',
+ );
+
+ $operations = array();
+ foreach ($actions_list as $key => $action) {
+ // Actions are keyed by callback.
+ // Advanced actions are keyed by aid and store the callback separately.
+ $callback = isset($action['callback']) ? $action['callback'] : $key;
+ // This action needs to be skipped.
+ if (in_array($callback, $hidden_actions)) {
+ continue;
+ }
+
+ // All operations must be prefixed with the operation type.
+ $new_operation_id = 'action::' . $key;
+
+ $operations[$new_operation_id] = array(
+ 'operation_type' => 'action',
+ 'type' => $action['type'],
+ // Keep the unprefixed key around, for internal use.
+ 'key' => $key,
+ 'callback' => $callback,
+ 'label' => isset($action['label']) ? $action['label'] : '',
+ 'parameters' => isset($action['parameters']) ? $action['parameters'] : array(),
+ 'configurable' => !empty($action['configurable']) || !empty($action['vbo_configurable']),
+ 'aggregate' => !empty($action['aggregate']),
+ 'behavior' => isset($action['behavior']) ? $action['behavior'] : array(),
+ 'permissions' => isset($action['permissions']) ? $action['permissions'] : NULL,
+ 'pass rows' => !empty($action['pass rows']),
+ );
+ }
+ }
+
+ if (isset($operation_id)) {
+ return isset($operations[$operation_id]) ? $operations[$operation_id] : FALSE;
+ }
+ else {
+ return $operations;
+ }
+}
+
+/**
+ * Get a list of advanced actions (created through the Action UI).
+ */
+function views_bulk_operations_operation_advanced_action_list() {
+ $actions = array();
+ $static_actions = actions_list();
+ $result = db_query("SELECT * FROM {actions} WHERE parameters > ''");
+ foreach ($result as $action) {
+ $parameters = unserialize($action->parameters);
+ $actions[$action->aid] = array(
+ 'label' => isset($action->label) ? $action->label : '',
+ 'callback' => $action->callback,
+ 'type' => $action->type,
+ 'configurable' => FALSE,
+ 'parameters' => $parameters,
+ );
+ foreach (array('aggregate', 'behavior', 'permissions', 'pass rows') as $attribute) {
+ if (isset($static_actions[$action->callback][$attribute])) {
+ $actions[$action->aid][$attribute] = $static_actions[$action->callback][$attribute];
+ }
+ }
+ if (isset($static_actions[$action->callback]['parameters'])) {
+ $actions[$action->aid]['parameters'] = array_merge($actions[$action->aid]['parameters'], $static_actions[$action->callback]['parameters']);
+ }
+ }
+ return $actions;
+}
diff --git a/sites/all/modules/views_bulk_operations/plugins/operation_types/base.class.php b/sites/all/modules/views_bulk_operations/plugins/operation_types/base.class.php
new file mode 100644
index 000000000..968921fcd
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/plugins/operation_types/base.class.php
@@ -0,0 +1,271 @@
+<?php
+
+/**
+ * @file
+ * Defines the base class for operations.
+ */
+
+abstract class ViewsBulkOperationsBaseOperation {
+
+ /**
+ * The id of the operation.
+ *
+ * Composed of the operation_type plugin name and the operation key,
+ * divided by '::'. For example: action::node_publish_action.
+ */
+ public $operationId;
+
+ /**
+ * The entity type that the operation is operating on.
+ *
+ * Not the same as $operationInfo['type'] since that value can be just
+ * "entity", which means "available to every entity type".
+ */
+ public $entityType;
+
+ /**
+ * Contains information about the current operation, as generated
+ * by the "list callback" function in the plugin file.
+ *
+ * @var array
+ */
+ protected $operationInfo;
+
+ /**
+ * Contains the options set by the admin for the current operation.
+ *
+ * @var array
+ */
+ protected $adminOptions;
+
+ /**
+ * Constructs an operation object.
+ *
+ * @param $operationId
+ * The id of the operation.
+ * @param $entityType
+ * The entity type that the operation is operating on.
+ * @param $operationInfo
+ * An array of information about the operation.
+ * @param $adminOptions
+ * An array of options set by the admin.
+ */
+ public function __construct($operationId, $entityType, array $operationInfo, array $adminOptions) {
+ $this->operationId = $operationId;
+ $this->entityType = $entityType;
+ $this->operationInfo = $operationInfo;
+ $this->adminOptions = $adminOptions;
+ }
+
+ /**
+ * Returns the value of an admin option.
+ */
+ public function getAdminOption($key, $default = NULL) {
+ return isset($this->adminOptions[$key]) ? $this->adminOptions[$key] : $default;
+ }
+
+ /**
+ * Returns the access bitmask for the operation, used for entity access checks.
+ */
+ public function getAccessMask() {
+ // Assume edit by default.
+ return VBO_ACCESS_OP_UPDATE;
+ }
+
+ /**
+ * Returns the id of the operation.
+ */
+ public function id() {
+ return $this->operationId;
+ }
+
+ /**
+ * Returns the name of the operation_type plugin that provides the operation.
+ */
+ public function type() {
+ return $this->operationInfo['operation_type'];
+ }
+
+ /**
+ * Returns the human-readable name of the operation, meant to be shown
+ * to the user.
+ */
+ public function label() {
+ $admin_label = $this->getAdminOption('label');
+ if (!empty($admin_label)) {
+ $label = t($admin_label);
+ }
+ else {
+ // If the admin didn't specify any label, fallback to the default one.
+ $label = $this->operationInfo['label'];
+ }
+ return $label;
+ }
+
+ /**
+ * Returns the human-readable name of the operation, meant to be shown
+ * to the admin.
+ */
+ public function adminLabel() {
+ return $this->operationInfo['label'];
+ }
+
+ /**
+ * Returns whether the operation is configurable. Used to determine
+ * whether the operation's form methods should be invoked.
+ */
+ public function configurable() {
+ return !empty($this->operationInfo['configurable']);
+ }
+
+ /**
+ * Returns whether the provided account has access to execute the operation.
+ *
+ * @param $account
+ */
+ public function access($account) {
+ return TRUE;
+ }
+
+ /**
+ * Returns the configuration form for the operation.
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ * @param $context
+ * An array of related data provided by the caller.
+ */
+ abstract function form($form, &$form_state, array $context);
+
+ /**
+ * Validates the configuration form.
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ */
+ abstract function formValidate($form, &$form_state);
+
+ /**
+ * Handles the submitted configuration form.
+ * This is where the operation can transform and store the submitted data.
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ */
+ abstract function formSubmit($form, &$form_state);
+
+ /**
+ * Returns the admin options form for the operation.
+ *
+ * The admin options form is embedded into the VBO field settings and used
+ * to configure operation behavior. The options can later be fetched
+ * through the getAdminOption() method.
+ *
+ * @param $dom_id
+ * The dom path to the level where the admin options form is embedded.
+ * Needed for #dependency.
+ * @param $field_handler
+ * The Views field handler object for the VBO field.
+ */
+ public function adminOptionsForm($dom_id, $field_handler) {
+ $label = $this->getAdminOption('label', '');
+
+ $form = array();
+ $form['override_label'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Override label'),
+ '#default_value' => $label !== '',
+ '#dependency' => array(
+ $dom_id . '-selected' => array(1),
+ ),
+ );
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Provide label'),
+ '#title_display' => 'invisible',
+ '#default_value' => $label,
+ '#dependency' => array(
+ $dom_id . '-selected' => array(1),
+ $dom_id . '-override-label' => array(1),
+ ),
+ '#dependency_count' => 2,
+ );
+
+ return $form;
+ }
+
+ /**
+ * Validates the admin options form.
+ *
+ * @param $form
+ * The admin options form.
+ * @param $form_state
+ * An array containing the current state of the form. Note that this array
+ * is constructed by the VBO views field handler, so it's not a real form
+ * state, it contains only the 'values' key.
+ * @param $error_element_base
+ * The base to prepend to field names when using form_set_error().
+ * Needed because the admin options form is embedded into a bigger form.
+ */
+ public function adminOptionsFormValidate($form, &$form_state, $error_element_base) {
+ // No need to do anything, but make the function have a body anyway
+ // so that it's callable by overriding methods.
+ }
+
+ /**
+ * Handles the submitted admin options form.
+ * Note that there is no need to handle saving the options, that is done
+ * by the VBO views field handler, which also injects the options into the
+ * operation object upon instantiation.
+ *
+ * @param $form
+ * The admin options form.
+ * @param $form_state
+ * An array containing the current state of the form. Note that this array
+ * is constructed by the VBO views field handler, so it's not a real form
+ * state, it contains only the 'values' key.
+ */
+ public function adminOptionsFormSubmit($form, &$form_state) {
+ // If the "Override label" checkbox was deselected, clear the entered value.
+ if (empty($form_state['values']['override_label'])) {
+ $form_state['values']['label'] = '';
+ }
+ }
+
+ /**
+ * Returns whether the selected entities should be aggregated
+ * (loaded in bulk and passed in together).
+ * To be avoided if possible, since aggregation makes it impossible to use
+ * Batch API or the Drupal Queue for execution.
+ */
+ public function aggregate() {
+ return !empty($this->operationInfo['aggregate']);
+ }
+
+ /**
+ * Returns whether the operation needs the full selected views rows to be
+ * passed to execute() as a part of $context.
+ */
+ public function needsRows() {
+ return FALSE;
+ }
+
+ /**
+ * Executes the selected operation on the provided data.
+ *
+ * @param $data
+ * The data to to operate on. An entity or an array of entities.
+ * @param $context
+ * An array of related data (selected views rows, etc).
+ */
+ abstract function execute($data, array $context);
+}
diff --git a/sites/all/modules/views_bulk_operations/plugins/operation_types/rules_component.class.php b/sites/all/modules/views_bulk_operations/plugins/operation_types/rules_component.class.php
new file mode 100644
index 000000000..c40da8b25
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/plugins/operation_types/rules_component.class.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Defines the class for rules components (rule, ruleset, action).
+ * Belongs to the "rules_component" operation type plugin.
+ */
+
+class ViewsBulkOperationsRulesComponent extends ViewsBulkOperationsBaseOperation {
+
+ /**
+ * Returns the access bitmask for the operation, used for entity access checks.
+ *
+ * Rules has its own permission system, so the lowest bitmask is enough.
+ */
+ public function getAccessMask() {
+ return VBO_ACCESS_OP_VIEW;
+ }
+
+ /**
+ * Returns whether the provided account has access to execute the operation.
+ *
+ * @param $account
+ */
+ public function access($account) {
+ return rules_action('component_' . $this->operationInfo['key'])->access();
+ }
+
+ /**
+ * Returns the configuration form for the operation.
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ * @param $context
+ * An array of related data provided by the caller.
+ */
+ public function form($form, &$form_state, array $context) {
+ $entity_key = $this->operationInfo['parameters']['entity_key'];
+ // List types need to match the original, so passing list<node> instead of
+ // list<entity> won't work. However, passing 'node' instead of 'entity'
+ // will work, and is needed in order to get the right tokens.
+ $list_type = 'list<' . $this->operationInfo['type'] . '>';
+ $entity_type = $this->aggregate() ? $list_type : $this->entityType;
+ $info = entity_get_info($this->entityType);
+
+ // The component action is wrapped in an action set using the entity, so
+ // that the action configuration form can make use of the entity e.g. for
+ // tokens.
+ $set = rules_action_set(array($entity_key => array('type' => $entity_type, 'label' => $info['label'])));
+ $action = rules_action('component_' . $this->operationInfo['key'], array($entity_key . ':select' => $entity_key));
+ $set->action($action);
+
+ // Embed the form of the component action, but default to "input" mode for
+ // all parameters if available.
+ foreach ($action->parameterInfo() as $name => $info) {
+ $form_state['parameter_mode'][$name] = 'input';
+ }
+ $action->form($form, $form_state);
+
+ // Remove the configuration form element for the "entity" param, as it
+ // should just use the passed in entity.
+ unset($form['parameter'][$entity_key]);
+
+ // Tweak direct input forms to be more end-user friendly.
+ foreach ($action->parameterInfo() as $name => $info) {
+ // Remove the fieldset and move its title to the form element.
+ if (isset($form['parameter'][$name]['settings'][$name]['#title'])) {
+ $form['parameter'][$name]['#type'] = 'container';
+ $form['parameter'][$name]['settings'][$name]['#title'] = $form['parameter'][$name]['#title'];
+ }
+ // Hide the switch button if it's there.
+ if (isset($form['parameter'][$name]['switch_button'])) {
+ $form['parameter'][$name]['switch_button']['#access'] = FALSE;
+ }
+ }
+
+ return $form;
+ }
+
+ /**
+ * Validates the configuration form.
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ */
+ public function formValidate($form, &$form_state) {
+ rules_ui_form_rules_config_validate($form, $form_state);
+ }
+
+ /**
+ * Stores the rules element added to the form state in form(), so that it
+ * can be used in execute().
+ * Only called if the operation is declared as configurable.
+ *
+ * @param $form
+ * The views form.
+ * @param $form_state
+ * An array containing the current state of the form.
+ */
+ public function formSubmit($form, &$form_state) {
+ $this->rulesElement = $form_state['rules_element']->root();
+ }
+
+ /**
+ * Executes the selected operation on the provided data.
+ *
+ * @param $data
+ * The data to to operate on. An entity or an array of entities.
+ * @param $context
+ * An array of related data (selected views rows, etc).
+ */
+ public function execute($data, array $context) {
+ // If there was a config form, there's a rules_element.
+ // If not, fallback to the component key.
+ if ($this->configurable()) {
+ $element = $this->rulesElement;
+ }
+ else {
+ $element = rules_action('component_' . $this->operationInfo['parameters']['component_key']);
+ }
+ $wrapper_type = is_array($data) ? "list<{$this->entityType}>" : $this->entityType;
+ $wrapper = entity_metadata_wrapper($wrapper_type, $data);
+ $element->execute($wrapper);
+ }
+}
diff --git a/sites/all/modules/views_bulk_operations/plugins/operation_types/rules_component.inc b/sites/all/modules/views_bulk_operations/plugins/operation_types/rules_component.inc
new file mode 100644
index 000000000..81f5c46d6
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/plugins/operation_types/rules_component.inc
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * CTools plugin. Provides support for rules components (rule, ruleset, action).
+ */
+
+$plugin = array(
+ 'title' => t('Rules component'),
+ 'list callback' => 'views_bulk_operations_operation_rules_component_list',
+ 'handler' => array(
+ 'file' => 'rules_component.class.php',
+ 'class' => 'ViewsBulkOperationsRulesComponent',
+ ),
+);
+
+/**
+ * Returns a prepared list of available rules components.
+ *
+ * @param $operation_id
+ * The full, prefixed operation_id of the operation (in this case, rules
+ * component) to return, or NULL to return an array with all operations.
+ */
+function views_bulk_operations_operation_rules_component_list($operation_id = NULL) {
+ if (!module_exists('rules')) {
+ return array();
+ }
+
+ $entity_info = entity_get_info();
+ $entity_types = array_keys($entity_info);
+ $supported_types = array('entity', 'list<entity>');
+ $list_types = array('list<entity>');
+ foreach ($entity_types as $type) {
+ $supported_types[] = $type;
+ $supported_types[] = "list<$type>";
+ $list_types[] = "list<$type>";
+ }
+
+ $components = array();
+ if (isset($operation_id)) {
+ $id_fragments = explode('::', $operation_id);
+ $components[$id_fragments[1]] = rules_config_load($id_fragments[1]);
+ // No need to go any further if the component no longer exists.
+ if (!$components[$id_fragments[1]]) {
+ return FALSE;
+ }
+ }
+ else {
+ $components = rules_get_components(FALSE, 'action');
+ }
+
+ $operations = array();
+ foreach ($components as $name => $component) {
+ $parameter_info = $component->parameterInfo();
+ $first_parameter = reset($parameter_info);
+ $parameter_keys = array_keys($parameter_info);
+ $entity_key = reset($parameter_keys);
+ // If the first param is not an entity type, skip the component.
+ if (!in_array($first_parameter['type'], $supported_types)) {
+ continue;
+ }
+
+ // If the first parameter is a list type (list<node>, list<entity>, etc)
+ // then turn aggregation on, and set the correct entity type.
+ if (in_array($first_parameter['type'], $list_types)) {
+ $type = str_replace(array('list<', '>'), '', $first_parameter['type']);
+ $aggregate = TRUE;
+ }
+ else {
+ $type = $first_parameter['type'];
+ $aggregate = FALSE;
+ }
+
+ // All operations must be prefixed with the operation type.
+ $new_operation_id = 'rules_component::' . $name;
+ $operations[$new_operation_id] = array(
+ 'operation_type' => 'rules_component',
+ // Keep the unprefixed key around, for internal use.
+ 'key' => $name,
+ 'label' => $component->label,
+ 'parameters' => array('component_key' => $name, 'entity_key' => $entity_key),
+ 'configurable' => count($parameter_info) > 1,
+ 'type' => $type,
+ 'aggregate' => $aggregate,
+ );
+ }
+
+ if (isset($operation_id)) {
+ return isset($operations[$operation_id]) ? $operations[$operation_id] : FALSE;
+ }
+ else {
+ return $operations;
+ }
+}
diff --git a/sites/all/modules/views_bulk_operations/views/views_bulk_operations.views.inc b/sites/all/modules/views_bulk_operations/views/views_bulk_operations.views.inc
new file mode 100644
index 000000000..1c7078a53
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/views/views_bulk_operations.views.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function views_bulk_operations_views_data_alter(&$data) {
+ foreach (entity_get_info() as $entity_type => $info) {
+ if (isset($info['base table']) && isset($data[$info['base table']]['table'])) {
+ $data[$info['base table']]['views_bulk_operations'] = array(
+ 'title' => $data[$info['base table']]['table']['group'],
+ 'group' => t('Bulk operations'),
+ 'help' => t('Provide a checkbox to select the row for bulk operations.'),
+ 'real field' => $info['entity keys']['id'],
+ 'field' => array(
+ 'handler' => 'views_bulk_operations_handler_field_operations',
+ 'click sortable' => FALSE,
+ ),
+ );
+ }
+ if (isset($info['revision table']) && isset($data[$info['revision table']]['table'])) {
+ $data[$info['revision table']]['views_bulk_operations'] = array(
+ 'title' => $data[$info['revision table']]['table']['group'],
+ 'group' => t('Bulk operations'),
+ 'help' => t('Provide a checkbox to select the row for bulk operations.'),
+ 'real field' => $info['entity keys']['revision'],
+ 'field' => array(
+ 'handler' => 'views_bulk_operations_handler_field_operations',
+ 'click sortable' => FALSE,
+ ),
+ );
+ }
+ }
+}
diff --git a/sites/all/modules/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc b/sites/all/modules/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc
new file mode 100644
index 000000000..61886d48e
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc
@@ -0,0 +1,346 @@
+<?php
+
+/**
+* @file
+* Views field handler. Contains all relevant VBO options and related logic.
+* Implements the Views Form API.
+*/
+
+class views_bulk_operations_handler_field_operations extends views_handler_field {
+ var $revision = FALSE;
+
+ function init(&$view, &$options) {
+ parent::init($view, $options);
+
+ // Update old settings
+ if (!empty($options['vbo']) && empty($this->options['vbo_operations'])) {
+ $this->options['vbo_operations'] = $options['vbo']['operations'];
+ unset($options['vbo']['operations']);
+ $this->options['vbo_settings'] = $options['vbo'] + $this->options['vbo_settings'];
+ }
+ // When updating old Views it is possible for this value to stay empty.
+ if (empty($this->options['vbo_settings']['entity_load_capacity'])) {
+ $this->options['vbo_settings']['entity_load_capacity'] = 10;
+ }
+
+ foreach ($this->options['vbo_operations'] as $operation_id => &$operation_options) {
+ // Prefix all un-prefixed operations.
+ if (strpos($operation_id, '::') === FALSE) {
+ $operations = views_bulk_operations_get_operation_info();
+ // Basically, guess.
+ foreach (array('action', 'rules_component') as $operation_type) {
+ $new_operation_id = $operation_type . '::' . $operation_id;
+ if (isset($operations[$new_operation_id])) {
+ $this->options['vbo_operations'][$new_operation_id] = $operation_options;
+ break;
+ }
+ }
+
+ // Remove the old operation in any case.
+ unset($this->options['vbo_operations'][$operation_id]);
+ }
+
+ // Rename the use_queue setting.
+ if (isset($operation_options['use_queue']) && !isset($operation_options['postpone_processing'])) {
+ $operation_options['postpone_processing'] = $operation_options['use_queue'];
+ unset($operation_options['use_queue']);
+ }
+ }
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ $options['vbo_settings'] = array(
+ 'contains' => array(
+ 'display_type' => array('default' => 0),
+ 'enable_select_all_pages' => array('default' => TRUE),
+ 'row_clickable' => array('default' => TRUE),
+ 'force_single' => array('default' => FALSE),
+ 'entity_load_capacity' => array('default' => 10),
+ 'skip_batching' => array('default' => 0),
+ ),
+ );
+ $options['vbo_operations'] = array(
+ 'default' => array(),
+ 'unpack_translatable' => 'unpack_operations',
+ 'export' => 'export_vbo_operations',
+ );
+
+ return $options;
+ }
+
+ function export_vbo_operations($indent, $prefix, $storage, $option, $definition, $parents) {
+ // Anti-recursion, since we use the parent export helper.
+ unset($definition['export']);
+
+ // Find and remove all unselected/disabled operations.
+ foreach ($storage['vbo_operations'] as $operation_id => $operation) {
+ if (empty($operation['selected'])) {
+ unset($storage['vbo_operations'][$operation_id]);
+ }
+ }
+
+ return parent::export_option($indent, $prefix, $storage, $option, $definition, $parents);
+ }
+
+ function unpack_operations(&$translatable, $storage, $option, $definition, $parents, $keys) {
+ $translatable[] = array(
+ 'value' => t('- Choose an operation -'),
+ 'keys' => array_merge($keys, array('noop')),
+ );
+ foreach ($storage[$option] as $key => $operation) {
+ if (!empty($operation['override_label']) && !empty($operation['label'])) {
+ $translatable[] = array(
+ 'value' => $operation['label'],
+ 'keys' => array_merge($keys, array($key)),
+ );
+ }
+ }
+ }
+
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['vbo_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Bulk operations settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['vbo_settings']['display_type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Display operations as'),
+ '#default_value' => $this->options['vbo_settings']['display_type'],
+ '#options' => array(
+ t('Dropdown selectbox with Submit button'),
+ t('Each action as a separate button'),
+ ),
+ );
+ $form['vbo_settings']['enable_select_all_pages'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable "Select all items on all pages"'),
+ '#default_value' => $this->options['vbo_settings']['enable_select_all_pages'],
+ '#description' => t('Check this box to enable the ability to select all items on all pages.'),
+ );
+ $form['vbo_settings']['row_clickable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Make the whole row clickable'),
+ '#default_value' => $this->options['vbo_settings']['row_clickable'],
+ '#description' => t('Check this box to select an item when its row has been clicked. Requires JavaScript.'),
+ );
+ $form['vbo_settings']['force_single'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Force single'),
+ '#default_value' => $this->options['vbo_settings']['force_single'],
+ '#description' => t('Check this box to restrict selection to a single value.'),
+ );
+ $form['vbo_settings']['entity_load_capacity'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Number of entities to load at once'),
+ '#description' => t("Improve execution performance at the cost of memory usage. Set to '1' if you're having problems."),
+ '#default_value' => $this->options['vbo_settings']['entity_load_capacity'],
+ );
+ $form['vbo_settings']['skip_batching'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Skip batching'),
+ '#default_value' => $this->options['vbo_settings']['skip_batching'],
+ '#description' => '<b>' . t('Warning:') . '</b> ' . t('This will cause timeouts for larger amounts of selected items.'),
+ );
+
+ // Display operations and their settings.
+ $form['vbo_operations'] = array(
+ '#tree' => TRUE,
+ '#type' => 'fieldset',
+ '#title' => t('Selected bulk operations'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ );
+
+ $entity_type = $this->get_entity_type();
+ $options = $this->options['vbo_operations'];
+ foreach (views_bulk_operations_get_applicable_operations($entity_type, $options) as $operation_id => $operation) {
+ $operation_options = !empty($options[$operation_id]) ? $options[$operation_id] : array();
+
+ $dom_id = 'edit-options-vbo-operations-' . str_replace(array('_', ':'), array('-', ''), $operation_id);
+ $form['vbo_operations'][$operation_id]['selected'] = array(
+ '#type' => 'checkbox',
+ '#title' => $operation->adminLabel(),
+ '#default_value' => !empty($operation_options['selected']),
+ );
+ if (!$operation->aggregate()) {
+ $form['vbo_operations'][$operation_id]['postpone_processing'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enqueue the operation instead of executing it directly'),
+ '#default_value' => !empty($operation_options['postpone_processing']),
+ '#dependency' => array(
+ $dom_id . '-selected' => array(1),
+ ),
+ );
+ }
+ $form['vbo_operations'][$operation_id]['skip_confirmation'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Skip confirmation step'),
+ '#default_value' => !empty($operation_options['skip_confirmation']),
+ '#dependency' => array(
+ $dom_id . '-selected' => array(1),
+ ),
+ );
+
+ $form['vbo_operations'][$operation_id] += $operation->adminOptionsForm($dom_id, $this);
+ }
+ }
+
+ function options_validate(&$form, &$form_state) {
+ parent::options_validate($form, $form_state);
+
+ $entity_type = $this->get_entity_type();
+ foreach ($form_state['values']['options']['vbo_operations'] as $operation_id => &$options) {
+ if (empty($options['selected'])) {
+ continue;
+ }
+
+ $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
+ $fake_form = $form['vbo_operations'][$operation_id];
+ $fake_form_state = array('values' => &$options);
+ $error_element_base = 'vbo_operations][' . $operation_id . '][';
+ $operation->adminOptionsFormValidate($fake_form, $fake_form_state, $error_element_base);
+ }
+ }
+
+ function options_submit(&$form, &$form_state) {
+ parent::options_submit($form, $form_state);
+
+ $entity_type = $this->get_entity_type();
+ foreach ($form_state['values']['options']['vbo_operations'] as $operation_id => &$options) {
+ if (empty($options['selected'])) {
+ continue;
+ }
+
+ $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
+ $fake_form = $form['vbo_operations'][$operation_id];
+ $fake_form_state = array('values' => &$options);
+ $operation->adminOptionsFormSubmit($fake_form, $fake_form_state);
+ }
+ }
+
+ /**
+ * Returns the value of a vbo option.
+ */
+ function get_vbo_option($key, $default = NULL) {
+ return isset($this->options['vbo_settings'][$key]) ? $this->options['vbo_settings'][$key] : $default;
+ }
+
+ /**
+ * If the view is using a table style, provide a
+ * placeholder for a "select all" checkbox.
+ */
+ function label() {
+ if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof views_plugin_style_table && !$this->options['vbo_settings']['force_single']) {
+ return '<!--views-bulk-operations-select-all-->';
+ }
+ else {
+ return parent::label();
+ }
+ }
+
+ function render($values) {
+ return '<!--form-item-' . $this->options['id'] . '--' . $this->view->row_index . '-->';
+ }
+
+ /**
+ * The form which replaces the placeholder from render().
+ */
+ function views_form(&$form, &$form_state) {
+ // The view is empty, abort.
+ if (empty($this->view->result)) {
+ return;
+ }
+
+ $form[$this->options['id']] = array(
+ '#tree' => TRUE,
+ );
+ // At this point, the query has already been run, so we can access the results
+ // in order to get the base key value (for example, nid for nodes).
+ foreach ($this->view->result as $row_index => $row) {
+ $entity_id = $this->get_value($row);
+
+ if ($this->options['vbo_settings']['force_single']) {
+ $form[$this->options['id']][$row_index] = array(
+ '#type' => 'radio',
+ '#parents' => array($this->options['id']),
+ '#return_value' => $entity_id,
+ );
+ }
+ else {
+ $form[$this->options['id']][$row_index] = array(
+ '#type' => 'checkbox',
+ '#return_value' => $entity_id,
+ '#default_value' => FALSE,
+ '#attributes' => array('class' => array('vbo-select')),
+ );
+ }
+ }
+ }
+
+ public function get_selected_operations() {
+ global $user;
+ $selected = drupal_static(__FUNCTION__);
+ if (!isset($selected)) {
+ $entity_type = $this->get_entity_type();
+ $selected = array();
+ foreach ($this->options['vbo_operations'] as $operation_id => $options) {
+ if (empty($options['selected'])) {
+ continue;
+ }
+
+ $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
+ if (!$operation || !$operation->access($user)) {
+ continue;
+ }
+ $selected[$operation_id] = $operation;
+ }
+ }
+
+ return $selected;
+ }
+
+ /**
+ * Returns the options stored for the provided operation id.
+ */
+ public function get_operation_options($operation_id) {
+ $options = $this->options['vbo_operations'];
+ return isset($options[$operation_id]) ? $options[$operation_id] : array();
+ }
+
+ /**
+ * Determine the base table of the VBO field, and then use it to determine
+ * the entity type that VBO is operating on.
+ */
+ public function get_entity_type() {
+ $base_table = $this->view->base_table;
+
+ // If the current field is under a relationship you can't be sure that the
+ // base table of the view is the base table of the current field.
+ // For example a field from a node author on a node view does have users as base table.
+ if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
+ $relationships = $this->view->display_handler->get_option('relationships');
+ $options = $relationships[$this->options['relationship']];
+ $data = views_fetch_data($options['table']);
+ $base_table = $data[$options['field']]['relationship']['base'];
+ }
+ // The base table is now known, use it to determine the entity type.
+ foreach (entity_get_info() as $entity_type => $info) {
+ if (isset($info['base table']) && $info['base table'] == $base_table) {
+ return $entity_type;
+ }
+ elseif (isset($info['revision table']) && $info['revision table'] == $base_table) {
+ $this->revision = TRUE;
+ return $entity_type;
+ }
+ }
+ // This should never happen.
+ _views_bulk_operations_report_error("Could not determine the entity type for VBO field on views base table %table", array('%table' => $base_table));
+ return FALSE;
+ }
+}
diff --git a/sites/all/modules/views_bulk_operations/views_bulk_operations.api.php b/sites/all/modules/views_bulk_operations/views_bulk_operations.api.php
new file mode 100644
index 000000000..45546021e
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/views_bulk_operations.api.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by Views Bulk Operations.
+ */
+
+/**
+ * Perform alterations on the VBO form before it is rendered.
+ *
+ * Usually, if a module wanted to alter the VBO form through hook_form_alter(),
+ * it would need to duplicate the views form checks from
+ * views_bulk_operations_form_alter(), while making sure that the hook
+ * runs after VBO's hook (by increasing the weight of the altering module's
+ * system entry). In order to reduce that complexity, VBO provides this hook.
+ *
+ * @param $form
+ * A step of the VBO form to be altered.
+ * @param $form_state
+ * Form state. Contains the name of the current step in $form_state['step'].
+ * @param $vbo
+ * The VBO views field. Contains a reference to the view in $vbo->view.
+ */
+function hook_views_bulk_operations_form_alter(&$form, &$form_state, $vbo) {
+ if ($form_state['step'] == 'views_form_views_form') {
+ // Alter the first step of the VBO form (the selection page).
+ $form['select']['#title'] = t('Bulk operations');
+ }
+ elseif ($form_state['step'] == 'views_bulk_operations_config_form') {
+ // Alter the configuration step of the VBO form.
+ }
+ elseif ($form_state['step'] == 'views_bulk_operations_confirm_form') {
+ // Alter the confirmation step of the VBO form.
+ }
+}
diff --git a/sites/all/modules/views_bulk_operations/views_bulk_operations.drush.inc b/sites/all/modules/views_bulk_operations/views_bulk_operations.drush.inc
new file mode 100644
index 000000000..09c4fb637
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/views_bulk_operations.drush.inc
@@ -0,0 +1,196 @@
+<?php
+
+/**
+ * Implementation of hook_drush_help().
+ */
+function views_bulk_operations_drush_help($section) {
+ switch ($section) {
+ case 'drush:vbo-list':
+ return dt('List all Views Bulk Operations (VBO) views, along with the operations associated with each.');
+ case 'drush:vbo-execute':
+ return dt('Execute a bulk operation based on a Views Bulk Operations (VBO) view.');
+ }
+}
+
+/**
+ * Implementation of hook_drush_command().
+ */
+function views_bulk_operations_drush_command() {
+ $items['vbo-list'] = array(
+ 'callback' => 'views_bulk_operations_drush_list',
+ 'description' => 'List all Views Bulk Operations (VBO) views, along with the operations associated with each.',
+ );
+ $items['vbo-execute'] = array(
+ 'callback' => 'views_bulk_operations_drush_execute',
+ 'description' => 'Execute a bulk operation based on a Views Bulk Operations (VBO) view.',
+ 'arguments' => array(
+ 'vid' => 'ID or name of the view to be executed.',
+ 'operation_id' => 'ID of the operation to be applied on the view results.',
+ 'type:[name=]value ...' => 'Parameters to be passed as view input filters, view arguments or operation arguments, where type is respectively {input, argument, operation}.',
+ ),
+ 'examples' => array(
+ '$ drush vbo-execute action::admin_content node_publish_action' =>
+ 'Publish nodes returned by view admin_content.',
+ '$ drush vbo-execute 44 action::node_assign_owner_action operation:owner_uid=3' =>
+ 'Change node ownership on nodes returned by view #44, passing argument owner_uid=3 to the action.',
+ '$ drush vbo-execute admin_content action::node_unpublish_action input:type=expense argument:3' =>
+ 'Unpublish nodes returned by view admin_content, filtering results of type expense and passing value 3 as first view argument.',
+ ),
+ );
+ return $items;
+}
+
+/**
+ * Implementation of 'vbo list' command.
+ */
+function views_bulk_operations_drush_list() {
+ // Impersonate admin.
+ global $user;
+ $user = user_load(1);
+ drupal_save_session(FALSE);
+
+ // Find all VBO views and their associated operations.
+ $rows = array(array(sprintf('%5s', dt('View ID')), dt('Name'), dt('Description'), dt('Operations')));
+ foreach (views_get_all_views() as $name => $view) {
+ $view->build();
+ $vbo = _views_bulk_operations_get_field($view);
+ if ($vbo) {
+ $operations = array();
+ foreach ($vbo->get_selected_operations() as $operation_id => $operation) {
+ $operations[] = $operation->label() .' ('. $operation_id .')';
+ }
+ $operations[] = "---------------";
+ $rows[] = array(
+ sprintf('%5d', $view->vid),
+ $view->name,
+ $view->description,
+ implode("\n", $operations),
+ );
+ }
+ }
+ drush_print_table($rows, TRUE);
+}
+
+/**
+ * Implementation of 'vbo execute' command.
+ */
+function views_bulk_operations_drush_execute($vid = NULL, $operation_id = NULL) {
+ // Parse arguments.
+ if (is_null($vid)) {
+ drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_VID', dt('Please specify a view ID to execute.'));
+ return;
+ }
+ if (is_null($operation_id)) {
+ drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_OPERATION', dt('Please specify an operation to execute.'));
+ return;
+ }
+ $args = func_get_args();
+ $view_exposed_input = array();
+ $operation_arguments = array();
+ $view_arguments = array();
+ if (count($args) > 2) for ($i=2; $i<count($args); $i++) {
+ $parts = array();
+ if (FALSE === preg_match('/^(?<type>\w+):(?:(?<name>\w+)=)?(?<value>(.*?))$/', $args[$i], $parts)) {
+ drush_set_error('VIEWS_BULK_OPERATIONS_INVALID_PARAMETER', dt('The parameter %arg should be of the form type:[name=]value where type in {input, argument, operation}.', array('%arg' => $args[$i])));
+ return;
+ }
+ switch ($parts['type']) {
+ case 'input':
+ $view_exposed_input[$parts['name']] = $parts['value'];
+ break;
+ case 'operation':
+ $operation_arguments[$parts['name']] = $parts['value'];
+ break;
+ case 'argument':
+ $view_arguments[] = $parts['value'];
+ break;
+ default:
+ drush_set_error('VIEWS_BULK_OPERATIONS_UNKNOWN_PARAMETER', dt('The parameter type %type is unknown. Please specify either input, argument or operation.', array('%type' => $parts['type'])));
+ return;
+ }
+ }
+
+ // Impersonate admin.
+ global $user;
+ $user = user_load(1);
+ drupal_save_session(FALSE);
+
+ // Load the view.
+ $view = views_get_view($vid);
+ if (!is_object($view)) {
+ _views_bulk_operations_report_error('Could not find view %vid.', array('%vid' => $vid));
+ return;
+ }
+
+ // Build the view, so that the VBO field can be found.
+ $view->set_exposed_input($view_exposed_input);
+ $view->set_arguments($view_arguments);
+ $view->build();
+ $view->query->set_limit(NULL); // reset the work done by the pager
+ $view->query->set_offset(NULL);
+
+ // Find the VBO field.
+ $vbo = _views_bulk_operations_get_field($view);
+ if (!$vbo) {
+ _views_bulk_operations_report_error('Could not find a VBO field in view %vid.', array('%vid' => $vid));
+ return;
+ }
+
+ $view->execute();
+
+ // Find the selected operation.
+ $operations = $vbo->get_selected_operations();
+ if (!isset($operations[$operation_id])) {
+ _views_bulk_operations_report_error('Could not find operation %operation_id in view %vid.', array('%operation_id' => $operation_id, '%vid' => $vid));
+ return;
+ }
+ $operation = views_bulk_operations_get_operation($operation_id, $vbo->get_entity_type(), $vbo->get_operation_options($operation_id));
+ if ($operation_arguments) {
+ $operation->formOptions = $operation_arguments;
+ }
+
+ // Select all rows.
+ $rows = array();
+ $current = 1;
+ foreach ($view->result as $row_index => $result) {
+ $rows[$row_index] = array(
+ 'entity_id' => $vbo->get_value($result),
+ 'views_row' => array(),
+ 'position' => array(
+ 'current' => $current++,
+ 'total' => $view->total_rows,
+ ),
+ );
+ // Some operations require full selected rows.
+ if ($operation->needsRows()) {
+ $rows[$row_index]['views_row'] = $result;
+ }
+ }
+
+ // Enqueue the fetched rows.
+ $queue_name = 'views_bulk_operations_active_queue_' . db_next_id();
+ $options = array(
+ 'revision' => $vbo->revision,
+ 'entity_load_capacity' => $vbo->get_vbo_option('entity_load_capacity', 10),
+ );
+ views_bulk_operations_enqueue_rows($queue_name, $rows, $operation, $options);
+
+ // Process the queue using Batch API.
+ $batch = array(
+ 'file' => drupal_get_path('module', 'views_bulk_operations') . '/views_bulk_operations.module',
+ 'operations' => array(
+ array('views_bulk_operations_active_queue_process', array($queue_name, $operation, $vbo->view->total_rows)),
+ ),
+ 'finished' => '_views_bulk_operations_execute_finished',
+ 'title' => t('Performing %operation on the selected items...', array('%operation' => $operation->label())),
+ );
+ batch_set($batch);
+ drush_backend_batch_process();
+
+ // Looks like drush has no way to show messages set in subprocesses,
+ // so all batch messages get lost. Setting a success message manually here.
+ drush_log(dt('Performed "!operation" on @items.', array(
+ '!operation' => $operation->label(),
+ '@items' => format_plural($vbo->view->total_rows, '1 item', '@count items'),
+ )), 'ok');
+}
diff --git a/sites/all/modules/views_bulk_operations/views_bulk_operations.info b/sites/all/modules/views_bulk_operations/views_bulk_operations.info
new file mode 100644
index 000000000..8fbdffaa5
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/views_bulk_operations.info
@@ -0,0 +1,17 @@
+name = Views Bulk Operations
+description = Provides a way of selecting multiple rows and applying operations to them.
+dependencies[] = entity
+dependencies[] = views
+package = Views
+core = 7.x
+php = 5.2.9
+
+files[] = plugins/operation_types/base.class.php
+files[] = views/views_bulk_operations_handler_field_operations.inc
+
+; Information added by Drupal.org packaging script on 2015-07-01
+version = "7.x-3.3"
+core = "7.x"
+project = "views_bulk_operations"
+datestamp = "1435764542"
+
diff --git a/sites/all/modules/views_bulk_operations/views_bulk_operations.install b/sites/all/modules/views_bulk_operations/views_bulk_operations.install
new file mode 100644
index 000000000..0ef321d2d
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/views_bulk_operations.install
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Installation and update functions.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function views_bulk_operations_uninstall() {
+ // Remove VBO actions that are now orphaned.
+ actions_synchronize(TRUE);
+}
diff --git a/sites/all/modules/views_bulk_operations/views_bulk_operations.module b/sites/all/modules/views_bulk_operations/views_bulk_operations.module
new file mode 100644
index 000000000..e74eeb1df
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/views_bulk_operations.module
@@ -0,0 +1,1298 @@
+<?php
+
+/**
+ * @file
+ * Allows operations to be performed on items selected in a view.
+ */
+
+// Access operations.
+define('VBO_ACCESS_OP_VIEW', 0x01);
+define('VBO_ACCESS_OP_UPDATE', 0x02);
+define('VBO_ACCESS_OP_CREATE', 0x04);
+define('VBO_ACCESS_OP_DELETE', 0x08);
+
+/**
+ * Implements hook_action_info().
+ * Registers custom VBO actions as Drupal actions.
+ */
+function views_bulk_operations_action_info() {
+ $actions = array();
+ $files = views_bulk_operations_load_action_includes();
+ foreach ($files as $filename) {
+ $action_info_fn = 'views_bulk_operations_'. str_replace('.', '_', basename($filename, '.inc')).'_info';
+ $action_info = call_user_func($action_info_fn);
+ if (is_array($action_info)) {
+ $actions += $action_info;
+ }
+ }
+
+ return $actions;
+}
+
+/**
+ * Loads the VBO actions placed in their own include files (under actions/).
+ *
+ * @return
+ * An array of containing filenames of the included actions.
+ */
+function views_bulk_operations_load_action_includes() {
+ static $loaded = FALSE;
+
+ // The list of VBO actions is fairly static, so it's hardcoded for better
+ // performance (hitting the filesystem with file_scan_directory(), and then
+ // caching the result has its cost).
+ $files = array(
+ 'archive.action',
+ 'argument_selector.action',
+ 'book.action',
+ 'delete.action',
+ 'modify.action',
+ 'script.action',
+ 'user_roles.action',
+ 'user_cancel.action',
+ );
+
+ if (!$loaded) {
+ foreach ($files as $file) {
+ module_load_include('inc', 'views_bulk_operations', 'actions/' . $file);
+ }
+ $loaded = TRUE;
+ }
+
+ return $files;
+}
+
+/**
+ * Implements hook_cron().
+ *
+ * Deletes queue items belonging to VBO active queues (used by VBO's batches)
+ * that are older than a day (since they can only be a result of VBO crashing
+ * or the execution being interrupted in some other way). This is the interval
+ * used to cleanup batches in system_cron(), so it can't be increased.
+ *
+ * Note: This code is specific to SystemQueue. Other queue implementations will
+ * need to do their own garbage collection.
+ */
+function views_bulk_operations_cron() {
+ db_delete('queue')
+ ->condition('name', db_like('views_bulk_operations_active_queue_'), 'LIKE')
+ ->condition('created', REQUEST_TIME - 86400, '<')
+ ->execute();
+}
+
+/**
+ * Implements of hook_cron_queue_info().
+ */
+function views_bulk_operations_cron_queue_info() {
+ return array(
+ 'views_bulk_operations' => array(
+ 'worker callback' => 'views_bulk_operations_queue_item_process',
+ 'time' => 30,
+ ),
+ );
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function views_bulk_operations_views_api() {
+ return array(
+ 'api' => 3,
+ 'path' => drupal_get_path('module', 'views_bulk_operations') . '/views',
+ );
+}
+
+/**
+ * Implements hook_theme().
+ */
+function views_bulk_operations_theme() {
+ $themes = array(
+ 'views_bulk_operations_select_all' => array(
+ 'variables' => array('view' => NULL, 'enable_select_all_pages' => TRUE),
+ ),
+ 'views_bulk_operations_confirmation' => array(
+ 'variables' => array('rows' => NULL, 'vbo' => NULL, 'operation' => NULL, 'select_all_pages' => FALSE),
+ ),
+ );
+ $files = views_bulk_operations_load_action_includes();
+ foreach ($files as $filename) {
+ $action_theme_fn = 'views_bulk_operations_'. str_replace('.', '_', basename($filename, '.inc')).'_theme';
+ if (function_exists($action_theme_fn)) {
+ $themes += call_user_func($action_theme_fn);
+ }
+ }
+
+ return $themes;
+}
+
+/**
+ * Implements hook_ctools_plugin_type().
+ */
+function views_bulk_operations_ctools_plugin_type() {
+ return array(
+ 'operation_types' => array(
+ 'classes' => array(
+ 'handler',
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_ctools_plugin_directory().
+ */
+function views_bulk_operations_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'views_bulk_operations') {
+ return 'plugins/' . $plugin;
+ }
+}
+
+/**
+ * Fetch metadata for a specific operation type plugin.
+ *
+ * @param $operation_type
+ * Name of the plugin.
+ *
+ * @return
+ * An array with information about the requested operation type plugin.
+ */
+function views_bulk_operations_get_operation_type($operation_type) {
+ ctools_include('plugins');
+ return ctools_get_plugins('views_bulk_operations', 'operation_types', $operation_type);
+}
+
+/**
+ * Fetch metadata for all operation type plugins.
+ *
+ * @return
+ * An array of arrays with information about all available operation types.
+ */
+function views_bulk_operations_get_operation_types() {
+ ctools_include('plugins');
+ return ctools_get_plugins('views_bulk_operations', 'operation_types');
+}
+
+/**
+ * Gets the info array of an operation from the provider plugin.
+ *
+ * @param $operation_id
+ * The id of the operation for which the info shall be returned, or NULL
+ * to return an array with info about all operations.
+ */
+function views_bulk_operations_get_operation_info($operation_id = NULL) {
+ $operations = &drupal_static(__FUNCTION__);
+
+ if (!isset($operations)) {
+ $operations = array();
+ $plugins = views_bulk_operations_get_operation_types();
+ foreach ($plugins as $plugin) {
+ $operations += $plugin['list callback']();
+ }
+
+ uasort($operations, create_function('$a, $b', 'return strcasecmp($a["label"], $b["label"]);'));
+ }
+
+ if (!empty($operation_id)) {
+ return $operations[$operation_id];
+ }
+ else {
+ return $operations;
+ }
+}
+
+/**
+ * Returns an operation instance.
+ *
+ * @param $operation_id
+ * The id of the operation to instantiate.
+ * For example: action::node_publish_action.
+ * @param $entity_type
+ * The entity type on which the operation operates.
+ * @param $options
+ * Options for this operation (label, operation settings, etc.)
+ */
+function views_bulk_operations_get_operation($operation_id, $entity_type, $options) {
+ $operations = &drupal_static(__FUNCTION__);
+
+ if (!isset($operations[$operation_id])) {
+ // Intentionally not using views_bulk_operations_get_operation_info() here
+ // since it's an expensive function that loads all the operations on the
+ // system, despite the fact that we might only need a few.
+ $id_fragments = explode('::', $operation_id);
+ $plugin = views_bulk_operations_get_operation_type($id_fragments[0]);
+ $operation_info = $plugin['list callback']($operation_id);
+
+ if ($operation_info) {
+ $operations[$operation_id] = new $plugin['handler']['class']($operation_id, $entity_type, $operation_info, $options);
+ }
+ else {
+ $operations[$operation_id] = FALSE;
+ }
+ }
+
+ return $operations[$operation_id];
+}
+
+/**
+ * Get all operations that match the current entity type.
+ *
+ * @param $entity_type
+ * Entity type.
+ * @param $options
+ * An array of options for all operations, in the form of
+ * $operation_id => $operation_options.
+ */
+function views_bulk_operations_get_applicable_operations($entity_type, $options) {
+ $operations = array();
+ foreach (views_bulk_operations_get_operation_info() as $operation_id => $operation_info) {
+ if ($operation_info['type'] == $entity_type || $operation_info['type'] == 'entity' || $operation_info['type'] == 'system') {
+ $options[$operation_id] = !empty($options[$operation_id]) ? $options[$operation_id] : array();
+ $operations[$operation_id] = views_bulk_operations_get_operation($operation_id, $entity_type, $options[$operation_id]);
+ }
+ }
+
+ return $operations;
+}
+
+/**
+ * Gets the VBO field if it exists on the passed-in view.
+ *
+ * @return
+ * The field object if found. Otherwise, FALSE.
+ */
+function _views_bulk_operations_get_field($view) {
+ foreach ($view->field as $field_name => $field) {
+ if ($field instanceof views_bulk_operations_handler_field_operations) {
+ // Add in the view object for convenience.
+ $field->view = $view;
+ return $field;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Implements hook_views_form_substitutions().
+ */
+function views_bulk_operations_views_form_substitutions() {
+ // Views check_plains the column label, so VBO needs to do the same
+ // in order for the replace operation to succeed.
+ $select_all_placeholder = check_plain('<!--views-bulk-operations-select-all-->');
+ $select_all = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ '#attributes' => array('class' => array('vbo-table-select-all')),
+ );
+
+ return array(
+ $select_all_placeholder => drupal_render($select_all),
+ );
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function views_bulk_operations_form_alter(&$form, &$form_state, $form_id) {
+ if (strpos($form_id, 'views_form_') === 0) {
+ $vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
+ }
+ // Not a VBO-enabled views form.
+ if (empty($vbo)) {
+ return;
+ }
+
+ // Add basic VBO functionality.
+ if ($form_state['step'] == 'views_form_views_form') {
+ // The submit button added by Views Form API might be used by a non-VBO Views
+ // Form handler. If there's no such handler on the view, hide the button.
+ $has_other_views_form_handlers = FALSE;
+ foreach ($vbo->view->field as $field) {
+ if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
+ if (!($field instanceof views_bulk_operations_handler_field_operations)) {
+ $has_other_views_form_handlers = TRUE;
+ }
+ }
+ }
+ if (!$has_other_views_form_handlers) {
+ $form['actions']['#access'] = FALSE;
+ }
+ // The VBO field is excluded from display, stop here.
+ if (!empty($vbo->options['exclude'])) {
+ return;
+ }
+
+ $form = views_bulk_operations_form($form, $form_state, $vbo);
+ }
+
+ // Cache the built form to prevent it from being rebuilt prior to validation
+ // and submission, which could lead to data being processed incorrectly,
+ // because the views rows (and thus, the form elements as well) have changed
+ // in the meantime. Matching views issue: http://drupal.org/node/1473276.
+ $form_state['cache'] = TRUE;
+
+ if (empty($vbo->view->override_url)) {
+ // If the VBO view is embedded using views_embed_view(), or in a block,
+ // $view->get_url() doesn't point to the current page, which means that
+ // the form doesn't get processed.
+ if (!empty($vbo->view->preview) || $vbo->view->display_handler instanceof views_plugin_display_block) {
+ $vbo->view->override_url = $_GET['q'];
+ // We are changing the override_url too late, the form action was already
+ // set by Views to the previous URL, so it needs to be overriden as well.
+ $query = drupal_get_query_parameters($_GET, array('q'));
+ $form['#action'] = url($_GET['q'], array('query' => $query));
+ }
+ }
+
+ // Give other modules a chance to alter the form.
+ drupal_alter('views_bulk_operations_form', $form, $form_state, $vbo);
+}
+
+/**
+ * Implements hook_views_post_build().
+ *
+ * Hides the VBO field if no operations are available.
+ * This causes the entire VBO form to be hidden.
+ *
+ * @see views_bulk_operations_form_alter().
+ */
+function views_bulk_operations_views_post_build(&$view) {
+ $vbo = _views_bulk_operations_get_field($view);
+ if ($vbo && count($vbo->get_selected_operations()) < 1) {
+ $vbo->options['exclude'] = TRUE;
+ }
+}
+
+/**
+ * Returns the 'select all' div that gets inserted below the table header row
+ * (for table style plugins with grouping disabled), or above the view results
+ * (for non-table style plugins), providing a choice between selecting items
+ * on the current page, and on all pages.
+ *
+ * The actual insertion is done by JS, matching the degradation behavior
+ * of Drupal core (no JS - no select all).
+ */
+function theme_views_bulk_operations_select_all($variables) {
+ $view = $variables['view'];
+ $enable_select_all_pages = $variables['enable_select_all_pages'];
+ $form = array();
+
+ if ($view->style_plugin instanceof views_plugin_style_table && empty($view->style_plugin->options['grouping'])) {
+ if (!$enable_select_all_pages) {
+ return '';
+ }
+
+ $wrapper_class = 'vbo-table-select-all-markup';
+ $this_page_count = format_plural(count($view->result), '1 row', '@count rows');
+ $this_page = t('Selected <strong>!row_count</strong> in this page.', array('!row_count' => $this_page_count));
+ $all_pages_count = format_plural($view->total_rows, '1 row', '@count rows');
+ $all_pages = t('Selected <strong>!row_count</strong> in this view.', array('!row_count' => $all_pages_count));
+
+ $form['select_all_pages'] = array(
+ '#type' => 'button',
+ '#attributes' => array('class' => array('vbo-table-select-all-pages')),
+ '#value' => t('Select all !row_count in this view.', array('!row_count' => $all_pages_count)),
+ '#prefix' => '<span class="vbo-table-this-page">' . $this_page . ' &nbsp;',
+ '#suffix' => '</span>',
+ );
+ $form['select_this_page'] = array(
+ '#type' => 'button',
+ '#attributes' => array('class' => array('vbo-table-select-this-page')),
+ '#value' => t('Select only !row_count in this page.', array('!row_count' => $this_page_count)),
+ '#prefix' => '<span class="vbo-table-all-pages" style="display: none">' . $all_pages . ' &nbsp;',
+ '#suffix' => '</span>',
+ );
+ }
+ else {
+ $wrapper_class = 'vbo-select-all-markup';
+
+ $form['select_all'] = array(
+ '#type' => 'fieldset',
+ '#attributes' => array('class' => array('vbo-fieldset-select-all')),
+ );
+ $form['select_all']['this_page'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Select all items on this page'),
+ '#default_value' => '',
+ '#attributes' => array('class' => array('vbo-select-this-page')),
+ );
+
+ if ($enable_select_all_pages) {
+ $form['select_all']['or'] = array(
+ '#type' => 'markup',
+ '#markup' => '<em>' . t('OR') . '</em>',
+ );
+ $form['select_all']['all_pages'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Select all items on all pages'),
+ '#default_value' => '',
+ '#attributes' => array('class' => array('vbo-select-all-pages')),
+ );
+ }
+ }
+
+ $output = '<div class="' . $wrapper_class . '">';
+ $output .= drupal_render($form);
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Extend the views_form multistep form with elements for executing an operation.
+ */
+function views_bulk_operations_form($form, &$form_state, $vbo) {
+ $form['#attached']['js'][] = drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.js';
+ $form['#attached']['js'][] = array(
+ 'data' => array('vbo' => array(
+ 'row_clickable' => $vbo->get_vbo_option('row_clickable'),
+ )),
+ 'type' => 'setting',
+ );
+
+ $form['#attached']['css'][] = drupal_get_path('module', 'views_bulk_operations') . '/css/views_bulk_operations.css';
+ // Wrap the form in a div with specific classes for JS targeting and theming.
+ $class = 'vbo-views-form';
+ if (empty($vbo->view->result)) {
+ $class .= ' vbo-views-form-empty';
+ }
+ $form['#prefix'] = '<div class="' . $class . '">';
+ $form['#suffix'] = '</div>';
+
+ // Force browser to reload the page if Back is hit.
+ if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('/msie/i', $_SERVER['HTTP_USER_AGENT'])) {
+ drupal_add_http_header('Cache-Control', 'no-cache'); // works for IE6+
+ }
+ else {
+ drupal_add_http_header('Cache-Control', 'no-store'); // works for Firefox and other browsers
+ }
+
+ // Set by JS to indicate that all rows on all pages are selected.
+ $form['select_all'] = array(
+ '#type' => 'hidden',
+ '#attributes' => array('class' => 'select-all-rows'),
+ '#default_value' => FALSE,
+ );
+ $form['select'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Operations'),
+ '#collapsible' => FALSE,
+ '#attributes' => array('class' => array('container-inline')),
+ );
+ if ($vbo->get_vbo_option('display_type') == 0) {
+ $options = array(0 => t('- Choose an operation -'));
+ foreach ($vbo->get_selected_operations() as $operation_id => $operation) {
+ $options[$operation_id] = $operation->label();
+ }
+
+ // Create dropdown and submit button.
+ $form['select']['operation'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ );
+ $form['select']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Execute'),
+ '#validate' => array('views_bulk_operations_form_validate'),
+ '#submit' => array('views_bulk_operations_form_submit'),
+ );
+ }
+ else {
+ // Create buttons for operations.
+ foreach ($vbo->get_selected_operations() as $operation_id => $operation) {
+ $form['select'][$operation_id] = array(
+ '#type' => 'submit',
+ '#value' => $operation->label(),
+ '#validate' => array('views_bulk_operations_form_validate'),
+ '#submit' => array('views_bulk_operations_form_submit'),
+ '#operation_id' => $operation_id,
+ );
+ }
+ }
+
+ // Adds the "select all" functionality if the view has results.
+ // If the view is using a table style plugin, the markup gets moved to
+ // a table row below the header.
+ // If we are using radio buttons, we don't use select all at all.
+ if (!empty($vbo->view->result) && !$vbo->get_vbo_option('force_single')) {
+ $enable_select_all_pages = FALSE;
+ // If the view is paginated, and "select all items on all pages" is
+ // enabled, tell that to the theme function.
+ if (count($vbo->view->result) != $vbo->view->total_rows && $vbo->get_vbo_option('enable_select_all_pages')) {
+ $enable_select_all_pages = TRUE;
+ }
+ $form['select_all_markup'] = array(
+ '#type' => 'markup',
+ '#markup' => theme('views_bulk_operations_select_all', array('view' => $vbo->view, 'enable_select_all_pages' => $enable_select_all_pages)),
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Validation callback for the first step of the VBO form.
+ */
+function views_bulk_operations_form_validate($form, &$form_state) {
+ $vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
+
+ if (!empty($form_state['triggering_element']['#operation_id'])) {
+ $form_state['values']['operation'] = $form_state['triggering_element']['#operation_id'];
+ }
+ if (!$form_state['values']['operation']) {
+ form_set_error('operation', t('No operation selected. Please select an operation to perform.'));
+ }
+
+ $field_name = $vbo->options['id'];
+ $selection = _views_bulk_operations_get_selection($vbo, $form_state);
+ if (!$selection) {
+ form_set_error($field_name, t('Please select at least one item.'));
+ }
+}
+
+/**
+ * Multistep form callback for the "configure" step.
+ */
+function views_bulk_operations_config_form($form, &$form_state, $view, $output) {
+ $vbo = _views_bulk_operations_get_field($view);
+ $operation = $form_state['operation'];
+ drupal_set_title(t('Set parameters for %operation', array('%operation' => $operation->label())), PASS_THROUGH);
+
+ $context = array(
+ 'entity_type' => $vbo->get_entity_type(),
+ // Pass the View along.
+ // Has no performance penalty since objects are passed by reference,
+ // but needing the full views object in a core action is in most cases
+ // a sign of a wrong implementation. Do it only if you have to.
+ 'view' => $view,
+ );
+ $form += $operation->form($form, $form_state, $context);
+
+ $query = drupal_get_query_parameters($_GET, array('q'));
+ $form['actions'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('form-actions')),
+ '#weight' => 999,
+ );
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Next'),
+ '#validate' => array('views_bulk_operations_config_form_validate'),
+ '#submit' => array('views_bulk_operations_form_submit'),
+ '#suffix' => l(t('Cancel'), $vbo->view->get_url(), array('query' => $query)),
+ );
+
+ return $form;
+}
+
+/**
+ * Validation callback for the "configure" step.
+ * Gives the operation a chance to validate its config form.
+ */
+function views_bulk_operations_config_form_validate($form, &$form_state) {
+ $operation = &$form_state['operation'];
+ $operation->formValidate($form, $form_state);
+}
+
+/**
+ * Multistep form callback for the "confirm" step.
+ */
+function views_bulk_operations_confirm_form($form, &$form_state, $view, $output) {
+ $vbo = _views_bulk_operations_get_field($view);
+ $operation = $form_state['operation'];
+ $rows = $form_state['selection'];
+ $query = drupal_get_query_parameters($_GET, array('q'));
+ $title = t('Are you sure you want to perform %operation on the selected items?', array('%operation' => $operation->label()));
+ $form = confirm_form($form,
+ $title,
+ array('path' => $view->get_url(), 'query' => $query),
+ theme('views_bulk_operations_confirmation', array('rows' => $rows, 'vbo' => $vbo, 'operation' => $operation, 'select_all_pages' => $form_state['select_all_pages']))
+ );
+ // Add VBO's submit handler to the Confirm button added by config_form().
+ $form['actions']['submit']['#submit'] = array('views_bulk_operations_form_submit');
+
+ // We can't set the View title here as $view is just a copy of the original,
+ // and our settings changes won't "stick" for the first page load of the
+ // confirmation form. We also can't just call drupal_set_title() directly
+ // because our title will be clobbered by the actual View title later. So
+ // let's tuck the title away in the form for use later.
+ // @see views_bulk_operations_preprocess_views_view()
+ $form['#vbo_confirm_form_title'] = $title;
+
+ return $form;
+}
+
+/**
+ * Theme function to show the confirmation page before executing the operation.
+ */
+function theme_views_bulk_operations_confirmation($variables) {
+ $select_all_pages = $variables['select_all_pages'];
+ $vbo = $variables['vbo'];
+ $entity_type = $vbo->get_entity_type();
+ $rows = $variables['rows'];
+ $items = array();
+ // Load the entities from the current page, and show their titles.
+ $entities = _views_bulk_operations_entity_load($entity_type, array_values($rows), $vbo->revision);
+ foreach ($entities as $entity) {
+ $items[] = check_plain(entity_label($entity_type, $entity));
+ }
+ // All rows on all pages have been selected, so show a count of additional items.
+ if ($select_all_pages) {
+ $more_count = $vbo->view->total_rows - count($vbo->view->result);
+ $items[] = t('...and <strong>!count</strong> more.', array('!count' => $more_count));
+ }
+
+ $count = format_plural(count($entities), 'item', '@count items');
+ $output = theme('item_list', array('items' => $items, 'title' => t('You selected the following <strong>!count</strong>:', array('!count' => $count))));
+ return $output;
+}
+
+/**
+ * Implements hook_preprocess_page().
+ *
+ * Hide action links on the configure and confirm pages.
+ */
+function views_bulk_operations_preprocess_page(&$variables) {
+ if (isset($_POST['select_all'], $_POST['operation'])) {
+ $variables['action_links'] = array();
+ }
+}
+
+/**
+ * Implements hook_preprocess_views_view().
+ */
+function views_bulk_operations_preprocess_views_view($variables) {
+ // If we've stored a title for the confirmation form, retrieve it here and
+ // retitle the View.
+ // @see views_bulk_operations_confirm_form()
+ if (array_key_exists('rows', $variables) && is_array($variables['rows']) && array_key_exists('#vbo_confirm_form_title', $variables['rows'])) {
+ $variables['view']->set_title($variables['rows']['#vbo_confirm_form_title']);
+ }
+}
+
+/**
+ * Goes through the submitted values, and returns
+ * an array of selected rows, in the form of
+ * $row_index => $entity_id.
+ */
+function _views_bulk_operations_get_selection($vbo, $form_state) {
+ $selection = array();
+ $field_name = $vbo->options['id'];
+
+ if (!empty($form_state['values'][$field_name])) {
+ // If using "force single", the selection needs to be converted to an array.
+ if (is_array($form_state['values'][$field_name])) {
+ $selection = array_filter($form_state['values'][$field_name]);
+ }
+ else {
+ $selection = array($form_state['values'][$field_name]);
+ }
+ }
+
+ return $selection;
+}
+
+/**
+ * Submit handler for all steps of the VBO multistep form.
+ */
+function views_bulk_operations_form_submit($form, &$form_state) {
+ $vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
+ $entity_type = $vbo->get_entity_type();
+
+ switch ($form_state['step']) {
+ case 'views_form_views_form':
+ $form_state['selection'] = _views_bulk_operations_get_selection($vbo, $form_state);
+ $form_state['select_all_pages'] = $form_state['values']['select_all'];
+
+ $options = $vbo->get_operation_options($form_state['values']['operation']);
+ $form_state['operation'] = $operation = views_bulk_operations_get_operation($form_state['values']['operation'], $entity_type, $options);
+ if (!$operation->configurable() && $operation->getAdminOption('skip_confirmation')) {
+ break; // Go directly to execution
+ }
+ $form_state['step'] = $operation->configurable() ? 'views_bulk_operations_config_form' : 'views_bulk_operations_confirm_form';
+ $form_state['rebuild'] = TRUE;
+ return;
+
+ case 'views_bulk_operations_config_form':
+ $form_state['step'] = 'views_bulk_operations_confirm_form';
+ $operation = &$form_state['operation'];
+ $operation->formSubmit($form, $form_state);
+
+ if ($operation->getAdminOption('skip_confirmation')) {
+ break; // Go directly to execution
+ }
+ $form_state['rebuild'] = TRUE;
+ return;
+
+ case 'views_bulk_operations_confirm_form':
+ break;
+ }
+
+ // Execute the operation.
+ views_bulk_operations_execute($vbo, $form_state['operation'], $form_state['selection'], $form_state['select_all_pages']);
+
+ // Redirect.
+ $query = drupal_get_query_parameters($_GET, array('q'));
+ $form_state['redirect'] = array('path' => $vbo->view->get_url(), array('query' => $query));
+}
+
+/**
+ * Entry point for executing the chosen operation upon selected rows.
+ *
+ * If the selected operation is an aggregate operation (requiring all selected
+ * items to be passed at the same time), restricted to a single value, or has
+ * the skip_batching option set, the operation is executed directly.
+ * This means that there is no batching & queueing, the PHP execution
+ * time limit is ignored (if allowed), all selected entities are loaded and
+ * processed.
+ *
+ * Otherwise, the selected entity ids are divided into groups not larger than
+ * $entity_load_capacity, and enqueued for processing.
+ * If all items on all pages should be processed, a batch job runs that
+ * collects and enqueues the items from all pages of the view, page by page.
+ *
+ * Based on the "Enqueue the operation instead of executing it directly"
+ * VBO field setting, the newly filled queue is either processed at cron
+ * time by the VBO worker function, or right away in a new batch job.
+ *
+ * @param $vbo
+ * The VBO field, containing a reference to the view in $vbo->view.
+ * @param $operation
+ * The operation object.
+ * @param $selection
+ * An array in the form of $row_index => $entity_id.
+ * @param $select_all_pages
+ * Whether all items on all pages should be selected.
+ */
+function views_bulk_operations_execute($vbo, $operation, $selection, $select_all_pages = FALSE) {
+ global $user;
+
+ // Determine if the operation needs to be executed directly.
+ $aggregate = $operation->aggregate();
+ $skip_batching = $vbo->get_vbo_option('skip_batching');
+ $force_single = $vbo->get_vbo_option('force_single');
+ $execute_directly = ($aggregate || $skip_batching || $force_single);
+ // Try to load all rows without a batch if needed.
+ if ($execute_directly && $select_all_pages) {
+ views_bulk_operations_direct_adjust($selection, $vbo);
+ }
+
+ // Options that affect execution.
+ $options = array(
+ 'revision' => $vbo->revision,
+ 'entity_load_capacity' => $vbo->get_vbo_option('entity_load_capacity', 10),
+ // The information needed to recreate the view, to avoid serializing the
+ // whole object. Passed to the executed operation. Also used by
+ // views_bulk_operations_adjust_selection().
+ 'view_info' => array(
+ 'name' => $vbo->view->name,
+ 'display' => $vbo->view->current_display,
+ 'arguments' => $vbo->view->args,
+ 'exposed_input' => $vbo->view->get_exposed_input(),
+ ),
+ );
+ // Create an array of rows in the needed format.
+ $rows = array();
+ $current = 1;
+ foreach ($selection as $row_index => $entity_id) {
+ $rows[$row_index] = array(
+ 'entity_id' => $entity_id,
+ 'views_row' => array(),
+ // Some operations rely on knowing the position of the current item
+ // in the execution set (because of specific things that need to be done
+ // at the beginning or the end of the set).
+ 'position' => array(
+ 'current' => $current++,
+ 'total' => count($selection),
+ ),
+ );
+ // Some operations require full selected rows.
+ if ($operation->needsRows()) {
+ $rows[$row_index]['views_row'] = $vbo->view->result[$row_index];
+ }
+ }
+
+ if ($execute_directly) {
+ // Execute the operation directly and stop here.
+ views_bulk_operations_direct_process($operation, $rows, $options);
+ return;
+ }
+
+ // Determine the correct queue to use.
+ if ($operation->getAdminOption('postpone_processing')) {
+ // Use the site queue processed on cron.
+ $queue_name = 'views_bulk_operations';
+ }
+ else {
+ // Use the active queue processed immediately by Batch API.
+ $queue_name = 'views_bulk_operations_active_queue_' . db_next_id();
+ }
+
+ $batch = array(
+ 'operations' => array(),
+ 'finished' => 'views_bulk_operations_execute_finished',
+ 'progress_message' => '',
+ 'title' => t('Performing %operation on the selected items...', array('%operation' => $operation->label())),
+ );
+
+ // All items on all pages should be selected, add a batch job to gather
+ // and enqueue them.
+ if ($select_all_pages && $vbo->view->query->pager->has_more_records()) {
+ $total_rows = $vbo->view->total_rows;
+
+ $batch['operations'][] = array(
+ 'views_bulk_operations_adjust_selection', array($queue_name, $operation, $options),
+ );
+ }
+ else {
+ $total_rows = count($rows);
+
+ // We have all the items that we need, enqueue them right away.
+ views_bulk_operations_enqueue_rows($queue_name, $rows, $operation, $options);
+
+ // Provide a status message to the user, since this is the last step if
+ // processing is postponed.
+ if ($operation->getAdminOption('postpone_processing')) {
+ drupal_set_message(t('Enqueued the selected operation (%operation).', array(
+ '%operation' => $operation->label(),
+ )));
+ }
+ }
+
+ // Processing is not postponed, add a batch job to process the queue.
+ if (!$operation->getAdminOption('postpone_processing')) {
+ $batch['operations'][] = array(
+ 'views_bulk_operations_active_queue_process', array($queue_name, $operation, $total_rows),
+ );
+ }
+
+ // If there are batch jobs to be processed, create the batch set.
+ if (count($batch['operations'])) {
+ batch_set($batch);
+ }
+}
+
+/**
+ * Batch API callback: loads the view page by page and enqueues all items.
+ *
+ * @param $queue_name
+ * The name of the queue to which the items should be added.
+ * @param $operation
+ * The operation object.
+ * @param $options
+ * An array of options that affect execution (revision, entity_load_capacity,
+ * view_info). Passed along with each new queue item.
+ */
+function views_bulk_operations_adjust_selection($queue_name, $operation, $options, &$context) {
+ if (!isset($context['sandbox']['progress'])) {
+ $context['sandbox']['progress'] = 0;
+ $context['sandbox']['max'] = 0;
+ }
+
+ $view_info = $options['view_info'];
+ $view = views_get_view($view_info['name']);
+ $view->set_exposed_input($view_info['exposed_input']);
+ $view->set_arguments($view_info['arguments']);
+ $view->set_display($view_info['display']);
+ $view->set_offset($context['sandbox']['progress']);
+ $view->build();
+ $view->execute($view_info['display']);
+ // Note the total number of rows.
+ if (empty($context['sandbox']['max'])) {
+ $context['sandbox']['max'] = $view->total_rows;
+ }
+
+ $vbo = _views_bulk_operations_get_field($view);
+ $rows = array();
+ foreach ($view->result as $row_index => $result) {
+ $rows[$row_index] = array(
+ 'entity_id' => $vbo->get_value($result),
+ 'views_row' => array(),
+ 'position' => array(
+ 'current' => ++$context['sandbox']['progress'],
+ 'total' => $context['sandbox']['max'],
+ ),
+ );
+ // Some operations require full selected rows.
+ if ($operation->needsRows()) {
+ $rows[$row_index]['views_row'] = $result;
+ }
+ }
+
+ // Enqueue the gathered rows.
+ views_bulk_operations_enqueue_rows($queue_name, $rows, $operation, $options);
+
+ if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ // Provide an estimation of the completion level we've reached.
+ $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ $context['message'] = t('Prepared @current out of @total', array('@current' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']));
+ }
+ else {
+ // Provide a status message to the user if this is the last batch job.
+ if ($operation->getAdminOption('postpone_processing')) {
+ $context['results']['log'][] = t('Enqueued the selected operation (%operation).', array(
+ '%operation' => $operation->label(),
+ ));
+ }
+ }
+}
+
+/**
+ * Divides the passed rows into groups and enqueues each group for processing
+ *
+ * @param $queue_name
+ * The name of the queue.
+ * @param $rows
+ * The rows to be enqueued.
+ * @param $operation
+ * The object representing the current operation.
+ * Passed along with each new queue item.
+ * @param $options
+ * An array of options that affect execution (revision, entity_load_capacity).
+ * Passed along with each new queue item.
+ */
+function views_bulk_operations_enqueue_rows($queue_name, $rows, $operation, $options) {
+ global $user;
+
+ $queue = DrupalQueue::get($queue_name, TRUE);
+ $row_groups = array_chunk($rows, $options['entity_load_capacity'], TRUE);
+
+ foreach ($row_groups as $row_group) {
+ $entity_ids = array();
+ foreach ($row_group as $row) {
+ $entity_ids[] = $row['entity_id'];
+ }
+
+ $job = array(
+ 'title' => t('Perform %operation on @type !entity_ids.', array(
+ '%operation' => $operation->label(),
+ '@type' => $operation->entityType,
+ '!entity_ids' => implode(',', $entity_ids),
+ )),
+ 'uid' => $user->uid,
+ 'arguments' => array($row_group, $operation, $options),
+ );
+ $queue->createItem($job);
+ }
+}
+
+/**
+ * Batch API callback: processes the active queue.
+ *
+ * @param $queue_name
+ * The name of the queue to process.
+ * @param $operation
+ * The object representing the current operation.
+ * @param $total_rows
+ * The total number of processable items (across all queue items), used
+ * to report progress.
+ *
+ * @see views_bulk_operations_queue_item_process()
+ */
+function views_bulk_operations_active_queue_process($queue_name, $operation, $total_rows, &$context) {
+ static $queue;
+
+ // It is still possible to hit the time limit.
+ drupal_set_time_limit(0);
+
+ // Prepare the sandbox.
+ if (!isset($context['sandbox']['progress'])) {
+ $context['sandbox']['progress'] = 0;
+ $context['sandbox']['max'] = $total_rows;
+ $context['results']['log'] = array();
+ }
+ // Instantiate the queue.
+ if (!isset($queue)) {
+ $queue = DrupalQueue::get($queue_name, TRUE);
+ }
+
+ // Process the queue as long as it has items for us.
+ $queue_item = $queue->claimItem(3600);
+ if ($queue_item) {
+ // Process the queue item, and update the progress count.
+ views_bulk_operations_queue_item_process($queue_item->data, $context['results']['log']);
+ $queue->deleteItem($queue_item);
+
+ // Provide an estimation of the completion level we've reached.
+ $context['sandbox']['progress'] += count($queue_item->data['arguments'][0]);
+ $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ $context['message'] = t('Processed @current out of @total', array('@current' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']));
+ }
+
+ if (!$queue_item || $context['finished'] === 1) {
+ // All done. Provide a status message to the user.
+ $context['results']['log'][] = t('Performed %operation on @items.', array(
+ '%operation' => $operation->label(),
+ '@items' => format_plural($context['sandbox']['progress'], '1 item', '@count items'),
+ ));
+ }
+}
+
+/**
+ * Processes the provided queue item.
+ *
+ * Used as a worker callback defined by views_bulk_operations_cron_queue_info()
+ * to process the site queue, as well as by
+ * views_bulk_operations_active_queue_process() to process the active queue.
+ *
+ * @param $queue_item_arguments
+ * The arguments of the queue item to process.
+ * @param $log
+ * An injected array of log messages, to be modified by reference.
+ * If NULL, the function defaults to using watchdog.
+ */
+function views_bulk_operations_queue_item_process($queue_item_data, &$log = NULL) {
+ list($row_group, $operation, $options) = $queue_item_data['arguments'];
+ $account = user_load($queue_item_data['uid']);
+ $entity_type = $operation->entityType;
+ $entity_ids = array();
+ foreach ($row_group as $row_index => $row) {
+ $entity_ids[] = $row['entity_id'];
+ }
+
+ $entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $options['revision']);
+ foreach ($row_group as $row_index => $row) {
+ $entity_id = $row['entity_id'];
+ // A matching entity couldn't be loaded. Skip this item.
+ if (!isset($entities[$entity_id])) {
+ continue;
+ }
+
+ if ($options['revision']) {
+ // Don't reload revisions for now, they are not statically cached and
+ // usually don't run into the edge case described below.
+ $entity = $entities[$entity_id];
+ }
+ else {
+ // A previous action might have resulted in the entity being resaved
+ // (e.g. node synchronization from a prior node in this batch), so try
+ // to reload it. If no change occurred, the entity will be retrieved
+ // from the static cache, resulting in no performance penalty.
+ $entity = entity_load_single($entity_type, $entity_id);
+ if (empty($entity)) {
+ // The entity is no longer valid.
+ continue;
+ }
+ }
+
+ // If the current entity can't be accessed, skip it and log a notice.
+ if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity, $account)) {
+ $message = 'Skipped %operation on @type %title due to insufficient permissions.';
+ $arguments = array(
+ '%operation' => $operation->label(),
+ '@type' => $entity_type,
+ '%title' => entity_label($entity_type, $entity),
+ );
+
+ if ($log) {
+ $log[] = t($message, $arguments);
+ }
+ else {
+ watchdog('views bulk operations', $message, $arguments, WATCHDOG_ALERT);
+ }
+
+ continue;
+ }
+
+ $operation_context = array(
+ 'progress' => $row['position'],
+ 'view_info' => $options['view_info'],
+ );
+ if ($operation->needsRows()) {
+ $operation_context['rows'] = array($row_index => $row['views_row']);
+ }
+ $operation->execute($entity, $operation_context);
+
+ unset($row_group[$row_index]);
+ }
+}
+
+/**
+ * Adjusts the selection for the direct execution method.
+ *
+ * Just like the direct method itself, this is legacy code, used only for
+ * aggregate actions.
+ */
+function views_bulk_operations_direct_adjust(&$selection, $vbo) {
+ // Adjust selection to select all rows across pages.
+ $view = views_get_view($vbo->view->name);
+ $view->set_exposed_input($vbo->view->get_exposed_input());
+ $view->set_arguments($vbo->view->args);
+ $view->set_display($vbo->view->current_display);
+ $view->display_handler->set_option('pager', array('type' => 'none', 'options' => array()));
+ $view->build();
+ // Unset every field except the VBO one (which holds the entity id).
+ // That way the performance hit becomes much smaller, because there is no
+ // chance of views_handler_field_field::post_execute() firing entity_load().
+ foreach ($view->field as $field_name => $field) {
+ if ($field_name != $vbo->options['id']) {
+ unset($view->field[$field_name]);
+ }
+ }
+
+ $view->execute($vbo->view->current_display);
+ $results = array();
+ foreach ($view->result as $row_index => $result) {
+ $results[$row_index] = $vbo->get_value($result);
+ }
+ $selection = $results;
+}
+
+/**
+ * Processes the passed rows directly (without batching and queueing).
+ */
+function views_bulk_operations_direct_process($operation, $rows, $options) {
+ global $user;
+
+ drupal_set_time_limit(0);
+
+ // Prepare an array of status information. Imitates the Batch API naming
+ // for consistency. Passed to views_bulk_operations_execute_finished().
+ $context = array();
+ $context['results']['progress'] = 0;
+ $context['results']['log'] = array();
+
+ if ($operation->aggregate()) {
+ // Load all entities.
+ $entity_type = $operation->entityType;
+ $entity_ids = array();
+ foreach ($rows as $row_index => $row) {
+ $entity_ids[] = $row['entity_id'];
+ }
+ $entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $options['revision']);
+
+ // Filter out entities that can't be accessed.
+ foreach ($entities as $id => $entity) {
+ if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {
+ $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
+ '%operation' => $operation->label(),
+ '@type' => $entity_type,
+ '%title' => entity_label($entity_type, $entity),
+ ));
+ unset($entities[$id]);
+ }
+ }
+
+ // If there are any entities left, execute the operation on them.
+ if ($entities) {
+ $operation_context = array(
+ 'view_info' => $options['view_info'],
+ );
+ // Pass the selected rows to the operation if needed.
+ if ($operation->needsRows()) {
+ $operation_context['rows'] = array();
+ foreach ($rows as $row_index => $row) {
+ $operation_context['rows'][$row_index] = $row['views_row'];
+ }
+ }
+ $operation->execute($entities, $operation_context);
+ }
+ }
+ else {
+ // Imitate a queue and process the entities one by one.
+ $queue_item_data = array(
+ 'uid' => $user->uid,
+ 'arguments' => array($rows, $operation, $options),
+ );
+ views_bulk_operations_queue_item_process($queue_item_data, $context['results']['log']);
+ }
+
+ $context['results']['progress'] += count($rows);
+ $context['results']['log'][] = t('Performed %operation on @items.', array(
+ '%operation' => $operation->label(),
+ '@items' => format_plural(count($rows), '1 item', '@count items'),
+ ));
+
+ views_bulk_operations_execute_finished(TRUE, $context['results'], array());
+}
+
+/**
+ * Helper function that runs after the execution process is complete.
+ */
+function views_bulk_operations_execute_finished($success, $results, $operations) {
+ if ($success) {
+ if (count($results['log']) > 1) {
+ $message = theme('item_list', array('items' => $results['log']));
+ }
+ else {
+ $message = reset($results['log']);
+ }
+ }
+ else {
+ // An error occurred.
+ // $operations contains the operations that remained unprocessed.
+ $error_operation = reset($operations);
+ $message = t('An error occurred while processing @operation with arguments: @arguments',
+ array('@operation' => $error_operation[0], '@arguments' => print_r($error_operation[0], TRUE)));
+ }
+
+ _views_bulk_operations_log($message);
+}
+
+/**
+ * Helper function to verify access permission to operate on an entity.
+ */
+function _views_bulk_operations_entity_access($operation, $entity_type, $entity, $account = NULL) {
+ if (!entity_type_supports($entity_type, 'access')) {
+ return TRUE;
+ }
+
+ $access_ops = array(
+ VBO_ACCESS_OP_VIEW => 'view',
+ VBO_ACCESS_OP_UPDATE => 'update',
+ VBO_ACCESS_OP_CREATE => 'create',
+ VBO_ACCESS_OP_DELETE => 'delete',
+ );
+ foreach ($access_ops as $bit => $op) {
+ if ($operation->getAccessMask() & $bit) {
+ if (!entity_access($op, $entity_type, $entity, $account)) {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Loads multiple entities by their entity or revision ids, and returns them,
+ * keyed by the id used for loading.
+ */
+function _views_bulk_operations_entity_load($entity_type, $ids, $revision = FALSE) {
+ if (!$revision) {
+ $entities = entity_load($entity_type, $ids);
+ }
+ else {
+ // D7 can't load multiple entities by revision_id. Lovely.
+ $info = entity_get_info($entity_type);
+ $entities = array();
+ foreach ($ids as $revision_id) {
+ $loaded_entities = entity_load($entity_type, array(), array($info['entity keys']['revision'] => $revision_id));
+ $entities[$revision_id] = reset($loaded_entities);
+ }
+ }
+
+ return $entities;
+}
+
+/**
+ * Helper function to report an error.
+ */
+function _views_bulk_operations_report_error($msg, $arg) {
+ watchdog('views bulk operations', $msg, $arg, WATCHDOG_ERROR);
+ if (function_exists('drush_set_error')) {
+ drush_set_error('VIEWS_BULK_OPERATIONS_EXECUTION_ERROR', strip_tags(dt($msg, $arg)));
+ }
+}
+
+/**
+ * Display a message to the user through the relevant function.
+ */
+function _views_bulk_operations_log($msg) {
+ // Is VBO being run through drush?
+ if (function_exists('drush_log')) {
+ drush_log(strip_tags($msg), 'ok');
+ }
+ else {
+ drupal_set_message($msg);
+ }
+}
diff --git a/sites/all/modules/views_bulk_operations/views_bulk_operations.rules.inc b/sites/all/modules/views_bulk_operations/views_bulk_operations.rules.inc
new file mode 100644
index 000000000..b3a64184d
--- /dev/null
+++ b/sites/all/modules/views_bulk_operations/views_bulk_operations.rules.inc
@@ -0,0 +1,298 @@
+<?php
+
+/**
+ * @file
+ * Views Bulk Operations conditions and actions for Rules.
+ */
+
+/**
+ * Implements hook_rules_condition_info().
+ */
+function views_bulk_operations_rules_condition_info() {
+ $conditions = array();
+ $conditions['views_bulk_operations_condition_result_count'] = array(
+ 'label' => t('Check number of results returned by a VBO View'),
+ 'parameter' => array(
+ 'view' => array(
+ 'type' => 'text',
+ 'label' => t('View and display'),
+ 'options list' => 'views_bulk_operations_views_list',
+ 'description' => t('Select the VBO view and display you want to check'),
+ 'restriction' => 'input',
+ ),
+ 'args' => array(
+ 'type' => 'text',
+ 'label' => t('Arguments'),
+ 'description' => t('Any arguments to pass to the view, one per line.
+ You may use token replacement patterns.'),
+ 'optional' => TRUE,
+ ),
+ 'minimum' => array(
+ 'type' => 'integer',
+ 'label' => t('Minimum number of results'),
+ 'description' => t('This condition returns TRUE if the view has at
+ least the given number of results.'),
+ ),
+ ),
+ 'group' => t('Views Bulk Operations'),
+ );
+
+ return $conditions;
+}
+
+/**
+ * Implements hook_rules_action_info().
+ */
+function views_bulk_operations_rules_action_info() {
+ $actions = array();
+ $actions['views_bulk_operations_action_load_list'] = array(
+ 'label' => t('Load a list of entity objects from a VBO View.'),
+ 'parameter' => array(
+ 'view' => array(
+ 'type' => 'text',
+ 'label' => t('View and display'),
+ 'options list' => 'views_bulk_operations_views_list',
+ 'description' => t('Select the view and display you want to use to
+ create a list.'),
+ 'restriction' => 'input',
+ ),
+ 'args' => array(
+ 'type' => 'text',
+ 'label' => t('Arguments'),
+ 'description' => t('Any arguments to pass to the view, one per line.
+ You may use token replacement patterns.'),
+ 'optional' => TRUE,
+ ),
+ ),
+ 'provides' => array(
+ 'entity_list' => array(
+ 'type' => 'list<entity>',
+ 'label' => t('A list of entities'),
+ ),
+ ),
+ 'group' => t('Views Bulk Operations'),
+ );
+ $actions['views_bulk_operations_action_load_id_list'] = array(
+ 'label' => t('Load a list of entity ids from a VBO View.'),
+ 'parameter' => array(
+ 'view' => array(
+ 'type' => 'text',
+ 'label' => t('View and display'),
+ 'options list' => 'views_bulk_operations_views_list',
+ 'description' => t('Select the view and display you want to use to
+ create a list.'),
+ 'restriction' => 'input',
+ ),
+ 'args' => array(
+ 'type' => 'text',
+ 'label' => t('Arguments'),
+ 'description' => t('Any arguments to pass to the view, one per line.
+ You may use token replacement patterns.'),
+ 'optional' => TRUE,
+ ),
+ ),
+ 'provides' => array(
+ 'entity_id_list' => array(
+ 'type' => 'list<integer>',
+ 'label' => t('A list of entity ids'),
+ ),
+ ),
+ 'group' => t('Views Bulk Operations'),
+ );
+
+ return $actions;
+}
+
+/**
+ * Lists all available VBO Views and their displays.
+ * Naturally, only the displays that contain a VBO field are listed.
+ *
+ * @return array
+ * An array of all views and their displays on the form 'view|display',
+ * formatted to be used as an select list.
+ */
+function views_bulk_operations_views_list() {
+ $selectable_displays = array();
+ foreach (views_get_enabled_views() as $name => $base_view) {
+ foreach ($base_view->display as $display_name => $display) {
+ $view = $base_view->clone_view();
+ $view->build($display_name);
+ $vbo = _views_bulk_operations_get_field($view);
+ if ($vbo) {
+ $selectable_displays[$view->name . '|' . $display_name] = check_plain($view->human_name) . ' | ' . check_plain($display->display_title);
+ }
+ }
+ }
+ return $selectable_displays;
+}
+
+/**
+ * The 'views_bulk_operations_condition_result_count' condition.
+ *
+ * @param $view
+ * A string in the format "$view_name|$display_name".
+ * @param $args
+ * Arguments that should be passed to the View.
+ * @param $minimum
+ * An integer representing the minimum number of results that satisfies the
+ * condition.
+ *
+ * @return
+ * TRUE if the view has more than $minimum results, FALSE otherwise.
+ */
+function views_bulk_operations_condition_result_count($view, $args, $minimum) {
+ $vbo = _views_bulk_operations_rules_get_field($view, $args);
+ return (count($vbo->view->result) >= $minimum);
+}
+
+/**
+ * The 'views_bulk_operations_action_views_load_list' action.
+ *
+ * @param $view
+ * A string in the format "$view_name|$display_name".
+ * @param $args
+ * Arguments that should be passed to the View.
+ * @return array
+ * Array containing the entity_list, an array of entity objects.
+ * - array('entity_list' => array(...))
+ */
+function views_bulk_operations_action_load_list($view, $args) {
+ $vbo = _views_bulk_operations_rules_get_field($view, $args);
+
+ // Get all entities, pass ids to the wrapper for lazy loading.
+ $entity_type = $vbo->get_entity_type();
+ $entities = entity_metadata_wrapper("list<$entity_type>", array());
+ foreach ($vbo->view->result as $row_index => $result) {
+ $entities[] = entity_metadata_wrapper($entity_type, $vbo->get_value($result));
+ }
+
+ return array('entity_list' => $entities);
+}
+
+/**
+ * The 'views_bulk_operations_action_views_load_id_list' action.
+ *
+ * @param $view
+ * A string in the format "$view_name|$display_name".
+ * @param $args
+ * Arguments that should be passed to the View.
+ * @return array
+ * Array containing the entity_id_list, an Array of entity ids as integer
+ * values.
+ * - array('entity_list' => array(...))
+ */
+function views_bulk_operations_action_load_id_list($view, $args) {
+ $vbo = _views_bulk_operations_rules_get_field($view, $args);
+
+ // Get all entity ids.
+ $ids = array();
+ foreach ($vbo->view->result as $row_index => $result) {
+ $ids[] = $vbo->get_value($result);
+ }
+
+ return array('entity_id_list' => $ids);
+}
+
+/**
+ * Info alteration callback for the 'views_bulk_operations_action_views_load_list' action.
+ *
+ * The info hook specifies that the action returns a generic list of entities
+ * (list<entity>). All actions that require entities of specific type can't
+ * use such entities, so this alter hook specifies the exact entity type
+ * after the action has been configured, allowing the view to be loaded
+ * and its entity type extracted.
+ */
+function views_bulk_operations_action_load_list_info_alter(&$element_info, RulesAbstractPlugin $element) {
+ // The action hasn't been configured yet, hence no view. Abort.
+ if (empty($element->settings['view'])) {
+ return;
+ }
+
+ $entity_type = _views_bulk_operations_rules_get_entity_type($element->settings['view']);
+ if ($entity_type) {
+ $element_info['provides']['entity_list']['type'] = "list<$entity_type>";
+ }
+}
+
+/**
+ * Helper function that loads and builds (but doesn't execute) the specified view,
+ * then determines the entity type on which the VBO field operates.
+ *
+ * @param $view_target
+ * A string in the format "$view_name|$display_name".
+ *
+ * @return
+ * The entity type on which the VBO field operates.
+ */
+function _views_bulk_operations_rules_get_entity_type($view_target) {
+ $entity_types = &drupal_static(__FUNCTION__);
+
+ if (!isset($entity_types[$view_target])) {
+ $views_settings = explode('|', $view_target);
+ if ($view = views_get_view($views_settings[0])) {
+ $view->set_display($views_settings[1]);
+ $view->build();
+
+ $vbo = _views_bulk_operations_get_field($view);
+ }
+ $entity_type = !empty($vbo) ? $vbo->get_entity_type() : '';
+ $entity_types[$view_target] = $entity_type;
+ }
+
+ return $entity_types[$view_target];
+}
+
+/**
+ * Helper function that loads, builds and executes the specified view,
+ * then returns its VBO field.
+ *
+ * @param $view_target
+ * A string in the format "$view_name|$display_name".
+ * @param $args
+ * Arguments that should be passed to the View.
+ *
+ * @return
+ * The VBO field. Contains a reference to the View.
+ */
+function _views_bulk_operations_rules_get_field($view_target, $args) {
+ $views = &drupal_static(__FUNCTION__);
+
+ $views_settings = explode('|', $view_target);
+ $view_name = $views_settings[0];
+ $display_name = $views_settings[1];
+ // Create an array of arguments.
+ $view_arguments = explode("\n", $args);
+ $view_arguments = array_map('trim', $view_arguments);
+ $view_arguments = array_filter($view_arguments, 'strlen');
+ // Append the filtered list of arguments to $views_target, so that the correct
+ // View is fetched from cache.
+ if (!empty($view_arguments)) {
+ $view_target .= '|' . implode('&', $view_arguments);
+ }
+
+ // Don't execute the requested View more than once in a single page request.
+ if (isset($views[$view_target])) {
+ $vbo = _views_bulk_operations_get_field($views[$view_target]);
+ return $vbo;
+ }
+
+ // Load the view and set the properties.
+ $view = views_get_view($view_name);
+ $view->set_display($display_name);
+ $view->set_arguments($view_arguments);
+ $view->build();
+ $vbo = _views_bulk_operations_get_field($view);
+ // Unset every field except the VBO one (which holds the entity id).
+ // That way the performance hit becomes much smaller, because there is no
+ // chance of views_handler_field_field::post_execute() firing entity_load().
+ foreach ($view->field as $field_name => $field) {
+ if ($field_name != $vbo->options['id']) {
+ unset($view->field[$field_name]);
+ }
+ }
+ $view->execute($view->current_display);
+ // Save the view in the static cache.
+ $views[$view_target] = $view;
+
+ return $vbo;
+}
diff --git a/sites/all/modules/views_custom_template/LICENSE.txt b/sites/all/modules/views_custom_template/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/views_custom_template/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/views_custom_template/README.txt b/sites/all/modules/views_custom_template/README.txt
new file mode 100644
index 000000000..c2a66861c
--- /dev/null
+++ b/sites/all/modules/views_custom_template/README.txt
@@ -0,0 +1,46 @@
+
+Views custom template
+=====================
+
+Add custom views template suggestions
+http://drupal.org/project/views_custom_template
+
+Features
+========
+
+- Add template suggestion for view displays, rows and fields so you can re-use one template for multiple views
+
+
+Requirements
+============
+
+- Views 3.x
+ http://drupal.org/project/views
+
+Installation
+============
+
+- Install Views custom template
+- Sit back and relax
+
+Usage
+=====
+
+- Go to Views
+- Inside your view display, (In the 3rd column) Other you will find the option "Template suggestion"
+- Enter the machine-name you want to use for the view template.
+- Re-use your template for multiple displays
+- If you override the views default template with a views suggestion like views-view-Page--1.tpl.php the custom template will NOT override this template!
+- Flush the theme cache after you add your template because it's in the Theme registry
+
+"My Custom Template" will become "my_custom_template" (machine-name).
+All levels Views templates will use this suggestion: view, row, fields.
+
+Example: My Custom Template
+
+views-view--my-custom-template.tpl.php
+views-view-unformatted-my-custom-template.tpl.php
+views-view-list--my-custom-template.tpl.php
+views-view-grid--my-custom-template.tpl.php
+views-view-fields--my-custom-template.tpl.php
+etc. \ No newline at end of file
diff --git a/sites/all/modules/views_custom_template/views_custom_template.info b/sites/all/modules/views_custom_template/views_custom_template.info
new file mode 100644
index 000000000..e22b8b686
--- /dev/null
+++ b/sites/all/modules/views_custom_template/views_custom_template.info
@@ -0,0 +1,18 @@
+
+name = Views custom template
+description = Specify a template for a fields row display.
+core = 7.x
+package = Views
+
+dependencies[] = views
+
+; views handlers
+files[] = views_plugin_display_custom_template.inc
+
+
+; Information added by drupal.org packaging script on 2012-07-05
+version = "7.x-2.0"
+core = "7.x"
+project = "views_custom_template"
+datestamp = "1341499047"
+
diff --git a/sites/all/modules/views_custom_template/views_custom_template.module b/sites/all/modules/views_custom_template/views_custom_template.module
new file mode 100644
index 000000000..6f14ad88f
--- /dev/null
+++ b/sites/all/modules/views_custom_template/views_custom_template.module
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * This module add's an addition field to your display and gives you the unique possibility to re-use views templates
+ *
+ */
+
+/**
+ * Implements hook_views_api().
+ */
+function views_custom_template_views_api() {
+ return array(
+ 'version' => 3,
+ );
+}
+
+/**
+ * Preprocess function to render certain elements into the view.
+ */
+function template_preprocess_views_custom_template(&$vars) {
+ // Current theme hook pattern (from the theme registry alter).
+ if (isset($vars['views_custom_template_theme_hook_pattern'])) {
+ $view = $vars['view'];
+ $pattern = $vars['views_custom_template_theme_hook_pattern'];
+
+ // Template suggestion added in the display settings.
+ $template_suggestion = $view->display_handler->get_option('template_suggestion');
+ if ($template_suggestion && trim($template_suggestion)) {
+ $template_suggestion = _format_views_custom_template_suggestion($template_suggestion);
+
+ // Might be overridden already.
+ $is_overridden = !empty($vars['theme_hook_suggestion']);
+
+ // Only change the template if it's not overridden already.
+ if (!$is_overridden) {
+ $vars['theme_hook_suggestion'] = $pattern . $template_suggestion;
+ }
+ }
+ }
+}
+
+/**
+ * Helper function for filtering suggestion
+ */
+function _format_views_custom_template_suggestion($template_suggestion) {
+ return trim(preg_replace('/[^a-z0-9_]+/', '_', strtolower(trim((string) $template_suggestion))), '_');
+}
+
+/**
+ * Implementation of hook_theme_registry_alter().
+ */
+function views_custom_template_theme_registry_alter(&$theme_registry) {
+ // Find interesting Views 'templates'.
+ foreach ($theme_registry as $hook => &$info) {
+ // Must be views, but only unspecific/default.
+ if (0 === strpos($hook, 'views_view') && !is_int(strpos($hook, '__'))) {
+ // Must be extensible.
+ if (!empty($info['pattern']) && !empty($info['template'])) {
+ $info['preprocess functions'][] = 'template_preprocess_views_custom_template';
+ $info['variables']['views_custom_template_theme_hook_pattern'] = $info['pattern'];
+ }
+ }
+ unset($info);
+ }
+
+}
diff --git a/sites/all/modules/views_custom_template/views_custom_template.views.inc b/sites/all/modules/views_custom_template/views_custom_template.views.inc
new file mode 100644
index 000000000..e1e31b4e7
--- /dev/null
+++ b/sites/all/modules/views_custom_template/views_custom_template.views.inc
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Views hooks for this module.
+ *
+ * Defines a display extender to add settings without creating a style plugin.
+ */
+
+/**
+ * Implements hook_views_plugins().
+ */
+function views_custom_template_views_plugins() {
+ return array(
+ // This just tells our themes are elsewhere.
+ 'module' => 'views',
+ // Display settings
+ 'display_extender' => array(
+ // Default settings for all display_extender plugins.
+ 'template_suggestion' => array(
+ 'title' => t('Template suggestion'),
+ 'help' => t('Default settings for this view.'),
+ 'handler' => 'views_plugin_display_custom_template',
+ // You can force the plugin to be enabled
+ 'enabled' => TRUE,
+ 'no ui' => FALSE,
+ ),
+ ),
+ );
+}
diff --git a/sites/all/modules/views_custom_template/views_plugin_display_custom_template.inc b/sites/all/modules/views_custom_template/views_plugin_display_custom_template.inc
new file mode 100644
index 000000000..bc39c4069
--- /dev/null
+++ b/sites/all/modules/views_custom_template/views_plugin_display_custom_template.inc
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Extend display options
+ *
+ * @see views_custom_template_views_plugins()
+ */
+
+class views_plugin_display_custom_template extends views_plugin_display_extender {
+
+ /**
+ * Expose option and set default.
+ */
+ function options_definition() {
+ $options = parent::option_definition();
+ $options['template_suggestion']['default'] = '';
+ return $options;
+ }
+
+ /**
+ * Provide a form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ if ($form_state['section'] == 'template_suggestion') {
+ $form['#title'] .= t('Template suggestion');
+ $form['description'] = array(
+ '#markup' => '<div class="description form-item">' . t('Add custom template suggestion for your view templates.') . '</div>',
+ );
+ $template_suggestion = $this->get_template_suggestion();
+ $form['template_suggestion'] = array(
+ '#type' => 'textfield',
+ '#title' => 'Custom template suggestion (machine-name style)',
+ '#default_value' => $template_suggestion,
+ '#size' => 30,
+ '#maxlength' => 30,
+ '#description' => t(
+ '<p>"<code>My Custom Template</code>" will become "<code>my_custom_template</code> (machine-name)".</br>All levels Views templates will use this suggestion: view, row, field.</p>
+ <p><strong>Example: My Custom Template</strong></p>
+ <ul>
+ <li>views-view--my-custom-template.tpl.php</li>
+ <li>views-view-unformatted-my-custom-template.tpl.php</li>
+ <li>views-view-list--my-custom-template.tpl.php</li>
+ <li>views-view-grid--my-custom-template.tpl.php</li>
+ <li>views-view-fields--my-custom-template.tpl.php</li>
+ <li>etc.</li>
+ </ul>
+ <p><strong>The template suggestion will be used after you flush the theme registry cache.</strong></p>'),
+ );
+ }
+ }
+
+ /**
+ * Save option to display
+ */
+ function options_submit(&$form, &$form_state) {
+ parent::options_submit($form, $form_state);
+
+ $template_suggestion = $this->get_template_suggestion($form_state['values']['template_suggestion']);
+ $this->display->set_option('template_suggestion', $template_suggestion);
+ }
+
+ /**
+ * Provide the default summary for options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) {
+ // Never for the Master display.
+ if (!empty($this->display->default_display) && $this->display->default_display != $this->display) {
+ parent::options_summary($categories, $options);
+
+ $template_suggestion = $this->get_template_suggestion() ?: t('None');
+
+ $options['template_suggestion'] = array(
+ 'category' => 'other',
+ 'title' => t('Template suggestion'),
+ 'value' => $template_suggestion,
+ 'desc' => t('Change the template suggestion that will be added to this display.'),
+ );
+ }
+ }
+
+ function get_template_suggestion($template_suggestion = '') {
+ if (!$template_suggestion) {
+ $template_suggestion = $this->display->get_option('template_suggestion');
+ }
+ $template_suggestion = _format_views_custom_template_suggestion($template_suggestion);
+ return $template_suggestion;
+ }
+
+}
diff --git a/sites/all/modules/views_tree/LICENSE.txt b/sites/all/modules/views_tree/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/views_tree/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/views_tree/views_tree.info b/sites/all/modules/views_tree/views_tree.info
new file mode 100644
index 000000000..4f9be2a47
--- /dev/null
+++ b/sites/all/modules/views_tree/views_tree.info
@@ -0,0 +1,13 @@
+name = Views Tree
+description = A Views style plugin to display a tree of elements using the adjacency model.
+package = Views
+core = 7.x
+dependencies[] = views
+files[] = views_tree_plugin_style_tree.inc
+
+; Information added by drupal.org packaging script on 2012-03-21
+version = "7.x-2.0"
+core = "7.x"
+project = "views_tree"
+datestamp = "1332368746"
+
diff --git a/sites/all/modules/views_tree/views_tree.module b/sites/all/modules/views_tree/views_tree.module
new file mode 100644
index 000000000..08e7d83b2
--- /dev/null
+++ b/sites/all/modules/views_tree/views_tree.module
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * Implements of hook_views_api().
+ */
+function views_tree_views_api() {
+ return array(
+ 'api' => '3.0-alpha1',
+ 'path' => drupal_get_path('module', 'views_tree'),
+ );
+}
+
+/**
+ * Implements of hook_theme().
+ */
+function views_tree_theme($existing, $type, $theme, $path) {
+ return array(
+ 'views_tree' => array(
+ 'variables' => array(
+ 'view' => NULL,
+ 'options' => array(),
+ 'rows' => array(),
+ 'title' => NULL,
+ ),
+ ),
+ 'views_tree_inner' => array(
+ 'variables' => array(
+ 'view' => NULL,
+ 'options' => array(),
+ 'rows' => array(),
+ 'title' => NULL,
+ 'result' => array(),
+ 'parent' => NULL,
+ ),
+ ),
+ );
+}
+
+/**
+ * Theme function for the tree style plugin.
+ *
+ * We need to do some weirdness that makes more sense as a theme function
+ * than as a template.
+ *
+ * @ingroup themeable
+ * @link http://drupal.org/node/355919
+ */
+function theme_views_tree($variables) {
+ $view = $variables['view'];
+ $options = $variables['options'];
+ $rows = $variables['rows'];
+ $title = $variables['title'];
+
+ $result = $view->result;
+ $fields = &$view->field;
+
+ $parents = array();
+
+ if (! $fields[$options['main_field']] instanceof views_handler_field) {
+ drupal_set_message(t('Main field is invalid: %field', array('%field' => $options['main_field'])), 'error');
+ return '';
+ }
+
+ if (! $fields[$options['parent_field']] instanceof views_handler_field) {
+ drupal_set_message(t('Parent field is invalid: %field', array('%field' => $options['parent_field'])), 'error');
+ return '';
+ }
+
+ // The field structure of Field API fields in a views result object is...
+ // ridiculous. To avoid having to deal with it, we'll first iterate over all
+ // records and normalize out the main and parent IDs to new properties. That
+ // vastly simplifies the code that follows. This particular magic
+ // incantation extracts the value from each record for the appropriate field
+ // specified by the user. It then normalizes that value down to just an int,
+ // even though in some cases it is an array. See views_tree_normalize_key().
+ // Finally, we build up a list of all main keys in the result set so that
+ // we can normalize top-level records below.
+ foreach ($result as $i => $record) {
+ $result[$i]->views_tree_main = views_tree_normalize_key($fields[$options['main_field']]->get_value($record), $fields[$options['main_field']]);
+ $result[$i]->views_tree_parent = views_tree_normalize_key($fields[$options['parent_field']]->get_value($record), $fields[$options['parent_field']]);
+
+ $parents[] = $record->views_tree_main;
+ }
+
+ // Normalize the top level of records to all point to 0 as their parent
+ // We only have to do this once, so we do it here in the wrapping function.
+ foreach ($result as $i => $record) {
+ if (! in_array($record->views_tree_parent, $parents)) {
+ $result[$i]->views_tree_parent = 0;
+ }
+ }
+
+ // Recursively render each item.
+ $tree = theme('views_tree_inner', array(
+ 'view' => $view,
+ 'options' => $options,
+ 'rows' => $rows,
+ 'title' => $title,
+ 'result' => $result,
+ 'parent' => 0,
+ )
+ );
+
+ return $title . $tree;
+}
+
+/**
+ * Inner recursive theme function for views tree theming.
+ *
+ * @ingroup themeable
+ * @param $view
+ * @param $options
+ * @param $row
+ * @param $title
+ * @param $result
+ * An array representing the raw data returned from the query.
+ * @param $parent
+ * The id of the parent entry in the call stack.
+ */
+function theme_views_tree_inner($variables) {
+ $view = $variables['view'];
+ $options = $variables['options'];
+ $rows = $variables['rows'];
+ $title = $variables['title'];
+ $result = $variables['result'];
+ $parent = $variables['parent'];
+
+ $items = array();
+ foreach ($result as $i => $record) {
+ if ($record->views_tree_parent == $parent) {
+ $variables['parent'] = $record->views_tree_main;
+ $items[] = $rows[$i] . call_user_func(__FUNCTION__, $variables);
+ }
+ }
+ return count($items) ? theme('item_list', array('items' => $items, 'type' => $options['type'])) : '';
+}
+
+/**
+ * Normalize a value out of the record to an int.
+ *
+ * If the field in question comes from Field API, then it will be an array, not
+ * an int. We need to detect that and extract the int value we want from it.
+ * Note that because Field API structures are so free-form, we have to specifically
+ * support each field type. For right now we support entityreference (target_id),
+ * nodereference (nid), userreference (uid), and taxonomyreference (tid).
+ *
+ * @param mixed $value
+ * The value to normalize. It should be either an int or an array. If an int,
+ * it is returned unaltered. If it's an array, we extract the int we want
+ * and return that.
+ * @param views_handler_field $field
+ * Metadata about the field we are extracting information from.
+ * @return int
+ * The value of this key, normalized to an int.
+ */
+function views_tree_normalize_key($value, views_handler_field $field) {
+ if (is_array($value) && count($value)) {
+ if (isset($field->field_info['columns'])) {
+ $columns = array_keys($field->field_info['columns']);
+ foreach ($columns as $column) {
+ if (in_array($column, array('target_id', 'nid', 'uid', 'tid'))) {
+ $field_property = $column;
+ break;
+ }
+ }
+ }
+ else {
+ $field_property = '';
+ }
+ return $field_property ? $value[0][$field_property] : 0;
+ }
+ else {
+ return $value ? $value : 0;
+ }
+}
diff --git a/sites/all/modules/views_tree/views_tree.views.inc b/sites/all/modules/views_tree/views_tree.views.inc
new file mode 100644
index 000000000..2e882221e
--- /dev/null
+++ b/sites/all/modules/views_tree/views_tree.views.inc
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Implementation of hook_views_plugins()
+ */
+function views_tree_views_plugins() {
+ $plugin = array(
+ 'style' => array(
+ 'tree' => array(
+ 'title' => t('Tree (Adjacency model)'),
+ 'help' => t('Display the results as a nested tree'),
+ 'handler' => 'views_tree_plugin_style_tree',
+ 'theme' => 'views_tree',
+ 'uses options' => TRUE,
+ 'uses row plugin' => TRUE,
+ 'uses fields' => TRUE,
+ 'uses grouping' => FALSE,
+ 'type' => 'normal',
+ 'parent' => 'list',
+ //'path' => drupal_get_path('module', 'views_tree'),
+ ),
+ ),
+ );
+
+ return $plugin;
+}
+
diff --git a/sites/all/modules/views_tree/views_tree_plugin_style_tree.inc b/sites/all/modules/views_tree/views_tree_plugin_style_tree.inc
new file mode 100644
index 000000000..14b4fe4a5
--- /dev/null
+++ b/sites/all/modules/views_tree/views_tree_plugin_style_tree.inc
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains the list style plugin.
+ */
+
+/**
+ * Style plugin to render each item in a slideshow of an ordered or unordered list.
+ *
+ * @ingroup views_style_plugins
+ */
+class views_tree_plugin_style_tree extends views_plugin_style_list {
+
+ /**
+ * Set default options
+ */
+ function option_definition() {
+ $options = parent::option_definition();
+ $options['class'] = array('default' => '');
+ $options['wrapper_class'] = array('default' => 'item-list');
+ $options['main_field'] = array('default' => '');
+ $options['parent_field'] = array('default' => '');
+
+ return $options;
+ }
+
+ /**
+ * Render the given style.
+ */
+ function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $fields = array('' => t('<None>'));
+
+ foreach ($this->display->handler->get_handlers('field') as $field => $handler) {
+ if ($label = $handler->label()) {
+ $fields[$field] = $label;
+ }
+ else {
+ $fields[$field] = $handler->ui_name();
+ }
+ }
+
+ $events = array('click' => t('On Click'), 'mouseover' => t('On Mouseover'));
+
+ $form['type']['#description'] = t('Whether to use an ordered or unordered list for the retrieved items. Most use cases will prefer Unordered.');
+
+ // Unused by the views tree module at this time.
+ unset($form['wrapper_class']);
+ unset($form['class']);
+
+ $form['main_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Main field'),
+ '#options' => $fields,
+ '#default_value' => $this->options['main_field'],
+ '#description' => t('Select the field with the unique identifier for each record.'),
+ '#required' => TRUE,
+ );
+
+ $form['parent_field'] = array(
+ '#type' => 'select',
+ '#title' => t('Parent field'),
+ '#options' => $fields,
+ '#default_value' => $this->options['parent_field'],
+ '#description' => t('Select the field that contains the unique identifier of the record\'s parent.'),
+ );
+ }
+}
diff --git a/sites/all/modules/wysiwyg/CHANGELOG.txt b/sites/all/modules/wysiwyg/CHANGELOG.txt
new file mode 100644
index 000000000..2072654e9
--- /dev/null
+++ b/sites/all/modules/wysiwyg/CHANGELOG.txt
@@ -0,0 +1,345 @@
+
+Wysiwyg 7.x-2.x, xxxx-xx-xx
+---------------------------
+#1388224 by ksenzee, sun, TwoD: Fixed editors detaching on form submissions.
+#682160 by n_vashenko, TwoD: Fixed lists plugin support for TinyMCE.
+#1414354 by Merco: Fixed none.js breaks if textarea.js is not loaded.
+#1064600 by TwoD: Fixed maximized editors hidden under Drupal's toolbar.
+#1405786 by logaritmisk: Fixed CKEditor being wider than parent elements.
+#1531896 by Chi: Fixed strict warning for WYMeditor.
+#1442226 by robertom: Fixed inverted list button names for WYMeditor.
+#1352426 by TwoD, sun: Added install notes (CKEditor edition clarification).
+#1112212 by timdiacon, TwoD: Added language direction buttons for CKEditor.
+#1398560 by markwittens: Fixed TinyMCE removing the longdesc attribute.
+#970452 by smk-ka, sun, TwoD, drzraf: Fixed outdated TinyMCE plugin info.
+#1155678 by james.elliott, Jody Lynn, sun: Add Drupal.detachBehaviors support.
+#624018 by smk-ka, quartsize, dagmar, nedjo, rickvug, catch, sun: Added Features support.
+#1238766 by Dave Reid: Fixed Missing cells in profile plugins table.
+#1073106 by scottrouse: Fixed 'Input Format' should be 'Text Format'.
+#1153458 by TwoD: Fixed TinyMCE 'Verify HTML' setting ignored.
+#1125582 by TwoD: Fixed TinyMCE fullscreen plugin deletes content.
+#1078834 by sun: Fixed coding standards errors.
+#1173476 by jim0203, sun: Fixed installation instructions in README.txt.
+
+
+Wysiwyg 7.x-2.1, 2011-06-19
+---------------------------
+#679056 by TwoD: Fixed patch for pressing enter in autocomplete, new jQuery API.
+#524126 by sun: Re-added #wysiwyg property to enforce no editor via code.
+#1153458 by Deciphered: Fixed TinyMCE 'Verify HTML' setting being ignored.
+#1079694 by TwoD: Fixed Whizzywig not restoring textarea styles when detached.
+#1132142 by tacituseu, TwoD, sun: Fixed nicEdit not removing its submit handler.
+#1143104 by EugenMayer: Fixed CKEditor 3.5.4 version detection.
+#1009880 by AndyF: Fixed another CKEditor selection handling issue.
+#1048556 by cousin_itt, TwoD: Fixed TinyMCE insertdatetime plugin setting.
+#1036900 by mattyoung: Minor code clean-up in wysiwyg_tinymce_version().
+#1026088 by sun: Fixed installation instructions in README.txt.
+by sun: Merged in module baseline to facilitate testing.
+#1034476 by quartsize, sun: Changed Wysiwyg profiles into entities.
+
+
+Wysiwyg 7.x-2.0, 2010-01-06
+---------------------------
+#950216 by TwoD, sun: Fixed missing editor for a single text format.
+#1007630 by aspilicious: Removed files[] declarations from .info file.
+#612954 by sun: Reverted 'buttons' change in profile configuration form.
+#975546 by TwoD, sun: Fixed markItUp CSS loaded with wrong weight.
+#659428 by TwoD, sun: Fixed editor is attached to disabled text format widgets.
+#975490 by Gbor Hojtsy: Updated for 'group' API change in drupal_add_js().
+#974604 by CrookedNumber, Gbor Hojtsy: Fixed theme CSS not loaded in editors.
+#931374 by TwoD, ksenzee: Updated for text format schema change.
+#826914 by catch, sun: Added database cache for wysiwyg_profile_load().
+#941230 by sun: Fixed missing Configuration link on Modules page.
+#739558 by sun, TwoD: Updated for new #type text_format.
+#612954 by TwoD: Fixed broken editor settings.
+#585932 by sun: Ported to Drupal 7.
+
+
+Wysiwyg 6.x-2.3, 2011-01-30
+---------------------------
+#1025296 by TwoD: Updated CKEditor to support iFrame button.
+#737318 by TwoD: Fixed CKEditor default skin array not being reindexed.
+by sun: Fixed coding style in wysiwyg_schema().
+#964978 by sun, TwoD: Added hook_wysiwyg_editor_settings_alter() documentation.
+#775972 by TwoD, Agileware, sun: Fixed broken user default status preferences.
+#1007066 by TwoD, penguin25: Fixed CKEditor ignores resizable option.
+#613944 by TwoD, sun: Fixed data.node not always present in CKEditor.
+#1009880 by TwoD: Fixed selection handling broken in CKEditor.
+
+
+Wysiwyg 6.x-2.2, 2010-12-20
+---------------------------
+#613944 by TwoD, sun: Fixed data.node not available in CKEditor.
+#748888 by TwoD, sun: Fixed isNode() not called in CKEditor.
+#767550 by TwoD, sun, ungeek: Fixed invalid API docs and logic for
+ $plugin['filename'].
+#988200 by sun: Changed static language list to ISO 639 defaults of Drupal core.
+#973808 by David_Rothstein: Fixed CKEditor incorrectly formatting the <br> tag.
+#773856 by Roi Danton: Added CSS path and file documentation.
+#735186 by TwoD, torbs: Fixed missing Norwegian language code.
+#678580 by TwoD, sun: Fixed Drupal.wysiwygAttachToggleLink breaks click events.
+#497654 by TwoD: Fixed Drupal plugins disabled in FCKeditor/WebKit browsers.
+#735624 by sun: Fixed enabling one button removes default editor toolbar.
+#755610 by sun, TwoD, BrightBold: Fixed white-space in block formats setting
+ breaks editors.
+#713942 by TwoD, sun: Fixed jQuery closure breaks OpenWYSIWYG.
+#679056 by sun, TwoD: Fixed pressing enter in autocomplete detaches editors.
+#80170 by sun: Changed dialog/plugin API for Inline API compatibility.
+#803466 by hotspoons: Fixed TinyMCE image map support in advimage plugin.
+#922436 by TwoD: Fixed Whizzywig Uncaught TypeError in Chrome.
+#922520 by TwoD: Fixed Whizzywig is not detached properly.
+#907186 by TwoD: Fixed Whizzywig v60+ compatibility.
+#765292 by TwoD: Added TinyMCE WordCount plugin.
+#768726 by TwoD: Added TinyMCE AutoResize plugin.
+#781086 by TwoD: Fixed TinyMCE plugin options merged wrongly.
+#767628 by TwoD: Fixed 'The version of markItUp could not be detected' error.
+#651490 by TwoD: Fixed Whizzywig width.
+#715228 by TwoD: Fixed TinyMCE image popups not launching for existing images.
+#606952 by TwoD: Fixed inserting content in fullscreen TinyMCE.
+#593008 by TwoD: Fixed third-party scripts breaking Wysiwyg.
+#695398 by RichieB, Cl1mh4224rd, mcpuddin: Fixed TinyMCE 3.3.9.1 detection.
+#737318 by dboune: Fixed CKEditor default skin depends on filesystem order.
+#775608 by TwoD: Fixed FCKEditor crashes IE on save.
+#824710 by TwoD: CKEditor not disabled upon enabling.
+#752516 by nquocbao, sun: Fixed openwysiwyg version callback.
+#753536 by TwoD: Fixed version detection for Whizzywig.
+#752516 by nquocbao, sun: Fixed file stream warnings in version callbacks.
+
+
+Wysiwyg 6.x-2.1, 2010-03-08
+---------------------------
+#628110 by quicksketch, sun, markus_petrux: Added editor settings alter hook.
+#689218 by wwalc, TwoD, sun: Improved support for CKEditor.
+#695398 by TwoD: Updated support for TinyMCE 3.3.
+#613096 by Scott Reynolds: Fixed no editor appearing for user signature field.
+#696040 by Dave Reid: Fixed missing Cancel link on profile form.
+#594322 by TwoD: Added insert method for NicEdit.
+#659200 by TwoD: Fixed YUI Editor content lost in IE.
+#594928 by ericbellot, TwoD, sun: Fixed 'attribs' button missing in TinyMCE.
+#557090 by TwoD: Fixed Whizzywig 56 instance not removed on detach().
+#667848 by TwoD, kaakuu: Fixed FCKeditor is not properly detached in IE.
+#695768 by sun: Fixed #resizable removed when no editor profiles are loaded.
+#631494 by TwoD: Fixed multi-site libraries directory failure for WYMeditor.
+#660080 by TwoD: Fixed Notice: Undefined offset.
+#613922 by TwoD, sun: Fixed PHP warning when saving profiles.
+#582298 by dereine: Added auto-paste from Word detection for FCKeditor.
+#597852 by sun: Fixed missing Turkish in language list.
+#620176 by sun: Fixed missing Ukrainian in language list.
+#613480 by TwoD, Dave Reid: Fixed PHP 5.3 compatibility.
+#462146 by TwoD: Cleaned up CKEditor implementation.
+#380586 by SimonEast: Updated YUI editor: Version detection not working.
+#610132 by TwoD: Updated CKEditor 3.0.1, stylesheets and version check.
+#620858 by quicksketch: Fixed focus event not firing for CKeditor.
+#585932 by sun: Synced various clean-ups from 7.x.
+#489156 by sun: Removed orphan global 'showToggle' JS setting.
+#462146 by sun, Niels Hackius: Fixed version detection for CKeditor.
+#545210 by sun: Fixed default value for editor toggle link.
+#372826 by Roi Danton, sun: Added Wysiwyg API developer documentation.
+by sun: Fixed PHP notice.
+#514912 by Likeless, sun: Added plugin/button handling for WYMeditor.
+#538996 by darktygur: Fixed 404 errors for non-existing theme CSS files.
+#509570 by Rob Loach, sun: Added forced detaching of editor upon form submit.
+#526644 by Darren Oh: Fixed broken editor theme validation.
+#490914 by sun: Fixed JS/CSS not updated after update with caching enabled.
+#522440 by authentictech, sun: UX: Fixed user interface for Wysiwyg profiles.
+#507608 by jfh: Added WYMeditor instance API methods.
+by sun: Fixed form_build_id not removed from serialized profile settings.
+#496744 by TwoD: Fixed FCKeditor: "Apply source formatting" not working.
+#462146 by TwoD, et al: Added support for CKeditor.
+#490270 by sun: Fixed openWYSIWYG displays no buttons by default.
+#490266 by sun: Fixed JS error when wysiwyg profile contains no buttons.
+#400482 by sun: Fixed editor.instance.prepareContent() breaks editor's native
+ markup handling. Drupal plugin authors should add the CSS class
+ 'drupal-content' to prevent the editor selection to activate internal editor
+ buttons.
+#394068 by kswan: Fixed missing button icons in markItUp.
+
+
+Wysiwyg 6.x-2.0, 2009-06-10
+---------------------------
+#474908 by TwoD: Fixed Teaser break causing error in IE8.
+by sun: Major code clean-up.
+#331089 by wwalc: Fixed FCKeditor toolbar buttons do not wrap.
+#407014 by sun: Fixed/removed migration from other editor integration modules.
+#485264 by sun: Changed installation instructions to be more concise.
+#479514 by sun: Fixed native plugin loading for TinyMCE (follow-up).
+#434590 by sun: Fixed path admin/settings/wysiwyg not found.
+#479514 by TwoD, sun: Added native plugin support for FCKeditor.
+#341054 by sun: Fixed toggle link configuration setting not working.
+by sun: Fixed markItUp button icons are not displayed.
+by sun: Added openWYSIWYG editor support.
+#362137 by jfh, sun: Fixed WYMeditor broken when JS/CSS aggregation is enabled.
+#328252 by sun: Added TinyMCE plugin BBCode for 3.x.
+#429926 by TwoD, sun: Fixed TinyMCE broken due to renamed Flash/Media plugin.
+#342864 by davexoxide, sun: Added YUI editor support.
+#332139 by sun: Fixed editor must not be changed when profile is configured.
+#362137 by jfh: Added WYMeditor support.
+#470928 by jfh, sun: Fixed Drupal.wysiwyg.clone turns arrays into objects.
+#445826 by TwoD: Fixed FCKeditor: Drupal.wysiwyg.activeId not updated.
+#478324 by jeffschuler: Fixed typo in profile configuration form.
+#373542 by sun: Fixed encoding of HTML entities for certain languages.
+#320562 by sun: Changed location for external editor libraries.
+#449134 by sun: Fixed stylesheets of theme missing in node form previews.
+
+
+Wysiwyg 6.x-2.0-ALPHA1, 2009-05-17
+----------------------------------
+#403728 by jfduchesneau: Fixed none.js breaks if textarea.js is not loaded.
+#454992 by sun: Fixed drupal_get_js() query string 'q' breaks plugin loading.
+#419696 by sun: Fixed native plugins plugins are not loaded for all profiles.
+#414768 by sun: Fixed Wysiwyg API not working in Konqueror.
+#293803 by sun: Fixed "Show summary in full view" checkbox not displayed.
+#416742 by sun: Fixed type casting of $profile in profile configuration form.
+#404532 by TwoD: Fixed Teaser break comment stripped in IE.
+#380698 by TwoD: Added Drupal plugin support for FCKeditor.
+#380698 by TwoD, sun: Added Drupal plugin support for FCKeditor (part I).
+#398848 by sun: Added support for TinyMCE 3.1.
+#394068 by StephaneC: markItUp: Fixed icons not displayed due to #385736.
+#385974 by sun: Fixed form element description for CSS path (for Define CSS).
+#390460 by sun: Fixed broken plugin loading due to #359626.
+#385736 by sun: Fixed markItUp: Wrong editor library location.
+#308912 by sun: Fixed TinyMCE: Buttons do not wrap in IE/Chrome.
+#380586 by sun, hass: YUI editor: Fixed version detection.
+#390224 by hass: Fixed JS error YAHOO.widget.Editor is not a constructor.
+#359626 by sun: Fixed external/Drupal plugins are not loaded for all profiles.
+#369115 by sun: Fixed TinyMCE's URL conversion magic breaks some input filters.
+#376400 by TwoD: Fixed bad grammar in help text on profile overview page.
+#367632 by sun: Fixed $this and i JavaScript variables defined in global scope.
+#319363 by sun: Fixed missing spacer.gif for Teaser break plugin.
+#373672 by chawl: Added (native) xhtmlextras plugin for TinyMCE 3.
+#287025 by sun: Fixed native editor plugin options for TinyMCE and FCKeditor.
+#373542 by sun: Fixed TinyMCE: entity_encoding 'raw' removes HTML entities.
+#372806 by sun: Fixed block format configuration form element description.
+#370277 by sun: Fixed "Uncaught SyntaxError: Unexpected token" in IE/Chrome.
+#367632 by sun: Fixed "Unexpected identifier, string or number" error in IE.
+#367632 by sun: Fixed invalid JavaScript syntax.
+#319363 by sun: Follow-up: Synced 1.x with 2.x where possible.
+#319363 by sun, quicksketch: Rewrote editor plugin API. The new plugin API
+ allows Drupal modules to expose editor plugins for ANY editor without
+ implementing editor-specific code. Major milestone for better content-editing
+ in Drupal.
+#364782 by sun: Fixed theme stylesheets not properly loaded.
+#352938 by sun: Fixed JS error (blank page) in IE when plugins are loaded.
+#331089 by wwalc, sun: Added custom toolbar configuration support for FCKeditor.
+#331089 by sun: Fixed PHP notice for 'user_choose'; FCKeditor clean-up.
+#344230 by wwalc: Fixed wrong editor base path setting for FCKeditor.
+#361289 by sun: Fixed CSS files do not need to use media 'screen'.
+#360696 by sun: Fixed IE does not trigger onChange event when selecting an input
+ format.
+#342376 by sun: Extended API to allow "preprocess" JavaScript option for some
+ editors.
+#352295 by sun: Added markItUp editor support.
+#352703 by sun: Fixed wrong default configuration options for TinyMCE 3.2.1+.
+#348317 by sun: Fixed TinyMCE's extended_valid_elements for advlink/advimage
+ plugin.
+#348986 by sun: Added CSS class for toggle link container.
+#342864 by davexoxide, sun: Added YUI editor support.
+#343217 by sun: Fixed improperly capitalized library script name for nicEdit.
+#341267 by sun: Fixed improper test for internal editor plugins.
+#341996 by sun: Fixed editor cannot be re-enabled with one input format only.
+#341267 by sun: Added support for extensions that do not need to be loaded.
+
+
+Wysiwyg 6.x-0.5, 2008-12-01
+---------------------------
+#340758 by sun: Changed installation instructions to be displayed permanently.
+#322657 by sun: Fixed "Enabled by default" option does not work when disabled.
+#328052 by sun: Fixed switching input formats leads to wrong editor/state.
+#337569 by sun: Fixed different profiles for same editor are not respected.
+#340195 by sun: Fixed #after_build function not invoked on all forms.
+#333521 by sun: Fixed TinyMCE version detection to look at the actual script.
+#329657 by svendecabooter, sun: Added Whizzywig support.
+#333521 by sun: Fixed TinyMCE version detection docs.
+#327100 by sun: Changed access permission for settings page to 'administer
+ filters' to prevent incomplete updates.
+#322731 by sun: Fixed improper use of t() in module install file.
+#329410 by sun: Fixed editor not loaded if there is only one input format.
+#324366 by sun: Fixed "Illegal offset type" error on custom content-types.
+#328948 by sun: Fixed PHP notices when editors are assigned, but not configured.
+#327710 by sun: Fixed nicEdit version could not be detected.
+#328116 by sun: Added Safari plugin for TinyMCE 3.
+#327710 by sun: Added nicEdit support.
+#323855 by sun: Increased supported version of jWYSIWYG to 0.5.
+#323671 by sun: Fixed TinyMCE editor not resized when browser is resized.
+#327152 by sun: Fixed breadcrumbs for profile configuration pages.
+#323855 by Rob Loach, sun: Added jWYSIWYG support.
+#327100 by sun: Associate editors/profiles with input formats. Major milestone.
+#325980 by markus_petrux: Added Spanish/Catalan translation for Break plugin.
+#323795 by sun: Removed obsolete Wysiwyg Editor module files.
+#308912 by sun: Fixed alignment of editor buttons in TinyMCE 3.
+#316507 by sun: Fixed TinyMCE 3 not detached properly from AJAX contents.
+#320559 by markus_petrux, sun: Added confirmation form to delete profiles.
+
+
+Wysiwyg 6.x-0.4, 2008-10-14
+---------------------------
+#321216 by sun: Replaced Wysiwyg Editor module with Wysiwyg module.
+#321086 by sun: Fixed (old-style) Teaser break plugin breaks TinyMCE 3.
+#316507 by sun: Code clean-up; editor settings should be cloned for init, too.
+#282717 by sun: Fixed FCKeditor default settings while FCKeditor maintainers get
+ up and running.
+#319363 by sun: Changed JS settings namespace 'wysiwygEditor' to 'wysiwyg'.
+#319363 by sun: Code clean-up; fixed missing namespace change in tinymce-3.js.
+#273408 by quicksketch: Added blockquote button for TinyMCE 3.
+#319363 by sun: Changed JavaScript namespaces and centralized namespace
+ initialization.
+#270780 by sun: Fixed TinyMCE 3 support for external plugins.
+#309832 by sun: Fixed README.txt.
+#253600 by sun: Changed editor integration so that client-side editors attach to
+ input formats instead of textareas and are invoked for input format enabled
+ textareas only.
+#282717 by sun: Added (basic) FCKeditor support.
+#316507 by sun: Added Drupal.wysiwyg function stacks to execute editor library
+ specific actions upon initializing, attaching, detaching, and toggling an
+ editor. Editor specific JavaScript resides in separate files now, as specified
+ and returned by implementations of hook_editor().
+ Wysiwyg is a real API finally, supporting multiple editors and editor versions.
+#316507 by sun: Rewrote Wysiwyg API's internal architecture to support multiple
+ editors.
+
+
+Wysiwyg 6.x-0.3, 2008-09-12
+---------------------------
+#125267 by sun: Removed Safari browser warning configuration option.
+#304243 by sun: Fixed profile configuration improperly passed to JavaScript.
+#304243 by sun: Code clean-up for wysiwyg_editor_profile_overview().
+#289218 by gustav: Fixed E^ALL notice if node has no body field.
+#304243 by sun: Code clean-up for wysiwyg_editor_user_status().
+#299108 by toniw: Added setting for TinyMCE's auto-cleanup paste feature.
+#293916 by sun: Clarified TinyMCE compatibility in README.txt.
+#293425 by sun: Fixed foreach warning during upgrade from TinyMCE module.
+#292517 by sun: Fixed SQL error during upgrade from TinyMCE module.
+#286470 by chayner, sun: Fixed wrong editorBasePath in editor configuration.
+#227687 by sun: Fixed improperly capitalized package name.
+#288028 by Matthew Davidson: Fixed outdated check for PHP input filter.
+#280727 by sun: Removed gzip compressor from installation instructions.
+
+
+Wysiwyg 5.x-0.2, 2008-07-16
+---------------------------
+by sun: Fixed JavaScript errors when JS aggregation/compression is enabled.
+#268562 by sun: Code clean-up; changed format for custom defined CSS classes
+ and removed error-prone auto-layout of buttons in favor of aligning them in
+ one row with a stylesheet; may break existing profiles.
+#270730 by hass, sun: Added German translation for Teaser break plugin.
+#268838 by sun: Fixed PHP warning if no buttons are enabled for a profile.
+#268838 by sun: Ported to Drupal 6.x.
+#152046 by sun: Added hook_wysiwyg_plugin().
+#268562 by sun: Code clean-up.
+#60667 by sun: Fixed wrong editor profile is loaded when user is granted access
+ to more than one profile.
+#264739 by sun: Fixed missing t() around some profile settings options.
+
+
+Wysiwyg 5.x-0.1, 2008-06-07
+---------------------------
+#264739 by sun: Improved output strings.
+#264739 by hass, sun: Fixed potx error due to wrong t() string.
+#264411 by sun: Cleaned coding-style using coder_format script.
+#264411 by sun: Moved admin functions into separate include file.
+#264411 by sun: Added TinyMCE data import upon installation.
+#264411 by sun: Renamed module to Wysiwyg Editor.
+#118747 by nedjo, sun: Upgraded code for jQuery.
+Initial fork of TinyMCE module (2008-05-30).
+
+
diff --git a/sites/all/modules/wysiwyg/LICENSE.txt b/sites/all/modules/wysiwyg/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/wysiwyg/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/wysiwyg/README.txt b/sites/all/modules/wysiwyg/README.txt
new file mode 100644
index 000000000..75d2b14c5
--- /dev/null
+++ b/sites/all/modules/wysiwyg/README.txt
@@ -0,0 +1,54 @@
+
+-- SUMMARY --
+
+Wysiwyg API allows to users of your site to use WYSIWYG/rich-text, and other
+client-side editors for editing contents. This module depends on third-party
+editor libraries, most often based on JavaScript.
+
+For a full description of the module, visit the project page:
+ http://drupal.org/project/wysiwyg
+To submit bug reports and feature suggestions, or to track changes:
+ http://drupal.org/project/issues/wysiwyg
+
+
+-- REQUIREMENTS --
+
+* None.
+
+
+-- INSTALLATION --
+
+* Install as usual, see
+ http://drupal.org/documentation/install/modules-themes/modules-7
+
+* Go to Administration » Configuration » Content authoring » Wysiwyg,
+ and follow the displayed installation instructions to download and install one
+ of the supported editors.
+
+
+-- CONFIGURATION --
+
+* Go to Administration » Configuration » Content authoring » Text formats, and
+
+ - either configure the Full HTML format, assign it to trusted roles, and
+ disable "HTML filter", "Line break converter", and (optionally) "URL filter".
+
+ - or add a new text format, assign it to trusted roles, and ensure that above
+ mentioned input filters are disabled.
+
+* Setup editor profiles in Administration » Configuration » Content authoring
+ » Wysiwyg.
+
+
+-- CONTACT --
+
+Current maintainers:
+* Daniel F. Kudwien (sun) - http://drupal.org/user/54136
+* Henrik Danielsson (TwoD) - http://drupal.org/user/244227
+
+This project has been sponsored by:
+* UNLEASHED MIND
+ Specialized in consulting and planning of Drupal powered sites, UNLEASHED
+ MIND offers installation, development, theming, customization, and hosting
+ to get you started. Visit http://www.unleashedmind.com for more information.
+
diff --git a/sites/all/modules/wysiwyg/editors/ckeditor.inc b/sites/all/modules/wysiwyg/editors/ckeditor.inc
new file mode 100644
index 000000000..fcf168ec3
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/ckeditor.inc
@@ -0,0 +1,464 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for CKEditor.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_ckeditor_editor() {
+ $editor['ckeditor'] = array(
+ 'title' => 'CKEditor',
+ 'vendor url' => 'http://ckeditor.com',
+ 'download url' => 'http://ckeditor.com/download',
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Default',
+ 'files' => array(
+ 'ckeditor.js' => array('preprocess' => FALSE),
+ ),
+ ),
+ 'src' => array(
+ 'title' => 'Source',
+ 'files' => array(
+ 'ckeditor_source.js' => array('preprocess' => FALSE),
+ ),
+ ),
+ ),
+ 'install note callback' => 'wysiwyg_ckeditor_install_note',
+ 'version callback' => 'wysiwyg_ckeditor_version',
+ 'themes callback' => 'wysiwyg_ckeditor_themes',
+ 'settings form callback' => 'wysiwyg_ckeditor_settings_form',
+ 'init callback' => 'wysiwyg_ckeditor_init',
+ 'settings callback' => 'wysiwyg_ckeditor_settings',
+ 'plugin callback' => 'wysiwyg_ckeditor_plugins',
+ 'plugin settings callback' => 'wysiwyg_ckeditor_plugin_settings',
+ 'proxy plugin' => array(
+ 'drupal' => array(
+ 'load' => TRUE,
+ 'proxy' => TRUE,
+ ),
+ ),
+ 'proxy plugin settings callback' => 'wysiwyg_ckeditor_proxy_plugin_settings',
+ 'versions' => array(
+ '3.0.0.3665' => array(
+ 'js files' => array('ckeditor-3.0.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Return an install note.
+ */
+function wysiwyg_ckeditor_install_note() {
+ return '<p class="warning">' . t('Do NOT download the "CKEditor for Drupal" edition.') . '</p>';
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_ckeditor_version($editor) {
+ $library = $editor['library path'] . '/ckeditor.js';
+ if (!file_exists($library)) {
+ return;
+ }
+ $library = fopen($library, 'r');
+ $max_lines = 8;
+ while ($max_lines && $line = fgets($library, 500)) {
+ // version:'CKEditor 3.0 SVN',revision:'3665'
+ // version:'3.0 RC',revision:'3753'
+ // version:'3.0.1',revision:'4391'
+ if (preg_match('@version:\'(?:CKEditor )?([\d\.]+)(?:.+revision:\'([\d]+))?@', $line, $version)) {
+ fclose($library);
+ // Version numbers need to have three parts since 3.0.1.
+ $version[1] = preg_replace('/^(\d+)\.(\d+)$/', '${1}.${2}.0', $version[1]);
+ return $version[1] . '.' . $version[2];
+ }
+ $max_lines--;
+ }
+ fclose($library);
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * An array of theme names. The first returned name should be the default
+ * theme name.
+ */
+function wysiwyg_ckeditor_themes($editor, $profile) {
+ // @todo Skins are not themes but this will do for now.
+ $path = $editor['library path'] . '/skins/';
+ if (file_exists($path) && ($dir_handle = opendir($path))) {
+ $themes = array();
+ while ($file = readdir($dir_handle)) {
+ if (is_dir($path . $file) && substr($file, 0, 1) != '.' && $file != 'CVS') {
+ $themes[] = $file;
+ }
+ }
+ closedir($dir_handle);
+ natcasesort($themes);
+ $themes = array_values($themes);
+ return !empty($themes) ? $themes : array('default');
+ }
+ else {
+ return array('default');
+ }
+}
+
+/**
+ * Enhances the editor profile settings form for CKEditor.
+ *
+ * Adds support for CKEditor's advanced stylesSets, which are a more advanced
+ * implementation and combination of block formats and font styles that allow
+ * to adjust the HTML element, attributes, and CSS styles at once.
+ *
+ * @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
+ * @see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.config.html#.stylesSet
+ */
+function wysiwyg_ckeditor_settings_form(&$form, &$form_state) {
+ if (version_compare($form_state['wysiwyg']['editor']['installed version'], '3.2.1', '>=')) {
+ // Replace CSS classes element description to explain the advanced syntax.
+ $form['css']['css_classes']['#description'] = t('Optionally define CSS classes for the "Font style" dropdown list.<br />Enter one class on each line in the format: !format. Example: !example<br />If left blank, CSS classes are automatically imported from loaded stylesheet(s).', array(
+ '!format' => '<code>[label]=[element].[class]</code>',
+ '!example' => '<code>Title=h1.title</code>',
+ ));
+ $form['css']['css_classes']['#element_validate'][] = 'wysiwyg_ckeditor_settings_form_validate_css_classes';
+ }
+ else {
+ // Versions below 3.2.1 do not support Font styles at all.
+ $form['css']['css_classes']['#access'] = FALSE;
+ }
+}
+
+/**
+ * #element_validate handler for CSS classes element altered by wysiwyg_ckeditor_settings_form().
+ */
+function wysiwyg_ckeditor_settings_form_validate_css_classes($element, &$form_state) {
+ if (wysiwyg_ckeditor_settings_parse_styles($element['#value']) === FALSE) {
+ form_error($element, t('The specified CSS classes are syntactically incorrect.'));
+ }
+}
+
+/**
+ * Returns an initialization JavaScript for this editor library.
+ *
+ * @param array $editor
+ * The editor library definition.
+ * @param string $library
+ * The library variant key from $editor['libraries'].
+ * @param object $profile
+ * The (first) wysiwyg editor profile.
+ *
+ * @return string
+ * A string containing inline JavaScript to execute before the editor library
+ * script is loaded.
+ */
+function wysiwyg_ckeditor_init($editor) {
+ // CKEditor unconditionally searches for its library filename in SCRIPT tags
+ // on the page upon loading the library in order to determine the base path to
+ // itself. When JavaScript aggregation is enabled, this search fails and all
+ // relative constructed paths within CKEditor are broken. The library has a
+ // CKEditor.basePath property, but it is not publicly documented and thus not
+ // reliable. The official documentation suggests to solve the issue through
+ // the global window variable.
+ // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Specifying_the_Editor_Path
+ $library_path = base_path() . $editor['library path'] . '/';
+ return <<<EOL
+window.CKEDITOR_BASEPATH = '$library_path';
+EOL;
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_ckeditor_settings($editor, $config, $theme) {
+ $settings = array(
+ 'width' => 'auto',
+ // For better compatibility with smaller textareas.
+ 'resize_minWidth' => 450,
+ 'height' => 420,
+ // @todo Do not use skins as themes and add separate skin handling.
+ 'theme' => 'default',
+ 'skin' => !empty($theme) ? $theme : 'kama',
+ // By default, CKEditor converts most characters into HTML entities. Since
+ // it does not support a custom definition, but Drupal supports Unicode, we
+ // disable at least the additional character sets. CKEditor always converts
+ // XML default characters '&', '<', '>'.
+ // @todo Check whether completely disabling ProcessHTMLEntities is an option.
+ 'entities_latin' => FALSE,
+ 'entities_greek' => FALSE,
+ );
+
+ // Add HTML block format settings; common block formats are already predefined
+ // by CKEditor.
+ if (isset($config['block_formats'])) {
+ $block_formats = explode(',', drupal_strtolower($config['block_formats']));
+ $predefined_formats = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'address', 'div');
+ foreach (array_diff($block_formats, $predefined_formats) as $tag) {
+ $tag = trim($tag);
+ $settings["format_$tag"] = array('element' => $tag);
+ }
+ $settings['format_tags'] = implode(';', $block_formats);
+ }
+
+ if (isset($config['apply_source_formatting'])) {
+ $settings['apply_source_formatting'] = $config['apply_source_formatting'];
+ }
+
+ if (isset($config['css_setting'])) {
+ // Versions below 3.0.1 could only handle one stylesheet.
+ if (version_compare($editor['installed version'], '3.0.1.4391', '<')) {
+ if ($config['css_setting'] == 'theme') {
+ $settings['contentsCss'] = reset(wysiwyg_get_css());
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['contentsCss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))));
+ }
+ }
+ else {
+ if ($config['css_setting'] == 'theme') {
+ $settings['contentsCss'] = wysiwyg_get_css();
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['contentsCss'] = explode(',', strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))));
+ }
+ }
+ }
+
+ // Parse and define the styles set for the Styles plugin (3.2.1+).
+ // @todo This should be a plugin setting, but Wysiwyg does not support
+ // plugin-specific settings yet.
+ if (!empty($config['buttons']['default']['Styles']) && version_compare($editor['installed version'], '3.2.1', '>=')) {
+ if ($styles = wysiwyg_ckeditor_settings_parse_styles($config['css_classes'])) {
+ $settings['stylesSet'] = $styles;
+ }
+ }
+
+ if (isset($config['language'])) {
+ $settings['language'] = $config['language'];
+ }
+ if (isset($config['resizing'])) {
+ // CKEditor performs a type-agnostic comparison on this particular setting.
+ $settings['resize_enabled'] = (bool) $config['resizing'];
+ }
+ if (isset($config['toolbar_loc'])) {
+ $settings['toolbarLocation'] = $config['toolbar_loc'];
+ }
+
+ $settings['toolbar'] = array();
+ if (!empty($config['buttons'])) {
+ $extra_plugins = array();
+ $plugins = wysiwyg_get_plugins($editor['name']);
+ foreach ($config['buttons'] as $plugin => $buttons) {
+ foreach ($buttons as $button => $enabled) {
+ // Iterate separately over buttons and extensions properties.
+ foreach (array('buttons', 'extensions') as $type) {
+ // Skip unavailable plugins.
+ if (!isset($plugins[$plugin][$type][$button])) {
+ continue;
+ }
+ // Add buttons.
+ if ($type == 'buttons') {
+ $settings['toolbar'][] = $button;
+ }
+ // Add external Drupal plugins to the list of extensions.
+ if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) {
+ $extra_plugins[] = $button;
+ }
+ // Add external plugins to the list of extensions.
+ elseif ($type == 'buttons' && empty($plugins[$plugin]['internal'])) {
+ $extra_plugins[] = $plugin;
+ }
+ // Add internal buttons that also need to be loaded as extension.
+ elseif ($type == 'buttons' && !empty($plugins[$plugin]['load'])) {
+ $extra_plugins[] = $plugin;
+ }
+ // Add plain extensions.
+ elseif ($type == 'extensions' && !empty($plugins[$plugin]['load'])) {
+ $extra_plugins[] = $plugin;
+ }
+ // Allow plugins to add or override global configuration settings.
+ if (!empty($plugins[$plugin]['options'])) {
+ $settings = array_merge($settings, $plugins[$plugin]['options']);
+ }
+ }
+ }
+ }
+ if (!empty($extra_plugins)) {
+ $settings['extraPlugins'] = implode(',', $extra_plugins);
+ }
+ }
+ // For now, all buttons are placed into one row.
+ $settings['toolbar'] = array($settings['toolbar']);
+
+ return $settings;
+}
+
+/**
+ * Parses CSS classes settings string into a stylesSet JavaScript settings array.
+ *
+ * @param string $css_classes
+ * A string containing CSS class definitions to add to the Style dropdown
+ * list, separated by newlines.
+ *
+ * @return array|false
+ * An array containing the parsed stylesSet definition, or FALSE on parse
+ * error.
+ *
+ * @see wysiwyg_ckeditor_settings_form()
+ * @see wysiwyg_ckeditor_settings_form_validate_css_classes()
+ *
+ * @todo This should be a plugin setting, but Wysiwyg does not support
+ * plugin-specific settings yet.
+ */
+function wysiwyg_ckeditor_settings_parse_styles($css_classes) {
+ $set = array();
+ $input = trim($css_classes);
+ if (empty($input)) {
+ return $set;
+ }
+ // Handle both Unix and Windows line-endings.
+ foreach (explode("\n", str_replace("\r", '', $input)) as $line) {
+ $line = trim($line);
+ // [label]=[element].[class][.[class]][...] pattern expected.
+ if (!preg_match('@^.+= *[a-zA-Z0-9]+(\.[a-zA-Z0-9_ -]+)*$@', $line)) {
+ return FALSE;
+ }
+ list($label, $selector) = explode('=', $line, 2);
+ $classes = explode('.', $selector);
+ $element = array_shift($classes);
+
+ $style = array();
+ $style['name'] = trim($label);
+ $style['element'] = trim($element);
+ if (!empty($classes)) {
+ $style['attributes']['class'] = implode(' ', array_map('trim', $classes));
+ }
+ $set[] = $style;
+ }
+ return $set;
+}
+
+/**
+ * Build a JS settings array of native external plugins that need to be loaded separately.
+ */
+function wysiwyg_ckeditor_plugin_settings($editor, $profile, $plugins) {
+ $settings = array();
+ foreach ($plugins as $name => $plugin) {
+ // Register all plugins that need to be loaded.
+ if (!empty($plugin['load'])) {
+ $settings[$name] = array();
+ // Add path for native external plugins.
+ if (empty($plugin['internal']) && isset($plugin['path'])) {
+ $settings[$name]['path'] = base_path() . $plugin['path'] . '/';
+ }
+ // Force native internal plugins to use the standard path.
+ else {
+ $settings[$name]['path'] = base_path() . $editor['library path'] . '/plugins/' . $name . '/';
+ }
+ // CKEditor defaults to 'plugin.js' on its own when filename is not set.
+ if (!empty($plugin['filename'])) {
+ $settings[$name]['fileName'] = $plugin['filename'];
+ }
+ }
+ }
+ return $settings;
+}
+
+/**
+ * Build a JS settings array for Drupal plugins loaded via the proxy plugin.
+ */
+function wysiwyg_ckeditor_proxy_plugin_settings($editor, $profile, $plugins) {
+ $settings = array();
+ foreach ($plugins as $name => $plugin) {
+ // Populate required plugin settings.
+ $settings[$name] = $plugin['dialog settings'] + array(
+ 'title' => $plugin['title'],
+ 'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'],
+ 'iconTitle' => $plugin['icon title'],
+ // @todo These should only be set if the plugin defined them.
+ 'css' => base_path() . $plugin['css path'] . '/' . $plugin['css file'],
+ );
+ }
+ return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_ckeditor_plugins($editor) {
+ $plugins = array(
+ 'default' => array(
+ 'buttons' => array(
+ 'Bold' => t('Bold'), 'Italic' => t('Italic'), 'Underline' => t('Underline'),
+ 'Strike' => t('Strike-through'),
+ 'JustifyLeft' => t('Align left'), 'JustifyCenter' => t('Align center'), 'JustifyRight' => t('Align right'), 'JustifyBlock' => t('Justify'),
+ 'BidiLtr' => t('Left-to-right'), 'BidiRtl' => t('Right-to-left'),
+ 'BulletedList' => t('Bullet list'), 'NumberedList' => t('Numbered list'),
+ 'Outdent' => t('Outdent'), 'Indent' => t('Indent'),
+ 'Undo' => t('Undo'), 'Redo' => t('Redo'),
+ 'Link' => t('Link'), 'Unlink' => t('Unlink'), 'Anchor' => t('Anchor'),
+ 'Image' => t('Image'),
+ 'TextColor' => t('Forecolor'), 'BGColor' => t('Backcolor'),
+ 'Superscript' => t('Superscript'), 'Subscript' => t('Subscript'),
+ 'Blockquote' => t('Blockquote'), 'Source' => t('Source code'),
+ 'HorizontalRule' => t('Horizontal rule'),
+ 'Cut' => t('Cut'), 'Copy' => t('Copy'), 'Paste' => t('Paste'),
+ 'PasteText' => t('Paste Text'), 'PasteFromWord' => t('Paste from Word'),
+ 'ShowBlocks' => t('Show blocks'),
+ 'RemoveFormat' => t('Remove format'),
+ 'SpecialChar' => t('Character map'),
+ 'Format' => t('HTML block format'), 'Font' => t('Font'), 'FontSize' => t('Font size'), 'Styles' => t('Font style'),
+ 'Table' => t('Table'),
+ 'SelectAll' => t('Select all'), 'Find' => t('Search'), 'Replace' => t('Replace'),
+ 'Flash' => t('Flash'), 'Smiley' => t('Smiley'),
+ 'CreateDiv' => t('Div container'),
+ 'Iframe' => t('iFrame'),
+ 'Maximize' => t('Maximize'),
+ 'SpellChecker' => t('Check spelling'), 'Scayt' => t('Check spelling as you type'),
+ 'About' => t('About'),
+ ),
+ 'internal' => TRUE,
+ ),
+ );
+
+ if (version_compare($editor['installed version'], '3.1.0.4885', '<')) {
+ unset($plugins['default']['buttons']['CreateDiv']);
+ }
+ if (version_compare($editor['installed version'], '3.4.0.5808', '<')) {
+ unset($plugins['default']['buttons']['BidiLtr']);
+ unset($plugins['default']['buttons']['BidiRtl']);
+ }
+ if (version_compare($editor['installed version'], '3.5.0.6260', '<')) {
+ unset($plugins['default']['buttons']['Iframe']);
+ }
+ return $plugins;
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/css/openwysiwyg.css b/sites/all/modules/wysiwyg/editors/css/openwysiwyg.css
new file mode 100644
index 000000000..2fea7fb3d
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/css/openwysiwyg.css
@@ -0,0 +1,11 @@
+
+/**
+ * openWYSIWYG.
+ */
+table.tableTextareaEditor, table.tableTextareaEditor table {
+ margin: 0;
+ border-collapse: separate;
+}
+table.tableTextareaEditor td {
+ padding: 0;
+}
diff --git a/sites/all/modules/wysiwyg/editors/css/tinymce-2.css b/sites/all/modules/wysiwyg/editors/css/tinymce-2.css
new file mode 100644
index 000000000..4aa201d4a
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/css/tinymce-2.css
@@ -0,0 +1,27 @@
+
+/**
+ * TinyMCE 2.x
+ */
+table.mceEditor {
+ clear: left;
+}
+
+/**
+ * Align all buttons and separators in a single row, so they wrap into multiple
+ * rows if required.
+ */
+.mceToolbarTop a, .mceToolbarBottom a {
+ float: left;
+}
+.mceSeparatorLine {
+ float: left;
+ margin-top: 3px;
+}
+.mceSelectList {
+ float: left;
+ margin-bottom: 1px;
+}
+/* Place table plugin buttons into new row */
+#mce_editor_0_table, #mce_editor_1_table {
+ clear: left;
+}
diff --git a/sites/all/modules/wysiwyg/editors/css/tinymce-3.css b/sites/all/modules/wysiwyg/editors/css/tinymce-3.css
new file mode 100644
index 000000000..5d0ebe940
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/css/tinymce-3.css
@@ -0,0 +1,24 @@
+
+/**
+ * TinyMCE 3.x
+ */
+table.mceLayout {
+ clear: left;
+}
+
+/**
+ * Align all buttons and separators in a single row, so they wrap into multiple
+ * rows if required.
+ */
+.mceToolbar td {
+ display: inline;
+}
+.mceToolbar a,
+.mceSeparator {
+ float: left;
+}
+.mceListBox,
+.mceSplitButton {
+ float: left;
+ margin-bottom: 1px;
+}
diff --git a/sites/all/modules/wysiwyg/editors/epiceditor.inc b/sites/all/modules/wysiwyg/editors/epiceditor.inc
new file mode 100644
index 000000000..7c8976867
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/epiceditor.inc
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for EpicEditor.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_epiceditor_editor() {
+ $editor['epiceditor'] = array(
+ 'title' => 'EpicEditor',
+ 'vendor url' => 'http://oscargodson.github.com/EpicEditor',
+ 'download url' => 'http://oscargodson.github.com/EpicEditor/docs/downloads/EpicEditor-v0.1.1.zip',
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Minified',
+ 'files' => array('js/epiceditor.min.js'),
+ ),
+ 'src' => array(
+ 'title' => 'Source',
+ 'files' => array('js/epiceditor.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_epiceditor_version',
+ 'themes callback' => 'wysiwyg_epiceditor_themes',
+ 'settings callback' => 'wysiwyg_epiceditor_settings',
+ 'versions' => array(
+ '0.1.1' => array(
+ 'js files' => array('epiceditor.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_epiceditor_version($editor) {
+ $library = $editor['library path'] . '/js/epiceditor.js';
+ if (!file_exists($library)) {
+ return;
+ }
+ // @todo Do not load the entire file; use fgets() instead.
+ $library = file_get_contents($library, 'r');
+ $version = preg_match('%EpicEditor\.version = \'(.*)\'\;%', $library, $matches);
+ if (!isset($matches[1])) {
+ return;
+ }
+ return $matches[1];
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * An array of theme names. The first returned name should be the default
+ * theme name.
+ */
+function wysiwyg_epiceditor_themes($editor, $profile) {
+ return array('epic-dark', 'epic-light');
+ // @todo Use the preview themes somewhere.
+ //return array('preview-dark', 'github');
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_epiceditor_settings($editor, $config, $theme) {
+ $settings = array(
+ 'basePath' => base_path() . $editor['library path'],
+ 'clientSideStorage' => FALSE,
+ 'theme' => $theme,
+ //'preview_theme' => '',
+ );
+ return $settings;
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/fckeditor.inc b/sites/all/modules/wysiwyg/editors/fckeditor.inc
new file mode 100644
index 000000000..27bcb7b38
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/fckeditor.inc
@@ -0,0 +1,292 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for FCKeditor.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_fckeditor_editor() {
+ $editor['fckeditor'] = array(
+ 'title' => 'FCKeditor',
+ 'vendor url' => 'http://www.fckeditor.net',
+ 'download url' => 'http://www.fckeditor.net/download',
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Default',
+ 'files' => array('fckeditor.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_fckeditor_version',
+ 'themes callback' => 'wysiwyg_fckeditor_themes',
+ 'settings callback' => 'wysiwyg_fckeditor_settings',
+ 'plugin callback' => 'wysiwyg_fckeditor_plugins',
+ 'plugin settings callback' => 'wysiwyg_fckeditor_plugin_settings',
+ 'proxy plugin' => array(
+ 'drupal' => array(
+ 'load' => TRUE,
+ 'proxy' => TRUE,
+ ),
+ ),
+ 'proxy plugin settings callback' => 'wysiwyg_fckeditor_proxy_plugin_settings',
+ 'versions' => array(
+ '2.6' => array(
+ 'js files' => array('fckeditor-2.6.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_fckeditor_version($editor) {
+ $library = $editor['library path'] . '/fckeditor.js';
+ if (!file_exists($library)) {
+ return;
+ }
+ $library = fopen($library, 'r');
+ $max_lines = 100;
+ while ($max_lines && $line = fgets($library, 60)) {
+ if (preg_match('@^FCKeditor.prototype.Version\s*= \'([\d\.]+)@', $line, $version)) {
+ fclose($library);
+ return $version[1];
+ }
+ $max_lines--;
+ }
+ fclose($library);
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * An array of theme names. The first returned name should be the default
+ * theme name.
+ */
+function wysiwyg_fckeditor_themes($editor, $profile) {
+ return array('default', 'office2003', 'silver');
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_fckeditor_settings($editor, $config, $theme) {
+ $settings = array(
+ 'EditorPath' => base_path() . $editor['library path'] . '/',
+ 'SkinPath' => base_path() . $editor['library path'] . '/editor/skins/' . $theme . '/',
+ 'CustomConfigurationsPath' => base_path() . drupal_get_path('module', 'wysiwyg') . '/editors/js/fckeditor.config.js',
+ 'Width' => '100%',
+ 'Height' => 420,
+ 'LinkBrowser' => FALSE,
+ 'LinkUpload' => FALSE,
+ 'ImageBrowser' => FALSE,
+ 'ImageUpload' => FALSE,
+ 'FlashBrowser' => FALSE,
+ 'FlashUpload' => FALSE,
+ // By default, FCKeditor converts most characters into HTML entities. Since
+ // it does not support a custom definition, but Drupal supports Unicode, we
+ // disable at least the additional character sets. FCKeditor always converts
+ // XML default characters '&', '<', '>'.
+ // @todo Check whether completely disabling ProcessHTMLEntities is an option.
+ 'IncludeLatinEntities' => FALSE,
+ 'IncludeGreekEntities' => FALSE,
+ );
+ if (isset($config['block_formats'])) {
+ $settings['FontFormats'] = strtr($config['block_formats'], array(',' => ';'));
+ }
+ if (isset($config['apply_source_formatting'])) {
+ $settings['FormatOutput'] = $settings['FormatSource'] = $config['apply_source_formatting'];
+ }
+ if (isset($config['paste_auto_cleanup_on_paste'])) {
+ $settings['AutoDetectPasteFromWord'] = $config['paste_auto_cleanup_on_paste'];
+ }
+
+ if (isset($config['css_setting'])) {
+ if ($config['css_setting'] == 'theme') {
+ $settings['EditorAreaCSS'] = implode(',', wysiwyg_get_css());
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['EditorAreaCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))));
+ }
+ }
+
+ // Use our custom toolbar set.
+ $settings['ToolbarSet'] = 'Wysiwyg';
+ // Populate our custom toolbar set for fckeditor.config.js.
+ $settings['buttons'] = array();
+ if (!empty($config['buttons'])) {
+ $plugins = wysiwyg_get_plugins($editor['name']);
+ foreach ($config['buttons'] as $plugin => $buttons) {
+ foreach ($buttons as $button => $enabled) {
+ // Iterate separately over buttons and extensions properties.
+ foreach (array('buttons', 'extensions') as $type) {
+ // Skip unavailable plugins.
+ if (!isset($plugins[$plugin][$type][$button])) {
+ continue;
+ }
+ // Add buttons.
+ if ($type == 'buttons') {
+ $settings['buttons'][] = $button;
+ }
+ // Allow plugins to add or override global configuration settings.
+ if (!empty($plugins[$plugin]['options'])) {
+ $settings = array_merge($settings, $plugins[$plugin]['options']);
+ }
+ }
+ }
+ }
+ }
+ // For now, all buttons are placed into one row.
+ $settings['buttons'] = array($settings['buttons']);
+
+ return $settings;
+}
+
+/**
+ * Build a JS settings array of native external plugins that need to be loaded separately.
+ */
+function wysiwyg_fckeditor_plugin_settings($editor, $profile, $plugins) {
+ $settings = array();
+ foreach ($plugins as $name => $plugin) {
+ // Register all plugins that need to be loaded.
+ if (!empty($plugin['load'])) {
+ $settings[$name] = array();
+ // Add path for native external plugins; internal ones do not need a path.
+ if (empty($plugin['internal']) && isset($plugin['path'])) {
+ // All native FCKeditor plugins use the filename fckplugin.js.
+ $settings[$name]['path'] = base_path() . $plugin['path'] . '/';
+ }
+ if (!empty($plugin['languages'])) {
+ $settings[$name]['languages'] = $plugin['languages'];
+ }
+ }
+ }
+ return $settings;
+}
+
+/**
+ * Build a JS settings array for Drupal plugins loaded via the proxy plugin.
+ */
+function wysiwyg_fckeditor_proxy_plugin_settings($editor, $profile, $plugins) {
+ $settings = array();
+ foreach ($plugins as $name => $plugin) {
+ // Populate required plugin settings.
+ $settings[$name] = $plugin['dialog settings'] + array(
+ 'title' => $plugin['title'],
+ 'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'],
+ 'iconTitle' => $plugin['icon title'],
+ // @todo These should only be set if the plugin defined them.
+ 'css' => base_path() . $plugin['css path'] . '/' . $plugin['css file'],
+ );
+ }
+ return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_fckeditor_plugins($editor) {
+ $plugins = array(
+ 'default' => array(
+ 'buttons' => array(
+ 'Bold' => t('Bold'), 'Italic' => t('Italic'), 'Underline' => t('Underline'),
+ 'StrikeThrough' => t('Strike-through'),
+ 'JustifyLeft' => t('Align left'), 'JustifyCenter' => t('Align center'), 'JustifyRight' => t('Align right'), 'JustifyFull' => t('Justify'),
+ 'UnorderedList' => t('Bullet list'), 'OrderedList' => t('Numbered list'),
+ 'Outdent' => t('Outdent'), 'Indent' => t('Indent'),
+ 'Undo' => t('Undo'), 'Redo' => t('Redo'),
+ 'Link' => t('Link'), 'Unlink' => t('Unlink'), 'Anchor' => t('Anchor'),
+ 'Image' => t('Image'),
+ 'TextColor' => t('Forecolor'), 'BGColor' => t('Backcolor'),
+ 'Superscript' => t('Superscript'), 'Subscript' => t('Subscript'),
+ 'Blockquote' => t('Blockquote'), 'Source' => t('Source code'),
+ 'Rule' => t('Horizontal rule'),
+ 'Cut' => t('Cut'), 'Copy' => t('Copy'), 'Paste' => t('Paste'),
+ 'PasteText' => t('Paste Text'), 'PasteWord' => t('Paste from Word'),
+ 'ShowBlocks' => t('Show blocks'),
+ 'RemoveFormat' => t('Remove format'),
+ 'SpecialChar' => t('Character map'),
+ 'About' => t('About'),
+ 'FontFormat' => t('HTML block format'), 'FontName' => t('Font'), 'FontSize' => t('Font size'), 'Style' => t('Font style'),
+ 'Table' => t('Table'),
+ 'Find' => t('Search'), 'Replace' => t('Replace'), 'SelectAll' => t('Select all'),
+ 'CreateDiv' => t('Create DIV container'),
+ 'Flash' => t('Flash'), 'Smiley' => t('Smiley'),
+ 'FitWindow' => t('FitWindow'),
+ 'SpellCheck' => t('Check spelling'),
+ ),
+ 'internal' => TRUE,
+ ),
+ 'autogrow' => array(
+ 'path' => $editor['library path'] . '/editor/plugins',
+ 'extensions' => array(
+ 'autogrow' => t('Autogrow'),
+ ),
+ 'options' => array(
+ 'AutoGrowMax' => 800,
+ ),
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'bbcode' => array(
+ 'path' => $editor['library path'] . '/editor/plugins',
+ 'extensions' => array(
+ 'bbcode' => t('BBCode'),
+ ),
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'dragresizetable' => array(
+ 'path' => $editor['library path'] . '/editor/plugins',
+ 'extensions' => array(
+ 'dragresizetable' => t('Table drag/resize'),
+ ),
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'tablecommands' => array(
+ 'path' => $editor['library path'] . '/editor/plugins',
+ 'buttons' => array(
+ 'TableCellProp' => t('Table: Cell properties'),
+ 'TableInsertRowAfter' => t('Table: Insert row after'),
+ 'TableInsertColumnAfter' => t('Table: Insert column after'),
+ 'TableInsertCellAfter' => t('Table: Insert cell after'),
+ 'TableDeleteRows' => t('Table: Delete rows'),
+ 'TableDeleteColumns' => t('Table: Delete columns'),
+ 'TableDeleteCells' => t('Table: Delete cells'),
+ 'TableMergeCells' => t('Table: Merge cells'),
+ 'TableHorizontalSplitCell' => t('Table: Horizontal split cell'),
+ ),
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ );
+ return $plugins;
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/js/ckeditor-3.0.js b/sites/all/modules/wysiwyg/editors/js/ckeditor-3.0.js
new file mode 100644
index 000000000..f28892832
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/ckeditor-3.0.js
@@ -0,0 +1,248 @@
+(function($) {
+
+Drupal.wysiwyg.editor.init.ckeditor = function(settings) {
+ // Plugins must only be loaded once. Only the settings from the first format
+ // will be used but they're identical anyway.
+ var registeredPlugins = {};
+ for (var format in settings) {
+ if (Drupal.settings.wysiwyg.plugins[format]) {
+ // Register native external plugins.
+ // Array syntax required; 'native' is a predefined token in JavaScript.
+ for (var pluginName in Drupal.settings.wysiwyg.plugins[format]['native']) {
+ if (!registeredPlugins[pluginName]) {
+ var plugin = Drupal.settings.wysiwyg.plugins[format]['native'][pluginName];
+ CKEDITOR.plugins.addExternal(pluginName, plugin.path, plugin.fileName);
+ registeredPlugins[pluginName] = true;
+ }
+ }
+ // Register Drupal plugins.
+ for (var pluginName in Drupal.settings.wysiwyg.plugins[format].drupal) {
+ if (!registeredPlugins[pluginName]) {
+ Drupal.wysiwyg.editor.instance.ckeditor.addPlugin(pluginName, Drupal.settings.wysiwyg.plugins[format].drupal[pluginName], Drupal.settings.wysiwyg.plugins.drupal[pluginName]);
+ registeredPlugins[pluginName] = true;
+ }
+ }
+ }
+ // Register Font styles (versions 3.2.1 and above).
+ if (Drupal.settings.wysiwyg.configs.ckeditor[format].stylesSet) {
+ CKEDITOR.stylesSet.add(format, Drupal.settings.wysiwyg.configs.ckeditor[format].stylesSet);
+ }
+ }
+};
+
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.ckeditor = function(context, params, settings) {
+ // Apply editor instance settings.
+ CKEDITOR.config.customConfig = '';
+
+ var $drupalToolbar = $('#toolbar', Drupal.overlayChild ? window.parent.document : document);
+
+ settings.on = {
+ instanceReady: function(ev) {
+ var editor = ev.editor;
+ // Get a list of block, list and table tags from CKEditor's XHTML DTD.
+ // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Output_Formatting.
+ var dtd = CKEDITOR.dtd;
+ var tags = CKEDITOR.tools.extend({}, dtd.$block, dtd.$listItem, dtd.$tableContent);
+ // Set source formatting rules for each listed tag except <pre>.
+ // Linebreaks can be inserted before or after opening and closing tags.
+ if (settings.apply_source_formatting) {
+ // Mimic FCKeditor output, by breaking lines between tags.
+ for (var tag in tags) {
+ if (tag == 'pre') {
+ continue;
+ }
+ this.dataProcessor.writer.setRules(tag, {
+ indent: true,
+ breakBeforeOpen: true,
+ breakAfterOpen: false,
+ breakBeforeClose: false,
+ breakAfterClose: true
+ });
+ }
+ }
+ else {
+ // CKEditor adds default formatting to <br>, so we want to remove that
+ // here too.
+ tags.br = 1;
+ // No indents or linebreaks;
+ for (var tag in tags) {
+ if (tag == 'pre') {
+ continue;
+ }
+ this.dataProcessor.writer.setRules(tag, {
+ indent: false,
+ breakBeforeOpen: false,
+ breakAfterOpen: false,
+ breakBeforeClose: false,
+ breakAfterClose: false
+ });
+ }
+ }
+ },
+
+ pluginsLoaded: function(ev) {
+ // Override the conversion methods to let Drupal plugins modify the data.
+ var editor = ev.editor;
+ if (editor.dataProcessor && Drupal.settings.wysiwyg.plugins[params.format]) {
+ editor.dataProcessor.toHtml = CKEDITOR.tools.override(editor.dataProcessor.toHtml, function(originalToHtml) {
+ // Convert raw data for display in WYSIWYG mode.
+ return function(data, fixForBody) {
+ for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
+ if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+ data = Drupal.wysiwyg.plugins[plugin].attach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], editor.name);
+ data = Drupal.wysiwyg.instances[params.field].prepareContent(data);
+ }
+ }
+ return originalToHtml.call(this, data, fixForBody);
+ };
+ });
+ editor.dataProcessor.toDataFormat = CKEDITOR.tools.override(editor.dataProcessor.toDataFormat, function(originalToDataFormat) {
+ // Convert WYSIWYG mode content to raw data.
+ return function(data, fixForBody) {
+ data = originalToDataFormat.call(this, data, fixForBody);
+ for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
+ if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+ data = Drupal.wysiwyg.plugins[plugin].detach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], editor.name);
+ }
+ }
+ return data;
+ };
+ });
+ }
+ },
+
+ selectionChange: function (event) {
+ var pluginSettings = Drupal.settings.wysiwyg.plugins[params.format];
+ if (pluginSettings && pluginSettings.drupal) {
+ $.each(pluginSettings.drupal, function (name) {
+ var plugin = Drupal.wysiwyg.plugins[name];
+ if ($.isFunction(plugin.isNode)) {
+ var node = event.data.selection.getSelectedElement();
+ var state = plugin.isNode(node ? node.$ : null) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
+ event.editor.getCommand(name).setState(state);
+ }
+ });
+ }
+ },
+
+ focus: function(ev) {
+ Drupal.wysiwyg.activeId = ev.editor.name;
+ },
+
+ afterCommandExec: function(ev) {
+ // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode.
+ if (ev.data.name != 'maximize') {
+ return;
+ }
+ if (ev.data.command.state == CKEDITOR.TRISTATE_ON) {
+ $drupalToolbar.hide();
+ }
+ else {
+ $drupalToolbar.show();
+ }
+ }
+ };
+
+ // Attach editor.
+ CKEDITOR.replace(params.field, settings);
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * @todo 3.x: editor.prototype.getInstances() should always return an array
+ * containing all instances or the passed in params.field instance, but
+ * always return an array to simplify all detach functions.
+ */
+Drupal.wysiwyg.editor.detach.ckeditor = function (context, params, trigger) {
+ var method = (trigger == 'serialize') ? 'updateElement' : 'destroy';
+ if (typeof params != 'undefined') {
+ var instance = CKEDITOR.instances[params.field];
+ if (instance) {
+ instance[method]();
+ }
+ }
+ else {
+ for (var instanceName in CKEDITOR.instances) {
+ if (CKEDITOR.instances.hasOwnProperty(instanceName)) {
+ CKEDITOR.instances[instanceName][method]();
+ }
+ }
+ }
+};
+
+Drupal.wysiwyg.editor.instance.ckeditor = {
+ addPlugin: function(pluginName, settings, pluginSettings) {
+ CKEDITOR.plugins.add(pluginName, {
+ // Wrap Drupal plugin in a proxy pluygin.
+ init: function(editor) {
+ if (settings.css) {
+ editor.on('mode', function(ev) {
+ if (ev.editor.mode == 'wysiwyg') {
+ // Inject CSS files directly into the editing area head tag.
+ $('head', $('#cke_contents_' + ev.editor.name + ' iframe').eq(0).contents()).append('<link rel="stylesheet" href="' + settings.css + '" type="text/css" >');
+ }
+ });
+ }
+ if (typeof Drupal.wysiwyg.plugins[pluginName].invoke == 'function') {
+ var pluginCommand = {
+ exec: function (editor) {
+ var data = { format: 'html', node: null, content: '' };
+ var selection = editor.getSelection();
+ if (selection) {
+ data.node = selection.getSelectedElement();
+ if (data.node) {
+ data.node = data.node.$;
+ }
+ if (selection.getType() == CKEDITOR.SELECTION_TEXT) {
+ if (CKEDITOR.env.ie) {
+ data.content = selection.getNative().createRange().text;
+ }
+ else {
+ data.content = selection.getNative().toString();
+ }
+ }
+ else if (data.node) {
+ // content is supposed to contain the "outerHTML".
+ data.content = data.node.parentNode.innerHTML;
+ }
+ }
+ Drupal.wysiwyg.plugins[pluginName].invoke(data, pluginSettings, editor.name);
+ }
+ };
+ editor.addCommand(pluginName, pluginCommand);
+ }
+ editor.ui.addButton(pluginName, {
+ label: settings.iconTitle,
+ command: pluginName,
+ icon: settings.icon
+ });
+
+ // @todo Add button state handling.
+ }
+ });
+ },
+ prepareContent: function(content) {
+ // @todo Don't know if we need this yet.
+ return content;
+ },
+
+ insert: function(content) {
+ content = this.prepareContent(content);
+ CKEDITOR.instances[this.field].insertHtml(content);
+ },
+
+ setContent: function (content) {
+ CKEDITOR.instances[this.field].setData(content);
+ },
+
+ getContent: function () {
+ return CKEDITOR.instances[this.field].getData();
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/epiceditor.js b/sites/all/modules/wysiwyg/editors/js/epiceditor.js
new file mode 100644
index 000000000..03c48017c
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/epiceditor.js
@@ -0,0 +1,38 @@
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.epiceditor = function (context, params, settings) {
+ var $target = $('#' + params.field);
+ var containerId = params.field + '-epiceditor';
+ var defaultContent = $target.val();
+ $target.hide().after('<div id="' + containerId + '" />');
+
+ settings.container = containerId;
+ settings.file = {
+ defaultContent: defaultContent
+ };
+ settings.theme = {
+ preview: '/themes/preview/preview-dark.css',
+ editor: '/themes/editor/' + settings.theme + '.css'
+ }
+ var editor = new EpicEditor(settings).load();
+ $target.data('epiceditor', editor);
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.epiceditor = function (context, params, trigger) {
+ var $target = $('#' + params.field);
+ var editor = $target.data('epiceditor');
+
+ $target.val(editor.exportFile());
+
+ editor.unload(function () {
+ $target.show();
+ });
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/fckeditor-2.6.js b/sites/all/modules/wysiwyg/editors/js/fckeditor-2.6.js
new file mode 100644
index 000000000..fd915e3fe
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/fckeditor-2.6.js
@@ -0,0 +1,196 @@
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.fckeditor = function(context, params, settings) {
+ var FCKinstance = new FCKeditor(params.field, settings.Width, settings.Height, settings.ToolbarSet);
+ // Apply editor instance settings.
+ FCKinstance.BasePath = settings.EditorPath;
+ FCKinstance.Config.wysiwygFormat = params.format;
+ FCKinstance.Config.CustomConfigurationsPath = settings.CustomConfigurationsPath;
+
+ // Load Drupal plugins and apply format specific settings.
+ // @see fckeditor.config.js
+ // @see Drupal.wysiwyg.editor.instance.fckeditor.init()
+
+ // Attach editor.
+ FCKinstance.ReplaceTextarea();
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.fckeditor = function (context, params, trigger) {
+ var instances = [];
+ if (typeof params != 'undefined' && typeof FCKeditorAPI != 'undefined') {
+ var instance = FCKeditorAPI.GetInstance(params.field);
+ if (instance) {
+ instances[params.field] = instance;
+ }
+ }
+ else {
+ instances = FCKeditorAPI.__Instances;
+ }
+
+ for (var instanceName in instances) {
+ var instance = instances[instanceName];
+ instance.UpdateLinkedField();
+ if (trigger == 'serialize') {
+ // The editor is not being removed from the DOM, so updating the linked
+ // field is the only action necessary.
+ continue;
+ }
+ // Since we already detach the editor and update the textarea, the submit
+ // event handler needs to be removed to prevent data loss (in IE).
+ // FCKeditor uses 2 nested iFrames; instance.EditingArea.Window is the
+ // deepest. Its parent is the iFrame containing the editor.
+ var instanceScope = instance.EditingArea.Window.parent;
+ instanceScope.FCKTools.RemoveEventListener(instance.GetParentForm(), 'submit', instance.UpdateLinkedField);
+ // Run cleanups before forcing an unload of the iFrames or IE crashes.
+ // This also deletes the instance from the FCKeditorAPI.__Instances array.
+ instanceScope.FCKTools.RemoveEventListener(instanceScope, 'unload', instanceScope.FCKeditorAPI_Cleanup);
+ instanceScope.FCKTools.RemoveEventListener(instanceScope, 'beforeunload', instanceScope.FCKeditorAPI_ConfirmCleanup);
+ if (jQuery.isFunction(instanceScope.FCKIECleanup_Cleanup)) {
+ instanceScope.FCKIECleanup_Cleanup();
+ }
+ instanceScope.FCKeditorAPI_ConfirmCleanup();
+ instanceScope.FCKeditorAPI_Cleanup();
+ // Remove the editor elements.
+ $('#' + instanceName + '___Config').remove();
+ $('#' + instanceName + '___Frame').remove();
+ $('#' + instanceName).show();
+ }
+};
+
+Drupal.wysiwyg.editor.instance.fckeditor = {
+ init: function(instance) {
+ // Track which editor instance is active.
+ instance.FCK.Events.AttachEvent('OnFocus', function(editorInstance) {
+ Drupal.wysiwyg.activeId = editorInstance.Name;
+ });
+
+ // Create a custom data processor to wrap the default one and allow Drupal
+ // plugins modify the editor contents.
+ var wysiwygDataProcessor = function() {};
+ wysiwygDataProcessor.prototype = new instance.FCKDataProcessor();
+ // Attach: Convert text into HTML.
+ wysiwygDataProcessor.prototype.ConvertToHtml = function(data) {
+ // Called from SetData() with stripped comments/scripts, revert those
+ // manipulations and attach Drupal plugins.
+ var data = instance.FCKConfig.ProtectedSource.Revert(data);
+ if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat] && Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
+ for (var plugin in Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
+ if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+ data = Drupal.wysiwyg.plugins[plugin].attach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], instance.FCK.Name);
+ data = Drupal.wysiwyg.editor.instance.fckeditor.prepareContent(data);
+ }
+ }
+ }
+ // Re-protect the source and use the original data processor to convert it
+ // into XHTML.
+ data = instance.FCKConfig.ProtectedSource.Protect(data);
+ return instance.FCKDataProcessor.prototype.ConvertToHtml.call(this, data);
+ };
+ // Detach: Convert HTML into text.
+ wysiwygDataProcessor.prototype.ConvertToDataFormat = function(rootNode, excludeRoot, ignoreIfEmptyParagraph, format) {
+ // Called from GetData(), convert the content's DOM into a XHTML string
+ // using the original data processor and detach Drupal plugins.
+ var data = instance.FCKDataProcessor.prototype.ConvertToDataFormat.call(this, rootNode, excludeRoot, ignoreIfEmptyParagraph, format);
+ if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat] && Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
+ for (var plugin in Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
+ if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+ data = Drupal.wysiwyg.plugins[plugin].detach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], instance.FCK.Name);
+ }
+ }
+ }
+ return data;
+ };
+ instance.FCK.DataProcessor = new wysiwygDataProcessor();
+ },
+
+ addPlugin: function(plugin, settings, pluginSettings, instance) {
+ if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
+ return;
+ }
+
+ if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal[plugin].css) {
+ instance.FCKConfig.EditorAreaCSS += ',' + Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal[plugin].css;
+ }
+
+ // @see fckcommands.js, fck_othercommands.js, fckpastewordcommand.js
+ instance.FCKCommands.RegisterCommand(plugin, {
+ // Invoke the plugin's button.
+ Execute: function () {
+ if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
+ var data = { format: 'html', node: instance.FCKSelection.GetParentElement() };
+ // @todo This is NOT the same as data.node.
+ data.content = data.node.innerHTML;
+ Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instance.FCK.Name);
+ }
+ },
+
+ // isNode: Return whether the plugin button should be enabled for the
+ // current selection.
+ // @see FCKUnlinkCommand.prototype.GetState()
+ GetState: function () {
+ // Always disabled if not in WYSIWYG mode.
+ if (instance.FCK.EditMode != FCK_EDITMODE_WYSIWYG) {
+ return FCK_TRISTATE_DISABLED;
+ }
+ var state = instance.FCK.GetNamedCommandState(this.Name);
+ // FCKeditor sets the wrong state in WebKit browsers.
+ if (!$.support.queryCommandEnabled && state == FCK_TRISTATE_DISABLED) {
+ state = FCK_TRISTATE_OFF;
+ }
+ if (state == FCK_TRISTATE_OFF && instance.FCK.EditMode == FCK_EDITMODE_WYSIWYG) {
+ if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+ var node = instance.FCKSelection.GetSelectedElement();
+ state = Drupal.wysiwyg.plugins[plugin].isNode(node) ? FCK_TRISTATE_ON : FCK_TRISTATE_OFF;
+ }
+ }
+ return state;
+ },
+
+ /**
+ * Return information about the plugin as a name/value array.
+ */
+ Name: plugin
+ });
+
+ // Register the plugin button.
+ // Arguments: commandName, label, tooltip, style, sourceView, contextSensitive, icon.
+ instance.FCKToolbarItems.RegisterItem(plugin, new instance.FCKToolbarButton(plugin, settings.iconTitle, settings.iconTitle, null, false, true, settings.icon));
+ },
+
+ openDialog: function(dialog, params) {
+ // @todo Implement open dialog.
+ },
+
+ closeDialog: function(dialog) {
+ // @todo Implement close dialog.
+ },
+
+ prepareContent: function(content) {
+ // @todo Not needed for FCKeditor?
+ return content;
+ },
+
+ insert: function(content) {
+ var instance = FCKeditorAPI.GetInstance(this.field);
+ // @see FCK.InsertHtml(), FCK.InsertElement()
+ instance.InsertHtml(content);
+ },
+
+ getContent: function () {
+ var instance = FCKeditorAPI.GetInstance(this.field);
+ return instance.GetData();
+ },
+
+ setContent: function (content) {
+ var instance = FCKeditorAPI.GetInstance(this.field);
+ instance.SetHTML(content);
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/fckeditor.config.js b/sites/all/modules/wysiwyg/editors/js/fckeditor.config.js
new file mode 100644
index 000000000..42efc3226
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/fckeditor.config.js
@@ -0,0 +1,86 @@
+
+Drupal = window.parent.Drupal;
+
+/**
+ * Fetch and provide original editor settings as local variable.
+ *
+ * FCKeditor does not support to pass complex variable types to the editor.
+ * Instance settings passed to FCKinstance.Config are temporarily stored in
+ * FCKConfig.PageConfig.
+ */
+var wysiwygFormat = FCKConfig.PageConfig.wysiwygFormat;
+var wysiwygSettings = Drupal.settings.wysiwyg.configs.fckeditor[wysiwygFormat];
+var pluginSettings = (Drupal.settings.wysiwyg.plugins[wysiwygFormat] ? Drupal.settings.wysiwyg.plugins[wysiwygFormat] : { 'native': {}, 'drupal': {} });
+
+/**
+ * Apply format-specific settings.
+ */
+for (var setting in wysiwygSettings) {
+ if (setting == 'buttons') {
+ // Apply custom Wysiwyg toolbar for this format.
+ // FCKConfig.ToolbarSets['Wysiwyg'] = wysiwygSettings.buttons;
+
+ // Temporarily stack buttons into multiple button groups and remove
+ // separators until #277954 is solved.
+ FCKConfig.ToolbarSets['Wysiwyg'] = [];
+ for (var i = 0; i < wysiwygSettings.buttons[0].length; i++) {
+ FCKConfig.ToolbarSets['Wysiwyg'].push([wysiwygSettings.buttons[0][i]]);
+ }
+ FCKTools.AppendStyleSheet(document, '#xToolbar .TB_Start { display:none; }');
+ // Set valid height of select element in silver and office2003 skins.
+ if (FCKConfig.SkinPath.match(/\/office2003\/$/)) {
+ FCKTools.AppendStyleSheet(document, '#xToolbar .SC_FieldCaption { height: 24px; } #xToolbar .TB_End { display: none; }');
+ }
+ else if (FCKConfig.SkinPath.match(/\/silver\/$/)) {
+ FCKTools.AppendStyleSheet(document, '#xToolbar .SC_FieldCaption { height: 27px; }');
+ }
+ }
+ else {
+ FCKConfig[setting] = wysiwygSettings[setting];
+ }
+}
+
+// Fix Drupal toolbar obscuring editor toolbar in fullscreen mode.
+var oldFitWindowExecute = FCKFitWindow.prototype.Execute;
+var $drupalToolbar = window.parent.jQuery('#toolbar', Drupal.overlayChild ? window.parent.window.parent.document : window.parent.document);
+FCKFitWindow.prototype.Execute = function() {
+ oldFitWindowExecute.apply(this, arguments);
+ if (this.IsMaximized) {
+ $drupalToolbar.hide();
+ }
+ else {
+ $drupalToolbar.show();
+ }
+}
+
+/**
+ * Initialize this editor instance.
+ */
+Drupal.wysiwyg.editor.instance.fckeditor.init(window);
+
+/**
+ * Register native plugins for this input format.
+ *
+ * Parameters to Plugins.Add are:
+ * - Plugin name.
+ * - Languages the plugin is available in.
+ * - Location of the plugin folder; <plugin_name>/fckplugin.js is appended.
+ */
+for (var plugin in pluginSettings['native']) {
+ // Languages and path may be undefined for internal plugins.
+ FCKConfig.Plugins.Add(plugin, pluginSettings['native'][plugin].languages, pluginSettings['native'][plugin].path);
+}
+
+/**
+ * Register Drupal plugins for this input format.
+ *
+ * Parameters to addPlugin() are:
+ * - Plugin name.
+ * - Format specific plugin settings.
+ * - General plugin settings.
+ * - A reference to this window so the plugin setup can access FCKConfig.
+ */
+for (var plugin in pluginSettings.drupal) {
+ Drupal.wysiwyg.editor.instance.fckeditor.addPlugin(plugin, pluginSettings.drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin], window);
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/js/jwysiwyg.js b/sites/all/modules/wysiwyg/editors/js/jwysiwyg.js
new file mode 100644
index 000000000..d3e7490f7
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/jwysiwyg.js
@@ -0,0 +1,43 @@
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.jwysiwyg = function(context, params, settings) {
+ // Attach editor.
+ $('#' + params.field).wysiwyg();
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.jwysiwyg = function (context, params, trigger) {
+ var $field = $('#' + params.field);
+ var editor = $field.data('wysiwyg');
+ if (typeof editor != 'undefined') {
+ editor.saveContent();
+ if (trigger != 'serialize') {
+ editor.element.remove();
+ }
+ }
+ $field.removeData('wysiwyg');
+ if (trigger != 'serialize') {
+ $field.show();
+ }
+};
+
+Drupal.wysiwyg.editor.instance.jwysiwyg = {
+ insert: function (content) {
+ $('#' + this.field).wysiwyg('insertHtml', content);
+ },
+
+ setContent: function (content) {
+ $('#' + this.field).wysiwyg('setContent', content);
+ },
+
+ getContent: function () {
+ return $('#' + this.field).wysiwyg('getContent');
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/markitup.js b/sites/all/modules/wysiwyg/editors/js/markitup.js
new file mode 100644
index 000000000..00e10b978
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/markitup.js
@@ -0,0 +1,46 @@
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.markitup = function(context, params, settings) {
+ $('#' + params.field, context).markItUp(settings);
+
+ // Adjust CSS for editor buttons.
+ $.each(settings.markupSet, function (button) {
+ $('.' + settings.nameSpace + ' .' + this.className + ' a')
+ .css({ backgroundImage: 'url(' + settings.root + 'sets/default/images/' + button + '.png' + ')' })
+ .parents('li').css({ backgroundImage: 'none' });
+ });
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.markitup = function (context, params, trigger) {
+ if (trigger == 'serialize') {
+ return;
+ }
+ if (typeof params != 'undefined') {
+ $('#' + params.field, context).markItUpRemove();
+ }
+ else {
+ $('.markItUpEditor', context).markItUpRemove();
+ }
+};
+
+Drupal.wysiwyg.editor.instance.markitup = {
+ insert: function (content) {
+ $.markItUp({ replaceWith: content });
+ },
+
+ setContent: function (content) {
+ $('#' + this.field).val(content);
+ },
+
+ getContent: function () {
+ return $('#' + this.field).val();
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/nicedit.js b/sites/all/modules/wysiwyg/editors/js/nicedit.js
new file mode 100644
index 000000000..10b7809c5
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/nicedit.js
@@ -0,0 +1,115 @@
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.nicedit = function(context, params, settings) {
+ // Intercept and ignore submit handlers or they will revert changes made
+ // since the instance was removed. The handlers are anonymous and hidden out
+ // of scope in a closure so we can't unbind them. The same operations are
+ // performed when the instance is detached anyway.
+ var oldAddEvent = bkLib.addEvent;
+ bkLib.addEvent = function(obj, type, fn) {
+ if (type != 'submit') {
+ oldAddEvent(obj, type, fn);
+ }
+ }
+ // Attach editor.
+ var editor = new nicEditor(settings);
+ editor.panelInstance(params.field);
+ // The old addEvent() must be restored after creating a new instance, as
+ // plugins with dialogs use it to bind submit handlers to their forms.
+ bkLib.addEvent = oldAddEvent;
+ editor.addEvent('focus', function () {
+ Drupal.wysiwyg.activeId = params.field;
+ });
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * See Drupal.wysiwyg.editor.detach.none() for a full description of this hook.
+ */
+Drupal.wysiwyg.editor.detach.nicedit = function (context, params, trigger) {
+ if (typeof params != 'undefined') {
+ var instance = nicEditors.findEditor(params.field);
+ if (instance) {
+ if (trigger == 'serialize') {
+ instance.saveContent();
+ }
+ else {
+ instance.ne.removeInstance(params.field);
+ instance.ne.removePanel();
+ }
+ }
+ }
+ else {
+ for (var e in nicEditors.editors) {
+ // Save contents of all editors back into textareas.
+ var instances = nicEditors.editors[e].nicInstances;
+ for (var i = 0; i < instances.length; i++) {
+ if (trigger == 'serialize') {
+ instances[i].saveContent();
+ }
+ else {
+ instances[i].remove();
+ }
+ }
+ // Remove all editor instances.
+ if (trigger != 'serialize') {
+ nicEditors.editors[e].nicInstances = [];
+ }
+ }
+ }
+};
+
+/**
+ * Instance methods for nicEdit.
+ */
+Drupal.wysiwyg.editor.instance.nicedit = {
+ insert: function (content) {
+ var instance = nicEditors.findEditor(this.field);
+ var editingArea = instance.getElm();
+ var sel = instance.getSel();
+ // IE.
+ if (document.selection) {
+ editingArea.focus();
+ sel.createRange().pasteHTML(content);
+ }
+ else {
+ // Convert selection to a range.
+ var range;
+ // W3C compatible.
+ if (sel.getRangeAt) {
+ range = sel.getRangeAt(0);
+ }
+ // Safari.
+ else {
+ range = editingArea.ownerDocument.createRange();
+ range.setStart(sel.anchorNode, sel.anchorOffset);
+ range.setEnd(sel.focusNode, userSeletion.focusOffset);
+ }
+ // The code below doesn't work in IE, but it never gets here.
+ var fragment = editingArea.ownerDocument.createDocumentFragment();
+ // Fragments don't support innerHTML.
+ var wrapper = editingArea.ownerDocument.createElement('div');
+ wrapper.innerHTML = content;
+ while (wrapper.firstChild) {
+ fragment.appendChild(wrapper.firstChild);
+ }
+ range.deleteContents();
+ // Only fragment children are inserted.
+ range.insertNode(fragment);
+ }
+ },
+
+ setContent: function (content) {
+ nicEditors.findEditor(this.field).setContent(content);
+ },
+
+ getContent: function () {
+ return nicEditors.findEditor(this.field).getContent();
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/none.js b/sites/all/modules/wysiwyg/editors/js/none.js
new file mode 100644
index 000000000..762f7fb2b
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/none.js
@@ -0,0 +1,91 @@
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ *
+ * @param context
+ * A DOM element, supplied by Drupal.attachBehaviors().
+ * @param params
+ * An object containing input format parameters. Default parameters are:
+ * - editor: The internal editor name.
+ * - theme: The name/key of the editor theme/profile to use.
+ * - field: The CSS id of the target element.
+ * @param settings
+ * An object containing editor settings for all enabled editor themes.
+ */
+Drupal.wysiwyg.editor.attach.none = function(context, params, settings) {
+ if (params.resizable) {
+ var $wrapper = $('#' + params.field).parents('.form-textarea-wrapper:first');
+ $wrapper.addClass('resizable');
+ if (Drupal.behaviors.textarea) {
+ Drupal.behaviors.textarea.attach();
+ }
+ }
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * The editor syncs its contents back to the original field before its instance
+ * is removed.
+ *
+ * @param context
+ * A DOM element, supplied by Drupal.attachBehaviors().
+ * @param params
+ * (optional) An object containing input format parameters. If defined,
+ * only the editor instance in params.field should be detached. Otherwise,
+ * all editors should be detached and saved, so they can be submitted in
+ * AJAX/AHAH applications.
+ * @param trigger
+ * A string describing why the editor is being detached.
+ * Possible triggers are:
+ * - unload: (default) Another or no editor is about to take its place.
+ * - move: Currently expected to produce the same result as unload.
+ * - serialize: The form is about to be serialized before an AJAX request or
+ * a normal form submission. If possible, perform a quick detach and leave
+ * the editor's GUI elements in place to avoid flashes or scrolling issues.
+ * @see Drupal.detachBehaviors
+ */
+Drupal.wysiwyg.editor.detach.none = function (context, params, trigger) {
+ if (typeof params != 'undefined' && (trigger != 'serialize')) {
+ var $wrapper = $('#' + params.field).parents('.form-textarea-wrapper:first');
+ $wrapper.removeOnce('textarea').removeClass('.resizable-textarea')
+ .find('.grippie').remove();
+ }
+};
+
+/**
+ * Instance methods for plain text areas.
+ */
+Drupal.wysiwyg.editor.instance.none = {
+ insert: function(content) {
+ var editor = document.getElementById(this.field);
+
+ // IE support.
+ if (document.selection) {
+ editor.focus();
+ var sel = document.selection.createRange();
+ sel.text = content;
+ }
+ // Mozilla/Firefox/Netscape 7+ support.
+ else if (editor.selectionStart || editor.selectionStart == '0') {
+ var startPos = editor.selectionStart;
+ var endPos = editor.selectionEnd;
+ editor.value = editor.value.substring(0, startPos) + content + editor.value.substring(endPos, editor.value.length);
+ }
+ // Fallback, just add to the end of the content.
+ else {
+ editor.value += content;
+ }
+ },
+
+ setContent: function (content) {
+ $('#' + this.field).val(content);
+ },
+
+ getContent: function () {
+ return $('#' + this.field).val();
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/openwysiwyg.js b/sites/all/modules/wysiwyg/editors/js/openwysiwyg.js
new file mode 100644
index 000000000..a01e8a0b1
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/openwysiwyg.js
@@ -0,0 +1,141 @@
+
+// Backup $ and reset it to jQuery.
+Drupal.wysiwyg._openwysiwyg = $;
+$ = jQuery;
+
+// Wrap openWYSIWYG's methods to temporarily use its version of $.
+jQuery.each(WYSIWYG, function (key, value) {
+ if (jQuery.isFunction(value)) {
+ WYSIWYG[key] = function () {
+ var old$ = $;
+ $ = Drupal.wysiwyg._openwysiwyg;
+ var result = value.apply(this, arguments);
+ $ = old$;
+ return result;
+ };
+ }
+});
+
+// Override editor functions.
+WYSIWYG.getEditor = function (n) {
+ return Drupal.wysiwyg._openwysiwyg("wysiwyg" + n);
+};
+
+(function($) {
+
+// Fix Drupal toolbar obscuring editor toolbar in fullscreen mode.
+var oldMaximize = WYSIWYG.maximize;
+WYSIWYG.maximize = function (n) {
+var $drupalToolbar = $('#toolbar', Drupal.overlayChild ? window.parent.document : document);
+ oldMaximize.apply(this, arguments);
+ if (this.maximized[n]) {
+ $drupalToolbar.hide();
+ }
+ else {
+ $drupalToolbar.show();
+ }
+}
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.openwysiwyg = function(context, params, settings) {
+ // Initialize settings.
+ settings.ImagesDir = settings.path + 'images/';
+ settings.PopupsDir = settings.path + 'popups/';
+ settings.CSSFile = settings.path + 'styles/wysiwyg.css';
+ //settings.DropDowns = [];
+ var config = new WYSIWYG.Settings();
+ for (var setting in settings) {
+ config[setting] = settings[setting];
+ }
+ // Attach editor.
+ WYSIWYG.setSettings(params.field, config);
+ WYSIWYG_Core.includeCSS(WYSIWYG.config[params.field].CSSFile);
+ WYSIWYG._generate(params.field, config);
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.openwysiwyg = function (context, params, trigger) {
+ if (typeof params != 'undefined') {
+ var instance = WYSIWYG.config[params.field];
+ if (typeof instance != 'undefined') {
+ WYSIWYG.updateTextArea(params.field);
+ if (trigger != 'serialize') {
+ jQuery('#wysiwyg_div_' + params.field).remove();
+ delete instance;
+ }
+ }
+ if (trigger != 'serialize') {
+ jQuery('#' + params.field).show();
+ }
+ }
+ else {
+ jQuery.each(WYSIWYG.config, function(field) {
+ WYSIWYG.updateTextArea(field);
+ if (trigger != 'serialize') {
+ jQuery('#wysiwyg_div_' + field).remove();
+ delete this;
+ jQuery('#' + field).show();
+ }
+ });
+ }
+};
+
+/**
+ * Instance methods for openWYSIWYG.
+ */
+Drupal.wysiwyg.editor.instance.openwysiwyg = {
+ insert: function (content) {
+ // If IE has dropped focus content will be inserted at the top of the page.
+ $('#wysiwyg' + this.field).contents().find('body').focus();
+ WYSIWYG.insertHTML(content, this.field);
+ },
+
+ setContent: function (content) {
+ // Based on openWYSIWYG's _generate() method.
+ var doc = WYSIWYG.getEditorWindow(this.field).document;
+ if (WYSIWYG.config[this.field].ReplaceLineBreaks) {
+ content = content.replace(/\n\r|\n/ig, '<br />');
+ }
+ if (WYSIWYG.viewTextMode[this.field]) {
+ var html = document.createTextNode(content);
+ doc.body.innerHTML = '';
+ doc.body.appendChild(html);
+ }
+ else {
+ doc.open();
+ doc.write(content);
+ doc.close();
+ }
+ },
+
+ getContent: function () {
+ // Based on openWYSIWYG's updateTextarea() method.
+ var content = '';
+ var doc = WYSIWYG.getEditorWindow(this.field).document;
+ if (WYSIWYG.viewTextMode[this.field]) {
+ if (WYSIWYG_Core.isMSIE) {
+ content = doc.body.innerText;
+ }
+ else {
+ var range = doc.body.ownerDocument.createRange();
+ range.selectNodeContents(doc.body);
+ content = range.toString();
+ }
+ }
+ else {
+ content = doc.body.innerHTML;
+ }
+ content = WYSIWYG.stripURLPath(this.field, content);
+ content = WYSIWYG_Core.replaceRGBWithHexColor(content);
+ if (WYSIWYG.config[this.field].ReplaceLineBreaks) {
+ content = content.replace(/(\r\n)|(\n)/ig, '');
+ }
+ return content;
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/tinymce-2.js b/sites/all/modules/wysiwyg/editors/js/tinymce-2.js
new file mode 100644
index 000000000..61a60ade4
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/tinymce-2.js
@@ -0,0 +1,203 @@
+(function($) {
+
+/**
+ * Initialize editor instances.
+ *
+ * This function needs to be called before the page is fully loaded, as
+ * calling tinyMCE.init() after the page is loaded breaks IE6.
+ *
+ * @param editorSettings
+ * An object containing editor settings for each input format.
+ */
+Drupal.wysiwyg.editor.init.tinymce = function(settings) {
+ // Initialize editor configurations.
+ for (var format in settings) {
+ tinyMCE.init(settings[format]);
+ if (Drupal.settings.wysiwyg.plugins[format]) {
+ // Load native external plugins.
+ // Array syntax required; 'native' is a predefined token in JavaScript.
+ for (var plugin in Drupal.settings.wysiwyg.plugins[format]['native']) {
+ tinyMCE.loadPlugin(plugin, Drupal.settings.wysiwyg.plugins[format]['native'][plugin]);
+ }
+ // Load Drupal plugins.
+ for (var plugin in Drupal.settings.wysiwyg.plugins[format].drupal) {
+ Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins[format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]);
+ }
+ }
+ }
+};
+
+/**
+ * Attach this editor to a target element.
+ *
+ * See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) {
+ // Configure editor settings for this input format.
+ for (var setting in settings) {
+ tinyMCE.settings[setting] = settings[setting];
+ }
+
+ // Remove TinyMCE's internal mceItem class, which was incorrectly added to
+ // submitted content by Wysiwyg <2.1. TinyMCE only temporarily adds the class
+ // for placeholder elements. If preemptively set, the class prevents (native)
+ // editor plugins from gaining an active state, so we have to manually remove
+ // it prior to attaching the editor. This is done on the client-side instead
+ // of the server-side, as Wysiwyg has no way to figure out where content is
+ // stored, and the class only affects editing.
+ $field = $('#' + params.field);
+ $field.val($field.val().replace(/(<.+?\s+class=['"][\w\s]*?)\bmceItem\b([\w\s]*?['"].*?>)/ig, '$1$2'));
+
+ // Attach editor.
+ tinyMCE.execCommand('mceAddControl', true, params.field);
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.detach.tinymce = function (context, params, trigger) {
+ if (typeof params != 'undefined') {
+ tinyMCE.removeMCEControl(tinyMCE.getEditorId(params.field));
+ $('#' + params.field).removeAttr('style');
+ }
+// else if (tinyMCE.activeEditor) {
+// tinyMCE.triggerSave();
+// tinyMCE.activeEditor.remove();
+// }
+};
+
+Drupal.wysiwyg.editor.instance.tinymce = {
+ addPlugin: function(plugin, settings, pluginSettings) {
+ if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
+ return;
+ }
+ tinyMCE.addPlugin(plugin, {
+
+ // Register an editor command for this plugin, invoked by the plugin's button.
+ execCommand: function(editor_id, element, command, user_interface, value) {
+ switch (command) {
+ case plugin:
+ if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
+ var ed = tinyMCE.getInstanceById(editor_id);
+ var data = { format: 'html', node: ed.getFocusElement(), content: ed.getFocusElement() };
+ Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, ed.formTargetElementId);
+ return true;
+ }
+ }
+ // Pass to next handler in chain.
+ return false;
+ },
+
+ // Register the plugin button.
+ getControlHTML: function(control_name) {
+ switch (control_name) {
+ case plugin:
+ return tinyMCE.getButtonHTML(control_name, settings.iconTitle, settings.icon, plugin);
+ }
+ return '';
+ },
+
+ // Load custom CSS for editor contents on startup.
+ initInstance: function(ed) {
+ if (settings.css) {
+ tinyMCE.importCSS(ed.getDoc(), settings.css);
+ }
+ },
+
+ cleanup: function(type, content) {
+ switch (type) {
+ case 'insert_to_editor':
+ // Attach: Replace plain text with HTML representations.
+ if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+ content = Drupal.wysiwyg.plugins[plugin].attach(content, pluginSettings, tinyMCE.selectedInstance.editorId);
+ content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(content);
+ }
+ break;
+
+ case 'get_from_editor':
+ // Detach: Replace HTML representations with plain text.
+ if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+ content = Drupal.wysiwyg.plugins[plugin].detach(content, pluginSettings, tinyMCE.selectedInstance.editorId);
+ }
+ break;
+ }
+ // Pass through to next handler in chain
+ return content;
+ },
+
+ // isNode: Return whether the plugin button should be enabled for the
+ // current selection.
+ handleNodeChange: function(editor_id, node, undo_index, undo_levels, visual_aid, any_selection) {
+ if (node === null) {
+ return;
+ }
+ if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+ if (Drupal.wysiwyg.plugins[plugin].isNode(node)) {
+ tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonSelected');
+ return true;
+ }
+ }
+ tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonNormal');
+ return true;
+ },
+
+ /**
+ * Return information about the plugin as a name/value array.
+ */
+ getInfo: function() {
+ return {
+ longname: settings.title
+ };
+ }
+ });
+ },
+
+ openDialog: function(dialog, params) {
+ var editor = tinyMCE.getInstanceById(this.field);
+ tinyMCE.openWindow({
+ file: dialog.url + '/' + this.field,
+ width: dialog.width,
+ height: dialog.height,
+ inline: 1
+ }, params);
+ },
+
+ closeDialog: function(dialog) {
+ var editor = tinyMCE.getInstanceById(this.field);
+ tinyMCEPopup.close();
+ },
+
+ prepareContent: function(content) {
+ // Certain content elements need to have additional DOM properties applied
+ // to prevent this editor from highlighting an internal button in addition
+ // to the button of a Drupal plugin.
+ var specialProperties = {
+ img: { 'name': 'mce_drupal' }
+ };
+ var $content = $('<div>' + content + '</div>'); // No .outerHTML() in jQuery :(
+ jQuery.each(specialProperties, function(element, properties) {
+ $content.find(element).each(function() {
+ for (var property in properties) {
+ if (property == 'class') {
+ $(this).addClass(properties[property]);
+ }
+ else {
+ $(this).attr(property, properties[property]);
+ }
+ }
+ });
+ });
+ return $content.html();
+ },
+
+ insert: function(content) {
+ content = this.prepareContent(content);
+ var editor = tinyMCE.getInstanceById(this.field);
+ editor.execCommand('mceInsertContent', false, content);
+ editor.repaint();
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/tinymce-3.js b/sites/all/modules/wysiwyg/editors/js/tinymce-3.js
new file mode 100644
index 000000000..83bae13a4
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/tinymce-3.js
@@ -0,0 +1,256 @@
+(function($) {
+
+/**
+ * Initialize editor instances.
+ *
+ * @todo Is the following note still valid for 3.x?
+ * This function needs to be called before the page is fully loaded, as
+ * calling tinyMCE.init() after the page is loaded breaks IE6.
+ *
+ * @param editorSettings
+ * An object containing editor settings for each input format.
+ */
+Drupal.wysiwyg.editor.init.tinymce = function(settings) {
+ // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode.
+ var $drupalToolbar = $('#toolbar', Drupal.overlayChild ? window.parent.document : document);
+ tinyMCE.onAddEditor.add(function (mgr, ed) {
+ if (ed.id == 'mce_fullscreen') {
+ $drupalToolbar.hide();
+ }
+ });
+ tinyMCE.onRemoveEditor.add(function (mgr, ed) {
+ if (ed.id == 'mce_fullscreen') {
+ $drupalToolbar.show();
+ }
+ });
+
+ // Initialize editor configurations.
+ for (var format in settings) {
+ if (Drupal.settings.wysiwyg.plugins[format]) {
+ // Load native external plugins.
+ // Array syntax required; 'native' is a predefined token in JavaScript.
+ for (var plugin in Drupal.settings.wysiwyg.plugins[format]['native']) {
+ tinymce.PluginManager.load(plugin, Drupal.settings.wysiwyg.plugins[format]['native'][plugin]);
+ }
+ // Load Drupal plugins.
+ for (var plugin in Drupal.settings.wysiwyg.plugins[format].drupal) {
+ Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins[format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]);
+ }
+ }
+ }
+};
+
+/**
+ * Attach this editor to a target element.
+ *
+ * See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) {
+ // Configure editor settings for this input format.
+ var ed = new tinymce.Editor(params.field, settings);
+ // Reset active instance id on any event.
+ ed.onEvent.add(function(ed, e) {
+ Drupal.wysiwyg.activeId = ed.id;
+ });
+ // Indicate that the DOM has been loaded (in case of Ajax).
+ tinymce.dom.Event.domLoaded = true;
+ // Make toolbar buttons wrappable (required for IE).
+ ed.onPostRender.add(function (ed) {
+ var $toolbar = $('<div class="wysiwygToolbar"></div>');
+ $('#' + ed.editorContainer + ' table.mceToolbar > tbody > tr > td').each(function () {
+ $('<div></div>').addClass(this.className).append($(this).children()).appendTo($toolbar);
+ });
+ $('#' + ed.editorContainer + ' table.mceLayout td.mceToolbar').append($toolbar);
+ $('#' + ed.editorContainer + ' table.mceToolbar').remove();
+ });
+
+ // Remove TinyMCE's internal mceItem class, which was incorrectly added to
+ // submitted content by Wysiwyg <2.1. TinyMCE only temporarily adds the class
+ // for placeholder elements. If preemptively set, the class prevents (native)
+ // editor plugins from gaining an active state, so we have to manually remove
+ // it prior to attaching the editor. This is done on the client-side instead
+ // of the server-side, as Wysiwyg has no way to figure out where content is
+ // stored, and the class only affects editing.
+ $field = $('#' + params.field);
+ $field.val($field.val().replace(/(<.+?\s+class=['"][\w\s]*?)\bmceItem\b([\w\s]*?['"].*?>)/ig, '$1$2'));
+
+ // Attach editor.
+ ed.render();
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.detach.tinymce = function (context, params, trigger) {
+ if (typeof params != 'undefined') {
+ var instance = tinyMCE.get(params.field);
+ if (instance) {
+ instance.save();
+ if (trigger != 'serialize') {
+ instance.remove();
+ }
+ }
+ }
+ else {
+ // Save contents of all editors back into textareas.
+ tinyMCE.triggerSave();
+ if (trigger != 'serialize') {
+ // Remove all editor instances.
+ for (var instance in tinyMCE.editors) {
+ tinyMCE.editors[instance].remove();
+ }
+ }
+ }
+};
+
+Drupal.wysiwyg.editor.instance.tinymce = {
+ addPlugin: function(plugin, settings, pluginSettings) {
+ if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
+ return;
+ }
+ tinymce.create('tinymce.plugins.' + plugin, {
+ /**
+ * Initialize the plugin, executed after the plugin has been created.
+ *
+ * @param ed
+ * The tinymce.Editor instance the plugin is initialized in.
+ * @param url
+ * The absolute URL of the plugin location.
+ */
+ init: function(ed, url) {
+ // Register an editor command for this plugin, invoked by the plugin's button.
+ ed.addCommand(plugin, function() {
+ if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
+ var data = { format: 'html', node: ed.selection.getNode(), content: ed.selection.getContent() };
+ // TinyMCE creates a completely new instance for fullscreen mode.
+ var instanceId = ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id;
+ Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instanceId);
+ }
+ });
+
+ // Register the plugin button.
+ ed.addButton(plugin, {
+ title : settings.iconTitle,
+ cmd : plugin,
+ image : settings.icon
+ });
+
+ // Load custom CSS for editor contents on startup.
+ ed.onInit.add(function() {
+ if (settings.css) {
+ ed.dom.loadCSS(settings.css);
+ }
+ });
+
+ // Attach: Replace plain text with HTML representations.
+ ed.onBeforeSetContent.add(function(ed, data) {
+ var editorId = (ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id);
+ if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+ data.content = Drupal.wysiwyg.plugins[plugin].attach(data.content, pluginSettings, editorId);
+ data.content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(data.content);
+ }
+ });
+
+ // Detach: Replace HTML representations with plain text.
+ ed.onGetContent.add(function(ed, data) {
+ var editorId = (ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id);
+ if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+ data.content = Drupal.wysiwyg.plugins[plugin].detach(data.content, pluginSettings, editorId);
+ }
+ });
+
+ // isNode: Return whether the plugin button should be enabled for the
+ // current selection.
+ ed.onNodeChange.add(function(ed, command, node) {
+ if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+ command.setActive(plugin, Drupal.wysiwyg.plugins[plugin].isNode(node));
+ }
+ });
+ },
+
+ /**
+ * Return information about the plugin as a name/value array.
+ */
+ getInfo: function() {
+ return {
+ longname: settings.title
+ };
+ }
+ });
+
+ // Register plugin.
+ tinymce.PluginManager.add(plugin, tinymce.plugins[plugin]);
+ },
+
+ openDialog: function(dialog, params) {
+ var instanceId = this.getInstanceId();
+ var editor = tinyMCE.get(instanceId);
+ editor.windowManager.open({
+ file: dialog.url + '/' + instanceId,
+ width: dialog.width,
+ height: dialog.height,
+ inline: 1
+ }, params);
+ },
+
+ closeDialog: function(dialog) {
+ var editor = tinyMCE.get(this.getInstanceId());
+ editor.windowManager.close(dialog);
+ },
+
+ prepareContent: function(content) {
+ // Certain content elements need to have additional DOM properties applied
+ // to prevent this editor from highlighting an internal button in addition
+ // to the button of a Drupal plugin.
+ var specialProperties = {
+ img: { 'class': 'mceItem' }
+ };
+ var $content = $('<div>' + content + '</div>'); // No .outerHTML() in jQuery :(
+ // Find all placeholder/replacement content of Drupal plugins.
+ $content.find('.drupal-content').each(function() {
+ // Recursively process DOM elements below this element to apply special
+ // properties.
+ var $drupalContent = $(this);
+ $.each(specialProperties, function(element, properties) {
+ $drupalContent.find(element).andSelf().each(function() {
+ for (var property in properties) {
+ if (property == 'class') {
+ $(this).addClass(properties[property]);
+ }
+ else {
+ $(this).attr(property, properties[property]);
+ }
+ }
+ });
+ });
+ });
+ return $content.html();
+ },
+
+ insert: function(content) {
+ content = this.prepareContent(content);
+ tinyMCE.execInstanceCommand(this.getInstanceId(), 'mceInsertContent', false, content);
+ },
+
+ setContent: function (content) {
+ content = this.prepareContent(content);
+ tinyMCE.execInstanceCommand(this.getInstanceId(), 'mceSetContent', false, content);
+ },
+
+ getContent: function () {
+ return tinyMCE.get(this.getInstanceId()).getContent();
+ },
+
+ isFullscreen: function() {
+ // TinyMCE creates a completely new instance for fullscreen mode.
+ return tinyMCE.activeEditor.id == 'mce_fullscreen' && tinyMCE.activeEditor.getParam('fullscreen_editor_id') == this.field;
+ },
+
+ getInstanceId: function () {
+ return this.isFullscreen() ? 'mce_fullscreen' : this.field;
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/whizzywig-56.js b/sites/all/modules/wysiwyg/editors/js/whizzywig-56.js
new file mode 100644
index 000000000..3fc2fe57c
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/whizzywig-56.js
@@ -0,0 +1,155 @@
+
+var wysiwygWhizzywig = { currentField: null, fields: {} };
+var buttonPath = null;
+
+/**
+ * Override Whizzywig's document.write() function.
+ *
+ * Whizzywig uses document.write() by default, which leads to a blank page when
+ * invoked in jQuery.ready(). Luckily, Whizzywig developers implemented a
+ * shorthand w() substitute function that we can override to redirect the output
+ * into the global wysiwygWhizzywig variable.
+ *
+ * @see o()
+ */
+var w = function (string) {
+ if (string) {
+ wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] += string;
+ }
+ return wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField];
+};
+
+/**
+ * Override Whizzywig's document.getElementById() function.
+ *
+ * Since we redirect the output of w() into a temporary string upon attaching
+ * an editor, we also have to override the o() shorthand substitute function
+ * for document.getElementById() to search in the document or our container.
+ * This override function also inserts the editor instance when Whizzywig
+ * tries to access its IFRAME, so it has access to the full/regular window
+ * object.
+ *
+ * @see w()
+ */
+var o = function (id) {
+ // Upon first access to "whizzy" + id, Whizzywig tries to access its IFRAME,
+ // so we need to insert the editor into the DOM.
+ if (id == 'whizzy' + wysiwygWhizzywig.currentField && wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField]) {
+ jQuery('#' + wysiwygWhizzywig.currentField).after('<div id="' + wysiwygWhizzywig.currentField + '-whizzywig"></div>');
+ // Iframe's .contentWindow becomes null in Webkit if inserted via .after().
+ jQuery('#' + wysiwygWhizzywig.currentField + '-whizzywig').html(w());
+ // Prevent subsequent invocations from inserting the editor multiple times.
+ wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = '';
+ }
+ // If id exists in the regular window.document, return it.
+ if (jQuery('#' + id).size()) {
+ return jQuery('#' + id).get(0);
+ }
+ // Otherwise return id from our container.
+ return jQuery('#' + id, w()).get(0);
+};
+
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) {
+ // Previous versions used per-button images found in this location,
+ // now it is only used for custom buttons.
+ if (settings.buttonPath) {
+ window.buttonPath = settings.buttonPath;
+ }
+ // Assign the toolbar image path used for native buttons, if available.
+ if (settings.toolbarImagePath) {
+ btn._f = settings.toolbarImagePath;
+ }
+ // Fall back to text labels for all buttons.
+ else {
+ window.buttonPath = 'textbuttons';
+ }
+ // Create Whizzywig container.
+ wysiwygWhizzywig.currentField = params.field;
+ wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = '';
+ // Whizzywig needs to have the width set 'inline'.
+ $field = $('#' + params.field);
+ var originalValues = Drupal.wysiwyg.instances[params.field];
+ originalValues.originalStyle = $field.attr('style');
+ $field.css('width', $field.width() + 'px');
+
+ // Attach editor.
+ makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all'));
+ // Whizzywig fails to detect and set initial textarea contents.
+ $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val()));
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) {
+ var detach = function (index) {
+ var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id];
+
+ // Save contents of editor back into textarea.
+ $field.val(instance.getContent());
+ // If the editor is just being serialized (not detached), our work is done.
+ if (trigger == 'serialize') {
+ return;
+ }
+ // Remove editor instance.
+ $('#' + id + '-whizzywig').remove();
+ whizzies.splice(index, 1);
+
+ // Restore original textarea styling.
+ $field.removeAttr('style').attr('style', instance.originalStyle);
+ };
+
+ if (typeof params != 'undefined') {
+ for (var i = 0; i < whizzies.length; i++) {
+ if (whizzies[i] == params.field) {
+ detach(i);
+ break;
+ }
+ }
+ }
+ else {
+ while (whizzies.length > 0) {
+ detach(0);
+ }
+ }
+};
+
+/**
+ * Instance methods for Whizzywig.
+ */
+Drupal.wysiwyg.editor.instance.whizzywig = {
+ insert: function (content) {
+ // Whizzywig executes any string beginning with 'js:'.
+ insHTML(content.replace(/^js:/, 'js&colon;'));
+ },
+
+ setContent: function (content) {
+ // Whizzywig shows the original textarea in source mode.
+ if ($field.css('display') == 'block') {
+ $('#' + this.field).val(content);
+ }
+ else {
+ var doc = $('#whizzy' + this.field).contents()[0];
+ doc.open();
+ doc.write(content);
+ doc.close();
+ }
+ },
+
+ getContent: function () {
+ // Whizzywig's tidyH() expects a document node. Clone the editing iframe's
+ // document so tidyH() won't mess with it if this gets called while editing.
+ var clone = $($('#whizzy' + this.field).contents()[0].documentElement).clone()[0].ownerDocument;
+ // Whizzywig shows the original textarea in source mode so update the body.
+ if ($field.css('display') == 'block') {
+ clone.body.innerHTML = $('#' + this.field).val();
+ }
+ return tidyH(clone);
+ }
+};
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/whizzywig-60.js b/sites/all/modules/wysiwyg/editors/js/whizzywig-60.js
new file mode 100644
index 000000000..bbc6e649d
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/whizzywig-60.js
@@ -0,0 +1,107 @@
+
+var buttonPath = null;
+
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) {
+ // Previous versions used per-button images found in this location,
+ // now it is only used for custom buttons.
+ if (settings.buttonPath) {
+ window.buttonPath = settings.buttonPath;
+ }
+ // Assign the toolbar image path used for native buttons, if available.
+ if (settings.toolbarImagePath) {
+ btn._f = settings.toolbarImagePath;
+ }
+ // Fall back to text labels for all buttons.
+ else {
+ window.buttonPath = 'textbuttons';
+ }
+ // Whizzywig needs to have the width set 'inline'.
+ $field = $('#' + params.field);
+ var originalValues = Drupal.wysiwyg.instances[params.field];
+ originalValues.originalStyle = $field.attr('style');
+ $field.css('width', $field.width() + 'px');
+
+ // Attach editor.
+ makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all'));
+ // Whizzywig fails to detect and set initial textarea contents.
+ $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val()));
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) {
+ var detach = function (index) {
+ var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id];
+
+ // Save contents of editor back into textarea.
+ $field.val(instance.getContent());
+ // If the editor is just being serialized (not detached), our work is done.
+ if (trigger == 'serialize') {
+ return;
+ }
+ // Move original textarea back to its previous location.
+ var $container = $('#CONTAINER' + id);
+ $field.insertBefore($container);
+ // Remove editor instance.
+ $container.remove();
+ whizzies.splice(index, 1);
+
+ // Restore original textarea styling.
+ $field.removeAttr('style').attr('style', instance.originalStyle);
+ }
+
+ if (typeof params != 'undefined') {
+ for (var i = 0; i < whizzies.length; i++) {
+ if (whizzies[i] == params.field) {
+ detach(i);
+ break;
+ }
+ }
+ }
+ else {
+ while (whizzies.length > 0) {
+ detach(0);
+ }
+ }
+};
+
+/**
+ * Instance methods for Whizzywig.
+ */
+Drupal.wysiwyg.editor.instance.whizzywig = {
+ insert: function (content) {
+ // Whizzywig executes any string beginning with 'js:'.
+ insHTML(content.replace(/^js:/, 'js&colon;'));
+ },
+
+ setContent: function (content) {
+ // Whizzywig shows the original textarea in source mode.
+ if ($field.css('display') == 'block') {
+ $('#' + this.field).val(content);
+ }
+ else {
+ var doc = $('#whizzy' + this.field).contents()[0];
+ doc.open();
+ doc.write(content);
+ doc.close();
+ }
+ },
+
+ getContent: function () {
+ // Whizzywig's tidyH() expects a document node. Clone the editing iframe's
+ // document so tidyH() won't mess with it if this gets called while editing.
+ var clone = $($('#whizzy' + this.field).contents()[0].documentElement).clone()[0].ownerDocument;
+ // Whizzywig shows the original textarea in source mode so update the body.
+ if ($field.css('display') == 'block') {
+ clone.body.innerHTML = $('#' + this.field).val();
+ }
+ return tidyH(clone);
+ }
+};
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/whizzywig.js b/sites/all/modules/wysiwyg/editors/js/whizzywig.js
new file mode 100644
index 000000000..e89ac5f09
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/whizzywig.js
@@ -0,0 +1,154 @@
+
+var wysiwygWhizzywig = { currentField: null, fields: {} };
+var buttonPath = null;
+
+/**
+ * Override Whizzywig's document.write() function.
+ *
+ * Whizzywig uses document.write() by default, which leads to a blank page when
+ * invoked in jQuery.ready(). Luckily, Whizzywig developers implemented a
+ * shorthand w() substitute function that we can override to redirect the output
+ * into the global wysiwygWhizzywig variable.
+ *
+ * @see o()
+ */
+var w = function (string) {
+ if (string) {
+ wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] += string;
+ }
+ return wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField];
+};
+
+/**
+ * Override Whizzywig's document.getElementById() function.
+ *
+ * Since we redirect the output of w() into a temporary string upon attaching
+ * an editor, we also have to override the o() shorthand substitute function
+ * for document.getElementById() to search in the document or our container.
+ * This override function also inserts the editor instance when Whizzywig
+ * tries to access its IFRAME, so it has access to the full/regular window
+ * object.
+ *
+ * @see w()
+ */
+var o = function (id) {
+ // Upon first access to "whizzy" + id, Whizzywig tries to access its IFRAME,
+ // so we need to insert the editor into the DOM.
+ if (id == 'whizzy' + wysiwygWhizzywig.currentField && wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField]) {
+ jQuery('#' + wysiwygWhizzywig.currentField).after('<div id="' + wysiwygWhizzywig.currentField + '-whizzywig"></div>');
+ // Iframe's .contentWindow becomes null in Webkit if inserted via .after().
+ jQuery('#' + wysiwygWhizzywig.currentField + '-whizzywig').html(w());
+ // Prevent subsequent invocations from inserting the editor multiple times.
+ wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = '';
+ }
+ // If id exists in the regular window.document, return it.
+ if (jQuery('#' + id).size()) {
+ return jQuery('#' + id).get(0);
+ }
+ // Otherwise return id from our container.
+ return jQuery('#' + id, w()).get(0);
+};
+
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) {
+ // Assign button images path, if available.
+ if (settings.buttonPath) {
+ window.buttonPath = settings.buttonPath;
+ }
+ // Create Whizzywig container.
+ wysiwygWhizzywig.currentField = params.field;
+ wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = '';
+ // Whizzywig needs to have the width set 'inline'.
+ $field = $('#' + params.field);
+ var originalValues = Drupal.wysiwyg.instances[params.field];
+ originalValues.originalStyle = $field.attr('style');
+ $field.css('width', $field.width() + 'px');
+
+ // Attach editor.
+ makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all'));
+ // Whizzywig fails to detect and set initial textarea contents.
+ $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val()));
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) {
+ var detach = function (index) {
+ var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id];
+
+ // Save contents of editor back into textarea.
+ $field.val(instance.getContent());
+ // If the editor is just being serialized (not detached), our work is done.
+ if (trigger == 'serialize') {
+ return;
+ }
+ // Remove editor instance.
+ $('#' + id + '-whizzywig').remove();
+ whizzies.splice(index, 1);
+
+ // Restore original textarea styling.
+ $field.removeAttr('style').attr('style', instance.originalStyle);
+ };
+
+ if (typeof params != 'undefined') {
+ for (var i = 0; i < whizzies.length; i++) {
+ if (whizzies[i] == params.field) {
+ detach(i);
+ break;
+ }
+ }
+ }
+ else {
+ while (whizzies.length > 0) {
+ detach(0);
+ }
+ }
+};
+
+/**
+ * Instance methods for Whizzywig.
+ */
+Drupal.wysiwyg.editor.instance.whizzywig = {
+ insert: function (content) {
+ // Whizzywig executes any string beginning with 'js:'.
+ insHTML(content.replace(/^js:/, 'js&colon;'));
+ },
+
+ setContent: function (content) {
+ var $field = $('#' + this.field);
+ // Whizzywig shows the original textarea in source mode.
+ if ($field.css('display') == 'block') {
+ $field.val(content);
+ }
+ else {
+ var doc = $('#whizzy' + this.field).contents()[0];
+ doc.open();
+ doc.write(content);
+ doc.close();
+ }
+ },
+
+ getContent: function () {
+ var $field = $('#' + this.field),
+ // Whizzywig shows the original textarea in source mode.
+ content = ($field.css('display') == 'block' ?
+ $field.val() : $('#whizzy' + this.field).contents().find('body').html()
+ );
+
+ content = tidyH(content);
+ // Whizzywig's get_xhtml() addon, if defined, expects a DOM node.
+ if ($.isFunction(window.get_xhtml)) {
+ var pre = document.createElement('pre');
+ pre.innerHTML = content;
+ content = get_xhtml(pre);
+ }
+ return content.replace(location.href + '#', '#');
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/wymeditor.js b/sites/all/modules/wysiwyg/editors/js/wymeditor.js
new file mode 100644
index 000000000..4989dc60b
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/wymeditor.js
@@ -0,0 +1,74 @@
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ */
+Drupal.wysiwyg.editor.attach.wymeditor = function (context, params, settings) {
+ // Prepend basePath to wymPath.
+ settings.wymPath = settings.basePath + settings.wymPath;
+ // Update activeId on focus.
+ settings.postInit = function (instance) {
+ $(instance._doc).focus(function () {
+ Drupal.wysiwyg.activeId = params.field;
+ });
+ };
+ // Attach editor.
+ $('#' + params.field).wymeditor(settings);
+};
+
+/**
+ * Detach a single or all editors.
+ */
+Drupal.wysiwyg.editor.detach.wymeditor = function (context, params, trigger) {
+ if (typeof params != 'undefined') {
+ var $field = $('#' + params.field);
+ var index = $field.data(WYMeditor.WYM_INDEX);
+ if (typeof index != 'undefined') {
+ var instance = WYMeditor.INSTANCES[index];
+ instance.update();
+ if (trigger != 'serialize') {
+ $(instance._box).remove();
+ $(instance._element).show();
+ delete instance;
+ }
+ }
+ if (trigger != 'serialize') {
+ $field.show();
+ }
+ }
+ else {
+ jQuery.each(WYMeditor.INSTANCES, function () {
+ this.update();
+ if (trigger != 'serialize') {
+ $(this._box).remove();
+ $(this._element).show();
+ delete this;
+ }
+ });
+ }
+};
+
+Drupal.wysiwyg.editor.instance.wymeditor = {
+ insert: function (content) {
+ this.getInstance().insert(content);
+ },
+
+ setContent: function (content) {
+ this.getInstance().html(content);
+ },
+
+ getContent: function () {
+ return this.getInstance().xhtml();
+ },
+
+ getInstance: function () {
+ var $field = $('#' + this.field);
+ var index = $field.data(WYMeditor.WYM_INDEX);
+ if (typeof index != 'undefined') {
+ return WYMeditor.INSTANCES[index];
+ }
+ return null;
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/js/yui.js b/sites/all/modules/wysiwyg/editors/js/yui.js
new file mode 100644
index 000000000..3f4e7c63a
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/js/yui.js
@@ -0,0 +1,145 @@
+(function($) {
+
+/**
+ * Attach this editor to a target element.
+ *
+ * Since buttons must be added before the editor is rendered, we add plugins
+ * buttons on attach event rather than in init.
+ */
+Drupal.wysiwyg.editor.attach.yui = function(context, params, settings) {
+ // Apply theme.
+ $('#' + params.field).parent().addClass('yui-skin-' + settings.theme);
+
+ // Load plugins stylesheet.
+ for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
+ settings.extracss += settings.extracss+' @import "'+Drupal.settings.wysiwyg.plugins[params.format].drupal[plugin].css+'"; ';
+ }
+
+ // Attach editor.
+ var editor = new YAHOO.widget.Editor(params.field, settings);
+
+ editor.on('toolbarLoaded', function() {
+ // Load Drupal plugins.
+ for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
+ Drupal.wysiwyg.instances[params.field].addPlugin(plugin, Drupal.settings.wysiwyg.plugins[params.format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]);
+ }
+ });
+
+ // Allow plugins to act on setEditorHTML.
+ var oldSetEditorHTML = editor.setEditorHTML;
+ editor.setEditorHTML = function (content) {
+ for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
+ var pluginSettings = Drupal.settings.wysiwyg.plugins.drupal[plugin];
+ if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+ content = Drupal.wysiwyg.plugins[plugin].attach(content, pluginSettings, params.field);
+ content = Drupal.wysiwyg.instances[params.field].prepareContent(content);
+ }
+ }
+ oldSetEditorHTML.call(this, content);
+ };
+
+ // Allow plugins to act on getEditorHTML.
+ var oldGetEditorHTML = editor.getEditorHTML;
+ editor.getEditorHTML = function () {
+ var content = oldGetEditorHTML.call(this);
+ for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
+ var pluginSettings = Drupal.settings.wysiwyg.plugins.drupal[plugin];
+ if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+ content = Drupal.wysiwyg.plugins[plugin].detach(content, pluginSettings, params.field);
+ }
+ }
+ return content;
+ }
+
+ // Reload the editor contents to give Drupal plugins a chance to act.
+ editor.on('editorContentLoaded', function (e) {
+ e.target.setEditorHTML(oldGetEditorHTML.call(e.target));
+ });
+
+ editor.on('afterNodeChange', function (e) {
+ for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
+ if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+ if (Drupal.wysiwyg.plugins[plugin].isNode(e.target._getSelectedElement())) {
+ this.toolbar.selectButton(plugin);
+ }
+ }
+ }
+ });
+
+ editor.render();
+};
+
+/**
+ * Detach a single or all editors.
+ *
+ * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook.
+ */
+Drupal.wysiwyg.editor.detach.yui = function (context, params, trigger) {
+ var method = (trigger && trigger == 'serialize') ? 'saveHTML' : 'destroy';
+ if (typeof params != 'undefined') {
+ var instance = YAHOO.widget.EditorInfo._instances[params.field];
+ if (instance) {
+ instance[method]();
+ if (method == 'destroy') {
+ delete YAHOO.widget.EditorInfo._instances[params.field];
+ }
+ }
+ }
+ else {
+ for (var e in YAHOO.widget.EditorInfo._instances) {
+ // Save contents of all editors back into textareas.
+ var instance = YAHOO.widget.EditorInfo._instances[e];
+ instance[method]();
+ if (method == 'destroy') {
+ delete YAHOO.widget.EditorInfo._instances[e];
+ }
+ }
+ }
+};
+
+/**
+ * Instance methods for YUI Editor.
+ */
+Drupal.wysiwyg.editor.instance.yui = {
+ addPlugin: function (plugin, settings, pluginSettings) {
+ if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
+ return;
+ }
+ var editor = YAHOO.widget.EditorInfo.getEditorById(this.field);
+ var button = editor.toolbar.getButtonByValue(plugin);
+ $(button._button).parent().css('background', 'transparent url(' + settings.icon + ') no-repeat center');
+ // 'this' will reference the toolbar while inside the event handler.
+ var instanceId = this.field;
+ editor.toolbar.on(plugin + 'Click', function (e) {
+ var selectedElement = editor._getSelectedElement();
+ // @todo Using .html() will cause XTHML vs HTML conflicts.
+ var data = {
+ format: 'html',
+ node: selectedElement,
+ content: $(selectedElement).html()
+ };
+ Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instanceId);
+ });
+ },
+
+ prepareContent: function (content) {
+ var editor = YAHOO.widget.EditorInfo.getEditorById(this.field);
+ content = editor.cleanHTML(content);
+ return content;
+ },
+
+ insert: function (content) {
+ YAHOO.widget.EditorInfo.getEditorById(this.field).cmd_inserthtml(content);
+ },
+
+ setContent: function (content) {
+ YAHOO.widget.EditorInfo.getEditorById(this.field).setEditorHTML(content);
+ },
+
+ getContent: function () {
+ var instance = YAHOO.widget.EditorInfo.getEditorById(this.field);
+ return instance.cleanHTML(instance.getEditorHTML(content));
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/editors/jwysiwyg.inc b/sites/all/modules/wysiwyg/editors/jwysiwyg.inc
new file mode 100644
index 000000000..fa65b7417
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/jwysiwyg.inc
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for jWYSIWYG.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_jwysiwyg_editor() {
+ $editor['jwysiwyg'] = array(
+ 'title' => 'jWYSIWYG',
+ 'vendor url' => 'http://code.google.com/p/jwysiwyg/',
+ 'download url' => 'http://code.google.com/p/jwysiwyg/downloads/list',
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Source',
+ 'files' => array('jquery.wysiwyg.js'),
+ ),
+ 'pack' => array(
+ 'title' => 'Packed',
+ 'files' => array('jquery.wysiwyg.pack.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_jwysiwyg_version',
+ // @todo Wrong property; add separate properties for editor requisites.
+ 'css path' => wysiwyg_get_path('jwysiwyg'),
+ 'versions' => array(
+ '0.5' => array(
+ 'js files' => array('jwysiwyg.js'),
+ 'css files' => array('jquery.wysiwyg.css'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_jwysiwyg_version($editor) {
+ $script = $editor['library path'] . '/jquery.wysiwyg.js';
+ if (!file_exists($script)) {
+ return;
+ }
+ $script = fopen($script, 'r');
+ fgets($script);
+ $line = fgets($script);
+ if (preg_match('@([0-9\.]+)$@', $line, $version)) {
+ fclose($script);
+ return $version[1];
+ }
+ fclose($script);
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/markitup.inc b/sites/all/modules/wysiwyg/editors/markitup.inc
new file mode 100644
index 000000000..57a37e836
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/markitup.inc
@@ -0,0 +1,189 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for markItUp.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_markitup_editor() {
+ $editor['markitup'] = array(
+ 'title' => 'markItUp',
+ 'vendor url' => 'http://markitup.jaysalvat.com',
+ 'download url' => 'http://markitup.jaysalvat.com/downloads',
+ 'library path' => wysiwyg_get_path('markitup'),
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Source',
+ 'files' => array('markitup/jquery.markitup.js'),
+ ),
+ 'pack' => array(
+ 'title' => 'Packed',
+ 'files' => array('markitup/jquery.markitup.pack.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_markitup_version',
+ 'themes callback' => 'wysiwyg_markitup_themes',
+ 'settings callback' => 'wysiwyg_markitup_settings',
+ 'plugin callback' => 'wysiwyg_markitup_plugins',
+ 'versions' => array(
+ '1.1.5' => array(
+ 'js files' => array('markitup.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_markitup_version($editor) {
+ // Changelog was in markitup/markitup/readme.txt <= 1.1.5.
+ $changelog = $editor['library path'] . '/markitup/readme.txt';
+ if (!file_exists($changelog)) {
+ // Changelog was moved up to markitup/CHANGELOG.md after 1.1.5.
+ $changelog = $editor['library path'] . '/CHANGELOG.md';
+ if (!file_exists($changelog)) {
+ return;
+ }
+ }
+ $changelog = fopen($changelog, 'r');
+ $line = fgets($changelog);
+ if (preg_match('@([0-9\.]+)@', $line, $version)) {
+ fclose($changelog);
+ return $version[1];
+ }
+ fclose($changelog);
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * An array of theme names. The first returned name should be the default
+ * theme name.
+ */
+function wysiwyg_markitup_themes($editor, $profile) {
+ return array('simple', 'markitup');
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_markitup_settings($editor, $config, $theme) {
+ drupal_add_css($editor['library path'] . '/markitup/skins/' . $theme . '/style.css', array(
+ // Specify an alternate basename; otherwise, style.css would override a
+ // commonly used style.css file of the theme.
+ 'basename' => 'markitup.' . $theme . '.style.css',
+ 'group' => CSS_THEME,
+ ));
+
+ $settings = array(
+ 'root' => base_path() . $editor['library path'] . '/markitup/',
+ 'nameSpace' => $theme,
+ 'markupSet' => array(),
+ );
+
+ // Add configured buttons or all available.
+ $default_buttons = array(
+ 'bold' => array(
+ 'name' => t('Bold'),
+ 'className' => 'markitup-bold',
+ 'key' => 'B',
+ 'openWith' => '(!(<strong>|!|<b>)!)',
+ 'closeWith' => '(!(</strong>|!|</b>)!)',
+ ),
+ 'italic' => array(
+ 'name' => t('Italic'),
+ 'className' => 'markitup-italic',
+ 'key' => 'I',
+ 'openWith' => '(!(<em>|!|<i>)!)',
+ 'closeWith' => '(!(</em>|!|</i>)!)',
+ ),
+ 'stroke' => array(
+ 'name' => t('Strike-through'),
+ 'className' => 'markitup-stroke',
+ 'key' => 'S',
+ 'openWith' => '<del>',
+ 'closeWith' => '</del>',
+ ),
+ 'image' => array(
+ 'name' => t('Image'),
+ 'className' => 'markitup-image',
+ 'key' => 'P',
+ 'replaceWith' => '<img src="[![Source:!:http://]!]" alt="[![Alternative text]!]" />',
+ ),
+ 'link' => array(
+ 'name' => t('Link'),
+ 'className' => 'markitup-link',
+ 'key' => 'K',
+ 'openWith' => '<a href="[![Link:!:http://]!]"(!( title="[![Title]!]")!)>',
+ 'closeWith' => '</a>',
+ 'placeHolder' => 'Your text to link...',
+ ),
+ // @todo
+ // 'cleanup' => array('name' => t('Clean-up'), 'className' => 'markitup-cleanup', 'replaceWith' => 'function(markitup) { return markitup.selection.replace(/<(.*?)>/g, "") }'),
+ 'preview' => array(
+ 'name' => t('Preview'),
+ 'className' => 'markitup-preview',
+ 'call' => 'preview',
+ ),
+ );
+ $settings['markupSet'] = array();
+ if (!empty($config['buttons'])) {
+ foreach ($config['buttons'] as $plugin) {
+ foreach ($plugin as $button => $enabled) {
+ if (isset($default_buttons[$button])) {
+ $settings['markupSet'][$button] = $default_buttons[$button];
+ }
+ }
+ }
+ }
+
+ return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_markitup_plugins($editor) {
+ return array(
+ 'default' => array(
+ 'buttons' => array(
+ 'bold' => t('Bold'), 'italic' => t('Italic'),
+ 'stroke' => t('Strike-through'),
+ 'link' => t('Link'),
+ 'image' => t('Image'),
+ // 'cleanup' => t('Clean-up'),
+ 'preview' => t('Preview'),
+ ),
+ 'internal' => TRUE,
+ ),
+ );
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/nicedit.inc b/sites/all/modules/wysiwyg/editors/nicedit.inc
new file mode 100644
index 000000000..6acc800dc
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/nicedit.inc
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for NicEdit.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_nicedit_editor() {
+ $editor['nicedit'] = array(
+ 'title' => 'NicEdit',
+ 'vendor url' => 'http://nicedit.com',
+ 'download url' => 'http://nicedit.com/download.php',
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Source',
+ 'files' => array('nicEdit.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_nicedit_version',
+ 'settings callback' => 'wysiwyg_nicedit_settings',
+ 'plugin callback' => 'wysiwyg_nicedit_plugins',
+ 'versions' => array(
+ '0.9' => array(
+ 'js files' => array('nicedit.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_nicedit_version($editor) {
+ // @see http://nicedit.com/forums/viewtopic.php?t=425
+ return '0.9';
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_nicedit_settings($editor, $config, $theme) {
+ $settings = array(
+ 'iconsPath' => base_path() . $editor['library path'] . '/nicEditorIcons.gif',
+ );
+
+ // Add configured buttons or all available.
+ $settings['buttonList'] = array();
+ if (!empty($config['buttons'])) {
+ $buttons = array();
+ foreach ($config['buttons'] as $plugin) {
+ $buttons = array_merge($buttons, $plugin);
+ }
+ $settings['buttonList'] = array_keys($buttons);
+ }
+
+ // Add editor content stylesheet.
+ if (isset($config['css_setting'])) {
+ if ($config['css_setting'] == 'theme') {
+ $css = drupal_get_path('theme', variable_get('theme_default', NULL)) . '/style.css';
+ if (file_exists($css)) {
+ $settings['externalCSS'] = base_path() . $css;
+ }
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))));
+ }
+ }
+
+ return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_nicedit_plugins($editor) {
+ return array(
+ 'default' => array(
+ 'buttons' => array(
+ 'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
+ 'strikethrough' => t('Strike-through'),
+ 'left' => t('Align left'), 'center' => t('Align center'), 'right' => t('Align right'),
+ 'ul' => t('Bullet list'), 'ol' => t('Numbered list'),
+ 'outdent' => t('Outdent'), 'indent' => t('Indent'),
+ 'image' => t('Image'),
+ 'forecolor' => t('Forecolor'), 'bgcolor' => t('Backcolor'),
+ 'superscript' => t('Superscript'), 'subscript' => t('Subscript'),
+ 'hr' => t('Horizontal rule'),
+ // @todo New challenge: Optional internal plugins packaged into editor
+ // library.
+ 'link' => t('Link'), 'unlink' => t('Unlink'),
+ 'fontFormat' => t('HTML block format'), 'fontFamily' => t('Font'), 'fontSize' => t('Font size'),
+ 'xhtml' => t('Source code'),
+ ),
+ 'internal' => TRUE,
+ ),
+ );
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/openwysiwyg.inc b/sites/all/modules/wysiwyg/editors/openwysiwyg.inc
new file mode 100644
index 000000000..b3ad84dd0
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/openwysiwyg.inc
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for openWYSIWYG.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_openwysiwyg_editor() {
+ $editor['openwysiwyg'] = array(
+ 'title' => 'openWYSIWYG',
+ 'vendor url' => 'http://www.openwebware.com',
+ 'download url' => 'http://www.openwebware.com/download.shtml',
+ 'library path' => wysiwyg_get_path('openwysiwyg') . '/scripts',
+ 'libraries' => array(
+ 'src' => array(
+ 'title' => 'Source',
+ 'files' => array('wysiwyg.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_openwysiwyg_version',
+ 'themes callback' => 'wysiwyg_openwysiwyg_themes',
+ 'settings callback' => 'wysiwyg_openwysiwyg_settings',
+ 'plugin callback' => 'wysiwyg_openwysiwyg_plugins',
+ 'versions' => array(
+ '1.4.7' => array(
+ 'js files' => array('openwysiwyg.js'),
+ 'css files' => array('openwysiwyg.css'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_openwysiwyg_version($editor) {
+ // 'library path' has '/scripts' appended already.
+ $changelog = $editor['editor path'] . '/changelog';
+ if (!file_exists($changelog)) {
+ return;
+ }
+ $changelog = fopen($changelog, 'r');
+ $line = fgets($changelog, 20);
+ if (preg_match('@v([\d\.]+)@', $line, $version)) {
+ fclose($changelog);
+ return $version[1];
+ }
+ fclose($changelog);
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * An array of theme names. The first returned name should be the default
+ * theme name.
+ */
+function wysiwyg_openwysiwyg_themes($editor, $profile) {
+ return array('default');
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_openwysiwyg_settings($editor, $config, $theme) {
+ $settings = array(
+ 'path' => base_path() . $editor['editor path'] . '/',
+ 'Width' => '100%',
+ );
+
+ if (isset($config['path_loc']) && $config['path_loc'] == 'none') {
+ $settings['StatusBarEnabled'] = FALSE;
+ }
+
+ if (isset($config['css_setting'])) {
+ if ($config['css_setting'] == 'theme') {
+ $settings['CSSFile'] = reset(wysiwyg_get_css());
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['CSSFile'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))));
+ }
+ }
+
+ $settings['Toolbar'] = array();
+ if (!empty($config['buttons'])) {
+ $plugins = wysiwyg_get_plugins($editor['name']);
+ foreach ($config['buttons'] as $plugin => $buttons) {
+ foreach ($buttons as $button => $enabled) {
+ foreach (array('buttons', 'extensions') as $type) {
+ // Skip unavailable plugins.
+ if (!isset($plugins[$plugin][$type][$button])) {
+ continue;
+ }
+ // Add buttons.
+ if ($type == 'buttons') {
+ $settings['Toolbar'][0][] = $button;
+ }
+ }
+ }
+ }
+ }
+
+ // @todo
+// if (isset($config['block_formats'])) {
+// $settings['DropDowns']['headings']['elements'] = explode(',', $config['block_formats']);
+// }
+
+ return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_openwysiwyg_plugins($editor) {
+ $plugins = array(
+ 'default' => array(
+ 'buttons' => array(
+ 'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
+ 'strikethrough' => t('Strike-through'),
+ 'justifyleft' => t('Align left'), 'justifycenter' => t('Align center'), 'justifyright' => t('Align right'), 'justifyfull' => t('Justify'),
+ 'unorderedlist' => t('Bullet list'), 'orderedlist' => t('Numbered list'),
+ 'outdent' => t('Outdent'), 'indent' => t('Indent'),
+ 'undo' => t('Undo'), 'redo' => t('Redo'),
+ 'createlink' => t('Link'),
+ 'insertimage' => t('Image'),
+ 'cleanup' => t('Clean-up'),
+ 'forecolor' => t('Forecolor'), 'backcolor' => t('Backcolor'),
+ 'superscript' => t('Sup'), 'subscript' => t('Sub'),
+ 'blockquote' => t('Blockquote'), 'viewSource' => t('Source code'),
+ 'hr' => t('Horizontal rule'),
+ 'cut' => t('Cut'), 'copy' => t('Copy'), 'paste' => t('Paste'),
+ 'visualaid' => t('Visual aid'),
+ 'removeformat' => t('Remove format'),
+ 'charmap' => t('Character map'),
+ 'headings' => t('HTML block format'), 'font' => t('Font'), 'fontsize' => t('Font size'),
+ 'maximize' => t('Fullscreen'),
+ 'preview' => t('Preview'),
+ 'print' => t('Print'),
+ 'inserttable' => t('Table'),
+ 'help' => t('Help'),
+ ),
+ 'internal' => TRUE,
+ ),
+ );
+ return $plugins;
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/tinymce.inc b/sites/all/modules/wysiwyg/editors/tinymce.inc
new file mode 100644
index 000000000..f3a92dd89
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/tinymce.inc
@@ -0,0 +1,650 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for TinyMCE.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ *
+ * @todo wysiwyg_<editor>_alter() to add/inject optional libraries like gzip.
+ */
+function wysiwyg_tinymce_editor() {
+ $editor['tinymce'] = array(
+ 'title' => 'TinyMCE',
+ 'vendor url' => 'http://tinymce.moxiecode.com',
+ 'download url' => 'http://tinymce.moxiecode.com/download.php',
+ 'library path' => wysiwyg_get_path('tinymce') . '/jscripts/tiny_mce',
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Minified',
+ 'files' => array('tiny_mce.js'),
+ ),
+ 'src' => array(
+ 'title' => 'Source',
+ 'files' => array('tiny_mce_src.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_tinymce_version',
+ 'themes callback' => 'wysiwyg_tinymce_themes',
+ 'init callback' => 'wysiwyg_tinymce_init',
+ 'settings callback' => 'wysiwyg_tinymce_settings',
+ 'plugin callback' => 'wysiwyg_tinymce_plugins',
+ 'plugin settings callback' => 'wysiwyg_tinymce_plugin_settings',
+ 'proxy plugin' => array(
+ 'drupal' => array(
+ 'load' => TRUE,
+ 'proxy' => TRUE,
+ ),
+ ),
+ 'proxy plugin settings callback' => 'wysiwyg_tinymce_proxy_plugin_settings',
+ 'versions' => array(
+ '2.1' => array(
+ 'js files' => array('tinymce-2.js'),
+ 'css files' => array('tinymce-2.css'),
+ 'download url' => 'http://sourceforge.net/project/showfiles.php?group_id=103281&package_id=111430&release_id=557383',
+ ),
+ // @todo Starting from 3.3, tiny_mce.js may support JS aggregation.
+ '3.1' => array(
+ 'js files' => array('tinymce-3.js'),
+ 'css files' => array('tinymce-3.css'),
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Minified',
+ 'files' => array(
+ 'tiny_mce.js' => array('preprocess' => FALSE),
+ ),
+ ),
+ 'jquery' => array(
+ 'title' => 'jQuery',
+ 'files' => array('tiny_mce_jquery.js'),
+ ),
+ 'src' => array(
+ 'title' => 'Source',
+ 'files' => array('tiny_mce_src.js'),
+ ),
+ ),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_tinymce_version($editor) {
+ $script = $editor['library path'] . '/tiny_mce.js';
+ if (!file_exists($script)) {
+ return;
+ }
+ $script = fopen($script, 'r');
+ // Version is contained in the first 200 chars.
+ $line = fgets($script, 200);
+ fclose($script);
+ // 2.x: this.majorVersion="2";this.minorVersion="1.3"
+ // 3.x: majorVersion:'3',minorVersion:'2.0.1'
+ if (preg_match('@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@', $line, $version)) {
+ return $version[1] . '.' . $version[2];
+ }
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * An array of theme names. The first returned name should be the default
+ * theme name.
+ */
+function wysiwyg_tinymce_themes($editor, $profile) {
+ /*
+ $themes = array();
+ $dir = $editor['library path'] . '/themes/';
+ if (is_dir($dir) && $dh = opendir($dir)) {
+ while (($file = readdir($dh)) !== FALSE) {
+ if (!in_array($file, array('.', '..', 'CVS', '.svn')) && is_dir($dir . $file)) {
+ $themes[$file] = $file;
+ }
+ }
+ closedir($dh);
+ asort($themes);
+ }
+ return $themes;
+ */
+ return array('advanced', 'simple');
+}
+
+/**
+ * Returns an initialization JavaScript for this editor library.
+ *
+ * @param array $editor
+ * The editor library definition.
+ * @param string $library
+ * The library variant key from $editor['libraries'].
+ * @param object $profile
+ * The (first) wysiwyg editor profile.
+ *
+ * @return string
+ * A string containing inline JavaScript to execute before the editor library
+ * script is loaded.
+ */
+function wysiwyg_tinymce_init($editor, $library) {
+ // TinyMCE unconditionally searches for its library filename in SCRIPT tags on
+ // on the page upon loading the library in order to determine the base path to
+ // itself. When JavaScript aggregation is enabled, this search fails and all
+ // relative constructed paths within TinyMCE are broken. The library has a
+ // tinyMCE.baseURL property, but it is not publicly documented and thus not
+ // reliable. The official support forum suggests to solve the issue through
+ // the global window.tinyMCEPreInit variable also used by various serverside
+ // compressor scrips available from the official website.
+ // @see http://www.tinymce.com/forum/viewtopic.php?id=23286
+ $settings = drupal_json_encode(array(
+ 'base' => base_path() . $editor['library path'],
+ 'suffix' => (strpos($library, 'src') !== FALSE || strpos($library, 'dev') !== FALSE ? '_src' : ''),
+ 'query' => '',
+ ));
+ return <<<EOL
+window.tinyMCEPreInit = $settings;
+EOL;
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_tinymce_settings($editor, $config, $theme) {
+ $settings = array(
+ 'button_tile_map' => TRUE, // @todo Add a setting for this.
+ 'document_base_url' => base_path(),
+ 'mode' => 'none',
+ 'plugins' => array(),
+ 'theme' => $theme,
+ 'width' => '100%',
+ // Strict loading mode must be enabled; otherwise TinyMCE would use
+ // document.write() in IE and Chrome.
+ 'strict_loading_mode' => TRUE,
+ // TinyMCE's URL conversion magic breaks Drupal modules that use a special
+ // syntax for paths. This makes 'relative_urls' obsolete.
+ 'convert_urls' => FALSE,
+ // The default entity_encoding ('named') converts too many characters in
+ // languages (like Greek). Since Drupal supports Unicode, we only convert
+ // HTML control characters and invisible characters. TinyMCE always converts
+ // XML default characters '&', '<', '>'.
+ 'entities' => '160,nbsp,173,shy,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm',
+ );
+ if (isset($config['apply_source_formatting'])) {
+ $settings['apply_source_formatting'] = $config['apply_source_formatting'];
+ }
+ if (isset($config['convert_fonts_to_spans'])) {
+ $settings['convert_fonts_to_spans'] = $config['convert_fonts_to_spans'];
+ }
+ if (isset($config['language'])) {
+ $settings['language'] = $config['language'];
+ }
+ if (isset($config['paste_auto_cleanup_on_paste'])) {
+ $settings['paste_auto_cleanup_on_paste'] = $config['paste_auto_cleanup_on_paste'];
+ }
+ if (isset($config['preformatted'])) {
+ $settings['preformatted'] = $config['preformatted'];
+ }
+ if (isset($config['remove_linebreaks'])) {
+ $settings['remove_linebreaks'] = $config['remove_linebreaks'];
+ }
+ if (isset($config['verify_html'])) {
+ // TinyMCE performs a type-agnostic comparison on this particular setting.
+ $settings['verify_html'] = (bool) $config['verify_html'];
+ }
+
+ if (!empty($config['css_classes'])) {
+ $settings['theme_advanced_styles'] = implode(';', array_filter(explode("\n", str_replace("\r", '', $config['css_classes']))));
+ }
+
+ if (isset($config['css_setting'])) {
+ if ($config['css_setting'] == 'theme') {
+ $settings['content_css'] = implode(',', wysiwyg_get_css());
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['content_css'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))));
+ }
+ }
+
+ // Find the enabled buttons and the button row they belong on.
+ // Also map the plugin metadata for each button.
+ // @todo What follows is a pain; needs a rewrite.
+ // $settings['buttons'] are stacked into $settings['theme_advanced_buttons1']
+ // later.
+ $settings['buttons'] = array();
+ if (!empty($config['buttons']) && is_array($config['buttons'])) {
+ // Only array keys in $settings['extensions'] matter; added to
+ // $settings['plugins'] later.
+ $settings['extensions'] = array();
+ // $settings['extended_valid_elements'] are just stacked, unique'd later,
+ // and transformed into a comma-separated string in
+ // wysiwyg_add_editor_settings().
+ // @todo Needs a complete plugin API redesign using arrays for
+ // tag => attributes definitions and array_merge_recursive().
+ $settings['extended_valid_elements'] = array();
+
+ $plugins = wysiwyg_get_plugins($editor['name']);
+ foreach ($config['buttons'] as $plugin => $buttons) {
+ foreach ($buttons as $button => $enabled) {
+ // Iterate separately over buttons and extensions properties.
+ foreach (array('buttons', 'extensions') as $type) {
+ // Skip unavailable plugins.
+ if (!isset($plugins[$plugin][$type][$button])) {
+ continue;
+ }
+ // Add buttons.
+ if ($type == 'buttons') {
+ $settings['buttons'][] = $button;
+ }
+ // Add external Drupal plugins to the list of extensions.
+ if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) {
+ $settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $button)] = 1;
+ }
+ // Add external plugins to the list of extensions.
+ elseif ($type == 'buttons' && empty($plugins[$plugin]['internal'])) {
+ $settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $plugin)] = 1;
+ }
+ // Add internal buttons that also need to be loaded as extension.
+ elseif ($type == 'buttons' && !empty($plugins[$plugin]['load'])) {
+ $settings['extensions'][$plugin] = 1;
+ }
+ // Add plain extensions.
+ elseif ($type == 'extensions' && !empty($plugins[$plugin]['load'])) {
+ $settings['extensions'][$plugin] = 1;
+ }
+ // Allow plugins to add valid HTML elements.
+ if (!empty($plugins[$plugin]['extended_valid_elements'])) {
+ $settings['extended_valid_elements'] = array_merge($settings['extended_valid_elements'], $plugins[$plugin]['extended_valid_elements']);
+ }
+ // Allow plugins to add or override global configuration settings.
+ if (!empty($plugins[$plugin]['options'])) {
+ $settings = array_merge($settings, $plugins[$plugin]['options']);
+ }
+ }
+ }
+ }
+ // Clean-up.
+ $settings['extended_valid_elements'] = array_unique($settings['extended_valid_elements']);
+ if ($settings['extensions']) {
+ $settings['plugins'] = array_keys($settings['extensions']);
+ }
+ unset($settings['extensions']);
+ }
+
+ // Add theme-specific settings.
+ switch ($theme) {
+ case 'advanced':
+ $settings += array(
+ 'theme_advanced_resize_horizontal' => FALSE,
+ 'theme_advanced_resizing_use_cookie' => FALSE,
+ 'theme_advanced_statusbar_location' => isset($config['path_loc']) ? $config['path_loc'] : 'bottom',
+ 'theme_advanced_resizing' => isset($config['resizing']) ? $config['resizing'] : 1,
+ 'theme_advanced_toolbar_location' => isset($config['toolbar_loc']) ? $config['toolbar_loc'] : 'top',
+ 'theme_advanced_toolbar_align' => isset($config['toolbar_align']) ? $config['toolbar_align'] : 'left',
+ );
+ if (isset($config['block_formats'])) {
+ $settings['theme_advanced_blockformats'] = $config['block_formats'];
+ }
+ if (isset($settings['buttons'])) {
+ // These rows explicitly need to be set to be empty, otherwise TinyMCE
+ // loads its default buttons of the advanced theme for each row.
+ $settings += array(
+ 'theme_advanced_buttons1' => array(),
+ 'theme_advanced_buttons2' => array(),
+ 'theme_advanced_buttons3' => array(),
+ );
+ // @todo Allow to sort/arrange editor buttons.
+ for ($i = 0; $i < count($settings['buttons']); $i++) {
+ $settings['theme_advanced_buttons1'][] = $settings['buttons'][$i];
+ }
+ }
+ break;
+ }
+ unset($settings['buttons']);
+
+ // Convert the config values into the form expected by TinyMCE.
+ $csv_settings = array('plugins', 'extended_valid_elements', 'theme_advanced_buttons1', 'theme_advanced_buttons2', 'theme_advanced_buttons3');
+ foreach ($csv_settings as $key) {
+ if (isset($settings[$key]) && is_array($settings[$key])) {
+ $settings[$key] = implode(',', $settings[$key]);
+ }
+ }
+
+ return $settings;
+}
+
+/**
+ * Build a JS settings array of native external plugins that need to be loaded separately.
+ *
+ * TinyMCE requires that external plugins (i.e. not residing in the editor's
+ * directory) are loaded (once) upon initializing the editor.
+ */
+function wysiwyg_tinymce_plugin_settings($editor, $profile, $plugins) {
+ $settings = array();
+ foreach ($plugins as $name => $plugin) {
+ if (!empty($plugin['load'])) {
+ // Add path for native external plugins; internal ones are loaded
+ // automatically.
+ if (empty($plugin['internal']) && isset($plugin['filename'])) {
+ $settings[$name] = base_path() . $plugin['path'] . '/' . $plugin['filename'];
+ }
+ }
+ }
+ return $settings;
+}
+
+/**
+ * Build a JS settings array for Drupal plugins loaded via the proxy plugin.
+ */
+function wysiwyg_tinymce_proxy_plugin_settings($editor, $profile, $plugins) {
+ $settings = array();
+ foreach ($plugins as $name => $plugin) {
+ // Populate required plugin settings.
+ $settings[$name] = $plugin['dialog settings'] + array(
+ 'title' => $plugin['title'],
+ 'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'],
+ 'iconTitle' => $plugin['icon title'],
+ );
+ if (isset($plugin['css file'])) {
+ $settings[$name]['css'] = base_path() . $plugin['css path'] . '/' . $plugin['css file'];
+ }
+ }
+ return $settings;
+}
+
+/**
+ * Add or remove leading hiven to/of external plugin names.
+ *
+ * TinyMCE requires that external plugins, which should not be loaded from
+ * its own plugin repository are prefixed with a hiven in the name.
+ *
+ * @param string $op
+ * Operation to perform, 'add' or 'remove' (hiven).
+ * @param string $name
+ * A plugin name.
+ */
+function _wysiwyg_tinymce_plugin_name($op, $name) {
+ if ($op == 'add') {
+ if (strpos($name, '-') !== 0) {
+ return '-' . $name;
+ }
+ return $name;
+ }
+ elseif ($op == 'remove') {
+ if (strpos($name, '-') === 0) {
+ return substr($name, 1);
+ }
+ return $name;
+ }
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_tinymce_plugins($editor) {
+ $plugins = array(
+ 'default' => array(
+ 'path' => $editor['library path'] . '/themes/advanced',
+ 'buttons' => array(
+ 'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
+ 'strikethrough' => t('Strike-through'),
+ 'justifyleft' => t('Align left'), 'justifycenter' => t('Align center'), 'justifyright' => t('Align right'), 'justifyfull' => t('Justify'),
+ 'bullist' => t('Bullet list'), 'numlist' => t('Numbered list'),
+ 'outdent' => t('Outdent'), 'indent' => t('Indent'),
+ 'undo' => t('Undo'), 'redo' => t('Redo'),
+ 'link' => t('Link'), 'unlink' => t('Unlink'), 'anchor' => t('Anchor'),
+ 'image' => t('Image'),
+ 'cleanup' => t('Clean-up'),
+ 'formatselect' => t('Block format'), 'styleselect' => t('Styles'),
+ 'fontselect' => t('Font'), 'fontsizeselect' => t('Font size'),
+ 'forecolor' => t('Forecolor'), 'backcolor' => t('Backcolor'),
+ 'sup' => t('Superscript'), 'sub' => t('Subscript'),
+ 'blockquote' => t('Blockquote'), 'code' => t('Source code'),
+ 'hr' => t('Horizontal rule'),
+ 'cut' => t('Cut'), 'copy' => t('Copy'), 'paste' => t('Paste'),
+ 'visualaid' => t('Visual aid'),
+ 'removeformat' => t('Remove format'),
+ 'charmap' => t('Character map'),
+ 'help' => t('Help'),
+ ),
+ 'internal' => TRUE,
+ ),
+ 'advhr' => array(
+ 'path' => $editor['library path'] . '/plugins/advhr',
+ 'buttons' => array('advhr' => t('Advanced horizontal rule')),
+ 'extended_valid_elements' => array('hr[class|width|size|noshade]'),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advhr',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'advimage' => array(
+ 'path' => $editor['library path'] . '/plugins/advimage',
+ 'extensions' => array('advimage' => t('Advanced image')),
+ 'extended_valid_elements' => array('img[src|alt|title|align|width|height|usemap|hspace|vspace|border|style|class|onmouseover|onmouseout|id|name|longdesc]'),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advimage',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'advlink' => array(
+ 'path' => $editor['library path'] . '/plugins/advlink',
+ 'extensions' => array('advlink' => t('Advanced link')),
+ 'extended_valid_elements' => array('a[name|href|target|title|class|onfocus|onblur|onclick|ondlbclick|onmousedown|onmouseup|onmouseover|onmouseout|onkeypress|onkeydown|onkeyup|id|style|rel]'),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advlink',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'autosave' => array(
+ 'path' => $editor['library path'] . '/plugins/autosave',
+ 'extensions' => array('autosave' => t('Auto save')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:autosave',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'contextmenu' => array(
+ 'path' => $editor['library path'] . '/plugins/contextmenu',
+ 'extensions' => array('contextmenu' => t('Context menu')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:contextmenu',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'directionality' => array(
+ 'path' => $editor['library path'] . '/plugins/directionality',
+ 'buttons' => array('ltr' => t('Left-to-right'), 'rtl' => t('Right-to-left')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:directionality',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'emotions' => array(
+ 'path' => $editor['library path'] . '/plugins/emotions',
+ 'buttons' => array('emotions' => t('Emotions')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:emotions',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'fullscreen' => array(
+ 'path' => $editor['library path'] . '/plugins/fullscreen',
+ 'buttons' => array('fullscreen' => t('Fullscreen')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:fullscreen',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'inlinepopups' => array(
+ 'path' => $editor['library path'] . '/plugins/inlinepopups',
+ 'extensions' => array('inlinepopups' => t('Inline popups')),
+ 'options' => array(
+ 'dialog_type' => array('modal'),
+ ),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:inlinepopups',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'insertdatetime' => array(
+ 'path' => $editor['library path'] . '/plugins/insertdatetime',
+ 'buttons' => array('insertdate' => t('Insert date'), 'inserttime' => t('Insert time')),
+ 'options' => array(
+ 'plugin_insertdate_dateFormat' => '%Y-%m-%d',
+ 'plugin_insertdate_timeFormat' => '%H:%M:%S',
+ ),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:insertdatetime',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'layer' => array(
+ 'path' => $editor['library path'] . '/plugins/layer',
+ 'buttons' => array('insertlayer' => t('Insert layer'), 'moveforward' => t('Move forward'), 'movebackward' => t('Move backward'), 'absolute' => t('Absolute')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:layer',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'paste' => array(
+ 'path' => $editor['library path'] . '/plugins/paste',
+ 'buttons' => array('pastetext' => t('Paste text'), 'pasteword' => t('Paste from Word'), 'selectall' => t('Select all')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:paste',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'preview' => array(
+ 'path' => $editor['library path'] . '/plugins/preview',
+ 'buttons' => array('preview' => t('Preview')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:preview',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'print' => array(
+ 'path' => $editor['library path'] . '/plugins/print',
+ 'buttons' => array('print' => t('Print')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:print',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'searchreplace' => array(
+ 'path' => $editor['library path'] . '/plugins/searchreplace',
+ 'buttons' => array('search' => t('Search'), 'replace' => t('Replace')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:searchreplace',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'style' => array(
+ 'path' => $editor['library path'] . '/plugins/style',
+ 'buttons' => array('styleprops' => t('Advanced CSS styles')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:style',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ 'table' => array(
+ 'path' => $editor['library path'] . '/plugins/table',
+ 'buttons' => array('tablecontrols' => t('Table')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:table',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ ),
+ );
+ if (version_compare($editor['installed version'], '3', '<')) {
+ $plugins['flash'] = array(
+ 'path' => $editor['library path'] . '/plugins/flash',
+ 'buttons' => array('flash' => t('Flash')),
+ 'extended_valid_elements' => array('img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|obj|param|embed]'),
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ );
+ }
+ if (version_compare($editor['installed version'], '2.0.6', '>')) {
+ $plugins['media'] = array(
+ 'path' => $editor['library path'] . '/plugins/media',
+ 'buttons' => array('media' => t('Media')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:media',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ );
+ $plugins['xhtmlxtras'] = array(
+ 'path' => $editor['library path'] . '/plugins/xhtmlxtras',
+ 'buttons' => array('cite' => t('Citation'), 'del' => t('Deleted'), 'abbr' => t('Abbreviation'), 'acronym' => t('Acronym'), 'ins' => t('Inserted'), 'attribs' => t('HTML attributes')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:xhtmlxtras',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ );
+ }
+ if (version_compare($editor['installed version'], '3', '>')) {
+ $plugins['bbcode'] = array(
+ 'path' => $editor['library path'] . '/plugins/bbcode',
+ 'extensions' => array('bbcode' => t('BBCode')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:bbcode',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ );
+ if (version_compare($editor['installed version'], '3.3', '<')) {
+ $plugins['safari'] = array(
+ 'path' => $editor['library path'] . '/plugins/safari',
+ 'extensions' => array('safari' => t('Safari compatibility')),
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ );
+ }
+ }
+ if (version_compare($editor['installed version'], '3.2.5', '>=')) {
+ $plugins['autoresize'] = array(
+ 'path' => $editor['library path'] . '/plugins/autoresize',
+ 'extensions' => array('autoresize' => t('Auto resize')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:autoresize',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ );
+ }
+ if (version_compare($editor['installed version'], '3.3', '>=')) {
+ $plugins['advlist'] = array(
+ 'path' => $editor['library path'] . '/plugins/advlist',
+ 'extensions' => array('advlist' => t('Advanced list')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advlist',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ );
+ }
+ if (version_compare($editor['installed version'], '3.2.6', '>=')) {
+ $plugins['wordcount'] = array(
+ 'path' => $editor['library path'] . '/plugins/wordcount',
+ 'extensions' => array('wordcount' => t('Word count')),
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ );
+ }
+ if (version_compare($editor['installed version'], '3.4.1', '>=')) {
+ $plugins['lists'] = array(
+ 'path' => $editor['library path'] . 'plugins/lists',
+ 'extensions' => array('lists' => t('List normalizer')),
+ 'url' => 'http://www.tinymce.com/wiki.php/Plugin:lists',
+ 'internal' => TRUE,
+ 'load' => TRUE,
+ 'extended_valid_elements' => array(
+ 'li[class|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title|type|value]',
+ 'ol[class|compact|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|start|style|title|type]',
+ 'ul[class|compact|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title|type]',
+ ),
+ );
+ }
+ return $plugins;
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/whizzywig.inc b/sites/all/modules/wysiwyg/editors/whizzywig.inc
new file mode 100644
index 000000000..acfc7de32
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/whizzywig.inc
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for Whizzywig.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_whizzywig_editor() {
+ $editor['whizzywig'] = array(
+ 'title' => 'Whizzywig',
+ 'vendor url' => 'http://www.unverse.net',
+ 'download url' => 'http://www.unverse.net/whizzywig-download.html',
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Default',
+ 'files' => array('whizzywig.js', 'xhtml.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_whizzywig_version',
+ 'settings callback' => 'wysiwyg_whizzywig_settings',
+ 'plugin callback' => 'wysiwyg_whizzywig_plugins',
+ 'versions' => array(
+ '55' => array(
+ 'js files' => array('whizzywig.js'),
+ ),
+ '56' => array(
+ 'js files' => array('whizzywig-56.js'),
+ ),
+ '60' => array(
+ 'js files' => array('whizzywig-60.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_whizzywig_version($editor) {
+ $script = $editor['library path'] . '/whizzywig.js';
+ if (!file_exists($script)) {
+ return;
+ }
+ $script = fopen($script, 'r');
+ $line = fgets($script, 43);
+ // 55: Whizzywig v55i
+ // 60: Whizzywig 60
+ if (preg_match('@Whizzywig v?([0-9]+)@', $line, $version)) {
+ fclose($script);
+ return $version[1];
+ }
+ fclose($script);
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_whizzywig_settings($editor, $config, $theme) {
+ $settings = array();
+
+ // Add path to button images, if available.
+ if (is_dir($editor['library path'] . '/btn')) {
+ $settings['buttonPath'] = base_path() . $editor['library path'] . '/btn/';
+ }
+ if (file_exists($editor['library path'] . '/WhizzywigToolbar.png')) {
+ $settings['toolbarImagePath'] = base_path() . $editor['library path'] . '/WhizzywigToolbar.png';
+ }
+ // Filename changed in version 60.
+ elseif (file_exists($editor['library path'] . '/icons.png')) {
+ $settings['toolbarImagePath'] = base_path() . $editor['library path'] . '/icons.png';
+ }
+
+ // Add configured buttons or all available.
+ $settings['buttons'] = array();
+ if (!empty($config['buttons'])) {
+ $buttons = array();
+ foreach ($config['buttons'] as $plugin) {
+ $buttons = array_merge($buttons, $plugin);
+ }
+ $settings['buttons'] = implode(' ', array_keys($buttons));
+ }
+
+ // Add editor content stylesheet.
+ if (isset($config['css_setting'])) {
+ if ($config['css_setting'] == 'theme') {
+ $css = drupal_get_path('theme', variable_get('theme_default', NULL)) . '/style.css';
+ if (file_exists($css)) {
+ $settings['externalCSS'] = base_path() . $css;
+ }
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))));
+ }
+ }
+
+ return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_whizzywig_plugins($editor) {
+ return array(
+ 'default' => array(
+ 'buttons' => array(
+ 'formatblock' => t('HTML block format'), 'fontname' => t('Font'), 'fontsize' => t('Font size'),
+ 'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
+ 'left' => t('Align left'), 'center' => t('Align center'), 'right' => t('Align right'),
+ 'bullet' => t('Bullet list'), 'number' => t('Numbered list'),
+ 'outdent' => t('Outdent'), 'indent' => t('Indent'),
+ 'undo' => t('Undo'), 'redo' => t('Redo'),
+ 'image' => t('Image'),
+ 'color' => t('Forecolor'), 'hilite' => t('Backcolor'),
+ 'rule' => t('Horizontal rule'),
+ 'link' => t('Link'),
+ 'image' => t('Image'),
+ 'table' => t('Table'),
+ 'clean' => t('Clean-up'),
+ 'html' => t('Source code'),
+ 'spellcheck' => t('Spell check'),
+ ),
+ 'internal' => TRUE,
+ ),
+ );
+}
+
diff --git a/sites/all/modules/wysiwyg/editors/wymeditor.inc b/sites/all/modules/wysiwyg/editors/wymeditor.inc
new file mode 100644
index 000000000..5f44d6415
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/wymeditor.inc
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for WYMeditor.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_wymeditor_editor() {
+ $editor['wymeditor'] = array(
+ 'title' => 'WYMeditor',
+ 'vendor url' => 'http://www.wymeditor.org/',
+ 'download url' => 'http://www.wymeditor.org/download/',
+ 'library path' => wysiwyg_get_path('wymeditor') . '/wymeditor',
+ 'libraries' => array(
+ 'min' => array(
+ 'title' => 'Minified',
+ 'files' => array('jquery.wymeditor.min.js'),
+ ),
+ 'pack' => array(
+ 'title' => 'Packed',
+ 'files' => array('jquery.wymeditor.pack.js'),
+ ),
+ 'src' => array(
+ 'title' => 'Source',
+ 'files' => array('jquery.wymeditor.js'),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_wymeditor_version',
+ 'themes callback' => 'wysiwyg_wymeditor_themes',
+ 'settings callback' => 'wysiwyg_wymeditor_settings',
+ 'plugin callback' => 'wysiwyg_wymeditor_plugins',
+ 'versions' => array(
+ '0.5-rc1' => array(
+ 'js files' => array('wymeditor.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_wymeditor_version($editor) {
+ $script = $editor['library path'] . '/jquery.wymeditor.js';
+ if (!file_exists($script)) {
+ return;
+ }
+ $script = fopen($script, 'r');
+ fgets($script);
+ $line = fgets($script);
+ if (preg_match('@version\s+([0-9a-z\.-]+)@', $line, $version)) {
+ fclose($script);
+ return $version[1];
+ }
+ fclose($script);
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * An array of theme names. The first returned name should be the default
+ * theme name.
+ */
+function wysiwyg_wymeditor_themes($editor, $profile) {
+ return array('compact', 'default', 'minimal', 'silver', 'twopanels');
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_wymeditor_settings($editor, $config, $theme) {
+ // @todo Setup $library in wysiwyg_load_editor() already.
+ $library = (isset($editor['library']) ? $editor['library'] : key($editor['libraries']));
+ $settings = array(
+ 'basePath' => base_path() . $editor['library path'] . '/',
+ 'wymPath' => $editor['libraries'][$library]['files'][0],
+ // @todo Does not work in Drupal; jQuery can live anywhere.
+ 'jQueryPath' => base_path() . 'misc/jquery.js',
+ 'updateSelector' => '.form-submit',
+ 'skin' => $theme,
+ );
+
+ if (isset($config['language'])) {
+ $settings['lang'] = $config['language'];
+ }
+
+ // Add configured buttons.
+ $settings['toolsItems'] = array();
+ if (!empty($config['buttons'])) {
+ $buttoninfo = _wysiwyg_wymeditor_button_info();
+ $plugins = wysiwyg_get_plugins($editor['name']);
+ foreach ($config['buttons'] as $plugin => $buttons) {
+ foreach ($buttons as $button => $enabled) {
+ // Iterate separately over buttons and extensions properties.
+ foreach (array('buttons', 'extensions') as $type) {
+ // Skip unavailable plugins.
+ if (!isset($plugins[$plugin][$type][$button])) {
+ continue;
+ }
+ // Add buttons.
+ if ($type == 'buttons') {
+ // Merge meta-data for internal default buttons.
+ if (isset($buttoninfo[$button])) {
+ $buttoninfo[$button] += array('name' => $button);
+ $settings['toolsItems'][] = $buttoninfo[$button];
+ }
+ // For custom buttons, try to provide a valid button definition.
+ else {
+ $settings['toolsItems'][] = array(
+ 'name' => $button,
+ 'title' => $plugins[$plugin][$type][$button],
+ 'css' => 'wym_tools_' . $button,
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!empty($config['block_formats'])) {
+ $containers = array(
+ 'p' => 'Paragraph',
+ 'h1' => 'Heading_1',
+ 'h2' => 'Heading_2',
+ 'h3' => 'Heading_3',
+ 'h4' => 'Heading_4',
+ 'h5' => 'Heading_5',
+ 'h6' => 'Heading_6',
+ 'pre' => 'Preformatted',
+ 'blockquote' => 'Blockquote',
+ 'th' => 'Table_Header',
+ );
+ foreach (explode(',', $config['block_formats']) as $tag) {
+ if (isset($containers[$tag])) {
+ $settings['containersItems'][] = array(
+ 'name' => strtoupper($tag),
+ 'title' => $containers[$tag],
+ 'css' => 'wym_containers_' . $tag,
+ );
+ }
+ }
+ }
+
+ if (isset($config['css_setting'])) {
+ if ($config['css_setting'] == 'theme') {
+ // WYMeditor only supports one CSS file currently.
+ $css = wysiwyg_get_css();
+ $settings['stylesheet'] = reset($css);
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['stylesheet'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))));
+ }
+ }
+
+ return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_wymeditor_plugins($editor) {
+ $plugins = array(
+ 'default' => array(
+ 'buttons' => array(
+ 'Bold' => t('Bold'), 'Italic' => t('Italic'),
+ 'InsertOrderedList' => t('Numbered list'), 'InsertUnorderedList' => t('Bullet list'),
+ 'Outdent' => t('Outdent'), 'Indent' => t('Indent'),
+ 'Undo' => t('Undo'), 'Redo' => t('Redo'),
+ 'CreateLink' => t('Link'), 'Unlink' => t('Unlink'),
+ 'InsertImage' => t('Image'),
+ 'Superscript' => t('Superscript'), 'Subscript' => t('Subscript'),
+ 'ToggleHtml' => t('Source code'),
+ 'Paste' => t('Paste'),
+ 'InsertTable' => t('Table'),
+ 'Preview' => t('Preview'),
+ ),
+ 'internal' => TRUE,
+ ),
+ );
+ return $plugins;
+}
+
+/**
+ * Helper function to provide additional meta-data for internal default buttons.
+ */
+function _wysiwyg_wymeditor_button_info() {
+ return array(
+ 'Bold' => array('title' => 'Strong', 'css' => 'wym_tools_strong'),
+ 'Italic' => array('title' => 'Emphasis', 'css' => 'wym_tools_emphasis'),
+ 'Superscript' => array('title' => 'Superscript', 'css' => 'wym_tools_superscript'),
+ 'Subscript' => array('title' => 'Subscript', 'css' => 'wym_tools_subscript'),
+ 'InsertOrderedList' => array('title' => 'Ordered_List', 'css' => 'wym_tools_ordered_list'),
+ 'InsertUnorderedList' => array('title' => 'Unordered_List', 'css' => 'wym_tools_unordered_list'),
+ 'Indent' => array('title' => 'Indent', 'css' => 'wym_tools_indent'),
+ 'Outdent' => array('title' => 'Outdent', 'css' => 'wym_tools_outdent'),
+ 'Undo' => array('title' => 'Undo', 'css' => 'wym_tools_undo'),
+ 'Redo' => array('title' => 'Redo', 'css' => 'wym_tools_redo'),
+ 'CreateLink' => array('title' => 'Link', 'css' => 'wym_tools_link'),
+ 'Unlink' => array('title' => 'Unlink', 'css' => 'wym_tools_unlink'),
+ 'InsertImage' => array('title' => 'Image', 'css' => 'wym_tools_image'),
+ 'InsertTable' => array('title' => 'Table', 'css' => 'wym_tools_table'),
+ 'Paste' => array('title' => 'Paste_From_Word', 'css' => 'wym_tools_paste'),
+ 'ToggleHtml' => array('title' => 'HTML', 'css' => 'wym_tools_html'),
+ 'Preview' => array('title' => 'Preview', 'css' => 'wym_tools_preview'),
+ );
+}
diff --git a/sites/all/modules/wysiwyg/editors/yui.inc b/sites/all/modules/wysiwyg/editors/yui.inc
new file mode 100644
index 000000000..36d0a5968
--- /dev/null
+++ b/sites/all/modules/wysiwyg/editors/yui.inc
@@ -0,0 +1,339 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for YUI editor.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_yui_editor() {
+ $editor['yui'] = array(
+ 'title' => 'YUI editor',
+ 'vendor url' => 'http://developer.yahoo.com/yui/editor/',
+ 'download url' => 'http://developer.yahoo.com/yui/download/',
+ 'library path' => wysiwyg_get_path('yui') . '/build',
+ 'libraries' => array(
+ 'min' => array(
+ 'title' => 'Minified',
+ 'files' => array(
+ 'yahoo-dom-event/yahoo-dom-event.js',
+ 'animation/animation-min.js',
+ 'element/element-min.js',
+ 'container/container-min.js',
+ 'menu/menu-min.js',
+ 'button/button-min.js',
+ 'editor/editor-min.js',
+ ),
+ ),
+ 'src' => array(
+ 'title' => 'Source',
+ 'files' => array(
+ 'yahoo-dom-event/yahoo-dom-event.js',
+ 'animation/animation.js',
+ 'element/element.js',
+ 'container/container.js',
+ 'menu/menu.js',
+ 'button/button.js',
+ 'editor/editor.js',
+ ),
+ ),
+ ),
+ 'version callback' => 'wysiwyg_yui_version',
+ 'themes callback' => 'wysiwyg_yui_themes',
+ 'load callback' => 'wysiwyg_yui_load',
+ 'settings callback' => 'wysiwyg_yui_settings',
+ 'plugin callback' => 'wysiwyg_yui_plugins',
+ 'plugin settings callback' => 'wysiwyg_yui_plugin_settings',
+ 'proxy plugin' => array(
+ 'drupal' => array(
+ 'load' => TRUE,
+ 'proxy' => TRUE,
+ ),
+ ),
+ 'proxy plugin settings callback' => 'wysiwyg_yui_proxy_plugin_settings',
+ 'versions' => array(
+ '2.7.0' => array(
+ 'js files' => array('yui.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Detect editor version.
+ *
+ * @param $editor
+ * An array containing editor properties as returned from hook_editor().
+ *
+ * @return
+ * The installed editor version.
+ */
+function wysiwyg_yui_version($editor) {
+ $library = $editor['library path'] . '/editor/editor.js';
+ if (!file_exists($library)) {
+ return;
+ }
+ $library = fopen($library, 'r');
+ $max_lines = 10;
+ while ($max_lines && $line = fgets($library, 60)) {
+ if (preg_match('@version:\s([0-9\.]+)@', $line, $version)) {
+ fclose($library);
+ return $version[1];
+ }
+ $max_lines--;
+ }
+ fclose($library);
+}
+
+/**
+ * Determine available editor themes or check/reset a given one.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * An array of theme names. The first returned name should be the default
+ * theme name.
+ */
+function wysiwyg_yui_themes($editor, $profile) {
+ return array('sam');
+}
+
+/**
+ * Perform additional actions upon loading this editor.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $library
+ * The internal library name (array key) to use.
+ */
+function wysiwyg_yui_load($editor, $library) {
+ drupal_add_css($editor['library path'] . '/menu/assets/skins/sam/menu.css');
+ drupal_add_css($editor['library path'] . '/button/assets/skins/sam/button.css');
+ drupal_add_css($editor['library path'] . '/fonts/fonts-min.css');
+ drupal_add_css($editor['library path'] . '/container/assets/skins/sam/container.css');
+ drupal_add_css($editor['library path'] . '/editor/assets/skins/sam/editor.css');
+}
+
+/**
+ * Return runtime editor settings for a given wysiwyg profile.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $config
+ * An array containing wysiwyg editor profile settings.
+ * @param $theme
+ * The name of a theme/GUI/skin to use.
+ *
+ * @return
+ * A settings array to be populated in
+ * Drupal.settings.wysiwyg.configs.{editor}
+ */
+function wysiwyg_yui_settings($editor, $config, $theme) {
+ $settings = array(
+ 'theme' => $theme,
+ 'animate' => TRUE,
+ 'handleSubmit' => TRUE,
+ 'markup' => 'xhtml',
+ 'ptags' => TRUE,
+ );
+
+ if (isset($config['path_loc']) && $config['path_loc'] != 'none') {
+ $settings['dompath'] = $config['path_loc'];
+ }
+ // Enable auto-height feature when editor should be resizable.
+ if (!empty($config['resizing'])) {
+ $settings['autoHeight'] = TRUE;
+ }
+
+ $settings += array(
+ 'toolbar' => array(
+ 'collapse' => FALSE,
+ 'draggable' => TRUE,
+ 'buttonType' => 'advanced',
+ 'buttons' => array(),
+ ),
+ );
+ if (!empty($config['buttons'])) {
+ $buttons = array();
+ foreach ($config['buttons'] as $plugin => $enabled_buttons) {
+ foreach ($enabled_buttons as $button => $enabled) {
+ $extra = array();
+ if ($button == 'heading') {
+ $extra = array('menu' => array(
+ array('text' => 'Normal', 'value' => 'none', 'checked' => TRUE),
+ ));
+ if (!empty($config['block_formats'])) {
+ $headings = array(
+ 'p' => array('text' => 'Paragraph', 'value' => 'p'),
+ 'h1' => array('text' => 'Heading 1', 'value' => 'h1'),
+ 'h2' => array('text' => 'Heading 2', 'value' => 'h2'),
+ 'h3' => array('text' => 'Heading 3', 'value' => 'h3'),
+ 'h4' => array('text' => 'Heading 4', 'value' => 'h4'),
+ 'h5' => array('text' => 'Heading 5', 'value' => 'h5'),
+ 'h6' => array('text' => 'Heading 6', 'value' => 'h6'),
+ );
+ foreach (explode(',', $config['block_formats']) as $tag) {
+ if (isset($headings[$tag])) {
+ $extra['menu'][] = $headings[$tag];
+ }
+ }
+ }
+ }
+ elseif ($button == 'fontname') {
+ $extra = array('menu' => array(
+ array('text' => 'Arial', 'checked' => TRUE),
+ array('text' => 'Arial Black'),
+ array('text' => 'Comic Sans MS'),
+ array('text' => 'Courier New'),
+ array('text' => 'Lucida Console'),
+ array('text' => 'Tahoma'),
+ array('text' => 'Times New Roman'),
+ array('text' => 'Trebuchet MS'),
+ array('text' => 'Verdana'),
+ ));
+ }
+ $buttons[] = wysiwyg_yui_button_setting($editor, $plugin, $button, $extra);
+ }
+ }
+ // Group buttons in a dummy group.
+ $buttons = array('group' => 'default', 'label' => '', 'buttons' => $buttons);
+ $settings['toolbar']['buttons'] = array($buttons);
+ }
+
+ if (isset($config['css_setting'])) {
+ if ($config['css_setting'] == 'theme') {
+ $settings['extracss'] = wysiwyg_get_css();
+ }
+ elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
+ $settings['extracss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))));
+ $settings['extracss'] = explode(',', $settings['extracss']);
+ }
+ // YUI only supports inline CSS, so we need to use @import directives.
+ // Syntax: '@import "/base/path/to/theme/style.css"; '
+ if (!empty($settings['extracss'])) {
+ $settings['extracss'] = '@import "' . implode('"; @import "', $settings['extracss']) . '";';
+ }
+ }
+
+ return $settings;
+}
+
+/**
+ * Create the JavaScript structure for a YUI button.
+ *
+ * @param $editor
+ * A processed hook_editor() array of editor properties.
+ * @param $plugin
+ * The internal name of a plugin.
+ * @param $button
+ * The internal name of a button, defined by $plugin.
+ * @param $extra
+ * (optional) An array containing arbitrary other elements to add to the
+ * resulting button.
+ */
+function wysiwyg_yui_button_setting($editor, $plugin, $button, $extra = array()) {
+ static $plugins;
+
+ if (!isset($plugins)) {
+ $plugins = wysiwyg_get_plugins($editor['name']);
+ }
+
+ // Return a simple separator.
+ if ($button === 'separator') {
+ return array('type' => 'separator');
+ }
+ // Setup defaults.
+ $type = 'push';
+ $label = $plugins[$plugin]['buttons'][$button];
+
+ // Special handling for certain buttons.
+ if (in_array($button, array('heading', 'fontname'))) {
+ $type = 'select';
+ $label = $extra['menu'][0]['text'];
+ }
+ elseif (in_array($button, array('fontsize'))) {
+ $type = 'spin';
+ }
+ elseif (in_array($button, array('forecolor', 'backcolor'))) {
+ $type = 'color';
+ }
+
+ $button = array(
+ 'type' => $type,
+ 'label' => $label,
+ 'value' => $button,
+ );
+ // Add arbitrary other elements, if defined.
+ if (!empty($extra)) {
+ $button = array_merge($button, $extra);
+ }
+ return $button;
+}
+
+/**
+ * Build a JS settings array of native external plugins that need to be loaded separately.
+ */
+function wysiwyg_yui_plugin_settings($editor, $profile, $plugins) {
+ $settings = array();
+ foreach ($plugins as $name => $plugin) {
+ if (!empty($plugin['load'])) {
+ // Add path for native external plugins; internal ones are loaded
+ // automatically.
+ if (empty($plugin['internal']) && isset($plugin['path'])) {
+ $settings[$name] = base_path() . $plugin['path'];
+ }
+ }
+ }
+ return $settings;
+}
+
+/**
+ * Build a JS settings array for Drupal plugins loaded via the proxy plugin.
+ */
+function wysiwyg_yui_proxy_plugin_settings($editor, $profile, $plugins) {
+ $settings = array();
+ foreach ($plugins as $name => $plugin) {
+ // Populate required plugin settings.
+ $settings[$name] = $plugin['dialog settings'] + array(
+ 'title' => $plugin['title'],
+ 'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'],
+ 'iconTitle' => $plugin['icon title'],
+ // @todo These should only be set if the plugin defined them.
+ 'css' => base_path() . $plugin['css path'] . '/' . $plugin['css file'],
+ );
+ }
+ return $settings;
+}
+
+/**
+ * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_yui_plugins($editor) {
+ return array(
+ 'default' => array(
+ 'buttons' => array(
+ 'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
+ 'strikethrough' => t('Strike-through'),
+ 'justifyleft' => t('Align left'), 'justifycenter' => t('Align center'), 'justifyright' => t('Align right'), 'justifyfull' => t('Justify'),
+ 'insertunorderedlist' => t('Bullet list'), 'insertorderedlist' => t('Numbered list'),
+ 'outdent' => t('Outdent'), 'indent' => t('Indent'),
+ 'undo' => t('Undo'), 'redo' => t('Redo'),
+ 'createlink' => t('Link'),
+ 'insertimage' => t('Image'),
+ 'forecolor' => t('Font Color'), 'backcolor' => t('Background Color'),
+ 'superscript' => t('Sup'), 'subscript' => t('Sub'),
+ 'hiddenelements' => t('Show/hide hidden elements'),
+ 'removeformat' => t('Remove format'),
+ 'heading' => t('HTML block format'), 'fontname' => t('Font'), 'fontsize' => t('Font size'),
+ ),
+ 'internal' => TRUE,
+ ),
+ );
+}
+
diff --git a/sites/all/modules/wysiwyg/plugins/break.inc b/sites/all/modules/wysiwyg/plugins/break.inc
new file mode 100644
index 000000000..887d5905a
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break.inc
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Wysiwyg API integration on behalf of Node module.
+ */
+
+/**
+ * Implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_break_plugin() {
+ $plugins['break'] = array(
+ 'title' => t('Teaser break'),
+ 'vendor url' => 'http://drupal.org/project/wysiwyg',
+ 'icon file' => 'break.gif',
+ 'icon title' => t('Separate the teaser and body of this content'),
+ 'settings' => array(),
+ );
+ return $plugins;
+}
+
diff --git a/sites/all/modules/wysiwyg/plugins/break/break.css b/sites/all/modules/wysiwyg/plugins/break/break.css
new file mode 100644
index 000000000..4aaab7617
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/break.css
@@ -0,0 +1,10 @@
+
+.wysiwyg-break {
+ display: block;
+ border: 0;
+ border-top: 1px dotted #ccc;
+ margin-top: 1em;
+ width: 100%;
+ height: 12px;
+ background: transparent url(images/breaktext.gif) no-repeat center top;
+}
diff --git a/sites/all/modules/wysiwyg/plugins/break/break.js b/sites/all/modules/wysiwyg/plugins/break/break.js
new file mode 100644
index 000000000..54aac4cdc
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/break.js
@@ -0,0 +1,68 @@
+(function ($) {
+
+// @todo Array syntax required; 'break' is a predefined token in JavaScript.
+Drupal.wysiwyg.plugins['break'] = {
+
+ /**
+ * Return whether the passed node belongs to this plugin.
+ */
+ isNode: function(node) {
+ return ($(node).is('img.wysiwyg-break'));
+ },
+
+ /**
+ * Execute the button.
+ */
+ invoke: function(data, settings, instanceId) {
+ if (data.format == 'html') {
+ // Prevent duplicating a teaser break.
+ if ($(data.node).is('img.wysiwyg-break')) {
+ return;
+ }
+ var content = this._getPlaceholder(settings);
+ }
+ else {
+ // Prevent duplicating a teaser break.
+ // @todo data.content is the selection only; needs access to complete content.
+ if (data.content.match(/<!--break-->/)) {
+ return;
+ }
+ var content = '<!--break-->';
+ }
+ if (typeof content != 'undefined') {
+ Drupal.wysiwyg.instances[instanceId].insert(content);
+ }
+ },
+
+ /**
+ * Replace all <!--break--> tags with images.
+ */
+ attach: function(content, settings, instanceId) {
+ content = content.replace(/<!--break-->/g, this._getPlaceholder(settings));
+ return content;
+ },
+
+ /**
+ * Replace images with <!--break--> tags in content upon detaching editor.
+ */
+ detach: function(content, settings, instanceId) {
+ var $content = $('<div>' + content + '</div>'); // No .outerHTML() in jQuery :(
+ // #404532: document.createComment() required or IE will strip the comment.
+ // #474908: IE 8 breaks when using jQuery methods to replace the elements.
+ // @todo Add a generic implementation for all Drupal plugins for this.
+ $.each($('img.wysiwyg-break', $content), function (i, elem) {
+ elem.parentNode.insertBefore(document.createComment('break'), elem);
+ elem.parentNode.removeChild(elem);
+ });
+ return $content.html();
+ },
+
+ /**
+ * Helper function to return a HTML placeholder.
+ */
+ _getPlaceholder: function (settings) {
+ return '<img src="' + settings.path + '/images/spacer.gif" alt="&lt;--break-&gt;" title="&lt;--break--&gt;" class="wysiwyg-break drupal-content" />';
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/plugins/break/images/break.gif b/sites/all/modules/wysiwyg/plugins/break/images/break.gif
new file mode 100644
index 000000000..4ff564d58
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/images/break.gif
Binary files differ
diff --git a/sites/all/modules/wysiwyg/plugins/break/images/breaktext.gif b/sites/all/modules/wysiwyg/plugins/break/images/breaktext.gif
new file mode 100644
index 000000000..619787352
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/images/breaktext.gif
Binary files differ
diff --git a/sites/all/modules/wysiwyg/plugins/break/images/spacer.gif b/sites/all/modules/wysiwyg/plugins/break/images/spacer.gif
new file mode 100644
index 000000000..388486517
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/images/spacer.gif
Binary files differ
diff --git a/sites/all/modules/wysiwyg/plugins/break/langs/ca.js b/sites/all/modules/wysiwyg/plugins/break/langs/ca.js
new file mode 100644
index 000000000..5ead93807
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/langs/ca.js
@@ -0,0 +1,6 @@
+
+tinyMCE.addToLang('break', {
+ title: 'Inserir marcador de document retallat',
+ desc: 'Generar el punt de separació entre la versió retallada del document i la resta del contingut'
+});
+
diff --git a/sites/all/modules/wysiwyg/plugins/break/langs/de.js b/sites/all/modules/wysiwyg/plugins/break/langs/de.js
new file mode 100644
index 000000000..d869a6977
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/langs/de.js
@@ -0,0 +1,6 @@
+
+tinyMCE.addToLang('break', {
+ title: 'Anrisstext trennen',
+ desc: 'Separiert den Anrisstext und Textkörper des Inhalts an dieser Stelle'
+});
+
diff --git a/sites/all/modules/wysiwyg/plugins/break/langs/en.js b/sites/all/modules/wysiwyg/plugins/break/langs/en.js
new file mode 100644
index 000000000..6d75ef734
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/langs/en.js
@@ -0,0 +1,6 @@
+
+tinyMCE.addToLang('break', {
+ title: 'Insert teaser break',
+ desc: 'Separate teaser and body of this content'
+});
+
diff --git a/sites/all/modules/wysiwyg/plugins/break/langs/es.js b/sites/all/modules/wysiwyg/plugins/break/langs/es.js
new file mode 100644
index 000000000..206023da6
--- /dev/null
+++ b/sites/all/modules/wysiwyg/plugins/break/langs/es.js
@@ -0,0 +1,6 @@
+
+tinyMCE.addToLang('break', {
+ title: 'Insertar marcador de documento recortado',
+ desc: 'Generar el punto de separación entre la versión recortada del documento y el resto del contenido'
+});
+
diff --git a/sites/all/modules/wysiwyg/tests/wysiwyg.test b/sites/all/modules/wysiwyg/tests/wysiwyg.test
new file mode 100644
index 000000000..263563c28
--- /dev/null
+++ b/sites/all/modules/wysiwyg/tests/wysiwyg.test
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * @file
+ * Tests for Wysiwyg module.
+ */
+
diff --git a/sites/all/modules/wysiwyg/tests/wysiwyg_test.info b/sites/all/modules/wysiwyg/tests/wysiwyg_test.info
new file mode 100644
index 000000000..feee12443
--- /dev/null
+++ b/sites/all/modules/wysiwyg/tests/wysiwyg_test.info
@@ -0,0 +1,14 @@
+name = Wysiwyg testing
+description = Tests Wysiwyg module functionality. Do not enable.
+core = 7.x
+package = Testing
+hidden = TRUE
+dependencies[] = wysiwyg
+files[] = wysiwyg_test.module
+
+; Information added by drupal.org packaging script on 2012-10-02
+version = "7.x-2.2"
+core = "7.x"
+project = "wysiwyg"
+datestamp = "1349213776"
+
diff --git a/sites/all/modules/wysiwyg/tests/wysiwyg_test.install b/sites/all/modules/wysiwyg/tests/wysiwyg_test.install
new file mode 100644
index 000000000..fc4f5271c
--- /dev/null
+++ b/sites/all/modules/wysiwyg/tests/wysiwyg_test.install
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * @file
+ * Installation functionality for Wysiwyg testing module.
+ */
+
diff --git a/sites/all/modules/wysiwyg/tests/wysiwyg_test.module b/sites/all/modules/wysiwyg/tests/wysiwyg_test.module
new file mode 100644
index 000000000..c2dd9a917
--- /dev/null
+++ b/sites/all/modules/wysiwyg/tests/wysiwyg_test.module
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Testing functionality for Wysiwyg module.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function wysiwyg_test_menu() {
+ $items['wysiwyg-test/ajax'] = array(
+ 'title' => 'Ajaxified form',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('wysiwyg_test_ajax_form'),
+ 'access callback' => TRUE,
+ );
+ return $items;
+}
+
+/**
+ * Form constructor for an ajaxified form lazy-loading a textarea.
+ */
+function wysiwyg_test_ajax_form($form, &$form_state) {
+ $form['enable'] = array(
+ '#type' => 'checkbox',
+ '#title' => 'Load textarea',
+ '#ajax' => array(
+ 'callback' => 'wysiwyg_test_ajax_form_callback',
+ 'wrapper' => 'ajax-wrapper',
+ ),
+ );
+ $form['wrapper'] = array(
+ '#type' => 'container',
+ '#id' => 'ajax-wrapper',
+ );
+ return $form;
+}
+
+/**
+ * #ajax callback for wysiwyg_test_ajax_form().
+ */
+function wysiwyg_test_ajax_form_callback($form, &$form_state) {
+ $form['body'] = array(
+ '#type' => 'text_format',
+ '#default_value' => '',
+ );
+ form_builder($form['form_id']['#value'], $form, $form_state);
+ return $form['body'];
+}
diff --git a/sites/all/modules/wysiwyg/wysiwyg-dialog-page.tpl.php b/sites/all/modules/wysiwyg/wysiwyg-dialog-page.tpl.php
new file mode 100644
index 000000000..3e77d79b4
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg-dialog-page.tpl.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Theme implementation to display a single Wysiwyg (plugin) dialog page.
+ */
+?>
+
+ <div id="page">
+
+ <?php print $messages; ?>
+
+ <div id="main-wrapper"><div id="main" class="clearfix">
+
+ <div id="content" class="column"><div class="section">
+ <a id="main-content"></a>
+ <?php print render($title_prefix); ?>
+ <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
+ <?php print render($title_suffix); ?>
+ <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?>
+ <?php print render($page['help']); ?>
+ <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>
+ <?php print render($page['content']); ?>
+ </div></div> <!-- /.section, /#content -->
+
+ </div></div> <!-- /#main, /#main-wrapper -->
+
+ </div> <!-- /#page -->
diff --git a/sites/all/modules/wysiwyg/wysiwyg.admin.inc b/sites/all/modules/wysiwyg/wysiwyg.admin.inc
new file mode 100644
index 000000000..497e5d4cf
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.admin.inc
@@ -0,0 +1,594 @@
+<?php
+
+/**
+ * @file
+ * Integrate Wysiwyg editors into Drupal.
+ */
+
+/**
+ * Form builder for Wysiwyg profile form.
+ */
+function wysiwyg_profile_form($form, &$form_state, $profile) {
+ // Merge in defaults.
+ $profile = (array) $profile;
+ $profile += array(
+ 'format' => '',
+ 'editor' => '',
+ );
+ if (empty($profile['settings'])) {
+ $profile['settings'] = array();
+ }
+ $profile['settings'] += array(
+ 'default' => TRUE,
+ 'user_choose' => FALSE,
+ 'show_toggle' => TRUE,
+ 'theme' => 'advanced',
+ 'language' => 'en',
+ 'access' => 1,
+ 'access_pages' => "node/*\nuser/*\ncomment/*",
+ 'buttons' => array(),
+ 'toolbar_loc' => 'top',
+ 'toolbar_align' => 'left',
+ 'path_loc' => 'bottom',
+ 'resizing' => TRUE,
+ // Also available, but buggy in TinyMCE 2.x: blockquote,code,dt,dd,samp.
+ 'block_formats' => 'p,address,pre,h2,h3,h4,h5,h6,div',
+ 'verify_html' => TRUE,
+ 'preformatted' => FALSE,
+ 'convert_fonts_to_spans' => TRUE,
+ 'remove_linebreaks' => TRUE,
+ 'apply_source_formatting' => FALSE,
+ 'paste_auto_cleanup_on_paste' => FALSE,
+ 'css_setting' => 'theme',
+ 'css_path' => NULL,
+ 'css_classes' => NULL,
+ );
+ $profile = (object) $profile;
+
+ $formats = filter_formats();
+ $editor = wysiwyg_get_editor($profile->editor);
+ drupal_set_title(t('%editor profile for %format', array('%editor' => $editor['title'], '%format' => $formats[$profile->format]->name)), PASS_THROUGH);
+
+ $form['format'] = array('#type' => 'value', '#value' => $profile->format);
+ $form['input_format'] = array('#type' => 'value', '#value' => $formats[$profile->format]->name);
+ $form['editor'] = array('#type' => 'value', '#value' => $profile->editor);
+
+ $form['basic'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Basic setup'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+
+ $form['basic']['default'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enabled by default'),
+ '#default_value' => $profile->settings['default'],
+ '#return_value' => 1,
+ '#description' => t('The default editor state for users having access to this profile. Users are able to override this state if the next option is enabled.'),
+ );
+
+ $form['basic']['user_choose'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow users to choose default'),
+ '#default_value' => $profile->settings['user_choose'],
+ '#return_value' => 1,
+ '#description' => t('If allowed, users will be able to choose their own editor default state in their user account settings.'),
+ );
+
+ $form['basic']['show_toggle'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show <em>enable/disable rich text</em> toggle link'),
+ '#default_value' => $profile->settings['show_toggle'],
+ '#return_value' => 1,
+ '#description' => t('Whether or not to show the <em>enable/disable rich text</em> toggle link below a textarea. If disabled, the user setting or global default is used (see above).'),
+ );
+
+ $form['basic']['theme'] = array(
+ '#type' => 'hidden',
+ '#value' => $profile->settings['theme'],
+ );
+
+ $form['basic']['language'] = array(
+ '#type' => 'select',
+ '#title' => t('Interface language'),
+ '#default_value' => $profile->settings['language'],
+ );
+ // @see _locale_prepare_predefined_list()
+ require_once DRUPAL_ROOT . '/includes/iso.inc';
+ $predefined = _locale_get_predefined_list();
+ foreach ($predefined as $key => $value) {
+ // Include native name in output, if possible
+ if (count($value) > 1) {
+ $tname = t($value[0]);
+ $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])";
+ }
+ else {
+ $predefined[$key] = t($value[0]);
+ }
+ }
+ asort($predefined);
+ $form['basic']['language']['#options'] = $predefined;
+
+ $form['buttons'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Buttons and plugins'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#tree' => TRUE,
+ '#theme' => 'wysiwyg_admin_button_table',
+ );
+
+ $plugins = wysiwyg_get_plugins($profile->editor);
+ // Generate the button list.
+ foreach ($plugins as $name => $meta) {
+ if (isset($meta['buttons']) && is_array($meta['buttons'])) {
+ foreach ($meta['buttons'] as $button => $title) {
+ $icon = '';
+ if (!empty($meta['path'])) {
+ // @todo Button icon locations are different in editors, editor versions,
+ // and contrib/custom plugins (like Image Assist, f.e.).
+ $img_src = $meta['path'] . "/images/$name.gif";
+ // Handle plugins that have more than one button.
+ if (!file_exists($img_src)) {
+ $img_src = $meta['path'] . "/images/$button.gif";
+ }
+ $icon = file_exists($img_src) ? '<img src="' . base_path() . $img_src . '" title="' . $button . '" style="border: 1px solid grey; vertical-align: middle;" />' : '';
+ }
+ $title = (!empty($icon) ? $icon . ' ' . check_plain($title) : check_plain($title));
+ $form['buttons'][$name][$button] = array(
+ '#type' => 'checkbox',
+ '#title' => $title,
+ '#default_value' => !empty($profile->settings['buttons'][$name][$button]) ? $profile->settings['buttons'][$name][$button] : FALSE,
+ '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL,
+ );
+ }
+ }
+ elseif (isset($meta['extensions']) && is_array($meta['extensions'])) {
+ foreach ($meta['extensions'] as $extension => $title) {
+ $form['buttons'][$name][$extension] = array(
+ '#type' => 'checkbox',
+ '#title' => check_plain($title),
+ '#default_value' => !empty($profile->settings['buttons'][$name][$extension]) ? $profile->settings['buttons'][$name][$extension] : FALSE,
+ '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL,
+ );
+ }
+ }
+ }
+
+ $form['appearance'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Editor appearance'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+
+ $form['appearance']['toolbar_loc'] = array(
+ '#type' => 'select',
+ '#title' => t('Toolbar location'),
+ '#default_value' => $profile->settings['toolbar_loc'],
+ '#options' => array('bottom' => t('Bottom'), 'top' => t('Top')),
+ '#description' => t('This option controls whether the editor toolbar is displayed above or below the editing area.'),
+ );
+
+ $form['appearance']['toolbar_align'] = array(
+ '#type' => 'select',
+ '#title' => t('Button alignment'),
+ '#default_value' => $profile->settings['toolbar_align'],
+ '#options' => array('center' => t('Center'), 'left' => t('Left'), 'right' => t('Right')),
+ '#description' => t('This option controls the alignment of icons in the editor toolbar.'),
+ );
+
+ $form['appearance']['path_loc'] = array(
+ '#type' => 'select',
+ '#title' => t('Path location'),
+ '#default_value' => $profile->settings['path_loc'],
+ '#options' => array('none' => t('Hide'), 'top' => t('Top'), 'bottom' => t('Bottom')),
+ '#description' => t('Where to display the path to HTML elements (i.e. <code>body > table > tr > td</code>).'),
+ );
+
+ $form['appearance']['resizing'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable resizing button'),
+ '#default_value' => $profile->settings['resizing'],
+ '#return_value' => 1,
+ '#description' => t('This option gives you the ability to enable/disable the resizing button. If enabled, the Path location toolbar must be set to "Top" or "Bottom" in order to display the resize icon.'),
+ );
+
+ $form['output'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Cleanup and output'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+
+ $form['output']['verify_html'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Verify HTML'),
+ '#default_value' => $profile->settings['verify_html'],
+ '#return_value' => 1,
+ '#description' => t('If enabled, potentially malicious code like <code>&lt;HEAD&gt;</code> tags will be removed from HTML contents.'),
+ );
+
+ $form['output']['preformatted'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Preformatted'),
+ '#default_value' => $profile->settings['preformatted'],
+ '#return_value' => 1,
+ '#description' => t('If enabled, the editor will insert TAB characters on tab and preserve other whitespace characters just like a PRE element in HTML does.'),
+ );
+
+ $form['output']['convert_fonts_to_spans'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Convert &lt;font&gt; tags to styles'),
+ '#default_value' => $profile->settings['convert_fonts_to_spans'],
+ '#return_value' => 1,
+ '#description' => t('If enabled, HTML tags declaring the font size, font family, font color and font background color will be replaced by inline CSS styles.'),
+ );
+
+ $form['output']['remove_linebreaks'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remove linebreaks'),
+ '#default_value' => $profile->settings['remove_linebreaks'],
+ '#return_value' => 1,
+ '#description' => t('If enabled, the editor will remove most linebreaks from contents. Disabling this option could avoid conflicts with other input filters.'),
+ );
+
+ $form['output']['apply_source_formatting'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Apply source formatting'),
+ '#default_value' => $profile->settings['apply_source_formatting'],
+ '#return_value' => 1,
+ '#description' => t('If enabled, the editor will re-format the HTML source code. Disabling this option could avoid conflicts with other input filters.'),
+ );
+
+ $form['output']['paste_auto_cleanup_on_paste'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Force cleanup on standard paste'),
+ '#default_value' => $profile->settings['paste_auto_cleanup_on_paste'],
+ '#return_value' => 1,
+ '#description' => t('If enabled, the default paste function (CTRL-V or SHIFT-INS) behaves like the "paste from word" plugin function.'),
+ );
+
+ $form['css'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('CSS'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+
+ $form['css']['block_formats'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Block formats'),
+ '#default_value' => $profile->settings['block_formats'],
+ '#size' => 40,
+ '#maxlength' => 250,
+ '#description' => t('Comma separated list of HTML block formats. Possible values: <code>@format-list</code>.', array('@format-list' => 'p,h1,h2,h3,h4,h5,h6,div,blockquote,address,pre,code,dt,dd')),
+ );
+
+ $form['css']['css_setting'] = array(
+ '#type' => 'select',
+ '#title' => t('Editor CSS'),
+ '#default_value' => $profile->settings['css_setting'],
+ '#options' => array('theme' => t('Use theme CSS'), 'self' => t('Define CSS'), 'none' => t('Editor default CSS')),
+ '#description' => t('Defines the CSS to be used in the editor area.<br />Use theme CSS - loads stylesheets from current site theme.<br/>Define CSS - enter path for stylesheet files below.<br />Editor default CSS - uses default stylesheets from editor.'),
+ );
+
+ $form['css']['css_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('CSS path'),
+ '#default_value' => $profile->settings['css_path'],
+ '#size' => 40,
+ '#maxlength' => 255,
+ '#description' => t('If "Define CSS" was selected above, enter path to a CSS file or a list of CSS files separated by a comma.') . '<br />' . t('Available tokens: <code>%b</code> (base path, eg: <code>/</code>), <code>%t</code> (path to theme, eg: <code>themes/garland</code>)') . '<br />' . t('Example:') . ' css/editor.css,/themes/garland/style.css,%b%t/style.css,http://example.com/external.css',
+ );
+
+ $form['css']['css_classes'] = array(
+ '#type' => 'textarea',
+ '#title' => t('CSS classes'),
+ '#default_value' => $profile->settings['css_classes'],
+ '#description' => t('Optionally define CSS classes for the "Font style" dropdown list.<br />Enter one class on each line in the format: !format. Example: !example<br />If left blank, CSS classes are automatically imported from all loaded stylesheet(s).', array('!format' => '<code>[title]=[class]</code>', '!example' => 'My heading=header1')),
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#weight' => 100,
+ );
+ $form['cancel'] = array(
+ '#value' => l(t('Cancel'), 'admin/config/content/wysiwyg'),
+ '#weight' => 110,
+ );
+
+ // Supply contextual information for other callbacks and handlers.
+ // @todo Modernize this form for D7+ and declare these earlier.
+ // $profile is the primary object of this form, and as an entity, usually
+ // expected to live in $form_state[$entity_type].
+ $form_state['wysiwyg_profile'] = $profile;
+ $form_state['wysiwyg']['editor'] = $editor;
+ $form_state['wysiwyg']['plugins'] = $plugins;
+
+ // Allow editor library specific changes to be made to the form.
+ if (isset($editor['settings form callback'])) {
+ $editor['settings form callback']($form, $form_state);
+ }
+
+ return $form;
+}
+
+/**
+ * Submit callback for Wysiwyg profile form.
+ *
+ * @see wysiwyg_profile_form()
+ */
+function wysiwyg_profile_form_submit($form, &$form_state) {
+ $values = $form_state['values'];
+ if (isset($values['buttons'])) {
+ // Store only enabled buttons for each plugin.
+ foreach ($values['buttons'] as $plugin => $buttons) {
+ $values['buttons'][$plugin] = array_filter($values['buttons'][$plugin]);
+ }
+ // Store only enabled plugins.
+ $values['buttons'] = array_filter($values['buttons']);
+ }
+ // Remove any white-space from 'block_formats' setting, since editor
+ // implementations rely on a comma-separated list to explode().
+ $values['block_formats'] = preg_replace('@\s+@', '', $values['block_formats']);
+
+ // Remove input format name.
+ $format = $values['format'];
+ $input_format = $values['input_format'];
+ $editor = $values['editor'];
+ unset($values['format'], $values['input_format'], $values['editor']);
+
+ // Remove FAPI values.
+ // @see system_settings_form_submit()
+ unset($values['submit'], $values['form_id'], $values['op'], $values['form_token'], $values['form_build_id']);
+
+ // Insert new profile data.
+ db_merge('wysiwyg')
+ ->key(array('format' => $format))
+ ->fields(array(
+ 'editor' => $editor,
+ 'settings' => serialize($values),
+ ))
+ ->execute();
+ wysiwyg_profile_cache_clear();
+
+ drupal_set_message(t('Wysiwyg profile for %format has been saved.', array('%format' => $input_format)));
+
+ $form_state['redirect'] = 'admin/config/content/wysiwyg';
+}
+
+/**
+ * Layout for the buttons in the Wysiwyg Editor profile form.
+ */
+function theme_wysiwyg_admin_button_table($variables) {
+ $form = $variables['form'];
+ $buttons = array();
+
+ // Flatten forms array.
+ foreach (element_children($form) as $name) {
+ foreach (element_children($form[$name]) as $button) {
+ $buttons[] = drupal_render($form[$name][$button]);
+ }
+ }
+
+ // Split checkboxes into rows with 3 columns.
+ $total = count($buttons);
+ $rows = array();
+ for ($i = 0; $i < $total; $i += 3) {
+ $row = array();
+ $row_buttons = array_slice($buttons, $i, 3) + array_fill(0, 3, array());
+ foreach ($row_buttons as $row_button) {
+ $row[] = array('data' => $row_button);
+ }
+ $rows[] = $row;
+ }
+
+ $output = theme('table', array('rows' => $rows, 'attributes' => array('width' => '100%')));
+
+ return $output;
+}
+
+/**
+ * Display overview of setup Wysiwyg Editor profiles; menu callback.
+ */
+function wysiwyg_profile_overview($form, &$form_state) {
+ include_once './includes/install.inc';
+
+ // Check which wysiwyg editors are installed.
+ $editors = wysiwyg_get_all_editors();
+ $count = count($editors);
+ $status = array();
+ $options = array('' => t('No editor'));
+
+ // D7's seven theme displays links in table headers as block elements.
+ drupal_add_css('table.system-status-report th a {display: inline;}', 'inline');
+
+ foreach ($editors as $name => $editor) {
+ $status[$name] = array(
+ 'severity' => (isset($editor['error']) ? REQUIREMENT_ERROR : ($editor['installed'] ? REQUIREMENT_OK : REQUIREMENT_INFO)),
+ 'title' => t('<a href="!vendor-url">@editor</a> (<a href="!download-url">Download</a>)', array('!vendor-url' => $editor['vendor url'], '@editor' => $editor['title'], '!download-url' => $editor['download url'])),
+ 'value' => (isset($editor['installed version']) ? $editor['installed version'] : t('Not installed.')),
+ 'description' => (isset($editor['error']) ? $editor['error'] : ''),
+ );
+ if ($editor['installed']) {
+ $options[$name] = $editor['title'] . (isset($editor['installed version']) ? ' ' . $editor['installed version'] : '');
+ }
+ else {
+ // Build on-site installation instructions.
+ // @todo Setup $library in wysiwyg_load_editor() already.
+ $library = (isset($editor['library']) ? $editor['library'] : key($editor['libraries']));
+ $targs = array(
+ '@editor-path' => $editor['editor path'],
+ '@library-filepath' => $editor['library path'] . '/' . (isset($editor['libraries'][$library]['files'][0]) ? $editor['libraries'][$library]['files'][0] : key($editor['libraries'][$library]['files'])),
+ );
+ $instructions = '<p>' . t('Extract the archive and copy its contents into a new folder in the following location:<br /><code>@editor-path</code>', $targs) . '</p>';
+ $instructions .= '<p>' . t('So the actual library can be found at:<br /><code>@library-filepath</code>', $targs) . '</p>';
+
+ // Add any install notes.
+ if (!empty($editor['install note callback']) && function_exists($editor['install note callback'])) {
+ $instructions .= '<div class="editor-install-note">' . $editor['install note callback']() . '</div>';
+ }
+
+ $status[$name]['description'] .= $instructions;
+ $count--;
+ }
+ // In case there is an error, always show installation instructions.
+ if (isset($editor['error'])) {
+ $show_instructions = TRUE;
+ }
+ }
+ if (!$count) {
+ $show_instructions = TRUE;
+ }
+ $form['status'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Installation instructions'),
+ '#collapsible' => TRUE,
+ '#collapsed' => !isset($show_instructions),
+ '#description' => (!$count ? t('There are no editor libraries installed currently. The following list contains a list of currently supported editors:') : ''),
+ '#weight' => 10,
+ );
+ $form['status']['report'] = array('#markup' => theme('status_report', array('requirements' => $status)));
+
+ if (!$count) {
+ return $form;
+ }
+
+ $formats = filter_formats();
+ $profiles = wysiwyg_profile_load_all();
+ $form['formats'] = array(
+ '#type' => 'item',
+ '#description' => t('To assign a different editor to a text format, click "delete" to remove the existing first.'),
+ '#tree' => TRUE,
+ );
+
+ $enable_save = FALSE;
+ foreach ($formats as $id => $format) {
+ $form['formats'][$id]['name'] = array(
+ '#markup' => check_plain($format->name),
+ );
+ // Only display editor selection for associated input formats to avoid
+ // confusion about disabled selection.
+ if (isset($profiles[$id]) && !empty($profiles[$id]->editor)) {
+ $editor_name = $profiles[$id]->editor;
+ $installed = !empty($editors[$editor_name]['installed']);
+ $form['formats'][$id]['editor'] = array(
+ '#wysiwyg-editor-name' => $editor_name,
+ );
+ if ($installed) {
+ $form['formats'][$id]['editor']['#markup'] = $options[$editor_name];
+ }
+ else {
+ drupal_set_message(t('Missing %editor library for %format format. Re-install the %editor library or delete the editor profile.', array(
+ '%editor' => $editors[$editor_name]['title'],
+ '%format' => $format->name,
+ )), 'warning');
+ }
+ }
+ else {
+ $form['formats'][$id]['editor'] = array(
+ '#type' => 'select',
+ '#default_value' => '',
+ '#options' => $options,
+ );
+ $enable_save = TRUE;
+ }
+ if (isset($profiles[$id]) && !empty($profiles[$id]->editor)) {
+ $form['formats'][$id]['edit'] = array(
+ '#markup' => l(t('Edit'), "admin/config/content/wysiwyg/profile/$id/edit"),
+ );
+ $form['formats'][$id]['delete'] = array(
+ '#markup' => l(t('Delete'), "admin/config/content/wysiwyg/profile/$id/delete"),
+ );
+ }
+ }
+
+ // Submitting the form when no editors can be selected causes errors.
+ if ($enable_save) {
+ $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+ }
+ return $form;
+}
+
+/**
+ * Return HTML for the Wysiwyg profile overview form.
+ */
+function theme_wysiwyg_profile_overview($variables) {
+ $form = $variables['form'];
+ if (!isset($form['formats'])) {
+ return;
+ }
+ $editors = wysiwyg_get_all_editors();
+ $output = '';
+ $header = array(t('Text format'), t('Editor'), array('data' => t('Operations'), 'colspan' => 2));
+ $rows = array();
+ foreach (element_children($form['formats']) as $item) {
+ $format = &$form['formats'][$item];
+ $row = array(
+ 'data' => array(
+ drupal_render($format['name']),
+ drupal_render($format['editor']),
+ isset($format['edit']) ? drupal_render($format['edit']) : '',
+ isset($format['delete']) ? drupal_render($format['delete']) : '',
+ ),
+ );
+ if (empty($row['data'][1])) {
+ $row['data'][1] = array(
+ 'data' => t('Missing library: @library', array('@library' => $editors[$format['editor']['#wysiwyg-editor-name']]['title'])),
+ 'class' => 'error',
+ );
+ $row['class'] = array('error');
+ }
+ $rows[] = $row;
+ }
+ $form['formats']['table']['#markup'] = theme('table', array('header' => $header, 'rows' => $rows));
+ $output .= drupal_render_children($form);
+ return $output;
+}
+
+/**
+ * Submit callback for Wysiwyg profile overview form.
+ */
+function wysiwyg_profile_overview_submit($form, &$form_state) {
+ foreach ($form_state['values']['formats'] as $format => $values) {
+ db_merge('wysiwyg')
+ ->key(array('format' => $format))
+ ->fields(array(
+ 'editor' => $values['editor'],
+ ))
+ ->execute();
+ }
+ wysiwyg_profile_cache_clear();
+}
+
+/**
+ * Delete editor profile confirmation form.
+ */
+function wysiwyg_profile_delete_confirm($form, &$form_state, $profile) {
+ $formats = filter_formats();
+ $format = $formats[$profile->format];
+ $form['format'] = array('#type' => 'value', '#value' => $format);
+ return confirm_form(
+ $form,
+ t('Are you sure you want to remove the profile for %name?', array('%name' => $format->name)),
+ 'admin/config/content/wysiwyg',
+ t('This action cannot be undone.'), t('Remove'), t('Cancel')
+ );
+}
+
+/**
+ * Submit callback for Wysiwyg profile delete form.
+ *
+ * @see wysiwyg_profile_delete_confirm()
+ */
+function wysiwyg_profile_delete_confirm_submit($form, &$form_state) {
+ $format = $form_state['values']['format'];
+ wysiwyg_profile_delete($format->format);
+ wysiwyg_profile_cache_clear();
+
+ drupal_set_message(t('Wysiwyg profile for %name has been deleted.', array('%name' => $format->name)));
+ $form_state['redirect'] = 'admin/config/content/wysiwyg';
+}
diff --git a/sites/all/modules/wysiwyg/wysiwyg.api.js b/sites/all/modules/wysiwyg/wysiwyg.api.js
new file mode 100644
index 000000000..0318b0b42
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.api.js
@@ -0,0 +1,97 @@
+
+/**
+ * Wysiwyg plugin button implementation for Awesome plugin.
+ */
+Drupal.wysiwyg.plugins.awesome = {
+ /**
+ * Return whether the passed node belongs to this plugin.
+ *
+ * @param node
+ * The currently focused DOM element in the editor content.
+ */
+ isNode: function(node) {
+ return ($(node).is('img.mymodule-awesome'));
+ },
+
+ /**
+ * Execute the button.
+ *
+ * @param data
+ * An object containing data about the current selection:
+ * - format: 'html' when the passed data is HTML content, 'text' when the
+ * passed data is plain-text content.
+ * - node: When 'format' is 'html', the focused DOM element in the editor.
+ * - content: The textual representation of the focused/selected editor
+ * content.
+ * @param settings
+ * The plugin settings, as provided in the plugin's PHP include file.
+ * @param instanceId
+ * The ID of the current editor instance.
+ */
+ invoke: function(data, settings, instanceId) {
+ // Generate HTML markup.
+ if (data.format == 'html') {
+ // Prevent duplicating a teaser break.
+ if ($(data.node).is('img.mymodule-awesome')) {
+ return;
+ }
+ var content = this._getPlaceholder(settings);
+ }
+ // Generate plain text.
+ else {
+ var content = '<!--break-->';
+ }
+ // Insert new content into the editor.
+ if (typeof content != 'undefined') {
+ Drupal.wysiwyg.instances[instanceId].insert(content);
+ }
+ },
+
+ /**
+ * Prepare all plain-text contents of this plugin with HTML representations.
+ *
+ * Optional; only required for "inline macro tag-processing" plugins.
+ *
+ * @param content
+ * The plain-text contents of a textarea.
+ * @param settings
+ * The plugin settings, as provided in the plugin's PHP include file.
+ * @param instanceId
+ * The ID of the current editor instance.
+ */
+ attach: function(content, settings, instanceId) {
+ content = content.replace(/<!--break-->/g, this._getPlaceholder(settings));
+ return content;
+ },
+
+ /**
+ * Process all HTML placeholders of this plugin with plain-text contents.
+ *
+ * Optional; only required for "inline macro tag-processing" plugins.
+ *
+ * @param content
+ * The HTML content string of the editor.
+ * @param settings
+ * The plugin settings, as provided in the plugin's PHP include file.
+ * @param instanceId
+ * The ID of the current editor instance.
+ */
+ detach: function(content, settings, instanceId) {
+ var $content = $('<div>' + content + '</div>');
+ $.each($('img.mymodule-awesome', $content), function (i, elem) {
+ //...
+ });
+ return $content.html();
+ },
+
+ /**
+ * Helper function to return a HTML placeholder.
+ *
+ * The 'drupal-content' CSS class is required for HTML elements in the editor
+ * content that shall not trigger any editor's native buttons (such as the
+ * image button for this example placeholder markup).
+ */
+ _getPlaceholder: function (settings) {
+ return '<img src="' + settings.path + '/images/spacer.gif" alt="&lt;--break-&gt;" title="&lt;--break--&gt;" class="wysiwyg-break drupal-content" />';
+ }
+};
diff --git a/sites/all/modules/wysiwyg/wysiwyg.api.php b/sites/all/modules/wysiwyg/wysiwyg.api.php
new file mode 100644
index 000000000..c4d885798
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.api.php
@@ -0,0 +1,283 @@
+<?php
+
+/**
+ * @file
+ * API documentation for Wysiwyg module.
+ *
+ * To implement a "Drupal plugin" button, you need to write a Wysiwyg plugin:
+ * - Implement hook_wysiwyg_include_directory() to register the directory
+ * containing plugin definitions.
+ * - In each plugin definition file, implement hook_INCLUDE_plugin().
+ * - For each plugin button, implement a JavaScript integration and an icon for
+ * the button.
+ *
+ * @todo Icon: Recommended size and type of image.
+ *
+ * For example implementations you may want to look at
+ * - Image Assist (img_assist)
+ * - Teaser break plugin (plugins/break; part of WYSIWYG)
+ * - IMCE (imce_wysiwyg)
+ */
+
+/**
+ * Return an array of native editor plugins.
+ *
+ * Only to be used for native (internal) editor plugins.
+ *
+ * @see hook_wysiwyg_include_directory()
+ *
+ * @param $editor
+ * The internal name of the currently processed editor.
+ * @param $version
+ * The version of the currently processed editor.
+ *
+ * @return
+ * An associative array having internal plugin names as keys and an array of
+ * plugin meta-information as values.
+ */
+function hook_wysiwyg_plugin($editor, $version) {
+ switch ($editor) {
+ case 'tinymce':
+ if ($version > 3) {
+ return array(
+ 'myplugin' => array(
+ // A URL to the plugin's homepage.
+ 'url' => 'http://drupal.org/project/img_assist',
+ // The full path to the native editor plugin, no trailing slash.
+ // Ignored when 'internal' is set to TRUE below.
+ 'path' => drupal_get_path('module', 'img_assist') . '/drupalimage',
+ // The name of the plugin's main JavaScript file.
+ // Ignored when 'internal' is set to TRUE below.
+ // Default value depends on which editor the plugin is for.
+ 'filename' => 'editor_plugin.js',
+ // A list of buttons provided by this native plugin. The key has to
+ // match the corresponding JavaScript implementation. The value is
+ // is displayed on the editor configuration form only.
+ 'buttons' => array(
+ 'img_assist' => t('Image Assist'),
+ ),
+ // A list of editor extensions provided by this native plugin.
+ // Extensions are not displayed as buttons and touch the editor's
+ // internals, so you should know what you are doing.
+ 'extensions' => array(
+ 'imce' => t('IMCE'),
+ ),
+ // A list of global, native editor configuration settings to
+ // override. To be used rarely and only when required.
+ 'options' => array(
+ 'file_browser_callback' => 'imceImageBrowser',
+ 'inline_styles' => TRUE,
+ ),
+ // Boolean whether the editor needs to load this plugin. When TRUE,
+ // the editor will automatically load the plugin based on the 'path'
+ // variable provided. If FALSE, the plugin either does not need to
+ // be loaded or is already loaded by something else on the page.
+ // Most plugins should define TRUE here.
+ 'load' => TRUE,
+ // Boolean whether this plugin is a native plugin, i.e. shipped with
+ // the editor. Definition must be ommitted for plugins provided by
+ // other modules. TRUE means 'path' and 'filename' above are ignored
+ // and the plugin is instead loaded from the editor's plugin folder.
+ 'internal' => TRUE,
+ // TinyMCE-specific: Additional HTML elements to allow in the markup.
+ 'extended_valid_elements' => array(
+ 'img[class|src|border=0|alt|title|width|height|align|name|style]',
+ ),
+ ),
+ );
+ }
+ break;
+ }
+}
+
+/**
+ * Register a directory containing Wysiwyg plugins.
+ *
+ * @param $type
+ * The type of objects being collected: either 'plugins' or 'editors'.
+ * @return
+ * A sub-directory of the implementing module that contains the corresponding
+ * plugin files. This directory must only contain integration files for
+ * Wysiwyg module.
+ */
+function hook_wysiwyg_include_directory($type) {
+ switch ($type) {
+ case 'plugins':
+ // You can just return $type, if you place your Wysiwyg plugins into a
+ // sub-directory named 'plugins'.
+ return $type;
+ }
+}
+
+/**
+ * Define a Wysiwyg plugin.
+ *
+ * Supposed to be used for "Drupal plugins" (cross-editor plugins) only.
+ *
+ * @see hook_wysiwyg_plugin()
+ *
+ * Each plugin file in the specified plugin directory of a module needs to
+ * define meta information about the particular plugin provided.
+ * The plugin's hook implementation function name is built out of the following:
+ * - 'hook': The name of the module providing the plugin.
+ * - 'INCLUDE': The basename of the file containing the plugin definition.
+ * - 'plugin': Static.
+ *
+ * For example, if your module's name is 'mymodule' and
+ * mymodule_wysiwyg_include_directory() returned 'plugins' as plugin directory,
+ * and this directory contains an "awesome" plugin file named 'awesome.inc', i.e.
+ * sites/all/modules/mymodule/plugins/awesome.inc
+ * then the corresponding plugin hook function name is:
+ * mymodule_awesome_plugin()
+ *
+ * @see hook_wysiwyg_include_directory()
+ *
+ * @return
+ * Meta information about the buttons provided by this plugin.
+ */
+function hook_INCLUDE_plugin() {
+ $plugins['awesome'] = array(
+ // The plugin's title; defaulting to its internal name ('awesome').
+ 'title' => t('Awesome plugin'),
+ // The (vendor) homepage of this plugin; defaults to ''.
+ 'vendor url' => 'http://drupal.org/project/wysiwyg',
+ // The path to the button's icon; defaults to
+ // '/[path-to-module]/[plugins-directory]/[plugin-name]/images'.
+ 'icon path' => 'path to icon',
+ // The button image filename; defaults to '[plugin-name].png'.
+ 'icon file' => 'name of the icon file with extension',
+ // The button title to display on hover.
+ 'icon title' => t('Do something'),
+ // An alternative path to the integration JavaScript; defaults to
+ // '[path-to-module]/[plugins-directory]/[plugin-name]'.
+ 'js path' => drupal_get_path('module', 'mymodule') . '/awesomeness',
+ // An alternative filename of the integration JavaScript; defaults to
+ // '[plugin-name].js'.
+ 'js file' => 'awesome.js',
+ // An alternative path to the integration stylesheet; defaults to
+ // '[path-to-module]/[plugins-directory]/[plugin-name]'.
+ 'css path' => drupal_get_path('module', 'mymodule') . '/awesomeness',
+ // An alternative filename of the integration stylesheet; defaults to
+ // '[plugin-name].css'.
+ 'css file' => 'awesome.css',
+ // An array of settings for this button. Required, but API is still in flux.
+ 'settings' => array(
+ ),
+ // TinyMCE-specific: Additional HTML elements to allow in the markup.
+ 'extended_valid_elements' => array(
+ 'tag1[attribute1|attribute2]',
+ 'tag2[attribute3|attribute4]',
+ ),
+ );
+ return $plugins;
+}
+
+/**
+ * Define a Wysiwyg editor library.
+ *
+ * @todo Complete this documentation.
+ */
+function hook_INCLUDE_editor() {
+ $editor['ckeditor'] = array(
+ // The official, human-readable label of the editor library.
+ 'title' => 'CKEditor',
+ // The URL to the library's homepage.
+ 'vendor url' => 'http://ckeditor.com',
+ // The URL to the library's download page.
+ 'download url' => 'http://ckeditor.com/download',
+ // A definition of available variants for the editor library.
+ // The first defined is used by default.
+ 'libraries' => array(
+ '' => array(
+ 'title' => 'Default',
+ 'files' => array(
+ 'ckeditor.js' => array('preprocess' => FALSE),
+ ),
+ ),
+ 'src' => array(
+ 'title' => 'Source',
+ 'files' => array(
+ 'ckeditor_source.js' => array('preprocess' => FALSE),
+ ),
+ ),
+ ),
+ // (optional) A callback to invoke to return additional notes for installing
+ // the editor library in the administrative list/overview.
+ 'install note callback' => 'wysiwyg_ckeditor_install_note',
+ // A callback to determine the library's version.
+ 'version callback' => 'wysiwyg_ckeditor_version',
+ // A callback to return available themes/skins for the editor library.
+ 'themes callback' => 'wysiwyg_ckeditor_themes',
+ // (optional) A callback to perform editor-specific adjustments or
+ // enhancements for the administrative editor profile settings form.
+ 'settings form callback' => 'wysiwyg_ckeditor_settings_form',
+ // (optional) A callback to return an initialization JavaScript snippet for
+ // this editor library, loaded before the actual library files. The returned
+ // JavaScript is executed as inline script in a primitive environment,
+ // before the DOM is loaded; typically used to prime a base path and other
+ // global window variables for the editor library before it is loaded.
+ // All implementations should verbosely document what they are doing and
+ // why that is required.
+ 'init callback' => 'wysiwyg_ckeditor_init',
+ // A callback to convert administrative profile/editor settings into
+ // JavaScript settings.
+ 'settings callback' => 'wysiwyg_ckeditor_settings',
+ // A callback to supply definitions of available editor plugins.
+ 'plugin callback' => 'wysiwyg_ckeditor_plugins',
+ // A callback to convert administrative plugin settings for a editor profile
+ // into JavaScript settings.
+ 'plugin settings callback' => 'wysiwyg_ckeditor_plugin_settings',
+ // (optional) Defines the proxy plugin that handles plugins provided by
+ // Drupal modules, which work in all editors that support proxy plugins.
+ 'proxy plugin' => array(
+ 'drupal' => array(
+ 'load' => TRUE,
+ 'proxy' => TRUE,
+ ),
+ ),
+ // (optional) A callback to convert proxy plugin settings into JavaScript
+ // settings.
+ 'proxy plugin settings callback' => 'wysiwyg_ckeditor_proxy_plugin_settings',
+ // Defines the list of supported (minimum) versions of the editor library,
+ // and the respective Drupal integration files to load.
+ 'versions' => array(
+ '3.0.0.3665' => array(
+ 'js files' => array('ckeditor-3.0.js'),
+ ),
+ ),
+ );
+ return $editor;
+}
+
+/**
+ * Act on editor profile settings.
+ *
+ * This hook is invoked from wysiwyg_get_editor_config() after the JavaScript
+ * settings have been generated for an editor profile and before the settings
+ * are added to the page. The settings may be customized or enhanced; typically
+ * with options that cannot be controlled through Wysiwyg module's
+ * administrative UI currently.
+ *
+ * Modules implementing this hook to enforce settings that can also be
+ * controlled through the UI should also implement
+ * hook_form_wysiwyg_profile_form_alter() to adjust or at least indicate on the
+ * editor profile configuration form that certain/affected settings cannot be
+ * changed.
+ *
+ * @param $settings
+ * An associative array of JavaScript settings to pass to the editor.
+ * @param $context
+ * An associative array containing additional context information:
+ * - editor: The plugin definition array of the editor.
+ * - profile: The editor profile object, as loaded from the database.
+ * - theme: The name of the editor theme/skin.
+ */
+function hook_wysiwyg_editor_settings_alter(&$settings, $context) {
+ // Each editor has its own collection of native settings that may be extended
+ // or overridden. Please consult the respective official vendor documentation
+ // for details.
+ if ($context['profile']->editor == 'tinymce') {
+ // Supported values to JSON data types.
+ $settings['cleanup_on_startup'] = TRUE;
+ }
+}
diff --git a/sites/all/modules/wysiwyg/wysiwyg.dialog.inc b/sites/all/modules/wysiwyg/wysiwyg.dialog.inc
new file mode 100644
index 000000000..c296d2e03
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.dialog.inc
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ * @file
+ * Wysiwyg dialog page handling functions.
+ */
+
+/**
+ * Page callback; Outputs a dialog page for a wysiwyg plugin.
+ *
+ * A Wysiwyg dialog is a bare minimum, simple HTML page; presented in a
+ * modal/popup window, triggered via JavaScript.
+ *
+ * However, Drupal core does not support such a concept, at all.
+ * Insanity happens on two separate layers:
+ * - All HTML pages go through the default delivery callback of
+ * drupal_deliver_html_page(), which calls into drupal_render_page(), which
+ * in turn *unconditionally* invokes hook_page_build() implementations. Thus,
+ * block_page_build() and similar implementations add the entirety of their
+ * page regions and blocks to our simple dialog page.
+ * Obviously, we don't want that.
+ * - There is a nice default 'page' theme template implementation, which
+ * performs all the heavy-lifting that is required for outputting a sane HTML
+ * page through preprocess and process functions. The theme system does not
+ * support to "inherit" preprocess and process hooks to alternative
+ * implementations. Even a very basic HTML page requires almost all of that.
+ * However, the default page template (normally overridden by a theme)
+ * contains too many regions and usually also huge a header and footer.
+ * Obviously, we don't want that.
+ *
+ * The poor workaround would be to follow the Overlay module's implementation in
+ * core: override the theme, build everything, and after doing all of that,
+ * strip away what isn't needed. Obviously, we don't want that.
+ *
+ * Instead, we bend Drupal to sane rules:
+ * - This page callback returns the actual main content.
+ * - wysiwyg_menu() defines a custom delivery callback that replaces
+ * drupal_deliver_html_page(), just because we need to replace
+ * drupal_render_page().
+ * - Our replacement for drupal_render_page() builds a $page that does not use
+ * #type 'page' but #type 'wysiwyg_dialog_page' instead.
+ * - #type 'wysiwyg_dialog_page' is defined like #type 'page' in
+ * system_element_info(), but is required, because there's no way to inherit
+ * a theme definition but override the page template file to be used.
+ * - As a consequence, #type 'wysiwyg_dialog_page' uses
+ * #theme 'wysiwyg_dialog_page', for which we have to implement stub
+ * preprocess and process callbacks in order to call into the ones for
+ * #theme 'page'.
+ *
+ * As a result we get:
+ * - A HTML response.
+ * - A HTML page wrapped into html.tpl.php.
+ * - A page title, title prefix/suffix, messages, help, etc.pp.
+ * - A simple page without regions and blocks (neither built nor rendered).
+ *
+ * @see wysiwyg_menu()
+ * @see wysiwyg_deliver_dialog_page
+ * @see wysiwyg_render_dialog_page()
+ * @see wysiwyg_element_info()
+ * @see wysiwyg_theme()
+ * @see template_preprocess_wysiwyg_dialog_page()
+ * @see template_process_wysiwyg_dialog_page()
+ *
+ * @see drupal_deliver_page()
+ * @see drupal_deliver_html_page()
+ * @see drupal_render_page()
+ * @see system_element_info()
+ * @see drupal_common_theme()
+ * @see template_preprocess_page()
+ * @see template_process_page()
+ */
+function wysiwyg_dialog($plugin, $instance) {
+ $plugins = wysiwyg_get_all_plugins();
+ if (!isset($plugins[$plugin])) {
+ return drupal_access_denied();
+ }
+ $callback = $plugin . '_wysiwyg_dialog';
+ if (!function_exists($callback)) {
+ return drupal_not_found();
+ }
+
+ // Suppress admin menu.
+ module_invoke('admin_menu', 'suppress');
+ // Add editor instance id to Drupal.settings.
+ $settings = array(
+ 'plugin' => $plugin,
+ 'instance' => $instance,
+ );
+ drupal_add_js(array('wysiwyg' => $settings), 'setting');
+
+ $build = $callback($instance);
+ if (!is_array($build)) {
+ $build = array('#markup' => $build);
+ }
+ $build += array(
+ '#instance' => $instance,
+ '#plugin' => $plugin,
+ );
+ return $build;
+}
+
+/**
+ * @see drupal_deliver_html_page()
+ */
+function wysiwyg_deliver_dialog_page($page_callback_result) {
+ // Menu status constants are integers; page content is a string or array.
+ if (is_int($page_callback_result)) {
+ return drupal_deliver_html_page($page_callback_result);
+ }
+
+ // Emit the correct charset HTTP header, but not if the page callback
+ // result is NULL, since that likely indicates that it printed something
+ // in which case, no further headers may be sent, and not if code running
+ // for this page request has already set the content type header.
+ if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) {
+ drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
+ }
+
+ // Send appropriate HTTP-Header for browsers and search engines.
+ global $language;
+ drupal_add_http_header('Content-Language', $language->language);
+
+ if (isset($page_callback_result)) {
+ // Print anything besides a menu constant, assuming it's not NULL or
+ // undefined.
+ print wysiwyg_render_dialog_page($page_callback_result);
+ }
+
+ // Perform end-of-request tasks.
+ drupal_page_footer();
+}
+
+/**
+ * @see drupal_render_page()
+ */
+function wysiwyg_render_dialog_page($page) {
+ $main_content_display = &drupal_static('system_main_content_added', FALSE);
+
+ // Allow menu callbacks to return strings or arbitrary arrays to render.
+ // If the array returned is not of #type page directly, we need to fill
+ // in the page with defaults.
+ if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) {
+ drupal_set_page_content($page);
+ $page = element_info('wysiwyg_dialog_page');
+ }
+
+ // Modules alter the $page as needed. Blocks are populated into regions like
+ // 'sidebar_first', 'footer', etc.
+ drupal_alter(array('wysiwyg_dialog_page', 'page'), $page);
+
+ // If no module has taken care of the main content, add it to the page now.
+ // This allows the site to still be usable even if no modules that
+ // control page regions (for example, the Block module) are enabled.
+ if (!$main_content_display) {
+ $page['content']['system_main'] = drupal_set_page_content();
+ }
+
+ return drupal_render($page);
+}
+
+/**
+ * Template preprocess function for theme_wysiwyg_dialog_page().
+ *
+ * @see wysiwyg_dialog()
+ * @see wysiwyg-dialog-page.tpl.php
+ * @see template_preprocess_page()
+ */
+function template_preprocess_wysiwyg_dialog_page(&$variables) {
+ template_preprocess_page($variables);
+}
+
+
+/**
+ * Template process function for theme_wysiwyg_dialog_page().
+ *
+ * @see wysiwyg_dialog()
+ * @see wysiwyg-dialog-page.tpl.php
+ * @see template_process_page()
+ */
+function template_process_wysiwyg_dialog_page(&$variables) {
+ template_process_page($variables);
+}
+
diff --git a/sites/all/modules/wysiwyg/wysiwyg.features.inc b/sites/all/modules/wysiwyg/wysiwyg.features.inc
new file mode 100644
index 000000000..5edd6421b
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.features.inc
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Implements hook_features_export_options().
+ */
+function wysiwyg_features_export_options() {
+ $profiles = array();
+
+ // Get human-readable name from filter module.
+ $formats = filter_formats();
+
+ foreach (array_keys(wysiwyg_profile_load_all()) as $format) {
+ // Text format may vanish without deleting the wysiwyg profile.
+ if (isset($formats[$format])) {
+ $profiles[$format] = $formats[$format]->name;
+ }
+ }
+ return $profiles;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function wysiwyg_features_export($data, &$export, $module_name = '') {
+ $pipe = array();
+
+ // The wysiwyg_default_formats() hook integration is provided by the
+ // features module so we need to add it as a dependency.
+ $export['dependencies']['features'] = 'features';
+ $export['dependencies']['wysiwyg'] = 'wysiwyg';
+
+ foreach ($data as $name) {
+ if ($profile = wysiwyg_get_profile($name)) {
+ // Add profile to exports.
+ $export['features']['wysiwyg'][$profile->format] = $profile->format;
+
+ // Chain filter format for export.
+ $pipe['filter'][] = $profile->format;
+ }
+ }
+
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function wysiwyg_features_export_render($module, $data, $export = NULL) {
+ $code = array();
+ $code[] = ' $profiles = array();';
+ $code[] = '';
+
+ foreach ($data as $name) {
+ if ($profile = wysiwyg_get_profile($name)) {
+ $profile_export = features_var_export($profile, ' ');
+ $profile_identifier = features_var_export($profile->format);
+ $code[] = " // Exported profile: {$profile->format}";
+ $code[] = " \$profiles[{$profile_identifier}] = {$profile_export};";
+ $code[] = "";
+ }
+ }
+
+ $code[] = ' return $profiles;';
+ $code = implode("\n", $code);
+ return array('wysiwyg_default_profiles' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function wysiwyg_features_revert($module) {
+ return wysiwyg_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ */
+function wysiwyg_features_rebuild($module) {
+ if ($defaults = features_get_default('wysiwyg', $module)) {
+ foreach ($defaults as $profile) {
+ db_merge('wysiwyg')
+ ->key(array('format' => $profile['format']))
+ ->fields(array(
+ 'editor' => $profile['editor'],
+ 'settings' => serialize($profile['settings']),
+ ))
+ ->execute();
+ }
+ wysiwyg_profile_cache_clear();
+ }
+}
+
diff --git a/sites/all/modules/wysiwyg/wysiwyg.info b/sites/all/modules/wysiwyg/wysiwyg.info
new file mode 100644
index 000000000..5f70c0cbb
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.info
@@ -0,0 +1,17 @@
+name = Wysiwyg
+description = Allows to edit content with client-side editors.
+package = User interface
+;dependencies[] = libraries
+;dependencies[] = ctools
+;dependencies[] = debug
+core = 7.x
+configure = admin/config/content/wysiwyg
+files[] = wysiwyg.module
+files[] = tests/wysiwyg.test
+
+; Information added by drupal.org packaging script on 2012-10-02
+version = "7.x-2.2"
+core = "7.x"
+project = "wysiwyg"
+datestamp = "1349213776"
+
diff --git a/sites/all/modules/wysiwyg/wysiwyg.init.js b/sites/all/modules/wysiwyg/wysiwyg.init.js
new file mode 100644
index 000000000..6ccdb3146
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.init.js
@@ -0,0 +1,19 @@
+
+Drupal.wysiwyg = Drupal.wysiwyg || { 'instances': {} };
+
+Drupal.wysiwyg.editor = Drupal.wysiwyg.editor || { 'init': {}, 'attach': {}, 'detach': {}, 'instance': {} };
+
+Drupal.wysiwyg.plugins = Drupal.wysiwyg.plugins || {};
+
+(function ($) {
+ // Determine support for queryCommandEnabled().
+ // An exception should be thrown for non-existing commands.
+ // Safari and Chrome (WebKit based) return -1 instead.
+ try {
+ document.queryCommandEnabled('__wysiwygTestCommand');
+ $.support.queryCommandEnabled = false;
+ }
+ catch (error) {
+ $.support.queryCommandEnabled = true;
+ }
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/wysiwyg.install b/sites/all/modules/wysiwyg/wysiwyg.install
new file mode 100644
index 000000000..e5dd046d9
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.install
@@ -0,0 +1,313 @@
+<?php
+
+/**
+ * @file
+ * Installation functions for Wysiwyg module.
+ */
+
+/**
+ * Implementation of hook_schema().
+ */
+function wysiwyg_schema() {
+ $schema['wysiwyg'] = array(
+ 'description' => 'Stores Wysiwyg profiles.',
+ 'fields' => array(
+ 'format' => array(
+ 'description' => 'The {filter_format}.format of the text format.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ // Primary keys are implicitly not null.
+ 'not null' => TRUE,
+ ),
+ 'editor' => array(
+ 'description' => 'Internal name of the editor attached to the text format.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'settings' => array(
+ 'description' => 'Configuration settings for the editor.',
+ 'type' => 'text',
+ 'size' => 'normal',
+ 'serialize' => TRUE,
+ ),
+ ),
+ 'primary key' => array('format'),
+ 'foreign keys' => array(
+ 'format' => array(
+ 'table' => 'filter_format',
+ 'columns' => array('format' => 'format'),
+ ),
+ ),
+ );
+ $schema['wysiwyg_user'] = array(
+ 'description' => 'Stores user preferences for wysiwyg profiles.',
+ 'fields' => array(
+ 'uid' => array(
+ 'description' => 'The {users}.uid of the user.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'format' => array(
+ 'description' => 'The {filter_format}.format of the text format.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ),
+ 'status' => array(
+ 'description' => 'Boolean indicating whether the format is enabled by default.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ ),
+ 'indexes' => array(
+ 'uid' => array('uid'),
+ 'format' => array('format'),
+ ),
+ 'foreign keys' => array(
+ 'uid' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid'),
+ ),
+ 'format' => array(
+ 'table' => 'filter_format',
+ 'columns' => array('format' => 'format'),
+ ),
+ ),
+ );
+ return $schema;
+}
+
+/**
+ * Implementation of hook_enable().
+ */
+function wysiwyg_enable() {
+ // Disable conflicting, obsolete editor integration modules whenever this
+ // module is enabled. This is crude, but the only way to ensure no conflicts.
+ module_disable(array(
+ 'ckeditor',
+ 'editarea',
+ 'editonpro',
+ 'editor',
+ 'fckeditor',
+ 'freerte',
+ 'htmlarea',
+ 'htmlbox',
+ 'jwysiwyg',
+ 'markitup',
+ 'nicedit',
+ 'openwysiwyg',
+ 'pegoeditor',
+ 'quicktext',
+ 'tinymce',
+ 'tinymce_autoconf',
+ 'tinytinymce',
+ 'whizzywig',
+ 'widgeditor',
+ 'wymeditor',
+ 'xstandard',
+ 'yui_editor',
+ ));
+}
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function wysiwyg_update_dependencies() {
+ // Ensure that format columns are only changed after Filter module has changed
+ // the primary records.
+ $dependencies['wysiwyg'][7000] = array(
+ 'filter' => 7010,
+ );
+
+ return $dependencies;
+}
+
+/**
+ * Retrieve a list of input formats to associate profiles to.
+ */
+function _wysiwyg_install_get_formats() {
+ $formats = array();
+ $result = db_query("SELECT format, name FROM {filter_formats}");
+ while ($format = db_fetch_object($result)) {
+ // Build a list of all formats.
+ $formats[$format->format] = $format->name;
+ // Fetch filters.
+ $result2 = db_query("SELECT module, delta FROM {filters} WHERE format = %d", $format->format);
+ while ($filter = db_fetch_object($result2)) {
+ // If PHP filter is enabled, remove this format.
+ if ($filter->module == 'php') {
+ unset($formats[$format->format]);
+ break;
+ }
+ }
+ }
+ return $formats;
+}
+
+/**
+ * Associate Wysiwyg profiles with input formats.
+ *
+ * Since there was no association yet, we can only assume that there is one
+ * profile only, and that profile must be duplicated and assigned to all input
+ * formats (except PHP code format). Also, input formats already have
+ * titles/names, so Wysiwyg profiles do not need an own.
+ *
+ * Because input formats are already granted to certain user roles only, we can
+ * remove our custom Wysiwyg profile permissions. A 1:1 relationship between
+ * input formats and permissions makes plugin_count obsolete, too.
+ *
+ * Since the resulting table is completely different, a new schema is installed.
+ */
+function wysiwyg_update_6001() {
+ $ret = array();
+ if (db_table_exists('wysiwyg')) {
+ return $ret;
+ }
+ // Install new schema.
+ db_create_table($ret, 'wysiwyg', array(
+ 'fields' => array(
+ 'format' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+ 'editor' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
+ 'settings' => array('type' => 'text', 'size' => 'normal'),
+ ),
+ 'primary key' => array('format'),
+ ));
+
+ // Fetch all input formats.
+ $formats = _wysiwyg_install_get_formats();
+
+ // Fetch all profiles.
+ $result = db_query("SELECT name, settings FROM {wysiwyg_profile}");
+ while ($profile = db_fetch_object($result)) {
+ $profile->settings = unserialize($profile->settings);
+ // Extract editor name from profile settings.
+ $profile->editor = $profile->settings['editor'];
+ // Clean-up.
+ unset($profile->settings['editor']);
+ unset($profile->settings['old_name']);
+ unset($profile->settings['name']);
+ unset($profile->settings['rids']);
+ // Sorry. There Can Be Only One. ;)
+ break;
+ }
+
+ if ($profile) {
+ // Rebuild profiles and associate with input formats.
+ foreach ($formats as $format => $name) {
+ // Insert profiles.
+ // We can't use update_sql() here because of curly braces in serialized
+ // array.
+ db_query("INSERT INTO {wysiwyg} (format, editor, settings) VALUES (%d, '%s', '%s')", $format, $profile->editor, serialize($profile->settings));
+ $ret[] = array(
+ 'success' => TRUE,
+ 'query' => strtr('Wysiwyg profile %profile converted and associated with input format %format.', array('%profile' => check_plain($profile->name), '%format' => check_plain($name))),
+ );
+ }
+ }
+
+ // Drop obsolete tables {wysiwyg_profile} and {wysiwyg_role}.
+ db_drop_table($ret, 'wysiwyg_profile');
+ db_drop_table($ret, 'wysiwyg_role');
+
+ return $ret;
+}
+
+/**
+ * Clear JS/CSS caches to ensure that clients load fresh copies.
+ */
+function wysiwyg_update_6200() {
+ $ret = array();
+ // Change query-strings on css/js files to enforce reload for all users.
+ _drupal_flush_css_js();
+
+ drupal_clear_css_cache();
+ drupal_clear_js_cache();
+
+ // Rebuild the menu to remove old admin/settings/wysiwyg/profile item.
+ menu_rebuild();
+
+ // Flush content caches.
+ cache_clear_all();
+
+ $ret[] = array(
+ 'success' => TRUE,
+ 'query' => 'Caches have been flushed.',
+ );
+ return $ret;
+}
+
+/**
+ * Change {wysiwyg}.format into a string.
+ */
+function wysiwyg_update_7000() {
+ db_drop_primary_key('wysiwyg');
+ db_change_field('wysiwyg', 'format', 'format', array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ ));
+ db_add_primary_key('wysiwyg', array('format'));
+}
+
+/**
+ * Create the {wysiwyg_user} table.
+ */
+function wysiwyg_update_7200() {
+ if (!db_table_exists('wysiwyg_user')) {
+ db_create_table('wysiwyg_user', array(
+ 'description' => 'Stores user preferences for wysiwyg profiles.',
+ 'fields' => array(
+ 'uid' => array(
+ 'description' => 'The {users}.uid of the user.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'format' => array(
+ 'description' => 'The {filter_format}.format of the text format.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ),
+ 'status' => array(
+ 'description' => 'Boolean indicating whether the format is enabled by default.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ ),
+ 'indexes' => array(
+ 'uid' => array('uid'),
+ 'format' => array('format'),
+ ),
+ 'foreign keys' => array(
+ 'uid' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid'),
+ ),
+ 'format' => array(
+ 'table' => 'filter_format',
+ 'columns' => array('format' => 'format'),
+ ),
+ ),
+ ));
+ }
+ else {
+ db_change_field('wysiwyg_user', 'format', 'format', array(
+ 'description' => 'The {filter_format}.format of the text format.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ));
+ }
+}
diff --git a/sites/all/modules/wysiwyg/wysiwyg.js b/sites/all/modules/wysiwyg/wysiwyg.js
new file mode 100644
index 000000000..29e2c54b8
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.js
@@ -0,0 +1,269 @@
+(function($) {
+
+/**
+ * Initialize editor libraries.
+ *
+ * Some editors need to be initialized before the DOM is fully loaded. The
+ * init hook gives them a chance to do so.
+ */
+Drupal.wysiwygInit = function() {
+ // This breaks in Konqueror. Prevent it from running.
+ if (/KDE/.test(navigator.vendor)) {
+ return;
+ }
+ jQuery.each(Drupal.wysiwyg.editor.init, function(editor) {
+ // Clone, so original settings are not overwritten.
+ this(jQuery.extend(true, {}, Drupal.settings.wysiwyg.configs[editor]));
+ });
+};
+
+/**
+ * Attach editors to input formats and target elements (f.e. textareas).
+ *
+ * This behavior searches for input format selectors and formatting guidelines
+ * that have been preprocessed by Wysiwyg API. All CSS classes of those elements
+ * with the prefix 'wysiwyg-' are parsed into input format parameters, defining
+ * the input format, configured editor, target element id, and variable other
+ * properties, which are passed to the attach/detach hooks of the corresponding
+ * editor.
+ *
+ * Furthermore, an "enable/disable rich-text" toggle link is added after the
+ * target element to allow users to alter its contents in plain text.
+ *
+ * This is executed once, while editor attach/detach hooks can be invoked
+ * multiple times.
+ *
+ * @param context
+ * A DOM element, supplied by Drupal.attachBehaviors().
+ */
+Drupal.behaviors.attachWysiwyg = {
+ attach: function (context, settings) {
+ // This breaks in Konqueror. Prevent it from running.
+ if (/KDE/.test(navigator.vendor)) {
+ return;
+ }
+
+ $('.wysiwyg', context).once('wysiwyg', function () {
+ if (!this.id || typeof Drupal.settings.wysiwyg.triggers[this.id] === 'undefined') {
+ return;
+ }
+ var $this = $(this);
+ var params = Drupal.settings.wysiwyg.triggers[this.id];
+ for (var format in params) {
+ params[format].format = format;
+ params[format].trigger = this.id;
+ params[format].field = params.field;
+ }
+ var format = 'format' + this.value;
+ // Directly attach this editor, if the input format is enabled or there is
+ // only one input format at all.
+ if ($this.is(':input')) {
+ Drupal.wysiwygAttach(context, params[format]);
+ }
+ // Attach onChange handlers to input format selector elements.
+ if ($this.is('select')) {
+ $this.change(function() {
+ // If not disabled, detach the current and attach a new editor.
+ Drupal.wysiwygDetach(context, params[format]);
+ format = 'format' + this.value;
+ Drupal.wysiwygAttach(context, params[format]);
+ });
+ }
+ // Detach any editor when the containing form is submitted.
+ $('#' + params.field).parents('form').submit(function (event) {
+ // Do not detach if the event was cancelled.
+ if (event.isDefaultPrevented()) {
+ return;
+ }
+ Drupal.wysiwygDetach(context, params[format], 'serialize');
+ });
+ });
+ },
+
+ detach: function (context, settings, trigger) {
+ var wysiwygs;
+ // The 'serialize' trigger indicates that we should simply update the
+ // underlying element with the new text, without destroying the editor.
+ if (trigger == 'serialize') {
+ // Removing the wysiwyg-processed class guarantees that the editor will
+ // be reattached. Only do this if we're planning to destroy the editor.
+ wysiwygs = $('.wysiwyg-processed', context);
+ }
+ else {
+ wysiwygs = $('.wysiwyg', context).removeOnce('wysiwyg');
+ }
+ wysiwygs.each(function () {
+ var params = Drupal.settings.wysiwyg.triggers[this.id];
+ Drupal.wysiwygDetach(context, params, trigger);
+ });
+ }
+};
+
+/**
+ * Attach an editor to a target element.
+ *
+ * This tests whether the passed in editor implements the attach hook and
+ * invokes it if available. Editor profile settings are cloned first, so they
+ * cannot be overridden. After attaching the editor, the toggle link is shown
+ * again, except in case we are attaching no editor.
+ *
+ * @param context
+ * A DOM element, supplied by Drupal.attachBehaviors().
+ * @param params
+ * An object containing input format parameters.
+ */
+Drupal.wysiwygAttach = function(context, params) {
+ if (typeof Drupal.wysiwyg.editor.attach[params.editor] == 'function') {
+ // (Re-)initialize field instance.
+ Drupal.wysiwyg.instances[params.field] = {};
+ // Provide all input format parameters to editor instance.
+ jQuery.extend(Drupal.wysiwyg.instances[params.field], params);
+ // Provide editor callbacks for plugins, if available.
+ if (typeof Drupal.wysiwyg.editor.instance[params.editor] == 'object') {
+ jQuery.extend(Drupal.wysiwyg.instances[params.field], Drupal.wysiwyg.editor.instance[params.editor]);
+ }
+ // Store this field id, so (external) plugins can use it.
+ // @todo Wrong point in time. Probably can only supported by editors which
+ // support an onFocus() or similar event.
+ Drupal.wysiwyg.activeId = params.field;
+ // Attach or update toggle link, if enabled.
+ if (params.toggle) {
+ Drupal.wysiwygAttachToggleLink(context, params);
+ }
+ // Otherwise, ensure that toggle link is hidden.
+ else {
+ $('#wysiwyg-toggle-' + params.field).hide();
+ }
+ // Attach editor, if enabled by default or last state was enabled.
+ if (params.status) {
+ Drupal.wysiwyg.editor.attach[params.editor](context, params, (Drupal.settings.wysiwyg.configs[params.editor] ? jQuery.extend(true, {}, Drupal.settings.wysiwyg.configs[params.editor][params.format]) : {}));
+ }
+ // Otherwise, attach default behaviors.
+ else {
+ Drupal.wysiwyg.editor.attach.none(context, params);
+ Drupal.wysiwyg.instances[params.field].editor = 'none';
+ }
+ }
+};
+
+/**
+ * Detach all editors from a target element.
+ *
+ * @param context
+ * A DOM element, supplied by Drupal.attachBehaviors().
+ * @param params
+ * An object containing input format parameters.
+ * @param trigger
+ * A string describing what is causing the editor to be detached.
+ *
+ * @see Drupal.detachBehaviors
+ */
+Drupal.wysiwygDetach = function (context, params, trigger) {
+ // Do not attempt to detach an unknown editor instance (Ajax).
+ if (typeof Drupal.wysiwyg.instances[params.field] == 'undefined') {
+ return;
+ }
+ trigger = trigger || 'unload';
+ var editor = Drupal.wysiwyg.instances[params.field].editor;
+ if (jQuery.isFunction(Drupal.wysiwyg.editor.detach[editor])) {
+ Drupal.wysiwyg.editor.detach[editor](context, params, trigger);
+ }
+};
+
+/**
+ * Append or update an editor toggle link to a target element.
+ *
+ * @param context
+ * A DOM element, supplied by Drupal.attachBehaviors().
+ * @param params
+ * An object containing input format parameters.
+ */
+Drupal.wysiwygAttachToggleLink = function(context, params) {
+ if (!$('#wysiwyg-toggle-' + params.field).length) {
+ var text = document.createTextNode(params.status ? Drupal.settings.wysiwyg.disable : Drupal.settings.wysiwyg.enable);
+ var a = document.createElement('a');
+ $(a).attr({ id: 'wysiwyg-toggle-' + params.field, href: 'javascript:void(0);' }).append(text);
+ var div = document.createElement('div');
+ $(div).addClass('wysiwyg-toggle-wrapper').append(a);
+ $('#' + params.field).after(div);
+ }
+ $('#wysiwyg-toggle-' + params.field)
+ .html(params.status ? Drupal.settings.wysiwyg.disable : Drupal.settings.wysiwyg.enable).show()
+ .unbind('click.wysiwyg', Drupal.wysiwyg.toggleWysiwyg)
+ .bind('click.wysiwyg', { params: params, context: context }, Drupal.wysiwyg.toggleWysiwyg);
+
+ // Hide toggle link in case no editor is attached.
+ if (params.editor == 'none') {
+ $('#wysiwyg-toggle-' + params.field).hide();
+ }
+};
+
+/**
+ * Callback for the Enable/Disable rich editor link.
+ */
+Drupal.wysiwyg.toggleWysiwyg = function (event) {
+ var context = event.data.context;
+ var params = event.data.params;
+ if (params.status) {
+ // Detach current editor.
+ params.status = false;
+ Drupal.wysiwygDetach(context, params);
+ // After disabling the editor, re-attach default behaviors.
+ // @todo We HAVE TO invoke Drupal.wysiwygAttach() here.
+ Drupal.wysiwyg.editor.attach.none(context, params);
+ Drupal.wysiwyg.instances[params.field] = Drupal.wysiwyg.editor.instance.none;
+ Drupal.wysiwyg.instances[params.field].editor = 'none';
+ Drupal.wysiwyg.instances[params.field].field = params.field;
+ $(this).html(Drupal.settings.wysiwyg.enable).blur();
+ }
+ else {
+ // Before enabling the editor, detach default behaviors.
+ Drupal.wysiwyg.editor.detach.none(context, params);
+ // Attach new editor using parameters of the currently selected input format.
+ params = Drupal.settings.wysiwyg.triggers[params.trigger]['format' + $('#' + params.trigger).val()];
+ params.status = true;
+ Drupal.wysiwygAttach(context, params);
+ $(this).html(Drupal.settings.wysiwyg.disable).blur();
+ }
+}
+
+/**
+ * Parse the CSS classes of an input format DOM element into parameters.
+ *
+ * Syntax for CSS classes is "wysiwyg-name-value".
+ *
+ * @param element
+ * An input format DOM element containing CSS classes to parse.
+ * @param params
+ * (optional) An object containing input format parameters to update.
+ */
+Drupal.wysiwyg.getParams = function(element, params) {
+ var classes = element.className.split(' ');
+ var params = params || {};
+ for (var i = 0; i < classes.length; i++) {
+ if (classes[i].substr(0, 8) == 'wysiwyg-') {
+ var parts = classes[i].split('-');
+ var value = parts.slice(2).join('-');
+ params[parts[1]] = value;
+ }
+ }
+ // Convert format id into string.
+ params.format = 'format' + params.format;
+ // Convert numeric values.
+ params.status = parseInt(params.status, 10);
+ params.toggle = parseInt(params.toggle, 10);
+ params.resizable = parseInt(params.resizable, 10);
+ return params;
+};
+
+/**
+ * Allow certain editor libraries to initialize before the DOM is loaded.
+ */
+Drupal.wysiwygInit();
+
+// Respond to CTools detach behaviors event.
+$(document).bind('CToolsDetachBehaviors', function(event, context) {
+ Drupal.behaviors.attachWysiwyg.detach(context, {}, 'unload');
+});
+
+})(jQuery);
diff --git a/sites/all/modules/wysiwyg/wysiwyg.module b/sites/all/modules/wysiwyg/wysiwyg.module
new file mode 100644
index 000000000..22130eab9
--- /dev/null
+++ b/sites/all/modules/wysiwyg/wysiwyg.module
@@ -0,0 +1,1135 @@
+<?php
+
+/**
+ * @file
+ * Integrates client-side editors with Drupal.
+ */
+
+/**
+ * Implements hook_entity_info().
+ */
+function wysiwyg_entity_info() {
+ $types['wysiwyg_profile'] = array(
+ 'label' => t('Wysiwyg profile'),
+ 'base table' => 'wysiwyg',
+ 'controller class' => 'WysiwygProfileController',
+ 'fieldable' => FALSE,
+ // When loading all entities, DrupalDefaultEntityController::load() ignores
+ // its static cache. Therefore, wysiwyg_profile_load_all() implements a
+ // custom static cache.
+ 'static cache' => FALSE,
+ 'entity keys' => array(
+ 'id' => 'format',
+ ),
+ );
+ return $types;
+}
+
+/**
+ * Controller class for Wysiwyg profiles.
+ */
+class WysiwygProfileController extends DrupalDefaultEntityController {
+ /**
+ * Overrides DrupalDefaultEntityController::attachLoad().
+ */
+ function attachLoad(&$queried_entities, $revision_id = FALSE) {
+ // Unserialize the profile settings.
+ foreach ($queried_entities as $key => $record) {
+ $queried_entities[$key]->settings = unserialize($record->settings);
+ }
+ // Call the default attachLoad() method.
+ parent::attachLoad($queried_entities, $revision_id);
+ }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function wysiwyg_menu() {
+ $items['admin/config/content/wysiwyg'] = array(
+ 'title' => 'Wysiwyg profiles',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('wysiwyg_profile_overview'),
+ 'description' => 'Configure client-side editors.',
+ 'access arguments' => array('administer filters'),
+ 'file' => 'wysiwyg.admin.inc',
+ );
+ $items['admin/config/content/wysiwyg/profile'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+ $items['admin/config/content/wysiwyg/profile/%wysiwyg_profile/edit'] = array(
+ 'title' => 'Edit',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('wysiwyg_profile_form', 5),
+ 'access arguments' => array('administer filters'),
+ 'file' => 'wysiwyg.admin.inc',
+ 'tab_root' => 'admin/config/content/wysiwyg/profile',
+ 'tab_parent' => 'admin/config/content/wysiwyg/profile/%wysiwyg_profile',
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items['admin/config/content/wysiwyg/profile/%wysiwyg_profile/delete'] = array(
+ 'title' => 'Remove',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('wysiwyg_profile_delete_confirm', 5),
+ 'access arguments' => array('administer filters'),
+ 'file' => 'wysiwyg.admin.inc',
+ 'tab_root' => 'admin/config/content/wysiwyg/profile',
+ 'tab_parent' => 'admin/config/content/wysiwyg/profile/%wysiwyg_profile',
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 10,
+ );
+ // @see wysiwyg_dialog()
+ $items['wysiwyg/%'] = array(
+ 'page callback' => 'wysiwyg_dialog',
+ 'page arguments' => array(1),
+ 'delivery callback' => 'wysiwyg_deliver_dialog_page',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'wysiwyg.dialog.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_element_info().
+ */
+function wysiwyg_element_info() {
+ // @see wysiwyg_dialog()
+ $types['wysiwyg_dialog_page'] = array(
+ '#theme' => 'wysiwyg_dialog_page',
+ '#theme_wrappers' => array('html'),
+ '#show_messages' => TRUE,
+ );
+ return $types;
+}
+
+/**
+ * Implementation of hook_theme().
+ *
+ * @see drupal_common_theme(), common.inc
+ * @see template_preprocess_page(), theme.inc
+ */
+function wysiwyg_theme() {
+ return array(
+ 'wysiwyg_profile_overview' => array(
+ 'render element' => 'form',
+ ),
+ 'wysiwyg_admin_button_table' => array(
+ 'render element' => 'form',
+ ),
+ // @see wysiwyg_dialog()
+ 'wysiwyg_dialog_page' => array(
+ 'render element' => 'page',
+ 'file' => 'wysiwyg.dialog.inc',
+ 'template' => 'wysiwyg-dialog-page',
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function wysiwyg_help($path, $arg) {
+ switch ($path) {
+ case 'admin/config/content/wysiwyg':
+ $output = '<p>' . t('A Wysiwyg profile is associated with a text format. A Wysiwyg profile defines which client-side editor is loaded with a particular text format, what buttons or themes are enabled for the editor, how the editor is displayed, and a few other editor-specific functions.') . '</p>';
+ return $output;
+ }
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function wysiwyg_form_alter(&$form, &$form_state) {
+ // Teaser splitter is unconditionally removed and NOT supported.
+ if (isset($form['body_field'])) {
+ unset($form['body_field']['teaser_js']);
+ }
+}
+
+/**
+ * Implements hook_element_info_alter().
+ */
+function wysiwyg_element_info_alter(&$types) {
+ $types['text_format']['#pre_render'][] = 'wysiwyg_pre_render_text_format';
+}
+
+/**
+ * Process a text format widget to load and attach editors.
+ *
+ * The element's #id is used as reference to attach client-side editors.
+ */
+function wysiwyg_pre_render_text_format($element) {
+ // filter_process_format() copies properties to the expanded 'value' child
+ // element. Skip this text format widget, if it contains no 'format' or when
+ // the current user does not have access to edit the value.
+ if (!isset($element['format']) || !empty($element['value']['#disabled'])) {
+ return $element;
+ }
+ // Allow modules to programmatically enforce no client-side editor by setting
+ // the #wysiwyg property to FALSE.
+ if (isset($element['#wysiwyg']) && !$element['#wysiwyg']) {
+ return $element;
+ }
+
+ $format_field = &$element['format'];
+ $field = &$element['value'];
+ $settings = array(
+ 'field' => $field['#id'],
+ );
+
+ // If this textarea is #resizable and we will load at least one
+ // editor, then only load the behavior and let the 'none' editor
+ // attach/detach it to avoid hi-jacking the UI. Due to our CSS class
+ // parsing, we can add arbitrary parameters for each input format.
+ // The #resizable property will be removed below, if at least one
+ // profile has been loaded.
+ $resizable = 0;
+ if (!empty($field['#resizable'])) {
+ $resizable = 1;
+ drupal_add_js('misc/textarea.js');
+ }
+ // Determine the available text formats.
+ foreach ($format_field['format']['#options'] as $format_id => $format_name) {
+ $format = 'format' . $format_id;
+ // Initialize default settings, defaulting to 'none' editor.
+ $settings[$format] = array(
+ 'editor' => 'none',
+ 'status' => 1,
+ 'toggle' => 1,
+ 'resizable' => $resizable,
+ );
+
+ // Fetch the profile associated to this text format.
+ $profile = wysiwyg_get_profile($format_id);
+ if ($profile) {
+ $loaded = TRUE;
+ $settings[$format]['editor'] = $profile->editor;
+ $settings[$format]['status'] = (int) wysiwyg_user_get_status($profile);
+ if (isset($profile->settings['show_toggle'])) {
+ $settings[$format]['toggle'] = (int) $profile->settings['show_toggle'];
+ }
+ // Check editor theme (and reset it if not/no longer available).
+ $theme = wysiwyg_get_editor_themes($profile, (isset($profile->settings['theme']) ? $profile->settings['theme'] : ''));
+
+ // Add plugin settings (first) for this text format.
+ wysiwyg_add_plugin_settings($profile);
+ // Add profile settings for this text format.
+ wysiwyg_add_editor_settings($profile, $theme);
+ }
+ }
+ // Use a hidden element for a single text format.
+ if (!$format_field['format']['#access']) {
+ $format_field['wysiwyg'] = array(
+ '#type' => 'hidden',
+ '#name' => $format_field['format']['#name'],
+ '#value' => $format_id,
+ '#attributes' => array(
+ 'id' => $format_field['format']['#id'],
+ 'class' => array('wysiwyg'),
+ ),
+ );
+ $format_field['wysiwyg']['#attached']['js'][] = array(
+ 'data' => array(
+ 'wysiwyg' => array(
+ 'triggers' => array(
+ $format_field['format']['#id'] => $settings,
+ ),
+ ),
+ ),
+ 'type' => 'setting',
+ );
+ }
+ // Otherwise, attach to text format selector.
+ else {
+ $format_field['format']['#attributes']['class'][] = 'wysiwyg';
+ $format_field['format']['#attached']['js'][] = array(
+ 'data' => array(
+ 'wysiwyg' => array(
+ 'triggers' => array(
+ $format_field['format']['#id'] => $settings,
+ ),
+ ),
+ ),
+ 'type' => 'setting',
+ );
+ }
+
+ // If we loaded at least one editor, then the 'none' editor will
+ // handle resizable textareas instead of core.
+ if (isset($loaded) && $resizable) {
+ $field['#resizable'] = FALSE;
+ }
+
+ return $element;
+}
+
+/**
+ * Determine the profile to use for a given input format id.
+ *
+ * This function also performs sanity checks for the configured editor in a
+ * profile to ensure that we do not load a malformed editor.
+ *
+ * @param $format
+ * The internal id of an input format.
+ *
+ * @return
+ * A wysiwyg profile.
+ *
+ * @see wysiwyg_load_editor(), wysiwyg_get_editor()
+ */
+function wysiwyg_get_profile($format) {
+ if ($profile = wysiwyg_profile_load($format)) {
+ if (wysiwyg_load_editor($profile)) {
+ return $profile;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Load an editor library and initialize basic Wysiwyg settings.
+ *
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @return
+ * TRUE if the editor has been loaded, FALSE if not.
+ *
+ * @see wysiwyg_get_profile()
+ */
+function wysiwyg_load_editor($profile) {
+ static $settings_added;
+ static $loaded = array();
+ $path = drupal_get_path('module', 'wysiwyg');
+
+ $name = $profile->editor;
+ // Library files must be loaded only once.
+ if (!isset($loaded[$name])) {
+ // Load editor.
+ $editor = wysiwyg_get_editor($name);
+ if ($editor) {
+ $default_library_options = array(
+ 'type' => 'file',
+ 'scope' => 'header',
+ 'defer' => FALSE,
+ 'cache' => TRUE,
+ 'preprocess' => TRUE,
+ );
+ // Determine library files to load.
+ // @todo Allow to configure the library/execMode to use.
+ if (isset($profile->settings['library']) && isset($editor['libraries'][$profile->settings['library']])) {
+ $library = $profile->settings['library'];
+ $files = $editor['libraries'][$library]['files'];
+ }
+ else {
+ // Fallback to the first defined library by default (external libraries can change).
+ $library = key($editor['libraries']);
+ $files = array_shift($editor['libraries']);
+ $files = $files['files'];
+ }
+
+ // Check whether the editor requires an initialization script.
+ if (!empty($editor['init callback'])) {
+ $init = $editor['init callback']($editor, $library, $profile);
+ if (!empty($init)) {
+ // Build a file for each of the editors to hold the init scripts.
+ // @todo Aggregate all initialization scripts into one file.
+ $uri = 'public://js/wysiwyg/wysiwyg_' . $name . '_' . drupal_hash_base64($init) . '.js';
+ $init_exists = file_exists($uri);
+ if (!$init_exists) {
+ $js_path = dirname($uri);
+ file_prepare_directory($js_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+ }
+ // Attempt to create the file, or fall back to an inline script (which
+ // will not work in Ajax calls).
+ if (!$init_exists && !file_unmanaged_save_data($init, $uri, FILE_EXISTS_REPLACE)) {
+ drupal_add_js($init, array('type' => 'inline') + $default_library_options);
+ }
+ else {
+ drupal_add_js(file_create_url($uri), $default_library_options);
+ }
+ }
+ }
+
+ foreach ($files as $file => $options) {
+ if (is_array($options)) {
+ $options += $default_library_options;
+ drupal_add_js($editor['library path'] . '/' . $file, $options);
+ }
+ else {
+ drupal_add_js($editor['library path'] . '/' . $options);
+ }
+ }
+ // If editor defines an additional load callback, invoke it.
+ // @todo Isn't the settings callback sufficient?
+ if (isset($editor['load callback']) && function_exists($editor['load callback'])) {
+ $editor['load callback']($editor, $library);
+ }
+ // Load JavaScript integration files for this editor.
+ $files = array();
+ if (isset($editor['js files'])) {
+ $files = $editor['js files'];
+ }
+ foreach ($files as $file) {
+ drupal_add_js($editor['js path'] . '/' . $file);
+ }
+ // Load CSS stylesheets for this editor.
+ $files = array();
+ if (isset($editor['css files'])) {
+ $files = $editor['css files'];
+ }
+ foreach ($files as $file) {
+ drupal_add_css($editor['css path'] . '/' . $file);
+ }
+ $loaded[$name] = TRUE;
+ }
+ else {
+ $loaded[$name] = FALSE;
+ }
+ }
+
+ // Add basic Wysiwyg settings if any editor has been added.
+ if (!isset($settings_added) && $loaded[$name]) {
+ drupal_add_js(array('wysiwyg' => array(
+ 'configs' => array(),
+ 'plugins' => array(),
+ 'disable' => t('Disable rich-text'),
+ 'enable' => t('Enable rich-text'),
+ )), 'setting');
+
+ // Initialize our namespaces in the *header* to do not force editor
+ // integration scripts to check and define Drupal.wysiwyg on its own.
+ drupal_add_js($path . '/wysiwyg.init.js', array('group' => JS_LIBRARY));
+
+ // The 'none' editor is a special editor implementation, allowing us to
+ // attach and detach regular Drupal behaviors just like any other editor.
+ drupal_add_js($path . '/editors/js/none.js');
+
+ // Add wysiwyg.js to the footer to ensure it's executed after the
+ // Drupal.settings array has been rendered and populated. Also, since editor
+ // library initialization functions must be loaded first by the browser,
+ // and Drupal.wysiwygInit() must be executed AFTER editors registered
+ // their callbacks and BEFORE Drupal.behaviors are applied, this must come
+ // last.
+ drupal_add_js($path . '/wysiwyg.js', array('scope' => 'footer'));
+
+ $settings_added = TRUE;
+ }
+
+ return $loaded[$name];
+}
+
+/**
+ * Add editor settings for a given input format.
+ */
+function wysiwyg_add_editor_settings($profile, $theme) {
+ static $formats = array();
+
+ if (!isset($formats[$profile->format])) {
+ $config = wysiwyg_get_editor_config($profile, $theme);
+ // drupal_to_js() does not properly convert numeric array keys, so we need
+ // to use a string instead of the format id.
+ if ($config) {
+ drupal_add_js(array('wysiwyg' => array('configs' => array($profile->editor => array('format' . $profile->format => $config)))), 'setting');
+ }
+ $formats[$profile->format] = TRUE;
+ }
+}
+
+/**
+ * Add settings for external plugins.
+ *
+ * Plugins can be used in multiple profiles, but not necessarily in all. Because
+ * of that, we need to process plugins for each profile, even if most of their
+ * settings are not stored per profile.
+ *
+ * Implementations of hook_wysiwyg_plugin() may execute different code for each
+ * editor. Therefore, we have to invoke those implementations for each editor,
+ * but process the resulting plugins separately for each profile.
+ *
+ * Drupal plugins differ to native plugins in that they have plugin-specific
+ * definitions and settings, which need to be processed only once. But they are
+ * also passed to the editor to prepare settings specific to the editor.
+ * Therefore, we load and process the Drupal plugins only once, and hand off the
+ * effective definitions for each profile to the editor.
+ *
+ * @param $profile
+ * A wysiwyg editor profile.
+ *
+ * @todo Rewrite wysiwyg_process_form() to build a registry of effective
+ * profiles in use, so we can process plugins in multiple profiles in one shot
+ * and simplify this entire function.
+ */
+function wysiwyg_add_plugin_settings($profile) {
+ static $plugins = array();
+ static $processed_plugins = array();
+ static $processed_formats = array();
+
+ // Each input format must only processed once.
+ // @todo ...as long as we do not have multiple profiles per format.
+ if (isset($processed_formats[$profile->format])) {
+ return;
+ }
+ $processed_formats[$profile->format] = TRUE;
+
+ $editor = wysiwyg_get_editor($profile->editor);
+
+ // Collect native plugins for this editor provided via hook_wysiwyg_plugin()
+ // and Drupal plugins provided via hook_wysiwyg_include_directory().
+ if (!array_key_exists($editor['name'], $plugins)) {
+ $plugins[$editor['name']] = wysiwyg_get_plugins($editor['name']);
+ }
+
+ // Nothing to do, if there are no plugins.
+ if (empty($plugins[$editor['name']])) {
+ return;
+ }
+
+ // Determine name of proxy plugin for Drupal plugins.
+ $proxy = (isset($editor['proxy plugin']) ? key($editor['proxy plugin']) : '');
+
+ // Process native editor plugins.
+ if (isset($editor['plugin settings callback'])) {
+ // @todo Require PHP 5.1 in 3.x and use array_intersect_key().
+ $profile_plugins_native = array();
+ foreach ($plugins[$editor['name']] as $plugin => $meta) {
+ // Skip Drupal plugins (handled below).
+ if ($plugin === $proxy) {
+ continue;
+ }
+ // Only keep native plugins that are enabled in this profile.
+ if (isset($profile->settings['buttons'][$plugin])) {
+ $profile_plugins_native[$plugin] = $meta;
+ }
+ }
+ // Invoke the editor's plugin settings callback, so it can populate the
+ // settings for native external plugins with required values.
+ $settings_native = call_user_func($editor['plugin settings callback'], $editor, $profile, $profile_plugins_native);
+
+ if ($settings_native) {
+ drupal_add_js(array('wysiwyg' => array('plugins' => array('format' . $profile->format => array('native' => $settings_native)))), 'setting');
+ }
+ }
+
+ // Process Drupal plugins.
+ if ($proxy && isset($editor['proxy plugin settings callback'])) {
+ $profile_plugins_drupal = array();
+ foreach (wysiwyg_get_all_plugins() as $plugin => $meta) {
+ if (isset($profile->settings['buttons'][$proxy][$plugin])) {
+ // JavaScript and plugin-specific settings for Drupal plugins must be
+ // loaded and processed only once. Plugin information is cached
+ // statically to pass it to the editor's proxy plugin settings callback.
+ if (!isset($processed_plugins[$proxy][$plugin])) {
+ $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin] = $meta;
+ // Load the Drupal plugin's JavaScript.
+ drupal_add_js($meta['js path'] . '/' . $meta['js file']);
+ // Add plugin-specific settings.
+ if (isset($meta['settings'])) {
+ drupal_add_js(array('wysiwyg' => array('plugins' => array('drupal' => array($plugin => $meta['settings'])))), 'setting');
+ }
+ }
+ else {
+ $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin];
+ }
+ }
+ }
+ // Invoke the editor's proxy plugin settings callback, so it can populate
+ // the settings for Drupal plugins with custom, required values.
+ $settings_drupal = call_user_func($editor['proxy plugin settings callback'], $editor, $profile, $profile_plugins_drupal);
+
+ if ($settings_drupal) {
+ drupal_add_js(array('wysiwyg' => array('plugins' => array('format' . $profile->format => array('drupal' => $settings_drupal)))), 'setting');
+ }
+ }
+}
+
+/**
+ * Retrieve available themes for an editor.
+ *
+ * Editor themes control the visual presentation of an editor.
+ *
+ * @param $profile
+ * A wysiwyg editor profile; passed/altered by reference.
+ * @param $selected_theme
+ * An optional theme name that ought to be used.
+ *
+ * @return
+ * An array of theme names, or a single, checked theme name if $selected_theme
+ * was given.
+ */
+function wysiwyg_get_editor_themes(&$profile, $selected_theme = NULL) {
+ static $themes = array();
+
+ if (!isset($themes[$profile->editor])) {
+ $editor = wysiwyg_get_editor($profile->editor);
+ if (isset($editor['themes callback']) && function_exists($editor['themes callback'])) {
+ $themes[$editor['name']] = $editor['themes callback']($editor, $profile);
+ }
+ // Fallback to 'default' otherwise.
+ else {
+ $themes[$editor['name']] = array('default');
+ }
+ }
+
+ // Check optional $selected_theme argument, if given.
+ if (isset($selected_theme)) {
+ // If the passed theme name does not exist, use the first available.
+ if (!in_array($selected_theme, $themes[$profile->editor])) {
+ $selected_theme = $profile->settings['theme'] = $themes[$profile->editor][0];
+ }
+ }
+
+ return isset($selected_theme) ? $selected_theme : $themes[$profile->editor];
+}
+
+/**
+ * Return plugin metadata from the plugin registry.
+ *
+ * @param $editor_name
+ * The internal name of an editor to return plugins for.
+ *
+ * @return
+ * An array for each plugin.
+ */
+function wysiwyg_get_plugins($editor_name) {
+ $plugins = array();
+ if (!empty($editor_name)) {
+ $editor = wysiwyg_get_editor($editor_name);
+ // Add internal editor plugins.
+ if (isset($editor['plugin callback']) && function_exists($editor['plugin callback'])) {
+ $plugins = $editor['plugin callback']($editor);
+ }
+ // Add editor plugins provided via hook_wysiwyg_plugin().
+ $plugins = array_merge($plugins, module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version']));
+ // Add API plugins provided by Drupal modules.
+ // @todo We need to pass the filepath to the plugin icon for Drupal plugins.
+ if (isset($editor['proxy plugin'])) {
+ $plugins += $editor['proxy plugin'];
+ $proxy = key($editor['proxy plugin']);
+ foreach (wysiwyg_get_all_plugins() as $plugin_name => $info) {
+ $plugins[$proxy]['buttons'][$plugin_name] = $info['title'];
+ }
+ }
+ }
+ return $plugins;
+}
+
+/**
+ * Return an array of initial editor settings for a Wysiwyg profile.
+ */
+function wysiwyg_get_editor_config($profile, $theme) {
+ $editor = wysiwyg_get_editor($profile->editor);
+ $settings = array();
+ if (!empty($editor['settings callback']) && function_exists($editor['settings callback'])) {
+ $settings = $editor['settings callback']($editor, $profile->settings, $theme);
+
+ // Allow other modules to alter the editor settings for this format.
+ $context = array('editor' => $editor, 'profile' => $profile, 'theme' => $theme);
+ drupal_alter('wysiwyg_editor_settings', $settings, $context);
+ }
+ return $settings;
+}
+
+/**
+ * Retrieve stylesheets for HTML/IFRAME-based editors.
+ *
+ * This assumes that the content editing area only needs stylesheets defined
+ * for the scope 'theme'.
+ *
+ * @return
+ * An array containing CSS files, including proper base path.
+ */
+function wysiwyg_get_css() {
+ static $files;
+
+ if (isset($files)) {
+ return $files;
+ }
+ // In node form previews, the theme has not been initialized yet.
+ if (!empty($_POST)) {
+ drupal_theme_initialize();
+ }
+
+ $files = array();
+ foreach (drupal_add_css() as $filepath => $info) {
+ if ($info['group'] >= CSS_THEME && $info['media'] != 'print') {
+ if ($info['type'] == 'external') {
+ $files[] = $filepath;
+ }
+ elseif (file_exists($filepath)) {
+ $files[] = base_path() . $filepath;
+ }
+ }
+ }
+ return $files;
+}
+
+/**
+ * Loads a profile for a given text format.
+ *
+ * Since there are commonly not many text formats, and each text format-enabled
+ * form element will possibly have to load every single profile, all existing
+ * profiles are loaded and cached once to reduce the amount of database queries.
+ */
+function wysiwyg_profile_load($format) {
+ $profiles = wysiwyg_profile_load_all();
+ return (isset($profiles[$format]) ? $profiles[$format] : FALSE);
+}
+
+/**
+ * Loads all profiles.
+ */
+function wysiwyg_profile_load_all() {
+ // entity_load(..., FALSE) does not re-use its own static cache upon
+ // repetitive calls, so a custom static cache is required.
+ // @see wysiwyg_entity_info()
+ $profiles = &drupal_static(__FUNCTION__);
+
+ if (!isset($profiles)) {
+ // Additional database cache to support alternative caches like memcache.
+ if ($cached = cache_get('wysiwyg_profiles')) {
+ $profiles = $cached->data;
+ }
+ else {
+ $profiles = entity_load('wysiwyg_profile', FALSE);
+ cache_set('wysiwyg_profiles', $profiles);
+ }
+ }
+
+ return $profiles;
+}
+
+/**
+ * Deletes a profile from the database.
+ */
+function wysiwyg_profile_delete($format) {
+ db_delete('wysiwyg')
+ ->condition('format', $format)
+ ->execute();
+}
+
+/**
+ * Clear all Wysiwyg profile caches.
+ */
+function wysiwyg_profile_cache_clear() {
+ entity_get_controller('wysiwyg_profile')->resetCache();
+ drupal_static_reset('wysiwyg_profile_load_all');
+ cache_clear_all('wysiwyg_profiles', 'cache');
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function wysiwyg_form_user_profile_form_alter(&$form, &$form_state, $form_id) {
+ $account = $form['#user'];
+ $user_formats = filter_formats($account);
+ $options = array();
+ $options_default = array();
+ foreach (wysiwyg_profile_load_all() as $format => $profile) {
+ // Only show profiles that have user_choose enabled.
+ if (!empty($profile->settings['user_choose']) && isset($user_formats[$format])) {
+ $options[$format] = check_plain($user_formats[$format]->name);
+ if (wysiwyg_user_get_status($profile, $account)) {
+ $options_default[] = $format;
+ }
+ }
+ }
+ if (!empty($options)) {
+ $form['wysiwyg']['wysiwyg_status'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Text formats enabled for rich-text editing'),
+ '#options' => $options,
+ '#default_value' => $options_default,
+ );
+ }
+}
+
+/**
+ * Implements hook_user_insert().
+ *
+ * Wysiwyg's user preferences are normally not exposed on the user registration
+ * form, but in case they are manually altered in, we invoke
+ * wysiwyg_user_update() accordingly.
+ */
+function wysiwyg_user_insert(&$edit, $account, $category) {
+ wysiwyg_user_update($edit, $account, $category);
+}
+
+/**
+ * Implements hook_user_update().
+ */
+function wysiwyg_user_update(&$edit, $account, $category) {
+ if (isset($edit['wysiwyg_status'])) {
+ db_delete('wysiwyg_user')
+ ->condition('uid', $account->uid)
+ ->execute();
+ $query = db_insert('wysiwyg_user')
+ ->fields(array('uid', 'format', 'status'));
+ foreach ($edit['wysiwyg_status'] as $format => $status) {
+ $query->values(array(
+ 'uid' => $account->uid,
+ 'format' => $format,
+ 'status' => (int) (bool) $status,
+ ));
+ }
+ $query->execute();
+ }
+}
+
+function wysiwyg_user_get_status($profile, $account = NULL) {
+ global $user;
+
+ if (!isset($account)) {
+ $account = $user;
+ }
+
+ // Default wysiwyg editor status information is only required on forms, so we
+ // do not pre-emptively load and attach this information on every user_load().
+ if (!isset($account->wysiwyg_status)) {
+ $account->wysiwyg_status = db_query("SELECT format, status FROM {wysiwyg_user} WHERE uid = :uid", array(
+ ':uid' => $account->uid,
+ ))->fetchAllKeyed();
+ }
+
+ if (!empty($profile->settings['user_choose']) && isset($account->wysiwyg_status[$profile->format])) {
+ $status = $account->wysiwyg_status[$profile->format];
+ }
+ else {
+ $status = isset($profile->settings['default']) ? $profile->settings['default'] : TRUE;
+ }
+
+ return (bool) $status;
+}
+
+/**
+ * @defgroup wysiwyg_api Wysiwyg API
+ * @{
+ *
+ * @todo Forked from Panels; abstract into a separate API module that allows
+ * contrib modules to define supported include/plugin types.
+ */
+
+/**
+ * Return library information for a given editor.
+ *
+ * @param $name
+ * The internal name of an editor.
+ *
+ * @return
+ * The library information for the editor, or FALSE if $name is unknown or not
+ * installed properly.
+ */
+function wysiwyg_get_editor($name) {
+ $editors = wysiwyg_get_all_editors();
+ return isset($editors[$name]) && $editors[$name]['installed'] ? $editors[$name] : FALSE;
+}
+
+/**
+ * Compile a list holding all supported editors including installed editor version information.
+ */
+function wysiwyg_get_all_editors() {
+ static $editors;
+
+ if (isset($editors)) {
+ return $editors;
+ }
+
+ $editors = wysiwyg_load_includes('editors', 'editor');
+ foreach ($editors as $editor => $properties) {
+ // Fill in required properties.
+ $editors[$editor] += array(
+ 'title' => '',
+ 'vendor url' => '',
+ 'download url' => '',
+ 'editor path' => wysiwyg_get_path($editors[$editor]['name']),
+ 'library path' => wysiwyg_get_path($editors[$editor]['name']),
+ 'libraries' => array(),
+ 'version callback' => NULL,
+ 'themes callback' => NULL,
+ 'settings form callback' => NULL,
+ 'settings callback' => NULL,
+ 'plugin callback' => NULL,
+ 'plugin settings callback' => NULL,
+ 'versions' => array(),
+ 'js path' => $editors[$editor]['path'] . '/js',
+ 'css path' => $editors[$editor]['path'] . '/css',
+ );
+ // Check whether library is present.
+ if (!($editors[$editor]['installed'] = file_exists($editors[$editor]['library path']))) {
+ continue;
+ }
+ // Detect library version.
+ if (function_exists($editors[$editor]['version callback'])) {
+ $editors[$editor]['installed version'] = $editors[$editor]['version callback']($editors[$editor]);
+ }
+ if (empty($editors[$editor]['installed version'])) {
+ $editors[$editor]['error'] = t('The version of %editor could not be detected.', array('%editor' => $properties['title']));
+ $editors[$editor]['installed'] = FALSE;
+ continue;
+ }
+ // Determine to which supported version the installed version maps.
+ ksort($editors[$editor]['versions']);
+ $version = 0;
+ foreach ($editors[$editor]['versions'] as $supported_version => $version_properties) {
+ if (version_compare($editors[$editor]['installed version'], $supported_version, '>=')) {
+ $version = $supported_version;
+ }
+ }
+ if (!$version) {
+ $editors[$editor]['error'] = t('The installed version %version of %editor is not supported.', array('%version' => $editors[$editor]['installed version'], '%editor' => $editors[$editor]['title']));
+ $editors[$editor]['installed'] = FALSE;
+ continue;
+ }
+ // Apply library version specific definitions and overrides.
+ $editors[$editor] = array_merge($editors[$editor], $editors[$editor]['versions'][$version]);
+ unset($editors[$editor]['versions']);
+ }
+ return $editors;
+}
+
+/**
+ * Invoke hook_wysiwyg_plugin() in all modules.
+ */
+function wysiwyg_get_all_plugins() {
+ static $plugins;
+
+ if (isset($plugins)) {
+ return $plugins;
+ }
+
+ $plugins = wysiwyg_load_includes('plugins', 'plugin');
+ foreach ($plugins as $name => $properties) {
+ $plugin = &$plugins[$name];
+ // Fill in required/default properties.
+ $plugin += array(
+ 'title' => $plugin['name'],
+ 'vendor url' => '',
+ 'js path' => $plugin['path'] . '/' . $plugin['name'],
+ 'js file' => $plugin['name'] . '.js',
+ 'css path' => $plugin['path'] . '/' . $plugin['name'],
+ 'css file' => $plugin['name'] . '.css',
+ 'icon path' => $plugin['path'] . '/' . $plugin['name'] . '/images',
+ 'icon file' => $plugin['name'] . '.png',
+ 'dialog path' => $plugin['name'],
+ 'dialog settings' => array(),
+ 'settings callback' => NULL,
+ 'settings form callback' => NULL,
+ );
+ // Fill in default settings.
+ $plugin['settings'] += array(
+ 'path' => base_path() . $plugin['path'] . '/' . $plugin['name'],
+ );
+ // Check whether library is present.
+ if (!($plugin['installed'] = file_exists($plugin['js path'] . '/' . $plugin['js file']))) {
+ continue;
+ }
+ }
+ return $plugins;
+}
+
+/**
+ * Load include files for wysiwyg implemented by all modules.
+ *
+ * @param $type
+ * The type of includes to search for, can be 'editors'.
+ * @param $hook
+ * The hook name to invoke.
+ * @param $file
+ * An optional include file name without .inc extension to limit the search to.
+ *
+ * @see wysiwyg_get_directories(), _wysiwyg_process_include()
+ */
+function wysiwyg_load_includes($type = 'editors', $hook = 'editor', $file = NULL) {
+ // Determine implementations.
+ $directories = wysiwyg_get_directories($type);
+ $directories['wysiwyg'] = drupal_get_path('module', 'wysiwyg') . '/' . $type;
+ $file_list = array();
+ foreach ($directories as $module => $path) {
+ $file_list[$module] = drupal_system_listing("/{$file}.inc\$/", $path, 'name', 0);
+ }
+
+ // Load implementations.
+ $info = array();
+ foreach (array_filter($file_list) as $module => $files) {
+ foreach ($files as $file) {
+ include_once './' . $file->uri;
+ $result = _wysiwyg_process_include($module, $module . '_' . $file->name, dirname($file->uri), $hook);
+ if (is_array($result)) {
+ $info = array_merge($info, $result);
+ }
+ }
+ }
+ return $info;
+}
+
+/**
+ * Helper function to build paths to libraries.
+ *
+ * @param $library
+ * The external library name to return the path for.
+ * @param $base_path
+ * Whether to prefix the resulting path with base_path().
+ *
+ * @return
+ * The path to the specified library.
+ *
+ * @ingroup libraries
+ */
+function wysiwyg_get_path($library, $base_path = FALSE) {
+ static $libraries;
+
+ if (!isset($libraries)) {
+ $libraries = wysiwyg_get_libraries();
+ }
+ if (!isset($libraries[$library])) {
+ // Most often, external libraries can be shared across multiple sites.
+ return 'sites/all/libraries/' . $library;
+ }
+
+ $path = ($base_path ? base_path() : '');
+ $path .= $libraries[$library];
+
+ return $path;
+}
+
+/**
+ * Return an array of library directories.
+ *
+ * Returns an array of library directories from the all-sites directory
+ * (i.e. sites/all/libraries/), the profiles directory, and site-specific
+ * directory (i.e. sites/somesite/libraries/). The returned array will be keyed
+ * by the library name. Site-specific libraries are prioritized over libraries
+ * in the default directories. That is, if a library with the same name appears
+ * in both the site-wide directory and site-specific directory, only the
+ * site-specific version will be listed.
+ *
+ * @return
+ * A list of library directories.
+ *
+ * @ingroup libraries
+ */
+function wysiwyg_get_libraries() {
+ global $profile;
+
+ // When this function is called during Drupal's initial installation process,
+ // the name of the profile that is about to be installed is stored in the
+ // global $profile variable. At all other times, the regular system variable
+ // contains the name of the current profile, and we can call variable_get()
+ // to determine the profile.
+ if (!isset($profile)) {
+ $profile = variable_get('install_profile', 'default');
+ }
+
+ $directory = 'libraries';
+ $searchdir = array();
+ $config = conf_path();
+
+ // The 'profiles' directory contains pristine collections of modules and
+ // themes as organized by a distribution. It is pristine in the same way
+ // that /modules is pristine for core; users should avoid changing anything
+ // there in favor of sites/all or sites/<domain> directories.
+ if (file_exists("profiles/$profile/$directory")) {
+ $searchdir[] = "profiles/$profile/$directory";
+ }
+
+ // Always search sites/all/*.
+ $searchdir[] = 'sites/all/' . $directory;
+
+ // Also search sites/<domain>/*.
+ if (file_exists("$config/$directory")) {
+ $searchdir[] = "$config/$directory";
+ }
+
+ // Retrieve list of directories.
+ // @todo Core: Allow to scan for directories.
+ $directories = array();
+ $nomask = array('CVS');
+ foreach ($searchdir as $dir) {
+ if (is_dir($dir) && $handle = opendir($dir)) {
+ while (FALSE !== ($file = readdir($handle))) {
+ if (!in_array($file, $nomask) && $file[0] != '.') {
+ if (is_dir("$dir/$file")) {
+ $directories[$file] = "$dir/$file";
+ }
+ }
+ }
+ closedir($handle);
+ }
+ }
+
+ return $directories;
+}
+
+/**
+ * Return a list of directories by modules implementing wysiwyg_include_directory().
+ *
+ * @param $plugintype
+ * The type of a plugin; can be 'editors'.
+ *
+ * @return
+ * An array containing module names suffixed with '_' and their defined
+ * directory.
+ *
+ * @see wysiwyg_load_includes(), _wysiwyg_process_include()
+ */
+function wysiwyg_get_directories($plugintype) {
+ $directories = array();
+ foreach (module_implements('wysiwyg_include_directory') as $module) {
+ $result = module_invoke($module, 'wysiwyg_include_directory', $plugintype);
+ if (isset($result) && is_string($result)) {
+ $directories[$module] = drupal_get_path('module', $module) . '/' . $result;
+ }
+ }
+ return $directories;
+}
+
+/**
+ * Process a single hook implementation of a wysiwyg editor.
+ *
+ * @param $module
+ * The module that owns the hook.
+ * @param $identifier
+ * Either the module or 'wysiwyg_' . $file->name
+ * @param $hook
+ * The name of the hook being invoked.
+ */
+function _wysiwyg_process_include($module, $identifier, $path, $hook) {
+ $function = $identifier . '_' . $hook;
+ if (!function_exists($function)) {
+ return NULL;
+ }
+ $result = $function();
+ if (!isset($result) || !is_array($result)) {
+ return NULL;
+ }
+
+ // Fill in defaults.
+ foreach ($result as $editor => $properties) {
+ $result[$editor]['module'] = $module;
+ $result[$editor]['name'] = $editor;
+ $result[$editor]['path'] = $path;
+ }
+ return $result;
+}
+
+/**
+ * @} End of "defgroup wysiwyg_api".
+ */
+
+/**
+ * Implements hook_features_api().
+ */
+function wysiwyg_features_api() {
+ return array(
+ 'wysiwyg' => array(
+ 'name' => t('Wysiwyg profiles'),
+ 'default_hook' => 'wysiwyg_default_profiles',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => TRUE,
+ 'file' => drupal_get_path('module', 'wysiwyg') . '/wysiwyg.features.inc',
+ ),
+ );
+}
+