summaryrefslogtreecommitdiff
path: root/sites/all/modules/ctools
diff options
context:
space:
mode:
Diffstat (limited to 'sites/all/modules/ctools')
-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
420 files changed, 52786 insertions, 0 deletions
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)),
+ );
+ }
+}