From 61cfe6d6058b90bf63d0be4adbc4781a7dede62f Mon Sep 17 00:00:00 2001 From: Melissa Miller <miller.2676@osu.edu> Date: Tue, 19 May 2015 21:17:44 -0400 Subject: [PATCH] daily build 1 Theme 3 fixes; Manual Crop for user pictures; Webform security update; Add makefile to OCIO Search; Remove user import module; Remove default content panes; Set Web Form default status to published; --- CHANGELOG.txt | 20 + includes/bootstrap.inc | 2 +- includes/common.inc | 22 +- includes/entity.inc | 34 + includes/form.inc | 4 +- includes/install.core.inc | 9 +- .../field_sql_storage.module | 24 +- .../field_sql_storage/field_sql_storage.test | 4 +- modules/filter/filter.module | 1 + modules/filter/filter.test | 6 +- modules/node/node.install | 10 + modules/node/node.module | 8 +- modules/poll/poll.module | 1 - modules/simpletest/drupal_web_test_case.php | 7 +- .../css_test_files/css_input_with_import.css | 2 + .../css_input_with_import.css.optimized.css | 2 +- .../css_input_with_import.css.unoptimized.css | 2 + modules/simpletest/tests/common.test | 1 + modules/simpletest/tests/file.test | 10 - modules/simpletest/tests/session.test | 50 + modules/system/system.api.php | 58 +- modules/system/system.module | 1 - modules/system/system.queue.inc | 2 +- modules/system/system.test | 5 + modules/translation/translation.module | 2 +- modules/update/update.module | 3 + profiles/wcm_base/README.md | 104 +- .../date_popup_authored.info | 9 +- .../modules/contrib/draggableviews/README.txt | 19 +- .../draggableviews/draggableviews.info | 6 +- .../draggableviews_book.info | 6 +- .../draggableviews_test.info | 6 +- .../views/draggableviews_handler_sort.inc | 2 +- .../contrib/file_entity/file_entity.info | 8 + .../file_entity/tests/file_entity_test.info | 8 + .../contrib/manualcrop/manualcrop.info | 8 + .../wcm_base/modules/contrib/media/media.info | 8 + .../media_bulk_upload/media_bulk_upload.info | 8 + .../media_internet/media_internet.info | 8 + .../media_migrate_file_types.info | 8 + .../modules/media_wysiwyg/media_wysiwyg.info | 8 + .../media_wysiwyg_view_mode.info | 8 + .../media/modules/mediafield/mediafield.info | 8 + .../media/tests/media_module_test.info | 8 + .../contrib/media_youtube/media_youtube.info | 8 + .../modules/contrib/user_import/API.txt | 44 - .../modules/contrib/user_import/LICENSE.txt | 339 ---- .../modules/contrib/user_import/README.txt | 214 --- .../modules/contrib/user_import/sample.txt | 37 - .../user_import/supported/content_profile.inc | 165 -- .../contrib/user_import/supported/field.inc | 191 --- .../user_import/supported/location.inc | 118 -- .../contrib/user_import/supported/profile.inc | 206 --- .../user_import/supported/subscribed.inc | 103 -- .../contrib/user_import/supported/user.inc | 384 ----- .../user_import/supported/user_import.inc | 431 ----- .../user_import/supported/watchdog.inc | 18 - .../contrib/user_import/user_import.admin.inc | 1089 ------------- .../contrib/user_import/user_import.drush.inc | 110 -- .../user_import/user_import.import.inc | 339 ---- .../contrib/user_import/user_import.info | 19 - .../contrib/user_import/user_import.install | 263 --- .../contrib/user_import/user_import.module | 817 ---------- .../contrib/user_import/user_import.test | 586 ------- .../modules/contrib/webform/README.txt | 1 + .../contrib/webform/components/date.inc | 192 ++- .../contrib/webform/components/email.inc | 73 +- .../contrib/webform/components/fieldset.inc | 2 +- .../contrib/webform/components/file.inc | 126 +- .../contrib/webform/components/grid.inc | 85 +- .../contrib/webform/components/hidden.inc | 14 +- .../contrib/webform/components/markup.inc | 27 +- .../contrib/webform/components/number.inc | 24 +- .../contrib/webform/components/pagebreak.inc | 4 +- .../contrib/webform/components/select.inc | 210 ++- .../contrib/webform/components/textarea.inc | 18 +- .../contrib/webform/components/textfield.inc | 18 +- .../contrib/webform/components/time.inc | 205 ++- .../contrib/webform/css/webform-admin.css | 8 +- .../includes/exporters/webform_exporter.inc | 25 + .../exporters/webform_exporter_excel_xlsx.inc | 20 +- .../webform/includes/webform.admin.inc | 91 +- .../webform/includes/webform.components.inc | 67 +- .../webform/includes/webform.conditionals.inc | 473 +++++- .../webform/includes/webform.emails.inc | 77 +- .../webform/includes/webform.export.inc | 2 +- .../webform/includes/webform.pages.inc | 44 +- .../webform/includes/webform.report.inc | 392 +++-- .../webform/includes/webform.submissions.inc | 350 ++-- .../includes/webform.webformconditionals.inc | 485 ++++++ .../contrib/webform/js/webform-admin.js | 91 +- .../modules/contrib/webform/js/webform.js | 422 +++-- .../templates/webform-analysis.tpl.php | 4 + .../templates/webform-confirmation.tpl.php | 3 +- .../webform/templates/webform-mail.tpl.php | 6 +- .../webform-submission-information.tpl.php | 4 +- .../contrib/webform/tests/conditionals.test | 37 +- .../contrib/webform/tests/webform.test | 88 + .../views/default_views/webform_analysis.inc | 61 + .../views/default_views/webform_results.inc | 4 +- .../default_views/webform_submissions.inc | 2 +- .../views/default_views/webform_webforms.inc | 219 +++ .../contrib/webform/views/webform.views.inc | 112 +- .../webform_handler_area_result_pager.inc | 10 +- .../views/webform_handler_field_form_body.inc | 2 +- .../webform_handler_field_node_link_edit.inc | 19 +- ...ebform_handler_field_node_link_results.inc | 24 +- .../webform_handler_field_submission_data.inc | 29 +- ...webform_handler_filter_submission_data.inc | 4 +- .../views/webform_handler_numeric_data.inc | 173 ++ .../webform_plugin_row_submission_view.inc | 8 +- .../modules/contrib/webform/webform.api.php | 25 +- .../modules/contrib/webform/webform.drush.inc | 100 +- .../modules/contrib/webform/webform.info | 13 +- .../modules/contrib/webform/webform.install | 428 ++++- .../modules/contrib/webform/webform.module | 1418 +++++++++++------ .../contrib/webform/webform.tokens.inc | 63 +- .../modules/contrib/workbench/workbench.info | 8 + .../workbench_media/workbench_media.info | 8 + .../tests/workbench_moderation_test.info | 8 + .../workbench_moderation.info | 8 + .../ocio_front_page/ocio_front_page.info | 1 - .../ocio_front_page.pages_default.inc | 150 -- ...o_permissions.features.user_permission.inc | 4 + .../ocio_permissions/ocio_permissions.info | 1 - .../custom/ocio_search/ocio_search.make | 16 + ...o_user_config.features.user_permission.inc | 9 - .../ocio_user_config/ocio_user_config.info | 6 +- .../ocio_user_config.strongarm.inc | 8 +- ...user_directory.features.field_instance.inc | 8 +- .../custom/ocio_web_form/ocio_web_form.info | 1 - .../ocio_web_form/ocio_web_form.strongarm.inc | 4 +- .../css/layouts/ocio-3/ocio-3.layout.css | 2 +- .../layouts/ocio-3/ocio-3.layout.no-query.css | 2 +- .../ocio_omega_3/css/ocio-3.no-query.css | 2 +- .../themes/ocio_omega_3/css/ocio-3.styles.css | 2 +- .../ocio_omega_3/sass/base/_layout-base.scss | 2 +- profiles/wcm_base/wcm_base.info | 5 - profiles/wcm_base/wcm_base.make | 14 +- sites/all/libraries/README.txt | 2 + 140 files changed, 5459 insertions(+), 7225 deletions(-) delete mode 100644 profiles/wcm_base/modules/contrib/user_import/API.txt delete mode 100755 profiles/wcm_base/modules/contrib/user_import/LICENSE.txt delete mode 100644 profiles/wcm_base/modules/contrib/user_import/README.txt delete mode 100644 profiles/wcm_base/modules/contrib/user_import/sample.txt delete mode 100644 profiles/wcm_base/modules/contrib/user_import/supported/content_profile.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/supported/field.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/supported/location.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/supported/profile.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/supported/subscribed.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/supported/user.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/supported/user_import.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/supported/watchdog.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/user_import.admin.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/user_import.drush.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/user_import.import.inc delete mode 100644 profiles/wcm_base/modules/contrib/user_import/user_import.info delete mode 100644 profiles/wcm_base/modules/contrib/user_import/user_import.install delete mode 100644 profiles/wcm_base/modules/contrib/user_import/user_import.module delete mode 100644 profiles/wcm_base/modules/contrib/user_import/user_import.test create mode 100644 profiles/wcm_base/modules/contrib/webform/includes/webform.webformconditionals.inc create mode 100644 profiles/wcm_base/modules/contrib/webform/views/default_views/webform_analysis.inc create mode 100644 profiles/wcm_base/modules/contrib/webform/views/default_views/webform_webforms.inc create mode 100644 profiles/wcm_base/modules/contrib/webform/views/webform_handler_numeric_data.inc create mode 100644 profiles/wcm_base/modules/custom/ocio_search/ocio_search.make create mode 100644 sites/all/libraries/README.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d6f1e47c..bf684a16 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,24 @@ +Drupal 7.37, 2015-05-07 +----------------------- +- Fixed a regression in Drupal 7.36 which caused certain kinds of content types + to become disabled if they were defined by a no-longer-enabled module. +- Removed a confusing description regarding automatic time zone detection from + the user account form (minor UI and data structure change). +- Allowed custom HTML tags with a dash in the name to pass through filter_xss() + when specified in the list of allowed tags. +- Allowed hook_field_schema() implementations to specify indexes for fields + based on a fixed-length column prefix (rather than the entire column), as was + already allowed in hook_schema() implementations. +- Fixed PDO exceptions on PostgreSQL when accessing invalid entity URLs. +- Added a sites/all/libraries folder to the codebase, with instructions for + using it. +- Added a description to the "Administer text formats and filters" permission + on the Permissions page (string change). +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + Drupal 7.36, 2015-04-01 ----------------------- - Added a 'file_public_schema' variable which allows modules that define diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 51c8de51..1088cc73 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.36'); +define('VERSION', '7.37'); /** * Core API compatibility. diff --git a/includes/common.inc b/includes/common.inc index 1a3096c8..9a249ad9 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1522,7 +1522,7 @@ function _filter_xss_split($m, $store = FALSE) { return '<'; } - if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) { + if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9\-]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) { // Seriously malformed. return ''; } @@ -3802,7 +3802,7 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { // Replaces @import commands with the actual stylesheet content. // This happens recursively but omits external files. - $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents); + $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)(?!\/\/)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents); return $contents; } @@ -7154,6 +7154,23 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU } } +/** + * Retrieves the type for every field in a table schema. + * + * @param $table + * The name of the table from which to retrieve type information. + * + * @return + * An array of types, keyed by field name. + */ +function drupal_schema_field_types($table) { + $table_schema = drupal_get_schema($table); + foreach ($table_schema['fields'] as $field_name => $field_info) { + $field_types[$field_name] = isset($field_info['type']) ? $field_info['type'] : NULL; + } + return $field_types; +} + /** * Retrieves a list of fields from a table schema. * @@ -7778,6 +7795,7 @@ function entity_get_info($entity_type = NULL) { // Prepare entity schema fields SQL info for // DrupalEntityControllerInterface::buildQuery(). if (isset($entity_info[$name]['base table'])) { + $entity_info[$name]['base table field types'] = drupal_schema_field_types($entity_info[$name]['base table']); $entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']); if (isset($entity_info[$name]['revision table'])) { $entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']); diff --git a/includes/entity.inc b/includes/entity.inc index 27434d04..62359a94 100644 --- a/includes/entity.inc +++ b/includes/entity.inc @@ -183,6 +183,11 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { } } + // Ensure integer entity IDs are valid. + if (!empty($ids)) { + $this->cleanIds($ids); + } + // Load any remaining entities from the database. This is the case if $ids // is set to FALSE (so we load all entities), if there are any ids left to // load, if loading a revision, or if $conditions was passed without $ids. @@ -223,6 +228,35 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { return $entities; } + /** + * Ensures integer entity IDs are valid. + * + * The identifier sanitization provided by this method has been introduced + * as Drupal used to rely on the database to facilitate this, which worked + * correctly with MySQL but led to errors with other DBMS such as PostgreSQL. + * + * @param array $ids + * The entity IDs to verify. Non-integer IDs are removed from this array if + * the entity type requires IDs to be integers. + */ + protected function cleanIds(&$ids) { + $entity_info = entity_get_info($this->entityType); + if (isset($entity_info['base table field types'])) { + $id_type = $entity_info['base table field types'][$this->idKey]; + if ($id_type == 'serial' || $id_type == 'int') { + $ids = array_filter($ids, array($this, 'filterId')); + $ids = array_map('intval', $ids); + } + } + } + + /** + * Callback for array_filter that removes non-integer IDs. + */ + protected function filterId($id) { + return is_numeric($id) && $id == (int) $id; + } + /** * Builds the query to load the entity. * diff --git a/includes/form.inc b/includes/form.inc index 0d358c2b..306747ba 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -2662,8 +2662,8 @@ function _form_options_flatten($array) { * - #required: (optional) Whether the user needs to select an option (TRUE) * or not (FALSE). Defaults to FALSE. * - #empty_option: (optional) The label to show for the first default option. - * By default, the label is automatically set to "- Please select -" for a - * required field and "- None -" for an optional field. + * By default, the label is automatically set to "- Select -" for a required + * field and "- None -" for an optional field. * - #empty_value: (optional) The value for the first default option, which is * used to determine whether the user submitted a value or not. * - If #required is TRUE, this defaults to '' (an empty string). diff --git a/includes/install.core.inc b/includes/install.core.inc index 38ad7248..e5a65865 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -362,7 +362,8 @@ function install_run_tasks(&$install_state) { * Runs an individual installation task. * * @param $task - * An array of information about the task to be run. + * An array of information about the task to be run as returned by + * hook_install_tasks(). * @param $install_state * An array of information about the current installation state. This is * passed in by reference so that it can be modified by the task. @@ -478,11 +479,15 @@ function install_run_task($task, &$install_state) { * the page request evolves (for example, if an installation profile hasn't * been selected yet, we don't yet know which profile tasks need to be run). * + * You can override this using hook_install_tasks() or + * hook_install_tasks_alter(). + * * @param $install_state * An array of information about the current installation state. * * @return - * A list of tasks to be performed, with associated metadata. + * A list of tasks to be performed, with associated metadata as returned by + * hook_install_tasks(). */ function install_tasks_to_perform($install_state) { // Start with a list of all currently available tasks. diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module index c7201dd7..842893ad 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -223,7 +223,17 @@ function _field_sql_storage_schema($field) { foreach ($field['indexes'] as $index_name => $columns) { $real_name = _field_sql_storage_indexname($field['field_name'], $index_name); foreach ($columns as $column_name) { - $current['indexes'][$real_name][] = _field_sql_storage_columnname($field['field_name'], $column_name); + // Indexes can be specified as either a column name or an array with + // column name and length. Allow for either case. + if (is_array($column_name)) { + $current['indexes'][$real_name][] = array( + _field_sql_storage_columnname($field['field_name'], $column_name[0]), + $column_name[1], + ); + } + else { + $current['indexes'][$real_name][] = _field_sql_storage_columnname($field['field_name'], $column_name); + } } } @@ -332,7 +342,17 @@ function field_sql_storage_field_storage_update_field($field, $prior_field, $has $real_name = _field_sql_storage_indexname($field['field_name'], $name); $real_columns = array(); foreach ($columns as $column_name) { - $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name); + // Indexes can be specified as either a column name or an array with + // column name and length. Allow for either case. + if (is_array($column_name)) { + $real_columns[] = array( + _field_sql_storage_columnname($field['field_name'], $column_name[0]), + $column_name[1], + ); + } + else { + $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name); + } } db_add_index($table, $real_name, $real_columns); db_add_index($revision_table, $real_name, $real_columns); diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.test b/modules/field/modules/field_sql_storage/field_sql_storage.test index 072739cb..7c88ac77 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.test +++ b/modules/field/modules/field_sql_storage/field_sql_storage.test @@ -355,14 +355,14 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { field_attach_insert('test_entity', $entity); // Add an index - $field = array('field_name' => $field_name, 'indexes' => array('value' => array('value'))); + $field = array('field_name' => $field_name, 'indexes' => array('value' => array(array('value', 255)))); field_update_field($field); foreach ($tables as $table) { $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), format_string("Index on value created in %table", array('%table' => $table))); } // Add a different index, removing the existing custom one. - $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array('value', 'format'))); + $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array(array('value', 127), array('format', 127)))); field_update_field($field); foreach ($tables as $table) { $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value_format"), format_string("Index on value_format created in %table", array('%table' => $table))); diff --git a/modules/filter/filter.module b/modules/filter/filter.module index 74621f10..f4bab9e6 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -340,6 +340,7 @@ function filter_admin_format_title($format) { function filter_permission() { $perms['administer filters'] = array( 'title' => t('Administer text formats and filters'), + 'description' => t('Define how text is handled by combining filters into <a href="@url">text formats</a>.', array('@url' => url('admin/config/content/formats'))), 'restrict access' => TRUE, ); diff --git a/modules/filter/filter.test b/modules/filter/filter.test index fe9cfc36..ddea6afb 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -1148,7 +1148,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { // Setup dummy filter object. $filter = new stdClass(); $filter->settings = array( - 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>', + 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <test-element>', 'filter_html_help' => 1, 'filter_html_nofollow' => 0, ); @@ -1184,6 +1184,10 @@ class FilterUnitTestCase extends DrupalUnitTestCase { $f = _filter_html('<code onerror> </code>', $filter); $this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes on default.'); + + // Custom tags are supported and should be allowed through. + $f = _filter_html('<test-element></test-element>', $filter); + $this->assertNormalized($f, 'test-element', 'HTML filter should allow custom elements.'); } /** diff --git a/modules/node/node.install b/modules/node/node.install index 76c2aec2..0b0a7bd5 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -933,6 +933,16 @@ function node_update_7014() { db_add_index('node', 'language', array('language')); } +/** + * Enable node types that may have been erroneously disabled in Drupal 7.36. + */ +function node_update_7015() { + db_update('node_type') + ->fields(array('disabled' => 0)) + ->condition('base', 'node_content') + ->execute(); +} + /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/node/node.module b/modules/node/node.module index fd848e24..7a6246d5 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -740,9 +740,11 @@ function _node_types_build($rebuild = FALSE) { $type_db = $type_object->type; // Original disabled value. $disabled = $type_object->disabled; - // Check for node types either from disabled modules or otherwise not defined - // and mark as disabled. - if (empty($type_object->custom) && empty($_node_types->types[$type_db])) { + // Check for node types from disabled modules and mark their types for removal. + // Types defined by the node module in the database (rather than by a separate + // module using hook_node_info) have a base value of 'node_content'. The isset() + // check prevents errors on old (pre-Drupal 7) databases. + if (isset($type_object->base) && $type_object->base != 'node_content' && empty($_node_types->types[$type_db])) { $type_object->disabled = TRUE; } if (isset($_node_types->types[$type_db])) { diff --git a/modules/poll/poll.module b/modules/poll/poll.module index d3d64b17..bfc72bf9 100644 --- a/modules/poll/poll.module +++ b/modules/poll/poll.module @@ -191,7 +191,6 @@ function poll_node_info() { 'base' => 'poll', 'description' => t('A <em>poll</em> is a question with a set of possible responses. A <em>poll</em>, once created, automatically provides a simple running count of the number of votes received for each response.'), 'title_label' => t('Question'), - 'has_body' => FALSE, ) ); } diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index 271efff3..fb5c6a6c 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -2257,8 +2257,13 @@ class DrupalWebTestCase extends DrupalTestCase { if ($wrapperNode) { // ajax.js adds an enclosing DIV to work around a Safari bug. $newDom = new DOMDocument(); + // DOM can load HTML soup. But, HTML soup can throw warnings, + // suppress them. $newDom->loadHTML('<div>' . $command['data'] . '</div>'); - $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE); + // Suppress warnings thrown when duplicate HTML IDs are + // encountered. This probably means we are replacing an element + // with the same ID. + $newNode = @$dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE); $method = isset($command['method']) ? $command['method'] : $ajax_settings['method']; // The "method" is a jQuery DOM manipulation function. Emulate // each one using PHP's DOMNode API. diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css b/modules/simpletest/files/css_test_files/css_input_with_import.css index 87afcb35..484db837 100644 --- a/modules/simpletest/files/css_test_files/css_input_with_import.css +++ b/modules/simpletest/files/css_test_files/css_input_with_import.css @@ -1,5 +1,7 @@ +@import url("http://example.com/style.css"); +@import url("//example.com/style.css"); @import "import1.css"; @import "import2.css"; diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css index a05f939f..a2af7b36 100644 --- a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css +++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css @@ -1,4 +1,4 @@ -ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);} +@import url("http://example.com/style.css");@import url("//example.com/style.css");ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);} p,select{font:1em/160% Verdana,sans-serif;color:#494949;} body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css index b8c7778d..bc3c7b64 100644 --- a/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css +++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css @@ -1,5 +1,7 @@ +@import url("http://example.com/style.css"); +@import url("//example.com/style.css"); ul, select { font: 1em/160% Verdana, sans-serif; diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index 0f0347fc..fcc9791a 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -942,6 +942,7 @@ class CascadingStylesheetsUnitTest extends DrupalUnitTestCase { * - Proper URLs in imported files. (https://drupal.org/node/265719) * - Retain pseudo-selectors. (https://drupal.org/node/460448) * - Don't adjust data URIs. (https://drupal.org/node/2142441) + * - Files imported from external URLs. (https://drupal.org/node/2014851) */ function testLoadCssBasic() { // Array of files to test living in 'simpletest/files/css_test_files/'. diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index b75327f1..89ecac7a 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -480,13 +480,6 @@ class FileValidatorTest extends DrupalWebTestCase { * Test file_validate_size(). */ function testFileValidateSize() { - global $user; - $original_user = $user; - drupal_save_session(FALSE); - - // Run these tests as a regular user. - $user = $this->drupalCreateUser(); - // Create a file with a size of 1000 bytes, and quotas of only 1 byte. $file = new stdClass(); $file->filesize = 1000; @@ -498,9 +491,6 @@ class FileValidatorTest extends DrupalWebTestCase { $this->assertEqual(count($errors), 1, 'Error for the user being over their limit.', 'File'); $errors = file_validate_size($file, 1, 1); $this->assertEqual(count($errors), 2, 'Errors for both the file and their limit.', 'File'); - - $user = $original_user; - drupal_save_session(TRUE); } } diff --git a/modules/simpletest/tests/session.test b/modules/simpletest/tests/session.test index 097503b6..893d03e9 100644 --- a/modules/simpletest/tests/session.test +++ b/modules/simpletest/tests/session.test @@ -477,6 +477,56 @@ class SessionHttpsTestCase extends DrupalWebTestCase { $this->assertResponse(200); } + /** + * Tests that empty session IDs do not cause unrelated sessions to load. + */ + public function testEmptySessionId() { + global $is_https; + + if ($is_https) { + $secure_session_name = session_name(); + } + else { + $secure_session_name = 'S' . session_name(); + } + + // Enable mixed mode for HTTP and HTTPS. + variable_set('https', TRUE); + + $admin_user = $this->drupalCreateUser(array('access administration pages')); + $standard_user = $this->drupalCreateUser(array('access content')); + + // First log in as the admin user on HTTP. + // We cannot use $this->drupalLogin() here because we need to use the + // special http.php URLs. + $edit = array( + 'name' => $admin_user->name, + 'pass' => $admin_user->pass_raw + ); + $this->drupalGet('user'); + $form = $this->xpath('//form[@id="user-login"]'); + $form[0]['action'] = $this->httpUrl('user'); + $this->drupalPost(NULL, $edit, t('Log in')); + + $this->curlClose(); + + // Now start a session for the standard user on HTTPS. + $edit = array( + 'name' => $standard_user->name, + 'pass' => $standard_user->pass_raw + ); + $this->drupalGet('user'); + $form = $this->xpath('//form[@id="user-login"]'); + $form[0]['action'] = $this->httpsUrl('user'); + $this->drupalPost(NULL, $edit, t('Log in')); + + // Make the secure session cookie blank. + curl_setopt($this->curlHandle, CURLOPT_COOKIE, "$secure_session_name="); + $this->drupalGet($this->httpsUrl('user')); + $this->assertNoText($admin_user->name, 'User is not logged in as admin'); + $this->assertNoText($standard_user->name, "The user's own name is not displayed because the invalid session cookie has logged them out."); + } + /** * Test that there exists a session with two specific session IDs. * diff --git a/modules/system/system.api.php b/modules/system/system.api.php index 632e2ffd..c87c1b57 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -606,7 +606,7 @@ function hook_cron() { * @return * An associative array where the key is the queue name and the value is * again an associative array. Possible keys are: - * - 'worker callback': A PHP callable to call that is an implementation of + * - 'worker callback': The name of an implementation of * callback_queue_worker(). * - 'time': (optional) How much time Drupal should spend on calling this * worker in seconds. Defaults to 15. @@ -643,28 +643,6 @@ function hook_cron_queue_info_alter(&$queues) { $queues['aggregator_feeds']['time'] = 90; } -/** - * Work on a single queue item. - * - * Callback for hook_queue_info(). - * - * @param $queue_item_data - * The data that was passed to DrupalQueue::createItem() when the item was - * queued. - * - * @throws \Exception - * The worker callback may throw an exception to indicate there was a problem. - * The cron process will log the exception, and leave the item in the queue to - * be processed again later. - * - * @see drupal_cron_run() - */ -function callback_queue_worker($queue_item_data) { - $node = node_load($queue_item_data); - $node->title = 'Updated title'; - $node->save(); -} - /** * Allows modules to declare their own Form API element types and specify their * default values. @@ -3735,8 +3713,9 @@ function hook_registry_files_alter(&$files, $modules) { * * Any tasks you define here will be run, in order, after the installer has * finished the site configuration step but before it has moved on to the - * final import of languages and the end of the installation. You can have any - * number of custom tasks to perform during this phase. + * final import of languages and the end of the installation. This is invoked + * by install_tasks(). You can have any number of custom tasks to perform + * during this phase. * * Each task you define here corresponds to a callback function which you must * separately define and which is called when your task is run. This function @@ -3829,6 +3808,8 @@ function hook_registry_files_alter(&$files, $modules) { * * @see install_state_defaults() * @see batch_set() + * @see hook_install_tasks_alter() + * @see install_tasks() */ function hook_install_tasks(&$install_state) { // Here, we define a variable to allow tasks to indicate that a particular, @@ -3931,6 +3912,8 @@ function hook_html_head_alter(&$head_elements) { /** * Alter the full list of installation tasks. * + * This hook is invoked on the install profile in install_tasks(). + * * @param $tasks * An array of all available installation tasks, including those provided by * Drupal core. You can modify this array to change or replace any part of @@ -3938,6 +3921,9 @@ function hook_html_head_alter(&$head_elements) { * is selected. * @param $install_state * An array of information about the current installation state. + * + * @see hook_install_tasks() + * @see install_tasks() */ function hook_install_tasks_alter(&$tasks, $install_state) { // Replace the "Choose language" installation task provided by Drupal core @@ -4824,6 +4810,28 @@ function hook_filetransfer_info_alter(&$filetransfer_info) { * @{ */ +/** + * Work on a single queue item. + * + * Callback for hook_cron_queue_info(). + * + * @param $queue_item_data + * The data that was passed to DrupalQueueInterface::createItem() when the + * item was queued. + * + * @throws Exception + * The worker callback may throw an exception to indicate there was a problem. + * The cron process will log the exception, and leave the item in the queue to + * be processed again later. + * + * @see drupal_cron_run() + */ +function callback_queue_worker($queue_item_data) { + $node = node_load($queue_item_data); + $node->title = 'Updated title'; + node_save($node); +} + /** * Return the URI for an entity. * diff --git a/modules/system/system.module b/modules/system/system.module index 6a6200ea..8fc517fc 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -2030,7 +2030,6 @@ function system_user_timezone(&$form, &$form_state) { '#description' => t('Select the desired local time and time zone. Dates and times throughout this site will be displayed using this time zone.'), ); if (!isset($account->timezone) && $account->uid == $user->uid && empty($form_state['input']['timezone'])) { - $form['timezone']['#description'] = t('Your time zone setting will be automatically detected if possible. Confirm the selection and click save.'); $form['timezone']['timezone']['#attributes'] = array('class' => array('timezone-detect')); drupal_add_js('misc/timezone.js'); } diff --git a/modules/system/system.queue.inc b/modules/system/system.queue.inc index 901c4d6d..6eeaae19 100644 --- a/modules/system/system.queue.inc +++ b/modules/system/system.queue.inc @@ -231,7 +231,7 @@ class SystemQueue implements DrupalReliableQueueInterface { // until an item is successfully claimed or we are reasonably sure there // are no unclaimed items left. while (TRUE) { - $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created ASC', 0, 1, array(':name' => $this->name))->fetchObject(); + $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created, item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject(); if ($item) { // Try to update the item. Only one thread can succeed in UPDATEing the // same row. We cannot rely on REQUEST_TIME because items might be diff --git a/modules/system/system.test b/modules/system/system.test index 3e26bae8..d4c98f0a 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -1068,6 +1068,11 @@ class PageNotFoundTestCase extends DrupalWebTestCase { ); $node = $this->drupalCreateNode($edit); + // As node IDs must be integers, make sure requests for non-integer IDs + // return a page not found error. + $this->drupalGet('node/invalid'); + $this->assertResponse(404); + // Use a custom 404 page. $this->drupalPost('admin/config/system/site-information', array('site_404' => 'node/' . $node->nid), t('Save configuration')); diff --git a/modules/translation/translation.module b/modules/translation/translation.module index 53c4641e..580d0007 100644 --- a/modules/translation/translation.module +++ b/modules/translation/translation.module @@ -428,7 +428,7 @@ function translation_node_delete($node) { * A node object. */ function translation_remove_from_set($node) { - if (isset($node->tnid)) { + if (isset($node->tnid) && $node->tnid) { $query = db_update('node') ->fields(array( 'tnid' => 0, diff --git a/modules/update/update.module b/modules/update/update.module index 2266ec6e..869c5ef0 100644 --- a/modules/update/update.module +++ b/modules/update/update.module @@ -278,12 +278,15 @@ function update_theme() { ), 'update_report' => array( 'variables' => array('data' => NULL), + 'file' => 'update.report.inc', ), 'update_version' => array( 'variables' => array('version' => NULL, 'tag' => NULL, 'class' => array()), + 'file' => 'update.report.inc', ), 'update_status_label' => array( 'variables' => array('status' => NULL), + 'file' => 'update.report.inc', ), ); } diff --git a/profiles/wcm_base/README.md b/profiles/wcm_base/README.md index eb5dafb7..8e94a17c 100644 --- a/profiles/wcm_base/README.md +++ b/profiles/wcm_base/README.md @@ -1,25 +1,101 @@ +## Cloning the WCM Base profile -CONTENTS OF THIS FILE ---------------------- +To get started, clone the WCM Base profile in your local web root: - * Introduction - * Installation - * Documentation +`git clone git@code.osu.edu:ocio_odee_web/wcm_base.git` +## Building the codebase -INTRODUCTION ------------- +After you've downloaded the most recent version of the profile, build the development site from your web root using the dev makefile: -@todo +`drush make --no-gitinfofile wcm_base/build-wcm_base-dev.make DRUPAL_ROOT` +If you are building for proudction, simply use the standard makefile: -INSTALLATION ------------- +`drush make --no-gitinfofile wcm_base/build-wcm_base.make DRUPAL_ROOT` -@todo +Replace `DRUPAL_ROOT` with the name you would like to give to the directory in which your local drupal site will reside. This will not appear in any repositories, so you may use any name you would like. If you do not specify a location, the distribution will be built in the current directory, which you do not want. +The `--no-gitinfofile` flag prevents drush from making changes or adding metadata to your .info files -DOCUMENTATION -------------- +The build might take couple of minutes, as all of Drupal core must be downloaded, as well as contrib and custom modules and themes. -@todo +## Updating the codebase + +In order to retrieve updates made to the WCM Base profile, including modules, themes and Drupal core, you will need to update the profile via git. + +Since the WMC Base profile was downloaded again within the Drupal site after first running `drush make`, you can simply update it there instead of going back to the one in your web root. This must be done from the `profiles/wcm_base` directory: + +`git pull --rebase` + +After the profile has been updated, run the following from your Drupal root: + +`drush make --no-gitinfofile profiles/wcm_base/build-wcm_base-dev.make .` + +This will rebuild the site using the parameters defined in the make file. Note that this command must be executed from the Drupal root. While you can generally build from any location and then add a parameter specifying the location where you'd like the files to go, drush will only let you rebuild an existing site from the site root. In this case, we are using the `.` character to target the current directory. This command can be executed without setting a target, but drush will then force you to confirm that you would like to build in the current directory. This confirmation can be skipped by appending the `-y` flag, but unless you want to be asked to confirm the build every time, simply targeting the current directory will circumvent this. + +If you are updating a production codebase, you can use the drush remake plugin, which will rebuild the codebase from any location within your Drupal site with a simplified command: + +`drush remake` + +To install this plugin, simple run the following command: + +`drush dl drush_remake` + +This plugin is not recommended for the development environment, as it appears to download everything into profiles instead of sites/all. + +**IMPORTANT:** Changes to any files within the Drupal root will be overwritten, so make sure you've committed and pushed changes to custom modules and themes to their corresponding UCR repositories before rebuilding. Existing modules and other files in your local environment will not be deleted however, so if you are testing modules that you have not added to the make file, they will still be there after you rebuild. If you have any local branches, they will be lost, so either push your branch to UCR, merge your changes into an existing branch and push, or simply make a copy of your repository and then move it back into the site after you rebuild. If you're not confident overwriting everything, you can always start a new build in a completely different directory than the one where your existing site lives, but this will require lots of copying and pasting and is very tedious. + +The rebuild would normally take a while, as once again, everything needs to be re-downloaded. Fortunately, this is not necessary if you just want to refresh modules and themes. If Drupal core has not been updated in the make file, this is just a waste of time, so in most cases, you can just add the `--no-core` flag: + +`drush make --no-core --no-gitinfofile profiles/wcm_base/build-wcm_base-dev.make .` + +This is significantly faster and usually all that is necessary to get your whole site up to date. + +## Installing the profile + +If you are installing the WCM Distribution for the first time, then you need to create a new database, configure settings.php, and run the Drupal installer. This can be accomplished by passing your database credentials, the name of the new database you'd like to create, and the name of the site to `drush site-install`, or more simply, `drush si`. + +`drush si wcm_base --db-url='mysql://[db_user]:[db_pass]@localhost/[db_name]' --site-name=Example -y` + +If you don't like the idea of your credentials being visible and appearing in your shell history, this can also be done by creating a new database manually and running the command without any extra parameters. This will require you to create and configure a settings.php file, either manually or through the installation web interface. + +`drush si wcm_base -y` + +Once the settings file has been created and configured for the first time, the shorter command above can also be used at any time to reinstall WCM Base, which will wipe out the site's database and recreate it from scratch. + + +## Building the distribution + +When updating the distribution repository, rebuild the codebase (but not with working copies): + +`drush make wcm_base/build-wcm_base.make DRUPAL_ROOT` + + +## Loose ends and bonus tips + +This might seem complicated, but it's really not so bad. It's unlikely that you will need to rebuild on a daily basis, as most changes that matter to you as a developer can be retrieved from the git repos. A full rebuild is only necessary when Drupal core is updated, which is not very often. If you're worried about whether changes to your repos will be easily communicated to other team members, remember that any time someone else wants to push to a repo you're working on, they will probably have to pull first, meaning your changes will get to them soon enough. As long as all your changes have been committed, rebuilding should never cause any problems, and will be necessary when contrib modules are updated or when modules and themes are added to the distribution. + +Here are some additional tips to help you along: + +- Typing out all these commands can be tedious, so it's highly recommended to use shell aliases to minimize the inconvenience. This is a common and straightforward practice and is not difficult to do. There are plenty of resources on the subject, but DigitalOcean has [a pretty good one](https://www.digitalocean.com/community/tutorials/an-introduction-to-useful-bash-aliases-and-functions). + +- You may also have noticed that the recommended method for updating the WCM is to use the ```--rebase``` flag when executing a `git pull`. This is generally a good practice whenever you pull because it prevents unnecessary merge commits from being added to the git log. You can also configure your local git configuration to always rebase on pull with `git config --bool pull.rebase true` + +- You may also may not know the the shell you use from the command line can help you out with keeping track of what's going on with git. [Oh My Zsh](http://ohmyz.sh) is an excellent replacement for the default Bash shell, and includes lots of options and customizations, such as showing you what branch you're on without having to type anything at all, as well as customizing the look and feel of your prompt, and additional enhancements for command line usage and navigation. + +## Why a make file instead of a mega git repository? + +Development of this distribution could be done by this method above, whereby building a make file downloads core and contrib and pulls down entire working git repos for modules and themes, or by using a repo of the pre-built site, with the smaller repos nested inside of it. There are pros and cons to each approach. + +### Build speed + +Rebuilding from the make file is slower than a git pull because every file is downloaded whole instead of just a diff of each changed file. However, the benefit is minimal since Drupal does not need to be updated frequently, and most of the work will be done on custom themes and modules, which will have their own repos, regardless of the method used to build the site. It's true that it is easier to do a quick pull to see if any changes have been made, as opposed to having to rebuild, + +### Ease of common development + +With nested repos, changes to modules and themes, which are frequent during early development need to be committed to individual repos and to the larger distribution repo. The introduces the potential human error involving adding and committing the right files to the right repo and adds frequent, unnecessary repetition. With the make file approach, developers need only focus on their version-controlled projects, which are updated automatically upon rebuilding, but can also be pulled manually. New modules and themes can simply be created or downloaded locally and then added to the make file for later distribution. Once they have been added to the make file, committing them to a parent repository becomes unnecessary, as contrib and core modules are never modified. + +### Hands-off updates + +Contrib modules, and even Drupal core itself must be updated manually, and then committed to a parent repo. With a make file, the newest version is pulled by default when rebuilding making regular maintenance easier. Specific versions can optionally be added, as well. As a bonus, this will prevent developers from hacking core and contrib due to the fact that any changes they make will be overwritten on the next rebuild. diff --git a/profiles/wcm_base/modules/contrib/date_popup_authored/date_popup_authored.info b/profiles/wcm_base/modules/contrib/date_popup_authored/date_popup_authored.info index bd506d7f..cad75db9 100644 --- a/profiles/wcm_base/modules/contrib/date_popup_authored/date_popup_authored.info +++ b/profiles/wcm_base/modules/contrib/date_popup_authored/date_popup_authored.info @@ -3,4 +3,11 @@ description = "Provides a datepicker for the 'Authored on' field on node forms." core = 7.x files[] = date_popup_authored.test package = Date/Time -dependencies[] = date_popup \ No newline at end of file +dependencies[] = date_popup + +; Information added by drush on 2015-05-19 +version = "7.x-1.1+2-dev" +core = "7.x" +project = "date_popup_authored" +datestamp = "1432070407" + diff --git a/profiles/wcm_base/modules/contrib/draggableviews/README.txt b/profiles/wcm_base/modules/contrib/draggableviews/README.txt index ce82ce88..ecbec8b2 100644 --- a/profiles/wcm_base/modules/contrib/draggableviews/README.txt +++ b/profiles/wcm_base/modules/contrib/draggableviews/README.txt @@ -95,10 +95,19 @@ empty set will be used. Using the "Prepare arguments with PHP code" option will let you alter arguments before they passed to "matching" with "args" column. For us this means that we can create, for example, several exposed filters, -but pass values of only one of values of exposed filters instead of all of them (like we create two exposed -filters: author and node type, but take into account for ordering only node type). -Please be aware that in PHP code arguments are passed as $arguments variable and you should return an array. -IE return array('status' => 1, 'user' => 2); or return $arguments; // $arguments is already an array +but pass values of only one of values of exposed filters instead of all of them. + +Please be aware that in PHP code arguments are passed as an $arguments variable and you should return an array. +An example: + return array('status' => 1, 'user' => 2); or return $arguments; // $arguments is already an array. + +In the $arguments array + - Contextual filters are NUMBER keyed. + - Exposed filters are NAME keyed. + +For example, say we create two exposed filters: author and node type, but wish to take into account for ordering +only node type. We would use the following: + unset($arguments['author']); return $arguments; When using arguments, make sure your ordering view display has the same arguments as the display you want to show the end user. If they do not match, then your ordering will not match. @@ -107,8 +116,6 @@ Using hook_draggableviews_handler_native_arguments_alter(&$arguments, $view, &$f the arguments save to the database, just as the "Prepare arguments with PHP code" option. See draggavleviews.api.php for more details. -In the $arguments array, Contextual filters are number keyed and exposed filters are name keyed. - Removed Arguments: - The pager 'item_per_page' exposed filter will never be saved. diff --git a/profiles/wcm_base/modules/contrib/draggableviews/draggableviews.info b/profiles/wcm_base/modules/contrib/draggableviews/draggableviews.info index 3bc862a7..554f67ec 100644 --- a/profiles/wcm_base/modules/contrib/draggableviews/draggableviews.info +++ b/profiles/wcm_base/modules/contrib/draggableviews/draggableviews.info @@ -18,9 +18,9 @@ files[] = handlers/draggableviews_hierarchy_handler_native.inc dependencies[] = entity -; Information added by Drupal.org packaging script on 2015-04-11 -version = "7.x-2.1+3-dev" +; Information added by Drupal.org packaging script on 2015-05-06 +version = "7.x-2.1+4-dev" core = "7.x" project = "draggableviews" -datestamp = "1428713887" +datestamp = "1430914992" diff --git a/profiles/wcm_base/modules/contrib/draggableviews/draggableviews_book/draggableviews_book.info b/profiles/wcm_base/modules/contrib/draggableviews/draggableviews_book/draggableviews_book.info index e183707a..776a0864 100644 --- a/profiles/wcm_base/modules/contrib/draggableviews/draggableviews_book/draggableviews_book.info +++ b/profiles/wcm_base/modules/contrib/draggableviews/draggableviews_book/draggableviews_book.info @@ -7,9 +7,9 @@ files[] = draggableviews_book_views_handler_argument.inc dependencies[] = draggableviews dependencies[] = book -; Information added by Drupal.org packaging script on 2015-04-11 -version = "7.x-2.1+3-dev" +; Information added by Drupal.org packaging script on 2015-05-06 +version = "7.x-2.1+4-dev" core = "7.x" project = "draggableviews" -datestamp = "1428713887" +datestamp = "1430914992" diff --git a/profiles/wcm_base/modules/contrib/draggableviews/test/draggableviews_test/draggableviews_test.info b/profiles/wcm_base/modules/contrib/draggableviews/test/draggableviews_test/draggableviews_test.info index 79cb8cf4..dd3c7613 100644 --- a/profiles/wcm_base/modules/contrib/draggableviews/test/draggableviews_test/draggableviews_test.info +++ b/profiles/wcm_base/modules/contrib/draggableviews/test/draggableviews_test/draggableviews_test.info @@ -4,9 +4,9 @@ dependencies[] = draggableviews package = Views core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-04-11 -version = "7.x-2.1+3-dev" +; Information added by Drupal.org packaging script on 2015-05-06 +version = "7.x-2.1+4-dev" core = "7.x" project = "draggableviews" -datestamp = "1428713887" +datestamp = "1430914992" diff --git a/profiles/wcm_base/modules/contrib/draggableviews/views/draggableviews_handler_sort.inc b/profiles/wcm_base/modules/contrib/draggableviews/views/draggableviews_handler_sort.inc index c81f0659..981c97f1 100644 --- a/profiles/wcm_base/modules/contrib/draggableviews/views/draggableviews_handler_sort.inc +++ b/profiles/wcm_base/modules/contrib/draggableviews/views/draggableviews_handler_sort.inc @@ -87,7 +87,7 @@ class draggableviews_handler_sort extends views_handler_sort { '#default_value' => $this->options['draggableviews_setting_arguments_php'], '#description' => t('Enter the php code to prepare the arguments. Do not enter <?php ?> tags. The following variables are available - $view (the view), $arguments (existing arguments - - manipulate these to alter the arguments used to sort). See README.txt for more details.'), + manipulate these, and then return them, to alter the arguments used to sort). See README.txt for more details.'), '#states' => array( 'visible' => array( 'input[name="options[draggableviews_setting_arguments]"]' => array('value' => 'php'), diff --git a/profiles/wcm_base/modules/contrib/file_entity/file_entity.info b/profiles/wcm_base/modules/contrib/file_entity/file_entity.info index 62f966f0..a3628ec9 100644 --- a/profiles/wcm_base/modules/contrib/file_entity/file_entity.info +++ b/profiles/wcm_base/modules/contrib/file_entity/file_entity.info @@ -24,3 +24,11 @@ configure = admin/config/media/file-settings ; We have to add a fake version so Git checkouts do not fail Media dependencies version = 7.x-2.x-dev + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+29-dev" +core = "7.x" +project = "file_entity" +datestamp = "1432070403" + diff --git a/profiles/wcm_base/modules/contrib/file_entity/tests/file_entity_test.info b/profiles/wcm_base/modules/contrib/file_entity/tests/file_entity_test.info index 35eaf29c..74f5463e 100644 --- a/profiles/wcm_base/modules/contrib/file_entity/tests/file_entity_test.info +++ b/profiles/wcm_base/modules/contrib/file_entity/tests/file_entity_test.info @@ -4,3 +4,11 @@ package = Testing core = 7.x dependencies[] = file_entity hidden = TRUE + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+29-dev" +core = "7.x" +project = "file_entity" +datestamp = "1432070403" + diff --git a/profiles/wcm_base/modules/contrib/manualcrop/manualcrop.info b/profiles/wcm_base/modules/contrib/manualcrop/manualcrop.info index f0defad2..206686ae 100644 --- a/profiles/wcm_base/modules/contrib/manualcrop/manualcrop.info +++ b/profiles/wcm_base/modules/contrib/manualcrop/manualcrop.info @@ -7,3 +7,11 @@ dependencies[] = image (>=7.8) dependencies[] = libraries (>=2.1) files[] = includes/views/manualcrop.views.inc + + +; Information added by drush on 2015-05-19 +version = "7.x-1.4+109-dev" +core = "7.x" +project = "manualcrop" +datestamp = "1432070291" + diff --git a/profiles/wcm_base/modules/contrib/media/media.info b/profiles/wcm_base/modules/contrib/media/media.info index 41db0918..fe005488 100644 --- a/profiles/wcm_base/modules/contrib/media/media.info +++ b/profiles/wcm_base/modules/contrib/media/media.info @@ -23,3 +23,11 @@ configure = admin/config/media/browser ; We have to add a fake version so Git checkouts do not fail Media dependencies version = 7.x-2.x-dev + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+98-dev" +core = "7.x" +project = "media" +datestamp = "1432070246" + diff --git a/profiles/wcm_base/modules/contrib/media/modules/media_bulk_upload/media_bulk_upload.info b/profiles/wcm_base/modules/contrib/media/modules/media_bulk_upload/media_bulk_upload.info index f930dbc0..9baf57b8 100644 --- a/profiles/wcm_base/modules/contrib/media/modules/media_bulk_upload/media_bulk_upload.info +++ b/profiles/wcm_base/modules/contrib/media/modules/media_bulk_upload/media_bulk_upload.info @@ -8,3 +8,11 @@ dependencies[] = multiform dependencies[] = plupload files[] = includes/MediaBrowserBulkUpload.inc + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+98-dev" +core = "7.x" +project = "media" +datestamp = "1432070246" + diff --git a/profiles/wcm_base/modules/contrib/media/modules/media_internet/media_internet.info b/profiles/wcm_base/modules/contrib/media/modules/media_internet/media_internet.info index 3ec8936c..7d19dc97 100644 --- a/profiles/wcm_base/modules/contrib/media/modules/media_internet/media_internet.info +++ b/profiles/wcm_base/modules/contrib/media/modules/media_internet/media_internet.info @@ -10,3 +10,11 @@ files[] = includes/MediaInternetBaseHandler.inc files[] = includes/MediaInternetFileHandler.inc files[] = includes/MediaInternetNoHandlerException.inc files[] = includes/MediaInternetValidationException.inc + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+98-dev" +core = "7.x" +project = "media" +datestamp = "1432070246" + diff --git a/profiles/wcm_base/modules/contrib/media/modules/media_migrate_file_types/media_migrate_file_types.info b/profiles/wcm_base/modules/contrib/media/modules/media_migrate_file_types/media_migrate_file_types.info index 32c98d11..ff081525 100644 --- a/profiles/wcm_base/modules/contrib/media/modules/media_migrate_file_types/media_migrate_file_types.info +++ b/profiles/wcm_base/modules/contrib/media/modules/media_migrate_file_types/media_migrate_file_types.info @@ -7,3 +7,11 @@ hidden = TRUE dependencies[] = media configure = admin/structure/file-types/upgrade + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+98-dev" +core = "7.x" +project = "media" +datestamp = "1432070246" + diff --git a/profiles/wcm_base/modules/contrib/media/modules/media_wysiwyg/media_wysiwyg.info b/profiles/wcm_base/modules/contrib/media/modules/media_wysiwyg/media_wysiwyg.info index 93b8a3aa..b00cc0e8 100644 --- a/profiles/wcm_base/modules/contrib/media/modules/media_wysiwyg/media_wysiwyg.info +++ b/profiles/wcm_base/modules/contrib/media/modules/media_wysiwyg/media_wysiwyg.info @@ -14,3 +14,11 @@ files[] = tests/media_wysiwyg.file_usage.test files[] = tests/media_wysiwyg.macro.test configure = admin/config/media/browser + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+98-dev" +core = "7.x" +project = "media" +datestamp = "1432070246" + diff --git a/profiles/wcm_base/modules/contrib/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info b/profiles/wcm_base/modules/contrib/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info index 8fbbdbbd..1ef4b078 100644 --- a/profiles/wcm_base/modules/contrib/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info +++ b/profiles/wcm_base/modules/contrib/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info @@ -8,3 +8,11 @@ dependencies[] = media_wysiwyg configure = admin/config/media/wysiwyg-view-mode files[] = media_wysiwyg_view_mode.test + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+98-dev" +core = "7.x" +project = "media" +datestamp = "1432070246" + diff --git a/profiles/wcm_base/modules/contrib/media/modules/mediafield/mediafield.info b/profiles/wcm_base/modules/contrib/media/modules/mediafield/mediafield.info index 24022ad7..d6ee37e0 100644 --- a/profiles/wcm_base/modules/contrib/media/modules/mediafield/mediafield.info +++ b/profiles/wcm_base/modules/contrib/media/modules/mediafield/mediafield.info @@ -3,3 +3,11 @@ description = "Provides a field type that stores media-specific data. <em>Deprec package = Media core = 7.x dependencies[] = media + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+98-dev" +core = "7.x" +project = "media" +datestamp = "1432070246" + diff --git a/profiles/wcm_base/modules/contrib/media/tests/media_module_test.info b/profiles/wcm_base/modules/contrib/media/tests/media_module_test.info index de618c2e..78cc7eab 100644 --- a/profiles/wcm_base/modules/contrib/media/tests/media_module_test.info +++ b/profiles/wcm_base/modules/contrib/media/tests/media_module_test.info @@ -3,3 +3,11 @@ description = Provides hooks for testing Media module functionality. package = Media core = 7.x hidden = TRUE + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-alpha3+98-dev" +core = "7.x" +project = "media" +datestamp = "1432070246" + diff --git a/profiles/wcm_base/modules/contrib/media_youtube/media_youtube.info b/profiles/wcm_base/modules/contrib/media_youtube/media_youtube.info index 49fd9edf..ee132447 100644 --- a/profiles/wcm_base/modules/contrib/media_youtube/media_youtube.info +++ b/profiles/wcm_base/modules/contrib/media_youtube/media_youtube.info @@ -8,3 +8,11 @@ dependencies[] = media_internet files[] = includes/MediaYouTubeStreamWrapper.inc files[] = includes/MediaInternetYouTubeHandler.inc files[] = includes/MediaYouTubeBrowser.inc + + +; Information added by drush on 2015-05-19 +version = "7.x-2.0-rc4+9-dev" +core = "7.x" +project = "media_youtube" +datestamp = "1432070404" + diff --git a/profiles/wcm_base/modules/contrib/user_import/API.txt b/profiles/wcm_base/modules/contrib/user_import/API.txt deleted file mode 100644 index e5fb3b09..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/API.txt +++ /dev/null @@ -1,44 +0,0 @@ - - -Overview -======== - -Import users into Drupal from a csv file (comma separated file). - -See files in user_import/supported/ for examples of the API in use. - -hook_user_import_form_field_match() -=========================================== -Add options to list of Drupal fields to match against column of data being imported. - - -hook_user_import_form_update_user() -=========================================== -Add options to the import settings form. - - -hook_user_import_data() -=========================================== -Manipulate imported data before creating or amending user accounts. - - -hook_user_import_after_save() -=========================================== -Take action(s) after each user account is created or amended. - - -hook_user_import_imported() -=========================================== -Take action(s) after import is complete. - -hook_field_user_import_supported_alter(&$supported) -=========================================== -Alter array of supported field types, allowing for adding custom field -processors. Example: - -MODULE_field_user_import_supported_alter(&$supported) { - $supported['MODULE'] = array( - 'validate' => 'MODULE_user_import_field_validator', - 'save' => 'MODULE_user_import_field_processor', - ); -} diff --git a/profiles/wcm_base/modules/contrib/user_import/LICENSE.txt b/profiles/wcm_base/modules/contrib/user_import/LICENSE.txt deleted file mode 100755 index d159169d..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/LICENSE.txt +++ /dev/null @@ -1,339 +0,0 @@ - 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/profiles/wcm_base/modules/contrib/user_import/README.txt b/profiles/wcm_base/modules/contrib/user_import/README.txt deleted file mode 100644 index c6c44bbb..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/README.txt +++ /dev/null @@ -1,214 +0,0 @@ -******************************************************************** - D R U P A L M O D U L E -******************************************************************** -Name: User Import Module -Author: Robert Castelo <www.codepositive.com> -Drupal: 7.x -******************************************************************** -DESCRIPTION: - -Import users into Drupal from a CSV file (Comma Separated File). - -Features include: - -* Creates an account for each user -* Match CSV columns to profile fields. -* Can optionally use the file's first row to map csv data to user profile fields -* Option to create Usernames based on data from file, e.g. "John" + "Smith" => "JohnSmith" -* Usernames can be made of abbreviated data from file, e.g. "Jane" + "Doe" => "JDoe" -* Option to create random, human readable, Usernames -* Option to import passwords -* Option to create random passwords for each user -* Can set user roles -* Option to send welcome email, with account details to each new user -* Can set each user's contact form to enabled -* Test mode option to check for errors -* Processing can be triggered by cron or manually by an administrator -* Can stagger number of users imported, so that not too many emails are sent at one time -* Multiple files can be imported/tested at the same time -* Import into Organic Groups -* Import into Node Profile -* Option to make new accounts immediately active, or inactive until user logs in -* Use CSV file already uploaded through FTP (useful for large imports) -* Designed to be massively scalable - -** Supported CSV File Formats ** - -The following settings are necessary when saving a csv file which will be used for the import. - -File needs to be saved as "Character Set: Unicode (UTF-8)". - -Field delimiter: , -- can be configured as something else, a comma is the default though. - -Text delimiter: " -- if there's an option to quote all text cells, enable it. - -The 'Windows line endings' setting complies with all of the above. - -If file import fails with "File copy failed: source file does not exist." try -setting the file extension to .txt. - - -** IMPORTANT ** - -- Note that Date fields are not yet supported. - -- Note that passwords can only be imported as plain text, and will be converted to MD5 by Drupal. - -- Note that if your data contains a backslash before the column separator it may not get imported as expected: - - "123","abc\","def" - The second field will be imported as: abc","def - - This has been fixed in PHP 5.3 - - -******************************************************************** -PREREQUISITES: - - Must have customised Profile fields already entered - if data is to be imported into user profiles. - - -******************************************************************** -INSTALLATION: - -Note: It is assumed that you have Drupal up and running. Be sure to -check the Drupal web site if you need assistance. - -1. Place the entire user_import directory into your Drupal directory: - sites/all/modules/ - - -2. Enable the user_import modules by navigating to: - - administer > modules - - Click the 'Save configuration' button at the bottom to commit your - changes. - -3. IMPORTANT - Navigate to: - admin/config/media/file-system - - Set the 'Private file system path' field. - - - -******************************************************************** -CONFIGURATION: - -Configuration for User Import: - -'People' --- 'Import' --- 'Configure' (admin/people/user_import/configure) - -* Uploads Directory - - This option provides a directory where files can be uploaded, and then selected when setting up - an import. The uploads directory will be in your Private files directory: - - [path to private files]/user_import/uploads/selectable - -* Automated Imports - - If this is set then each import template will have the option to create a matching directory which - will be scanned for any files that have been uploaded to it, and when a file is found it will - automatically be used to create new user accounts. Directories are scanned during cron runs. - - Scanned directories will be in: - - [path to private files]/user_import/uploads/[name of directory] - - - -******************************************************************** -USAGE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - For more detailed instructions (with pictures) please go to the - documentation pages for this module: - - http://drupal.org/node/137653 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -1. To set permissions of who can import users into the site, navigate to: - -'People' --- 'Permissions' --- 'User Import' --- 'Import users' (admin/people/permissions) - -2. To import users, navigate to: - -'People' --- 'Import' --- 'New Import' (admin/people/user_import/add) - -* Note that Drupal may require its caches to be flushed before the User Import menu options appear - -3. Press the 'browse' button to select a file to import, - or select a file already added through FTP. - -5. Click on Next. - -6. Use the "Use Different CSV File" fieldset to remove and add a different CSV file - -7. Under Field Match you should see the various columns from your profile page. - -8. For each csv column select a Drupal field to map. - -9. Under username select 'No', if the field is not to be used to generate the username, or select '1' - '4' - for the order to use the field in generating username. - - Example: 'LastName' and 'FirstName' are fields to be used as username. So under the username - selection chose '1' for 'FirstName' and '2' for 'Lastname', and the username generated will be in - the form 'FirstNameLastName'. - -10. Under Options you should see Ignore First Line ( use if the first row are labels ), - - Contact, and Send Email. Select whichever is appropiate. - -11. Under Role Assign select the roles the imported users will be assigned. - -12. Under Email Message, you can override the default message sent to new users. Leave blank to use the default message. - -13. Under Update Existing Users, you can set whether existing users matching ones from the CSV file will be updated, replaced or added. - -12. Under Save Settings, you can save your settings for use on future imports. - -13. Click "Test" to do an import without committing changes to the database. Fix any errors that are generated. - -14. Click "Import" to complete the import. - - - -******************************************************************** -AUTHOR CONTACT - -- Report Bugs/Request Features: - http://drupal.org/project/user_import - -- Comission New Features: - http://drupal.org/user/3555/contact - -- Want To Say Thank You: - http://www.amazon.com/gp/registry/O6JKRQEQ774F - - -******************************************************************** -ACKNOWLEDGEMENT - -- I looked at a script by David McIntosh (neofactor.com) before coding this module. -- Documentation help Steve (spatz4000) -- patch by mfredrickson -- patch by idealso -- code from Nedjo Rogers - - -******************************************************************** -SPONSORS - diff --git a/profiles/wcm_base/modules/contrib/user_import/sample.txt b/profiles/wcm_base/modules/contrib/user_import/sample.txt deleted file mode 100644 index 7361f738..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/sample.txt +++ /dev/null @@ -1,37 +0,0 @@ -"First Name","Last Name","Password","Tel","Fax","Email","Blog","Contact","Contact Preference","Interests","CV","Birthday" -"12345","O'Neil","password1","","5678 76 55 6","john@example.com","http://www.example.com",1,"email",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.",11/10/1976 -"Mandy","Smith-Jones","password2","","5678 76 55 5","mandy@example.com","http://www.example.com",1,"telephone","Drupal, PHP, CSS","Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"édward","André","password3","0123 666666","5678 76 55 5","charles@example.com","http://www.example.com/charlessmith",,"post",,, -"ěddy","Szěged","","0123 45678","5678 76 55 5","sarah@example.com","http://www.example.com",1,"post",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"šarah","Smithš","password3","0123 45678","5678 76 55 5","sarah_smith@example.com","http://www.example.com",0,"telephone",,, -"Helen","Doeč","password3","0123 45678","5678 76 55 5","helen@example.com","http://www.example.com",0,,,, -"Claire","Does","password3","0123 45678","5678 76 55 5","claire@example.com","http://www.example.com",0,"email",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Victoria","DoeH","password3","0123 45678|02 0897 456","5678 76 55 5","victoria@example.com","http://www.example.com",1,"post",,, -"James","Wilson","password3","0123 45678","5678 76 55 5","james@example.com","http://www.example.com",0,"post",,, -"Anna","Smith","password1","0123 45678","5678 76 55 6","anna@example.com","http://www.example.com",1,"email",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.",11/11/1976 -"Tino","Smith","password2","0123 45678","5678 76 55 5","tino@example.com","http://www.example.com",1,"telephone","Drupal, PHP, CSS","Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Sofia","Smith","password3","0123 45678","5678 76 55 5","sofia@example.com","http://www.example.com/charlessmith",,"post",,, -"Steve","Smith","password3","0123 45678","5678 76 55 5","steve@example.com","http://www.example.com",1,"post",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Lucy","Smith","password3","0123 45678","5678 76 55 5","lucy@example.com","http://www.example.com",0,"telephone",,, -"Angie","Doe","password3","0123 45678","5678 76 55 5","angie@example.com","http://www.example.com",0,,,, -"Carmen","Doe","password3","0123 45678","5678 76 55 5","carmen@example.com","http://www.example.com",0,"email",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Paul","Doe","password3","0123 45678","5678 76 55 5","paul@example.com","http://www.example.com",1,"post",,, -"Jason","Wilson","password3","0123 45678","5678 76 55 5","jason@example.com","http://www.example.com",0,"post",,, -"Mike","Smith","password1","0123 45678","5678 76 55 6","mike@example.com","http://www.example.com",1,"email",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.",11/11/1976 -"Mary","Smith","password2","0123 45678","5678 76 55 5","mary@example.com","http://www.example.com",1,"telephone","Drupal, PHP, CSS","Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Simon","Smith","password3","0123 45678","5678 76 55 5","simon@example.com","http://www.example.com/charlessmith",,"post",,, -"Kieran","Smith","password3","0123 45678","5678 76 55 5","kieran@example.com","http://www.example.com",1,"post",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Arthur","Smith","password3","0123 45678","5678 76 55 5","arthur@example.com","http://www.example.com",0,"telephone",,, -"Gwen","Doe","password3","0123 45678","5678 76 55 5","gwen@example.com","http://www.example.com",0,,,, -"Chester","Doe","password3","0123 45678","5678 76 55 5","chester@example.com","http://www.example.com",0,"email",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Dorothy","Doe","password3","0123 45678","5678 76 55 5","dorothy@example.com","http://www.example.com",1,"post",,, -"Cameron","Wilson","password3","0123 45678","5678 76 55 5","cameron@example.com","http://www.example.com",0,"post",,, -"Trisha","Smith","password1","0123 45678","5678 76 55 6","trisha@example.com","http://www.example.com",1,"email",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.",11/11/2001 -"David","Smith","password2","0123 45678","5678 76 55 5","david@example.com","http://www.example.com",1,"telephone","Drupal, PHP, CSS","Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Peter","Smith","password3","0123 45678","5678 76 55 5","peter@example.com","http://www.example.com/charlessmith",,"post",,, -"Saul","Smith","password3","0123 45678","5678 76 55 5","saul@example.com","http://www.example.com",1,"post",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Noel","Smith","password3","0123 45678","5678 76 55 5","noel@example.com","http://www.example.com",0,"telephone",,, -"Matt","Doe","password3","0123 45678","5678 76 55 5","matt@example.com","http://www.example.com",0,,,, -"Aston","Doe","password3","0123 45678","5678 76 55 5","aston@example.com","http://www.example.com",0,"email",,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis scelerisque, urna sit amet malesuada tincidunt, nisl lacus euismod tortor, at rhoncus quam nunc id quam. Cras pellentesque nunc et nunc. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tristique massa at sapien. Morbi tincidunt cursus risus. Nulla facilisi. Vivamus tincidunt nisl sed felis. Duis nunc magna, viverra in, tristique ut, feugiat eget, metus. Aliquam tincidunt volutpat sem. Vivamus lectus felis, accumsan a, bibendum id, porttitor et; lectus.", -"Mille","Doe","password3","0123 45678","5678 76 55 5","mille@example.com","http://www.example.com",1,"post",,, -"Ernest","Wilson","password3","0123 45678","5678 76 55 5","ernest@example.com","http://www.example.com",0,"post",,, diff --git a/profiles/wcm_base/modules/contrib/user_import/supported/content_profile.inc b/profiles/wcm_base/modules/contrib/user_import/supported/content_profile.inc deleted file mode 100644 index 2c36e7f6..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/supported/content_profile.inc +++ /dev/null @@ -1,165 +0,0 @@ -<?php - -// Functionality depends on node_import and CCK. -if (module_exists('node_import') && module_exists('content')) { - - // Load the required API files. - include_once('./' . drupal_get_path('module', 'node_import') . '/node_import.api.php'); - include_once('./' . drupal_get_path('module', 'node_import') . '/node_import.inc'); - - - /** - * Implementation of hook_user_import_form_field_match(). Add supported Content Profile fields into our dropdown list. - */ - function content_profile_user_import_form_field_match() { - $options = array(); - $options['content_profile'] = array(); - $field_options = array(); - $contentprofile_types = content_profile_get_types(); - - foreach ($contentprofile_types as $type => $data) { - $fields = node_import_fields('node:' . $type, TRUE); - - // Give fields a more descriptive title. - foreach (array_keys($fields) as $key) { - if (strstr($key, 'cck:field_')) { - $field_options["$type $key"] = t('Content Profile: (!type) !key ', array('!key' => $fields[$key]['title'], '!type' => $type)); - } - } - - // skip merge if there are no fields on the content type - if (!empty($field_options)) { - $options['content_profile'] = array_merge($options['content_profile'], $field_options); - } - } - - /* We do not support taxonomy yet */ - - return $options; - } - - /** - * Implementation of hook_user_import_form_update_user(). - */ - function content_profile_user_import_form_update_user() { - $form['content_profile'] = array('title' => t('Content Profile'), 'description' => t('Affected: fields in Content Profile nodes.')); - - return $form; - } - - /** - * Implementation of hook_user_import_data(). - */ - function content_profile_user_import_data($settings, $update_setting, $column_settings, $module, $field_id, $data, $column_id) { - if ($module != 'content_profile') return; - - return trim($data[$column_id]); - } - - /** - * Implementation of hook_user_import_after_save(). - */ - function content_profile_user_import_after_save($settings, $account, $password, $fields, $updated, $update_setting_per_module) { - - if (!is_array($fields['content_profile'])) return; - - // check if it's an existing user and if content profile is to be updated - if ($updated && $update_setting_per_module['content_profile'] == UPDATE_NONE) return; - - // arrange values by content type - foreach ($fields['content_profile'] as $column_id => $column_data) { - if (!empty($column_data)) { - $keys = explode(' ', $column_id); - $contentprofile[$keys[0]][$keys[1]] = $column_data; - } - } - - $contentprofile_types = content_profile_get_types(); - - // process each $content profile - foreach ($contentprofile_types as $type => $configuration) { - content_profile_user_import_node($type, $contentprofile, $account, $fields, $updated, $update_setting_per_module['content_profile']); - } - - return; - } - - /** - * callback to create or update a node if appropriate - */ - function content_profile_user_import_node($type, $content_profile, $account, $fields, $updated, $update_setting) { - - if (empty($content_profile[$type])) return; // pass only those, which are used - - $errors = array(); - $title_empty = time(); - - if ($updated) { - $node = node_load(array('type' => $type, 'uid' => $account->uid)); - } - - if (empty($node)) { - $node = new stdClass(); - $node->type = $type; - $node->status = 1; - $node->title = $title_empty; - } - - // Assign the mapped fields to the $node. - foreach ($content_profile[$type] as $column_id => $column_data) { - - $field_data = explode(':', $column_id); - $field_name = !empty($field_data[1]) ? $field_data[1] : $column_id; - $field_key = !empty($field_data[2]) ? $field_data[2] : 'value'; - $field_value = array(0 => array($field_key => $column_data[0])); - - if (!$updated) { - $node->{$field_name} = $field_value; - } - elseif ($updated && $update_setting == UPDATE_ADD) { - - $current_content = content_format($field_name, $node->{$field_name}[0], 'default', $node); - - if (empty($current_content) && !empty($column_data[0])) { - $node->{$field_name} = $field_value; - } - } - elseif ($updated && $update_setting == UPDATE_REPLACE) { - $node->{$field_name} = $field_value; - } - } - - // not actually checking for errors at the moment, but lete's leave this in for when we do - if (empty($errors)) { - $node->uid = $account->uid; - $node->name = $account->name; - - // Assign a default title if one has not already been mapped. - if (!isset($node->title) || empty($node->title) || $node->title == $title_empty) { - $node->title = $node->name; - } - - $node = node_submit($node); - - // make sure author is not changed when submited (hapens if existing node) - $node->uid = $account->uid; - $node->name = $account->name; - - node_save($node); - - if (!$node->nid) { - drupal_set_message(t('Unknown error on saving the node: %node_data! Check watchdog logs for details.', array('%node_data' => "$node->title ($node->type)")), 'error'); - } - - } - else { - /** - * @todo report errors - */ - } - } - -} -else { - drupal_set_message(t('Please enable %module module!', array('%module' => 'node_import'))); -} diff --git a/profiles/wcm_base/modules/contrib/user_import/supported/field.inc b/profiles/wcm_base/modules/contrib/user_import/supported/field.inc deleted file mode 100644 index 4e360a83..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/supported/field.inc +++ /dev/null @@ -1,191 +0,0 @@ -<?php - -/** - * Implementation of hook_user_import_form_field_match(). - */ -function field_user_import_form_field_match() { - $options = array(); - $supported_field_types = field_user_import_supported_fields(); - $fields_info = field_info_instances('user', 'user'); - - foreach ($fields_info as $field_name => $value) { - $field_info = field_info_field($field_name); - $type = $field_info['type']; - - if (isset($supported_field_types[$type])) { - $options['field'][$field_name] = $value['label']; - } - else { - drupal_set_message(t('Field %field is not supported', array('%field' => $value['label'])), 'warning'); - } - } - - return $options; -} - -/** - * Implementation of hook_user_import_data(). - */ -function field_user_import_data($settings, $update_setting, $column_settings, $module, $field_id, $data, $column_id) { - - if ($module != 'field') return; - - $value = $data[$column_id]; - - return $value; -} - -/** - * Implementation of hook_user_import_after_save(). - */ -function field_user_import_after_save($settings, $account, $password, $fields, $updated, $update_setting_per_module) { - - $fields_data = isset($fields['field']) ? $fields['field'] : array(); - - if (empty($fields_data)) return; - - $processors = field_user_import_supported_fields(); - $delimiter = $settings['multi_value_delimiter']; - $user_fields = user_load($account->uid); - $processed_fields = array(); - - foreach ($fields_data as $field_name => $data) { - $field_info = field_info_field($field_name); - $field_type = $field_info['type']; - $processor_function = $processors[$field_type]['save']; - - // Deal with multiple values. - $values = explode($delimiter, $data[0]); - - // Use callback function to process info for field before saving. - $processed_fields[$field_name] = $processor_function($user_fields, $field_name, $values); - } - - if (!empty($processed_fields)) { - user_save($account, $processed_fields); - } - - return; -} - -function field_user_import_default_field_processor($user_fields, $field_name, $values) { - // @todo deal with multiple values - // @todo deal with language - // @todo deal with updating existing account - - $field = $user_fields->$field_name; - - for ($i = 0; $i < count($values); $i++) { - if (!empty($values[$i])) { - $field[LANGUAGE_NONE][$i]['value'] = $values[$i]; - } - } - - return $field; -} - -function field_user_import_supported_fields($output = 'all') { - static $supported = array(); - - if (empty($supported)) { - $supported['text'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - $supported['list_boolean'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - $supported['number_decimal'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - $supported['number_float'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - $supported['number_integer'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - $supported['list_float'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - $supported['list_integer'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - $supported['list_text'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - // 'format'? 'safe_value'? - DONE - $supported['text_long'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - // 'format'? 'safe_value'? 'summary'? 'safe_summary'? - $supported['text_with_summary'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - $supported['taxonomy_term_reference'] = array( - 'validate' => 'field_user_import_default_field_validator', - 'save' => 'field_user_import_default_field_processor', - ); - - /** Unsupported for the moment **/ - -// $supported['file'] = array( -// 'validate' => 'field_user_import_default_field_validator', -// 'save' => 'field_user_import_default_field_processor', -// ); - -// $supported['image'] = array( -// 'validate' => 'field_user_import_default_field_validator', -// 'save' => 'field_user_import_default_field_processor', -// ); - - - /** Not core - move to their own .inc files **/ - -// $supported['datetime'] = array( -// 'validate' => 'field_user_import_default_field_validator', -// 'save' => 'field_user_import_default_field_processor', -// ); -// -// $supported['date'] = array( -// 'validate' => 'field_user_import_default_field_validator', -// 'save' => 'field_user_import_default_field_processor', -// ); -// -// -// $supported['datestamp'] = array( -// 'validate' => 'field_user_import_default_field_validator', -// 'save' => 'field_user_import_default_field_processor', -// ); - -// $supported['addressfield'] = array( -// 'validate' => 'field_user_import_default_field_validator', -// 'save' => 'field_user_import_default_field_processor', -// ); - - // hook to add functions to this array. - // Will call all modules implementing hook_field_user_import_supported_alter - drupal_alter('field_user_import_supported', $supported); - - } - - return $supported; -} diff --git a/profiles/wcm_base/modules/contrib/user_import/supported/location.inc b/profiles/wcm_base/modules/contrib/user_import/supported/location.inc deleted file mode 100644 index 07147e98..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/supported/location.inc +++ /dev/null @@ -1,118 +0,0 @@ -<?php - -// Functionality depends on User Location. -if (module_exists('location_user')) { - - /** - * Implementation of hook_user_import_form_field_match(). Add supported Location fields into our dropdown list. - */ - function location_user_import_form_field_match() { - $options = array(); - $options['location'] = location_field_names(); - - return $options; - } - - /** - * Implementation of hook_user_import_form_update_user(). - */ - function location_user_import_form_update_user() { - $form['location'] = array( - 'title' => t('Location'), - 'description' => t('Affected: fields in user Location.'), - ); - - return $form; - } - - /** - * Implementation of hook_user_import_data(). - */ - function location_user_import_data($settings, $update_setting, $column_settings, $module, $field_id, $data, $column_id) { - if ($module != 'location') { - return; - } - if (empty($data[$column_id])) { - $defaults = location_locationapi($data, 'defaults'); - $data[$column_id] = $defaults[$field_id]['default']; - } - switch ($field_id) { - case 'country': - $country = location_country_name($data[$column_id]); - if ($country != '') { - return trim($data[$column_id]); - } - else { - $countries = location_get_iso3166_list(); - if ($country = array_search($data[$column_id], $countries)) { - if ($country !== FALSE) { - return $country; - } - } - } - - user_import_errors(t('Invalid country')); - - return trim($data[$column_id]); - - case 'province': - if (!empty($data['country'])) { - if (!empty($data[$column_id])) { - $provinces = location_get_provinces($data['country']); - $found = FALSE; - $province = strtoupper($data['province']); - foreach ($provinces as $key => $value) { - if ($province == strtoupper($key) || $province == strtoupper($value)) { - $found = TRUE; - break; - } - } - if (!$found) { - user_import_errors(t('Province not found')); - } - - return trim($data[$column_id]); - } - } - - return trim($data[$column_id]); - - default: - return trim($data[$column_id]); - } - } - - /** - * Implementation of hook_user_import_after_save(). - */ - function location_user_import_after_save($settings, $account, $password, $fields, $updated, $update_setting_per_module) { - if (!is_array($fields['location'])) { - return; - } - // check if it's an existing user and if location is to be updated - if ($updated && $update_setting_per_module['location'] == UPDATE_NONE) { - return; - } - // Arrange values for location array - $location = array(); - - foreach ($fields['location'] as $column_id => $column_data) { - $location[0][$column_id] = $column_data[0]; - } - // Merge defaults in - $dummy = array(); - $defaults = location_invoke_locationapi($dummy, 'defaults'); - - foreach ($defaults as $key => $value) { - if (!isset($location[0][$key])) { - $location[0][$key] = $value['default']; - } - } - - location_save_locations($location, array('uid' => $account->uid)); - - return; - } - -} - diff --git a/profiles/wcm_base/modules/contrib/user_import/supported/profile.inc b/profiles/wcm_base/modules/contrib/user_import/supported/profile.inc deleted file mode 100644 index f64a0c1f..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/supported/profile.inc +++ /dev/null @@ -1,206 +0,0 @@ -<?php - -/** - * Implementation of hook_user_import_form_field_match(). - */ -function profile_user_import_form_field_match() { - module_load_include('inc', 'user_import', 'user_import.admin'); - $fields = _user_import_profile('fid', 'title'); - $options['profile'] = $fields; - - return $options; -} - -/** - * Implementation of hook_user_import_form_update_user(). - */ -function profile_user_import_form_update_user() { - $form['profile'] = array('title' => t('Profile'), 'description' => t('Affected: Profile fields.')); - - return $form; -} - -/** - * Implementation of hook_user_import_data(). - */ -function profile_user_import_data($settings, $update_setting, $column_settings, $module, $field_id, $data, $column_id) { - - if ($module != 'profile') return; - - return trim($data[$column_id]); -} - -/** - * Implementation of hook_user_import_after_save(). - */ -function profile_user_import_after_save($settings, $account, $password, $fields, $updated, $update_setting_per_module) { - - // get all fields - $profile_fields = profile_get_fields(); - $data = $old_data = unserialize($account->data); - - foreach ($profile_fields as $field) { - profile_user_import_save_profile($field, $account->uid, $fields['profile'][$field->fid][0], $updated, $update_setting_per_module['profile'], $data); - } - - // data column in the user table needs to be updated - if ($data != $old_data) { - - db_update('users') - ->fields(array('data' => serialize($data))) - ->condition('uid', $account->uid) - ->execute(); - } - - return; -} - - -function profile_user_import_save_profile($field, $uid, $value, $updated, $update_setting, &$data) { - - // when the profile field is displayed on the registration form an empty value is automatically saved by the Profile module - $exists = db_query_range('SELECT value FROM {profile_values} WHERE fid = :fid AND uid = :uid', 0, 1, array(':fid' => $field->fid, ':uid' => $uid))->fetchField(); - - user_import_profile_date($value, $field->type); - - if ($updated) { - - switch ($update_setting) { - case UPDATE_NONE: - return; - - case UPDATE_ADD: - if (empty($value) || (!empty($exists) && $exists != '')) return; - - case UPDATE_REPLACE: - - if (empty($value) && $update_setting == UPDATE_REPLACE) { - - db_query("DELETE FROM {profile_values} WHERE fid = :fid AND uid = :uid", array(':fid' => $field->fid, ':uid' => $uid)); - unset($data[$field->name]); - - return; - } - - if ((empty($exists) && $exists != '') || $exists === FALSE) { - - $id = db_insert('profile_values') - ->fields(array( - 'fid' => $field->fid, - 'uid' => $uid, - 'value' => $value, - )) - ->execute(); - } - else { - - $id = db_update('profile_values') - ->fields(array( - 'value' => $value, - )) - ->condition('fid', $field->fid) - ->condition('uid', $uid) - ->execute(); - } - - $data[$field->name] = $value; - - return; - } - - } - else { - - if (empty($value)) return; - - if ((empty($exists) && $exists != '') || $exists === FALSE) { - - $id = db_insert('profile_values') - ->fields(array('fid' => $field->fid, 'uid' => $uid, 'value' => $value)) - ->execute(); - - } - else { - - db_update('profile_values') - ->fields(array('value' => $value)) - ->condition('fid', $field->fid) - ->condition('uid', $uid) - ->limit(1) - ->execute(); - - $data[$field->name] = $value; - } - } - - return; -} - - -/** - * - */ -function profile_get_fields() { - - static $fields = array(); - - // Avoid making more than one database call for profile info. - if (empty($fields)) { - $results = db_query('SELECT * FROM {profile_field}'); - - foreach ($results as $row) { - $fields[] = $row; - } - } - - return $fields; -} - -/** - * Convert date into format that Profile module expects: - * a:3:{s:5:"month";s:1:"1";s:3:"day";s:1:"1";s:4:"year";s:4:"1976";} - */ -function user_import_profile_date(&$value, $field_type) { - - if ($field_type != "date" || empty($value)) { - return; - } - - $date = explode('/', $value); - - if (!is_array($date)) { - return; - } - - // Get seleceted format. - $date_format = variable_get('user_import_profile_date_format', 'MM/DD/YYYY'); - - if ($date_format == 'MM/DD/YYYY') { - list($month, $day, $year) = explode('/', $value); - } - else { - if ($date_format == 'DD/MM/YYYY') { - list($day, $month, $year) = explode('/', $value); - } - else { - if ($date_format == 'YYYY/MM/DD') { - list($year, $month, $day) = explode('/', $value); - } - else { - if ($date_format == 'YYYY/DD/MM') { - list($year, $day, $month) = explode('/', $value); - } - } - } - } - - // Leading zeros cause a problem so remove them. - if (substr($day, 0, 1) == '0') { - $day = substr($day, 1, 1); - } - if (substr($month, 0, 1) == '0') { - $month = substr($month, 1, 1); - } - - $value = serialize(array('month' => $month, 'day' => $day, 'year' => $year)); -} diff --git a/profiles/wcm_base/modules/contrib/user_import/supported/subscribed.inc b/profiles/wcm_base/modules/contrib/user_import/supported/subscribed.inc deleted file mode 100644 index 5be2e3d7..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/supported/subscribed.inc +++ /dev/null @@ -1,103 +0,0 @@ -<?php - -/** - * Implementation of hook_user_import_form_fieldsets(). - */ -function subscribed_user_import_form_fieldset($import, $collapsed) { - - if (module_exists('publication') && module_exists('schedule')) { - - $publications = publication_select_publications('enewsletter'); - if (empty($publications)) return; - - $form['subscribed'] = array( - '#type' => 'fieldset', - '#title' => t('Subscriptions'), - '#collapsible' => TRUE, - '#collapsed' => $collapsed, - '#tree' => TRUE, - ); - - foreach ($publications as $publication) { - - $type = $publication->type; - - $form['subscribed'][$type] = array( - '#type' => 'fieldset', - '#title' => check_plain($type), - ); - - } - - reset($publications); - $subscribed = $import['options']['subscribed']; - - foreach ($publications as $publication) { - - $options = array(); - $schedules = schedule_select_schedules($type, $publication->publication_id); - $options[0] = t('No Subscription'); - - foreach ($schedules as $schedule) { - $options[$schedule['schedule_id']] = $schedule['schedule_title']; - } - - $subscription_default = empty($subscribed[$type][$publication->publication_id]) ? 0 : $subscribed[$type][$publication->publication_id][0]; - - $form['subscribed'][$type][$publication->publication_id][] = array( - '#type' => 'radios', - '#title' => check_plain($publication->title), - '#default_value' => $subscription_default, - '#options' => $options, - '#description' => check_plain($publication->description), - ); - - } - } - - return $form; -} - -/** - * Implementation of hook_user_import_form_update_user(). - */ -function subscribed_user_import_form_update_user() { - $form['subscribed'] = array('title' => t('Subscribed'), 'description' => t('Affected: subscriptions.')); - - return $form; -} - -/** - * Implementation of hook_user_import_after_save(). - */ -function subscribed_user_import_after_save($settings, $account, $password, $fields, $updated) { - /** - * @todo change to new update architecture - */ - if (!module_exists('publication') || !module_exists('schedule') || !module_exists('identity_hash') || empty($settings['options']['subscribed'])) return; - - $subscribed_settings = $settings['options']['subscribed']; - $uid = $account->uid; - - if (is_array($subscribed_settings)) { - - foreach ($subscribed_settings as $type => $type_subscriptions) { - - $subscriptions = $type_subscriptions; - - foreach ($type_subscriptions as $publication_id => $schedule) { - if (empty($schedule[0])) unset($subscriptions[$publication_id]); - } - - $publications = publication_select_publications_and_terms($type); - - if (!empty($publications) && !empty($subscriptions)) { - subscribed_set_subscriptions($type, $uid, $publications, $subscriptions); - subscribed_set_subscriptions_terms($type, $uid, $publications, $subscriptions); - identity_hash_set_hash($uid); - } - } - } - - return; -} diff --git a/profiles/wcm_base/modules/contrib/user_import/supported/user.inc b/profiles/wcm_base/modules/contrib/user_import/supported/user.inc deleted file mode 100644 index 5011b8fc..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/supported/user.inc +++ /dev/null @@ -1,384 +0,0 @@ -<?php - -/** - * @todo move contact options to separate contact.inc - */ - -/** - * Implementation of hook_user_import_form_field_match(). - */ -function user_user_import_form_field_match() { - $options = array(); - $options['user']['email'] = t('Email Address*'); - $options['user']['password'] = t('Password'); - $options['user']['roles'] = t('Roles'); - $options['user']['created'] = t('Account Creation Date'); - - // $options['user']['username'] = t('Username'); - // $options['user']['uid'] = t('UID'); - // $options['user']['modified'] = t('Modified'); - // $options['user']['picture_uri'] = t('Picture'); - // $options['user']['timezone'] = t('Timezone'); - // $options['user']['md5_password'] = t('Password MD5'); - - return $options; -} - - -/** - * Implementation of hook_user_import_form_fieldsets(). - */ -function user_user_import_form_fieldset($import, $collapsed) { - - $form = array(); - user_user_import_edit_roles_fields($form, $import, $collapsed); - user_user_import_edit_email_fields($form, $import, $collapsed); - - return $form; -} - -/** - * Implementation of hook_user_import_form_update_user(). - */ -function user_user_import_form_update_user() { - $form['roles'] = array('title' => t('Roles'), 'description' => t('Affected: roles assigned to user.')); - $form['password'] = array('title' => t('Password'), 'description' => t('Affected: password.'), 'exclude_add' => TRUE); - $form['contact'] = array('title' => t('Contact'), 'description' => t('Affected: user contact option.'), 'exclude_add' => TRUE); - - return $form; -} - -/** - * Implementation of hook_user_import_data(). - */ -function user_user_import_data($settings, $update_setting, $column_settings, $module, $field_id, $data, $column_id) { - - if ($module != 'user') return; - - if ($field_id == 'email') { - $value = trim($data[$column_id]); - - if (!empty($settings['options']['email_domain'])) { - $value .= $settings['options']['email_domain']; - } - - _user_import_validate_email($value, $update_setting); - } - - if ($field_id == 'password') { - $value = trim($data[$column_id]); - } - - if ($field_id == 'created') { - $value = trim($data[$column_id]); - - if (!empty($value) && !is_numeric($value)) { - $parsed = date_parse($value); - $timestamp = mktime( - $parsed['hour'], - $parsed['minute'], - $parsed['second'], - $parsed['month'], - $parsed['day'], - $parsed['year'] - ); - - if (!empty($timestamp) && is_numerric($timestamp)) { - $value = $timestamp; - } - } - } - - if ($field_id == 'roles') { - $value = user_user_import_roles_data($data[$column_id], $settings['roles_new']); - } - - return $value; -} - -/** - * Implementation of hook_user_import_pre_save(). - */ -function user_user_import_pre_save($settings, $account, $fields, $errors, $update_setting_per_module) { - - $account_add['mail'] = $fields['user']['email'][0]; - - if (!empty($account['uid'])) { - - // update roles - switch ($update_setting_per_module['roles']) { - case UPDATE_ADD: - // include currently assigned roles - foreach ($account['roles'] as $rid => $role_name) { - $account_add['roles'][$rid] = $rid; - } - - case UPDATE_REPLACE: - // update roles - if (!isset($account_add['roles'])) { - $account_add['roles'] = array(); - } - - foreach ($settings['roles'] as $rid => $role_set) { - if (!empty($role_set)) { - $account_add['roles'][$rid] = $rid; - } - } - - break; - } - - // update password - if ($update_setting_per_module['password'] == UPDATE_REPLACE) { - $account_add['pass'] = (empty($fields['user']['password'][0])) ? user_password() : $fields['user']['password'][0]; - } - else { - $account_add['pass'] = ""; - } - - // update contact - if ($update_setting_per_module['contact'] == UPDATE_REPLACE) { - $account_add['contact'] = $settings['contact']; - } - else { - $account_add['contact'] = isset($account['contact']) ? $account['contact'] : ''; - } - - } - else { - - $account_add['timezone'] = variable_get('date_default_timezone', @date_default_timezone_get()); - $account_add['status'] = 1; - $account_add['init'] = $fields['user']['email'][0]; - $account_add['pass'] = (empty($fields['user']['password'][0])) ? user_password() : $fields['user']['password'][0]; - - if (isset($fields['user']['created'][0]) && !empty($fields['user']['created'][0])) { - $account_add['created'] = $fields['user']['created'][0]; - } - - $account_add['init'] = $fields['user']['email'][0]; - - - if (!empty($settings['options']['activate'])) { - $account_add['access'] = time(); - $account_add['login'] = time(); - } - - // add selected roles - foreach ($settings['roles'] as $rid => $role_set) { - if (!empty($role_set)) $account_add['roles'][$rid] = $rid; - } - } - - return $account_add; -} - -/** - * Implementation of hook_user_import_after_save(). - */ -function user_user_import_after_save($settings, $account, $password, $fields, $updated, $update_setting_per_module) { - /** - * @todo change hook_user_import_after_save() so that all changes to data are returned and saved in one hit - */ - - $roles = isset($fields['user']['roles']) ? $fields['user']['roles'] : array(); - user_user_import_after_save_role($account, $settings['roles_new'], $account->roles, $roles); - - return; -} - -function user_user_import_edit_roles_fields(&$form, $import, $collapsed) { - $roles = array(); - $roles_data = user_roles(); - - // remove 'anonymous user' option - while (list ($rid, $role_name) = each($roles_data)) { - if ($role_name != 'anonymous user' && $role_name != 'authenticated user') $roles[$rid] = $role_name; - } - - // roles selected - if (!empty($import['roles'])) { - foreach ($import['roles'] as $rid) { - if ($rid != 0) $roles_selected[] = $rid; - } - } - - if (empty($roles_selected)) $roles_selected[] = 2; - - $form['role_selection'] = array( - '#type' => 'fieldset', - '#title' => t('Role Assign'), - '#weight' => -80, - '#collapsible' => TRUE, - '#collapsed' => $collapsed, - ); - - $form['role_selection']['roles'] = array( - '#title' => t('Assign Role(s) To All Users'), - '#type' => 'checkboxes', - '#options' => $roles, - '#default_value' => $roles_selected, - '#description' => t("Select which role(s) all imported users should be assigned. The role 'authenticated user' is assigned automatically."), - ); - - $form['role_selection']['roles_new'] = array( - '#type' => 'checkbox', - '#title' => t('Add New Roles'), - '#default_value' => isset($import['roles_new']) ? $import['roles_new'] : 0, - '#description' => t('Create imported role(s) that are not found and assign it to the user, in addition to any role(s) selected above. Warning: incorrect roles will be created if the incoming data includes typos.'), - ); - - return; -} - -function user_user_import_edit_email_fields(&$form, $import, $collapsed) { - - $form['email_message'] = array( - '#type' => 'fieldset', - '#title' => t('Email Message'), - '#description' => t('Welcome message to be sent to imported users. Leave blank to use the default !message. If an existing user account is updated no welcome email will be sent to that user. <strong>Note - if "Activate Accounts" option is enabled !login_url (one time login) will not work.</strong>', array('!message' => l(t('message'), 'admin/people/settings'))), - '#collapsible' => TRUE, - '#collapsed' => $collapsed, - ); - - $form['email_message']['subject'] = array( - '#type' => 'textfield', - '#title' => t('Message Subject'), - '#default_value' => isset($import['subject']) ? $import['subject'] : '', - '#description' => t('Customize the subject of the welcome e-mail, which is sent to imported members.') . ' ' . t('Available variables are:') . ' !username, !site, !password, !uri, !uri_brief, !mailto, !date, !login_uri, !edit_uri, !login_url.', - '#element_validate' => array('user_import_send_email_subject_validate'), - ); - - $form['email_message']['message'] = array( - '#type' => 'textarea', - '#title' => t('Message'), - '#default_value' => isset($import['message']) ? $import['message'] : '', - '#description' => t('Customize the body of the welcome e-mail, which is sent to imported members.') . ' ' . t('Available variables are:') . ' !username, !site, !password, !uri, !uri_brief, !mailto, !login_uri, !edit_uri, !login_url.', - '#element_validate' => array('user_import_send_email_message_validate'), - ); - - $form['email_message']['message_format'] = array( - '#type' => 'radios', - '#title' => t('Email Format'), - '#default_value' => isset($import['message_format']) ? $import['message_format'] : 0, - '#options' => array(t('Plain Text'), t('HTML')), - ); - - $form['email_message']['message_css'] = array( - '#type' => 'textarea', - '#title' => t('CSS'), - '#default_value' => isset($import['message_css']) ? $import['message_css'] : '', - '#description' => t('Use if sending HTML formated email.'), - ); - - return; -} - -function _user_import_validate_email($email = NULL, $duplicates_allowed = FALSE) { - - if (empty($email)) { - user_import_errors(t('no email')); - } - elseif (!valid_email_address($email)) { - user_import_errors(t('invalid email')); - } - // Handle duplicate emails. - elseif (!$duplicates_allowed && _user_import_existing_uid($email)) { - user_import_errors(t('duplicate email')); - } - - return; -} - -function user_user_import_roles_data($data, $new_roles_allowed) { - $roles = array(); - - if (empty($data)) return; - $values = explode(',', $data); - - // check if any roles are specified that don't already exist - $existing_roles = user_roles(); - - foreach ($values as $piece) { - $role = trim($piece); - $unrecognised = array(); - - if (!empty($role)) { - // only add if role is recognized or adding new roles is allowed - if (empty($new_roles_allowed) && !array_search($role, $existing_roles)) { - $unrecognised[] = $role; - } - else { - $roles[] = $role; - } - } - - } - - if (!empty($unrecognised)) { - user_import_errors(t('The following unrecognised roles were specified:') . implode(',', $unrecognised)); - } - - return $roles; -} - -/** - * Return an existing user ID, if present, for a given email. - */ -function _user_import_existing_uid($email) { - - $sql = 'SELECT uid FROM {users} WHERE mail = :mail'; - $args = array(':mail' => $email); - $uid = db_query_range($sql, 0, 1, $args)->fetchField(); - - return $uid; -} - -function user_user_import_after_save_role($account, $new_roles_allowed, $account_roles, $roles) { - - $existing_roles = user_roles(); - - // if roles were specified, add to existing roles - $assign_roles = array(); - - if (is_array($roles) && !empty($roles)) { - - foreach ($roles as $role) { - - if (!empty($role)) { - $key = array_search($role, $existing_roles); - if (!empty($new_roles_allowed) && empty($key)) { - - $key = db_insert('role') - ->fields(array( - 'name' => $role, - )) - ->execute(); - - $existing_roles[$key] = $role; - } - - $assign_roles[$key] = $role; - } - } - - $need_update = FALSE; - - foreach ($assign_roles as $key => $role) { - if (!isset($account_roles[$key])) { - $need_update = TRUE; - $account_roles[$key] = $role; - } - } - - if ($need_update) { - - user_save($account, array('roles' => $account_roles)); - } - } - - return; -} - - - diff --git a/profiles/wcm_base/modules/contrib/user_import/supported/user_import.inc b/profiles/wcm_base/modules/contrib/user_import/supported/user_import.inc deleted file mode 100644 index 7fa68ead..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/supported/user_import.inc +++ /dev/null @@ -1,431 +0,0 @@ -<?php - -/** - * Implementation of hook_user_import_form_fieldset(). - * Add fieldsets to an import settings form. - */ -function user_import_user_import_form_fieldset($import, $collapsed) { - - $form = array(); - _user_import_edit_template_fields($form, $import); - _user_import_edit_settings_fields($form, $import, $collapsed); - _user_import_edit_remove_fields($form, $import); - - return $form; -} - -/** - * Implementation of hook_user_import_after_save(). - */ -function user_import_user_import_after_save($settings, $account, $password, $fields, $updated, $update_setting_per_module) { - if (!empty($settings['send_email']) && !$updated) { - - $subscribed = isset($settings['subscribed']) ? $settings['subscribed'] : NULL; - - _user_import_send_email($account, - $password, - $fields, - $settings['subject'], - $settings['message'], - $settings['message_format'], - $settings['message_css'], - $subscribed - ); - } -} - -/** - * Implementation of hook_user_import_imported(). - */ -function user_import_user_import_imported($import_id, $settings) { - // Delete file after it's been processed, - //_user_import_file_deletion($settings['filepath'], $settings['filename'], $settings['oldfilename'], FALSE, FALSE); - file_unmanaged_delete($settings['filepath']); -} - -// Send email when account is created -function _user_import_send_email($account, $password, $profile, $subject, $body, $format, $css, $subscribed) { - - global $base_url; - - // All system mails need to specify the module and template key (mirrored from - // hook_mail()) that the message they want to send comes from. - $module = 'user_import'; - $key = 'welcome'; - - // Specify 'to' and 'from' addresses. - $to = $account->mail; - $from = variable_get('site_mail', NULL); - - $params = array( - '!username' => $account->name, - '!uid' => $account->uid, - '!site' => variable_get('site_name', 'drupal'), - '!login_url' => user_pass_reset_url($account), - '!password' => $password, - '!uri' => $base_url, - '!uri_brief' => drupal_substr($base_url, drupal_strlen('http://')), - '!mailto' => $account->mail, - '!date' => format_date(time()), - '!login_uri' => url('user', array('absolute' => TRUE)), - '!edit_uri' => url('user/' . $account->uid . '/edit', array('absolute' => TRUE)), - 'subject' => $subject, - 'body' => $body, - 'email_format' => $format, - 'css' => $css, - ); - - _user_import_publication_email($params, $account, $subscribed, $format); - - // import info to profile - if (module_exists('profile') && is_array($profile)) { - - $profile_name = _user_import_profile('fid', 'name'); - - foreach ($profile_name as $fid => $field_name) { - $params['!' . $field_name] = $profile[$fid]; - } - } - - $language = user_preferred_language($account); - - // Whether or not to automatically send the mail when drupal_mail() is - // called. This defaults to TRUE, and is normally what you want unless you - // need to do additional processing before drupal_mail_send() is called. - $send = TRUE; - - $sent = drupal_mail($module, $key, $to, $language, $params, $from, $send); - - return; -} - -/** - * Implementation of hook_mail(). - */ -function user_import_mail($key, &$message, $params) { - - switch ($key) { - case 'welcome': - $message['subject'] = (empty($params['subject'])) ? _user_mail_text('register_admin_created_subject', $message['language'], $params) : strtr($params['subject'], $params); - $body = (empty($params['body'])) ? _user_mail_text('register_admin_created_body', $message['language'], $params) : strtr($params['body'], $params); - - if ($params['email_format'] == 1) { - $message['headers']['Content-Type'] = 'text/html; charset=UTF-8'; - - $body_head = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />'; - - if (!empty($params['css'])) $body_head .= '<style type="text/css">' . check_plain($params['css']) . '</style>'; - $message['body'][] = $body_head . '</head><body>' . $body . '</body></html>'; - } - else { - $message['body'][] = $body; - } - - break; - } -} - -function _user_import_edit_settings_fields(&$form, $import, $collapsed) { - - $form['optional'] = array( - '#type' => 'fieldset', - '#title' => t('Options'), - '#weight' => -85, - '#collapsible' => TRUE, - '#collapsed' => $collapsed, - ); - - $form['optional']['first_line_skip'] = array( - '#type' => 'checkbox', - '#title' => t('Ignore First Line'), - '#default_value' => isset($import['first_line_skip']) ? $import['first_line_skip'] : 0, - '#description' => t('If the first line is the names of the data columns, set to ignore first line.'), - ); - /** - * @todo move contact options to a separate contact.inc support file - */ - $form['optional']['contact'] = array( - '#type' => 'checkbox', - '#title' => t('Contact'), - '#default_value' => isset($import['contact']) ? $import['contact'] : 0, - '#description' => t("Set each user's personal contact form to 'allowed'."), - ); - - $form['optional']['send_email'] = array( - '#type' => 'checkbox', - '#title' => t('Send Email'), - '#default_value' => isset($import['send_email']) ? $import['send_email'] : 0, - '#description' => t('Send email to users when their account is created.'), - ); - - $form['optional']['username_space'] = array( - '#type' => 'checkbox', - '#title' => t('Username Space'), - '#default_value' => isset($import['username_space']) ? $import['username_space'] : 0, - '#description' => t("Include spaces in usernames, e.g. 'John' + 'Smith' => 'John Smith'."), - ); - - $form['optional']['activate'] = array( - '#type' => 'checkbox', - '#title' => t('Activate Accounts'), - '#default_value' => isset($import['activate']) ? $import['activate'] : 0, - '#description' => t("User accounts will not be visible to other users until their owner logs in. Select this option to make all imported user accounts visible. <strong>Note - one time login links in welcome emails will no longer work if this option is enabled.</strong>"), - ); - - $form['optional']['delimiter'] = array( - '#type' => 'textfield', - '#title' => t('File Delimiter'), - '#size' => 4, - '#default_value' => isset($import['delimiter']) ? $import['delimiter'] : ',', - '#description' => t("The column delimiter for the file. Use '/t' for Tab."), - ); - - $form['optional']['multi_value_delimiter'] = array( - '#type' => 'textfield', - '#title' => t('Multi Value Delimiter'), - '#size' => 4, - '#default_value' => isset($import['multi_value_delimiter']) ? $import['multi_value_delimiter'] : '*||*', - '#description' => t("Character(s) to use to split data so that it can be added to a field set to multiple values, e.g. '||', '*||*'"), - ); - - $form['optional']['email_domain'] = array( - '#type' => 'textfield', - '#title' => t('Email Domain'), - '#description' => t("Create an email address with the combined contents of Email Address (in Field Match above) and this field. <em><br />For example if Email Address is 'Luthor' and Email Domain is '@lexcorp.com', the account address woud be 'luthor@lexcorp.com'. <br />Note that '@example.com' is recommended for dummy addresses.</em>"), - '#default_value' => isset($import['email_domain']) ? $import['email_domain'] : '', - ); - - return; -} - -function _user_import_edit_template_fields(&$form, $import) { - - // settings template update controls - if (empty($import['name'])) { - - // new settings template save controls - - $form['save'] = array( - '#type' => 'fieldset', - '#title' => t('Save Settings'), - '#description' => t('Save settings for re-use on other imports.'), - '#weight' => 90, - '#collapsible' => TRUE, - '#collapsed' => FALSE, - ); - - $form['save']['name'] = array( - '#type' => 'textfield', - '#title' => t('Settings Name'), - '#size' => 26, - '#maxlength' => 25, - '#description' => t('Name to identify these settings by.'), - ); - - $auto_imports_enabled = variable_get('user_import_auto_imports_enabled', FALSE); - - if (!empty($auto_imports_enabled)) { - $form['save']['auto_import_directory'] = array( - '#type' => 'textfield', - '#title' => t('Auto Import Directory Name'), - '#description' => t('If this is set a directory with this name will be created, into which files can be uploaded and automatically processed, using the settings on this page to create new user accounts.'), - '#default_value' => isset($import['auto_import_directory']) ? $import['auto_import_directory'] : '', - ); - } - - $form['save'][] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#validate' => array('user_import_template_has_name_validate', - 'user_import_template_unique_name_validate', - 'user_import_edit_validate', - ), - '#submit' => array('user_import_template_new_submit'), - ); - - } - else { - - $form['save'] = array( - '#type' => 'fieldset', - '#title' => t('Saved Settings'), - '#description' => t("If changes have neen made to the settings since they where last saved you can update the saved template, or save them as a new template."), - '#weight' => 90, - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - - $form['#current_template'] = $import['name']; - - $form['save']['update'] = array( - '#type' => 'fieldset', - '#title' => t('Update'), - '#description' => t("Update '%name' settings template", array('%name' => $import['name'])), - ); - - $auto_imports_enabled = variable_get('user_import_auto_imports_enabled', FALSE); - - if (!empty($auto_imports_enabled)) { - $form['save']['auto_import_directory'] = array( - '#type' => 'textfield', - '#title' => t('Auto Import Directory Name'), - '#description' => t('If this is set a directory with this name will be created, into which files can be uploaded and automatically processed, using the settings on this page to create new user accounts.'), - '#default_value' => isset($import['auto_import_directory']) ? $import['auto_import_directory'] : '', - ); - } - - $form['save']['update']['submit'] = array( - '#type' => 'submit', - '#value' => t('Update'), - '#validate' => array('user_import_edit_validate'), - '#submit' => array('user_import_template_update_submit'), - ); - - $form['save']['new'] = array( - '#type' => 'fieldset', - '#title' => t('Create New'), - '#description' => t("Save as new settings template"), - ); - - $form['save']['new']['name'] = array( - '#type' => 'textfield', - '#title' => t('Save As New'), - '#size' => 30, - '#maxlength' => 25, - '#description' => t('Name to identify these settings by.'), - ); - - $auto_imports_enabled = variable_get('user_import_auto_imports_enabled', FALSE); - - if (!empty($auto_imports_enabled)) { - $form['save']['auto_import_directory'] = array( - '#type' => 'textfield', - '#title' => t('Auto Import Directory Name'), - '#description' => t('If this is set a directory with this name will be created, into which files can be uploaded and automatically processed, using the settings on this page to create new user accounts.'), - '#default_value' => isset($import['auto_import_directory']) ? $import['auto_import_directory'] : '', - ); - } - - $form['save']['new'][] = array( - '#type' => 'submit', - '#value' => t('Save As New'), - '#validate' => array('user_import_template_has_name_validate', 'user_import_template_unique_name_validate', 'user_import_edit_validate'), - '#submit' => array('user_import_template_new_submit'), - ); - } - - return; -} - -/** - * Validate that a template has a name. - */ -function user_import_template_has_name_validate($form, &$form_state) { - $template_name = trim($form_state['values']['name']); - if (empty($template_name)) form_set_error('name', t('A name needs to be set to save this settings template.')); -} - -/** - * Validate that a template has a unique name. - */ -function user_import_template_unique_name_validate($form, &$form_state) { - $template_name = trim($form_state['values']['name']); - $unique_name = db_query('SELECT COUNT(import_id) FROM {user_import} WHERE name = :name', array(':name' => $template_name))->fetchField(); - if (!empty($unique_name)) form_set_error('name', t("'!name' is already in use by another settings template.", array('!name' => $template_name))); -} - -/** - * Validate that a email subject line has been set if Send Email is enabled. - */ -function user_import_send_email_subject_validate($element, &$form_state) { - if (!empty($form_state['values']['send_email']) && empty($form_state['values']['subject'])) { - form_error($element, t('If Send Email has been enabled then an <strong>email subject</strong> line must set.')); - } -} - -/** - * Validate that a email message has been set if Send Email is enabled. - */ -function user_import_send_email_message_validate($element, &$form_state) { - if (!empty($form_state['values']['send_email']) && empty($form_state['values']['message'])) { - form_error($element, t('If Send Email has been enabled then an <strong>email message</strong> must set.')); - } -} - -function _user_import_edit_remove_fields(&$form, $import) { - - $form['remove'] = array( - '#type' => 'fieldset', - '#title' => t('Use Different CSV File'), - '#description' => t('Remove file to use a different file. All settings on this page will be deleted.'), - '#weight' => -100, - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - - $form['remove']['file'] = array( - '#type' => 'item', - '#title' => t('Uploaded file: @filename', array('@filename' => $import['filename'])), - '#value' => $import['filename'], - ); - - $form['remove']['submit'] = array( - '#type' => 'submit', - '#value' => t('Remove file'), - '#validate' => array('user_import_edit_remove_file_validate'), - ); - - return; -} - -/** - * Delete settings and uploaded file - */ -function user_import_edit_remove_file_validate($form, &$form_state) { - - $settings = _user_import_settings_select($form_state['values']['import_id']); - _user_import_settings_deletion($form_state['values']['import_id']); - //_user_import_file_deletion($settings['filepath'], $settings['filename'], $settings['oldfilename'], $settings['options']['ftp']); - file_unmanaged_delete($settings['filepath']); - drupal_goto('admin/people/user_import/add'); -} - -function _user_import_publication_email(&$variables, $account, $subscribed, $format) { - - if (!module_exists('publication') || !module_exists('schedule') || !module_exists('identity_hash')) { - return; - } - - $id_hash = identity_hash_select_hash($account->uid); - $variables['!id_hash'] = $id_hash->hash; - - while (list($type, $subscriptions) = each($subscribed)) { - - while (list($publication_id, $shedule) = each($subscriptions)) { - - if (!empty($shedule[0])) { - - $publication = publication_select_publications($type, $publication_id); - - $update_link = url('subscribed/preferences/' . $publication_id . '/' . $id_hash->hash, array('absolute' => TRUE)); - $unsubscribe_link = url('subscribed/delete/' . $publication_id . '/' . $id_hash->hash, array('absolute' => TRUE)); - - if ($format == 1) { - - $variables['!subscribed_links'] .= '<strong>' . $publication->title . '</strong><br />' . - '<a href="' . $update_link . '">' . t('Update Preferences') . '</a> | <a href="' . $unsubscribe_link . '">' . t('Unsubscribe') . '</a><br />'; - - } - else { - - $variables['!subscribed_links'] .= $publication->title . "\n" . - ' - ' . t('Update Preferences') . ' ' . $update_link . '\n' . - ' - ' . t('Unsubscribe') . ' ' . $unsubscribe_link . '\n'; - } - } - } - } -} diff --git a/profiles/wcm_base/modules/contrib/user_import/supported/watchdog.inc b/profiles/wcm_base/modules/contrib/user_import/supported/watchdog.inc deleted file mode 100644 index 16582d45..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/supported/watchdog.inc +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -/** - * @todo add option to not record watchdog messages for each user created - * on large imports user import could add a lot of rows to the watchdog table - */ - -/** - * Implementation of hook_user_import_after_save(). - */ -function watchdog_user_import_after_save($settings, $account, $password, $fields, $updated) { - - if (empty($updated)) { - watchdog('user', 'New user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit')); - } - - return; -} diff --git a/profiles/wcm_base/modules/contrib/user_import/user_import.admin.inc b/profiles/wcm_base/modules/contrib/user_import/user_import.admin.inc deleted file mode 100644 index 161c71bb..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/user_import.admin.inc +++ /dev/null @@ -1,1089 +0,0 @@ -<?php - -/** - * @file - * Provide administration configuration pages to import users. - */ - -function user_import_list($action = NULL, $import_id = NULL) { - - // clear incomplete imports - _user_import_incomplete_deletion(); - - if (!empty($import_id) && is_numeric($import_id)) { - - $pager_id = 1; - $limit = 10; - $rows = array(); - $import = _user_import_settings_select($import_id); - - $total = db_query('SELECT count(data) FROM {user_import_errors} WHERE import_id = :import_id', array(':import_id' => $import_id))->fetchField(); - - // Select table - $query = db_select('user_import_errors', 'imp_usr_errors'); - - // Select fields - $query->fields('imp_usr_errors', array('import_id', 'data', 'errors')); - - // Set conditions. - $query->condition('import_id', $import['import_id']); - - // For pagination - $query = $query->extend('TableSort')->extend('PagerDefault')->limit($limit); - - // Execute query - $result = $query->execute(); - - foreach ($result as $line) { - $rows[] = array('import_id' => $line->import_id, 'data' => unserialize($line->data), 'errors' => unserialize($line->errors)); - } - - $output = theme('user_import_errors_display', array('import' => $import, 'file_lines' => $rows, 'total' => $total)); - - } - else { - - $output = theme('user_import_list'); - } - - return $output; -} - -function user_import_preferences($import_id = NULL, $template_id = NULL) { - - if (empty($import_id)) { - $private_path = variable_get('file_private_path', FALSE); - - // Private path hasn't been set, show a message instead of the form. - if (!$private_path) { - drupal_set_message(t('!path needs to be set before users can be imported.', array('!path' => l('Private file system path', 'admin/config/media/file-system', array('query' => array('destination' => 'admin/people/user_import/add'))))), 'warning'); - return ' '; - } - - - $output = drupal_get_form('user_import_add_form'); - } - else { - $output = drupal_get_form('user_import_edit', $import_id, $template_id); - } - - return $output; -} - -function user_import_continue($import_id = NULL) { - - if (!empty($import_id) && is_numeric($import_id)) { - module_load_include('inc', 'user_import', 'user_import.import'); - $import = _user_import_settings_select($import_id); - _user_import_process($import); - } - - drupal_goto('admin/people/user_import'); -} - -function user_import_import($import_id = NULL) { - - if (!empty($import_id) && is_numeric($import_id)) { - - $import = _user_import_settings_select($import_id); - _user_import_initialise_import($import); - } - - drupal_goto('admin/people/user_import'); -} - -function user_import_delete($import_id = NULL, $return_path = 'admin/people/user_import') { - - if (empty($import_id) || !is_numeric($import_id)) drupal_goto($return_path); - - $import = _user_import_settings_select($import_id); - _user_import_settings_deletion($import_id); - //_user_import_file_deletion($import['filepath'], $import['filename'], $import['oldfilename'], $import['ftp']); - file_unmanaged_delete($import['filepath']); - drupal_goto($return_path); - - return; -} - -/** - * Configuration form define (settings affect all user imports) - */ -function user_import_configure_form() { - - $form['selectable_files'] = array( - '#type' => 'fieldset', - '#title' => t('Uploads Directory'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - - if (variable_get('user_import_selectable_files', 0) == 0) { - $selectable_files_description = t('This option provides a directory where files can be uploaded, and then selected when setting up an import.'); - } - else { - $selectable_files_description = t(''); - } - - $form['selectable_files']['selectable_files'] = array( - '#type' => 'checkbox', - '#title' => t('Enable Uploads Directory'), - '#description' => $selectable_files_description, - '#default_value' => variable_get('user_import_selectable_files', 0), - ); - - - $form['auto_imports'] = array( - '#type' => 'fieldset', - '#title' => t('Automated Imports'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - - $form['auto_imports']['auto_imports_enabled'] = array( - '#type' => 'checkbox', - '#title' => t('Enable Automated Imports'), - '#description' => t('Each import template will have the option to create a directory which will be scanned for any files that have been uploaded to it, - and when a file is found it will automatically be used to create new user accounts. Directories are scanned durring !cron runs.', array('!cron' => l(t('cron'), 'admin/config/system/cron'))), - '#default_value' => variable_get('user_import_auto_imports_enabled', 0), - ); - - $form['performance'] = array( - '#type' => 'fieldset', - '#title' => t('Performance'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - - $form['performance']['user_import_max'] = array( - '#type' => 'textfield', - '#title' => t('Maximum Users/Process'), - '#default_value' => variable_get('user_import_max', 250), - '#size' => 10, - '#maxlength' => 10, - '#description' => t('Maximum number of users to import each time the file is processed, useful for controling the rate at which emails are sent out.'), - ); - - $form['performance']['user_import_line_max'] = array( - '#type' => 'textfield', - '#title' => t('Maximum length of line'), - '#default_value' => variable_get('user_import_line_max', 1000), - '#size' => 10, - '#maxlength' => 10, - '#description' => t('The default is set at 1,000 characters, if a line in your csv is longer than this you should set a higher maximum here. Setting higher maximums will slow down imports.'), - ); - - if (module_exists('profile')) { - $date_options = array( - 'MM/DD/YYYY' => 'MM/DD/YYYY', - 'DD/MM/YYYY' => 'DD/MM/YYYY', - 'YYYY/MM/DD' => 'YYYY/MM/DD', - 'YYYY/DD/MM' => 'YYYY/DD/MM', - ); - $form['profile'] = array( - '#type' => 'fieldset', - '#title' => t('Profile Settings'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - $form['profile']['profile_date_format'] = array( - '#type' => 'select', - '#title' => t('Date field format'), - '#description' => t('Select a format for importing dates into user profiles (Profile module).'), - '#default_value' => variable_get('user_import_profile_date_format', 'MM/DD/YYYY'), - '#options' => $date_options, - ); - } - - $saved_templates = _user_import_settings_select(NULL, TRUE); - - if (!empty($saved_templates)) { - - $form['settings_templates'] = array( - '#type' => 'fieldset', - '#title' => t('Settings Templates'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - - $templates_list = array('-- none --'); - - foreach ($saved_templates AS $template) { - $templates_list[$template['import_id']] = $template['name']; - $templates_delete[$template['import_id']] = $template['name']; - } - - $form['settings_templates']['user_import_settings'] = array( - '#type' => 'select', - '#title' => t('Default Settings'), - '#description' => t('Select if you want to use a previously saved set of settings as default for all imports.'), - '#default_value' => variable_get('user_import_settings', 0), - '#options' => $templates_list, - ); - - - $form['settings_templates']['templates'] = array( - '#type' => 'checkboxes', - '#title' => t('Delete Templates'), - '#options' => $templates_delete, - ); - - } - - $form['submit'] = array( - '#type' => 'submit', - '#value' => t('Save'), - ); - - return $form; -} - -function user_import_configure_form_validate($form, &$form_state) { - - if (is_numeric($form_state['values']['user_import_max'])) { - if ($form_state['values']['user_import_max'] < 10) form_set_error('user_import_max', t("Value should be at least 10.")); - } - else { - form_set_error('user_import_max', t('Value must be a number.')); - } - - if (is_numeric($form_state['values']['user_import_line_max'])) { - if ($form_state['values']['user_import_line_max'] < 1000) form_set_error('user_import_line_max', t("Value must be higher than 1000.")); - if ($form_state['values']['user_import_line_max'] > 1000000) form_set_error('user_import_line_max', t("Value must be lower than 1,000,000.")); - } - else { - form_set_error('user_import_line_max', t('Value must be a number.')); - } - - // Check private files path is set. - if (!empty($form_state['values']['auto_imports_enabled'])) { - $file_private_path = variable_get('file_private_path', FALSE); - - if (!$file_private_path) { - form_set_error('auto_imports_enabled', t('The !private_file_path must be set to use this feature.', array('!private_file_path' => l(t('Private file system path'), 'admin/config/media/file-system')))); - } - } - - // Check private files path is set. - if (!empty($form_state['values']['selectable_files'])) { - $file_private_path = variable_get('file_private_path', FALSE); - - if (!$file_private_path) { - form_set_error('selectable_files', t('The !private_file_path must be set to use the Uploads Directory feature.', array('!private_file_path' => l(t('Private file system path'), 'admin/config/media/file-system')))); - } - } - - return; -} - -function user_import_configure_form_submit($form, &$form_state) { - $deleted = ''; - settype($form_state['values']['user_import_max'], 'integer'); - settype($form_state['values']['user_import_line_max'], 'integer'); - variable_set('user_import_max', $form_state['values']['user_import_max']); - variable_set('user_import_line_max', $form_state['values']['user_import_line_max']); - - $user_import_settings = isset($form_state['values']['user_import_settings']) ? $form_state['values']['user_import_settings'] : 0; - variable_set('user_import_settings', $user_import_settings); - - $profile_date_format = isset($form_state['values']['profile_date_format']) ? $form_state['values']['profile_date_format'] : 0; - variable_set('user_import_profile_date_format', $profile_date_format); - - variable_set('user_import_selectable_files', $form_state['values']['selectable_files']); - variable_set('user_import_auto_imports_enabled', $form_state['values']['auto_imports_enabled']); - - // Create a directory for processing incoming files if auto imports are enabled. - if (!empty($form_state['values']['auto_imports_enabled'])) { - user_import_create_directory('processing', ''); - } - - // Create a directory for processing incoming files if auto imports are enabled. - if (!empty($form_state['values']['selectable_files'])) { - user_import_create_directory('selectable'); - } - - if (!empty($form_state['values']['templates'])) { - - foreach ($form_state['values']['templates'] as $import_id) { - - if (!empty($import_id)) { - - $template = _user_import_settings_select($import_id); - if (!empty($deleted)) $deleted .= ', '; - $deleted .= $template['name']; - _user_import_settings_deletion($import_id); - } - } - } - - if (!empty($deleted)) drupal_set_message(t('Settings templates deleted: @deleted', array('@deleted' => $deleted))); - - drupal_set_message(t('Configuration settings have been saved.')); - $form_state['redirect'] = 'admin/people/user_import'; -} - -/** - * Start new import. - * Form to select file. - **/ -function user_import_add_form($import_id = NULL) { - - $form = array(); - $ftp_files = array(); - - if (variable_get('user_import_selectable_files', 0) == 1) { - $ftp_files = _user_import_ftp_files(); - } - - user_import_add_file_form($form, $ftp_files); - - user_import_delimiter_form($form); - $settings = _user_import_settings_select(NULL, TRUE); - - if ($settings) { - - $saved_settings = array(t('-- none --')); - foreach ($settings AS $settings_set) { - $saved_settings[$settings_set['import_id']] = $settings_set['name']; - } - - $form['import_template_select'] = array( - '#type' => 'select', - '#title' => t('Saved Settings'), - '#description' => t('Select if you want to use a previously saved set of settings.'), - '#default_value' => variable_get('user_import_settings', 0), - '#options' => $saved_settings, - ); - - } - - $form['next'] = array( - '#type' => 'submit', - '#value' => t('Next') - ); - - // Set form parameters so we can accept file uploads. - $form['#attributes'] = array('enctype' => 'multipart/form-data'); - - return $form; -} - -function user_import_add_form_validate($form, &$form_state) { - - if (isset($form_state['values']['file_ftp']) && !empty($form_state['values']['file_ftp'])) { - $file_ftp = $form_state['values']['file_ftp']; - } - else { - $file_ftp = FALSE; - } - - $file = _user_import_file(NULL, $file_ftp); - - // check file uploaded OK - if (empty($file->filename)) { - form_set_error('file_upload', t('A file must be uploaded or selected from FTP updates.')); - } - else { - $form_state['values']['file_info'] = $file; - } - - return; -} - -function user_import_add_form_submit($form, &$form_state) { - $redirect = 'admin/people/user_import/add/'; - - if (isset($form_state['values']['file_ftp']) && !empty($form_state['values']['file_ftp'])) { - $file_ftp = $form_state['values']['file_ftp']; - } - else { - $file_ftp = FALSE; - } - - $file = _user_import_file(NULL, $file_ftp); - $file_name = user_import_move_file_for_processing($file->uri, $file->filename); - - $form_state['values']['ftp'] = $file_ftp; - $form_state['values']['filename'] = $file_name; - $form_state['values']['oldfilename'] = $file->filename; - $form_state['values']['filepath'] = 'private://user_import/processing/' . $file_name; - $form_state['values']['setting'] = 'file set'; - - // create import setting - $import = _user_import_settings_save($form_state['values']); - $redirect .= $import['import_id']; - - if (!empty($form_state['values']['import_template_select'])) { - $redirect .= '/' . check_plain($form_state['values']['import_template_select']); - } - - $form_state['redirect'] = $redirect; -} - -function user_import_delimiter_form(&$form, $value = ',') { - $form['file_settings'] = array( - '#type' => 'fieldset', - '#title' => t('File Settings'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#description' => t("File column delimiter"), - ); - - $form['file_settings']['delimiter'] = array( - '#type' => 'textfield', - '#title' => t('Delimiter'), - '#size' => 4, - '#default_value' => $value, - '#required' => TRUE, - '#description' => t("The column delimiter for the file. Use '/t' for Tab."), - ); -} - -function user_import_edit($form, $form_state, $import_id, $template_id = NULL) { - - // load code for supported modules - user_import_load_supported(); - - $form = array(); - - $import = _user_import_settings_select($import_id); - $import['template_id'] = $template_id; - - $form['ftp'] = array( - '#type' => 'value', - '#value' => $import['options']['ftp'], - ); - - // add setting template values - if ($import['setting'] == 'file set') { - $import = _user_import_initialise_import($import); - } - - $form['import_id'] = array( - '#type' => 'value', - '#value' => $import_id, - ); - - $form['setting'] = array( - '#type' => 'value', - '#value' => $import['setting'], - ); - - $form['return_path'] = array( - '#type' => 'value', - '#default_value' => 'admin/people/user_import', - ); - - $form['og_id'] = array( - '#type' => 'value', - '#default_value' => 0, - ); - - // don't use hook because these need to be added in this order; - user_import_edit_file_fields($form, $import); - user_import_form_field_match($form, $import); - - $collapsed = (empty($import['name'])) ? FALSE : TRUE; - $additional_fieldsets = module_invoke_all('user_import_form_fieldset', $import, $collapsed); - if (is_array($additional_fieldsets)) $form = $form + $additional_fieldsets; - - $update_user = module_invoke_all('user_import_form_update_user'); - - if (is_array($update_user)) { - - $form['update_user'] = array( - '#type' => 'fieldset', - '#title' => t('Update Existing Users'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#tree' => TRUE, - ); - - foreach ($update_user as $module => $display) { - - $options = array(UPDATE_NONE => t('No Update'), UPDATE_REPLACE => t('Replace Data'), UPDATE_ADD => t('Add Data')); - if (isset($display['exclude_add']) && $display['exclude_add'] == TRUE) unset($options[UPDATE_ADD]); - if (isset($display['exclude_replace']) && $display['exclude_replace'] == TRUE) unset($options[UPDATE_REPLACE]); - - $form['update_user'][$module] = array( - '#type' => 'radios', - '#title' => $display['title'], - '#options' => $options, - '#default_value' => empty($import['options']['update_user'][$module]) ? UPDATE_NONE : $import['options']['update_user'][$module], - '#description' => $display['description'], - ); - } - } - - // don't show test option if import has started - if ($import['setting'] != 'import' && $import['setting'] != 'imported') { - - $form['test'] = array( - '#type' => 'submit', - '#value' => t('Test'), - '#weight' => 100, - '#submit' => array('user_import_test_submit', 'user_import_edit_submit'), - ); - } - - $form['import'] = array( - '#type' => 'submit', - '#value' => t('Import'), - '#weight' => 100, - '#submit' => array('user_import_import_submit', 'user_import_edit_submit'), - ); - - $form['cancel'] = array( - '#type' => 'submit', - '#value' => t('Cancel'), - '#weight' => 100, - '#validate' => array('user_import_edit_cancel_validate'), - ); - - return $form; -} - -function user_import_edit_cancel_validate($form, &$form_state) { - - // if import was being added - delete file - if ($form_state['values']['setting'] == 'file set') { - $settings = _user_import_settings_select($form_state['values']['import_id']); - _user_import_settings_deletion($form_state['values']['import_id']); - // _user_import_file_deletion($settings['filepath'], $settings['filename'], $settings['oldfilename'], $settings['options']['ftp']); - file_unmanaged_delete($settings['filepath']); - } - - $form_state['redirect'] = 'admin/people/user_import'; -} - -function user_import_edit_validate($form, &$form_state) { - $email = FALSE; - $fields = array(); - - foreach ($form_state['values']['field_match'] as $row => $values) { - - // check each field is unique - if ($values['field_match'] != '0' && $values['field_match'] != '-------------' && in_array($values['field_match'], $fields)) { - form_set_error('field_match', t('Database fields can only be matched to one column of the csv file.')); - } - - $fields[$values['field_match']] = $values['field_match']; - - // check email address has been selected - if ($values['field_match'] == 'user-email') $email = TRUE; - } - - if (!$email) form_set_error('email', t('One column of the csv file must be set as the email address.')); - - if ($form_state['values']['name']) { - $form_state['values']['name'] = rtrim($form_state['values']['name']); - - if (drupal_strlen($form_state['values']['name']) < 1 || drupal_strlen($form_state['values']['name']) > 25) { - form_set_error('name', t('Name of saved settings must be 25 characters or less.')); - } - } - - // Check auto uploads directory is not already in use by another template. - if (!empty($form_state['values']['auto_import_directory'])) { - $auto_import_directory = 'private://user_import/uploads/' . $form_state['values']['auto_import_directory']; - - if (file_prepare_directory($auto_import_directory, FILE_MODIFY_PERMISSIONS) || $form_state['values']['auto_import_directory'] == 'selectable') { - form_set_error('auto_import_directory', t("Directory '%directory' already exists and can not be used.", array('%directory' => $form_state['values']['auto_import_directory']))); - } - } - - return; -} - -/** - * Save a new template. - */ -function user_import_template_new_submit($form, &$form_state) { - - // save settings for import - _user_import_settings_save($form_state['values']); - - // save settings for template - $import_id = $form_state['values']['import_id']; - $form_state['values']['setting'] = 'template'; - unset($form_state['values']['import_id']); - - if (isset($form_state['values']['auto_import_directory']) && !empty($form_state['values']['auto_import_directory'])) { - user_import_create_directory($form_state['values']['auto_import_directory']); - } - - - _user_import_initialise_import($form_state['values']); - drupal_set_message(t("'%name' was saved as a settings template.", array('%name' => $form_state['values']['name']))); - - // reload settings page - $form_state['redirect'] = 'admin/people/user_import/add/' . $import_id; - - return; -} - -/** - * Update an existing template. - */ -function user_import_template_update_submit($form, &$form_state) { - - // save settings for import - $import_id = $form_state['values']['import_id']; - _user_import_settings_save($form_state['values']); - - // get template details - $template_id = db_query_range("SELECT import_id from {user_import} where setting = 'template' AND name = :name", 0, 1, array(':name' => $form['#current_template']))->fetchField(); - - // save settings for template - $form_state['values']['setting'] = 'template'; - $form_state['values']['import_id'] = $template_id; - $form_state['values']['name'] = $form['#current_template']; - _user_import_initialise_import($form_state['values']); - drupal_set_message(t("'%name' settings template was updated.", array('%name' => $form['#current_template']))); - - // reload settings page - $form_state['redirect'] = 'admin/people/user_import/add/' . $import_id; - - return; -} - -/** - * - */ -function user_import_test_submit($form, &$form_state) { - $form_state['values']['setting'] = 'test'; - drupal_set_message(t('Tested')); -} - -/** - * - */ -function user_import_import_submit($form, &$form_state) { - $form_state['values']['setting'] = 'import'; - drupal_set_message(t('Imported')); -} - -function user_import_move_file_for_processing($source, $file_name) { - // Make sure processing directory exists. - user_import_create_directory(NULL, 'processing'); - - $destination = 'private://user_import/processing/' . $file_name; - $uri = file_unmanaged_move($source, $destination, FILE_EXISTS_RENAME); - $file_name = substr($uri, 33); - return $file_name; -} - -/** - * - */ -function user_import_edit_submit($form, &$form_state) { - // Deal with import being canceled. - if ($form_state['clicked_button']['#value'] == t('Cancel')) { - $form_state['redirect'] = 'admin/people/user_import'; - - return; - } - - // Load import functions. - module_load_include('inc', 'user_import', 'user_import.import'); - - if ($form_state['values']['setting'] == 'file set') { -// $file = new stdClass(); -// $file->uri = $form_state['values']['filepath']; -// $file->filename = $form_state['values']['filename']; - //$filepath = file_move($file, file_directory_path() . '/' . $form_state['values']['filename']); - $filepath = user_import_move_file_for_processing($form_state['values']['filepath']); - } - - if (!empty($form_state['values']['og_id'])) $form_state['values']['groups'][$form_state['values']['og_id']] = $form_state['values']['og_id']; - $form_state['values']['ftp'] = $form_state['values']['ftp']; - $form_state['values'] = _user_import_settings_save($form_state['values']); - $form_state['values']['save']['update'] = NULL; - $form_state['values']['import_template_id'] = NULL; - $form_state['values']['save']['name'] = NULL; - $form_state['redirect'] = $form_state['values']['return_path']; - _user_import_process($form_state['values']); -} - -function user_import_add_file_form(&$form, $ftp_files = NULL) { - - $form['browser'] = array( - '#type' => 'fieldset', - '#title' => t('Browser Upload'), - '#collapsible' => TRUE, - '#description' => t("Upload a CSV file."), - ); - - if (function_exists('file_upload_max_size')) { - $file_size = t('Maximum file size: @size.', array('@size' => format_size(file_upload_max_size()))); - } - - $form['browser']['file_upload'] = array( - '#type' => 'file', - '#title' => t('CSV File'), - '#size' => 40, - '#description' => check_plain(t('Select the CSV file to be imported.') . ' ' . $file_size), - ); - - if (!empty($ftp_files)) { - - $form['ftp'] = array( - '#type' => 'fieldset', - '#title' => t('FTP Upload'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#description' => t("Any files uploaded to the 'user_import' directory using FTP can be selected for import here. Useful if the import file is too large for upload via the browser."), - ); - - $form['ftp']['file_ftp'] = array( - '#type' => 'radios', - '#title' => t('Files'), - '#default_value' => '0', - '#options' => $ftp_files, - ); - - // reload the page to show any files that have been added by FTP - $form['ftp']['scan'] = array( - '#type' => 'submit', - '#value' => t('Check for new files'), - '#validate' => array(), - '#submit' => array(), - ); - - } - - return; -} - -function user_import_edit_file_fields(&$form, $import) { - - $form['filename'] = array( - '#type' => 'value', - '#value' => $import['filename'], - ); - - $form['oldfilename'] = array( - '#type' => 'value', - '#value' => $import['oldfilename'], - ); - - $form['filepath'] = array( - '#type' => 'value', - '#value' => $import['filepath'], - ); - - return; -} - -// - - - - - - - - FILE HANDLING - - - - - - - - - -/** - * open file - */ -function _user_import_file_open($filepath, $filename) { - - ini_set('auto_detect_line_endings', TRUE); - $handle = @fopen($filepath, "r"); - - if (!$handle) { - form_set_error('file', t("Could not find the csv file '%filename'", array('%filename' => $filename)), 'error'); - - return t("Please add your file again."); - } - - return $handle; -} - -/* - * File being used - * $import_id - use file info stored in database - * $ftp_file - chosen from FTP uploaded files - * $uploaded_file - uploaded through browser - */ -function _user_import_file($import_id = NULL, $ftp_file_selected = NULL) { - - static $file; - if (!empty($file)) return $file; - - // File was uploaded through browser. - if (empty($ftp_file_selected) && empty($import_id)) { - // Delete record from database management so we don't have duplicates problems. - db_delete('file_managed')->condition('uri', 'temporary://' . $_FILES['files']['name']['file_upload'])->execute(); - - $file = file_save_upload('file_upload', array('file_validate_extensions' => array('csv txt')), FALSE, FILE_EXISTS_RENAME); - if (!empty($file)) return $file; - } - - // File was uploaded by FTP - if (!empty($ftp_file_selected)) { - $ftp_files = file_scan_directory('private://user_import/uploads/selectable', '/.*$/', array('key' => 'filename')); - $file = $ftp_files[$ftp_file_selected]; - $file->filepath = $file->uri; - - return $file; - } - - // Use file info stored in database - if (!empty($import_id)) { - $import = _user_import_settings_select($import_id); - $file->uri = $import['filepath']; - $file->filepath = $import['filepath']; - $file->oldfilename = $import['oldfilename']; - $file->filename = $import['filename']; - - return $file; - } - - return; -} - -// get info on files uploaded via FTP -function _user_import_ftp_files() { - - //$directory = opendir(drupal_get_path('module', 'user_import')); - - $files = file_scan_directory('private://user_import/uploads/selectable', '/.*$/'); - $filenames[] = t('none'); - - foreach ($files as $file) { - $filenames[$file->filename] = $file->filename; - } - - return $filenames; -} - -// delete incomplete import settings, where only the file has been uploaded -function _user_import_incomplete_deletion() { - - $result = db_query("SELECT * FROM {user_import} WHERE setting = 'file set'"); - - foreach ($result as $import) { - $options = unserialize($import->options); - //_user_import_file_deletion($import->filepath, $import->filename, $import->oldfilename, $options['ftp'], FALSE); - file_unmanaged_delete($import->filepath); - _user_import_settings_deletion($import->import_id); - } - - return; -} - -/** - * Create a directory in the private files directory. - * - **/ -function user_import_create_directory($directory_name, $path_prefix = 'uploads/') { - - if (!file_stream_wrapper_valid_scheme('private')) { - drupal_set_message(t('Directory %path could not be created as the Private files path has not been set.', array('%path' => $directory_name)), 'warning'); - } - - $path = 'private://user_import/' . $path_prefix . $directory_name; - $directory_created = file_prepare_directory($path, FILE_CREATE_DIRECTORY); - - if ($directory_created) { - file_create_htaccess($path); - } -} - -// - - - - - - - - MISC - - - - - - - - - -function user_import_form_field_match(&$form, $import) { - - $delimiter = isset($import['options']['delimiter']) && !empty($import['options']['delimiter']) ? $import['options']['delimiter'] : ','; - $collapsed = (empty($import['name'])) ? FALSE : TRUE; - $handle = _user_import_file_open($form['filepath']['#value'], $form['filename']['#value']); - $data_row = _user_import_file_row($form['filename']['#value'], $handle, $delimiter); - - $fieldmatch_description_parts = array( - '<strong>' . t('Drupal fields') . ':</strong> ' . t("Match columns in CSV file to drupal user fields, leave as '----' to ignore the column."), - '<strong>' . t('Username') . ':</strong> ' . t("If username is selected for multiple fields, the username will be built in the order selected. Otherwise, the username will be randomly generated."), - '<strong>' . t('Abbreviate') . ':</strong> ' . t("Use the first letter of a field in uppercase for the Username, e.g. 'john' -> 'J'."), - ); - - $fieldmatch_description = theme('item_list', array('items' => $fieldmatch_description_parts)); - - $form['field_match'] = array( - '#type' => 'fieldset', - '#title' => t('Field Match'), - '#description' => $fieldmatch_description, - '#weight' => -90, - '#collapsible' => TRUE, - '#collapsed' => $collapsed, - '#tree' => TRUE, - ); - - // add default and email address options - $user_fields[0] = '-------------'; - $additional_user_fields = module_invoke_all('user_import_form_field_match'); - - foreach ($additional_user_fields as $type => $type_options) { - if (is_array($type_options)) { - foreach ($type_options as $field_id => $label) { - $user_fields["$type-$field_id"] = $label; - } - } - } - - asort($user_fields); - - $row = 0; - $sort = array(t('--'), 1, 2, 3, 4); - - if (empty($data_row)) return; - - foreach ($data_row as $data_cell) { - - $form['field_match'][$row] = array( - '#tree' => TRUE, - ); - - $form['field_match'][$row]['csv'] = array( - '#markup' => check_plain(drupal_substr($data_cell, 0, 40)), - ); - - $form['field_match'][$row]['field_match'] = array( - '#type' => 'select', - '#default_value' => isset($import['field_match'][$row]['field_match']) ? $import['field_match'][$row]['field_match'] : $user_fields[0], - '#options' => $user_fields, - ); - - $form['field_match'][$row]['username'] = array( - '#type' => 'select', - '#default_value' => isset($import['field_match'][$row]['username']) ? $import['field_match'][$row]['username'] : $sort[0], - '#options' => $sort, - ); - - $form['field_match'][$row]['abbreviate'] = array( - '#type' => 'checkbox', - '#default_value' => isset($import['field_match'][$row]['abbreviate']) ? $import['field_match'][$row]['abbreviate'] : NULL, - ); - - $row++; - } - - return; -} - -// get first row of file -function _user_import_file_row($filename, $handle, $delimiter = ',') { - - // Handle folks who may list 'tab' as 't' or 'tab' - if (strtolower($delimiter) == 't' || strtolower($delimiter) == 'tab' || $delimiter == '\t' || $delimiter == '/t') { - $delimiter = '\t'; - } - - $data_row = @fgetcsv($handle, 1000000, $delimiter); - - if (!$data_row) { - form_set_error('file', t("Could not get data, the file '%filename' is either empty or has incompatible line endings.", array('%filename' => $filename)), 'error'); - } - - return $data_row; -} - -// move from one stage to the next -// set up all necessary variables -function _user_import_initialise_import($import) { - - //_user_import_process won't work w/o this include - module_load_include('inc', 'user_import', 'user_import.import'); - - switch ($import['setting']) { - case 'imported': - drupal_set_message(t('File has already been imported'), 'error'); - break; - - // add setting template values to new import settings - case 'file set': - if (empty($import['template_id'])) return $import; - $template = _user_import_settings_select($import['template_id']); - $template['import_id'] = $import['import_id']; - $template['filename'] = $import['filename']; - $template['oldfilename'] = $import['oldfilename']; - $template['filepath'] = $import['filepath']; - $template['started'] = 0; - $template['setting'] = 'file set'; - - return $template; - - case 'test': - case 'tested': - $import['setting'] = 'import'; - $import['started'] = 0; - $import['pointer'] = 0; - $import['processed'] = 0; - $import['valid'] = 0; - _user_import_errors_display_delete($import['import_id']); - _user_import_settings_save($import); - _user_import_process($import); - break; - - case 'template': - unset($import['filename']); - unset($import['oldfilename']); - unset($import['filepath']); - $import['started'] = 0; - $import['pointer'] = 0; - $import['processed'] = 0; - $import['valid'] = 0; - _user_import_settings_save($import); - break; - - default: - _user_import_process($import); - drupal_set_message(t('Imported')); - break; - } - - return; -} - - -/** - * Delete errors from database for a specified import. - * @param $import_id - */ -function _user_import_errors_display_delete($import_id) { - db_query('DELETE FROM {user_import_errors} WHERE import_id = :import_id', array(':import_id' => $import_id)); - - return; -} - -function _user_import_profile($key = 'fid', $return_value = NULL) { - - if (!module_exists('profile')) return; - - static $fields_static; - $fields = array(); - - // avoid making more than one database call for profile info - if (empty($fields_static)) { - - $results = db_query("SELECT * FROM {profile_field}"); - - foreach ($results as $row) { - // don't include private fields - if (user_access('administer users') || $row->visibility != PROFILE_PRIVATE) { - $fields_static[] = $row; - } - } - } - - if (empty($fields_static)) return array(); - - // return all profile fields info, or just specific type - if (empty($return_value)) { - - foreach ($fields_static as $field) { - $fields[$field->{$key}] = $field; - } - } - else { - foreach ($fields_static as $field) { - $fields[$field->{$key}] = $field->{$return_value}; - } - } - - asort($fields); - - return $fields; -} diff --git a/profiles/wcm_base/modules/contrib/user_import/user_import.drush.inc b/profiles/wcm_base/modules/contrib/user_import/user_import.drush.inc deleted file mode 100644 index 5cbec649..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/user_import.drush.inc +++ /dev/null @@ -1,110 +0,0 @@ -<?php - -/** - * @file - * Drush integration for the User Import module. - */ - -function user_import_drush_help($command) { - switch ($command) { - case 'drush:user-import': - return dt('Queue an import of users from a CSV file'); - case 'error:template-not-found': - return dt('Template not found'); - case 'error:file-not-found': - return dt('File not found'); - case 'error:no-default-template': - return dt('There is no default template configured'); - } -} - -/** - * Implementation of hook_drush_command(). - * - * In this hook, you specify which commands your - * drush module makes available, what it does and - * description. - * - * Notice how this structure closely resembles how - * you define menu hooks. - * - * See `drush topic docs-commands` for a list of recognized keys. - * - * @return - * An associative array describing your command(s). - */ -function user_import_drush_command() { - $commands = array(); - $commands['user-import'] = array( - 'description' => dt('Queue an import of users'), - 'arguments' => array( - 'file' => dt('The CSV file with user data'), - 'template' => dt('Name of the template to use (optional, if not provided the default template is used)'), - ), - 'examples' => array( - dt('standard example') => 'drush user-import users.csv', - ), - ); - - return $commands; -} - -function drush_user_import($original_file = NULL, $template_name = NULL) { - - if (!file_exists($original_file)) { - return drush_set_error('file-not-found'); - } - $original_file = realpath($original_file); - - if ($template_name) { - $template = db_query("SELECT * FROM {user_import} WHERE name = :name AND setting = 'template'", array(':name' => $template_name))->fetchObject(); - - if (!$template) { - return drush_set_error('template-not-found'); - } - } - else { - $template_id = variable_get('user_import_settings', '0'); - - if (!$template_id) { - return drush_set_error('no-default-template'); - } - - $template = db_query("SELECT * FROM {user_import} WHERE import_id = :import_id AND setting = 'template'", array(':import_id' => $template_id))->fetchObject(); - - if (!$template) { - return drush_set_error('template-not-found'); - } - - drush_print(dt('Using default settings template "!template"', array('!template' => $template->name))); - } - - $template->options = unserialize($template->options); - $template->field_match = unserialize($template->field_match); - $template->roles = unserialize($template->roles); - - $file = new stdClass(); - $file->filepath = $original_file; - $file->filename = basename($original_file); - $destination = 'private://user_import/processing'; - $filepath = file_unmanaged_copy($file->filepath, $destination, FILE_EXISTS_RENAME); - - // initialize import from template - $import = new stdClass(); - $import->oldfilename = basename($original_file); - $import->filename = $file->filename; - $import->filepath = $filepath; - $import->started = time(); - $import->field_match = $template->field_match; - $import->roles = $template->roles; - $import->options = $template->options; - $import->setting = 'import'; - $result = drupal_write_record('user_import', $import); - - if ($result == SAVED_NEW) { - drush_print(dt('Successfully queued user import. The original CSV file !file has been copied to the Drupal installation and can be removed.', array('!file' => $original_file))); - } - else { - drush_set_error('import_failed', dt('Unable to queue the user import.')); - } -} diff --git a/profiles/wcm_base/modules/contrib/user_import/user_import.import.inc b/profiles/wcm_base/modules/contrib/user_import/user_import.import.inc deleted file mode 100644 index e4f5113c..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/user_import.import.inc +++ /dev/null @@ -1,339 +0,0 @@ -<?php - -function _user_import_process($settings) { - // Load supported modules - user_import_load_supported(); - - $remaining_data = FALSE; - $line_max = variable_get('user_import_line_max', 1000); - $import_max = variable_get('user_import_max', 250); - $field_match = _user_import_unconcatenate_field_match($settings['field_match']); - $update_setting = _user_import_update_user_check($settings['options']['update_user']); - $update_setting_per_module = $settings['options']['update_user']; - $username_data = array(); - $username_order = array(); - $username_abbreviate = array(); - $first_line_skip = 0; - $delimiter = isset($settings['delimiter']) && !empty($settings['delimiter']) ? $settings['delimiter'] : ','; - - ini_set('auto_detect_line_endings', TRUE); - $handle = @fopen($settings['filepath'], "r"); - - // move pointer to where test/import last finished - if ($settings['pointer'] != 0) fseek($handle, $settings['pointer']); - - // start count of imports on this cron run - $processed_counter = 0; - - while ($data = fgetcsv($handle, $line_max, $delimiter)) { - - $errors = user_import_errors(FALSE, TRUE); - - // if importing, check we are not over max number of imports per cron - if ($settings['setting'] == 'import' && $processed_counter >= $import_max) { - $remaining_data = TRUE; - break; - } - - // don't process empty lines - $line_filled = (count($data) == 1 && drupal_strlen($data[0]) == 0) ? FALSE : TRUE; - - if ($line_filled) { - - // check if this is first line - if so should we skip? - if (!empty($settings['first_line_skip']) && $settings['processed'] == 0) { - // reset to false on second process - $first_line_skip = ($first_line_skip === 0) ? TRUE : FALSE; - } - - if (!$first_line_skip) { - - unset($errors, $fields); - reset($field_match); - $password = ''; - - // Process data cell. - foreach ($field_match as $column_id => $column_settings) { - - $type = $column_settings['type']; - $field_id = $column_settings['field_id']; - - // Skip if this is a field used as part of a username but - // not otherwise mapped for import. - if ($type != 'username_part') { - $fields[$type][$field_id] = module_invoke_all('user_import_data', $settings, $update_setting, $column_settings, $type, $field_id, $data, $column_id); - } - // Read in data if present for concatenating a user name. - if ($column_settings['username'] > 0) { - - $username_data[$column_id] = $data[$column_id]; - $username_order[$column_id] = $column_settings['username']; - $username_abbreviate[$column_id] = $column_settings['abbreviate']; - } - } - - $errors = user_import_errors(); - $account = array(); - $existing_account = FALSE; - $updated = FALSE; - - // if we update existing users matched by email (and therefore passed validation even if this email already exists) - // look for and use an existing account. - if ($update_setting && !empty($fields['user']['email'][0])) { - $existing_account = user_load_by_mail($fields['user']['email'][0]); - if ($existing_account) $account = (array)$existing_account; - } - - // if $account['uid'] is not empty then we can assume the account is being updated - $account_additions = module_invoke_all('user_import_pre_save', $settings, $account, $fields, $errors, $update_setting_per_module); - - foreach ($account_additions as $field_name => $value) { - $account[$field_name] = $value; - } - - if (empty($errors)) { - - if ($settings['setting'] == 'import') { - - if ($existing_account) { - $account = user_save($existing_account, $account); - $updated = TRUE; - } - else { - // Only set a user name if we are not updating an existing record. - $account['name'] = _user_import_create_username($username_order, $username_data, $username_abbreviate, $settings['username_space']); - $password = $account['pass']; - $account = user_save('', $account); - } - - module_invoke_all('user_import_after_save', $settings, $account, $password, $fields, $updated, $update_setting_per_module); - $processed_counter++; - } - - $settings['valid']++; - } - - // If first line is skipped it doesn't count as processed. - $settings['processed']++; - } - - $settings['pointer'] = ftell($handle); - - // save lines that have fatal errors - if (!empty($errors)) { - $account_email = isset($account['email']) ? $account['email'] : ''; - _user_import_errors_display_save($settings['import_id'], $fields, $account_email, $errors); - } - } - - $settings['setting'] = _user_import_save_progress($settings['setting'], $remaining_data, $settings['pointer'], $settings['processed'], $settings['valid'], $settings['import_id']); - } - - // Save progress. - $settings['setting'] = _user_import_save_progress($settings['setting'], $remaining_data, $settings['pointer'], $settings['processed'], $settings['valid'], $settings['import_id'], TRUE); - - if ($settings['setting'] == 'imported') { - module_invoke_all('user_import_imported', $settings['import_id'], $settings); - } - - fclose($handle); - - return $settings; -} - -// errors for user being imported -function user_import_errors($error = FALSE, $clear = FALSE) { - - static $errors = array(); - if ($clear) $errors = array(); - if ($error) $errors[] = $error; - - return $errors; -} - -function _user_import_create_username($order, $data, $abbreviate, $username_space) { - - $username = ''; - - if (is_array($order)) { - - asort($order); - //reset($order); - - - //while (list ($file_column, $sequence) = each ($order)) { - foreach ($order as $file_column => $sequence) { - - if (!empty($username) && !empty($username_space)) { - $username .= ' '; - } - - if ($abbreviate[$file_column] == 1) { - //$username .= trim(drupal_strtoupper(chr(ord($data[$file_column])))); - $first_character = trim($data[$file_column]); - $first_character = drupal_substr($first_character, 0, 1); - $username .= drupal_strtoupper($first_character); - } - else { - $username .= trim($data[$file_column]); - } - - } - } - - if (empty($username)) $username = _user_import_random_username(); - - $username = _user_import_sanitise_username($username); - $username = _user_import_unique_username($username, TRUE); - - return $username; -} - -/** - * conform to Drupal username rules - */ -function _user_import_sanitise_username($username) { - - // username cannot contain an illegal character - $username = preg_replace('/[^\x80-\xF7 [:alnum:]@_.-]/', '', $username); - $username = preg_replace( - '/[\x{80}-\x{A0}' . // Non-printable ISO-8859-1 + NBSP - '\x{AD}' . // Soft-hyphen - '\x{2000}-\x{200F}' . // Various space characters - '\x{2028}-\x{202F}' . // Bidirectional text overrides - '\x{205F}-\x{206F}' . // Various text hinting characters - '\x{FEFF}' . // Byte order mark - '\x{FF01}-\x{FF60}' . // Full-width latin - '\x{FFF9}-\x{FFFD}' . // Replacement characters - '\x{0}]/u', - '', $username); - - // username cannot contain multiple spaces in a row - $username = preg_replace('/[ ]+/', ' ', $username); - - // username must be less than 56 characters - $username = substr($username, 0, 56); - - // username cannot begin or end with a space - $username = trim($username); - - return $username; -} - -/** - * deal with duplicate usernames - */ -function _user_import_unique_username($username, $start = FALSE) { - - static $suffix = 1; - if ($start) $suffix = 1; - - if ($suffix < 2) { - $duplicate = db_query_range('SELECT uid from {users} where name = :name', 0, 1, array(':name' => $username))->fetchField(); - } - else { - $duplicate = db_query_range('SELECT uid from {users} where name = :name', 0, 1, array(':name' => "$username $suffix"))->fetchField(); - } - - // loop until name is valid - if (!empty($duplicate)) { - $suffix++; - - // If we loop to many times PHP will kill the script, - // for large user bases that might be a problem with popular names. - if ($suffix > 10) { - $suffix = $suffix * mt_rand(10, 99); - } - - _user_import_unique_username($username); - } - - // add number at end of username if it already exists - $username = ($suffix < 2) ? $username : "$username $suffix"; - - return $username; -} - -// Update settings for existing import -function _user_import_settings_update($pointer, $processed, $valid, $setting, $import_id) { - - if (empty($import_id)) return; - - db_update('user_import') - ->fields(array( - 'pointer' => $pointer, - 'processed' => $processed, - 'valid' => $valid, - 'setting' => $setting - )) - ->condition('import_id', $import_id) - ->execute(); -} - -function _user_import_random_username() { - $username = ''; - $vowels = 'aoueiy'; - $consonants = 'bcdfghjklmnpqrstvwxz'; - $length = 8; - - mt_srand((double)microtime() * 10000000); - $next_vowel = 0; - - for ($count = 0; $count <= $length; $count++) { - - if ($next_vowel) { - $rand = mt_rand(0, 5); - $username .= $vowels{$rand}; - $next_vowel = 0; - - } - else { - $rand = mt_rand(0, 19); - $username .= $consonants{$rand}; - $next_vowel = 1; - } - } - - return $username; -} - -// check if any updates are to be made -function _user_import_update_user_check($settings) { - - foreach ($settings as $setting) { - if ($setting != UPDATE_NONE) return TRUE; - } - - return FALSE; -} - -function _user_import_errors_display_save($import_id, $data, $email, $errors) { - - $data['email'] = $email; - - $id = db_insert('user_import_errors') - ->fields(array( - 'import_id' => $import_id, - 'data' => serialize($data), - 'errors' => serialize($errors), - )) - ->execute(); - - return; -} - -/** - * Save progress status and counter of the import. - */ -function _user_import_save_progress($status, $remaining_data, $pointer, $processed, $valid, $import_id, $status_check = FALSE) { - - if ($status_check) { - if ($status == 'import' && !$remaining_data) $status = 'imported'; - if ($status == 'test') $status = 'tested'; - } - - _user_import_settings_update($pointer, $processed, $valid, $status, $import_id); - - return $status; -} - diff --git a/profiles/wcm_base/modules/contrib/user_import/user_import.info b/profiles/wcm_base/modules/contrib/user_import/user_import.info deleted file mode 100644 index 99ac2a2d..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/user_import.info +++ /dev/null @@ -1,19 +0,0 @@ -name = User Import -description = "Import users into Drupal from a CSV file." -core = 7.x -package = Deployment -files[] = user_import.module -files[] = user_import.install -files[] = user_import.admin.inc -files[] = user_import.import.inc -files[] = user_import.test - - - - -; Information added by Drupal.org packaging script on 2014-03-09 -version = "7.x-2.2" -core = "7.x" -project = "user_import" -datestamp = "1394331207" - diff --git a/profiles/wcm_base/modules/contrib/user_import/user_import.install b/profiles/wcm_base/modules/contrib/user_import/user_import.install deleted file mode 100644 index de895fb6..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/user_import.install +++ /dev/null @@ -1,263 +0,0 @@ -<?php - -/** - * @file - * Import and update users from a comma separated file (csv). - */ - -/** - * Implementation of hook_schema(). - */ -function user_import_schema() { - $schema['user_import'] = array( - 'description' => t("Settings for each import, and import setting templates."), - 'fields' => array( - 'import_id' => array( - 'description' => t("ID key of import or template."), - 'type' => 'serial', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'disp-width' => '10' - ), - 'name' => array( - 'description' => t("Label of import template, only used if this is an import template."), - 'type' => 'varchar', - 'length' => '25', - 'not null' => TRUE, - 'default' => '' - ), - 'auto_import_directory' => array( - 'description' => t("Name of directory associated with an import template."), - 'type' => 'varchar', - 'length' => '255', - 'not null' => TRUE, - 'default' => '' - ), - 'filename' => array( - 'description' => t("Name of file being used as source of data for import."), - 'type' => 'varchar', - 'length' => '50', - 'not null' => TRUE, - 'default' => '' - ), - 'oldfilename' => array( - 'description' => t("Original name of file being used as source of data for import."), - 'type' => 'varchar', - 'length' => '50', - 'not null' => TRUE, - 'default' => '' - ), - 'filepath' => array( - 'description' => t("Path to file being used as source of data for import."), - 'type' => 'text', - 'size' => 'small', - 'not null' => TRUE - ), - 'started' => array( - 'description' => t("Datestamp of when import was started."), - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'disp-width' => '11' - ), - 'pointer' => array( - 'description' => t("Pointer to where test/import last finished."), - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'disp-width' => '10' - ), - 'processed' => array( - 'description' => t("Number of users processed by import."), - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'disp-width' => '10' - ), - 'valid' => array( - 'description' => t("Number of users processed without errors."), - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'disp-width' => '10' - ), - 'field_match' => array( - 'description' => t("Settings for how data matches to Drupal fields."), - 'type' => 'text', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE - ), - 'roles' => array( - 'description' => t("Roles to give imported users."), - 'type' => 'text', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE - ), - 'options' => array( - 'description' => t("Store of all other options for import. Most of the other settings in this table will be moved into here in future."), - 'type' => 'text', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE - ), - 'setting' => array( - 'description' => t("Status of import, or whether it is an import template."), - 'type' => 'varchar', - 'length' => '10', - 'not null' => TRUE, - 'default' => '' - ) - ), - 'primary key' => array('import_id'), - ); - - $schema['user_import_errors'] = array( - 'description' => t("Record of errors encountered during an import."), - 'fields' => array( - 'import_id' => array( - 'description' => t("ID key of import or template."), - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'disp-width' => '10' - ), - 'data' => array( - 'description' => t("Data (matched to fields) for user that failed to import due to error."), - 'type' => 'text', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE - ), - 'errors' => array( - 'description' => t("Error(s) encountered for user that failed to import."), - 'type' => 'text', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE - ) - ), - 'indexes' => array( - 'import_id' => array('import_id') - ), - ); - - return $schema; -} - -function user_import_update_1() { - $ret = array(); - _system_update_utf8(array('user_import', 'user_import_errors')); - - return $ret; -} - -function user_import_update_2() { - $ret = array(); - db_add_column($ret, 'user_import', 'options', 'longtext'); - - return $ret; -} - -function user_import_update_3() { - $ret = array(); - db_drop_primary_key($ret, 'user_import'); - db_change_field($ret, 'user_import', 'iid', 'import_id', array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, 'disp-width' => '10'), array('primary key' => array('import_id'))); - db_change_field($ret, 'user_import', 'first_line', 'first_line_skip', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, 'disp-width' => '10'), array('primary key' => array('import_id'))); - db_drop_index($ret, 'user_import_errors', 'import_id'); - db_change_field($ret, 'user_import_errors', 'iid', 'import_id', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, 'disp-width' => '10')); - db_add_index($ret, 'user_import_errors', 'import_id', array('import_id')); - - return $ret; -} - -function user_import_update_4() { - $ret = array(); - db_drop_index($ret, 'user_import_errors', 'import_id'); - db_change_field($ret, 'user_import_errors', 'error', 'errors', array('type' => 'text', 'size' => 'big', 'not null' => TRUE, 'serialize' => TRUE)); - db_add_index($ret, 'user_import_errors', 'import_id', array('import_id')); - - return $ret; -} - -function user_import_update_5() { - $ret = array(); - db_drop_index($ret, 'user_import_errors', 'import_id'); - db_change_field($ret, 'user_import_errors', 'errors', 'errors', array('type' => 'text', 'size' => 'big', 'not null' => TRUE, 'serialize' => TRUE)); - db_add_index($ret, 'user_import_errors', 'import_id', array('import_id')); - - return $ret; -} - -function user_import_update_6001() { - // Rebuild schema cache - drupal_get_schema('user_import', TRUE); - - return array(); -} - -/** - * Move settings into the 'options' column. - */ -function user_import_update_6002() { - $ret = array(); - $result = db_query("SELECT * FROM {user_import}"); - - // Update each import. - while ($import = db_fetch_array($result)) { - $options = unserialize($import['options']); - $options['first_line_skip'] = $import['first_line_skip']; - $options['contact'] = $import['contact']; - $options['username_space'] = $import['username_space']; - $options['send_email'] = $import['send_email']; - //Avoid using update_sql() as it has issues with serialized data. - db_query("UPDATE {user_import} SET options = '%s' WHERE import_id = %d", serialize($options), $import['import_id']); - } - - $ret[] = update_sql('ALTER TABLE {user_import} DROP COLUMN first_line_skip'); - $ret[] = update_sql('ALTER TABLE {user_import} DROP COLUMN contact'); - $ret[] = update_sql('ALTER TABLE {user_import} DROP COLUMN username_space'); - $ret[] = update_sql('ALTER TABLE {user_import} DROP COLUMN send_email'); - - return $ret; -} - -/** - * Change the Roles column to LOMGTEXT. - */ -function user_import_update_6003() { - $ret = array(); - db_change_field($ret, 'user_import', 'roles', 'roles', array('type' => 'text', 'size' => 'big', 'not null' => TRUE, 'serialize' => TRUE)); - - return $ret; -} - -/** - * Add database field to store the name of a directory associated with an import template. - * - **/ -function user_import_update_7200(&$sandbox) { - $field = array( - 'description' => t("Name of directory associated with an import template."), - 'type' => 'varchar', - 'length' => '255', - 'not null' => TRUE, - 'default' => '', - ); - - db_add_field('user_import', 'auto_import_directory', $field); -} - - -/** - * Implementation of hook_uninstall(). - */ -function user_import_uninstall() { - variable_del('user_import_settings'); - variable_del('user_import_max'); - variable_del('user_import_line_max'); - variable_del('user_export_checked_usernames'); - variable_del('user_import_profile_date_format'); -} - diff --git a/profiles/wcm_base/modules/contrib/user_import/user_import.module b/profiles/wcm_base/modules/contrib/user_import/user_import.module deleted file mode 100644 index 4160a58d..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/user_import.module +++ /dev/null @@ -1,817 +0,0 @@ -<?php - -/** - * @file - * Import or update users with data from a comma separated file (csv). - */ - -// Update options for existing users -define ('UPDATE_NONE', 0); -define ('UPDATE_REPLACE', 1); -define ('UPDATE_ADD', 2); - -/** - * - - - - - - - - HOOKS - - - - - - - - - */ - -/** - * Implementation of hook_theme(). - */ -function user_import_theme() { - return array( - 'user_import_list' => array( - 'variables' => array(), - ), - 'user_import_edit' => array( - 'render element' => 'form', - ), - 'user_import_errors_display' => array( - 'variables' => array('settings' => NULL), - ), - 'user_import_username_errors' => array( - 'variables' => array('errors' => NULL), - ), - ); -} - -/** - * Implementation of hook_help(). - */ -// function user_import_help($path, $arg) { -// switch ($path) { -// case 'admin/people/user_import': -// return t("Import or update users from a comma separated file (csv). Click 'Import' to start a new import."); -// } -// } - -/** - * Implementation of hook_perm(). - */ -function user_import_permission() { - - return array( - 'import users' => array( - 'title' => t('Import users'), - 'description' => t('Import users.'), - ), - ); -} - -/** - * Implementation of hook_menu(). - */ -function user_import_menu() { - $items['admin/people/user_import'] = array( - 'title' => 'Import', - 'description' => 'Import or update users from a comma separated file (csv).', - 'page callback' => 'user_import_list', - 'access arguments' => array('import users'), - 'type' => MENU_LOCAL_TASK, - 'file' => 'user_import.admin.inc', - ); - $items['admin/people/user_import/list'] = array( - 'title' => 'List Imports', - 'access arguments' => array('import users'), - 'weight' => -10, - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'file' => 'user_import.admin.inc', - ); - $items['admin/people/user_import/add'] = array( - 'title' => 'New Import', - 'page callback' => 'user_import_preferences', - 'access arguments' => array('import users'), - 'weight' => -5, - 'type' => MENU_LOCAL_TASK, - 'file' => 'user_import.admin.inc', - ); - $items['admin/people/user_import/continue'] = array( - 'title' => 'Continue', - 'page callback' => 'user_import_continue', - 'access arguments' => array('import users'), - 'type' => MENU_CALLBACK, - 'file' => 'user_import.admin.inc', - ); - $items['admin/people/user_import/import'] = array( - 'title' => 'Import', - 'page callback' => 'user_import_import', - 'access arguments' => array('import users'), - 'type' => MENU_CALLBACK, - 'file' => 'user_import.admin.inc', - ); - $items['admin/people/user_import/delete'] = array( - 'title' => 'Delete Import', - 'page callback' => 'user_import_delete', - 'access arguments' => array('import users'), - 'type' => MENU_CALLBACK, - 'file' => 'user_import.admin.inc', - ); - $items['admin/people/user_import/configure'] = array( - 'title' => 'Configure', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('user_import_configure_form'), - 'access arguments' => array('import users'), - 'type' => MENU_LOCAL_TASK, - 'file' => 'user_import.admin.inc', - ); - $items['user_import/delete'] = array( - 'title' => 'Remove Info', - 'page callback' => 'user_import_limited_delete', - 'type' => MENU_CALLBACK, - 'access arguments' => array('limited user import'), - ); - $items['user_import/errors'] = array( - 'title' => 'Import Errors', - 'page callback' => 'user_import_limited_errors', - 'type' => MENU_CALLBACK, - 'access arguments' => array('limited user import'), - ); - - return $items; -} - - -/** - * Implementation of hook_cron(). - */ -function user_import_cron() { - module_load_include('inc', 'user_import', 'user_import.import'); - - // Continue any on-going imports. - user_import_continue_imports(); - - // Check for new imports. - user_import_trigger_imports(); -} - -/** - * Continue any on-going imports, durring a cron run. - * - **/ -function user_import_continue_imports() { - $imports = _user_import_settings_select(); - - if ($imports) { - - foreach ($imports as $import) { - if ($import['setting'] == 'test' || $import['setting'] == 'import') { - _user_import_process($import); - } - } - } -} - -/** - * Trigger imports if new files are found durring a cron run. - */ -function user_import_trigger_imports() { - - $auto_imports_enabled = variable_get('user_import_auto_imports_enabled', FALSE); - - if (empty($auto_imports_enabled)) { - return; - } - - // Load import functions. - module_load_include('inc', 'user_import', 'user_import.admin'); - - // Get list of templates to check. - $imports = db_query("SELECT * FROM {user_import} WHERE auto_import_directory != '' AND setting = 'template'"); - - foreach ($imports as $import) { - // Check for file in the uploads directory of this template. - $directory = 'private://user_import/uploads/' . $import->auto_import_directory; - $files = file_scan_directory($directory, '/.*$/'); - - foreach ($files as $import_file) { - // Move file to processing directory. - $filename_new = $import_file->filename . '-' . rand(1000000, 2000000); - $import_file_new = file_unmanaged_move($import_file->uri, 'private://user_import/processing/' . $filename_new); - - // Create import. - - // Get template. - $settings = _user_import_settings_select($import->import_id); - $import_id = ''; - $name = ''; - $pointer = 0; - $processed = 0; - $valid = 0; - $field_match = isset($settings['field_match']) ? serialize($settings['field_match']) : ''; - $roles = isset($settings['roles']) ? serialize($settings['roles']) : ''; - $options = isset($settings['options']) ? serialize($settings['options']) : ''; - $setting = 'import'; - - $file->filename = $filename_new; - $file->oldfilename = $import_file->filename; - $file->filepath = 'private://user_import/processing/' . $filename_new; - - $import_id = user_import_import_set($name, $file, $pointer, $processed, $valid, $field_match, $roles, $options, $setting, $import_id); - $settings = _user_import_settings_select($import_id); - _user_import_process($settings); - - } - } - - - return; - - - - // Get template. - $settings = _user_import_settings_select(2); - - foreach ($files as $filename) { - - if ($filename == 'sample.txt') { - - - // Check if it's used for an import already. - $imported = db_query('SELECT import_id FROM {user_import} WHERE filename = :filename', array(':filename' => $filename))->fetchField(); - - if (!$imported) { - - $import_id = ''; - $name = ''; - $pointer = 0; - $processed = 0; - $valid = 0; - - - $field_match = isset($settings['field_match']) ? serialize($settings['field_match']) : ''; - $roles = isset($settings['roles']) ? serialize($settings['roles']) : ''; - $options = isset($settings['options']) ? serialize($settings['options']) : ''; - $setting = 'import'; - - $file->filename = $filename; - $file->oldfilename = $filename; - $file->filepath = drupal_get_path('module', 'user_import') . '/' . $filename; - - $import_id = user_import_import_set($name, $file, $pointer, $processed, $valid, $field_match, $roles, $options, $setting, $import_id); - $settings = _user_import_settings_select($import_id); - _user_import_process($settings); - - } - - } - - } - - return; -} - - -// - - - - - - - - FORMS - - - - - - - - - -/** - * Saves options on content type configuration form - * @todo check if this is cruft - * @todo check $form['type'] - */ -function user_import_content_type_submit($form, &$form_state) { - // user import template for Organic Groups content type - $templates = variable_get('user_import_og_template', array()); - $templates[$form['type']] = $form_state['values']['user_import_og']; - variable_set('user_import_og_template', $templates); -} - -// - - - - - - - - PAGES - - - - - - - - - -function user_import_limited_errors($import_id = NULL, $template_id = NULL) { - - if (empty($import_id) || !is_numeric($import_id)) { - drupal_goto('user_import/' . $template_id); - } - - $pager_id = 1; - $max = 25; - $import = _user_import_settings_select($import_id); - $output = ''; - - $total = db_query('SELECT count(data) FROM {user_import_errors} WHERE :import_id = %d', array(':import_id' => $import['import_id']))->fetchField(); - - if (empty($total)) { - - $output .= theme('There were no import errors'); - } - else { - - $header = array( - array('data' => t('ID'), 'field' => 'import_id', 'sort' => 'desc'), - array('data' => t('Data'), 'field' => 'data'), - array('data' => t('Errors'), 'field' => 'errors') - ); - - $query = db_select('user_import_errors') - ->extend('PagerDefault') - ->extend('TableSort'); - - $query - ->condition('import_id', $import['import_id']) - ->limit($max) - ->orderByHeader($header); - - $result = $query->execute(); - - foreach ($result as $line) { - $file_lines[] = array('data' => unserialize($line->data), 'errors' => unserialize($line->errors)); - } - - $output .= theme('user_import_errors_display', array('import' => $import, 'file_lines' => $file_lines, 'total' => $total)); - $output .= theme('pager', array('max' => $max, 'pager_id' => $pager_id)); - } - - $output .= l(t('Return'), "user_import/$template_id/$import_id"); - - return $output; -} - - -/** - * @param null $import_id - * @param null $template_id - */ -function user_import_limited_delete($import_id = NULL, $template_id = NULL) { - user_import_delete($import_id, "user_import/$template_id"); -} - -// - - - - - - - - THEMES - - - - - - - - - - -/** - * @return string - */ -function theme_user_import_list() { - $output = ''; - $imports = _user_import_settings_select(); - - if (!$imports) return ' '; - - foreach ($imports as $import) { - - // header labels - $import_label = ($import['setting'] == 'tested' || $import['setting'] == 'test') ? t('importable') : t('imported'); - $header = array(t('file'), t('started'), t('processed'), $import_label, t('errors'), t('status')); - - // info row - $errors = db_query('SELECT COUNT(import_id) FROM {user_import_errors} WHERE import_id = :import_id', array(':import_id' => $import['import_id']))->fetchField(); - $errors_link = ($errors == 0) ? '0' : l($errors, 'admin/people/user_import/errors/' . $import['import_id']); - - $rows[0] = array( - check_plain($import['oldfilename']), - format_date($import['started'], 'small'), - array("data" => $import['processed'], "align" => 'center'), - array("data" => $import['valid'], "align" => 'center'), - array("data" => $errors_link, "align" => 'center'), - $import['setting'], - ); - - $output .= theme('table', array('header' => $header, 'rows' => $rows)); - - // action buttons - $delete_link = l(t('Delete'), 'admin/people/user_import/delete/' . $import['import_id']); - $continue_link = l(t('Continue Processing'), 'admin/people/user_import/continue/' . $import['import_id']); - $import_link = l(t('Import'), 'admin/people/user_import/import/' . $import['import_id']); - - $output .= $delete_link; - if ($import['setting'] == 'tested' || $import['setting'] == 'test') $output .= ' | ' . $import_link; - if ($import['setting'] == 'test' || $import['setting'] == 'import') $output .= ' | ' . $continue_link; - } - - return $output; -} - -function theme_user_import_edit($variables) { - - $output = ''; - $rows = array(); - $form = $variables['form']; - $header = array(t('CSV column'), t('Drupal fields'), t('Username'), t('Abbreviate')); - - foreach (element_children($form['field_match']) as $key) { - - $rows[] = array( - drupal_render($form['field_match'][$key]['csv']), - drupal_render($form['field_match'][$key]['field_match']), - drupal_render($form['field_match'][$key]['username']), - drupal_render($form['field_match'][$key]['abbreviate']), - ); - } - - $form['field_match']['#value'] = theme('table', array('header' => $header, 'rows' => $rows)); - - if (isset($form['remove'])) { - $output .= drupal_render($form['remove']); - } - - if (isset($form['options'])) { - $output .= drupal_render($form['options']); - } - - if (isset($form['field_match'])) { - $output .= drupal_render($form['field_match']); - } - - $output .= drupal_render_children($form); - - return $output; -} - -function theme_user_import_errors_display($settings) { - - $output = ''; - $header[0] = t('Email Address'); - $data = $settings['file_lines']; - $total = $settings['total']; - $oldfilename = $settings['import']['oldfilename']; - $field_match = $settings['import']['field_match']; - $error_count = 0; - $field_match = _user_import_unconcatenate_field_match($field_match); - - foreach ($data as $data_row) { - - $row = array(); - - foreach ($data_row['data'] as $type => $fields) { - - if (!empty($fields)) { - - foreach ($fields as $field_id => $field_data) { - - foreach ($field_match as $column_info) { - - if ($column_info['type'] == $type && $column_info['field_id'] == $field_id) { - - if (!empty($column_info['username'])) { - $header[$column_info['username']] = t('Name %sort', array('%sort' => $column_info['username'])); - $row[$column_info['username']] = array("data" => $field_data[0], "align" => "left"); - } - - if ($column_info['field_id'] == 'email') { - $row[0] = array("data" => $field_data[0], "align" => "left"); - } - } - } - - } - } - } - - ksort($row); - $row[] = implode('<br />', $data_row['errors']); - $rows[] = $row; - } - - $output .= '<p>' . t('<strong>CSV File:</strong> %file', array('%file' => $oldfilename)) . '<br />'; - $output .= t('<strong>Errors:</strong> !total', array('!total' => $total)) . '</p>'; - - $header['errors'] = t('Errors'); - - // Output of table with the paging - $output .= theme('table', - array( - "header" => $header, - "rows" => $rows, - "attributes" => array(), - "sticky" => FALSE, // Table header will be sticky - "caption" => '', - "colgroups" => array(), - "empty" => t("There are no errors.") // The message to be displayed if table is empty - ) - ) . theme("pager"); - - //$output .= theme('table', array('header' => $header, 'rows' => $rows)); - return $output; -} - -function theme_user_import_username_errors($errors) { - - if (empty($errors)) { - $output = '<p><strong>' . t('All usernames are OK.') . '</strong></p>'; - } - else { - $header = array(t('User ID'), t('Email'), t('Username'), t('Error')); - $output = theme('table', array('header' => $header, 'errors' => $errors)); - } - - return $output; -} - -// - - - - - - - - MISC - - - - - - - - - -function _user_import_settings_save($settings) { - // Database field defaults. - $database_fields = array('import_id' => NULL, - 'name' => '', - 'auto_import_directory' => '', - 'filename' => '', - 'oldfilename' => '', - 'filepath' => '', - 'started' => 0, - 'pointer' => 0, - 'processed' => 0, - 'valid' => 0, - 'field_match' => array(), - 'roles' => '', - 'options' => array(), - 'setting' => '', - ); - - // Form elements we never want to save in the options column. - $form_variables = array('form_id', 'form_token', 'form_build_id', 'cancel', 'import', 'submit', 'op', 0, 'return_path'); - $form_variables = array_flip($form_variables); - - // Remove settings we don't need in the options column. - $options = array_diff_key($settings, $database_fields); - $options = array_diff_key($options, $form_variables); - - // Optimise Email Domain option if it's set. - if (!empty($options['email_domain'])) { - $options['email_domain'] = trim($options['email_domain']); - - if (substr($options['email_domain'], 0, 1) != '@') { - $options['email_domain'] = '@'. $options['email_domain']; - } - } - - // Set defaults for options. - foreach ($options as $key => $value) { - $settings['options'][$key] = isset($settings[$key]) ? $settings[$key] : ''; - } - - // Optimise Email Domain option if it's set. - if (!empty($settings['options']['email_domain'])) { - $settings['options']['email_domain'] = trim($settings['options']['email_domain']); - - if (substr($settings['options']['email_domain'], 0, 1) != '@') { - $settings['options']['email_domain'] = '@'. $settings['options']['email_domain']; - } - } - - // Set defaults for fields. - foreach ($database_fields as $key => $value) { - $settings[$key] = isset($settings[$key]) ? $settings[$key] : $value; - } - - // Set default values. - $import_id = isset($settings['import_id']) ? $settings['import_id'] : ''; - $name = isset($settings['name']) ? trim($settings['name']) : ''; - $pointer = isset($settings['pointer']) ? $settings['pointer'] : 0; - $processed = isset($settings['processed']) ? $settings['processed'] : 0; - $valid = isset($settings['valid']) ? $settings['valid'] : 0; - $field_match = isset($settings['field_match']) ? serialize($settings['field_match']) : ''; - $roles = isset($settings['roles']) ? serialize($settings['roles']) : ''; - $options = isset($settings['options']) ? serialize($settings['options']) : ''; - $setting = isset($settings['setting']) ? $settings['setting'] : ''; - - // Only set an auto import directory if saving a template. - if (isset($settings['auto_import_directory']) && $settings['setting'] == 'template') { - $auto_import_directory = $settings['auto_import_directory']; - } - else { - $auto_import_directory = ''; - } - - $file->filename = isset($settings['filename']) ? $settings['filename'] : ''; - $file->oldfilename = isset($settings['oldfilename']) ? $settings['oldfilename'] : ''; - $file->filepath = isset($settings['filepath']) ? $settings['filepath'] : ''; - - $settings['import_id'] = user_import_import_set($name, $file, $pointer, $processed, $valid, $field_match, $roles, $options, $setting, $auto_import_directory, $import_id); - - return $settings; -} - - -function user_import_import_set($name = '', $file = '', $pointer = 0, $processed = 0, $valid = 0, $field_match = '', $roles = '', $options = '', $setting = '', $auto_import_directory = '', $import_id = '') { - // Update settings for existing import. - if (!empty($import_id)) { - - db_update('user_import') - ->fields(array( - 'name' => $name, - 'auto_import_directory' => $auto_import_directory, - 'filename' => $file->filename, - 'oldfilename' => $file->oldfilename, - 'filepath' => $file->filepath, - 'pointer' => $pointer, - 'processed' => $processed, - 'valid' => $valid, - 'field_match' => $field_match, - 'roles' => $roles, - 'options' => $options, - 'setting' => $setting - )) - ->condition('import_id', $import_id) - ->execute(); - } - else { - - $import_id = db_insert('user_import') - ->fields(array( - 'name' => $name, - 'auto_import_directory' => $auto_import_directory, - 'filename' => $file->filename, - 'oldfilename' => $file->oldfilename, - 'filepath' => $file->filepath, - 'started' => time(), - 'pointer' => $pointer, - 'processed' => $processed, - 'valid' => $valid, - 'field_match' => $field_match, - 'roles' => $roles, - 'options' => $options, - 'setting' => $setting - )) - ->execute(); - } - - return $import_id; -} - -/** - * Return either a single import setting, or all template, or all non-template settings. - */ -function _user_import_settings_select($import_id = NULL, $template = FALSE) { - $import = array(); - - if (!empty($import_id) && !is_numeric($import_id)) return; - - if (!empty($import_id)) { - $sql = 'SELECT * FROM {user_import} WHERE import_id = :import_id'; - if ($template) $sql .= " AND setting = 'template'"; - - - $import = (array)db_query_range($sql, 0, 1, array(':import_id' => $import_id))->fetchObject(); - - if (empty($import)) return FALSE; - - $import['field_match'] = unserialize($import['field_match']); - $import['roles'] = unserialize($import['roles']); - $import['options'] = unserialize($import['options']); - - if (is_array($import['options'])) { - foreach ($import['options'] as $key => $value) { - $import[$key] = $value; - } - } - } - else { - - $query = ($template) ? "SELECT * FROM {user_import} WHERE setting = 'template'" : "SELECT * FROM {user_import} WHERE setting <> 'template' ORDER BY started DESC"; - $result = db_query($query); - - foreach ($result as $row_data) { - $row = (array)$row_data; - $row['field_match'] = unserialize($row['field_match']); - $row['roles'] = unserialize($row['roles']); - $row['options'] = unserialize($row['options']); - - foreach ($row['options'] as $key => $value) { - $row[$key] = $value; - } - - $import[] = $row; - } - } - - return $import; -} - -function _user_import_settings_deletion($import_id) { - - $sql = 'SELECT auto_import_directory FROM {user_import} WHERE import_id = :import_id'; - $auto_import_directory = db_query_range($sql, 0, 1, array(':import_id' => $import_id))->fetchField(); - - if (!empty($auto_import_directory)) { - $deleted = file_unmanaged_delete_recursive('private://user_import/uploads/' . $auto_import_directory); - - if ($deleted) { - watchdog('Usr Import', t("Directory '%directory' has been deleted."), array('%directory' => $auto_import_directory)); - } - } - - db_delete('user_import') - ->condition('import_id', $import_id) - ->execute(); - - db_delete('user_import_errors') - ->condition('import_id', $import_id) - ->execute(); - - return; -} - -// Used by user_import_og.module -function user_import_profile_load($user) { - - $result = db_query('SELECT f.name, f.type, f.fid, v.value FROM {profile_field} f INNER JOIN {profile_values} v ON f.fid = v.fid WHERE uid = %d', $user->uid); - - foreach ($result as $field) { - - if (empty($profile[$field->fid])) { - $profile[$field->fid] = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value; - } - - } - - return $profile; -} - -function _user_import_unconcatenate_field_match($settings) { - - $settings_updated = array(); - - foreach ($settings as $column_id => $values) { - - if (!empty($values['field_match']) || !empty($values['username'])) { - // If we have a username but no field_match, set a special type. - // This allows us to skip saving the field but still use it in - // concatenating a username value. - if (empty($values['field_match'])) { - $values['type'] = 'username_part'; - $values['field_id'] = 'username_part_' . $column_id; - } - else { - $key_parts = explode('-', $values['field_match']); - $values['type'] = array_shift($key_parts); - $values['field_id'] = implode('-', $key_parts); - } - unset($values['field_match']); - $settings_updated[$column_id] = $values; - } - } - - return $settings_updated; -} - -/** - * Loads the hooks for the supported modules. - */ -function user_import_load_supported() { - static $loaded = FALSE; - - if (!$loaded) { - $path = drupal_get_path('module', 'user_import') . '/supported'; - $files = drupal_system_listing("/\.inc$/", $path, 'name', 0); - - foreach ($files as $module_name => $file) { - if (module_exists($module_name)) { - include_once($file->uri); - } - } - - $loaded = TRUE; - } -} - -/** - * Implementation of hook_simpletest(). - */ -function user_import_simpletest() { - $module_name = 'user_import'; - $dir = drupal_get_path('module', $module_name) . '/tests'; - $tests = file_scan_directory($dir, '\.test$'); - - return array_keys($tests); -} - -/** - * @param $filepath - * @param $filename - * @param $old_filename - * @param $ftp - * @param bool $message - */ -function _user_import_file_deletion($filepath, $filename, $old_filename, $ftp, $message = TRUE) { - - $path_parts = explode(':', $filepath); - - if ($path_parts[0] == 'private') { - // Delete files in the private files directory that have been uploaded by sftp/ftp. - file_unmanaged_delete($filepath); - } - else { - // Delete files uploaded through browser. - $file = new stdClass(); - $file->uri = $filepath; - $file->filename = $filename; - $file->fid = db_query("SELECT fid FROM {file_managed} WHERE uri = :filepath", array(':filepath' => $filepath))->fetchField(); - ; - $removed = file_delete($file); - } - - if (!$message) return; - - if (empty($removed)) { - drupal_set_message(t("File error: file '%old_filename' (%filename) could not be deleted.", array('%old_filename' => $oldfilename, '%filename' => $filename)), 'error'); - } - else { - drupal_set_message(t("File '%old_filename' was deleted.", array('%old_filename' => $old_filename))); - } - - return; -} - - - - - diff --git a/profiles/wcm_base/modules/contrib/user_import/user_import.test b/profiles/wcm_base/modules/contrib/user_import/user_import.test deleted file mode 100644 index 51efee33..00000000 --- a/profiles/wcm_base/modules/contrib/user_import/user_import.test +++ /dev/null @@ -1,586 +0,0 @@ -<?php - -/** - * User Import module base test class. - */ -class UserImportWebTestCase extends DrupalWebTestCase { - protected $admin_user; - protected $user_importer; - - /** - * Select CSV file (the included example file) - */ - function settingsFileSelect() { - $edit = array('file_ftp' => 1); - $this->drupalPost('admin/people/user_import/add', $edit, t('Next')); - - /* Check file was selected */ - $this->assertText(t('Use Different CSV File'), '[assert] File was selected'); - } - - function settingsEmailMatch(&$edit) { - $edit['field_match[5][field_match]'] = 'user-email'; - } - - function settingsIgnoreFirstLine(&$edit) { - $edit['first_line_skip'] = 1; - } - - function checkAccountsExist($list_failures = FALSE) { - $failures_list = ''; - $users_email = $this->usersList(); - $failed = array(); - - foreach ($users_email as $mail) { - $user = user_load_by_mail($mail); - if (empty($user)) $failed[] = $mail; - } - - if (!empty($failed) && $list_failures) { - $failures_list = t('. Failed accounts: %failures', array('%failures' => implode(', ', $failed))); - } - - $this->assertTrue(empty($failed), t('Accounts created for users imported') . $failures_list); - } - - /** - * List of users (email addresses) being imported - * To Do - Generate this dynamically, bearing in mind it could be used for stress testing - */ - function usersList() { - return array( - 'john@example.com', - 'mandy@example.com', - 'charles@example.com', - 'sarah@example.com', - 'sarah_smith@example.com', - 'helen@example.com', - 'claire@example.com', - 'victoria@example.com', - 'james@example.com', - 'anna@example.com', - 'tino@example.com', - 'sofia@example.com', - 'steve@example.com', - 'lucy@example.com', - 'angie@example.com', - 'carmen@example.com', - 'paul@example.com', - 'jason@example.com', - 'mike@example.com', - 'mary@example.com', - 'simon@example.com', - 'kieran@example.com', - 'arthur@example.com', - 'gwen@example.com', - 'chester@example.com', - 'dorothy@example.com', - 'cameron@example.com', - 'trisha@example.com', - 'david@example.com', - 'peter@example.com', - 'saul@example.com', - 'noel@example.com', - 'matt@example.com', - 'aston@example.com', - 'mille@example.com', - 'ernest@example.com', - ); - } - - /** - * Store import ID - * - set on import settings page, retrieve on later tasks - */ - function importID($url = NULL) { - static $import_id = 0; - - if (empty($import_id) && !empty($url)) { - $args = explode('/', $url); - $import_id = $args[7]; - } - - return $import_id; - } - - /** - * SimpleTest core method: code run after each and every test method. - */ - function tearDown() { - - // delete accounts of users imported - $users_email = $this->usersList(); - - foreach ($users_email as $mail) { - $account = user_load_by_mail($mail); - if (!empty($account)) user_delete($account->uid); - } - - // delete the import - $import_id = $this->importID(); - $this->assertTrue(!empty($import_id), t('Import ID: !id', array('!id' => $import_id))); - _user_import_settings_deletion($import_id); - - // Always call the tearDown() function from the parent class. - parent::tearDown(); - } - -} - - -/** - * User Import module base test class. - */ -class UserImportBasicsTestCase extends UserImportWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Import Users (Basics)', - 'description' => 'Import users from a CSV file, test basic functions.', - 'group' => 'User Import', - ); - } - - function setUp() { - parent::setUp(array('user_import')); - $this->admin_user = $this->drupalCreateUser(array('administer users', 'access administration pages', 'access overlay')); - $this->user_importer = $this->drupalCreateUser(array('import users')); - } - - /** - * User with right permissions creates import (with new settings) - * - test core functions - */ - function testCreateImport() { - - // Prepare a user to do testing - $this->drupalLogin($this->user_importer); - - // Select CSV file (the included example file) - $this->settingsFileSelect(); - - // import settings - $this->importID($this->getUrl()); // store import ID for later - $setting_edit = array(); - $this->settingsEmailMatch($settings); - $this->settingsIgnoreFirstLine($settings); - $this->drupalPost($this->getUrl(), $settings, 'Import'); - - // check if users have been imported - $this->checkAccountsExist(TRUE); - } - -} - - -/** - * Test import of user data into Profile module - */ -//class UserImportNodeprofileTestCase extends UserImportWebTestCase { -// -// public static function getInfo() { -// return array( -// 'name' => 'Import Users (Nodeprofile)', -// 'description' => 'Test import of user data into Nodeprofile module.', -// 'group' => 'User Import', -// ); -// } -// -// function setUp() { -// parent::setUp('content', 'number', 'optionwidgets', 'text', 'link', 'date_api', 'date', 'node_import', 'content_profile', 'user_import'); -// $this->admin_user = $this->drupalCreateUser(array('administer users', 'administer permissions', 'access administration pages', 'administer site configuration', 'administer content types', 'administer taxonomy')); -// $this->user_importer = $this->drupalCreateUser(array('import users')); -// } -// -// /** -// * User with right permissions creates import (with new settings) -// * - test import of user data into Nodeprofile module -// */ -// function testCreateImport() { -// $this->drupalLogin($this->admin_user); -// // $this->drupalGet('admin/build/modules'); -// // file_put_contents('output.html', $this->drupalGetContent()); -// -// $this->nodeprofileConfiguration(); -// -// // Prepare a user to do testing -// $this->drupalGet('logout'); // log out first -// $this->drupalLogin($this->user_importer); -// -// // Select CSV file (the included example file) -// $this->settingsFileSelect(); -// -// // import settings -// $this->importID($this->getUrl()); // store import ID for later -// $settings = array(); -// $this->settingsEmailMatch($settings); -// $this->settingsNodeprofileMatch($settings); -// $this->settingsIgnoreFirstLine($settings); -// $this->drupalPost($this->getUrl(), $settings, 'Import'); -// -// // check if users have been imported -// $this->checkNodeprofileExist(); -// -// } -// -// /** -// * Configure modules -// */ -// function nodeprofileConfiguration() { -// -// // create Identity content type -// $edit = array('name' => 'Identity', 'type' => 'identity', 'title_label' => 'Title', 'body_label' => '', 'node_options[promote]' => 0, 'content_profile_use' => 1); -// $this->drupalPost('admin/content/types/add', $edit, t('Save content type')); -// -// // create First Name field -// $edit = array('_add_new_field[label]' => 'First Name', '_add_new_field[field_name]' => 'first_name', '_add_new_field[type]' => 'text', '_add_new_field[widget_type]' => 'text_textfield'); -// $this->drupalPost('admin/content/node-type/identity/fields', $edit, t('Save')); -// -// $edit = array('required' => 1); -// $this->drupalPost('admin/content/node-type/identity/fields/field_first_name', $edit, t('Save field settings')); -// -// // create Last Name field -// $edit = array('_add_new_field[label]' => 'Last Name', '_add_new_field[field_name]' => 'last_name', '_add_new_field[type]' => 'text', '_add_new_field[widget_type]' => 'text_textfield'); -// $this->drupalPost('admin/content/node-type/identity/fields', $edit, t('Save')); -// -// $edit = array('required' => 1); -// $this->drupalPost('admin/content/node-type/identity/fields/field_last_name', $edit, t('Save field settings')); -// -// // create Biography content type -// $edit = array('name' => 'Biography', 'type' => 'biography', 'title_label' => 'Title', 'body_label' => '', 'node_options[promote]' => 0, 'content_profile_use' => 1); -// $this->drupalPost('admin/content/types/add', $edit, t('Save content type')); -// -// // create CV field -// $edit = array('_add_new_field[label]' => 'CV', '_add_new_field[field_name]' => 'cv', '_add_new_field[type]' => 'text', '_add_new_field[widget_type]' => 'text_textarea'); -// $this->drupalPost('admin/content/node-type/biography/fields', $edit, t('Save')); -// -// $edit = array('required' => 1, 'rows' => 5); -// $this->drupalPost('admin/content/node-type/biography/fields/field_cv', $edit, t('Save field settings')); -// -// // create Blog field (URL) -// $edit = array('_add_new_field[label]' => 'Blog', '_add_new_field[field_name]' => 'blog', '_add_new_field[type]' => 'link', '_add_new_field[widget_type]' => 'link'); -// $this->drupalPost('admin/content/node-type/biography/fields', $edit, t('Save')); -// -// // create Birthday field (date) -// $edit = array('_add_new_field[label]' => 'Birthday', '_add_new_field[field_name]' => 'birthday', '_add_new_field[type]' => 'datestamp', '_add_new_field[widget_type]' => 'date_text'); -// $this->drupalPost('admin/content/node-type/biography/fields', $edit, t('Save')); -// -// // create Interests Vocabulary -// $edit = array('name' => 'Interests', 'nodes[biography]' => 'biography', 'tags' => 1); -// $this->drupalPost('admin/content/taxonomy/add/vocabulary', $edit, t('Save')); -// -// $vocabularies = taxonomy_get_vocabularies('biography'); -// -// foreach ($vocabularies as $vocabulary) { -// $this->vocabulary_id = $vocabulary->vid; -// } -// -// // create Contact Details contact type -// $edit = array('name' => 'Contact Details', 'type' => 'contact_details', 'title_label' => 'Title', 'body_label' => '', 'node_options[promote]' => 0, 'content_profile_use' => 1); -// $this->drupalPost('admin/content/types/add', $edit, t('Save content type')); -// -// // create Can Be Contacted field -// $edit = array('_add_new_field[label]' => 'Contact', '_add_new_field[field_name]' => 'can_be_contacted', '_add_new_field[type]' => 'number_integer', '_add_new_field[widget_type]' => 'optionwidgets_onoff'); -// $this->drupalPost('admin/content/node-type/contact-details/fields', $edit, t('Save')); -// -// $edit = array('allowed_values' => "0 -// 1|Can be contacted"); -// $this->drupalPost('admin/content/node-type/contact-details/fields/field_can_be_contacted', $edit, t('Save field settings')); -// -// // create Contact Preference field -// $edit = array('_add_new_field[label]' => 'Contact Preference', '_add_new_field[field_name]' => 'contact_preference', '_add_new_field[type]' => 'text', '_add_new_field[widget_type]' => 'optionwidgets_select'); -// $this->drupalPost('admin/content/node-type/contact-details/fields', $edit, t('Save')); -// -// $edit = array('allowed_values' => 'email|email -// telephone|telephone -// post|post'); -// $this->drupalPost('admin/content/node-type/contact-details/fields/field_contact_preference', $edit, t('Save field settings')); -// -// // set access perm for authenticated users to creade profile nodes -// $edit = array('2[create identity content]' => 'create identity content', '2[create biography content]' => 'create biography content', '2[create contact_details content]' => 'create contact_details content'); -// $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); -// } -// -// -// /** -// * Match CSV columns to Profile fields -// */ -// function settingsNodeprofileMatch(&$edit) { -// $edit['field_match[0][field_match]'] = 'content_profile-identity cck:field_first_name:value'; // First Name -// $edit['field_match[1][field_match]'] = 'content_profile-identity cck:field_last_name:value'; // Last Name -// $edit['field_match[10][field_match]'] = 'content_profile-biography cck:field_cv:value'; // CV -// $edit['field_match[7][field_match]'] = 'content_profile-contact_details cck:field_can_be_contacted:value'; // Contact Permision -// $edit['field_match[8][field_match]'] = 'content_profile-contact_details cck:field_contact_preference:value'; // Contact Preference -//// $edit['field_match[9][field_match]'] = 'taxonomy-' . $this->vocabulary_id; // Interests -// $edit['field_match[6][field_match]'] = 'content_profile-biography cck:field_blog:url'; // Blog -// $edit['field_match[11][field_match]'] = 'content_profile-biography cck:field_birthday:value'; // Birthday -// } -// -// /** -// * Check data in CSV file matches data in profiles -// */ -// function checkNodeprofileExist() { -// $file_path = drupal_get_path('module', 'user_import') . '/sample.txt'; -// $handle = @fopen($file_path, "r"); -// $row = 0; -// -// while ($csv = fgetcsv($handle, 1000, ',')) { -// -// if ($row > 0) { -// -// $user = user_load_by_mail($csv[5]); -// // test each data cell against nodeprofile field content -// $identity = node_load(array('type' => 'identity', 'uid' => $user->uid), NULL, TRUE); -// $this->drupalGet("node/$identity->nid"); -// $this->assertText(check_plain($csv[0]), "[Compare CSV and Profile data] Row: $row Field: First Name"); -// $this->assertText(check_plain($csv[1]), "[Compare CSV and Profile data] Row: $row Field: Last Name " . $csv[1]); -// -// $biography = node_load(array('type' => 'biography', 'uid' => $user->uid), NULL, TRUE); -// $this->drupalGet("node/$biography->nid"); -// $this->assertText($csv[6], "[Compare CSV and Profile data] Row: $row Field: Blog"); -// $this->assertText($csv[10], "[Compare CSV and Profile data] Row: $row Field: CV"); -// $birthday = format_date(strtotime($csv[11]), 'custom', 'D, j/m/Y'); -// $this->assertText($birthday, "[Compare CSV and Profile data] Row: $row Field: Birthday " . $birthday); -// -// -// $contact_details = node_load(array('type' => 'contact_details', 'uid' => $user->uid), NULL, TRUE); -// $this->drupalGet("node/$contact_details->nid"); -// -// if (isset($csv[7]) && !empty($csv[7])) { -// $this->assertText('Can be contacted', "[Compare CSV and Profile data] Row: $row Field: Contact Permission set"); -// } -// else { -// $this->assertNoText('Can be contacted', "[Compare CSV and Profile data] Row: $row Field: Contact Permission not set"); -// } -// -// $this->assertText($csv[8], "[Compare CSV and Profile data] Row: $row Field: Contact Preference"); -// -// //test interests link on profile page -// if (!empty($user->profile_interests)) { -// $interests = explode(',', $user->profile_interests); -// $this->drupalGet('profile/profile_interests/' . $interests[0]); -// $this->assertWantedRaw('<a title="View user profile." href="/' . url('user/' . $user->uid) . '">' . $user->name . '</a>' , '[Freeform List] User is listed on page about item in list'); -// } -// -// } -// -// $row++; -// } -// -// } -// -// /** -// * SimpleTest core method: code run after each and every test method. -// */ -// function tearDown() { -// -// // delete accounts of users imported -// $users_email = $this->usersList(); -// -// foreach ($users_email as $mail) { -// $account = user_load_by_mail($mail); -// // delete node profile nodes -// if (!empty($account)) { -// $identity = node_load(array('type' => 'identity', 'uid' => $account->uid)); -// $biography = node_load(array('type' => 'biography', 'uid' => $account->uid)); -// $contact_details = node_load(array('type' => 'contact_details', 'uid' => $account->uid)); -// node_delete($identity->nid); -// node_delete($biography->nid); -// node_delete($contact_details->nid); -// user_delete(array(), $account->uid); -// } -// } -// -// // delete the import -// $import_id = $this->importID(); -// $this->assertTrue(!empty($import_id), t('Import ID: !id', array('!id' => $import_id))); -// _user_import_settings_deletion($import_id); -// -// // delete vocabulary -// taxonomy_del_vocabulary($this->vocabulary_id); -// -// // uninstall modules -// // - tear down disable doesn't seem to do this -// // foreach($this->modules as $module) { -// // if (function_exists($module . '_uninstall')) { -// // $result = call_user_func_array($module . '_uninstall', array()); -// // } -// // } -// -// // delete nodeprofile content types -// node_type_delete('dummy'); // (for some reason) the first node_type_delete is completely ignored -// node_type_delete('identity'); -// node_type_delete('biography'); -// node_type_delete('contact_details'); -// -// // Always call the tearDown() function from the parent class. -// parent::tearDown(); -// } -//} - -/** - * Test import of user data into Profile module - */ -//class UserImportProfileTestCase extends UserImportWebTestCase { -// -// -// public static function getInfo() { -// return array( -// 'name' => 'Import Users (Profile)', -// 'description' => 'Test import of user data into Profile module.', -// 'group' => 'User Import', -// ); -// } -// -// function setUp() { -// parent::setUp('user_import', 'profile'); -// $this->admin_user = $this->drupalCreateUser(array('administer users', 'access administration pages', 'administer site configuration')); -// $this->user_importer = $this->drupalCreateUser(array('import users')); -// } -// -// /** -// * User with right permissions creates import (with new settings) -// * - test import of user data into Profile module -// */ -// function testCreateImport() { -// $this->drupalLogin($this->admin_user); -// $this->profileFieldsCreate(); -// -// // Prepare a user to do testing -// $this->drupalGet('logout'); // log out first -// $this->drupalLogin($this->user_importer); -// -// // Select CSV file (the included example file) -// $this->settingsFileSelect(); -// -// // import settings -// $this->importID($this->getUrl()); // store import ID for later -// -// $settings = array(); -// $this->settingsEmailMatch($settings); -// $this->settingsProfileMatch($settings); -// $this->settingsIgnoreFirstLine($settings); -// $this->drupalPost($this->getUrl(), $settings, 'Import'); -// -// // check if users have been imported -// $this->checkProfileExist(); -// } -// -// /** -// * create profile fields -// */ -// function profileFieldsCreate() { -// -// // Textfield -// $edit = array('category' => 'Name', 'title' => 'First Name', 'name' => 'profile_first_name'); -// $this->drupalPost('admin/people/profile/add/textfield', $edit, t('Save field')); -// -// // Textfield -// $edit = array('category' => 'Name', 'title' => 'Last Name', 'name' => 'profile_last_name'); -// $this->drupalPost('admin/people/profile/add/textfield', $edit, t('Save field')); -// -// // Textarea -// $edit = array('category' => 'Biography', 'title' => 'CV', 'name' => 'profile_cv'); -// $this->drupalPost('admin/people/profile/add/textarea', $edit, t('Save field')); -// -// // Checkbox -// $edit = array('category' => 'Contact Details', 'title' => 'Can Be Contacted', 'name' => 'profile_contact_permission'); -// $this->drupalPost('admin/people/profile/add/checkbox', $edit, t('Save field')); -// -// // List -// $edit = array('category' => 'Contact Details', 'title' => 'Contact Preference', 'name' => 'profile_contact_preference', 'options' => 'email,telephone,post'); -// $this->drupalPost('admin/people/profile/add/selection', $edit, t('Save field')); -// -// // Freeform List -// $edit = array('category' => 'Biography', 'title' => 'Interests', 'name' => 'profile_interests'); -// $this->drupalPost('admin/people/profile/add/list', $edit, t('Save field')); -// -// // URL -// $edit = array('category' => 'Biography', 'title' => 'Blog', 'name' => 'profile_blog'); -// $this->drupalPost('admin/people/profile/add/url', $edit, t('Save field')); -// -// // Date -// $edit = array('category' => 'Biography', 'title' => 'Birthday', 'name' => 'profile_birthday'); -// $this->drupalPost('admin/people/profile/add/date', $edit, t('Save field')); -// } -// -// /** -// * Match CSV columns to Profile fields -// */ -// function settingsProfileMatch(&$edit) { -// $edit['field_match[0][field_match]'] = 'profile-1'; // First Name -// $edit['field_match[1][field_match]'] = 'profile-2'; // Last Name -// $edit['field_match[10][field_match]'] = 'profile-3'; // CV -// $edit['field_match[7][field_match]'] = 'profile-4'; // Contact Permision -// $edit['field_match[8][field_match]'] = 'profile-5'; // Contact Preference -// $edit['field_match[9][field_match]'] = 'profile-6'; // Interests -// $edit['field_match[6][field_match]'] = 'profile-7'; // Blog -// $edit['field_match[11][field_match]'] = 'profile-8'; // Birthday -// } -// -// /** -// * Check data in CSV file matches data in profiles -// */ -// function checkProfileExist() { -// -// $file_path = drupal_get_path('module', 'user_import') . '/sample.txt'; -// $handle = @fopen($file_path, "r"); -// $row = 0; -// -// while ($csv = fgetcsv($handle, 1000, ',')) { -// -// if ($row > 0) { -// $user = user_load_by_mail($csv[5]); -// // test each data cell against Profile field content -// $profile_first_name = isset($user->profile_first_name) ? $user->profile_first_name : ''; -// $this->assertEqual($profile_first_name, $csv[0], "[Compare CSV data to Profile data] Row: $row Field: First Name"); -// -// $profile_last_name = isset($user->profile_last_name) ? $user->profile_last_name : ''; -// $this->assertEqual($profile_last_name, $csv[1], "[Compare CSV data to Profile data] Row: $row Field: Last Name"); -// -// $profile_blog = isset($user->profile_blog) ? $user->profile_blog : ''; -// $this->assertEqual($profile_blog, $csv[6], "[Compare CSV data to Profile data] Row: $row Field: Blog"); -// -// $profile_contact_permission = isset($user->profile_contact_permission) ? $user->profile_contact_permission : ''; -// $file_field_value = (!isset($csv[7]) || empty($csv[7])) ? 0 : 1; -// $this->assertEqual($profile_contact_permission, $file_field_value, "[Compare CSV data to Profile data] Row: $row Field: Contact Permission"); -// -// $profile_contact_preference = isset($user->profile_contact_preference) ? $user->profile_contact_preference : ''; -// $this->assertEqual($profile_contact_preference, $csv[8], "[Compare CSV data to Profile data] Row: $row Field: Contact Preference"); -// -// $profile_interests = isset($user->profile_interests) ? $user->profile_interests : ''; -// $this->assertEqual($profile_interests, $csv[9], "[Compare CSV data to Profile data] Row: $row Field: Profile Interests"); -// -// $profile_cv = isset($user->profile_cv) ? $user->profile_cv : ''; -// $this->assertEqual($profile_cv, $csv[10], "[Compare CSV data to Profile data] Row: $row Field: CV"); -// -// $profile_birthday = isset($user->profile_birthday) ? implode('/', $user->profile_birthday) : ''; -// -// if (isset($user->profile_birthday)) { -// $profile_birthday = $user->profile_birthday['month'] . '/' . $user->profile_birthday['day'] . '/' . $user->profile_birthday['year']; -// } -// else { -// $profile_birthday = ''; -// } -// -// $this->assertEqual($profile_birthday, $csv[11], "[Compare CSV data to Profile data] Row: $row Field: Birthday"); -// /** -// * @todo test below fails because it gets an access denied message. -// */ -// //test interests link on profile page -// // if (!empty($user->profile_interests)) { -// // $interests = explode(',', $user->profile_interests); -// // $this->drupalGet('profile/profile_interests/' . $interests[0]); -// // $this->assertRaw('<a title="View user profile." href="/'. url('user/'. $user->uid) .'">'. $user->name .'</a>', '[Freeform List] User is listed on page about item in list'); -// // } -// -// } -// -// $row++; -// } -// } -// -//} - - - diff --git a/profiles/wcm_base/modules/contrib/webform/README.txt b/profiles/wcm_base/modules/contrib/webform/README.txt index f29f1388..572faddb 100644 --- a/profiles/wcm_base/modules/contrib/webform/README.txt +++ b/profiles/wcm_base/modules/contrib/webform/README.txt @@ -9,6 +9,7 @@ can optionally be mailed to e-mail addresses upon submission. Requirements ------------ Drupal 7.x +See https://www.drupal.org/project/webform for additional requirements. Installation ------------ diff --git a/profiles/wcm_base/modules/contrib/webform/components/date.inc b/profiles/wcm_base/modules/contrib/webform/components/date.inc index 581f9f97..f84c2f23 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/date.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/date.inc @@ -18,6 +18,7 @@ function _webform_defaults_date() { 'required' => 0, 'extra' => array( 'timezone' => 'user', + 'exclude' => array(), 'start_date' => '-2 years', 'end_date' => '+2 years', 'year_textfield' => 0, @@ -74,6 +75,19 @@ function _webform_edit_date($component) { '#access' => variable_get('configurable_timezones', 1), ); + $form['extra']['exclude'] = array( + '#type' => 'checkboxes', + '#title' => t('Hide'), + '#default_value' => $component['extra']['exclude'], + '#options' => array( + 'day' => t('Day'), + 'month' => t('Month'), + 'year' => t('Year'), + ), + '#description' => t('A hidden day or month will be set to 1. A hidden year will be set to the year of the default value.'), + '#weight' => 3, + ); + $form['display']['datepicker'] = array( '#type' => 'checkbox', '#title' => t('Enable popup calendar'), @@ -111,13 +125,47 @@ function _webform_edit_date($component) { '#parents' => array('extra', 'end_date'), ); + $form['#validate'] = array('_webform_edit_date_validate'); return $form; } +/** + * Implements hook_form_id_validate. + * + * Warns user about hiding all the date fields and not using the date picker. + */ +function _webform_edit_date_validate($form, &$form_state) { + // Reduce checkbox values to simple non-associative array of values. + form_set_value($form['extra']['exclude'], array_filter(array_values($form_state['values']['extra']['exclude'])), $form_state); + + // Note that Drupal 7 doesn't support setting errors no checkboxes due to + // browser issues. @see https://www.drupal.org/node/222380 + if (count($form_state['values']['extra']['exclude']) == 3) { + form_set_error('extra][exclude', 'The day, month and year can\'t all be hidden.'); + } + if ($form_state['values']['extra']['exclude'] == array('month')) { + form_set_error('extra][exclude', 'You cannot hide just the month.'); + } + + // Validate that the start and end dates are valid. Don't validate the default + // date because with token substitution, it might not be valid at component + // definition time. Also note that validation was introduced in 7.x-4.8 and + // previously-defined webform may have invalid start and end dates. + foreach (array('start_date', 'end_date') as $field) { + if (trim($form_state['values']['extra'][$field]) && !($date[$field] = webform_strtodate('c', $form_state['values']['extra'][$field]))) { + form_set_error("extra][$field", t('The @field could not be interpreted in <a href="http://www.gnu.org/software/tar/manual/html_chapter/Date-input-formats.html">GNU Date Input Format</a>.', + array('@field' => $form['validation'][$field]['#title']))); + } + } + if (!empty($date['start_date']) && !empty($date['end_date']) && $date['end_date'] < $date['start_date']) { + form_set_error('extra][end_date', t('The End date must be on or after the Start date.')); + } +} + /** * Implements _webform_render_component(). */ -function _webform_render_date($component, $value = NULL, $filter = TRUE) { +function _webform_render_date($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( @@ -129,9 +177,11 @@ function _webform_render_date($component, $value = NULL, $filter = TRUE) { '#required' => $component['required'], '#start_date' => trim($component['extra']['start_date']), '#end_date' => trim($component['extra']['end_date']), + '#reference_timestamp' => $submission && $submission->completed ? $submission->completed : NULL, '#year_textfield' => $component['extra']['year_textfield'], '#default_value' => $filter ? webform_replace_tokens($component['value'], $node) : $component['value'], '#timezone' => $component['extra']['timezone'], + '#exclude' => $component['extra']['exclude'], '#process' => array('webform_expand_date'), '#theme' => 'webform_date', '#theme_wrappers' => array('webform_element'), @@ -161,9 +211,10 @@ function _webform_render_date($component, $value = NULL, $filter = TRUE) { * Form API #process function for Webform date fields. */ function webform_expand_date($element) { + $timezone = $element['#timezone'] != 'user' ? NULL : 'user'; + // Accept a string or array value for #default_value. if (!empty($element['#default_value']) && is_string($element['#default_value'])) { - $timezone = $element['#timezone'] != 'user' ? NULL : 'user'; $timestring = webform_strtodate('c', $element['#default_value'], $timezone); $element['#default_value'] = webform_date_array($timestring, 'date'); } @@ -191,31 +242,95 @@ function webform_expand_date($element) { // Let Drupal do it's normal expansion. $element = form_process_date($element); + // Convert relative dates to absolute and calculate the year, month and day. + $timezone = $element['#timezone'] != 'user' ? NULL : 'user'; + foreach (array('start', 'end') as $start_end) { + $element_field = &$element["#{$start_end}_date"]; + $element_field = $element_field ? webform_strtodate('Y-m-d', $element_field, $timezone, $element['#reference_timestamp']) : ''; + if ($element_field) { + $parts = explode('-', $element_field); + } + else { + $parts = $start_end == 'start' ? array(webform_strtodate('Y', '-2 years'), 1, 1) : array(webform_strtodate('Y', '+2 years'), 12, 31); + $element_field = ''; + } + unset($element_field); // Drop PHP reference. + $parts[3] = $parts[0] . '-' . $parts[1] . '-' . $parts[2]; + $range[$start_end] = array_combine(array('year', 'month', 'day', 'date'), $parts); + } + + // The start date is not guaranteeed to be early than the end date for + // historical reasons. + if ($range['start']['date'] > $range['end']['date']) { + $temp = $range['start']; + $range['start'] = $range['end']; + $range['end'] = $temp; + } + + // Restrict the months and days when not all options are valid choices. + if ($element['#start_date'] && $element['#end_date']) { + $delta_months = ($range['end']['year'] * 12 + $range['end']['month']) - ($range['start']['year'] * 12 + $range['start']['month']); + if ($delta_months < 11) { + // There are 10 or fewer months between the start and end date. If there + // were 11, then every month would be possible, and the menu select + // should not be pruned. + $month_options = &$element['month']['#options']; + if ($range['start']['month'] <= $range['end']['month']) { + $month_options = array_intersect_key($month_options, array_flip(range($range['start']['month'], $range['end']['month']))); + } + else { + $month_options = array_intersect_key($month_options, array_flip(range($range['start']['month'], 12))) + + array_intersect_key($month_options, array_flip(range(1, $range['end']['month']))); + } + unset($month_options); // Drop PHP reference. + if ($delta_months <= 1) { + // The start and end date are either on the same month or consequtive + // months. See if the days should be pruned. + $day_options = &$element['day']['#options']; + if ($range['start']['month'] == $range['end']['month']) { + // Range is within the same month. The days are a simple range from + // start day to end day. + $day_options = array_intersect_key($day_options, array_flip(range($range['start']['day'], $range['end']['day']))); + } + elseif ($range['start']['day'] > $range['end']['day'] + 1) { + // Range spans two months and at least one day would be omitted. + $days_in_month = date('t', mktime(0, 0, 0, $range['start']['month'], 1, $range['start']['year'])); + $day_options = array_intersect_key($day_options, array_flip(range($range['start']['day'], $days_in_month))) + + array_intersect_key($day_options, array_flip(range(1, $range['end']['day']))); + } + unset($day_options); // Drop PHP reference. + } + } + } + // Set default values. foreach ($default_values as $type => $value) { switch ($type) { case 'month': $none = t('Month'); + $hidden_default = 1; break; case 'day': $none = t('Day'); + $hidden_default = 1; break; case 'year': $none = t('Year'); + $hidden_default = !empty($element['#default_value']['year']) ? $element['#default_value']['year'] : webform_strtodate('Y', 'today', $timezone); break; } unset($element[$type]['#value']); $element[$type]['#title'] = $none; $element[$type]['#title_display'] = 'invisible'; - $element[$type]['#default_value'] = isset($default_values[$type]) ? $default_values[$type] : NULL; + $element[$type]['#default_value'] = $default_values[$type]; $element[$type]['#options'] = array('' => $none) + $element[$type]['#options']; - } - - // Convert relative dates to absolute ones. - foreach (array('start_date', 'end_date') as $start_end) { - $timezone = $element['#timezone'] != 'user' ? NULL : 'user'; - $date = webform_strtodate('Y-m-d', $element['#' . $start_end], $timezone); - $element['#' . $start_end] = $date ? $date : ''; + if (in_array($type, $element['#exclude'])) { + $element[$type] += array( + '#prefix' => '<div class="webform-date-field-wrapper element-invisible">', + '#suffix' => '</div>', + ); + $element[$type]['#default_value'] = $hidden_default; + } } // Tweak the year field. @@ -226,9 +341,7 @@ function webform_expand_date($element) { unset($element['year']['#options']); } elseif ($element['#start_date'] || $element['#end_date']) { - $start_year = $element['#start_date'] ? webform_strtodate('Y', $element['#start_date']) : webform_strtodate('Y', '-2 years'); - $end_year = $element['#end_date'] ? webform_strtodate('Y', $element['#end_date']) : webform_strtodate('Y', '+2 years'); - $element['year']['#options'] = array('' => t('Year')) + drupal_map_assoc(range($start_year, $end_year)); + $element['year']['#options'] = array('' => t('Year')) + drupal_map_assoc(range($range['start']['year'], $range['end']['year'])); } return $element; @@ -251,6 +364,13 @@ function theme_webform_date($variables) { $element['day']['#attributes']['class'][] = 'error'; } + // Add HTML5 required attribute, if needed. + if ($element['#required']) { + $element['year']['#attributes']['required'] = 'required'; + $element['month']['#attributes']['required'] = 'required'; + $element['day']['#attributes']['required'] = 'required'; + } + $class = array('webform-container-inline'); // Add the JavaScript calendar if available (provided by Date module package). @@ -283,21 +403,18 @@ function theme_webform_date($variables) { function webform_validate_date($element, $form_state) { if ($element['month']['#value'] !== '' || $element['day']['#value'] !== '' || $element['year']['#value'] !== '') { // Check that each part of the date has been filled in. - $missing_fields = array(); foreach (array('day', 'month', 'year') as $field_type) { if (empty($element[$field_type]['#value'])) { - $missing_fields[] = $field_type; + form_error($element[$field_type], t('!part in !name is missing.', array('!name' => $element['#title'], '!part' => $element[$field_type]['#title']))); + $missing_fields = TRUE; } } - if (count($missing_fields)) { - foreach ($missing_fields as $missing_field) { - form_error($element[$missing_field], t('!part in !name is missing.', array('!name' => $element['#title'], '!part' => $element[$missing_field]['#title']))); - } + if (isset($missing_fields)) { return; } // Check for a valid date. - if (!checkdate((int) $element['month']['#value'], (int) $element['day']['#value'], (int) $element['year']['#value'])) { + if (!checkdate($element['month']['#value'], $element['day']['#value'], $element['year']['#value'])) { form_error($element, t('Entered !name is not a valid date.', array('!name' => $element['#title']))); return; } @@ -306,7 +423,8 @@ function webform_validate_date($element, $form_state) { $timestamp = strtotime($element['year']['#value'] . '-' . $element['month']['#value'] . '-' . $element['day']['#value']); $format = webform_date_format('short'); - // Flip start and end if needed. + // Flip start and end if needed. Prior to 7.x-4.8, it was possible to save + // a date component with the end date earlier than the start date. $date1 = strtotime($element['#start_date']); $date2 = strtotime($element['#end_date']); if ($date1 !== FALSE && $date2 !== FALSE) { @@ -319,17 +437,13 @@ function webform_validate_date($element, $form_state) { } // Check that the date is after the start date. - if ($start_date !== FALSE) { - if ($timestamp < $start_date) { - form_error($element, t('The entered date must be @start_date or later.', array('@start_date' => date($format, $start_date)))); - } + if ($start_date !== FALSE && $timestamp < $start_date) { + form_error($element, t('The entered date must be @start_date or later.', array('@start_date' => date($format, $start_date)))); } // Check that the date is before the end date. - if ($end_date !== FALSE) { - if ($timestamp > $end_date) { - form_error($element, t('The entered date must be @end_date or earlier.', array('@end_date' => date($format, $end_date)))); - } + if ($end_date !== FALSE && $timestamp > $end_date) { + form_error($element, t('The entered date must be @end_date or earlier.', array('@end_date' => date($format, $end_date)))); } } elseif ($element['#required']) { @@ -358,6 +472,8 @@ function _webform_display_date($component, $value, $format = 'html') { '#theme' => 'webform_display_date', '#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'), '#format' => $format, + '#exclude' => $component['extra']['exclude'], + '#value' => $value, '#translatable' => array('title'), ); @@ -371,7 +487,7 @@ function theme_webform_display_date($variables) { $output = ' '; if ($element['#value']['year'] && $element['#value']['month'] && $element['#value']['day']) { $timestamp = webform_strtotime($element['#value']['month'] . '/' . $element['#value']['day'] . '/' . $element['#value']['year']); - $format = webform_date_format('medium'); + $format = webform_date_format(NULL, $element['#exclude']); $output = format_date($timestamp, 'custom', $format, 'UTC'); } @@ -381,15 +497,19 @@ function theme_webform_display_date($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_date($component, $sids = array()) { +function _webform_analysis_date($component, $sids = array(), $single = FALSE, $join = NULL) { $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) ->fields('wsd', array('no', 'data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']) - ->orderBy('sid'); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']) + ->orderBy('wsd.sid'); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $result = $query->execute(); @@ -419,7 +539,7 @@ function _webform_analysis_date($component, $sids = array()) { function _webform_table_date($component, $value) { if ($value[0]) { $timestamp = webform_strtotime($value[0]); - $format = webform_date_format('short'); + $format = webform_date_format('short', $component['extra']['exclude']); return format_date($timestamp, 'custom', $format, 'UTC'); } else { diff --git a/profiles/wcm_base/modules/contrib/webform/components/email.inc b/profiles/wcm_base/modules/contrib/webform/components/email.inc index feadb70d..d4077924 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/email.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/email.inc @@ -17,6 +17,8 @@ function _webform_defaults_email() { 'value' => '', 'required' => 0, 'extra' => array( + 'multiple' => 0, + 'format' => 'short', 'width' => '', 'unique' => 0, 'disabled' => 0, @@ -70,6 +72,25 @@ function _webform_edit_email($component) { '#weight' => 0, '#element_validate' => array('_webform_edit_email_validate'), ); + $form['extra']['multiple'] = array( + '#type' => 'checkbox', + '#title' => t('Multiple'), + '#default_value' => $component['extra']['multiple'], + '#description' => t('Allow multiple e-mail addresses, separated by commas.'), + '#weight' => 0, + ); + if (webform_variable_get('webform_email_address_format') == 'long') { + $form['extra']['format'] = array( + '#type' => 'radios', + '#title' => t('Format'), + '#options' => array( + 'long' => t('Allow long format: "Example Name" <name@example.com>'), + 'short' => t('Short format only: name@example.com'), + ), + '#default_value' => $component['extra']['format'], + '#description' => t('Not all servers support the "long" format.'), + ); + } $form['display']['width'] = array( '#type' => 'textfield', '#title' => t('Width'), @@ -119,7 +140,7 @@ function _webform_edit_email_validate($element, &$form_state) { /** * Implements _webform_render_component(). */ -function _webform_render_email($component, $value = NULL, $filter = TRUE) { +function _webform_render_email($component, $value = NULL, $filter = TRUE, $submission = NULL) { global $user; $node = isset($component['nid']) ? node_load($component['nid']) : NULL; @@ -137,6 +158,10 @@ function _webform_render_email($component, $value = NULL, $filter = TRUE) { '#translatable' => array('title', 'description'), ); + if ($component['required']) { + $element['#attributes']['required'] = 'required'; + } + // Add an e-mail class for identifying the difference from normal textfields. $element['#attributes']['class'][] = 'email'; @@ -145,6 +170,27 @@ function _webform_render_email($component, $value = NULL, $filter = TRUE) { $element['#element_validate'][] = 'webform_validate_unique'; } + if ($component['extra']['format'] == 'long') { + // html5 email elements enforce short-form email validation in addition to + // pattern validation. This means that long format email addresses must be + // rendered as text. + $element['#attributes']['type'] = 'text'; + + // html5 patterns have implied delimters and start and end patterns. + // The are also case sensitive, not global, and not multi-line. + // See https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation + // See http://stackoverflow.com/questions/19605773/html5-email-validation + $address = '[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*'; + $name = '("[^<>"]*?"|[^<>",]*?)'; + $short_long = "($name *<$address>|$address)"; + $element['#attributes']['pattern'] = $component['extra']['multiple'] + ? "$short_long(, *$short_long)*" + : $short_long; + } + elseif ($component['extra']['multiple']) { + $element['#attributes']['multiple'] = 'multiple'; + } + if (isset($value[0])) { $element['#default_value'] = $value[0]; } @@ -206,13 +252,12 @@ function theme_webform_email($variables) { */ function _webform_validate_email($form_element, &$form_state) { $component = $form_element['#webform_component']; - $value = trim($form_element['#value']); - if ($value !== '' && !valid_email_address($value)) { - form_error($form_element, t('%value is not a valid email address.', array('%value' => $value))); - } - else { - form_set_value($form_element, $value, $form_state); - } + $format = webform_variable_get('webform_email_address_format') == 'long' ? $component['extra']['format'] : 'short'; + webform_email_validate($form_element['#value'], + implode('][', $form_element ['#parents']), + TRUE, // Required validation is done elsewhere. + $component['extra']['multiple'], + $format); } /** @@ -243,14 +288,18 @@ function theme_webform_display_email($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_email($component, $sids = array()) { +function _webform_analysis_email($component, $sids = array(), $single = FALSE, $join = NULL) { $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) ->fields('wsd', array('no', 'data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $nonblanks = 0; diff --git a/profiles/wcm_base/modules/contrib/webform/components/fieldset.inc b/profiles/wcm_base/modules/contrib/webform/components/fieldset.inc index 56e4ee27..8b43ae33 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/fieldset.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/fieldset.inc @@ -51,7 +51,7 @@ function _webform_edit_fieldset($component) { /** * Implements _webform_render_component(). */ -function _webform_render_fieldset($component, $value = NULL, $filter = TRUE) { +function _webform_render_fieldset($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( diff --git a/profiles/wcm_base/modules/contrib/webform/components/file.inc b/profiles/wcm_base/modules/contrib/webform/components/file.inc index 46620110..6ec752c8 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/file.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/file.inc @@ -21,6 +21,7 @@ function _webform_defaults_file() { 'addextensions' => '', 'size' => '2 MB', ), + 'rename' => '', 'scheme' => 'public', 'directory' => '', 'progress_indicator' => 'throbber', @@ -151,11 +152,20 @@ function _webform_edit_file($component) { '#type' => 'textfield', '#title' => t('Upload directory'), '#default_value' => $component['extra']['directory'], - '#description' => t('You may optionally specify a sub-directory to store your files.'), + '#description' => t('You may optionally specify a sub-directory to store your files.') . ' ' . theme('webform_token_help'), '#weight' => 5, '#field_prefix' => 'webform/', ); + $form['extra']['rename'] = array( + '#type' => 'textfield', + '#title' => t('Rename files'), + '#default_value' => $component['extra']['rename'], + '#description' => t('You may optionally use tokens to create a pattern used to rename files upon submission. Omit the extension; it will be added automatically.').' '.theme('webform_token_help', array('groups' => array('node', 'submission'))), + '#weight' => 6, + '#element_validate' => array('_webform_edit_file_rename_validate'), + ); + $form['display']['progress_indicator'] = array( '#type' => 'radios', '#title' => t('Progress indicator'), @@ -187,6 +197,18 @@ function _webform_edit_file($component) { return $form; } +/** + * A Form API element validate function to ensure that the rename string is + * either empty or contains at least one token. + */ +function _webform_edit_file_rename_validate($element, &$form_state, $form) { + $rename = trim($form_state['values']['extra']['rename']); + form_set_value($element, $rename, $form_state); + if (strlen($rename) && !count(token_scan($rename))) { + form_error($element, t('To create unique file names, use at least one token in the file name pattern.')); + } +} + /** * A Form API element validate function to check filesize is valid. */ @@ -215,15 +237,15 @@ function _webform_edit_file_check_directory($element) { $directory = $element['extra']['directory']['#value']; $destination_dir = file_stream_wrapper_uri_normalize($scheme . '://webform/' . $directory); + $tokenized_dir = drupal_strtolower(webform_replace_tokens($destination_dir, $element['#node'])); // Sanity check input to prevent use parent (../) directories. - if (preg_match('/\.\.[\/\\\]/', $destination_dir . '/')) { - form_error($element['extra']['directory'], t('The save directory %directory is not valid.', array('%directory' => $directory))); + if (preg_match('/\.\.[\/\\\]/', $tokenized_dir . '/')) { + form_error($element['extra']['directory'], t('The save directory %directory is not valid.', array('%directory' => $tokenized_dir))); } else { - $destination_success = file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY); - if (!$destination_success) { - form_error($element['extra']['directory'], t('The save directory %directory could not be created. Check that the webform files directory is writable.', array('%directory' => $directory))); + if (!file_prepare_directory($tokenized_dir, FILE_CREATE_DIRECTORY)) { + form_error($element['extra']['directory'], t('The save directory %directory could not be created. Check that the webform files directory is writable.', array('%directory' => $tokenized_dir))); } } @@ -243,6 +265,10 @@ function _webform_edit_file_extensions_validate($element, &$form_state) { foreach (array_keys($element['types'][$category]['#value']) as $extension) { if ($element['types'][$category][$extension]['#value']) { $extensions[] = $extension; + // jpeg is an exception. It is allowed anytime jpg is allowed. + if ($extension == 'jpg') { + $extensions[] = 'jpeg'; + } } } } @@ -304,7 +330,7 @@ function theme_webform_edit_file_extensions($variables) { /** * Implements _webform_render_component(). */ -function _webform_render_file($component, $value = NULL, $filter = TRUE) { +function _webform_render_file($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; // Cap the upload size according to the PHP limit. @@ -326,7 +352,9 @@ function _webform_render_file($component, $value = NULL, $filter = TRUE) { 'file_validate_extensions' => array(implode(' ', $component['extra']['filtering']['types'])), ), '#pre_render' => array_merge(element_info_property('managed_file', '#pre_render'), array('webform_file_allow_access')), - '#upload_location' => $component['extra']['scheme'] . '://webform/' . $component['extra']['directory'], + '#upload_location' => $component['extra']['scheme'] . '://webform/' . ($filter + ? drupal_strtolower(webform_replace_tokens($component['extra']['directory'], $node)) + : $component['extra']['directory']), '#progress_indicator' => $component['extra']['progress_indicator'], '#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'], '#weight' => $component['weight'], @@ -345,12 +373,15 @@ function _webform_render_file($component, $value = NULL, $filter = TRUE) { * Implements _webform_submit_component(). */ function _webform_submit_file($component, $value) { - if (is_array($value)) { - return !empty($value['fid']) ? $value['fid'] : ''; - } - else { - return !empty($value) ? $value : ''; + $fid = is_array($value) + ? (!empty($value['fid']) ? $value['fid'] : '') + : (!empty($value) ? $value : ''); + // Extend access to this file, even if the submission has not been saved yet. This may happen when + // previewing a private file which was selected but not explicitly uploaded, and then previewed. + if ($fid) { + $_SESSION['webform_files'][$fid] = $fid; } + return $fid; } /** @@ -424,14 +455,18 @@ function _webform_attachments_file($component, $value) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_file($component, $sids = array()) { +function _webform_analysis_file($component, $sids = array(), $single = FALSE, $join = NULL) { $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) ->fields('wsd', array('no', 'data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $nonblanks = 0; @@ -539,3 +574,60 @@ function webform_file_usage_adjust($submission) { } } } + +/** + * Rename any files which are eligible for renaming, if this submission is being + * submitted for the first time. + */ +function webform_file_rename($node, $submission) { + if (isset($submission->file_usage)) { + foreach ($submission->file_usage['renameable'] as $cid => $fids) { + foreach ($fids as $fid) { + webform_file_process_rename($node, $submission, $node->webform['components'][$cid], $fid); + } + } + } +} + + +/** + * Renames the uploaded file name using tokens. + * + * @param $node + * The webform node object. + * @param $submission + * The webform submission object. + * @param $component + * Component settings array for which fid is going to be processed. + * @param $fid + * A file id to be processed. + */ +function webform_file_process_rename($node, $submission, $component, $fid) { + $file = webform_get_file($fid); + + if ($file) { + // Get the destination uri. + $destination_dir = $component['extra']['scheme'] . '://webform/' . drupal_strtolower(webform_replace_tokens($component['extra']['directory'], $node)); + $destination_dir = file_stream_wrapper_uri_normalize($destination_dir); + + // Get the file extension. + $info = pathinfo($file->uri); + $extension = $info['extension']; + + // Prepare new file name without extension. + $new_file_name = webform_replace_tokens($component['extra']['rename'], $node, $submission, NULL, TRUE); + $new_file_name = trim($new_file_name); + $new_file_name = _webform_transliterate($new_file_name); + $new_file_name = str_replace('/', '_', $new_file_name); + $new_file_name = preg_replace('/[^a-zA-Z0-9_\- ]/', '', $new_file_name); + if (strlen($new_file_name)) { + // Prepare the new uri with new filename. + $destination = "$destination_dir/$new_file_name.$extension"; + + // Compare the uri and Rename the file name. + if ($file->uri != $destination) { + file_move($file, $destination, FILE_EXISTS_RENAME); + } + } + } +} diff --git a/profiles/wcm_base/modules/contrib/webform/components/grid.inc b/profiles/wcm_base/modules/contrib/webform/components/grid.inc index 7782e717..3e5bab89 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/grid.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/grid.inc @@ -104,7 +104,7 @@ function _webform_edit_grid($component) { '#type' => 'textarea', '#title' => t('Options'), '#default_value' => $component['extra']['options'], - '#description' => t('Options to select across the top. One option per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys.') . theme('webform_token_help'), + '#description' => t('Options to select across the top. One option per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys.') . ' ' .theme('webform_token_help'), '#cols' => 60, '#rows' => 5, '#weight' => -3, @@ -116,7 +116,7 @@ function _webform_edit_grid($component) { '#type' => 'textarea', '#title' => t('Questions'), '#default_value' => $component['extra']['questions'], - '#description' => t('Questions list down the side of the grid. One question per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys.') . theme('webform_token_help'), + '#description' => t('Questions list down the side of the grid. One question per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable question"</strong>. For a heading column on the right, append "|" and the right-side heading. Use of only alphanumeric characters and underscores is recommended in keys.') . ' ' . theme('webform_token_help'), '#cols' => 60, '#rows' => 5, '#weight' => -2, @@ -173,7 +173,7 @@ function _webform_edit_grid($component) { /** * Implements _webform_render_component(). */ -function _webform_render_grid($component, $value = NULL, $filter = TRUE) { +function _webform_render_grid($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE); @@ -240,6 +240,11 @@ function webform_expand_grid($element) { '#webform_validated' => FALSE, '#translatable' => array('title'), ); + + // Add HTML5 required attribute, if needed. + if ($element['#required']) { + $element[$key]['#attributes']['required'] = 'required'; + } } } @@ -303,14 +308,20 @@ function theme_webform_display_grid($variables) { $format = $element['#format']; if ($format == 'html') { + $right_titles = _webform_grid_right_titles($element); $rows = array(); - $header = array(array('data' => '', 'class' => array('webform-grid-question'))); + $title = array('data' => '', 'class' => array('webform-grid-question')); + $header = array($title); foreach ($element['#grid_options'] as $option) { $header[] = array('data' => webform_filter_xss($option), 'class' => array('checkbox', 'webform-grid-option')); } + if ($right_titles) { + $header[] = $title; + } foreach ($element['#grid_questions'] as $question_key => $question) { $row = array(); - $row[] = array('data' => webform_filter_xss($question), 'class' => array('webform-grid-question')); + $questions = explode('|', $question, 2); + $row[] = array('data' => webform_filter_xss($questions[0]), 'class' => array('webform-grid-question')); foreach ($element['#grid_options'] as $option_value => $option_label) { if (strcmp($element[$question_key]['#value'], $option_value) == 0) { $row[] = array('data' => '<strong>X</strong>', 'class' => array('checkbox', 'webform-grid-option')); @@ -319,6 +330,9 @@ function theme_webform_display_grid($variables) { $row[] = array('data' => ' ', 'class' => array('checkbox', 'webform-grid-option')); } } + if ($right_titles) { + $row[] = array('data' => isset($questions[1]) ? webform_filter_xss($questions[1]) : '', 'class' => array('webform-grid-question')); + } $rows[] = $row; } @@ -328,7 +342,8 @@ function theme_webform_display_grid($variables) { else { $items = array(); foreach (element_children($element) as $key) { - $items[] = ' - ' . $element[$key]['#title'] . ': ' . (isset($element['#grid_options'][$element[$key]['#value']]) ? $element['#grid_options'][$element[$key]['#value']] : ''); + $items[] = ' - ' . _webform_grid_question_header($element[$key]['#title']) . ': ' . + (isset($element['#grid_options'][$element[$key]['#value']]) ? $element['#grid_options'][$element[$key]['#value']] : ''); } $output = implode("\n", $items); } @@ -339,7 +354,7 @@ function theme_webform_display_grid($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_grid($component, $sids = array()) { +function _webform_analysis_grid($component, $sids = array(), $single = FALSE, $join = NULL) { // Generate the list of options and questions. $node = node_load($component['nid']); $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE); @@ -350,15 +365,19 @@ function _webform_analysis_grid($component, $sids = array()) { // Generate a lookup table of results. $query = db_select('webform_submitted_data', 'wsd') ->fields('wsd', array('no', 'data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']) - ->condition('data', '', '<>') - ->groupBy('no') - ->groupBy('data'); - $query->addExpression('COUNT(sid)', 'datacount'); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']) + ->condition('wsd.data', '', '<>') + ->groupBy('wsd.no') + ->groupBy('wsd.data'); + $query->addExpression('COUNT(wsd.sid)', 'datacount'); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $result = $query->execute(); @@ -405,7 +424,7 @@ function _webform_table_grid($component, $value) { // Set the value as a single string. foreach ($questions as $key => $label) { if (isset($value[$key]) && isset($options[$value[$key]])) { - $output .= webform_filter_xss($label) . ': ' . webform_filter_xss($options[$value[$key]]) . '<br />'; + $output .= webform_filter_xss(_webform_grid_question_header($label)) . ': ' . webform_filter_xss($options[$value[$key]]) . '<br />'; } } @@ -431,7 +450,7 @@ function _webform_csv_headers_grid($component, $export_options) { $header[1][] = ''; } // The value for this option. - $header[2][] = $export_options['header_keys'] ? $key : $item; + $header[2][] = $export_options['header_keys'] ? $key : _webform_grid_question_header($item); $count++; } @@ -486,19 +505,26 @@ function _webform_edit_grid_unique_validate($element) { function theme_webform_grid($variables) { $element = $variables['element']; + $right_titles = _webform_grid_right_titles($element); $rows = array(); - $header = array(array('data' => '', 'class' => array('webform-grid-question'))); + $title = array('data' => '', 'class' => array('webform-grid-question')); + $header = array($title); // Set the header for the table. foreach ($element['#grid_options'] as $option) { $header[] = array('data' => webform_filter_xss($option), 'class' => array('checkbox', 'webform-grid-option')); } + if ($right_titles) { + $header[] = $title; + } foreach (element_children($element) as $key) { $question_element = $element[$key]; + $question_titles = explode('|', $question_element['#title'], 2); // Create a row with the question title. - $row = array(array('data' => webform_filter_xss($question_element['#title']), 'class' => array('webform-grid-question'))); + $title = array('data' => webform_filter_xss($question_titles[0]), 'class' => array('webform-grid-question')); + $row = array($title); // Render each radio button in the row. $radios = form_process_radios($question_element); @@ -507,9 +533,32 @@ function theme_webform_grid($variables) { $radios[$key]['#title_display'] = 'invisible'; $row[] = array('data' => drupal_render($radios[$key]), 'class' => array('checkbox', 'webform-grid-option')); } + if ($right_titles) { + $row[] = array('data' => isset($question_titles[1]) ? webform_filter_xss($question_titles[1]) : '', 'class' => array('webform-grid-question')); + } $rows[] = $row; } $option_count = count($header) - 1; return theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => $element['#sticky'], 'attributes' => array('class' => array('webform-grid', 'webform-grid-' . $option_count)))); } + +/** + * Determine if a right-side title column has been specified. + */ +function _webform_grid_right_titles($element) { + foreach ($element['#grid_questions'] as $question_key => $question) { + if (substr_count($question, '|')) { + return TRUE; + } + } + return FALSE; +} + +/** + * Create a question header for left, right or left/right question headers. + */ +function _webform_grid_question_header($text) { + return implode('/', array_filter(explode('|', $text))); +} + diff --git a/profiles/wcm_base/modules/contrib/webform/components/hidden.inc b/profiles/wcm_base/modules/contrib/webform/components/hidden.inc index 233cf87a..0740018f 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/hidden.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/hidden.inc @@ -68,7 +68,7 @@ function _webform_edit_hidden($component) { /** * Implements _webform_render_component(). */ -function _webform_render_hidden($component, $value = NULL, $filter = TRUE) { +function _webform_render_hidden($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $default_value = $filter ? webform_replace_tokens($component['value'], $node) : $component['value']; @@ -126,14 +126,18 @@ function theme_webform_display_hidden($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_hidden($component, $sids = array()) { +function _webform_analysis_hidden($component, $sids = array(), $single = FALSE, $join = NULL) { $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) ->fields('wsd', array('no', 'data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $nonblanks = 0; diff --git a/profiles/wcm_base/modules/contrib/webform/components/markup.inc b/profiles/wcm_base/modules/contrib/webform/components/markup.inc index f6c8452d..6ceefb26 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/markup.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/markup.inc @@ -18,6 +18,7 @@ function _webform_defaults_markup() { 'extra' => array( 'format' => NULL, 'private' => FALSE, + 'display_on' => 'form', ), ); } @@ -49,6 +50,19 @@ function _webform_edit_markup($component) { '#element_validate' => array('_webform_edit_markup_validate'), ); + $form['display']['display_on'] = array( + '#type' => 'select', + '#title' => t('Display on'), + '#default_value' => $component['extra']['display_on'], + '#options' => array( + 'form' => t('form only'), + 'display' => t('viewed submission only'), + 'both' => t('both form and viewed submission'), + ), + '#weight' => 1, + '#parents' => array('extra', 'display_on'), + ); + return $form; } @@ -65,17 +79,18 @@ function _webform_edit_markup_validate($form, &$form_state) { /** * Implements _webform_render_component(). */ -function _webform_render_markup($component, $value = NULL, $filter = TRUE) { +function _webform_render_markup($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( '#type' => 'markup', '#title' => $filter ? NULL : $component['name'], '#weight' => $component['weight'], - '#markup' => $filter ? check_markup(webform_replace_tokens($component['value'], $node, NULL, NULL, TRUE), $component['extra']['format'], '', TRUE) : $component['value'], + '#markup' => $filter ? webform_replace_tokens($component['value'], $node, NULL, NULL, $component['extra']['format']) : $component['value'], '#format' => $component['extra']['format'], '#theme_wrappers' => array('webform_element'), '#translatable' => array('title', 'markup'), + '#access' => $component['extra']['display_on'] != 'display', ); return $element; @@ -91,8 +106,9 @@ function _webform_display_markup($component, $value, $format = 'html') { '#weight' => $component['weight'], '#theme' => 'webform_display_markup', '#format' => $format, - '#value' => check_markup(webform_replace_tokens($component['value'], $node, NULL, NULL, TRUE), $component['extra']['format'], '', TRUE), + '#value' => webform_replace_tokens($component['value'], $node, NULL, NULL, $component['extra']['format']), '#translatable' => array('title'), + '#access' => $component['extra']['display_on'] != 'form', ); } @@ -100,7 +116,6 @@ function _webform_display_markup($component, $value, $format = 'html') { * Format the output of data for this component. */ function theme_webform_display_markup($variables) { - // Markup components are not displayed but can be by overriding this theme - // function. - return ''; + $element = $variables['element']; + return $element['#access'] ? $element['#value'] : ''; } diff --git a/profiles/wcm_base/modules/contrib/webform/components/number.inc b/profiles/wcm_base/modules/contrib/webform/components/number.inc index 7383234e..9fe0bb56 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/number.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/number.inc @@ -272,7 +272,7 @@ function theme_webform_number($variables) { /** * Implements _webform_render_component(). */ -function _webform_render_number($component, $value = NULL, $filter = TRUE) { +function _webform_render_number($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( @@ -297,6 +297,10 @@ function _webform_render_number($component, $value = NULL, $filter = TRUE) { '#translatable' => array('title', 'description', 'field_prefix', 'field_suffix'), ); + if ($component['required']) { + $element['#attributes']['required'] = 'required'; + } + // Set the decimal count to zero for integers. if ($element['#integer'] && $element['#decimals'] === '') { $element['#decimals'] = 0; @@ -403,16 +407,20 @@ function theme_webform_display_number($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_number($component, $sids = array(), $single = FALSE) { +function _webform_analysis_number($component, $sids = array(), $single = FALSE, $join = NULL) { $advanced_stats = $single; $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) ->fields('wsd', array('data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $population = array(); @@ -865,10 +873,10 @@ function webform_modulo($a, $b) { * See @link http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm @endlink * See @link http://floating-point-gui.de/errors/comparison/ @endlink * See @link http://en.wikipedia.org/wiki/IEEE_754-1985#Denormalized_numbers @endlink - - * @param $number_1 float + * + * @param float $number_1 * The first number. - * @param $number_2 float + * @param float $number_2 * The second number. * * @return int|null diff --git a/profiles/wcm_base/modules/contrib/webform/components/pagebreak.inc b/profiles/wcm_base/modules/contrib/webform/components/pagebreak.inc index 81f5e02f..a02367d8 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/pagebreak.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/pagebreak.inc @@ -66,7 +66,7 @@ function _webform_edit_pagebreak($component) { /** * Implements _webform_render_component(). */ -function _webform_render_pagebreak($component, $value = NULL, $filter = TRUE) { +function _webform_render_pagebreak($component, $value = NULL, $filter = TRUE, $submission = NULL) { $element = array( '#type' => 'hidden', '#value' => $component['name'], @@ -76,7 +76,7 @@ function _webform_render_pagebreak($component, $value = NULL, $filter = TRUE) { } /** - * Implements _webform_render_component(). + * Implements _webform_display_component(). */ function _webform_display_pagebreak($component, $value = NULL, $format = 'html') { $element = array( diff --git a/profiles/wcm_base/modules/contrib/webform/components/select.inc b/profiles/wcm_base/modules/contrib/webform/components/select.inc index 3ad24b34..af6c69e9 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/select.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/select.inc @@ -20,6 +20,7 @@ function _webform_defaults_select() { 'items' => '', 'multiple' => NULL, 'aslist' => NULL, + 'empty_option' => '', 'optrand' => 0, 'other_option' => NULL, 'other_text' => t('Other...'), @@ -176,6 +177,22 @@ function _webform_edit_select($component) { '#description' => t('Check this option if you want the select component to be displayed as a select list box instead of radio buttons or checkboxes. Option groups (nested options) are only supported with listbox components.'), '#parents' => array('extra', 'aslist'), ); + $form['display']['empty_option'] = array( + '#type' => 'textfield', + '#title' => t('Empty option'), + '#default_value' => $component['extra']['empty_option'], + '#size' => 60, + '#maxlength' => 255, + '#description' => t('The list item to show when no default is provided. Leave blank for "- None -" or "- Select -".'), + '#parents' => array('extra', 'empty_option'), + '#states' => array( + 'visible' => array( + ':input[name="extra[aslist]"]' => array('checked' => TRUE), + ':input[name="extra[multiple]"]' => array('checked' => FALSE), + ':input[name="value"]' => array('filled' => FALSE), + ), + ), + ); $form['display']['optrand'] = array( '#type' => 'checkbox', '#title' => t('Randomize options'), @@ -297,7 +314,7 @@ function _webform_edit_validate_set_aslist($options, &$form_state) { /** * Implements _webform_render_component(). */ -function _webform_render_select($component, $value = NULL, $filter = TRUE) { +function _webform_render_select($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( @@ -311,6 +328,11 @@ function _webform_render_select($component, $value = NULL, $filter = TRUE) { '#translatable' => array('title', 'description', 'options'), ); + // Add HTML5 required attribute, if needed and possible (not working on multiple checkboxes). + if ($component['required'] && ($component['extra']['aslist'] || !$component['extra']['multiple'])) { + $element['#attributes']['required'] = 'required'; + } + // Convert the user-entered options list into an array. $default_value = $filter ? webform_replace_tokens($component['value'], $node) : $component['value']; $options = _webform_select_options($component, !$component['extra']['aslist'], $filter); @@ -327,53 +349,42 @@ function _webform_render_select($component, $value = NULL, $filter = TRUE) { // Drupal 7's adding of the option for us. See @form_process_select(). if ($component['extra']['aslist'] && !$component['extra']['multiple'] && $default_value === '') { $element['#empty_value'] = ''; + $element['#empty_option'] = $component['extra']['empty_option']; } // Set the component options. $element['#options'] = $options; + // Use the component's default value if the component is currently empty. + if (!isset($value)) { + // The default for multiple selects is a comma-delimited list, without white-space or empty entries. + $value = $component['extra']['multiple'] ? array_filter(array_map('trim', explode(',', $default_value)), 'strlen') : $default_value; + } + + // Convert all values into an array; component may now be single but was previously multiple, or vice-versa + $value = (array)$value; + // Set the default value. Note: "No choice" is stored as an empty string, // which will match a 0 key for radios; NULL is used to avoid unintentional // defaulting to the 0 option. - if (isset($value)) { - if ($component['extra']['multiple']) { - // Set the value as an array. - $element['#default_value'] = array(); - foreach ((array) $value as $key => $option_value) { - $element['#default_value'][] = $option_value === '' ? NULL : $option_value; - } - } - else { - // Set the value as a single string. - $element['#default_value'] = ''; - foreach ((array) $value as $option_value) { - $element['#default_value'] = $option_value === '' ? NULL : $option_value; - } - } - } - elseif ($default_value !== '') { - // Convert default value to a list if necessary. - if ($component['extra']['multiple']) { - $varray = explode(',', $default_value); - foreach ($varray as $key => $v) { - $v = trim($v); - if ($v !== '') { - $element['#default_value'][] = $v; - } - } - } - else { - $element['#default_value'] = $default_value; + if ($component['extra']['multiple']) { + // Set the value as an array. + $element['#default_value'] = array(); + foreach ($value as $option_value) { + $element['#default_value'][] = $option_value === '' ? NULL : $option_value; } } - elseif ($component['extra']['multiple']) { - $element['#default_value'] = array(); + else { + // Set the value as a single string. + $option_value = reset($value); + $element['#default_value'] = $option_value === '' ? NULL : $option_value; } - + if ($component['extra']['other_option'] && module_exists('select_or_other')) { // Set display as a select_or_other element: $element['#type'] = 'select_or_other'; $element['#other'] = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...'); + $element['#translatable'][] = 'other'; $element['#other_title'] = $element['#title'] . ' ' . $element['#other']; $element['#other_title_display'] = 'invisible'; $element['#other_unknown_defaults'] = 'other'; @@ -410,11 +421,6 @@ function _webform_render_select($component, $value = NULL, $filter = TRUE) { $element['#type'] = 'checkboxes'; $element['#theme_wrappers'] = array_merge(array('checkboxes'), $element['#theme_wrappers']); $element['#process'] = array_merge(element_info_property('checkboxes', '#process'), array('webform_expand_select_ids')); - - // Entirely replace the normal expand checkboxes with our custom version. - // This helps render checkboxes in multipage forms. - $process_key = array_search('form_process_checkboxes', $element['#process']); - $element['#process'][$process_key] = 'webform_expand_checkboxes'; } else { // Set display as a radio set. @@ -459,35 +465,6 @@ function webform_expand_select_or_other($element) { return $element; } -/** - * Webform-specific checkbox #process function. Sanitize values before output. - */ -function webform_expand_checkboxes($element) { - // Elements that have a value set are already in the form structure cause - // them not to be written when the expand_checkboxes function is called. - $default_value = array(); - foreach (element_children($element) as $key) { - if (isset($element[$key]['#default_value'])) { - $default_value[$key] = $element[$key]['#default_value']; - unset($element[$key]); - } - } - - $element = form_process_checkboxes($element); - - // Escape the values of checkboxes. - foreach (element_children($element) as $key) { - $element[$key]['#return_value'] = check_plain($element[$key]['#return_value']); - $element[$key]['#name'] = $element['#name'] . '[' . $element[$key]['#return_value'] . ']'; - } - - foreach ($default_value as $key => $val) { - $element[$key]['#default_value'] = $val; - } - - return $element; -} - /** * FAPI process function to rename IDs attached to checkboxes and radios. */ @@ -509,6 +486,10 @@ function webform_expand_select_ids($element) { * Implements _webform_display_component(). */ function _webform_display_select($component, $value, $format = 'html') { + // Sort values by numeric key. These may be in alphabetic order from the database query, + // which is not numeric order for keys '10' and higher. + $value = (array) $value; + ksort($value, SORT_NUMERIC); return array( '#title' => $component['name'], '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before', @@ -518,7 +499,7 @@ function _webform_display_select($component, $value, $format = 'html') { '#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'), '#format' => $format, '#options' => _webform_select_options($component, !$component['extra']['aslist']), - '#value' => (array) $value, + '#value' => $value, '#translatable' => array('title', 'options'), ); } @@ -553,7 +534,7 @@ function _webform_submit_select($component, $value) { // Checkboxes submit an integer value of 0 when unchecked. A checkbox // with a value of '0' is valid, so we can't use empty() here. if ($option_value === 0 && !$component['extra']['aslist'] && $component['extra']['multiple']) { - unset($value[$option_value]); + // Don't save unchecked values } else { $return[] = $option_value; @@ -565,6 +546,11 @@ function _webform_submit_select($component, $value) { $return[] = $option_value; } } + + // If no elements are selected, then save an empty string to indicate that this components should not be defaulted again + if (!$return) { + $return = array(''); + } } elseif (is_string($value)) { $return = $value; @@ -642,67 +628,76 @@ function theme_webform_display_select($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_select($component, $sids = array(), $single = FALSE) { +function _webform_analysis_select($component, $sids = array(), $single = FALSE, $join = NULL) { $options = _webform_select_options($component, TRUE); - $show_other_results = $single; - // Gather the normal results first (not "other" options). + // Create a generic query for the component. $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) - ->fields('wsd', array('data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']) - ->condition('data', '', '<>') - ->groupBy('data'); - $query->addExpression('COUNT(data)', 'datacount'); - - if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']) + ->condition('wsd.data', '', '<>'); + + if ($sids) { + $query->condition('wsd.sid', $sids, 'IN'); } - // Duplicate the query for other options. - if ($show_other_results) { - $other_query = clone($query); - $other_query->condition('data', array_keys($options), 'NOT IN'); - $other_query->orderBy('datacount', 'DESC'); + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } - $query->condition('data', array_keys($options), 'IN'); + // Clone the query for later use, if needed. + if ($component['extra']['other_option']) { + $count_query = clone $query; + if ($single) { + $other_query = clone $query; + } + } - $result = $query->execute(); $rows = array(); $other = array(); $normal_count = 0; - foreach ($result as $data) { - $display_option = isset($options[$data['data']]) ? $options[$data['data']] : $data['data']; - $rows[$data['data']] = array(webform_filter_xss($display_option), $data['datacount']); - $normal_count += $data['datacount']; - } - // Order the results according to the normal options array. - $ordered_rows = array(); - foreach (array_intersect_key($options, $rows) as $key => $label) { - $ordered_rows[] = $rows[$key]; + if ($options) { + // Gather the normal results first (not "other" options). + $query->addExpression('COUNT(wsd.data)', 'datacount'); + $result = $query + ->condition('wsd.data', array_keys($options), 'IN') + ->fields('wsd', array('data')) + ->groupBy('wsd.data') + ->execute(); + foreach ($result as $data) { + $display_option = isset($options[$data['data']]) ? $options[$data['data']] : $data['data']; + $rows[$data['data']] = array(webform_filter_xss($display_option), $data['datacount']); + $normal_count += $data['datacount']; + } + + // Order the results according to the normal options array. + $ordered_rows = array(); + foreach (array_intersect_key($options, $rows) as $key => $label) { + $ordered_rows[] = $rows[$key]; + } + $rows = $ordered_rows; } - $rows = $ordered_rows; + // Add a row for displaying the total unknown or user-entered values. if ($component['extra']['other_option']) { - $count_query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']) - ->condition('data', '', '<>'); $count_query->addExpression('COUNT(*)', 'datacount'); - if (count($sids)) { - $count_query->condition('sid', $sids, 'IN'); - } $full_count = $count_query->execute()->fetchField(); $other_count = $full_count - $normal_count; $display_option = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...'); - $other_text = ($other_count && !$show_other_results) ? $other_count . ' (' . l(t('view'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']) . ')' : $other_count; + $other_text = ($other_count && !$single) ? $other_count . ' (' . l(t('view'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']) . ')' : $other_count; $rows[] = array($display_option, $other_text); // If showing all results, execute the "other" query and append their rows. - if ($show_other_results) { + if ($single) { + $other_query->addExpression('COUNT(wsd.data)', 'datacount'); + $other_query + ->fields('wsd', array('data')) + ->groupBy('wsd.data'); + if ($options) { + $other_query->condition('wsd.data', array_keys($options), 'NOT IN'); + } $other_result = $other_query->execute(); foreach ($other_result as $data) { $other[] = array(check_plain($data['data']), $data['datacount']); @@ -727,6 +722,7 @@ function _webform_table_select($component, $value) { $options = _webform_select_options($component, TRUE); $value = (array) $value; + ksort($value, SORT_NUMERIC); $items = array(); // Set the value as a single string. foreach ($value as $option_value) { diff --git a/profiles/wcm_base/modules/contrib/webform/components/textarea.inc b/profiles/wcm_base/modules/contrib/webform/components/textarea.inc index 1e7e301c..41d0f57d 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/textarea.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/textarea.inc @@ -106,7 +106,7 @@ function _webform_edit_textarea($component) { /** * Implements _webform_render_component(). */ -function _webform_render_textarea($component, $value = NULL, $filter = TRUE) { +function _webform_render_textarea($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( @@ -125,6 +125,10 @@ function _webform_render_textarea($component, $value = NULL, $filter = TRUE) { '#translatable' => array('title', 'description'), ); + if ($component['required']) { + $element['#attributes']['required'] = 'required'; + } + if ($component['extra']['placeholder']) { $element['#attributes']['placeholder'] = $component['extra']['placeholder']; } @@ -176,14 +180,18 @@ function theme_webform_display_textarea($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_textarea($component, $sids = array()) { +function _webform_analysis_textarea($component, $sids = array(), $single = FALSE, $join = NULL) { $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) ->fields('wsd', array('no', 'data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $nonblanks = 0; diff --git a/profiles/wcm_base/modules/contrib/webform/components/textfield.inc b/profiles/wcm_base/modules/contrib/webform/components/textfield.inc index e7cb104b..7ada5fce 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/textfield.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/textfield.inc @@ -131,7 +131,7 @@ function _webform_edit_textfield($component) { /** * Implements _webform_render_component(). */ -function _webform_render_textfield($component, $value = NULL, $filter = TRUE) { +function _webform_render_textfield($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( @@ -149,6 +149,10 @@ function _webform_render_textfield($component, $value = NULL, $filter = TRUE) { '#translatable' => array('title', 'description', 'field_prefix', 'field_suffix'), ); + if ($component['required']) { + $element['#attributes']['required'] = 'required'; + } + if ($component['extra']['placeholder']) { $element['#attributes']['placeholder'] = $component['extra']['placeholder']; } @@ -214,14 +218,18 @@ function theme_webform_display_textfield($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_textfield($component, $sids = array()) { +function _webform_analysis_textfield($component, $sids = array(), $single = FALSE, $join = NULL) { $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) ->fields('wsd', array('data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $nonblanks = 0; diff --git a/profiles/wcm_base/modules/contrib/webform/components/time.inc b/profiles/wcm_base/modules/contrib/webform/components/time.inc index 6dcda65b..786dae56 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/time.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/time.inc @@ -21,6 +21,8 @@ function _webform_defaults_time() { 'required' => 0, 'extra' => array( 'timezone' => 'user', + 'start_time' => '', + 'end_time' => '', 'hourformat' => '12-hour', 'minuteincrements' => 1, 'title_display' => 0, @@ -61,6 +63,24 @@ function _webform_edit_time($component) { '#maxlength' => 127, '#weight' => 0, ); + $form['validation']['start_time'] = array( + '#type' => 'textfield', + '#title' => t('Start time'), + '#default_value' => $component['extra']['start_time'], + '#description' => t('The earliest time that may be entered into the field.'), + '#size' => 10, + '#weight' => 3, + '#parents' => array('extra', 'start_time'), + ); + $form['validation']['end_time'] = array( + '#type' => 'textfield', + '#title' => t('End time'), + '#default_value' => $component['extra']['end_time'], + '#description' => t('The latest time that may be entered into the field.'), + '#size' => 10, + '#weight' => 4, + '#parents' => array('extra', 'end_time'), + ); $form['extra']['timezone'] = array( '#type' => 'radios', '#title' => t('Default value timezone'), @@ -92,13 +112,32 @@ function _webform_edit_time($component) { '#weight' => 3, '#parents' => array('extra', 'minuteincrements'), ); + $form['#validate'] = array('_webform_edit_time_validate'); return $form; } +/** + * Implements hook_form_id_validate. + * + * Validate start and end times. + */ +function _webform_edit_time_validate($form, &$form_state) { + // Validate that the start and end times are valid. Don't validate the default + // time because with token substitution, it might not be valid at component + // definition time. The end time may be before the start time to faciliate + // time ranges spanning midnight. + foreach (array('start_time', 'end_time') as $field) { + $time[$field] = FALSE; + if (trim($form_state['values']['extra'][$field]) && ($time[$field] = strtotime('1-1-1970 UTC ' . $form_state['values']['extra'][$field])) === FALSE) { + form_set_error("extra][$field", t('The @field isn\'t a valid time.', array('@field' => $form['validation'][$field]['#title']))); + } + } +} + /** * Implements _webform_render_component(). */ -function _webform_render_time($component, $value = NULL, $filter = TRUE) { +function _webform_render_time($component, $value = NULL, $filter = TRUE, $submission = NULL) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( @@ -109,6 +148,8 @@ function _webform_render_time($component, $value = NULL, $filter = TRUE) { '#weight' => $component['weight'], '#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'], '#element_validate' => array('webform_validate_time'), + '#start_time' => trim($component['extra']['start_time']), + '#end_time' => trim($component['extra']['end_time']), '#hourformat' => $component['extra']['hourformat'], '#minuteincrements' => $component['extra']['minuteincrements'], '#default_value' => $filter ? webform_replace_tokens($component['value'], $node) : $component['value'], @@ -146,58 +187,62 @@ function webform_expand_time($element) { else { $default_values = array( 'hour' => '', - 'minute' => '', + 'minute' => '0', 'second' => '', ); } - - $first_hour = 0; - $last_hour = 23; - if ($element['#hourformat'] == '12-hour') { - $first_hour = 1; - $last_hour = 12; - $default_values = webform_time_convert($default_values, '12-hour'); - $default_values['ampm'] = $default_values['ampm'] ? $default_values['ampm'] : 'am'; + $start_hour = $element['#start_time'] ? date('G', strtotime('1-1-1970 ' . $element['#start_time'])) : FALSE; + $end_hour = $element['#end_time'] ? date('G', strtotime('1-1-1970 ' . $element['#end_time'])) : FALSE; + $reduced_range = ($start_hour !== FALSE && $start_hour > 0) || ($end_hour !== FALSE && $end_hour < 23); + $format_12_hour = $element['#hourformat'] == '12-hour'; + + // Generate the choices for the hour drop-down select. + $hours = $format_12_hour && !$reduced_range ? array_slice(range(0, 12), 1, 12, TRUE) : range(0, 23); + if ($format_12_hour && $reduced_range) { + $hours = array_map(function($hour) { + return (1 + ($hour + 11) % 12) . ($hour < 12 ? ' am' : ' pm'); + }, $hours); } - // Generate the choices for drop-down selects. - $hours[''] = t('hour'); - $minutes[''] = t('minute'); - for ($i = $first_hour; $i <= $last_hour; $i++) { - $hours[$i] = $i; - } - for ($i = 0; $i <= 59; $i += $element['#minuteincrements']) { - $minutes[$i] = $i < 10 ? "0$i" : $i; + // Prune the hours to the allowed range. + if ($reduced_range) { + // $start_hour of FALSE type-juggles nicely to 0. + $end_hour = $end_hour === FALSE ? 23 : $end_hour; + if ($start_hour <= $end_hour) { + $hours = array_intersect_key($hours, array_flip(range($start_hour, $end_hour))); + } + else { + $hours = array_intersect_key($hours, array_flip(range($start_hour, 23))) + + array_intersect_key($hours, array_flip(range(0, $end_hour))); + } } - $ampms = array('am' => t('am'), 'pm' => t('pm')); - // Adjust the default for minutes if needed, rounding up to the closest value. + // Generate the choices for the minute drop-down select. + $minutes = range(0, 59, $element['#minuteincrements']); + $minutes = array_combine($minutes, array_map(function ($minute) { + return substr('00' . $minute, -2); + }, $minutes)); + + // Add the labels to the drop-down selects. + $hours = array('' => t('Hour')) + $hours; + $minutes = array('' => t('Minute')) + $minutes; + + // Adjust the default for minutes if needed, rounding down if needed. + // Rounding down eliminate the problem of rounding up going to the next hour. + // Worse, rounding 23:59 up would actually be the next day, which can't be + // represented because time components aren't linked to date components. if (!isset($minutes[$default_values['minute']])) { - foreach ($minutes as $minute => $padded_minute) { - if ($minute > $default_values['minute']) { - $default_values['minute'] = $minute; - break; - } - } + $default_values['minute'] -= $default_values['minute'] % $element['#minuteincrements']; } - // If the above loop didn't set a value, it's because rounding up would go to - // the next hour. This gets quite a bit more complicated, since we need to - // deal with looping around on hours, as well as flipping am/pm. - if (!isset($minutes[$default_values['minute']])) { - $default_values['minute'] = 0; - $default_values['hour']++; - // If the hour rolls over also, set hour to the first hour in the list. - if (!isset($hours[$default_values['hour']])) { - $default_values['hour'] = $element['#hourformat'] == '12-hour' ? 1 : 0; - } - // If the hour has been incremented to 12:00 in 12-hour format, flip am/pm. - // Note that technically midnight and noon are neither am or pm, but common - // convention (and US standard) is to represent 12:00am as midnight. - // See http://en.wikipedia.org/wiki/Midnight#Start_and_end_of_day. - if ($element['#hourformat'] == '12-hour' && $default_values['hour'] == 12) { - $default_values['ampm'] = $default_values['ampm'] == 'am' ? 'pm' : 'am'; - } + // Set the overall default value. + if ($default_values['hour'] !== '') { + $element['#default_value'] = webform_date_string($default_values); + } + + // Convert default to 12-hour if needed. + if ($format_12_hour && !$reduced_range) { + $default_values = webform_time_convert($default_values, '12-hour'); } $element['hour'] = array( @@ -216,19 +261,14 @@ function webform_expand_time($element) { '#default_value' => $default_values['minute'], '#options' => $minutes, ); - if (strcmp($element['#hourformat'], '12-hour') == 0) { + if ($format_12_hour && !$reduced_range) { $element['ampm'] = array( '#type' => 'radios', - '#default_value' => $default_values['ampm'], - '#options' => $ampms, + '#default_value' => $default_values['ampm'] ? $default_values['ampm'] : 'am', + '#options' => array('am' => t('am'), 'pm' => t('pm')), ); } - // Set the overall default value. - if ($default_values['hour'] !== '') { - $element['#default_value'] = webform_date_string($default_values); - } - return $element; } @@ -247,6 +287,16 @@ function theme_webform_time($variables) { $element['minute']['#attributes']['class'][] = 'error'; } + // Add HTML5 required attribute, if needed. + if ($element['#required']) { + $element['hour']['#attributes']['required'] = 'required'; + $element['minute']['#attributes']['required'] = 'required'; + if (!empty($element['ampm'])) { + $element['ampm']['am']['#attributes']['required'] = 'required'; + $element['ampm']['pm']['#attributes']['required'] = 'required'; + } + } + $output = '<div class="webform-container-inline">' . drupal_render($element['hour']) . drupal_render($element['minute']) . drupal_render($element['ampm']) . '</div>'; return $output; @@ -256,19 +306,46 @@ function webform_validate_time($element, $form_state) { $form_key = $element['#webform_component']['form_key']; // Check if the user filled the required fields. - foreach ($element['#hourformat'] == '12-hour' ? array('hour', 'minute', 'ampm') : array('hour', 'minute') as $field_type) { - if ($element[$field_type]['#value'] === '' && $element['#required']) { - form_error($element, t('!name field is required.', array('!name' => $element['#title']))); - return; + if ($element['#required']) { + foreach (array('hour', 'minute', 'ampm') as $field_type) { + if (isset($element[$field_type]) && $element[$field_type]['#value'] === '') { + form_error($element, t('!name field is required.', array('!name' => $element['#title']))); + return; + } } } - // Check for a valid time. - if ($element['hour']['#value'] !== '' || $element['minute']['#value'] !== '') { + // Check for a valid time. Allow a minute with no hour as "no time set". + if ($element['hour']['#value'] !== '' ) { if (!is_numeric($element['hour']['#value']) || !is_numeric($element['minute']['#value']) || (isset($element['ampm']) && $element['ampm']['#value'] === '')) { form_error($element, t('Entered !name is not a valid time.', array('!name' => $element['#title']))); return; } + + // Enforce the start and end times, if any. + $timestamp = strtotime($element['hour']['#value'] . ':' . $element['minute']['#value'] . ' ' . + (isset($element['ampm']) ? $element['ampm']['#value'] : '')); + $start_time = strtotime($element['#start_time']); + $end_time = strtotime($element['#end_time']); + $subs = array( + '@start_time' => $element['#start_time'], + '@end_time' => $element['#end_time'], + ); + if ($start_time !== FALSE && $end_time !== FALSE && $start_time > $end_time) { + // Validate as "over midnight" date range. + if ($end_time < $timestamp && $timestamp < $start_time) { + form_error($element, t('The entered time must be from @start_time to midnight to @end_time.', $subs)); + } + } + else { + // Validate the start and end times are a regular (over noon) time range. + if ($start_time !== FALSE && $timestamp < $start_time) { + form_error($element, t('The entered time must be no earlier than @start_time.', $subs)); + } + if ($end_time !== FALSE && $timestamp > $end_time) { + form_error($element, t('The entered time must be no later than @end_time.', $subs)); + } + } } } @@ -327,15 +404,19 @@ function theme_webform_display_time($variables) { /** * Implements _webform_analysis_component(). */ -function _webform_analysis_time($component, $sids = array()) { +function _webform_analysis_time($component, $sids = array(), $single = FALSE, $join = NULL) { $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC)) ->fields('wsd', array('no', 'data')) - ->condition('nid', $component['nid']) - ->condition('cid', $component['cid']) - ->orderBy('sid'); + ->condition('wsd.nid', $component['nid']) + ->condition('wsd.cid', $component['cid']) + ->orderBy('wsd.sid'); if (count($sids)) { - $query->condition('sid', $sids, 'IN'); + $query->condition('wsd.sid', $sids, 'IN'); + } + + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); } $result = $query->execute(); diff --git a/profiles/wcm_base/modules/contrib/webform/css/webform-admin.css b/profiles/wcm_base/modules/contrib/webform/css/webform-admin.css index e6a42da5..520a86cf 100644 --- a/profiles/wcm_base/modules/contrib/webform/css/webform-admin.css +++ b/profiles/wcm_base/modules/contrib/webform/css/webform-admin.css @@ -78,6 +78,9 @@ height: 12px; margin: 0 2px 2px; } +.webform-component-select-suffix { + margin-top: 10px; +} .webform-select-list-format table { border: 1px solid; width: auto; @@ -164,12 +167,13 @@ td.webform-email-option { padding-left: 1em; padding-right: 8em; margin-left: 3em; - max-width: 500px; + max-width: 600px; } .webform-conditional-new { text-align: right; - margin-left: 8em; + margin-left: 11em; + padding-right: 0; } .webform-conditional-if { diff --git a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter.inc b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter.inc index 670600e0..3de1396f 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter.inc @@ -4,6 +4,7 @@ */ class webform_exporter { public $options = array(); + public $export_wordrap; /** * Constructor for webform_exporter classes. @@ -13,6 +14,30 @@ class webform_exporter { */ function __construct($options) { $this->options = $options; + $this->export_wordwrap = webform_variable_get('webform_export_wordwrap'); + } + + /** + * Determines whether a cell is eligible for word-wrapping based upon + * position in file and the contents of cell. + * + * Return true when the global word-wrapping option is enabled and the cell + * is anything other than the first column in either of the first two rows. + * By default, these rows are long and are intended to overlap the columns + * to the right. Also returns true when the cell contains a return character. + * + * @param int $row + * Row number, counting from 0. + * @param int $column + * Column number, counting from 0. + * @param string $value + * The value of the cell. + * @return boolean + * Whether the cell position is elegible for wordwrapping. + */ + function wrappable($row, $column, $value) { + return strpos($value, "\n") !== FALSE || + $this->export_wordwrap && ($row > 2 || $column > 0 || $this->options['header_keys'] < 0); } /** diff --git a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_xlsx.inc b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_xlsx.inc index 9a5b676c..dbdcaacc 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_xlsx.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_xlsx.inc @@ -17,11 +17,17 @@ class webform_exporter_excel_xlsx extends webform_exporter { $output = '<row>'; $utc_timezone = new DateTimeZone('UTC'); foreach ($data as $key => $value) { + // Strip UTF8 characters that are not legal in XML files. + // See http://www.w3.org/TR/xml/#charsets + // See http://stackoverflow.com/questions/3466035/how-to-skip-invalid-characters-in-xml-file-using-php + // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + $value = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', '', $value); + $cell_position = $col . $row; - if (empty($value) && strlen($value) === 0) { + if (strlen($value) === 0) { // Skip empty cells. } - elseif (is_numeric($value)) { + elseif (is_numeric($value) && $value[0] !== '+') { $output .= '<c r="' . $cell_position . '"><v>'; $output .= $value; $output .= '</v></c>'; @@ -76,8 +82,8 @@ class webform_exporter_excel_xlsx extends webform_exporter { } } else { - $output .= '<c r="' . $cell_position . '" t="inlineStr"' . (strpos($value, "\n") === FALSE ? '' : ' s="4"') . '><is><t>'; - $output .= htmlspecialchars($value, ENT_QUOTES); + $output .= '<c r="' . $cell_position . '" t="inlineStr"' . ($this->wrappable($row_count, $key, $value) ? ' s="4"' : '') . '><is><t>'; + $output .= htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); $output .= '</t></is></c>'; } @@ -160,7 +166,11 @@ class webform_exporter_excel_xlsx extends webform_exporter { // Switch the results file name to the new zip (xlsx) file. unlink($file_uri); - rename($zip_uri, $file_uri); + if (!@rename($zip_uri, $file_uri)) { + // The file could not be renamed, probably due to different stream wrappers during drush wfx execution. + copy($zip_uri, $file_uri); + unlink($zip_uri); + } } } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.admin.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.admin.inc index 5af87f58..8306e87e 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.admin.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.admin.inc @@ -60,6 +60,20 @@ function webform_admin_settings() { '#description' => t('The default subject line of any e-mailed results.'), ); + $form['email']['webform_email_replyto'] = array( + '#type' => 'checkbox', + '#title' => t('Use Reply-To header'), + '#default_value' => webform_variable_get('webform_email_replyto'), + '#description' => t('Sends all e-mail from the domain of the default address above and sets the "Reply-To" header to the actual sender. Helps prevent e-mail from being flagged as spam.'), + ); + + $form['email']['webform_email_html_capable'] = array( + '#type' => 'checkbox', + '#title' => t('HTML mail system'), + '#default_value' => webform_variable_get('webform_email_html_capable'), + '#description' => t('Whether the mail system configured for webform is capable of sending mail in HTML format.'), + ); + $form['email']['webform_default_format'] = array( '#type' => 'radios', '#title' => t('Format'), @@ -67,9 +81,13 @@ function webform_admin_settings() { 0 => t('Plain text'), 1 => t('HTML'), ), - '#default_value' => variable_get('webform_default_format', 0), + '#default_value' => webform_variable_get('webform_default_format'), '#description' => t('The default format for new e-mail settings. Webform e-mail options take precedence over the settings for system-wide e-mails configured in MIME mail.'), - '#access' => webform_email_html_capable(), + '#states' => array( + 'visible' => array( + ':input[name="webform_email_html_capable"]' => array('checked' => TRUE), + ), + ), ); $form['email']['webform_format_override'] = array( @@ -79,9 +97,13 @@ function webform_admin_settings() { 0 => t('Per-webform configuration of e-mail format'), 1 => t('Send all e-mails in the default format'), ), - '#default_value' => variable_get('webform_format_override', 0), + '#default_value' => webform_variable_get('webform_format_override'), '#description' => t('Force all webform e-mails to be sent in the default format.'), - '#access' => webform_email_html_capable(), + '#states' => array( + 'visible' => array( + ':input[name="webform_email_html_capable"]' => array('checked' => TRUE), + ), + ), ); $form['progressbar'] = array( @@ -124,15 +146,6 @@ function webform_admin_settings() { '#weight' => 20, ); - $form['advanced']['webform_search_index'] = array( - '#type' => 'checkbox', - '#checked_value' => 1, - '#title' => t('Include webform forms in search index'), - '#default_value' => variable_get('webform_search_index', 1), - '#description' => t('When selected, all Webform nodes will have their form components indexed by the search engine.'), - '#access' => module_exists('search'), - ); - $form['advanced']['webform_tracking_mode'] = array( '#type' => 'radios', '#title' => t('Track anonymous users by:'), @@ -141,8 +154,8 @@ function webform_admin_settings() { 'ip_address' => t('IP address only'), 'strict' => t('Both cookie and IP address (most strict)'), ), - '#default_value' => variable_get('webform_tracking_mode', 'cookie'), - '#description' => t('<a href="http://www.wikipedia.org/wiki/HTTP_cookie">Cookies</a> can be used to help prevent the same user from repeatedly submitting a webform. Limiting by IP address is more effective against repeated submissions, but may result in unintentional blocking of users sharing the same address. Logged-in users are always tracked by their user ID and are not affected by this option.'), + '#default_value' => webform_variable_get('webform_tracking_mode'), + '#description' => t('<a href="http://www.wikipedia.org/wiki/HTTP_cookie">Cookies</a> can be used to help prevent the same user from repeatedly submitting a webform. Limiting by IP address is more effective against repeated submissions, but may result in unintentional blocking of users sharing the same address. Confidential submissions are tracked by cookie only. Logged-in users are always tracked by their user ID and are not affected by this option.'), ); $form['advanced']['webform_email_address_format'] = array( @@ -152,10 +165,33 @@ function webform_admin_settings() { 'long' => t('Long format: "Example Name" <name@example.com>'), 'short' => t('Short format: name@example.com'), ), - '#default_value' => variable_get('webform_email_address_format', 'long'), + '#default_value' => webform_variable_get('webform_email_address_format'), '#description' => t('Most servers support the "long" format which will allow for more friendly From addresses in e-mails sent. However many Windows-based servers are unable to send in the long format. Change this option if experiencing problems sending e-mails with Webform.'), ); + $form['advanced']['webform_email_address_individual'] = array( + '#type' => 'radios', + '#title' => t('E-mailing multiple recipients'), + '#options' => array( + '0' => t('Send a single e-mail to all recipients'), + '1' => t('Send individual e-mails to each recipient'), + ), + '#default_value' => webform_variable_get('webform_email_address_individual'), + '#description' => t('Individual e-mails increases privacy by not revealing the addresses of other recipients. A single e-mail to all recipients lets them use "Reply All" to communicate.'), + ); + + $date_format_options = array(); + foreach (system_get_date_types() as $type => $type_info) { + $date_format_options[$type] = t('@title — @sample', array('@title' => $type_info['title'], '@sample' => format_date(REQUEST_TIME, 'custom', webform_date_format($type)))); + } + $form['advanced']['webform_date_type'] = array( + '#type' => 'select', + '#title' => t('Date format'), + '#options' => $date_format_options, + '#default_value' => webform_variable_get('webform_date_type'), + '#description' => t('Choose the format for the display of date components. Only the date portion of the format is used. Reporting and export use the short format.'), + ); + $form['advanced']['webform_export_format'] = array( '#type' => 'radios', '#title' => t('Default export format'), @@ -179,6 +215,17 @@ function webform_admin_settings() { ), ); + $form['advanced']['webform_export_wordwrap'] = array( + '#type' => 'radios', + '#title' => t('Export word-wrap'), + '#options' => array( + '0' => t('Only text containing return characters'), + '1' => t('All text'), + ), + '#default_value' => webform_variable_get('webform_export_wordwrap'), + '#description' => t('Some export formats, such as Microsoft Excel, support word-wrapped text cells.'), + ); + $form['advanced']['webform_submission_access_control'] = array( '#type' => 'radios', '#title' => t('Submission access control'), @@ -186,14 +233,14 @@ function webform_admin_settings() { '1' => t('Select the user roles that may submit each individual webform'), '0' => t('Disable Webform submission access control'), ), - '#default_value' => variable_get('webform_submission_access_control', 1), + '#default_value' => webform_variable_get('webform_submission_access_control'), '#description' => t('By default, the configuration form for each webform allows the administrator to choose which roles may submit the form. You may want to allow users to always submit the form if you are using a separate node access module to control access to webform nodes themselves.'), ); $form['advanced']['webform_email_select_max'] = array( '#type' => 'textfield', '#title' => t("Select email mapping limit"), - '#default_value' => variable_get('webform_email_select_max', 50), + '#default_value' => webform_variable_get('webform_email_select_max'), '#description' => t('When mapping emails addresses to a select component, limit the choice to components with less than the amount of options indicated. This is to avoid flooding the email settings form. '), ); @@ -254,6 +301,12 @@ function theme_webform_admin_settings($variables) { * Menu callback for admin/content/webform. Displays all webforms on the site. */ function webform_admin_content() { + // Determine whether views or hard-coded tables should be used for the webforms table. + if (!webform_variable_get('webform_table')) { + $view = views_get_view('webform_webforms'); + return $view->preview('default'); + } + $query = db_select('webform', 'w'); $query->join('node', 'n', 'w.nid = n.nid'); $query->fields('n'); @@ -269,7 +322,7 @@ function webform_admin_type_list() { $webform_type_list = ''; $webform_type_count = count($webform_types); foreach ($webform_types as $n => $type) { - $webform_type_list .= l(node_type_get_name($type), 'node/add/' . $type); + $webform_type_list .= l(node_type_get_name($type), 'node/add/' . str_replace('_', '-', $type)); if ($n + 1 < $webform_type_count) { $webform_type_list .= $webform_type_count == 2 ? ' ' : ', '; } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.components.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.components.inc index 0900f651..c2b9e733 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.components.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.components.inc @@ -144,6 +144,10 @@ function webform_components_form($form, $form_state, $node) { '#value' => t('Save'), '#access' => count($node->webform['components']) > 0, ); + $form['warning'] = array( + '#weight' => -1, + ); + webform_input_vars_check($form, $form_state, 'components', 'warning'); return $form; } @@ -277,6 +281,7 @@ function _webform_components_form_rows($node, $cid, $component, $level, &$form, */ function theme_webform_components_form($variables) { $output = ''; + $output .= drupal_render_children($variables['form']['warning']); $output .= theme('table', array('header' => $variables['header'], 'rows' => $variables['rows'], 'attributes' => array('id' => 'webform-components'))); $output .= drupal_render_children($variables['form']); return $output; @@ -851,25 +856,38 @@ function webform_component_delete($node, $component) { // Delete any conditionals dependent on this component. module_load_include('inc', 'webform', 'includes/webform.conditionals'); foreach ($node->webform['conditionals'] as $rgid => $conditional) { - // Delete the whole conditional if this component is the target. - if ($conditional['target_type'] === 'component' && $conditional['target'] == $component['cid']) { - webform_conditional_delete($node, $conditional); - } - else { - foreach ($conditional['rules'] as $rid => $rule) { - if ($rule['source_type'] === 'component' && $rule['source'] == $component['cid']) { - // Delete the conditional if this component is the only source. - if (count($conditional['rules']) === 1) { + $delete_conditional = FALSE; + $specs = array( + array( + 'field' => 'rules', + 'table' => 'webform_conditional_rules', + 'component' => 'source', + 'component_type' => 'source_type', + 'index' => 'rid', + ), + array( + 'field' => 'actions', + 'table' => 'webform_conditional_actions', + 'component' => 'target', + 'component_type' => 'target_type', + 'index' => 'aid', + ), + ); + foreach ($specs as $spec) { + $count = count($conditional[$spec['field']]); + foreach ($conditional[$spec['field']] as $key => $thing) { + if ($thing[$spec['component_type']] === 'component' && $thing[$spec['component']] == $component['cid']) { + if ($count == 1) { + // Delete the conditional if this component is the only source or target. webform_conditional_delete($node, $conditional); + break 2; } - // Delete only one rule if this component is one of multiple rules. - else { - db_delete('webform_conditional_rules') - ->condition('nid', $node->nid) - ->condition('rgid', $rgid) - ->condition('rid', $rid) - ->execute(); - } + db_delete($spec['table']) + ->condition('nid', $node->nid) + ->condition('rgid', $rgid) + ->condition($spec['index'], $key) + ->execute(); + $count--; } } } @@ -934,10 +952,12 @@ function webform_component_feature($type, $feature) { 'title_display' => TRUE, 'title_inline' => TRUE, 'conditional' => TRUE, + 'conditional_action_set' => FALSE, 'spam_analysis' => FALSE, 'group' => FALSE, 'attachment' => FALSE, 'private' => TRUE, + 'placeholder' => FALSE, 'wrapper_classes' => TRUE, 'css_classes' => TRUE, 'views_range' => FALSE, @@ -1060,9 +1080,11 @@ function theme_webform_component_select($variables) { $header = array(array('class' => array('select-all'), 'data' => ' ' . t('Include all components'))); } foreach (element_children($element) as $key) { - $rows[] = array( - theme('indentation', array('size' => $element[$key]['#indent'])) . drupal_render($element[$key]), - ); + if ($key != 'suffix') { + $rows[] = array( + theme('indentation', array('size' => $element[$key]['#indent'])) . drupal_render($element[$key]), + ); + } } $element['#type'] = 'fieldset'; @@ -1082,6 +1104,11 @@ function theme_webform_component_select($variables) { $element['#children'] = '<div class="webform-component-select-wrapper">' . theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => FALSE)) . '</div>'; } + if (isset($element['suffix'])) { + $element['#children'] .= '<div class="webform-component-select-suffix">' . drupal_render($element['suffix']) . '</div>'; + } + + return theme('fieldset', array('element' => $element)); } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.conditionals.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.conditionals.inc index 9a68e4f3..7578a7bb 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.conditionals.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.conditionals.inc @@ -24,13 +24,21 @@ function webform_conditionals_form($form, &$form_state, $node) { else { $conditionals = $node->webform['conditionals']; } - // Empty out any conditionals that have no rules. + // Empty out any conditionals that have no rules or actions. foreach ($conditionals as $rgid => $conditional) { - if (empty($conditional['rules'])) { + if (empty($conditional['rules']) || empty($conditional['actions'])) { unset($conditionals[$rgid]); } } + // Check the current topological sort order for the conditionals and report any errors, + // but only for actual form submissions and not for ajax-related form builds, such as + // adding or removing a condtion or conditional group. + if (empty($form_state['triggering_element']['#ajax'])) { + $node->webform['conditionals'] = $conditionals; + webform_get_conditional_sorter($node)->reportErrors($conditionals); + } + $form['#tree'] = TRUE; $form['#node'] = $node; @@ -64,8 +72,9 @@ function webform_conditionals_form($form, &$form_state, $node) { '#nid' => $node->nid, '#sources' => $source_list, '#actions' => array( - 'hide' => t('hide'), - 'show' => t('show'), + 'show' => t('shown'), + 'require' => t('required'), + 'set' => t('set to'), ), '#targets' => $target_list, '#parents' => array('conditionals', $rgid), @@ -109,6 +118,11 @@ function webform_conditionals_form($form, &$form_state, $node) { '#submit' => array('webform_conditionals_form_submit'), ); + // Estimate if the form is too long for PHP max_input_vars and detect whether a previous submission was truncated. + // The estimate will be accurate because the form elements for this page are well known. Ajax use of this + // page will not generate user-visible errors, so a preflight may be the only indication to the user that + // the page is too long. + webform_input_vars_check($form, $form_state, 'conditionals', ''); return $form; } @@ -133,8 +147,14 @@ function webform_conditionals_form_add($form, &$form_state) { ), ), 'andor' => 'and', - 'action' => 'hide', - 'target' => NULL, + 'actions' => array( + array( + 'target' => NULL, + 'invert' => NULL, + 'action' => NULL, + 'argument' => NULL, + ), + ), 'weight' => $weight + 1, ); $form_state['rebuild'] = TRUE; @@ -152,12 +172,38 @@ function webform_conditionals_form_validate($form, &$form_state) { return; } + $node = $form['#node']; + $components = $node->webform['components']; + $component_options = webform_component_options(); foreach ($form_state['complete form']['conditionals'] as $conditional_key => $element) { if (substr($conditional_key, 0, 1) !== '#' && $conditional_key !== 'new') { $conditional = $element['conditional']; + $targets = array(); + foreach ($conditional['actions'] as $action_key => $action) { + if (substr($action_key, 0, 1) !== '#') { + $target_id = $action['target']['#value']; + if (isset($targets[$target_id])) { + form_set_error('conditionals][' . $conditional_key . '][actions][' . $action_key . '][target', + t('A conditional cannot show or hide a component more than once. (%target).', + array('%target' => $components[$action['target']['#value']]['name']))); + } + $component_type = $node->webform['components'][$action['target']['#value']]['type']; + if (!webform_conditional_action_able($component_type, $action['action']['#value'])) { + form_set_error('conditionals][' . $conditional_key . '][actions][' . $action_key . '][action', + t('A component of type %type can\'t be %action. (%target)', + array( + '%action' => $action['action']['#options'][$action['action']['#value']], + '%type' => $component_options[$component_type], + '%target' => $components[$action['target']['#value']]['name']))); + } + $targets[$target_id] = $target_id; + } + } foreach ($conditional['rules'] as $rule_key => $rule) { - if (substr($rule_key, 0, 1) !== '#' && $rule['source']['#value'] === $conditional['target']['#value']) { - form_set_error('conditionals][' . $conditional_key . '][rules][' . $rule_key . '][source', t('The subject of the conditional cannot be the same as the component that is shown or hidden (%target).', array('%target' => $conditional['target']['#options'][$conditional['target']['#value']]))); + if (substr($rule_key, 0, 1) !== '#' && in_array($rule['source']['#value'], $targets)) { + form_set_error('conditionals][' . $conditional_key . '][rules][' . $rule_key . '][source', + t('The subject of the conditional cannot be the same as the component that is shown or hidden (%target).', + array('%target' => $components[$rule['source']['#value']]['name']))); } } } @@ -178,23 +224,18 @@ function webform_conditionals_form_submit($form, &$form_state) { unset($form_state['values']['conditionals']['new']); $conditionals = array(); - $targets = array(); // Fill in missing properties for each value so that it can save properly. // TODO: Remove hard-coded source and target type. foreach ($form_state['values']['conditionals'] as $rgid => $conditional) { $conditional['rgid'] = $rgid; - $conditional['target_type'] = 'component'; foreach ($conditional['rules'] as $rid => $rule) { $conditional['rules'][$rid]['source_type'] = 'component'; } - $conditionals[$rgid] = $conditional; - // Warn if there are multiple conditionals targetting the same component. - if (isset($targets[$conditional['target']])) { - drupal_set_message(t('More than one conditional hides or shows component "@name". Only the most-recently evaluated conditional will determine whether "@name" is displayed.', - array('@name' => $node->webform['components'][$conditional['target']]['name'])), 'warning', FALSE); + foreach ($conditional['actions'] as $aid => $action) { + $conditional['actions'][$aid]['target_type'] = 'component'; } - $targets[$conditional['target']] = TRUE; + $conditionals[$rgid] = $conditional; } $node->webform['conditionals'] = $conditionals; @@ -306,7 +347,7 @@ function _webform_conditional_expand($element) { $element['rules'][$rid]['value'] = array( '#type' => 'textfield', '#title' => t('Value'), - '#size' => 30, + '#size' => 20, '#default_value' => $element['#default_value']['rules'][$rid]['value'], ); $element['rules'][$rid]['remove'] = array( @@ -355,18 +396,61 @@ function _webform_conditional_expand($element) { // Remove the last and/or. unset($element['rules'][$rid]['andor']); - $element['action'] = array( - '#type' => 'select', - '#title' => t('Action'), - '#options' => $element['#actions'], - '#default_value' => $element['#default_value']['action'], - ); - $element['target'] = array( - '#type' => 'select', - '#title' => t('Operator'), - '#options' => $element['#targets'], - '#default_value' => $element['#default_value']['target'], - ); + foreach ($element['#default_value']['actions'] as $aid => $action) { + $element['actions'][$aid]['target'] = array( + '#type' => 'select', + '#title' => t('Target'), + '#options' => $element['#targets'], + '#default_value' => $element['#default_value']['actions'][$aid]['target'], + ); + $element['actions'][$aid]['invert'] = array( + '#type' => 'select', + '#title' => t('Is/Isn\'t'), + '#options' => array( + '0' => t('is'), + '1' => t('isn\'t'), + ), + '#default_value' => $element['#default_value']['actions'][$aid]['invert'], + ); + $element['actions'][$aid]['action'] = array( + '#type' => 'select', + '#title' => t('Action'), + '#options' => $element['#actions'], + '#default_value' => $element['#default_value']['actions'][$aid]['action'], + ); + $element['actions'][$aid]['argument'] = array( + '#type' => 'textfield', + '#title' => t('Argument'), + '#size' => 20, + '#default_value' => $element['#default_value']['actions'][$aid]['argument'], + ); + $element['actions'][$aid]['remove'] = array( + '#type' => 'submit', + '#value' => t('-'), + '#submit' => array('webform_conditional_element_remove'), + '#name' => implode('_', $element['#parents']) . '_actions_' . $aid . '_remove', + '#attributes' => array('class' => array('webform-conditional-action-remove')), + '#ajax' => array( + 'progress' => 'none', + 'callback' => 'webform_conditional_element_ajax', + 'wrapper' => $wrapper_id, + 'event' => 'click', + ), + ); + $element['actions'][$aid]['add'] = array( + '#type' => 'submit', + '#value' => t('+'), + '#submit' => array('webform_conditional_element_add'), + '#name' => implode('_', $element['#parents']) . '_actions_' . $aid . '_add', + '#attributes' => array('class' => array('webform-conditional-action-add')), + '#ajax' => array( + 'progress' => 'none', + 'callback' => 'webform_conditional_element_ajax', + 'wrapper' => $wrapper_id, + 'event' => 'click', + ), + ); + } return $element; } @@ -462,13 +546,13 @@ function webform_conditional_element_add($form, &$form_state) { } /** - * Submit handler for webform_conditional elements to remove a rule. + * Submit handler for webform_conditional elements to remove a rule or action. */ function webform_conditional_element_remove($form, &$form_state) { $button = $form_state['clicked_button']; $parents = $button['#parents']; $action = array_pop($parents); - $current_rid = array_pop($parents); + $current_id = array_pop($parents); // Recurse through the form values until we find the root Webform conditional. $parent_values = &$form_state['values']; @@ -478,8 +562,8 @@ function webform_conditional_element_remove($form, &$form_state) { } } - // Remove this rule from the list of conditionals. - unset($parent_values[$current_rid]); + // Remove this rule or action from the list of conditionals. + unset($parent_values[$current_id]); $form_state['rebuild'] = TRUE; } @@ -499,6 +583,10 @@ function webform_conditional_element_ajax($form, $form_state) { $element = $form; foreach ($parents as $key) { + if (!isset($element[$key])) { + // The entire conditional has been removed + return ''; + } $element = $element[$key]; } @@ -554,20 +642,39 @@ function theme_webform_conditional($variables) { } // Hide labels. - $element['action']['#title_display'] = 'none'; - $element['target']['#title_display'] = 'none'; + foreach (element_children($element['actions']) as $aid) { + // Hide labels. + $element['actions'][$aid]['target']['#title_display'] = 'none'; + $element['actions'][$aid]['invert']['#title_display'] = 'none'; + $element['actions'][$aid]['action']['#title_display'] = 'none'; + $element['actions'][$aid]['argument']['#title_display'] = 'none'; + + $target = '<div class="webform-conditional-target">' . drupal_render($element['actions'][$aid]['target']) . '</div>'; + $invert = '<div class="webform-conditional-invert">' . drupal_render($element['actions'][$aid]['invert']) . '</div>'; + $action = '<div class="webform-conditional-action">' . drupal_render($element['actions'][$aid]['action']) . '</div>'; + $argument = '<div class="webform-conditional-argument">' . drupal_render($element['actions'][$aid]['argument']) . '</div>'; + + $target_phrase = t('then !target !invert !action !argument', array( + '!target' => $target, + '!invert' => $invert, + '!action' => $action, + '!argument' => $argument, + )); - $action = '<div class="webform-conditional-action">' . drupal_render($element['action']) . '</div>'; - $target = '<div class="webform-conditional-target">' . drupal_render($element['target']) . '</div>'; + $output .= '<div class="webform-conditional-action">'; + $output .= '<div class="webform-container-inline webform-conditional-condition">'; + $output .= $target_phrase; + $output .= '</div>'; - $target_phrase = t('then !action !target', array( - '!action' => $action, - '!target' => $target, - )); + if (isset($element['actions'][$aid]['add']) || isset($element['actions'][$aid]['remove'])) { + $output .= '<span class="webform-conditional-operations webform-container-inline">'; + $output .= drupal_render($element['actions'][$aid]['remove']); + $output .= drupal_render($element['actions'][$aid]['add']); + $output .= '</span>'; + } - $output .= '<div class="webform-container-inline">'; - $output .= $target_phrase; - $output .= '</div>'; + $output .= '</div>'; + } $output .= '</div>'; @@ -669,15 +776,25 @@ function _webform_conditional_operator_info() { 'comparison callback' => 'webform_conditional_operator_numeric_not_equal', 'js comparison callback' => 'conditionalOperatorNumericNotEqual', ); + $operators['numeric']['less_than'] = array( + 'label' => t('is less than'), + 'comparison callback' => 'webform_conditional_operator_numeric_less_than', + 'js comparison callback' => 'conditionalOperatorNumericLessThan', + ); + $operators['numeric']['less_than_equal'] = array( + 'label' => t('is less than or equal'), + 'comparison callback' => 'webform_conditional_operator_numeric_less_than_equal', + 'js comparison callback' => 'conditionalOperatorNumericLessThanEqual', + ); $operators['numeric']['greater_than'] = array( 'label' => t('is greater than'), 'comparison callback' => 'webform_conditional_operator_numeric_greater_than', 'js comparison callback' => 'conditionalOperatorNumericGreaterThan', ); - $operators['numeric']['less_than'] = array( - 'label' => t('is less than'), - 'comparison callback' => 'webform_conditional_operator_numeric_less_than', - 'js comparison callback' => 'conditionalOperatorNumericLessThan', + $operators['numeric']['greater_than_equal'] = array( + 'label' => t('is greater than or equal'), + 'comparison callback' => 'webform_conditional_operator_numeric_greater_than_equal', + 'js comparison callback' => 'conditionalOperatorNumericGreaterThanEqual', ); $operators['numeric']['empty'] = array( 'label' => t('is blank'), @@ -705,6 +822,30 @@ function _webform_conditional_operator_info() { 'js comparison callback' => 'conditionalOperatorStringNotEqual', 'form callback' => 'webform_conditional_form_select', ); + $operators['select']['less_than'] = array( + 'label' => t('is before'), + 'comparison callback' => 'webform_conditional_operator_select_less_than', + 'js comparison callback' => 'conditionalOperatorSelectLessThan', + 'form callback' => 'webform_conditional_form_select', + ); + $operators['select']['less_than_equal'] = array( + 'label' => t('is or is before'), + 'comparison callback' => 'webform_conditional_operator_select_less_than_equal', + 'js comparison callback' => 'conditionalOperatorSelectLessThanEqual', + 'form callback' => 'webform_conditional_form_select', + ); + $operators['select']['greater_than'] = array( + 'label' => t('is after'), + 'comparison callback' => 'webform_conditional_operator_select_greater_than', + 'js comparison callback' => 'conditionalOperatorSelectGreaterThan', + 'form callback' => 'webform_conditional_form_select', + ); + $operators['select']['greater_than_equal'] = array( + 'label' => t('is or is after'), + 'comparison callback' => 'webform_conditional_operator_select_greater_than_equal', + 'js comparison callback' => 'conditionalOperatorSelectGreaterThanEqual', + 'form callback' => 'webform_conditional_form_select', + ); $operators['select']['empty'] = array( 'label' => t('is empty'), 'comparison callback' => 'webform_conditional_operator_string_empty', @@ -726,6 +867,13 @@ function _webform_conditional_operator_info() { 'js comparison callback' => 'conditionalOperatorDateEqual', 'form callback' => 'webform_conditional_form_date', ); + $operators['date']['not_equal'] = array( + 'label' => t('is not on'), + 'comparison callback' => 'webform_conditional_operator_datetime_not_equal', + 'comparison prepare js' => 'webform_conditional_prepare_date_js', + 'js comparison callback' => 'conditionalOperatorDateNotEqual', + 'form callback' => 'webform_conditional_form_date', + ); $operators['date']['before'] = array( 'label' => t('is before'), 'comparison callback' => 'webform_conditional_operator_datetime_before', @@ -733,6 +881,13 @@ function _webform_conditional_operator_info() { 'js comparison callback' => 'conditionalOperatorDateBefore', 'form callback' => 'webform_conditional_form_date', ); + $operators['date']['before_equal'] = array( + 'label' => t('is on or before'), + 'comparison callback' => 'webform_conditional_operator_datetime_before_equal', + 'comparison prepare js' => 'webform_conditional_prepare_date_js', + 'js comparison callback' => 'conditionalOperatorDateBeforeEqual', + 'form callback' => 'webform_conditional_form_date', + ); $operators['date']['after'] = array( 'label' => t('is after'), 'comparison callback' => 'webform_conditional_operator_datetime_after', @@ -740,6 +895,13 @@ function _webform_conditional_operator_info() { 'js comparison callback' => 'conditionalOperatorDateAfter', 'form callback' => 'webform_conditional_form_date', ); + $operators['date']['after_equal'] = array( + 'label' => t('is on or after'), + 'comparison callback' => 'webform_conditional_operator_datetime_after_equal', + 'comparison prepare js' => 'webform_conditional_prepare_date_js', + 'js comparison callback' => 'conditionalOperatorDateAfterEqual', + 'form callback' => 'webform_conditional_form_date', + ); // Time operators: $operators['time']['equal'] = array( @@ -749,6 +911,13 @@ function _webform_conditional_operator_info() { 'js comparison callback' => 'conditionalOperatorTimeEqual', 'form callback' => 'webform_conditional_form_time', ); + $operators['time']['not_equal'] = array( + 'label' => t('is not at'), + 'comparison callback' => 'webform_conditional_operator_datetime_not_equal', + 'comparison prepare js' => 'webform_conditional_prepare_time_js', + 'js comparison callback' => 'conditionalOperatorTimeNotEqual', + 'form callback' => 'webform_conditional_form_time', + ); $operators['time']['before'] = array( 'label' => t('is before'), 'comparison callback' => 'webform_conditional_operator_datetime_before', @@ -756,6 +925,13 @@ function _webform_conditional_operator_info() { 'js comparison callback' => 'conditionalOperatorTimeBefore', 'form callback' => 'webform_conditional_form_time', ); + $operators['time']['before_equal'] = array( + 'label' => t('is at or before'), + 'comparison callback' => 'webform_conditional_operator_datetime_before_equal', + 'comparison prepare js' => 'webform_conditional_prepare_time_js', + 'js comparison callback' => 'conditionalOperatorTimeBeforeEqual', + 'form callback' => 'webform_conditional_form_time', + ); $operators['time']['after'] = array( 'label' => t('is after'), 'comparison callback' => 'webform_conditional_operator_datetime_after', @@ -763,6 +939,13 @@ function _webform_conditional_operator_info() { 'js comparison callback' => 'conditionalOperatorTimeAfter', 'form callback' => 'webform_conditional_form_time', ); + $operators['time']['after_equal'] = array( + 'label' => t('is at or after'), + 'comparison callback' => 'webform_conditional_operator_datetime_after_equal', + 'comparison prepare js' => 'webform_conditional_prepare_time_js', + 'js comparison callback' => 'conditionalOperatorTimeAfterEqual', + 'form callback' => 'webform_conditional_form_time', + ); return $operators; } @@ -853,6 +1036,12 @@ function webform_conditional_insert($conditional) { $rule['rid'] = $rid; drupal_write_record('webform_conditional_rules', $rule); } + foreach ($conditional['actions'] as $aid => $action) { + $action['nid'] = $conditional['nid']; + $action['rgid'] = $conditional['rgid']; + $action['aid'] = $aid; + drupal_write_record('webform_conditional_actions', $action); + } } /** @@ -875,6 +1064,10 @@ function webform_conditional_delete($node, $conditional) { ->condition('nid', $node->nid) ->condition('rgid', $conditional['rgid']) ->execute(); + db_delete('webform_conditional_actions') + ->condition('nid', $node->nid) + ->condition('rgid', $conditional['rgid']) + ->execute(); } /** @@ -884,49 +1077,78 @@ function webform_conditional_delete($node, $conditional) { * settings. We remove unnecessary data structures and provide a "source map" * so that JavaScript can quickly determine if it needs to check rules when a * field on the page has been modified. + * + * @param object $node + * The loaded node object, containing the webform. + * @param array $submission_data + * The cid-indexed array of existing submission values to be included for + * sources outside of the current page. + * @param integer $page_num + * The number of the page for which javascript settings should be generated. + * @return array + * Array of settings to be send to the browser as javascript settings. */ -function webform_conditional_prepare_javascript($node, $submission_data) { +function webform_conditional_prepare_javascript($node, $submission_data, $page_num) { $settings = array( 'ruleGroups' => array(), 'sourceMap' => array(), 'values' => array(), ); $operators = webform_conditional_operators(); - foreach ($node->webform['conditionals'] as $conditional) { + $conditionals = $node->webform['conditionals']; + $components = $node->webform['components']; + $topological_order = webform_get_conditional_sorter($node)->getOrder(); + foreach ($topological_order[$page_num] as $conditional_spec) { + $conditional = $conditionals[$conditional_spec['rgid']]; + $rgid_key = 'rgid_' . $conditional['rgid']; // Assemble the main conditional group settings. - if ($conditional['target_type'] == 'component') { - $target_component = $node->webform['components'][$conditional['target']]; - $target_parents = webform_component_parent_keys($node, $target_component); - $target_id = 'webform-component--' . str_replace('_', '-', implode('--', $target_parents)); - $settings['ruleGroups'][$conditional['rgid']]['target'] = $target_id; - $settings['ruleGroups'][$conditional['rgid']]['andor'] = $conditional['andor']; - $settings['ruleGroups'][$conditional['rgid']]['action'] = $conditional['action']; + + $settings['ruleGroups'][$rgid_key] = array( + 'andor' => $conditional['andor'], + ); + foreach ($conditional['actions'] as $action) { + if ($action['target_type'] == 'component') { + $target_component = $components[$action['target']]; + $target_parents = webform_component_parent_keys($node, $target_component); + $aid_key = 'aid_' . $action['aid']; + $action_settings = array( + 'target' => 'webform-component--' . str_replace('_', '-', implode('--', $target_parents)), + 'invert' => (int)$action['invert'], + 'action' => $action['action'], + 'argument' => $components[$action['target']]['type'] == 'markup' ? filter_xss_admin($action['argument']) : $action['argument'], + ); + $settings['ruleGroups'][$rgid_key]['actions'][$aid_key] = $action_settings; + } } // Add on the list of rules to the conditional group. foreach ($conditional['rules'] as $rule) { if ($rule['source_type'] == 'component') { - $source_component = $node->webform['components'][$rule['source']]; + $source_component = $components[$rule['source']]; $source_parents = webform_component_parent_keys($node, $source_component); $source_id = 'webform-component--' . str_replace('_', '-', implode('--', $source_parents)); + $rid_key = 'rid_' . $rule['rid']; // If this source has a value set, add that as a setting. + // NULL or array(NULL) should be sent as an empty array to simplify the jQuery. if (isset($submission_data[$source_component['cid']])) { $source_value = $submission_data[$source_component['cid']]; - $settings['values'][$source_id] = is_array($source_value) ? $source_value : array($source_value); + $source_value = is_array($source_value) ? $source_value : array($source_value); + $settings['values'][$source_id] = $source_value === array(NULL) ? array() : $source_value; } $conditional_type = webform_component_property($source_component['type'], 'conditional_type'); $operator_info = $operators[$conditional_type][$rule['operator']]; - $rule_settings = array(); - $rule_settings['source'] = $source_id; - $rule_settings['value'] = $rule['value']; - $rule_settings['callback'] = $operator_info['js comparison callback']; + $rule_settings = array( + 'source' => $source_id, + 'value' => $rule['value'], + 'callback' => $operator_info['js comparison callback'], + ); if (isset($operator_info['comparison prepare js'])) { $callback = $operator_info['comparison prepare js']; $rule_settings['value'] = $callback($rule['value']); } - $settings['ruleGroups'][$conditional['rgid']]['rules'][$rule['rid']] = $rule_settings; - $settings['sourceMap'][$source_id][$conditional['rgid']] = $conditional['rgid']; + $settings['ruleGroups'][$rgid_key]['rules'][$rid_key] = $rule_settings; + $settings['sourceMap'][$source_id][$rgid_key] = $rgid_key; } } } @@ -934,6 +1156,23 @@ function webform_conditional_prepare_javascript($node, $submission_data) { return $settings; } +/** + * Determine whether a component type is capable of a given conditional action. + */ +function webform_conditional_action_able($component_type, $action) { + switch ($action) { + case 'show': + return TRUE; + // break; + case 'require': + return webform_component_feature($component_type, 'required'); + // break; + default: + return webform_component_feature($component_type, "conditional_action_$action"); + // break; + } +} + /** * Prepare a conditional value for adding as a JavaScript setting. */ @@ -1041,6 +1280,36 @@ function webform_conditional_operator_string_not_empty($input_values, $rule_valu return !webform_conditional_operator_string_empty($input_values, $rule_value); } +/** + * Conditional callback for select comparisons. + */ +function webform_conditional_operator_select_less_than($input_values, $rule_value, $component) { + return empty($input_values) ? FALSE : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE)) < 0; +} + +/** + * Conditional callback for select comparisons. + */ +function webform_conditional_operator_select_less_than_equal($input_values, $rule_value, $component) { + $comparison = empty($input_values) ? NULL : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE)); + return $comparison < 0 || $comparison === 0; +} + +/** + * Conditional callback for select comparisons. + */ +function webform_conditional_operator_select_greater_than($input_values, $rule_value, $component) { + return empty($input_values) ? FALSE : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE)) > 0; +} + +/** + * Conditional callback for select comparisons. + */ +function webform_conditional_operator_select_greater_than_equal($input_values, $rule_value, $component) { + $comparison = empty($input_values) ? NULL : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE)); + return $comparison > 0 || $comparison === 0; +} + /** * Conditional callback for numeric comparisons. */ @@ -1055,6 +1324,21 @@ function webform_conditional_operator_numeric_not_equal($input_values, $rule_val return !webform_conditional_operator_numeric_equal($input_values, $rule_value); } +/** + * Conditional callback for numeric comparisons. + */ +function webform_conditional_operator_numeric_less_than($input_values, $rule_value) { + return empty($input_values) ? FALSE : webform_compare_floats($input_values[0], $rule_value) < 0; +} + +/** + * Conditional callback for numeric comparisons. + */ +function webform_conditional_operator_numeric_less_than_equal($input_values, $rule_value) { + $comparison = empty($input_values) ? NULL : webform_compare_floats($input_values[0], $rule_value); + return $comparison < 0 || $comparison === 0; +} + /** * Conditional callback for numeric comparisons. */ @@ -1065,8 +1349,9 @@ function webform_conditional_operator_numeric_greater_than($input_values, $rule_ /** * Conditional callback for numeric comparisons. */ -function webform_conditional_operator_numeric_less_than($input_values, $rule_value) { - return empty($input_values) ? FALSE : webform_compare_floats($input_values[0], $rule_value) < 0; +function webform_conditional_operator_numeric_greater_than_equal($input_values, $rule_value) { + $comparison = empty($input_values) ? NULL : webform_compare_floats($input_values[0], $rule_value); + return $comparison > 0 || $comparison === 0; } /** @@ -1077,6 +1362,13 @@ function webform_conditional_operator_datetime_equal($input_values, $rule_value) return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) === webform_strtotime($rule_value); } +/** + * Conditional callback for date and time comparisons. + */ +function webform_conditional_operator_datetime_not_equal($input_values, $rule_value) { + return !webform_conditional_operator_datetime_equal($input_values, $rule_value); +} + /** * Conditional callback for date and time comparisons. */ @@ -1085,6 +1377,14 @@ function webform_conditional_operator_datetime_after($input_values, $rule_value) return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) > webform_strtotime($rule_value); } +/** + * Conditional callback for date and time comparisons. + */ +function webform_conditional_operator_datetime_after_equal($input_values, $rule_value) { + return webform_conditional_operator_datetime_after($input_values, $rule_value) || + webform_conditional_operator_datetime_equal($input_values, $rule_value); +} + /** * Conditional callback for date and time comparisons. */ @@ -1093,6 +1393,14 @@ function webform_conditional_operator_datetime_before($input_values, $rule_value return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) < webform_strtotime($rule_value); } +/** + * Conditional callback for date and time comparisons. + */ +function webform_conditional_operator_datetime_before_equal($input_values, $rule_value) { + return webform_conditional_operator_datetime_before($input_values, $rule_value) || + webform_conditional_operator_datetime_equal($input_values, $rule_value); +} + /** * Utility function to convert incoming time and dates into strings. */ @@ -1103,3 +1411,34 @@ function webform_conditional_value_datetime($input_values) { $input_values = isset($input_values['month']) ? array(webform_date_string($input_values, 'date')) : $input_values; return $input_values; } + +/** + * Utility function to compare values of a select component. + * @param string $a + * First select option key to compare + * @param string $b + * Second select option key to compare + * @param array $options + * Associative array where the $a and $b are within the keys + * @return integer based upon position of $a and $b in $options + * -N if $a above (<) $b + * 0 if $a = $b + * +N if $a is below (>) $b + */ +function webform_compare_select($a, $b, $options) { + $options_array = array_keys($options); + $a_position = array_search($a, $options_array, TRUE); + $b_position = array_search($b, $options_array, TRUE); + if ($a_position === FALSE && $a_position === FALSE) { + return NULL; + } + elseif ($a_position === FALSE) { + return 1; + } + elseif ($b_position === FALSE) { + return -1; + } + else { + return $a_position - $b_position; + } +} diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.emails.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.emails.inc index a9f6358b..7f24e03e 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.emails.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.emails.inc @@ -20,13 +20,8 @@ function webform_emails_form($form, $form_state, $node) { $form['components'] = array(); foreach ($node->webform['emails'] as $eid => $email) { - $email_addresses = array_filter(explode(',', check_plain($email['email']))); - foreach ($email_addresses as $key => $email_address) { - $email_addresses[$key] = webform_format_email_address($email_address, NULL, $node, NULL, FALSE); - } - $form['emails'][$eid]['email'] = array( - '#markup' => implode('<br />', $email_addresses), + '#markup' => nl2br(check_plain(implode("\n", webform_format_email_address($email['email'], NULL, $node, NULL, FALSE, FALSE)))), ); $form['emails'][$eid]['subject'] = array( '#markup' => check_plain(webform_format_email_subject($email['subject'], $node)), @@ -248,7 +243,7 @@ function webform_email_edit_form($form, $form_state, $node, $email = array(), $c // To avoid flooding the form with hundreds of textfields, skip select // lists that have huge numbers of options. - if (count($options) > variable_get('webform_email_select_max', 50)) { + if (count($options) > webform_variable_get('webform_email_select_max')) { unset($form[$field . '_component']['#options'][$cid]); continue; } @@ -271,6 +266,7 @@ function webform_email_edit_form($form, $form_state, $node, $email = array(), $c '#title' => $label, '#default_value' => is_numeric($email[$field]) && $email[$field] == $cid && is_array($email['extra']) && isset($email['extra'][$field . '_mapping'][$key]) ? $email['extra'][$field . '_mapping'][$key] : '', '#attributes' => array('placeholder' => t('email@example.com')), + '#maxlength' => 500, ); } } @@ -278,7 +274,7 @@ function webform_email_edit_form($form, $form_state, $node, $email = array(), $c } // Do not show the "E-mail from name" if using the short e-mail format. - if (variable_get('webform_email_address_format', 'long') == 'short') { + if (webform_variable_get('webform_email_address_format') == 'short') { $form['from_name_option']['#access'] = FALSE; $form['from_name_custom']['#access'] = FALSE; $form['from_name_component']['#access'] = FALSE; @@ -311,7 +307,7 @@ function webform_email_edit_form($form, $form_state, $node, $email = array(), $c '#type' => 'textarea', '#rows' => max(10, min(20, count(explode("\n", $template)))), '#default_value' => $template, - '#wysiwyg' => webform_email_html_capable() ? NULL : FALSE, + '#wysiwyg' => webform_variable_get('webform_email_html_capable') ? NULL : FALSE, '#description' => theme('webform_token_help', array('groups' => array('node', 'submission'))), ); @@ -319,14 +315,14 @@ function webform_email_edit_form($form, $form_state, $node, $email = array(), $c '#type' => 'checkbox', '#title' => t('Send e-mail as HTML'), '#default_value' => $email['html'], - '#access' => webform_email_html_capable() && !variable_get('webform_format_override', 0), + '#access' => webform_variable_get('webform_email_html_capable') && !webform_variable_get('webform_format_override'), ); $form['template']['attachments'] = array( '#type' => 'checkbox', '#title' => t('Include files as attachments'), '#default_value' => $email['attachments'], - '#access' => webform_email_html_capable(), + '#access' => webform_variable_get('webform_email_html_capable'), ); $form['template']['components'] = array( @@ -340,6 +336,12 @@ function webform_email_edit_form($form, $form_state, $node, $email = array(), $c '#process' => array('webform_component_select'), ); + $form['template']['components']['suffix']['exclude_empty'] = array( + '#type' => 'checkbox', + '#title' => t('Exclude empty components'), + '#default_value' => $email['exclude_empty'], + ); + // TODO: Allow easy re-use of existing templates. $form['templates']['#tree'] = TRUE; $form['templates']['default'] = array( @@ -456,18 +458,7 @@ function theme_webform_email_component_mapping($variables) { */ function webform_email_address_validate($form, &$form_state) { if ($form_state['values']['email_option'] == 'custom') { - $email = trim($form_state['values']['email_custom']); - if (empty($email)) { - form_set_error('email_custom', t('When adding a new custom e-mail, the e-mail field is required.')); - } - else { - $emails = array_filter(explode(',', $email)); - foreach ($emails as $email) { - if (!valid_email_address(webform_replace_tokens(trim($email), $form['#node']))) { - form_set_error('email_custom', t('The entered e-mail address "@email" does not appear valid.', array('@email' => $email))); - } - } - } + webform_email_validate($form_state['values']['email_custom'], 'email_custom', FALSE, TRUE); } } @@ -475,36 +466,19 @@ function webform_email_address_validate($form, &$form_state) { * Validate handler for webform_email_edit_form(). */ function webform_email_edit_form_validate($form, &$form_state) { - if ($form_state['values']['from_address_option'] == 'custom' && !valid_email_address(webform_replace_tokens($form_state['values']['from_address_custom'], $form['#node']))) { - form_set_error('from_address_custom', t('The entered e-mail address "@email" does not appear valid.', array('@email' => $form_state['values']['from_address_custom']))); + if ($form_state['values']['from_address_option'] == 'custom') { + webform_email_validate($form_state['values']['from_address_custom'], 'from_address_custom', FALSE, FALSE); } // Validate component-based values for the TO and FROM address. - $field_names = array('email', 'from_address'); - foreach ($field_names as $field_name) { + foreach (array('email', 'from_address') as $field_name) { if ($form_state['values'][$field_name . '_option'] == 'component') { $cid = $form_state['values'][$field_name . '_component']; - $empty_allowed = $field_name === 'email'; - $multiple_allowed = $field_name === 'email'; if (isset($form_state['values'][$field_name . '_mapping'][$cid])) { - foreach ($form_state['values'][$field_name . '_mapping'][$cid] as $key => $value) { - $email_value = _webform_filter_values(trim($value), $form['#node']); - if (empty($email_value) && !$empty_allowed) { - form_set_error("{$field_name}_mapping][$cid][$key", t('An e-mail address must be provided for each option.')); - } - elseif (!empty($email_value)) { - if (!$multiple_allowed && !valid_email_address($email_value)) { - form_set_error("{$field_name}_mapping][$cid][$key", t('The entered e-mail address "@email" does not appear valid', array('@email' => $value))); - } - else { - $emails = array_filter(explode(',', $value)); - foreach ($emails as $email) { - if (!valid_email_address(webform_replace_tokens(trim($email), $form['#node']))) { - form_set_error("{$field_name}_mapping][$cid][$key", t('The entered e-mail address "@email" does not appear valid', array('@email' => $value))); - } - } - } - } + $empty_allowed = $field_name === 'email'; + $multiple_allowed = $field_name === 'email'; + foreach ($form_state['values'][$field_name . '_mapping'][$cid] as $key => &$value) { + webform_email_validate($value, "{$field_name}_mapping][$cid][$key", $empty_allowed, $multiple_allowed); } } } @@ -569,6 +543,8 @@ function webform_email_edit_form_submit($form, &$form_state) { $excluded = array_diff(array_keys($node->webform['components']), $included); $email['excluded_components'] = $excluded; + $email['exclude_empty'] = empty($form_state['values']['exclude_empty']) ? 0 : 1; + if ($form_state['values']['clone']) { drupal_set_message(t('Email settings cloned.')); $form_state['values']['eid'] = webform_email_clone($email); @@ -651,15 +627,16 @@ function webform_email_load($eid, $nid) { 'from_address' => 'default', 'template' => 'default', 'excluded_components' => array(), - 'html' => variable_get('webform_default_format', 0), + 'exclude_empty' => 0, + 'html' => webform_variable_get('webform_default_format'), 'attachments' => 0, 'extra' => '', ); } else { $email = isset($node->webform['emails'][$eid]) ? $node->webform['emails'][$eid] : FALSE; - if (variable_get('webform_format_override', 0)) { - $email['html'] = variable_get('webform_default_format', 0); + if (webform_variable_get('webform_format_override')) { + $email['html'] = webform_variable_get('webform_default_format'); } } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.export.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.export.inc index ccc8a443..249f3126 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.export.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.export.inc @@ -50,7 +50,7 @@ function webform_webform_exporters() { unset($exporters['excel']); } // By default the legacy Excel exporter is disabled. - if (!variable_get('webform_excel_legacy_exporter', 0)) { + if (!webform_variable_get('webform_excel_legacy_exporter')) { unset($exporters['excel_legacy']); } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.pages.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.pages.inc index b5ce0793..a4c35ec3 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.pages.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.pages.inc @@ -167,8 +167,13 @@ function webform_configure_form($form, &$form_state, $node) { '#collapsible' => TRUE, '#collapsed' => FALSE, '#description' => t('These permissions affect which roles can submit this webform. It does not prevent access to the webform page. If needing to prevent access to the webform page entirely, use a content access module such as <a href="http://drupal.org/project/taxonomy_access">Taxonomy Access</a> or <a href="http://drupal.org/project/node_privacy_byrole">Node Privacy by Role</a>.'), - '#access' => variable_get('webform_submission_access_control', 1), + '#access' => webform_variable_get('webform_submission_access_control'), '#weight' => 10, + '#states' => array( + 'invisible' => array( + ':input[name="confidential"]' => array('checked' => TRUE), + ), + ), ); $user_roles = user_roles(); foreach ($user_roles as $rid => $rname) { @@ -341,6 +346,12 @@ function webform_configure_form($form, &$form_state, $node) { '#default_value' => $node->webform['submit_notice'], '#description' => t('Show the previous submissions notification that appears when users have previously submitted this form.'), ); + $form['advanced']['confidential'] = array( + '#type' => 'checkbox', + '#title' => t('Confidential submissions'), + '#default_value' => $node->webform['confidential'], + '#description' => t('Confidential submissions have no recorded IP address and must be submitted while logged out.'), + ); $form['advanced']['submit_text'] = array( '#type' => 'textfield', '#title' => t('Submit button label'), @@ -375,17 +386,6 @@ function webform_configure_form($form, &$form_state, $node) { * Validate handler for webform_configure_form(). */ function webform_configure_form_validate($form, &$form_state) { - // Ensure the entered e-mail addresses are valid. - if (!empty($form_state['values']['email'])) { - $emails = explode(',', $form_state['values']['email']); - foreach ($emails as $email) { - if (!valid_email_address(trim($email))) { - form_error($form['submission']['redirect_url'], t('The entered email address %address is not a valid address.', array('%address' => $email))); - break; - } - } - } - // Ensure the entered redirect URL is valid. if ($form_state['values']['redirect'] == 'url') { $redirect_url = trim($form_state['values']['redirect_url']); @@ -417,7 +417,20 @@ function webform_configure_form_validate($form, &$form_state) { // Serial number must be a positive integer greater than any existing serial number. $next_min = _webform_submission_serial_next_value_used($form['#node']->nid); if ((int) $form['advanced']['next_serial']['#value'] < $next_min) { - form_error($form['advanced']['next_serial'], t('The next submission number must be at least %min (greater than any existing serial number).', array('%min' => $next_min))); + form_error($form['advanced']['next_serial'], + t('The next submission number must be at least %min (greater than any existing serial number).', + array('%min' => $next_min))); + } + + // Prohibit the combination of confidential + per-user limit + ip-only + // submission tracking for anonymous users as it would not be enforceable. + if (webform_variable_get('webform_tracking_mode') == 'ip_address' && + $form_state['values']['confidential'] && + $form_state['values']['enforce_limit'] == 'yes') { + // Note that FAPI doesn't actually support error highlighting on radio or + // checkbox form elements. + form_error($form['advanced']['confidential'], + t('Choose a "Per user submission limit" or "Confidential submissions", but not both. Or ask the adminstrator to track anonymous users by cookie, rather than IP address only.')); } } @@ -498,6 +511,9 @@ function webform_configure_form_submit($form, &$form_state) { // Set submit notice. $node->webform['submit_notice'] = $form_state['values']['submit_notice']; + // Set confidential. + $node->webform['confidential'] = $form_state['values']['confidential']; + // Set submit button text. $node->webform['submit_text'] = $form_state['values']['submit_text']; @@ -516,7 +532,7 @@ function webform_configure_form_submit_save($form, &$form_state) { drupal_set_message(t('The form settings have been updated.')); $node = &$form['#node']; - if (!$node->webform['block'] && + if (!$node->webform['block'] && function_exists('block_load') && ($block = block_load('webform', 'client-block-' . $node->nid)) && !empty($block->bid)) { // An existing block for this not-currently-available block was already configured. diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.report.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.report.inc index 9f4ddb3e..d2c81110 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.report.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.report.inc @@ -16,7 +16,8 @@ module_load_include('inc', 'webform', 'includes/webform.submissions'); function webform_results_submissions($node, $user_filter, $pager_count) { global $user; - // Determine whether views or hard-coded tables should be used for the submissions table. + // Determine whether views or hard-coded tables should be used for the + // submissions table. if (!webform_variable_get('webform_table')) { // Load the submissions view $view = webform_get_view($node, 'webform_submissions'); @@ -28,7 +29,6 @@ function webform_results_submissions($node, $user_filter, $pager_count) { drupal_set_title(t('Your submissions')); webform_disable_page_cache(); } - webform_set_breadcrumb($node); return $view->preview('default', array($node->nid, $user->uid)); } else { @@ -49,7 +49,6 @@ function webform_results_submissions($node, $user_filter, $pager_count) { drupal_set_title(t('Your submissions')); webform_disable_page_cache(); } - webform_set_breadcrumb($node); $submissions = webform_get_submissions(array('nid' => $node->nid, 'uid' => $user->uid), $header, $pager_count); $count = webform_get_submission_count($node->nid, $user->uid, NULL); } @@ -215,8 +214,9 @@ function template_preprocess_webform_results_submissions(&$vars) { * Create a table containing all submitted values for a webform node. */ function webform_results_table($node, $pager_count = 0) { - - // Determine whether views or hard-coded tables should be used for the submissions table. + + // Determine whether views or hard-coded tables should be used for the + // submissions table. if (!webform_variable_get('webform_table')) { // Load and preview the results view with a node id argument $view = webform_get_view($node, 'webform_results'); @@ -389,7 +389,7 @@ function webform_results_download_form($form, &$form_state, $node) { '#type' => 'select', '#title' => t('Delimited text format'), '#description' => t('This is the delimiter used in the CSV/TSV file when downloading Webform results. Using tabs in the export is the most reliable method for preserving non-latin characters. You may want to change this to another character depending on the program with which you anticipate importing results.'), - '#default_value' => variable_get('webform_csv_delimiter', '\t'), + '#default_value' => webform_variable_get('webform_csv_delimiter'), '#options' => array( ',' => t('Comma (,)'), '\t' => t('Tab (\t)'), @@ -410,6 +410,7 @@ function webform_results_download_form($form, &$form_state, $node) { '#type' => 'radios', '#title' => t('Column header format'), '#options' => array( + -1 => t('None'), 0 => t('Label'), 1 => t('Field Key'), ), @@ -484,6 +485,7 @@ function webform_results_download_form($form, &$form_state, $node) { 'new' => t('Only new submissions since your last download'), 'latest' => t('Only the latest'), 'range_serial' => t('All submissions starting from'), + 'range_date' => t('All submissions by date'), ), '#default_value' => 'all', ); @@ -502,20 +504,31 @@ function webform_results_download_form($form, &$form_state, $node) { '#size' => 5, '#maxlength' => 8, ); + $date_attributes = array('placeholder' => format_date(REQUEST_TIME, 'custom', webform_date_format('short'))); + $form['range']['start_date'] = array( + '#type' => 'textfield', + '#size' => 20, + '#attributes' => $date_attributes, + ); + $form['range']['end_date'] = array( + '#type' => 'textfield', + '#size' => 20, + '#attributes' => $date_attributes, + ); - // If drafts are allowed, provide options to filter download based on draft status. - if ($node->webform['allow_draft'] || $node->webform['auto_save']) { - $form['range']['completion_type'] = array( - '#type' => 'radios', - '#title' => t('Included submissions'), - '#default_value' => 'all', - '#options' => array( - 'all' => t('Finished and draft submissions'), - 'finished' => t('Finished submissions only'), - 'draft' => t('Drafts only'), - ), - ); - } + // If drafts are allowed, provide options to filter download based on draft + // status. + $form['range']['completion_type'] = array( + '#type' => 'radios', + '#title' => t('Included submissions'), + '#default_value' => 'all', + '#options' => array( + 'all' => t('Finished and draft submissions'), + 'finished' => t('Finished submissions only'), + 'draft' => t('Drafts only'), + ), + '#access' => ($node->webform['allow_draft'] || $node->webform['auto_save']), + ); // By default results are downloaded. User can override this value if // programmatically submitting this form. @@ -543,11 +556,11 @@ function webform_results_download_range_validate($element, $form_state) { if ($element['latest']['#value'] == '') { form_error($element['latest'], t('Latest number of submissions field is required.')); } - else{ + else { if (!is_numeric($element['latest']['#value'])) { form_error($element['latest'], t('Latest number of submissions must be numeric.')); } - else{ + else { if ($element['latest']['#value'] <= 0) { form_error($element['latest'], t('Latest number of submissions must be greater than 0.')); } @@ -560,11 +573,11 @@ function webform_results_download_range_validate($element, $form_state) { if ($element['start']['#value'] == '') { form_error($element['start'], t('Start submission number is required.')); } - else{ + else { if (!is_numeric($element['start']['#value'])) { form_error($element['start'], t('Start submission number must be numeric.')); } - else{ + else { if ($element['start']['#value'] <= 0) { form_error($element['start'], t('Start submission number must be greater than 0.')); } @@ -575,25 +588,57 @@ function webform_results_download_range_validate($element, $form_state) { if (!is_numeric($element['end']['#value'])) { form_error($element['end'], t('End submission number must be numeric.')); } - else{ + else { if ($element['end']['#value'] <= 0) { form_error($element['end'], t('End submission number must be greater than 0.')); } - else{ + else { if ($element['end']['#value'] < $element['start']['#value']) { - form_error($element['end'], t('End submission number may not be less than Start submission number.')); + form_error($element['end'], t('End submission number must not be less than Start submission number.')); } } } } - // Check that the range will return something at all. - $form_state['values']['sids'] = webform_download_sids($form_state['values']['node']->nid, array('range_type' => $element['range_type']['#value'], 'start' => $element['start']['#value'], 'end' => $element['end']['#value'])); - if (empty($form_state['values']['sids'])) { - form_error($element['start'], t('The specified range will not return any results.')); + break; + case 'range_date': + // Download Start-end range of submissions. + // Start submission time. + $start_date = strtotime($element['start_date']['#value']); + if ($element['start_date']['#value'] == '') { + form_error($element['start_date'], t('Start date range is required.')); + } + elseif ($start_date === FALSE) { + form_error($element['start_date'], t('Start date range is not in a valid format.')); + } + // End submission time. + $end_date = strtotime($element['end_date']['#value']); + if ($element['end_date']['#value'] != '') { + if ($end_date === FALSE) { + form_error($element['end_date'], t('End date range is not in a valid format.')); + } + elseif ($start_date !== FALSE && $start_date > $end_date) { + form_error($element['end_date'], t('End date range must not be before the Start date..')); + } } break; } + // Check that the range will return something at all. + $range_options = array( + 'range_type' => $element['range_type']['#value'], + 'start' => $element['start']['#value'], + 'end' => $element['end']['#value'], + 'latest' => $element['latest']['#value'], + 'start_date' => $element['start_date']['#value'], + 'end_date' => $element['end_date']['#value'], + 'completion_type' => $element['completion_type']['#value'], + 'batch_size' => 1, + 'batch_number' => 0, + ); + if (!webform_download_sids_count($form_state['values']['node']->nid, $range_options)) { + form_error($element['range_type'], t('The specified range will not return any results.')); + } + } /** @@ -603,16 +648,13 @@ function webform_results_download_range_after_build($element, &$form_state) { $node = $form_state['values']['node']; // Build a list of counts of new and total submissions. - $count = webform_get_submission_count($node->nid, NULL, NULL); - $sids = webform_download_sids($node->nid, array('range_type' => 'new')); - $last_download = webform_download_last_download_info($node->nid); $element['#webform_download_info']['sid'] = $last_download ? $last_download['sid'] : 0; $element['#webform_download_info']['serial'] = $last_download ? $last_download['serial'] : NULL; $element['#webform_download_info']['requested'] = $last_download ? $last_download['requested'] : $node->created; - $element['#webform_download_info']['total'] = $count; - $element['#webform_download_info']['new'] = count($sids); + $element['#webform_download_info']['total'] = webform_get_submission_count($node->nid, NULL, NULL); + $element['#webform_download_info']['new'] = webform_download_sids_count($node->nid, array('range_type' => 'new')); return $element; } @@ -628,13 +670,13 @@ function theme_webform_results_download_range($variables) { // Set description for total of all submissions. $element['range_type']['all']['#theme_wrappers'] = array('webform_inline_radio'); - $element['range_type']['all']['#description'] = '(' . t('@count total', array('@count' => $download_info['total'])) . ')'; + $element['range_type']['all']['#title'] .= ' (' . t('@count total', array('@count' => $download_info['total'])) . ')'; // Set description for "New submissions since last download". $format = webform_date_format('short'); $requested_date = format_date($download_info['requested'], 'custom', $format); $element['range_type']['new']['#theme_wrappers'] = array('webform_inline_radio'); - $element['range_type']['new']['#description'] = '(' . t('@count new since @date', array('@count' => $download_info['new'], '@date' => $requested_date)) . ')'; + $element['range_type']['new']['#title'] .= ' (' . t('@count new since @date', array('@count' => $download_info['new'], '@date' => $requested_date)) . ')'; // Disable option if there are no new submissions. @@ -653,11 +695,18 @@ function theme_webform_results_download_range($variables) { $element['end']['#attributes']['class'][] = 'webform-set-active'; $element['start']['#theme_wrappers'] = array(); $element['end']['#theme_wrappers'] = array(); + $element['start_date']['#attributes']['class'] = array('webform-set-active'); + $element['end_date']['#attributes']['class'] = array('webform-set-active'); + $element['start_date']['#theme_wrappers'] = array(); + $element['end_date']['#theme_wrappers'] = array(); $element['range_type']['range_serial']['#theme_wrappers'] = array('webform_inline_radio'); - $element['range_type']['range_serial']['#title'] = t('All submissions starting from: !start and optionally to: !end', array('!start' => drupal_render($element['start']), '!end' => drupal_render($element['end']))); - $last_serial = $download_info['serial'] ? $download_info['serial'] : drupal_placeholder(t('none')); - $element['range_type']['range_serial']['#description'] = '(' . t('Last downloaded end submission number: !serial.', array('!serial' => $last_serial)) . ')'; + $element['range_type']['range_serial']['#title'] = t('Submissions by number from !start and optionally to: !end (Last downloaded: !serial)', + array('!start' => drupal_render($element['start']), '!end' => drupal_render($element['end']), '!serial' => $last_serial)); + + // date range + $element['range_type']['range_date']['#theme_wrappers'] = array('webform_inline_radio'); + $element['range_type']['range_date']['#title'] = t('Submissions by date from !start_date and optionally to: !end_date', array('!start_date' => drupal_render($element['start_date']), '!end_date' => drupal_render($element['end_date']))); return drupal_render_children($element); } @@ -721,15 +770,6 @@ function webform_results_download_form_submit(&$form, &$form_state) { $options += $defaults; $options['range'] += $defaults['range']; - // Use a pre-built list of SIDs provided by validate handlers (if any). - if (isset($form_state['values']['sids'])) { - $options['sids'] = $form_state['values']['sids']; - } - // Retrieve the list of required SIDs. - elseif ($options['range']['range_type'] != 'all') { - $options['sids'] = webform_download_sids($node->nid, $options['range']); - } - // Determine an appropriate batch size based on the form and server specs. if (!isset($options['range']['batch_size'])) { // Start the batch size at 50,000 per batch, but divide by number of @@ -743,12 +783,12 @@ function webform_results_download_form_submit(&$form, &$form_state) { $memory_modifier = max(1, ($memory_limit - (64 * $mb)) / (32 * $mb)); $batch_size = ceil($batch_size * $memory_modifier); - // For time reasons, limit the batch size to 10,000. - $batch_size = min($batch_size, 10000); + // For time reasons, limit the batch size to 5,000. + $batch_size = min($batch_size, 5000); // Allow a non-UI configuration to override the batch size. $batch_size = variable_get('webform_export_batch_size', $batch_size); - + $options['range']['batch_size'] = $batch_size; } @@ -774,6 +814,9 @@ function _webform_export_tempname() { * * @see webform_results_export_batch() * + * @deprecated This function is schedule to be removed in webform 7.x-5.x. Use + * the batch opertions instead. + * * @return array|null * The array of export info or null if the file could not be opened. */ @@ -803,8 +846,11 @@ function webform_results_export($node, $format = 'delimited', $options = array() $col_count = 0; $headers = webform_results_download_headers($node, $options); foreach ($headers as $row) { - $exporter->add_row($handle, $row, $row_count); - $row_count++; + // Output header if header_keys is non-negative. -1 means no headers. + if ($options['header_keys'] >= 0) { + $exporter->add_row($handle, $row, $row_count); + $row_count++; + } $col_count = count($row) > $col_count ? count($row) : $col_count; } @@ -826,7 +872,8 @@ function webform_results_export($node, $format = 'delimited', $options = array() $export_info['file_name'] = $file_name; $export_info['row_count'] = $row_count; $export_info['col_count'] = $col_count; - $export_info['last_sid'] = $sid; + $export_info['last_sid'] = end($submissions) ? key($submissions) : NULL; + $export_info['exporter'] = $exporter; return $export_info; } @@ -927,6 +974,8 @@ function webform_results_download_headers($node, $options) { /** * Returns rows of downloadable webform data. * + * @deprecated This function is scheduled to be removed in webform 7.x-5.x. + * * @param $node * The webform node on which to generate the analysis. * @param array $options @@ -941,8 +990,6 @@ function webform_results_download_headers($node, $options) { * by the super-global $_GET['page'] variable. */ function webform_results_download_rows($node, $options, $serial_start = 0) { - module_load_include('inc', 'webform', 'includes/webform.components'); - // Get all the required submissions for the download. $filters['nid'] = $node->nid; if (isset($options['sids'])){ @@ -953,11 +1000,37 @@ function webform_results_download_rows($node, $options, $serial_start = 0) { } $submissions = webform_get_submissions($filters, NULL); + + return webform_results_download_rows_process($node, $options, $serial_start, $submissions); +} + +/** + * Processes the submissions to be downloaded into exported rows. + * + * This is an internal routine and not intended for use by other modules. + * + * @param $node + * The webform node on which to generate the analysis. + * @param array $options + * A list of options that define the output format. These are generally passed + * through from the GUI interface. + * @param $serial_start + * The starting position for the Serial column in the output. + * @param array $submissions + * An associative array of loaded submissions, indexed by sid. + * + * @return $rows + * An array of rows built according to the provided $serial_start and + * $pager_count variables. Note that the current page number is determined + * by the super-global $_GET['page'] variable. + */ +function webform_results_download_rows_process($node, $options, $serial_start, $submissions) { + module_load_include('inc', 'webform', 'includes/webform.components'); + $submission_information = webform_results_download_submission_information($node, $options); // Generate a row for each submission. $row_count = 0; - $sid = 0; $rows = array(); foreach ($submissions as $sid => $submission) { $row_count++; @@ -968,7 +1041,8 @@ function webform_results_download_rows($node, $options, $serial_start = 0) { $cell = module_invoke_all('webform_results_download_submission_information_data', $token, $submission, $options, $serial_start, $row_count); $context = array('token' => $token, 'submission' => $submission, 'options' => $options, 'serial_start' => $serial_start, 'row_count' => $row_count); drupal_alter('webform_results_download_submission_information_data', $cell, $context); - // implode() to ensure everything from a single value goes into one column, even if more than one module responds to this item. + // implode() to ensure everything from a single value goes into one + // column, even if more than one module responds to this item. $row[] = implode(', ', $cell); } @@ -1031,7 +1105,9 @@ function webform_webform_results_download_submission_information_info() { return array( 'webform_serial' => t('Serial'), 'webform_sid' => t('SID'), - 'webform_time' => t('Time'), + 'webform_time' => t('Submitted Time'), + 'webform_completed_time' => t('Completed Time'), + 'webform_modified_time' => t('Modified Time'), 'webform_draft' => t('Draft'), 'webform_ip_address' => t('IP Address'), 'webform_uid' => t('UID'), @@ -1049,12 +1125,32 @@ function webform_webform_results_download_submission_information_data($token, $s case 'webform_sid': return $submission->sid; case 'webform_time': + // Return timestamp in local time (not UTC). if (!empty($options['iso8601_date'])) { - return format_date($submission->submitted, 'custom', 'Y-m-d\TH:i:s', 'UTC'); + return format_date($submission->submitted, 'custom', 'Y-m-d\TH:i:s'); } else { return format_date($submission->submitted, 'short'); } + case 'webform_completed_time': + if (!$submission->completed) { + return ''; + } + // Return timestamp in local time (not UTC). + elseif (!empty($options['iso8601_date'])) { + return format_date($submission->completed, 'custom', 'Y-m-d\TH:i:s'); + } + else { + return format_date($submission->completed, 'short'); + } + case 'webform_modified_time': + // Return timestamp in local time (not UTC). + if (!empty($options['iso8601_date'])) { + return format_date($submission->modified, 'custom', 'Y-m-d\TH:i:s'); + } + else { + return format_date($submission->modified, 'short'); + } case 'webform_draft': return $submission->is_draft; case 'webform_ip_address': @@ -1081,7 +1177,7 @@ function webform_results_download_default_options($node, $format) { $submission_information = webform_results_download_submission_information($node); $options = array( - 'delimiter' => variable_get('webform_csv_delimiter', '\t'), + 'delimiter' => webform_variable_get('webform_csv_delimiter'), 'components' => array_merge(array_keys($submission_information), array_keys(webform_component_list($node, 'csv', TRUE))), 'header_keys' => 0, 'select_keys' => 0, @@ -1129,17 +1225,14 @@ function webform_results_download($node, $export_info) { } // Update user last downloaded sid if required. - if ((isset($export_info['options']['sids']) || $export_info['options']['range']['range_type'] != 'range') && !empty($export_info['last_sid'])) { - // Delete existing record. - db_delete('webform_last_download') - ->condition('nid', $node->nid) - ->condition('uid', $user->uid) - ->execute(); - // Write new record. - db_insert('webform_last_download') - ->fields(array( + if (!in_array($export_info['options']['range']['range_type'], array('range', 'range_serial', 'range_date')) && !empty($export_info['last_sid'])) { + // Insert a new record or update an existing record. + db_merge('webform_last_download') + ->key(array( 'nid' => $node->nid, 'uid' => $user->uid, + )) + ->fields(array( 'sid' => $export_info['last_sid'], 'requested' => REQUEST_TIME, )) @@ -1214,8 +1307,11 @@ function webform_results_batch_headers($node, $format = 'delimited', $options = $row_count = 0; $col_count = 0; foreach ($headers as $row) { - $exporter->add_row($handle, $row, $row_count); - $row_count++; + // Output header if header_keys is non-negative. -1 means no headers. + if ($options['header_keys'] >= 0) { + $exporter->add_row($handle, $row, $row_count); + $row_count++; + } $col_count = count($row) > $col_count ? count($row) : $col_count; } $context['results']['row_count'] = $row_count; @@ -1229,19 +1325,30 @@ function webform_results_batch_headers($node, $format = 'delimited', $options = function webform_results_batch_rows($node, $format = 'delimited', $options = array(), &$context) { module_load_include('inc', 'webform', 'includes/webform.export'); + // Initialize the sandbox if this is the first execution of the batch + // operation. if (!isset($context['sandbox']['batch_number'])) { $context['sandbox']['batch_number'] = 0; - $sids = isset($options['sids']) ? $options['sids'] : webform_download_sids($node->nid, $options['range']); - $context['sandbox']['sid_count'] = count($sids); + $context['sandbox']['sid_count'] = webform_download_sids_count($node->nid, $options['range']); $context['sandbox']['batch_max'] = max(1, ceil($context['sandbox']['sid_count'] / $options['range']['batch_size'])); $context['sandbox']['serial'] = 0; $context['sandbox']['last_sid'] = 0; } + // Retrieve the submissions for this batch process. $options['range']['batch_number'] = $context['sandbox']['batch_number']; - $sids = isset($options['sids']) ? $options['sids'] : webform_download_sids($node->nid, $options['range']); - $options['sids'] = $sids; - $rows = webform_results_download_rows($node, $options, $context['sandbox']['serial']); + + $query = webform_download_sids_query($node->nid, $options['range']); + + // Join to the users table to include user name in results, as required by + // webform_results_download_rows_process. + $query->leftJoin('users', 'u', 'u.uid = ws.uid'); + $query->fields('u', array('name')); + $query->fields('ws'); + + $submissions = webform_get_submissions_load($query); + + $rows = webform_results_download_rows_process($node, $options, $context['sandbox']['serial'], $submissions); // Write these submissions to the file. $exporter = webform_export_create_handler($format, $options); @@ -1253,8 +1360,9 @@ function webform_results_batch_rows($node, $format = 'delimited', $options = arr $exporter->add_row($handle, $row, $context['results']['row_count']); $context['results']['row_count']++; } - $context['sandbox']['serial'] += count($sids); - $context['sandbox']['last_sid'] = end($sids); + + $context['sandbox']['serial'] += count($submissions); + $context['sandbox']['last_sid'] = end($submissions) ? key($submissions) : NULL; $context['sandbox']['batch_number']++; @fclose($handle); @@ -1367,6 +1475,34 @@ function webform_results_analysis($node, $sids = array(), $analysis_component = '#component' => $analysis_component, ); + // See if a query (possibly with exposed filter) needs to restrict the + // submissions that are being analyzed. + $query = NULL; + if (empty($sids)) { + $view = webform_get_view($node, 'webform_analysis'); + if ($view->type != t('Default') || $view->name != 'webform_analysis') { + // The view has been customized from the no-op built-in view. Use it. + $view->set_display(); + $view->init_handlers(); + $view->override_url = $_GET['q']; + $view->preview = TRUE; + $view->pre_execute(array($node->nid)); + $view->build(); + // Let modules modify the view just prior to executing it. + foreach (module_implements('views_pre_execute') as $module) { + $function = $module . '_views_pre_execute'; + $function($view); + } + // If the view is already executed, there was an error in generating it. + $query = $view->executed ? NULL : $view->query->query(); + $view->post_execute(); + + if (isset($view->exposed_widgets)) { + $analysis['exposed_filter']['#markup'] = $view->exposed_widgets; + } + } + } + // If showing all components, display selection form. if (!$analysis_component) { $analysis['form'] = drupal_get_form('webform_analysis_components_form', $node); @@ -1377,7 +1513,7 @@ function webform_results_analysis($node, $sids = array(), $analysis_component = foreach ($components as $cid) { // Do component specific call. $component = $node->webform['components'][$cid]; - if ($data = webform_component_invoke($component['type'], 'analysis', $component, $sids, isset($analysis_component))) { + if ($data = webform_component_invoke($component['type'], 'analysis', $component, $sids, isset($analysis_component), $query)) { drupal_alter('webform_analysis_component_data', $data, $node, $component); $analysis['data'][$cid] = array( '#theme' => array('webform_analysis_component__' . $node->nid . '__' . $cid, 'webform_analysis_component__' . $node->nid, 'webform_analysis_component'), @@ -1596,46 +1732,95 @@ function theme_webform_results_analysis($variables) { /** * Given a set of range options, retrieve a set of SIDs for a webform node. + * + * @deprecated This function is scheduled to be removed int webform 7.x-5.x. + * Use webform_download_sids_query() instead. + * */ function webform_download_sids($nid, $range_options, $uid = NULL) { - $query = db_select('webform_submissions', 'ws') + return webform_download_sids_query($nid, $range_options, $uid) ->fields('ws', array('sid')) + ->execute() + ->fetchCol(); +} + +/** + * Retrieves a count the number of matching submissions. + * + */ +function webform_download_sids_count($nid, $range_options, $uid = NULL) { + return webform_download_sids_query($nid, $range_options, $uid) + ->countQuery() + ->execute() + ->fetchField(); +} + +/** + * Given a set of range options, return an unexecuted select query. + * + * The query will have no fields as they should be added by the caller as + * desired. + * + * @param integer $nid + * The node id of the webform. + * @param array $range_options + * Associate array of range options. + * @param integer $uid + * The user id of the user whose last download information should be used, + * or the current user if NULL. This is unrelated to which user submitted + * the submissions. + */ +function webform_download_sids_query($nid, $range_options, $uid = NULL) { + $query = db_select('webform_submissions', 'ws') ->condition('nid', $nid) ->addTag('webform_download_sids'); switch ($range_options['range_type']) { case 'all': // All Submissions. - $query->orderBy('sid', 'ASC'); + $query->orderBy('ws.sid', 'ASC'); break; case 'new': // All Since Last Download. $download_info = webform_download_last_download_info($nid, $uid); $last_sid = $download_info ? $download_info['sid'] : 0; $query - ->condition('sid', $last_sid, '>') - ->orderBy('sid', 'ASC'); + ->condition('ws.sid', $last_sid, '>') + ->orderBy('ws.sid', 'ASC'); break; case 'latest': // Last x Submissions. $start_sid = webform_download_latest_start_sid($nid, $range_options['latest'], $range_options['completion_type']); - $query->condition('sid', $start_sid, '>='); + $query->condition('ws.sid', $start_sid, '>='); break; case 'range': // Submissions Start-End. - $query->condition('sid', $range_options['start'], '>='); + $query->condition('ws.sid', $range_options['start'], '>='); if ($range_options['end']){ - $query->condition('sid', $range_options['end'], '<='); + $query->condition('ws.sid', $range_options['end'], '<='); } - $query->orderBy('sid', 'ASC'); + $query->orderBy('ws.sid', 'ASC'); break; case 'range_serial': // Submissions Start-End, using serial numbers. - $query->condition('serial', $range_options['start'], '>='); + $query->condition('ws.serial', $range_options['start'], '>='); if ($range_options['end']){ - $query->condition('serial', $range_options['end'], '<='); + $query->condition('ws.serial', $range_options['end'], '<='); } - $query->orderBy('serial', 'ASC'); + $query->orderBy('ws.serial', 'ASC'); + break; + case 'range_date': + $date_field = $range_options['completion_type'] == 'finished' ? 'ws.completed' : 'ws.submitted'; + $query->condition($date_field, strtotime($range_options['start_date']), '>='); + if ($range_options['end_date'] != '' && ($end_time = strtotime($range_options['end_date'])) !== FALSE) { + if ($end_time == strtotime('midnight', $end_time)) { + // Full day specified + $end_time += 86399; // 86400 is a full day of seconds. + } + $query->condition($date_field, $end_time, '<='); + } + ; + $query->orderBy($date_field, 'ASC'); break; } @@ -1644,27 +1829,10 @@ function webform_download_sids($nid, $range_options, $uid = NULL) { $query->condition('is_draft', (int) ($range_options['completion_type'] === 'draft')); } - // Set batch properties. Unfortunately the pager query class reads directly - // from $_GET['page'] to determine the current page number, so we have to set - // this super-global and then restore it after executing the query. - if (isset($range_options['batch_number']) && isset($range_options['batch_size'])) { - if (isset($_GET['page'])) { - $current_page = $_GET['page']; - } - $_GET['page'] = $range_options['batch_number']; - $query = $query->extend('PagerDefault'); - $query->limit($range_options['batch_size']); - $query->element(0); - } - - $sids = $query->execute()->fetchCol(); - - // Restore the current page number (if any). - if (isset($current_page)) { - $_GET['page'] = $current_page; + if (isset($range_options['batch_number']) && !empty($range_options['batch_size'])) { + $query->range($range_options['batch_number'] * $range_options['batch_size'], $range_options['batch_size']); } - - return $sids; + return $query; } /** diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.submissions.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.submissions.inc index ab470e71..3c0fb59c 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.submissions.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.submissions.inc @@ -40,20 +40,42 @@ function webform_submission_data($node, $submitted) { /** * Given set of $form_state values, prepare a psuedo-submission. + * + * @param object $node + * The webform node object. + * @param object $account + * The user account that is creating this submission. + * @param array $form_state + * The form_state containing the values for the submission + * @param object $prototype + * An existing submission that is being previewed, if any. + * @return object + * A new submission object, possibly for preview */ -function webform_submission_create($node, $account, $form_state, $is_preview = FALSE) { - $submission = (object) array( - 'nid' => $node->nid, - 'uid' => $account->uid, - 'sid' => NULL, - 'submitted' => REQUEST_TIME, - 'remote_addr' => ip_address(), - 'is_draft' => TRUE, - 'preview' => $is_preview, - 'serial' => NULL, - 'data' => webform_submission_data($node, $form_state['values']['submitted']), - ); +function webform_submission_create($node, $account, $form_state, $is_preview = FALSE, $prototype = NULL) { + $data = webform_submission_data($node, $form_state['values']['submitted']); + if ($prototype) { + $submission = clone $prototype; + $submission->preview = $is_preview; + $submission->data = $data; + } + else { + $submission = (object) array( + 'nid' => $node->nid, + 'uid' => $account->uid, + 'sid' => NULL, + 'submitted' => REQUEST_TIME, + 'completed' => 0, + 'modified' => REQUEST_TIME, + 'remote_addr' => webform_ip_address($node), + 'is_draft' => TRUE, + 'highest_valid_page' => 0, + 'preview' => $is_preview, + 'serial' => NULL, + 'data' => $data, + ); drupal_alter('webform_submission_create', $submission, $node, $account, $form_state); + } return $submission; } @@ -74,6 +96,9 @@ function webform_submission_update($node, $submission) { $function($node, $submission); } + $submission->completed = empty($submission->completed) && !$submission->is_draft ? REQUEST_TIME : $submission->completed; + $submission->modified = REQUEST_TIME; + // Update the main submission info. drupal_write_record('webform_submissions', $submission, 'sid'); @@ -125,6 +150,7 @@ function webform_submission_insert($node, $submission) { if (empty($submission->serial)) { $submission->serial = _webform_submission_serial_next_value($node->nid); } + $submission->completed = empty($submission->completed) && !$submission->is_draft ? REQUEST_TIME : $submission->completed; drupal_write_record('webform_submissions', $submission); $is_new = TRUE; } @@ -207,7 +233,7 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) { $send_count = 0; foreach ($emails as $eid => $email) { // Set the HTML property based on availablity of MIME Mail. - $email['html'] = ($email['html'] && webform_email_html_capable()); + $email['html'] = ($email['html'] && webform_variable_get('webform_email_html_capable')); // Pass through the theme layer if using the default template. if ($email['template'] == 'default') { @@ -218,7 +244,7 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) { } // Replace tokens in the message. - $email['message'] = webform_replace_tokens($email['message'], $node, $submission, $email, $email['html'] ? TRUE : FALSE); + $email['message'] = webform_replace_tokens($email['message'], $node, $submission, $email, (boolean)$email['html']); // Build the e-mail headers. $email['headers'] = theme(array('webform_mail_headers_' . $node->nid, 'webform_mail_headers'), array('node' => $node, 'submission' => $submission, 'email' => $email)); @@ -230,17 +256,32 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) { unset($email['headers']['From']); } else { - // Replace "from_address" field content with chosen email mapping. For the - // from address, only a single address is supported, so we use the first. - if (isset($email['extra']['from_address_mapping'])) { - if (isset($submission->data[$email['from_address']][0])) { - $selected = $submission->data[$email['from_address']][0]; - if (isset($email['extra']['from_address_mapping'][$selected])) { - $email['from_address'] = $email['extra']['from_address_mapping'][$selected]; - } - } + // Format the From address. + $mapping = isset($email['extra']['from_address_mapping']) ? $email['extra']['from_address_mapping'] : NULL; + $email['from'] = webform_format_email_address($email['from_address'], $email['from_name'], $node, $submission, TRUE, TRUE, NULL, $mapping); + } + + // If requested and not already set, set Reply-To to the From and re-format From address. + if (webform_variable_get('webform_email_replyto') && + empty($email['headers']['Reply-To']) && + ($default_from_name = webform_variable_get('webform_default_from_name')) && + ($default_from_address = webform_variable_get('webform_default_from_address')) && + ($default_from_parts = explode('@', $default_from_address)) && + count($default_from_parts) == 2 && + $default_from_parts[1] && + stripos($email['from'], '@' . $default_from_parts[1]) === FALSE) { + // Message is not already being sent from the domain of the default + // webform from address. + $email['headers']['Reply-To'] = $email['from']; + $email['from'] = $default_from_address; + if (webform_variable_get('webform_email_address_format') == 'long') { + $email_parts = webform_parse_email_address($email['headers']['Reply-To']); + $from_name = t('!name via !site_name', + array('!name' => strlen($email_parts['name']) ? $email_parts['name'] : $email_parts['address'], + '!site_name' => $default_from_name)); + $from_name = implode(' ', array_map('mime_header_encode', explode(' ', $from_name))); + $email['from'] = '"' . $from_name . '" <' . $email['from'] . '>'; } - $email['from'] = webform_format_email_address($email['from_address'], $email['from_name'], $node, $submission); } // Update the subject if set in the themed headers. @@ -252,54 +293,44 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) { $email['subject'] = webform_format_email_subject($email['subject'], $node, $submission); } - // Replace the "email" field content with chosen email mapping. - if (isset($email['extra']['email_mapping']) && !empty($submission->data[$email['email']])) { - $component_emails = array(); - foreach ($submission->data[$email['email']] as $email_value) { - if (isset($email['extra']['email_mapping'][$email_value])) { - $component_emails[] = $email['extra']['email_mapping'][$email_value]; - } - } - $email['email'] = implode(',', $component_emails); - } - // Update the to e-mail if set in the themed headers. if (isset($email['headers']['To'])) { $email['email'] = $email['headers']['To']; unset($email['headers']['To']); + $addresses = array_filter(explode(',', $email['email'])); + } + else { + // Format the To address(es). + $mapping = isset($email['extra']['email_mapping']) ? $email['extra']['email_mapping'] : NULL; + $addresses = webform_format_email_address($email['email'], NULL, $node, $submission, TRUE, FALSE, NULL, $mapping); + $email['email'] = implode(',', $addresses); } // Generate the list of addresses that this e-mail will be sent to. - $addresses = array_filter(explode(',', $email['email'])); - $addresses_final = array(); - foreach ($addresses as $address) { - $address = trim($address); - - // After filtering e-mail addresses with component values, a single value - // might contain multiple addresses (such as from checkboxes or selects). - $address = webform_format_email_address($address, NULL, $node, $submission, TRUE, FALSE, 'short'); - - if (is_array($address)) { - foreach ($address as $new_address) { - $new_address = trim($new_address); - if (valid_email_address($new_address)) { - $addresses_final[] = $new_address; - } - } - } - elseif (valid_email_address($address)) { - $addresses_final[] = $address; - } + $addresses_final = array_filter($addresses, 'webform_valid_email_address'); + + if (!$addresses_final) { + continue; } - // Mail the webform results. + // Verify that this submission is not attempting to send any spam hacks. foreach ($addresses_final as $address) { - // Verify that this submission is not attempting to send any spam hacks. if (_webform_submission_spam_check($address, $email['subject'], $email['from'], $email['headers'])) { watchdog('webform', 'Possible spam attempt from @remote_addr' . "<br />\n" . nl2br(htmlentities($email['message'])), array('@remote_add' => ip_address())); drupal_set_message(t('Illegal information. Data not submitted.'), 'error'); return FALSE; } + } + + // Consolidate addressees into one message if permitted by configuration. + $send_increment = 1; + if (!webform_variable_get('webform_email_address_individual')) { + $send_increment = count($addresses_final); + $addresses_final = array(implode(', ', $addresses_final)); + } + + // Mail the webform results. + foreach ($addresses_final as $address) { $language = $user->uid ? user_preferred_language($user) : language_default(); $mail_params = array( @@ -311,7 +342,7 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) { 'email' => $email, ); - if (webform_email_html_capable()) { + if (webform_variable_get('webform_email_html_capable')) { // Load attachments for the e-mail. $attachments = array(); if ($email['attachments']) { @@ -344,7 +375,7 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) { // Mail the submission. $message = drupal_mail('webform', 'submission', $address, $language, $mail_params, $email['from']); if ($message['result']) { - $send_count++; + $send_count += $send_increment; } } } @@ -480,21 +511,22 @@ function webform_submission_resend($form, $form_state, $node, $submission) { $form['#submission'] = $submission; foreach ($node->webform['emails'] as $eid => $email) { - $email_addresses = array_filter(explode(',', check_plain($email['email']))); - foreach ($email_addresses as $key => $email_address) { - $email_addresses[$key] = webform_format_email_address($email_address, NULL, $node, $submission, FALSE); - } - $valid_email = !empty($email_addresses[0]) && valid_email_address($email_addresses[0]); + + $mapping = isset($email['extra']['email_mapping']) ? $email['extra']['email_mapping'] : NULL; + $addresses = webform_format_email_address($email['email'], NULL, $node, $submission, FALSE, FALSE, NULL, $mapping); + $addresses_valid = array_map('webform_valid_email_address', $addresses); + $valid_email = count($addresses) == array_sum($addresses_valid); + $form['resend'][$eid] = array( '#type' => 'checkbox', '#default_value' => $valid_email ? TRUE : FALSE, '#disabled' => $valid_email ? FALSE : TRUE, ); $form['emails'][$eid]['email'] = array( - '#markup' => implode('<br />', $email_addresses), + '#markup' => nl2br(check_plain(implode("\n", $addresses))), ); if (!$valid_email) { - $form['emails'][$eid]['email']['#markup'] .= ' (' . t('empty') . ')'; + $form['emails'][$eid]['email']['#markup'] .= ' (' . t('empty or invalid') . ')'; } $form['emails'][$eid]['subject'] = array( '#markup' => check_plain(webform_format_email_subject($email['subject'], $node, $submission)), @@ -583,7 +615,7 @@ function theme_webform_submission_resend($variables) { /** * Print a Webform submission for display on a page or in an e-mail. */ -function webform_submission_render($node, $submission, $email, $format, $excluded_components = array()) { +function webform_submission_render($node, $submission, $email, $format, $excluded_components = NULL) { $component_tree = array(); $renderable = array(); $page_count = 1; @@ -598,10 +630,20 @@ function webform_submission_render($node, $submission, $email, $format, $exclude // Set the theme function for submissions. $renderable['#theme'] = array('webform_submission_' . $node->nid, 'webform_submission'); - // Remove excluded components. $components = $node->webform['components']; - foreach ($excluded_components as $cid) { - unset($components[$cid]); + + // Remove excluded components. + if (is_array($excluded_components)) { + foreach ($excluded_components as $cid) { + unset($components[$cid]); + } + if ($email && $email['exclude_empty']) { + foreach($submission->data as $cid => $data) { + if (!isset($data[0]) || $data[0] == '') { + unset($components[$cid]); + } + } + } } module_load_include('inc', 'webform', 'includes/webform.components'); @@ -610,9 +652,10 @@ function webform_submission_render($node, $submission, $email, $format, $exclude // Make sure at least one field is available if (isset($component_tree['children'])) { // Recursively add components to the form. - $input_values = $submission->data; + $sorter = webform_get_conditional_sorter($node); + $input_values = $sorter->executeConditionals($submission->data); foreach ($component_tree['children'] as $cid => $component) { - if (_webform_client_form_rule_check($node, $component, $component['page_num'], $input_values, $format)) { + if ($sorter->componentVisibility($cid, $component['page_num']) == webformConditionals::componentShown) { _webform_client_form_add_component($node, $component, NULL, $renderable, $renderable, $input_values, $format); } } @@ -636,22 +679,42 @@ function webform_submission_render($node, $submission, $email, $format, $exclude * Optional. The number of submissions to include in the results. */ function webform_get_submissions($filters = array(), $header = NULL, $pager_count = 0) { - $submissions = array(); + return webform_get_submissions_load(webform_get_submissions_query($filters, $header, $pager_count)); +} +/** + * Returns an unexecuted webform_submissions query on for the arguments. + * + * This is an internal routine and not intended for use by other modules. + * + * @param $filters + * An array of filters to apply to this query. Usually in the format + * array('nid' => $nid, 'uid' => $uid). A single integer may also be passed + * in, which will be equivalent to specifying a $nid filter. 'sid' may also + * be included, either as a single sid or an array of sid's. + * @param $header + * If the results of this fetch will be used in a sortable + * table, pass the array header of the table. + * @param $pager_count + * Optional. The number of submissions to include in the results. + */ +function webform_get_submissions_query($filters = array(), $header = NULL, $pager_count = 0) { if (!is_array($filters)) { $filters = array('ws.nid' => $filters); } - // UID filters need to be against a specific table. - if (isset($filters['uid'])) { - $filters['u.uid'] = $filters['uid']; - unset($filters['uid']); + // Qualify all filters with a table alias. ws.* is assumed, except for u.uid. + foreach ($filters as $column => $value) { + if (strpos($column, '.') === FALSE) { + $filters[($column == 'uid' ? 'u.' : 'ws.') . $column] = $value; + unset($filters[$column]); + } } // If the sid is specified, but there are none, force the query to fail // rather than query on an empty array - if (isset($filters['sid']) && empty($filters['sid'])) { - $filters['sid'] = 0; + if (isset($filters['ws.sid']) && empty($filters['ws.sid'])) { + $filters['ws.sid'] = 0; } // Build the list of submissions and load their basic information. @@ -660,7 +723,9 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun // sorting on a column that uses multiple rows for its data. ->distinct() ->addTag('webform_get_submissions_sids') - ->fields('ws', array('sid', 'nid', 'serial', 'submitted', 'remote_addr', 'uid', 'is_draft')); + ->fields('ws'); + + // Add each filter. foreach ($filters as $column => $value) { $pager_query->condition($column, $value); } @@ -671,10 +736,10 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun if (isset($filters['u.uid']) && $filters['u.uid'] === 0) { if (!empty($_SESSION['webform_submission'])) { $anonymous_sids = array_keys($_SESSION['webform_submission']); - $pager_query->condition('sid', $anonymous_sids, 'IN'); + $pager_query->condition('ws.sid', $anonymous_sids, 'IN'); } else { - $pager_query->condition('sid', 0); + $pager_query->condition('ws.sid', 0); } } @@ -684,7 +749,8 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun $metadata_columns[] = $header_item['data']; } $sort = drupal_get_query_parameters(); - // Sort by submitted data column if order is set but not in $metadata_columns. + // Sort by submitted data column if order is set but not in + // $metadata_columns. if (isset($sort['order']) && !in_array($sort['order'], $metadata_columns, TRUE)) { // Default if sort is unset or invalid. if (!isset($sort['sort']) || !in_array($sort['sort'], array('asc', 'desc'), TRUE)) { @@ -693,7 +759,7 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun $pager_query->leftJoin('webform_component', 'wc', 'ws.nid = wc.nid AND wc.name = :form_key', array('form_key' => $sort['order'])); $pager_query->leftJoin('webform_submitted_data', 'wsd', 'wc.nid = wsd.nid AND ws.sid = wsd.sid AND wc.cid = wsd.cid'); $pager_query->orderBy('wsd.data', $sort['sort']); - $pager_query->orderBy('sid', 'ASC'); + $pager_query->orderBy('ws.sid', 'ASC'); } // Sort by metadata column. else { @@ -703,7 +769,7 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun } } else { - $pager_query->orderBy('sid', 'ASC'); + $pager_query->orderBy('ws.sid', 'ASC'); } if ($pager_count) { @@ -711,43 +777,75 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun $pager_query = $pager_query->extend('PagerDefault'); $pager_query->limit($pager_count); } - $result = $pager_query->execute(); + return $pager_query; +} - $sids = array(); - foreach ($result as $row) { - $sids[] = $row->sid; - $submissions[$row->sid] = new stdClass(); - $submissions[$row->sid]->sid = $row->sid; - $submissions[$row->sid]->nid = $row->nid; - $submissions[$row->sid]->serial = $row->serial; - $submissions[$row->sid]->submitted = $row->submitted; - $submissions[$row->sid]->remote_addr = $row->remote_addr; - $submissions[$row->sid]->uid = $row->uid; - $submissions[$row->sid]->name = $row->name; - $submissions[$row->sid]->is_draft = $row->is_draft; - $submissions[$row->sid]->preview = FALSE; - $submissions[$row->sid]->data = array(); +/** + * Retrieve and load the submissions for the specified submissions query. + * + * This is an internal routine and not intended for use by other modules. + * + * @params object $pager_query + * A select or extended select query containing the needed fields: + * webform_submissions: all fields + * user: name + * @return array + * An array of loaded webform submissions. + */ +function webform_get_submissions_load($pager_query) { + // If the "$pager_query" is actually an unextended select query, then instead + // of querying the webform_submissions_data table with a pontentially huge + // array of sids in an IN clause, use the select query directy as this will + // be much faster. Extended queries don't work in join clauses. The query + // is assumed to include the sid. + if ($pager_query instanceof SelectQuery) { + $submissions_query = clone $pager_query; } + // Extract any filter on node id to use in an optimization below + foreach ($pager_query->conditions() as $index => $condition) { + if ($index !== '#conjunction' && $condition['operator'] === '=' && ($condition['field'] === 'nid' || $condition['field'] === 'ws.nid')) { + $nid = $condition['value']; + break; + } + } + + $result = $pager_query->execute(); + $submissions = $result->fetchAllAssoc('sid'); + // If there are no submissions being retrieved, return an empty array. - if (empty($sids)) { + if (!$submissions) { return $submissions; } + foreach ($submissions as $sid => $submission) { + $submissions[$sid]->preview = FALSE; + $submissions[$sid]->data = array(); + } + // Query the required submission data. $query = db_select('webform_submitted_data', 'sd'); $query ->addTag('webform_get_submissions_data') ->fields('sd', array('sid', 'cid', 'no', 'data')) - ->condition('sd.sid', $sids, 'IN') ->orderBy('sd.sid', 'ASC') ->orderBy('sd.cid', 'ASC') ->orderBy('sd.no', 'ASC'); + if (isset($submissions_query)) { + // If available, prefer joining on the subquery as it is much faster than an + // IN clause on a large array. A subquery with the IN operator doesn't work + // when the subquery has a LIMIT clause, requiring an inner join instead. + $query->innerJoin($submissions_query, 'ws2', 'sd.sid = ws2.sid'); + } + else { + $query->condition('sd.sid', array_keys($submissions), 'IN'); + } + // By adding the NID to this query we allow MySQL to use the primary key on // in webform_submitted_data for sorting (nid_sid_cid_no). - if (isset($filters['nid'])) { - $query->condition('sd.nid', $filters['nid']); + if (isset($nid)) { + $query->condition('sd.nid', $nid); } $result = $query->execute(); @@ -785,7 +883,6 @@ function webform_get_submission_count($nid, $uid = NULL, $is_draft = 0) { $query = db_select('webform_submissions', 'ws') ->addTag('webform_get_submission_count') ->condition('ws.nid', $nid); - $arguments = array($nid); if ($uid !== NULL) { $query->condition('ws.uid', $uid); } @@ -847,7 +944,7 @@ function _webform_submission_spam_check($to, $subject, $from, $headers = array() */ function webform_submission_user_limit_check($node, $account = NULL) { global $user; - $tracking_mode = variable_get('webform_tracking_mode', 'cookie'); + $tracking_mode = webform_variable_get('webform_tracking_mode'); if (!isset($account)) { $account = $user; @@ -864,26 +961,26 @@ function webform_submission_user_limit_check($node, $account = NULL) { return FALSE; // No check enabled. } - // Retrieve submission data for this IP address or username from the database. - $query = db_select('webform_submissions') - ->addTag('webform_submission_user_limit_check') - ->condition('nid', $node->nid) - ->condition('is_draft', 0); - - if ($node->webform['submit_interval'] != -1) { - $query->condition('submitted', REQUEST_TIME - $node->webform['submit_interval'], '>'); - } - - if ($account->uid) { - $query->condition('uid', $account->uid); - } - else { - $query->condition('remote_addr', ip_address()); - } - - // Fetch all the entries from the database within the submit interval with this username and IP. + // Fetch all the entries from the database within the submit interval with + // this username and IP. $num_submissions_database = 0; - if ($account->uid !== 0 || $tracking_mode === 'ip_address' || $tracking_mode === 'strict') { + if (!$node->webform['confidential'] && + ($account->uid !== 0 || $tracking_mode === 'ip_address' || $tracking_mode === 'strict')) { + $query = db_select('webform_submissions') + ->addTag('webform_submission_user_limit_check') + ->condition('nid', $node->nid) + ->condition('is_draft', 0); + + if ($node->webform['submit_interval'] != -1) { + $query->condition('submitted', REQUEST_TIME - $node->webform['submit_interval'], '>'); + } + + if ($account->uid) { + $query->condition('uid', $account->uid); + } + else { + $query->condition('remote_addr', ip_address()); + } $num_submissions_database = $query->countQuery()->execute()->fetchField(); } @@ -903,9 +1000,6 @@ function webform_submission_user_limit_check($node, $account = NULL) { // Count the number of submissions recorded in cookies. $num_submissions_cookie = count($_COOKIE[$cookie_name]); } - else { - $num_submissions_cookie = 0; - } } if ($num_submissions_database >= $node->webform['submit_limit'] || $num_submissions_cookie >= $node->webform['submit_limit']) { diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.webformconditionals.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.webformconditionals.inc new file mode 100644 index 00000000..d406838c --- /dev/null +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.webformconditionals.inc @@ -0,0 +1,485 @@ +<?php + +/** + * @file + * Conditional engine to process dependencies within the webform's conditionals. + */ + +/** + * Performs analysis and topological sorting on the conditionals. + */ +class WebformConditionals { + + // Define constants for the result of an analysis of the conditionals on + // a page for a given set of input values. Determines whether the component + // is always hidden, always shown, or may or may not be shown depending upon + // other values on the same page. In the last case, the component needs to be + // rendered on the page because at least one source component is on the same + // page. The field will be hidden with JavaScript. + const componentHidden = 0; + const componentShown = 1; + const componentDependent = 2; + + protected static $conditionals = array(); + + protected $node; + protected $topologicalOrder; + protected $pageMap; + protected $childrenMap; + protected $visibilityMap; + protected $requiredMap; + + public $errors; + + /** + * Creates and caches a WebformConditional for a given node. + */ + static function factory($node) { + if (!isset(self::$conditionals[$node->nid])) { + self::$conditionals[$node->nid] = new WebformConditionals($node); + } + return self::$conditionals[$node->nid]; + } + + /** + * Constructs a WebformConditional. + */ + function __construct($node) { + $this->node = $node; + } + + /** + * Sorts the conditionals into topological order. + * + * The "nodes" of the list are the conditionals, not the components that + * they operate upon. + * + * The webform components must already be sorted into component tree order + * before calling this method. + * + * See http://en.wikipedia.org/wiki/Topological_sorting + */ + protected function topologicalSort() { + $components = $this->node->webform['components']; + $conditionals = $this->node->webform['conditionals']; + $errors = array(); + + // Generate a component to condional map for conditional targets. + $cid_to_target_rgid = array(); + $cid_hidden = array(); + foreach ($conditionals as $rgid => $conditional) { + foreach ($conditional['actions'] as $aid => $action) { + $target_id = $action['target']; + $cid_to_target_rgid[$target_id][$rgid] = $rgid; + if ($action['action'] == 'show') { + $cid_hidden[$target_id] = isset($cid_hidden[$target_id]) ? $cid_hidden[$target_id] + 1 : 1; + if ($cid_hidden[$target_id] == 2) { + $component = $components[$target_id]; + $errors[$component['page_num']][] = t('More than one conditional hides or shows component "@name".', + array('@name' => $component['name'])); + } + } + } + } + + // Generate T-Orders for each page + $new_entry = array('in' => array(), 'out' => array(), 'rgid' => array()); + $page_num = 0; + // If the first component is a page break, then no component is on page 1. Create empty arrays for page 1. + $sorted = array(1 => array()); + $page_map = array(1 => array()); + $component = reset($components); + while ($component) { + $cid = $component['cid']; + + // Start a new page, if needed + if ($component['page_num'] > $page_num ) { + $page_num = $component['page_num']; + // Create an empty list that will contain the sorted elements. + // This list is known as L in the literature. + $sorted[$page_num] = array(); + + // Create an empty list of dependency nodes for this page. + $nodes = array(); + } + + // Create the pageMap as a side benefit of generating the t-sort. + $page_map[$page_num][$cid] = $cid; + + // Process component by adding it's conditional data to a component-tree-traversal order an index of: + // - incoming dependencies = the source components for the conditions for this target component and + // - outgoing dependencies = components which depend upon the target component + // Note: Surprisingly, 0 is a valid rgid, as well as a valid rid. Use -1 as a semaphore. + if (isset($cid_to_target_rgid[$cid])) { + // The component is the target of conditional(s) + foreach ($cid_to_target_rgid[$cid] as $rgid) { + $conditional = $conditionals[$rgid]; + if (!isset($nodes[$cid])) { + $nodes[$cid] = $new_entry; + } + $nodes[$cid]['rgid'][$rgid] = $rgid; + foreach ($conditional['rules'] as $rule) { + if ($rule['source_type'] == 'component') { + $source_id = $rule['source']; + if (!isset($nodes[$source_id])) { + $nodes[$source_id] = $new_entry; + } + $nodes[$cid]['in'][$source_id] = $source_id; + $nodes[$source_id]['out'][$cid] = $cid; + $source_component = $components[$source_id]; + $source_pid = $source_component['pid']; + if ($source_pid) { + if (!isset($nodes[$source_pid])) { + $nodes[$source_pid] = $new_entry; + } + // The rule source is within a parent fieldset. Create a dependency on the parent. + $nodes[$source_pid]['out'][$source_id] = $source_id; + $nodes[$source_id]['in'][$source_pid] = $source_pid; + } + if ($source_component['page_num'] > $page_num) { + $errors[$page_num][] = t('A forward reference from page @from, %from to %to was found.', + array( + '%from' => $source_component['name'], + '@from' => $source_component['page_num'], + '%to' => $component['name'], + )); + } + elseif ($source_component['page_num'] == $page_num && $component['type'] == 'pagebreak') { + $errors[$page_num][] = t('The page break %to can\'t be controlled by %from on the same page.', + array( + '%from' => $source_component['name'], + '%to' => $component['name'], + )); + } + } + } + } + } + + // Fetch the next component, if any. + $component = next($components); + + // Finish any previous page already processed. + if (!$component || $component['page_num'] > $page_num) { + + // Create a set of all components which have are not dependent upon anything. + // This list is known as S in the literature. + $start_nodes = array(); + foreach ($nodes as $id => $n) { + if (!$n['in']) { + $start_nodes[] = $id; + } + } + + // Process the start nodes, removing each one in turn from the queue. + while ($start_nodes) { + $id = array_shift($start_nodes); + // If the node represents an actual conditional, it can now be added + // to the end of the sorted order because anything it depends upon has + // already been calcuated. + if ($nodes[$id]['rgid']) { + foreach ($nodes[$id]['rgid'] as $rgid) { + $sorted[$page_num][] = array( + 'cid' => $id, + 'rgid' => $rgid, + 'name' => $components[$id]['name'], + ); + + } + } + + // Any other nodes that depend upon this node may now have their dependency + // on this node removed, since it has now been calculated. + foreach ($nodes[$id]['out'] as $out_id) { + unset($nodes[$out_id]['in'][$id]); + if (!$nodes[$out_id]['in']) { + $start_nodes[] = $out_id; + } + } + + // All out-going dependencies have been handled. + $nodes[$id]['out'] = array(); + } + + // Check for a cyclic graph (circular dependency) + foreach ($nodes as $id => $n) { + if ($n['in'] || $n['out']) { + $errors[$page_num][] = t('A circular reference involving %name was found.', + array('%name' => $components[$id]['name'])); + } + } + + } // End finshing previous page + + } // End component loop + + // Create an empty page map for the preview page. + $page_map[$page_num + 1] = array(); + + $this->topologicalOrder = $sorted; + $this->errors = $errors; + $this->pageMap = $page_map; + } + + /** + * Returns the (possibly cached) topological sort order. + */ + function getOrder() { + if (!$this->topologicalOrder) { + $this->topologicalSort(); + } + return $this->topologicalOrder; + } + + /** + * Returns an index of components by page number. + */ + function getPageMap() { + if (!$this->pageMap) { + $this->topologicalSort(); + } + return $this->pageMap; + } + + /** + * Displays and error messages from the previously-generated sort order. + * + * User's who can't fix the webform are shown a single, simplified message. + */ + function reportErrors() { + $this->getOrder(); + if ($this->errors) { + if (webform_node_update_access($this->node)) { + foreach ($this->errors as $page_num => $page_errors) { + drupal_set_message(format_plural(count($page_errors), + 'Conditional error on page @num:', + 'Conditional errors on page @num:', + array('@num' => $page_num)) . + '<br /><ul><li>' . implode('</li><li>', $page_errors) . '</li></ul>', 'warning'); + } + } + else { + drupal_set_message(t('This form is improperly configured. Contact the administrator.'), 'warning'); + } + } + } + + /** + * Creates and caches a map of the children of a each component. + * + * Called after the component tree has been made and then flattened again. + * Alas, the children data is removed when the tree is flattened. The + * components are indexecd by cid but in tree order. Because cid's are + * numeric, they may not appear in IDE's or debuggers in their actual order. + */ + function getChildrenMap() { + if (!$this->childrenMap) { + $map = array(); + foreach ($this->node->webform['components'] as $cid => $component) { + $pid = $component['pid']; + if ($pid) { + $map[$pid][] = $cid; + } + } + + $this->childrenMap = $map; + } + return $this->childrenMap; + } + + /** + * Deletes the value of the given component, plus any descendents. + */ + protected function deleteFamily(&$input_values, $parent_id, &$page_visiblity_page) { + if (isset($input_values[$parent_id])) { + $input_values[$parent_id] = NULL; + } + if (isset($this->childrenMap[$parent_id])) { + foreach ($this->childrenMap[$parent_id] as $child_id) { + $page_visiblity_page[$child_id] = $page_visiblity_page[$parent_id]; + $this->deleteFamily($input_values, $child_id, $page_visiblity_page); + } + } + } + + /** + * Executes the conditionals on a submission, removing any data which should + * be hidden. + */ + function executeConditionals($input_values, $page_num = 0) { + $this->getOrder(); + $this->getChildrenMap(); + if (!$this->visibilityMap || $page_num == 0) { + // Create a new visitibily map, with all components shown. + $this->visibilityMap = $this->pageMap; + array_walk_recursive($this->visibilityMap, function (&$status) { + $status = WebformConditionals::componentShown; + }); + // Create an empty required map + $this->requiredMap = array_fill(1, count($this->pageMap), array()); + + } else { + array_walk($this->visibilityMap[$page_num], function (&$status) { + $status = WebformConditionals::componentShown; + }); + $this->requiredMap[$page_num] = array(); + } + + module_load_include('inc', 'webform', 'includes/webform.conditionals'); + + $components = $this->node->webform['components']; + $conditionals = $this->node->webform['conditionals']; + $operators = webform_conditional_operators(); + $targetLocked = array(); + + $first_page = $page_num ? $page_num : 1; + $last_page = $page_num ? $page_num : count($this->topologicalOrder); + for ($page = $first_page; $page <= $last_page; $page++) { + foreach ($this->topologicalOrder[$page] as $conditional_spec) { + + $conditional = $conditionals[$conditional_spec['rgid']]; + $conditional_result = TRUE; + $source_page_nums = array(); + + // Execute each comparison callback. + $conditional_results = array(); + foreach ($conditional['rules'] as $rule) { + // TODO: Support other source types besides components? + if ($rule['source_type'] !== 'component') { + continue; + } + $source_component = $components[$rule['source']]; + $source_cid = $source_component['cid']; + + $source_values = array(); + if (isset($input_values[$source_cid])) { + $component_value = $input_values[$source_cid]; + // For select_or_other components, use only the select values because $source_values must not be a nested array. + // During preview, the array is already flattened. + if ($source_component['type'] === 'select' && + !empty($source_component['extra']['other_option']) && + isset($component_value['select'])) { + $component_value = $component_value['select']; + } + $source_values = is_array($component_value) ? $component_value : array($component_value); + } + + // Determine the operator and callback. + $conditional_type = webform_component_property($source_component['type'], 'conditional_type'); + $operator_info = $operators[$conditional_type]; + + // Perform the comparison callback and build the results for this group. + $comparison_callback = $operator_info[$rule['operator']]['comparison callback']; + $conditional_results[] = $comparison_callback($source_values, $rule['value'], $source_component); + + // Record page number to later determine any intra-page dependency on this source. + $source_page_nums[$source_component['page_num']] = $source_component['page_num']; + } + + // Calculate the and/or result. + $filtered_results = array_filter($conditional_results); + $conditional_result = $conditional['andor'] === 'or' + ? count($filtered_results) > 0 + : count($filtered_results) === count($conditional_results); + + foreach ($conditional['actions'] as $action) { + $action_result = $action['invert'] ? !$conditional_result : $conditional_result; + $target = $action['target']; + $page_num = $components[$target]['page_num']; + switch ($action['action']) { + case 'show': + if (!$action_result) { + $this->visibilityMap[$page_num][$target] = in_array($page_num, $source_page_nums) ? self::componentDependent : self::componentHidden; + $this->deleteFamily($input_values, $target, $this->visibilityMap[$page_num]); + $targetLocked[$target] = TRUE; + } + break; + case 'require': + $this->requiredMap[$page_num][$target] = $action_result; + break; + case 'set': + if ($action_result && empty($targetLocked[$target]) && $components[$target]['type'] != 'markup') { + $input_values[$target] = $action['argument']; + } + break; + } + } + + + } // End conditinal loop + } // End page loop + + return $input_values; + } + + /** + * Returns whether a given component is always hidden, always shown, or might + * be shown depending upon other sources on the same page. + * + * Assumes that the conditionals have already been executed on the given page. + * + * @param integer $cid + * The component id of the component whose visibilty is being sought. + * @param integer $page_num + * The page number that the component is on. + * @return integer + * self::componentHidden, ...Shown, or ...Dependent. + */ + function componentVisibility($cid, $page_num) { + if (!$this->visibilityMap) { + // The conditionals have not yet been executed on a submission. + $this->executeConditionals(array(), 0); + watchdog('webform', 'WebformConditionals::componentVisibility called prior to evaluating a submission.', 'error'); + } + return isset($this->visibilityMap[$page_num][$cid]) ? $this->visibilityMap[$page_num][$cid] : self::componentShown; + } + + /** + * Returns whether a given page should be displayed. This requires any + * conditional for the page itself to be shown, plus at least one component + * within the page must be shown too. The first and preview pages are always + * shown, however. + * + * @param integer $page_num + * The page number that the component is on. + * @return integer + * self::componentHidden or ...Shown. + */ + function pageVisibility($page_num) { + $result = self::componentHidden; + if ($page_num == 1 || empty($this->visibilityMap[$page_num])) { + $result = self::componentShown; + } elseif (($page_map = $this->pageMap[$page_num]) && $this->componentVisibility(reset($page_map), $page_num)) { + while ($cid = next($page_map)) { + if ($this->componentVisibility($cid, $page_num) != self::componentHidden) { + $result = self::componentShown; + break; + } + } + } + return $result; + } + + /** + * Returns whether a given component is always required, always opption, or + * unchanged by conditional logic. + * + * Assumes that the conditionals have already been executed on the given page. + * + * @param integer $cid + * The component id of the component whose required state is being sought + * @param integer $page_num + * The page number that the component is on. + * @return boolean + * Whether the component is required based on conditionals. + */ + function componentRequired($cid, $page_num) { + if (!$this->requiredMap) { + // The conditionals have not yet been executed on a submission. + $this->executeConditionals(array(), 0); + watchdog('webform', 'WebformConditionals::componentRequired called prior to evaluating a submission.', 'error'); + } + return isset($this->requiredMap[$page_num][$cid]) ? $this->requiredMap[$page_num][$cid] : NULL; + } + +} diff --git a/profiles/wcm_base/modules/contrib/webform/js/webform-admin.js b/profiles/wcm_base/modules/contrib/webform/js/webform-admin.js index 1bf6aed9..2050649a 100644 --- a/profiles/wcm_base/modules/contrib/webform/js/webform-admin.js +++ b/profiles/wcm_base/modules/contrib/webform/js/webform-admin.js @@ -18,6 +18,9 @@ Drupal.behaviors.webformAdmin.attach = function(context) { Drupal.webform.downloadExport(context); // Enhancements for the conditionals administrative page. Drupal.webform.conditionalAdmin(context); + // Trigger radio/checkbox change when label click automatically selected by + // browser. + Drupal.webform.radioLabelAutoClick(context); } Drupal.webform = Drupal.webform || {}; @@ -46,6 +49,7 @@ Drupal.webform.setActive = function(context) { } }; +// Upldate e-mail templates between default and custom. Drupal.webform.updateTemplate = function(context) { var defaultTemplate = $('#edit-templates-default').val(); var $templateSelect = $('#webform-template-fieldset select#edit-template-option', context); @@ -141,9 +145,19 @@ Drupal.webform.conditionalAdmin = function(context) { if ($target.is('.webform-conditional-andor select')) { Drupal.webform.conditionalAndOrChange.apply(e.target); } + + if ($target.is('.webform-conditional-action select')) { + Drupal.webform.conditionalActionChange.apply(e.target); + } }); + // Add event handlers to delete the entire row if the last rule or action is removed. $context.find('.webform-conditional-rule-remove:not(.webform-conditional-processed)').bind('click', function() { + this.webformRemoveClass = '.webform-conditional-rule-remove'; + window.setTimeout($.proxy(Drupal.webform.conditionalRemove, this), 100); + }).addClass('webform-conditional-processed'); + $context.find('.webform-conditional-action-remove:not(.webform-conditional-processed)').bind('click', function() { + this.webformRemoveClass = '.webform-conditional-action-remove'; window.setTimeout($.proxy(Drupal.webform.conditionalRemove, this), 100); }).addClass('webform-conditional-processed'); @@ -151,6 +165,9 @@ Drupal.webform.conditionalAdmin = function(context) { // the operator handlers. $context.find('.webform-conditional-source select').trigger('change'); + // Trigger defaults handlers on the action element. + $context.find('.webform-conditional-action select').trigger('change'); + // When adding a new table row, make it draggable and hide the weight column. if ($context.is('tr.ajax-new-content') && $context.find('.webform-conditional').length === 1) { Drupal.tableDrag['webform-conditionals-table'].makeDraggable($context[0]); @@ -167,8 +184,8 @@ Drupal.webform.conditionalAdmin = function(context) { */ Drupal.webform.conditionalRemove = function() { // See if there are any remaining rules in this element. - var ruleCount = $(this).parents('.webform-conditional:first').find('.webform-conditional-rule-remove').length; - if (ruleCount <= 1) { + var rowCount = $(this).parents('.webform-conditional:first').find(this.webformRemoveClass).length; + if (rowCount <= 1) { var $tableRow = $(this).parents('tr:first'); var $table = $('#webform-conditionals-table'); if ($tableRow.length && $table.length) { @@ -197,12 +214,15 @@ Drupal.webform.conditionalSourceChange = function() { var $newList = $originalList.filter('optgroup[label=' + dataType + ']'); var newHTML = $newList[0].innerHTML; - // Update the options and fire the change event handler on the list to update the value field, - // only if the options have changed. This avoids resetting existing selections. + // Update the options and fire the change event handler on the list to update + // the value field, only if the options have changed. This avoids resetting + // existing selections. if (newHTML != $operator.html()) { $operator.html(newHTML); - $operator.trigger('change'); } + // Trigger the change in case the source component changed from one select + // component to another. + $operator.trigger('change'); } @@ -219,6 +239,7 @@ Drupal.webform.conditionalOperatorChange = function() { // Given the dataType and operator, we can determine the form key. var formKey = Drupal.settings.webform.conditionalValues.operators[dataType][operator]['form']; + var formSource = typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'undefined' ? false : source; // On initial request, save the default field as printed on the original page. if (!$value[0]['webformConditionalOriginal']) { @@ -226,13 +247,15 @@ Drupal.webform.conditionalOperatorChange = function() { originalValue = $value.find('input:first').val(); } // On changes to an existing operator, check if the form key is different - // before bothering with replacing the form with an identical version. - else if ($value[0]['webformConditionalFormKey'] == formKey) { + // (and any per-source form, such as a select option list) before replacing + // the form with an identical version. + else if ($value[0]['webformConditionalFormKey'] == formKey && $value[0]['webformConditionalFormSource'] == formSource) { return; } // Store the current form key for checking the next time the operator changes. $value[0]['webformConditionalFormKey'] = formKey; + $value[0]['webformConditionalFormSource'] = formSource; // If using the default (a textfield), restore the original field. if (formKey === 'default') { @@ -242,17 +265,14 @@ Drupal.webform.conditionalOperatorChange = function() { else if (formKey === false) { $value[0].innerHTML = ' '; } - // Lastly check if there is a specialized form for this source and operator. + // If there is a per-source form for this operator (e.g. option lists), use + // the specialized value form. + else if (typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'object') { + $value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey][source]; + } + // Otherwise all the sources use a generic field (e.g. a text field). else { - // If there is a per-source form for this operator (e.g. option lists), use - // the specialized value form. - if (typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'object') { - $value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey][source]; - } - // Otherwise all the sources use a generic field (e.g. a text field). - else { - $value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey]; - } + $value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey]; } // Set the name attribute to match the original placeholder field. @@ -271,6 +291,28 @@ Drupal.webform.conditionalAndOrChange = function() { $(this).parents('.webform-conditional:first').find('.webform-conditional-andor select').val(this.value); } +/** + * Event callback to show argument only for appropriate actions. + */ +Drupal.webform.conditionalActionChange = function() { + var action = $(this).val(); + var $argument = $(this).parents('.webform-conditional:first').find('.webform-conditional-argument input'); + var isShown = $argument.is(':visible'); + switch (action) { + case 'show': + case 'require': + if (isShown) { + $argument.hide(); + } + break; + case 'set': + if (!isShown) { + $argument.show(); + } + break; + } +} + /** * Given a table's DOM element, restripe the odd/even classes. */ @@ -285,4 +327,19 @@ Drupal.webform.restripeTable = function(table) { .filter(':even').filter('.even') .removeClass('even').addClass('odd'); }; + +/** + * Triggers a change event when a label receives a click. + * + * When the browser automatically selects a radio button when it's label is + * clicked, the FAPI states jQuery code doesn't receive an event. This function + * ensures that automatically-selected radio buttons keep in sync with the + * FAPI states. + */ +Drupal.webform.radioLabelAutoClick = function(context) { + $('label').once('webform-label').click(function(){ + $(this).prev('input:radio').change(); + }); +} + })(jQuery); diff --git a/profiles/wcm_base/modules/contrib/webform/js/webform.js b/profiles/wcm_base/modules/contrib/webform/js/webform.js index 2304c6be..887c8213 100644 --- a/profiles/wcm_base/modules/contrib/webform/js/webform.js +++ b/profiles/wcm_base/modules/contrib/webform/js/webform.js @@ -98,9 +98,7 @@ Drupal.webform.conditional = function(context) { $currentForm.bind('change', { 'settings': settings }, Drupal.webform.conditionalCheck); // Trigger all the elements that cause conditionals on this form. - $.each(Drupal.settings.webform.conditionals[formKey]['sourceMap'], function(elementKey) { - $currentForm.find('.' + elementKey).find('input,select,textarea').filter(':first').trigger('change'); - }); + Drupal.webform.doConditions($form, settings); }) }); }; @@ -115,63 +113,118 @@ Drupal.webform.conditionalCheck = function(e) { var $form = $triggerElement.closest('form'); var triggerElementKey = $triggerElement.attr('class').match(/webform-component--[^ ]+/)[0]; var settings = e.data.settings; - - if (settings.sourceMap[triggerElementKey]) { - $.each(settings.sourceMap[triggerElementKey], function(n, rgid) { - var ruleGroup = settings.ruleGroups[rgid]; - - // Perform the comparison callback and build the results for this group. - var conditionalResult = true; - var conditionalResults = []; - $.each(ruleGroup['rules'], function(m, rule) { - var elementKey = rule['source']; - var element = $form.find('.' + elementKey)[0]; - var existingValue = settings.values[elementKey] ? settings.values[elementKey] : null; - conditionalResults.push(window['Drupal']['webform'][rule.callback](element, existingValue, rule['value'] )); - }); + Drupal.webform.doConditions($form, settings); + } +}; - // Filter out false values. - var filteredResults = []; - for (var i = 0; i < conditionalResults.length; i++) { - if (conditionalResults[i]) { - filteredResults.push(conditionalResults[i]); - } - } +/** + * Processes all conditional. + */ +Drupal.webform.doConditions = function($form, settings) { + // Track what has be set/shown for each target component. + var targetLocked = []; + + $.each(settings.ruleGroups, function(rgid_key, rule_group) { + var ruleGroup = settings.ruleGroups[rgid_key]; + + // Perform the comparison callback and build the results for this group. + var conditionalResult = true; + var conditionalResults = []; + $.each(ruleGroup['rules'], function(m, rule) { + var elementKey = rule['source']; + var element = $form.find('.' + elementKey)[0]; + var existingValue = settings.values[elementKey] ? settings.values[elementKey] : null; + conditionalResults.push(window['Drupal']['webform'][rule.callback](element, existingValue, rule['value'] )); + }); - // Calculate the and/or result. - if (ruleGroup['andor'] === 'or') { - conditionalResult = filteredResults.length > 0; - } - else { - conditionalResult = filteredResults.length === conditionalResults.length; + // Filter out false values. + var filteredResults = []; + for (var i = 0; i < conditionalResults.length; i++) { + if (conditionalResults[i]) { + filteredResults.push(conditionalResults[i]); } + } - // Flip the result of the action is to hide. - var showComponent; - if (ruleGroup['action'] == 'hide') { - showComponent = !conditionalResult; - } - else { - showComponent = conditionalResult; - } + // Calculate the and/or result. + if (ruleGroup['andor'] === 'or') { + conditionalResult = filteredResults.length > 0; + } + else { + conditionalResult = filteredResults.length === conditionalResults.length; + } - var $target = $form.find('.' + ruleGroup['target']); - var $targetElements; - if (showComponent) { - $targetElements = $target.find('.webform-conditional-disabled').removeClass('webform-conditional-disabled'); - $.fn.prop ? $targetElements.prop('disabled', false) : $targetElements.removeAttr('disabled'); - $target.show(); + $.each(ruleGroup['actions'], function(aid, action) { + var $target = $form.find('.' + action['target']); + var actionResult = action['invert'] ? !conditionalResult : conditionalResult; + switch (action['action']) { + case 'show': + if (actionResult != Drupal.webform.isVisible($target)) { + var $targetElements = actionResult + ? $target.find('.webform-conditional-disabled').removeClass('webform-conditional-disabled') + : $target.find(':input').addClass('webform-conditional-disabled'); + $targetElements.webformProp('disabled', !actionResult); + $target.toggleClass('webform-conditional-hidden', !actionResult); + if (actionResult) { + $target.show(); + } + else { + $target.hide(); + // Record that the target was hidden. + targetLocked[action['target']] = 'hide'; + } + } + break; + case 'require': + var $requiredSpan = $target.find('.form-required, .form-optional').first(); + if (actionResult != $requiredSpan.hasClass('form-required')) { + var $targetInputElements = $target.find("input:text,textarea,input[type='email'],select,input:radio,input:file"); + // Rather than hide the required tag, remove it so that other jQuery can respond via Drupal behaviors. + Drupal.detachBehaviors($requiredSpan); + $targetInputElements + .webformProp('required', actionResult) + .toggleClass('required', actionResult); + if (actionResult) { + $requiredSpan.replaceWith('<span class="form-required" title="' + Drupal.t('This field is required.') + '">*</span>'); + } + else { + $requiredSpan.replaceWith('<span class="form-optional"></span>'); + } + Drupal.attachBehaviors($requiredSpan); + } + break; + case 'set': + var isLocked = targetLocked[action['target']]; + var $texts = $target.find("input:text,textarea,input[type='email']"); + var $selects = $target.find('select,select option,input:radio,input:checkbox'); + if (actionResult) { + var multiple = $.map(action['argument'].split(','), $.trim); + $selects.webformVal(multiple); + $texts.val([action['argument']]); + // A special case is made for markup. It is sanitized with filter_xss_admin on the server. + // otherwise text() should be used to avoid an XSS vulnerability. text() however would + // preclude the use of tags like <strong> or <a> + $target.filter('.webform-component-markup').html(action['argument']); + } + if (!isLocked) { + // If not previously hidden or set, disable the element readonly or readonly-like behavior. + $selects.webformProp('disabled', actionResult); + $texts.webformProp('readonly', actionResult); + targetLocked[action['target']] = actionResult ? 'set' : false; + } + break; } - else { - $targetElements = $target.find(':input').addClass('webform-conditional-disabled'); - $.fn.prop ? $targetElements.prop('disabled', true) : $targetElements.attr('disabled', true); - $target.hide(); - } - }); - } + }); // End look on each action for one conditional + }); // End loop on each conditional +} -}; +/** + * Event handler to prevent propogation of events, typically click for disabling + * radio and checkboxes. + */ +Drupal.webform.stopEvent = function() { + return false; +} Drupal.webform.conditionalOperatorStringEqual = function(element, existingValue, ruleValue) { var returnValue = false; @@ -259,6 +312,28 @@ Drupal.webform.conditionalOperatorStringNotEmpty = function(element, existingVal return !Drupal.webform.conditionalOperatorStringEmpty(element, existingValue, ruleValue); }; +Drupal.webform.conditionalOperatorSelectGreaterThan = function(element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + return Drupal.webform.compare_select(currentValue[0], ruleValue, element) > 0; +}; + +Drupal.webform.conditionalOperatorSelectGreaterThanEqual = function(element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element); + return comparison > 0 || comparison === 0; +}; + +Drupal.webform.conditionalOperatorSelectLessThan = function(element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + return Drupal.webform.compare_select(currentValue[0], ruleValue, element) < 0; +}; + +Drupal.webform.conditionalOperatorSelectLessThanEqual = function(element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element); + return comparison < 0 || comparison === 0; +}; + Drupal.webform.conditionalOperatorNumericEqual = function(element, existingValue, ruleValue) { // See float comparison: http://php.net/manual/en/language.types.float.php var currentValue = Drupal.webform.stringValue(element, existingValue); @@ -280,73 +355,170 @@ Drupal.webform.conditionalOperatorNumericGreaterThan = function(element, existin return parseFloat(currentValue[0]) > parseFloat(ruleValue); }; +Drupal.webform.conditionalOperatorNumericGreaterThanEqual = function(element, existingValue, ruleValue) { + return Drupal.webform.conditionalOperatorNumericGreaterThan(element, existingValue, ruleValue) || + Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue); +}; + Drupal.webform.conditionalOperatorNumericLessThan = function(element, existingValue, ruleValue) { var currentValue = Drupal.webform.stringValue(element, existingValue); return parseFloat(currentValue[0]) < parseFloat(ruleValue); }; +Drupal.webform.conditionalOperatorNumericLessThanEqual = function(element, existingValue, ruleValue) { + return Drupal.webform.conditionalOperatorNumericLessThan(element, existingValue, ruleValue) || + Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue); +}; + Drupal.webform.conditionalOperatorDateEqual = function(element, existingValue, ruleValue) { var currentValue = Drupal.webform.dateValue(element, existingValue); return currentValue === ruleValue; }; +Drupal.webform.conditionalOperatorDateNotEqual = function(element, existingValue, ruleValue) { + return !Drupal.webform.conditionalOperatorDateEqual(element, existingValue, ruleValue); +}; + Drupal.webform.conditionalOperatorDateBefore = function(element, existingValue, ruleValue) { var currentValue = Drupal.webform.dateValue(element, existingValue); return (currentValue !== false) && currentValue < ruleValue; }; +Drupal.webform.conditionalOperatorDateBeforeEqual = function(element, existingValue, ruleValue) { + var currentValue = Drupal.webform.dateValue(element, existingValue); + return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue); +}; + Drupal.webform.conditionalOperatorDateAfter = function(element, existingValue, ruleValue) { var currentValue = Drupal.webform.dateValue(element, existingValue); return (currentValue !== false) && currentValue > ruleValue; }; +Drupal.webform.conditionalOperatorDateAfterEqual = function(element, existingValue, ruleValue) { + var currentValue = Drupal.webform.dateValue(element, existingValue); + return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue); +}; + Drupal.webform.conditionalOperatorTimeEqual = function(element, existingValue, ruleValue) { var currentValue = Drupal.webform.timeValue(element, existingValue); return currentValue === ruleValue; }; +Drupal.webform.conditionalOperatorTimeNotEqual = function(element, existingValue, ruleValue) { + return !Drupal.webform.conditionalOperatorTimeEqual(element, existingValue, ruleValue); +}; + Drupal.webform.conditionalOperatorTimeBefore = function(element, existingValue, ruleValue) { // Date and time operators intentionally exclusive for "before". var currentValue = Drupal.webform.timeValue(element, existingValue); return (currentValue !== false) && (currentValue < ruleValue); }; +Drupal.webform.conditionalOperatorTimeBeforeEqual = function(element, existingValue, ruleValue) { + // Date and time operators intentionally exclusive for "before". + var currentValue = Drupal.webform.timeValue(element, existingValue); + return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue); +}; + Drupal.webform.conditionalOperatorTimeAfter = function(element, existingValue, ruleValue) { // Date and time operators intentionally inclusive for "after". var currentValue = Drupal.webform.timeValue(element, existingValue); - return (currentValue !== false) && (currentValue >= ruleValue); + return (currentValue !== false) && (currentValue > ruleValue); }; +Drupal.webform.conditionalOperatorTimeAfterEqual = function(element, existingValue, ruleValue) { + // Date and time operators intentionally inclusive for "after". + var currentValue = Drupal.webform.timeValue(element, existingValue); + return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue); +}; + +/** + * Utility function to compare values of a select component. + * @param string a + * First select option key to compare + * @param string b + * Second select option key to compare + * @param array options + * Associative array where the a and b are within the keys + * @return integer based upon position of $a and $b in $options + * -N if $a above (<) $b + * 0 if $a = $b + * +N if $a is below (>) $b + */ +Drupal.webform.compare_select = function(a, b, element) { + var optionList = []; + $('option,input:radio,input:checkbox', element).each(function() { + optionList.push($(this).val()); + }) + var a_position = optionList.indexOf(a); + var b_position = optionList.indexOf(b); + if (a_position < 0 && b_position < 0) { + return null; + } + else if (a_position < 0) { + return 1; + } + else if (b_position < 0) { + return -1; + } + else { + return a_position - b_position; + } +} + +/** + * Utility to return current visibility. Uses actual visibility, except for + * hidden components which use the applied disabled class. + */ +Drupal.webform.isVisible = function($element) { + return $element.hasClass('webform-component-hidden') + ? !$element.find('input').first().hasClass('webform-conditional-disabled') + : $element.closest('.webform-conditional-hidden').length == 0; +} + /** * Utility function to get a string value from a select/radios/text/etc. field. */ Drupal.webform.stringValue = function(element, existingValue) { var value = []; - if (element) { - // Checkboxes and radios. - $(element).find('input[type=checkbox]:checked,input[type=radio]:checked').each(function() { - value.push(this.value); - }); - // Select lists. - if (!value.length) { - var selectValue = $(element).find('select').val(); - if (selectValue) { - value.push(selectValue); - } - } - // Simple text fields. This check is done last so that the select list in - // select-or-other fields comes before the "other" text field. - if (!value.length) { - $(element).find('input:not([type=checkbox],[type=radio]),textarea').each(function() { + var $element = $(element); + if (Drupal.webform.isVisible($element)) { + // Checkboxes and radios. + $element.find('input[type=checkbox]:checked,input[type=radio]:checked').each(function() { value.push(this.value); }); + // Select lists. + if (!value.length) { + var selectValue = $element.find('select').val(); + if (selectValue) { + if ($.isArray(selectValue)) { + value = selectValue; + } + else { + value.push(selectValue); + } + } + } + // Simple text fields. This check is done last so that the select list in + // select-or-other fields comes before the "other" text field. + if (!value.length) { + $element.find('input:not([type=checkbox],[type=radio]),textarea').each(function() { + value.push(this.value); + }); + } } } - else if (existingValue) { - value = existingValue; + else { + switch ($.type(existingValue)) { + case 'array': + value = existingValue; + break; + case 'string': + value.push(existingValue); + break; + } } - return value; }; @@ -354,45 +526,105 @@ Drupal.webform.stringValue = function(element, existingValue) { * Utility function to calculate a millisecond timestamp from a time field. */ Drupal.webform.dateValue = function(element, existingValue) { + var value = false; if (element) { - var day = $(element).find('[name*=day]').val(); - var month = $(element).find('[name*=month]').val(); - var year = $(element).find('[name*=year]').val(); - // Months are 0 indexed in JavaScript. - if (month) { - month--; + var $element = $(element); + if (Drupal.webform.isVisible($element)) { + var day = $element.find('[name*=day]').val(); + var month = $element.find('[name*=month]').val(); + var year = $element.find('[name*=year]').val(); + // Months are 0 indexed in JavaScript. + if (month) { + month--; + } + if (year !== '' && month !== '' && day !== '') { + value = Date.UTC(year, month, day) / 1000; + } } - return (year !== '' && month !== '' && day !== '') ? Date.UTC(year, month, day) / 1000 : false; } else { - var existingValue = existingValue.length ? existingValue[0].split('-') : existingValue; - return existingValue.length ? Date.UTC(existingValue[0], existingValue[1], existingValue[2]) / 1000 : false; + if ($.type(existingValue) === 'array' && existingValue.length) { + existingValue = existingValue[0]; + } + if ($.type(existingValue) === 'string') { + existingValue = existingValue.split('-'); + } + if (existingValue.length === 3) { + value = Date.UTC(existingValue[0], existingValue[1], existingValue[2]) / 1000; + } } + return value; }; /** * Utility function to calculate a millisecond timestamp from a time field. */ Drupal.webform.timeValue = function(element, existingValue) { + var value = false; if (element) { - var hour = $(element).find('[name*=hour]').val(); - var minute = $(element).find('[name*=minute]').val(); - var ampm = $(element).find('[name*=ampm]:checked').val(); - - // Convert to integers if set. - hour = (hour === '') ? hour : parseInt(hour); - minute = (minute === '') ? minute : parseInt(minute); - - if (hour !== '') { - hour = (hour < 12 && ampm == 'pm') ? hour + 12 : hour; - hour = (hour === 12 && ampm == 'am') ? 0 : hour; + var $element = $(element); + if (Drupal.webform.isVisible($element)) { + var hour = $element.find('[name*=hour]').val(); + var minute = $element.find('[name*=minute]').val(); + var ampm = $element.find('[name*=ampm]:checked').val(); + + // Convert to integers if set. + hour = (hour === '') ? hour : parseInt(hour); + minute = (minute === '') ? minute : parseInt(minute); + + if (hour !== '') { + hour = (hour < 12 && ampm == 'pm') ? hour + 12 : hour; + hour = (hour === 12 && ampm == 'am') ? 0 : hour; + } + if (hour !== '' && minute !== '') { + value = Date.UTC(1970, 0, 1, hour, minute) / 1000; + } } - return (hour !== '' && minute !== '') ? Date.UTC(1970, 0, 1, hour, minute) / 1000 : false; } else { - var existingValue = existingValue.length ? existingValue[0].split(':') : existingValue; - return existingValue.length ? Date.UTC(1970, 0, 1, existingValue[0], existingValue[1]) / 1000 : false; + if ($.type(existingValue) === 'array' && existingValue.length) { + existingValue = existingValue[0]; + } + if ($.type(existingValue) === 'string') { + existingValue = existingValue.split(':'); + } + if (existingValue.length >= 2) { + value = Date.UTC(1970, 0, 1, existingValue[0], existingValue[1]) / 1000; + } } + return value; }; +/** + * Make a prop shim for jQuery < 1.9. + */ +$.fn.webformProp = function(name, value) { + if (value) { + $.fn.prop ? this.prop(name, true) : this.attr(name, true); + } + else { + $.fn.prop ? this.prop(name, false) : this.removeAttr(name); + } + return this; +} + +/** + * Make a multi-valued val() function for setting checkboxes, radios, and select + * elements. + */ +$.fn.webformVal = function(values) { + this.each(function() { + var $this = $(this); + var value = $this.val(); + var on = $.inArray($this.val(), values) != -1; + if (this.nodeName == 'OPTION') { + $this.webformProp('selected', on ? value : false); + } + else { + $this.val(on ? [value] : false); + } + }); + return this; +} + })(jQuery); diff --git a/profiles/wcm_base/modules/contrib/webform/templates/webform-analysis.tpl.php b/profiles/wcm_base/modules/contrib/webform/templates/webform-analysis.tpl.php index 734fab8d..2525a93c 100644 --- a/profiles/wcm_base/modules/contrib/webform/templates/webform-analysis.tpl.php +++ b/profiles/wcm_base/modules/contrib/webform/templates/webform-analysis.tpl.php @@ -9,6 +9,9 @@ * contain a Webform component. Otherwise all components are having their * analysis printed on the same page. * - $analysis: A renderable object containing the following children: + * - 'exposed_filter': The output of any exposed filter created by the + * webform_analysis, webform_analysis_CONTENTTYPE, or webform_analysis_NID + * view. * - 'form': A form for selecting which components should be included in the * analysis. * - 'data': An render array of analysis results for each component enabled. @@ -17,6 +20,7 @@ <div class="webform-analysis"> <?php print drupal_render($analysis['form']['help']); ?> + <?php print drupal_render($analysis['exposed_filter']); ?> <div class="webform-analysis-data"> <?php print drupal_render($analysis['data']); ?> </div> diff --git a/profiles/wcm_base/modules/contrib/webform/templates/webform-confirmation.tpl.php b/profiles/wcm_base/modules/contrib/webform/templates/webform-confirmation.tpl.php index 25365fb9..9ac179ff 100644 --- a/profiles/wcm_base/modules/contrib/webform/templates/webform-confirmation.tpl.php +++ b/profiles/wcm_base/modules/contrib/webform/templates/webform-confirmation.tpl.php @@ -16,6 +16,7 @@ * - $confirmation_message: The confirmation message input by the webform * author. * - $sid: The unique submission ID of this submission. + * - $url: The URL of the form (or for in-block confirmations, the same page). */ ?> <?php print $progressbar; ?> @@ -29,5 +30,5 @@ </div> <div class="links"> - <a href="<?php print url('node/'. $node->nid) ?>"><?php print t('Go back to the form') ?></a> + <a href="<?php print $url; ?>"><?php print t('Go back to the form') ?></a> </div> diff --git a/profiles/wcm_base/modules/contrib/webform/templates/webform-mail.tpl.php b/profiles/wcm_base/modules/contrib/webform/templates/webform-mail.tpl.php index cb8f5bd9..578c8277 100644 --- a/profiles/wcm_base/modules/contrib/webform/templates/webform-mail.tpl.php +++ b/profiles/wcm_base/modules/contrib/webform/templates/webform-mail.tpl.php @@ -12,8 +12,10 @@ * - $node: The node object for this webform. * - $submission: The webform submission. * - $email: The entire e-mail configuration settings. - * - $user: The current user submitting the form. - * - $ip_address: The IP address of the user submitting the form. + * - $user: The current user submitting the form. Always the Anonymous user + * (uid 0) for confidential submissions. + * - $ip_address: The IP address of the user submitting the form or '(unknown)' + * for confidential submissions. * * The $email['email'] variable can be used to send different e-mails to different users * when using the "default" e-mail template. diff --git a/profiles/wcm_base/modules/contrib/webform/templates/webform-submission-information.tpl.php b/profiles/wcm_base/modules/contrib/webform/templates/webform-submission-information.tpl.php index 7088b3ca..14f42c72 100644 --- a/profiles/wcm_base/modules/contrib/webform/templates/webform-submission-information.tpl.php +++ b/profiles/wcm_base/modules/contrib/webform/templates/webform-submission-information.tpl.php @@ -18,7 +18,7 @@ <div class="webform-submission-info-text"> <div><?php print t('Form: !form', array('!form' => l($node->title, 'node/' . $node->nid))); ?></div> <div><?php print t('Submitted by !name', array('!name' => theme('username', array('account' => $account)))); ?></div> - <div><?php print format_date($submission->submitted, 'long'); ?></div> - <div><?php print $submission->remote_addr; ?></div> + <div><?php print check_plain(format_date($submission->submitted, webform_variable_get('webform_date_type'))); ?></div> + <div><?php print check_plain($submission->remote_addr); ?></div> </div> </fieldset> diff --git a/profiles/wcm_base/modules/contrib/webform/tests/conditionals.test b/profiles/wcm_base/modules/contrib/webform/tests/conditionals.test index 4c434cc1..3f16049e 100644 --- a/profiles/wcm_base/modules/contrib/webform/tests/conditionals.test +++ b/profiles/wcm_base/modules/contrib/webform/tests/conditionals.test @@ -41,15 +41,18 @@ class WebformConditionalsTestCase extends WebformTestCase { $this->webformReset(); $test_components = $this->testWebformComponents(); + $test_specs = array( + 'match conditional values' => TRUE, + 'mismatch conditional values' => FALSE, + ); + // Test each component, processing all 'match conditional values' for TRUE + // and all 'mismatch conditional values for FALSE. foreach ($test_components as $key => $component_info) { - if (isset($component_info['match conditional values'])) { - foreach ($component_info['match conditional values'] as $operator => $match_value) { - $this->webformTestConditionalComponent($component_info['component'], $component_info['sample values'], $operator, $match_value, TRUE); - } - } - if (isset($component_info['mismatch conditional values'])) { - foreach ($component_info['mismatch conditional values'] as $operator => $mismatch_value) { - $this->webformTestConditionalComponent($component_info['component'], $component_info['sample values'], $operator, $mismatch_value, FALSE); + foreach ($test_specs as $values_key => $result) { + if (isset($component_info[$values_key])) { + foreach ($component_info[$values_key] as $operator => $match_value) { + $this->webformTestConditionalComponent($component_info['component'], $component_info['sample values'], $operator, $match_value, $result); + } } } } @@ -132,15 +135,21 @@ class WebformConditionalsTestCase extends WebformTestCase { } $conditional = array( 'rgid' => 0, + 'rules' => $rules, 'andor' => 'and', - 'action' => 'hide', - 'target_type' => 'component', - 'target' => NULL, // Target set individually. + 'actions' => array( + 0 => array( + 'action' => 'show', + 'target' => NULL, // Target set individually. + 'target_type' => 'component', + 'invert' => '1', + 'argument' => NULL, + ), + ), 'weight' => 0, - 'rules' => $rules, ); - $conditional['target'] = 2; // The same-page textfield test. + $conditional['actions'][0]['target'] = 2; // The same-page textfield test. $node->webform['conditionals'] = array($conditional); node_save($node); @@ -159,7 +168,7 @@ class WebformConditionalsTestCase extends WebformTestCase { } // Adjust the conditionals to make them separate-page conditionals. - $conditional['target'] = 3; // The separate-page textfield test. + $conditional['actions'][0]['target'] = 3; // The separate-page textfield test. $node->webform['conditionals'] = array($conditional); $node->webform['components'][2]['required'] = '0'; node_save($node); diff --git a/profiles/wcm_base/modules/contrib/webform/tests/webform.test b/profiles/wcm_base/modules/contrib/webform/tests/webform.test index 088e32a4..cfad1317 100644 --- a/profiles/wcm_base/modules/contrib/webform/tests/webform.test +++ b/profiles/wcm_base/modules/contrib/webform/tests/webform.test @@ -149,12 +149,16 @@ class WebformTestCase extends DrupalWebTestCase { 'match conditional values' => array( 'equal' => '1982/9/30', 'before' => '1982/10/1', + 'before_equal' => '1982/9/30', 'after' => '1982/9/29', + 'after_equal' => '1982/9/29', ), 'mismatch conditional values' => array( 'equal' => '1981/9/30', 'before' => '1982/9/30', + 'before_equal' => '1982/9/29', 'after' => '1982/9/30', + 'after_equal' => '1982/10/1', ), ), @@ -303,6 +307,40 @@ class WebformTestCase extends DrupalWebTestCase { 'not_equal' => '0', ), ), + 'radios_relative' => array( + 'component' => array( + 'form_key' => 'radios_relative', + 'name' => 'Radios relative', + 'type' => 'select', + 'value' => 'one', + 'extra' => array( + 'items' => "zero|Zero\none|One\ntwo|Two\nthree|Three\n", + ), + 'required' => '1', + 'pid' => '0', + 'weight' => '-9', + ), + 'sample values' => 'one', + 'database values' => array('one'), + 'database default values' => array('one'), + // Conditionals match against the 'sample values'. + 'match conditional values' => array( + 'equal' => 'one', + 'not_equal' => 'zero', + 'less_than' => 'two', + 'less_than_equal' => 'one', + 'greater_than' => 'zero', + 'greater_than_equal' => 'zero', + ), + 'mismatch conditional values' => array( + 'equal' => 'zero', + 'not_equal' => 'one', + 'less_than' => 'one', + 'less_than_equal' => 'zero', + 'greater_than' => 'two', + 'greater_than_equal' => 'two', + ), + ), 'select' => array( 'component' => array( 'form_key' => 'select', @@ -471,6 +509,41 @@ class WebformTestCase extends DrupalWebTestCase { 'database values' => array('one', 'two'), 'database default values' => array('one', 'two'), ), + 'select_relative' => array( + 'component' => array( + 'form_key' => 'select_relative', + 'name' => 'Select relative', + 'type' => 'select', + 'value' => 'one', + 'extra' => array( + 'items' => "zero|Zero\none|One\ntwo|Two\nthree|Three\n", + 'aslist' => 1, + ), + 'required' => '1', + 'pid' => '0', + 'weight' => '-9', + ), + 'sample values' => 'one', + 'database values' => array('one'), + 'database default values' => array('one'), + // Conditionals match against the 'sample values'. + 'match conditional values' => array( + 'equal' => 'one', + 'not_equal' => 'zero', + 'less_than' => 'two', + 'less_than_equal' => 'one', + 'greater_than' => 'zero', + 'greater_than_equal' => 'zero', + ), + 'mismatch conditional values' => array( + 'equal' => 'zero', + 'not_equal' => 'one', + 'less_than' => 'one', + 'less_than_equal' => 'zero', + 'greater_than' => 'two', + 'greater_than_equal' => 'two', + ), + ), // Test date components. 'date_textfield' => array( @@ -496,12 +569,16 @@ class WebformTestCase extends DrupalWebTestCase { 'match conditional values' => array( 'equal' => '1982/9/30', 'before' => '1982/10/1', + 'before_equal' => '1982/9/30', 'after' => '1982/9/29', + 'after_equal' => '1982/9/29', ), 'mismatch conditional values' => array( 'equal' => '1981/9/30', 'before' => '1982/9/30', + 'before_equal' => '1982/9/29', 'after' => '1982/9/30', + 'after_equal' => '1982/10/1', ), ), @@ -691,11 +768,14 @@ class WebformTestCase extends DrupalWebTestCase { 'match conditional values' => array( 'equal' => '12:00pm', 'before' => '1:00pm', + 'before_equal' => '12:00pm', 'after' => '11:00am', + 'after_equal' => '11:00am', ), 'mismatch conditional values' => array( 'equal' => '12:00am', 'before' => '12:00pm', + 'before_equal' => '11:00am', 'after' => '12:00pm', ), ), @@ -720,12 +800,16 @@ class WebformTestCase extends DrupalWebTestCase { 'match conditional values' => array( 'equal' => '5:00', 'before' => '24:00', + 'before_equal' => '5:00', 'after' => '00:00', + 'after_equal' => '00:00', ), 'mismatch conditional values' => array( 'equal' => '5:01', 'before' => '5:00', + 'before_equal' => '4:59', 'after' => '5:00', + 'after_equal' => '5:01', ), ), @@ -755,14 +839,18 @@ class WebformTestCase extends DrupalWebTestCase { 'equal' => '2', 'not_equal' => '0', 'less_than' => '3', + 'less_than_equal' => '2', 'greater_than' => '1', + 'greater_than_equal' => '1', 'not_empty' => TRUE, ), 'mismatch conditional values' => array( 'equal' => '0', 'not_equal' => '2', 'less_than' => '2', + 'less_than_equal' => '1', 'greater_than' => '2', + 'greater_than_equal' => '3', 'empty' => TRUE, ), 'error values' => array( diff --git a/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_analysis.inc b/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_analysis.inc new file mode 100644 index 00000000..24acc08c --- /dev/null +++ b/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_analysis.inc @@ -0,0 +1,61 @@ +<?php + +/** + * @file + * webform_analysis view definition. + */ + +// --- Paste exported view below --- + +$view = new view(); +$view->name = 'webform_analysis'; +$view->description = 'Limit submissions for the webform node\'s analysis. Query must have sid as a field. Don\'t duplicate sids in result.'; +$view->tag = 'webform'; +$view->base_table = 'webform_submissions'; +$view->human_name = 'Webform Analysis'; +$view->core = 7; +$view->api_version = '3.0'; +$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + +/* Display: Master */ +$handler = $view->new_display('default', 'Master', 'default'); +$handler->display->display_options['use_more_always'] = FALSE; +$handler->display->display_options['access']['type'] = 'none'; +$handler->display->display_options['cache']['type'] = 'none'; +$handler->display->display_options['query']['type'] = 'views_query'; +$handler->display->display_options['query']['options']['distinct'] = TRUE; +$handler->display->display_options['exposed_form']['type'] = 'basic'; +$handler->display->display_options['pager']['type'] = 'none'; +$handler->display->display_options['pager']['options']['offset'] = '0'; +$handler->display->display_options['style_plugin'] = 'default'; +$handler->display->display_options['row_plugin'] = 'fields'; +/* Field: Webform submissions: Sid */ +$handler->display->display_options['fields']['sid']['id'] = 'sid'; +$handler->display->display_options['fields']['sid']['table'] = 'webform_submissions'; +$handler->display->display_options['fields']['sid']['field'] = 'sid'; +/* Contextual filter: Webform submissions: Node */ +$handler->display->display_options['arguments']['nid']['id'] = 'nid'; +$handler->display->display_options['arguments']['nid']['table'] = 'webform_submissions'; +$handler->display->display_options['arguments']['nid']['field'] = 'nid'; +$handler->display->display_options['arguments']['nid']['default_action'] = 'empty'; +$handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed'; +$handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0'; +$handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary'; +$handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25'; +$translatables['webform_analysis'] = array( + t('Master'), + t('more'), + t('Apply'), + t('Reset'), + t('Sort by'), + t('Asc'), + t('Desc'), + t('Sid'), + t('.'), + t(','), + t('All'), +); + +// --- Paste exported view above --- + +$views[$view->name] = $view; diff --git a/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_results.inc b/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_results.inc index a376a30c..2e381de5 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_results.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_results.inc @@ -10,7 +10,7 @@ $view = new view(); $view->name = 'webform_results'; $view->description = ''; -$view->tag = 'default'; +$view->tag = 'webform'; $view->base_table = 'webform_submissions'; $view->human_name = 'Webform Results'; $view->core = 7; @@ -129,7 +129,7 @@ $handler->display->display_options['fields']['webform_all_fields']['field'] = 'w $handler->display->display_options['fields']['webform_all_fields']['label'] = ''; $handler->display->display_options['fields']['webform_all_fields']['exclude'] = TRUE; $handler->display->display_options['fields']['webform_all_fields']['alter']['nl2br'] = TRUE; - +$handler->display->display_options['fields']['webform_all_fields']['format'] = 'text'; /* Contextual filter: Webform submissions: Node */ $handler->display->display_options['arguments']['nid']['id'] = 'nid'; $handler->display->display_options['arguments']['nid']['table'] = 'webform_submissions'; diff --git a/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_submissions.inc b/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_submissions.inc index 2de0b07c..2c93d06d 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_submissions.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_submissions.inc @@ -10,7 +10,7 @@ $view = new view(); $view->name = 'webform_submissions'; $view->description = ''; -$view->tag = 'default'; +$view->tag = 'webform'; $view->base_table = 'webform_submissions'; $view->human_name = 'Webform Submissions'; $view->core = 7; diff --git a/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_webforms.inc b/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_webforms.inc new file mode 100644 index 00000000..7035e772 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/webform/views/default_views/webform_webforms.inc @@ -0,0 +1,219 @@ +<?php + +/** + * @file + * webform_webforms view definition. + */ + +// --- Paste exported view below --- + +$view = new view(); +$view->name = 'webform_webforms'; +$view->description = 'An administrative list of webform nodes'; +$view->tag = 'webform'; +$view->base_table = 'webform'; +$view->human_name = 'Webforms'; +$view->core = 7; +$view->api_version = '3.0'; +$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + +/* Display: Master */ +$handler = $view->new_display('default', 'Master', 'default'); +$handler->display->display_options['use_more_always'] = FALSE; +$handler->display->display_options['access']['type'] = 'none'; +$handler->display->display_options['cache']['type'] = 'none'; +$handler->display->display_options['query']['type'] = 'views_query'; +$handler->display->display_options['exposed_form']['type'] = 'basic'; +$handler->display->display_options['pager']['type'] = 'full'; +$handler->display->display_options['pager']['options']['items_per_page'] = '25'; +$handler->display->display_options['pager']['options']['offset'] = '0'; +$handler->display->display_options['pager']['options']['id'] = '0'; +$handler->display->display_options['pager']['options']['quantity'] = '9'; +$handler->display->display_options['style_plugin'] = 'table'; +$handler->display->display_options['style_options']['columns'] = array( + 'title' => 'title', + 'created' => 'created', + 'status' => 'status', + 'webform_results' => 'webform_results', + 'webform_results_2' => 'webform_results', + 'webform_results_3' => 'webform_results', + 'webform_results_4' => 'webform_results', + 'edit_node' => 'edit_node', + 'webform_edit' => 'edit_node', + 'webform_results_1' => 'edit_node', +); +$handler->display->display_options['style_options']['default'] = 'created'; +$handler->display->display_options['style_options']['info'] = array( + 'title' => array( + 'sortable' => 1, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), + 'created' => array( + 'sortable' => 1, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), + 'status' => array( + 'sortable' => 1, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), + 'webform_results' => array( + 'align' => '', + 'separator' => ' ', + 'empty_column' => 0, + ), + 'webform_results_2' => array( + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), + 'webform_results_3' => array( + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), + 'webform_results_4' => array( + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), + 'edit_node' => array( + 'align' => '', + 'separator' => ' ', + 'empty_column' => 0, + ), + 'webform_edit' => array( + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), + 'webform_results_1' => array( + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), +); +/* Relationship: Webform: Node */ +$handler->display->display_options['relationships']['nid']['id'] = 'nid'; +$handler->display->display_options['relationships']['nid']['table'] = 'webform'; +$handler->display->display_options['relationships']['nid']['field'] = 'nid'; +$handler->display->display_options['relationships']['nid']['required'] = TRUE; +/* Field: Content: Title */ +$handler->display->display_options['fields']['title']['id'] = 'title'; +$handler->display->display_options['fields']['title']['table'] = 'node'; +$handler->display->display_options['fields']['title']['field'] = 'title'; +$handler->display->display_options['fields']['title']['relationship'] = 'nid'; +/* Field: Content: Post date */ +$handler->display->display_options['fields']['created']['id'] = 'created'; +$handler->display->display_options['fields']['created']['table'] = 'node'; +$handler->display->display_options['fields']['created']['field'] = 'created'; +$handler->display->display_options['fields']['created']['relationship'] = 'nid'; +$handler->display->display_options['fields']['created']['label'] = 'Created'; +$handler->display->display_options['fields']['created']['date_format'] = 'short'; +$handler->display->display_options['fields']['created']['second_date_format'] = 'long'; +/* Field: Webform: Status */ +$handler->display->display_options['fields']['status']['id'] = 'status'; +$handler->display->display_options['fields']['status']['table'] = 'webform'; +$handler->display->display_options['fields']['status']['field'] = 'status'; +/* Field: Webform: Webform results link */ +$handler->display->display_options['fields']['webform_results']['id'] = 'webform_results'; +$handler->display->display_options['fields']['webform_results']['table'] = 'node'; +$handler->display->display_options['fields']['webform_results']['field'] = 'webform_results'; +$handler->display->display_options['fields']['webform_results']['relationship'] = 'nid'; +$handler->display->display_options['fields']['webform_results']['label'] = 'View'; +$handler->display->display_options['fields']['webform_results']['element_label_colon'] = FALSE; +$handler->display->display_options['fields']['webform_results']['text'] = 'Submissions'; +/* Field: Webform: Webform results link */ +$handler->display->display_options['fields']['webform_results_2']['id'] = 'webform_results_2'; +$handler->display->display_options['fields']['webform_results_2']['table'] = 'node'; +$handler->display->display_options['fields']['webform_results_2']['field'] = 'webform_results'; +$handler->display->display_options['fields']['webform_results_2']['relationship'] = 'nid'; +$handler->display->display_options['fields']['webform_results_2']['label'] = 'Analysis'; +$handler->display->display_options['fields']['webform_results_2']['element_label_colon'] = FALSE; +$handler->display->display_options['fields']['webform_results_2']['text'] = 'Analysis'; +$handler->display->display_options['fields']['webform_results_2']['subpath'] = 'analysis'; +/* Field: Webform: Webform results link */ +$handler->display->display_options['fields']['webform_results_3']['id'] = 'webform_results_3'; +$handler->display->display_options['fields']['webform_results_3']['table'] = 'node'; +$handler->display->display_options['fields']['webform_results_3']['field'] = 'webform_results'; +$handler->display->display_options['fields']['webform_results_3']['relationship'] = 'nid'; +$handler->display->display_options['fields']['webform_results_3']['label'] = 'Table'; +$handler->display->display_options['fields']['webform_results_3']['element_label_colon'] = FALSE; +$handler->display->display_options['fields']['webform_results_3']['text'] = 'Table'; +$handler->display->display_options['fields']['webform_results_3']['subpath'] = 'table'; +/* Field: Webform: Webform results link */ +$handler->display->display_options['fields']['webform_results_4']['id'] = 'webform_results_4'; +$handler->display->display_options['fields']['webform_results_4']['table'] = 'node'; +$handler->display->display_options['fields']['webform_results_4']['field'] = 'webform_results'; +$handler->display->display_options['fields']['webform_results_4']['relationship'] = 'nid'; +$handler->display->display_options['fields']['webform_results_4']['label'] = 'Download'; +$handler->display->display_options['fields']['webform_results_4']['element_label_colon'] = FALSE; +$handler->display->display_options['fields']['webform_results_4']['text'] = 'Download'; +$handler->display->display_options['fields']['webform_results_4']['subpath'] = 'download'; +/* Field: Content: Edit link */ +$handler->display->display_options['fields']['edit_node']['id'] = 'edit_node'; +$handler->display->display_options['fields']['edit_node']['table'] = 'views_entity_node'; +$handler->display->display_options['fields']['edit_node']['field'] = 'edit_node'; +$handler->display->display_options['fields']['edit_node']['relationship'] = 'nid'; +$handler->display->display_options['fields']['edit_node']['label'] = 'Operations'; +$handler->display->display_options['fields']['edit_node']['alter']['alter_text'] = TRUE; +$handler->display->display_options['fields']['edit_node']['alter']['text'] = 'Edit'; +$handler->display->display_options['fields']['edit_node']['element_label_colon'] = FALSE; +/* Field: Webform: Webform edit link */ +$handler->display->display_options['fields']['webform_edit']['id'] = 'webform_edit'; +$handler->display->display_options['fields']['webform_edit']['table'] = 'node'; +$handler->display->display_options['fields']['webform_edit']['field'] = 'webform_edit'; +$handler->display->display_options['fields']['webform_edit']['relationship'] = 'nid'; +$handler->display->display_options['fields']['webform_edit']['label'] = 'Components'; +$handler->display->display_options['fields']['webform_edit']['element_label_colon'] = FALSE; +$handler->display->display_options['fields']['webform_edit']['text'] = 'Components'; +$handler->display->display_options['fields']['webform_edit']['subpath'] = 'components'; +/* Field: Webform: Webform results link */ +$handler->display->display_options['fields']['webform_results_1']['id'] = 'webform_results_1'; +$handler->display->display_options['fields']['webform_results_1']['table'] = 'node'; +$handler->display->display_options['fields']['webform_results_1']['field'] = 'webform_results'; +$handler->display->display_options['fields']['webform_results_1']['relationship'] = 'nid'; +$handler->display->display_options['fields']['webform_results_1']['label'] = 'Clear'; +$handler->display->display_options['fields']['webform_results_1']['text'] = 'Clear'; +$handler->display->display_options['fields']['webform_results_1']['subpath'] = 'clear'; +$translatables['webform_webforms'] = array( + t('Master'), + t('more'), + t('Apply'), + t('Reset'), + t('Sort by'), + t('Asc'), + t('Desc'), + t('Items per page'), + t('- All -'), + t('Offset'), + t('« first'), + t('‹ previous'), + t('next ›'), + t('last »'), + t('Node for webform'), + t('Title'), + t('Created'), + t('Status'), + t('View'), + t('Submissions'), + t('Analysis'), + t('Table'), + t('Download'), + t('Operations'), + t('Edit'), + t('Components'), + t('Clear'), +); + +// --- Paste exported view above --- + +$views[$view->name] = $view; diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform.views.inc b/profiles/wcm_base/modules/contrib/webform/views/webform.views.inc index 3b84c8da..9590ae54 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform.views.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform.views.inc @@ -11,6 +11,11 @@ function webform_views_data() { * Webform table definitions. */ $data['webform']['table']['group'] = t('Webform'); + $data['webform']['table']['base'] = array( + 'field' => 'nid', + 'title' => t('Webform nodes'), + 'help' => t('Nodes which have webforms.'), + ); $data['webform']['table']['join'] = array( 'node' => array( 'left_field' => 'nid', @@ -19,6 +24,30 @@ function webform_views_data() { ), ); + // NID + $data['webform']['nid'] = array( + 'title' => t('Node'), + 'help' => t('The node this webform is part of.'), + 'relationship' => array( + 'base' => 'node', + 'field' => 'nid', + 'handler' => 'views_handler_relationship', + 'label' => t('Node for webform'), + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_node_nid', + 'name field' => 'title', // the field to display in the summary. + 'numeric' => TRUE, + 'validate type' => 'nid', + ), + ); + // status $data['webform']['status'] = array( 'title' => t('Status'), @@ -180,7 +209,7 @@ function webform_views_data() { // Submitted timestamp. $data['webform_submissions']['submitted'] = array( 'title' => t('Submitted'), - 'help' => t('The date this submission was submitted.'), + 'help' => t('Timestamp when the form was first saved as draft or submitted.'), 'field' => array( 'handler' => 'views_handler_field_date', 'click sortable' => TRUE, @@ -197,6 +226,46 @@ function webform_views_data() { ), ); + // Completed timestamp. + $data['webform_submissions']['completed'] = array( + 'title' => t('Completed'), + 'help' => t('Timestamp when the form was submitted as complete (not draft).'), + 'field' => array( + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'title' => t('Completed'), + 'handler' => 'views_handler_filter_date', + ), + 'sort' => array( + 'handler' => 'views_handler_sort_date', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_date', + ), + ); + + // Modified timestamp. + $data['webform_submissions']['modified'] = array( + 'title' => t('Modified'), + 'help' => t('Timestamp when the form was last saved (complete or draft).'), + 'field' => array( + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'title' => t('Modified'), + 'handler' => 'views_handler_filter_date', + ), + 'sort' => array( + 'handler' => 'views_handler_sort_date', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_date', + ), + ); + // IP Address (remote_addr). $data['webform_submissions']['remote_addr'] = array( 'title' => t('Remote address'), @@ -303,6 +372,30 @@ function webform_views_data() { ), ); + // Same as 'data', but handled as numeric data. + $data['webform_submitted_data']['data_numeric'] = array( + 'table' => 'webform_submitted_data', + 'title' => t('Data field (numeric)'), + 'help' => t('The numeric value of a component as submitted by a user.'), + 'real field' => 'data', + 'field' => array( + 'title' => t('Numeric value (raw)'), // Distinguish from the normal value handler. + 'help' => t('The raw value from the database, cast to a number, as submitted by a user. Use only when needing to sort on a field value.'), + 'handler' => 'webform_handler_field_numeric_data', + 'click sortable' => TRUE, + 'float' => TRUE, + ), + 'argument' => array( + 'handler' => 'views_handler_argument_numeric', + ), + 'filter' => array( + 'handler' => 'webform_handler_filter_numeric_data', + ), + 'sort' => array( + 'handler' => 'webform_handler_sort_numeric_data', + ), + ); + // Number field for multi-value fields. $data['webform_submitted_data']['no'] = array( 'title' => t('Value delta'), @@ -411,8 +504,8 @@ function webform_views_data_alter(&$data) { ); $data['views']['webform_result'] = array( - 'title' => t('Result summary with additional tokens'), - 'help' => t('Shows result summary, for example the items per page (and links to change the items per page).'), + 'title' => t('Result summary with an additional token to change the items/page'), + 'help' => t('Shows result summary, for example the items per page, plus links to change the items per page.'), 'area' => array( 'handler' => 'webform_handler_area_result_pager', ), @@ -472,8 +565,7 @@ function webform_views_pre_view($view, $display_id, $args) { 'id' => $new_id, 'field' => 'value', 'table' => 'webform_submissions', - 'label' => $component['name'], // CHECK FOR XSS TODO - 'format' => 'plain', + 'label' => $component['name'], 'webform_nid' => $node->nid, 'webform_cid' => $component['cid'], 'exclude' => 0, @@ -498,7 +590,9 @@ function webform_views_pre_view($view, $display_id, $args) { $display->handler->set_option('fields', $fields); // If this display's style is a table, add columns for click-sorting. - if ($display->handler->get_option('style_plugin') == 'table') { + // Note: Test for count($new_columns) is necessary because prior to PHP 5.6, + // array_fill requires a positve number of elements to insert. + if ($display->handler->get_option('style_plugin') == 'table' && count($new_columns)) { $style_options = $display->handler->get_option('style_options'); $style_options['columns'] += $new_columns; $style_prototype = isset($style_options['info'][$all_fields_id]) @@ -512,12 +606,6 @@ function webform_views_pre_view($view, $display_id, $args) { 'empty_column' => 0, ); $style_options['info'] += array_combine($new_columns, array_fill(1, count($new_columns), $style_prototype)); - - // - //$key_index = array_flip(array_keys($columns)); - //$offset = isset($key_index[$id]) ? $key_index[$id] : count($columns); - //$columns = array_slice($columns, 0, $offset, TRUE) + $new_columns + array_slice($columns, $offset, NULL, TRUE); - // Drop PHP Reference. $display->handler->set_option('style_options', $style_options); } diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_area_result_pager.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_area_result_pager.inc index bc633a67..6b84c931 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_area_result_pager.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_area_result_pager.inc @@ -14,6 +14,7 @@ class webform_handler_area_result_pager extends views_handler_area_result { function option_definition() { $options = parent::option_definition(); + $options['content']['default'] = t('Displaying @start - @end of @total. @items_per_page_links'); $options['tokenize'] = array('default' => FALSE, 'bool' => TRUE); return $options; } @@ -22,7 +23,6 @@ class webform_handler_area_result_pager extends views_handler_area_result { parent::options_form($form, $form_state); $form['content']['#description'] .= '<br/>' . t('Plus @items_per_page_links -- the list of links to change the items/page, with prompt'); - // @TODO: Refactor token handling into a base class. $form['tokenize'] = array( '#type' => 'checkbox', '#title' => t('Use replacement tokens from the first row'), @@ -57,9 +57,9 @@ class webform_handler_area_result_pager extends views_handler_area_result { } function render_items_per_page($pager) { - $options = explode(',', $pager->options['expose']['items_per_page_options']); $sanitized_options = array(); - if (is_array($options)) { + if (!empty($pager->options['expose']['items_per_page_options'])) { + $options = explode(',', $pager->options['expose']['items_per_page_options']); foreach ($options as $option) { if ($pager->total_items <= intval($option)) { break; @@ -69,10 +69,12 @@ class webform_handler_area_result_pager extends views_handler_area_result { if (!empty($sanitized_options) && !empty($pager->options['expose']['items_per_page_options_all']) && !empty($pager->options['expose']['items_per_page_options_all_label'])) { $sanitized_options[0] = $pager->options['expose']['items_per_page_options_all_label']; } + $url_query = $_GET; + unset($url_query['q']); foreach ($sanitized_options as $items_per_page => &$label) { $selected = $pager->options['items_per_page'] == $items_per_page || ($items_per_page == 0 && $pager->total_items < intval($pager->options['items_per_page'])); - $label = l($label, $_GET['q'], array('query' => array('items_per_page' => $label) + $_GET, + $label = l($label, $_GET['q'], array('query' => array('items_per_page' => $label) + $url_query, 'attributes' => array( 'class' => $selected ? array('selected') : array(), ))); diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_form_body.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_form_body.inc index 4c8b206a..6816b901 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_form_body.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_form_body.inc @@ -30,7 +30,7 @@ class webform_handler_field_form_body extends views_handler_field { if (node_access('view', $node)) { // Populate $node->content['webform'] by reference. - webform_node_view($node, 'full'); + webform_node_view($node, 'form'); $form_body = isset($node->content['webform']) ? drupal_render($node->content['webform']) : NULL; } else { diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_edit.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_edit.inc index 3d5e1baa..2528b9a1 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_edit.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_edit.inc @@ -10,6 +10,22 @@ */ class webform_handler_field_node_link_edit extends views_handler_field_node_link { + function option_definition() { + $options = parent::option_definition(); + $options['subpath'] = array('default' => ''); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['subpath'] = array( + '#type' => 'textfield', + '#title' => t('Subpath'), + '#default_value' => $this->options['subpath'], + '#field_prefix' => 'node/NID/webform/', + ); + } + /** * Renders the link. */ @@ -20,7 +36,8 @@ class webform_handler_field_node_link_edit extends views_handler_field_node_link } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "node/$node->nid/webform"; + $this->options['alter']['path'] = "node/$node->nid/webform" . + (strlen($this->options['subpath']) ? '/' . $this->options['subpath'] : ''); $text = !empty($this->options['text']) ? $this->options['text'] : t('edit webform'); return $text; diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_results.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_results.inc index 10546175..0102cc5f 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_results.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_results.inc @@ -10,6 +10,22 @@ */ class webform_handler_field_node_link_results extends views_handler_field_node_link { + function option_definition() { + $options = parent::option_definition(); + $options['subpath'] = array('default' => ''); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['subpath'] = array( + '#type' => 'textfield', + '#title' => t('Subpath'), + '#default_value' => $this->options['subpath'], + '#field_prefix' => 'node/NID/webform-results/', + ); + } + /** * Renders the link. */ @@ -19,8 +35,14 @@ class webform_handler_field_node_link_results extends views_handler_field_node_l return; } + // For clear, ensure user has access to clear all the submissions. + if (stripos($this->options['subpath'], 'clear') === 0 && !user_access('delete all webform submissions')) { + return; + } + $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "node/$node->nid/webform-results"; + $this->options['alter']['path'] = "node/$node->nid/webform-results". + (strlen($this->options['subpath']) ? '/' . $this->options['subpath'] : ''); $text = !empty($this->options['text']) ? $this->options['text'] : t('results'); return $text; diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_submission_data.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_submission_data.inc index 8bc1dc0f..99a3054b 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_submission_data.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_submission_data.inc @@ -53,12 +53,12 @@ class webform_handler_field_submission_data extends views_handler_field { // Modify behavior for the type of data in the component. $form['webform_datatype'] = array( '#type' => 'select', - '#title' => t('Display format'), + '#title' => t('Data type'), '#options' => array( 'string' => t('String'), 'number' => t('Number'), ), - '#default_value' => $this->options['format'], + '#default_value' => $this->options['webform_datatype'], ); // Provide the selection for the display format. @@ -67,7 +67,7 @@ class webform_handler_field_submission_data extends views_handler_field { '#title' => t('Display format'), '#options' => array( 'html' => t('HTML'), - 'plain' => t('Plain text'), + 'text' => t('Plain text'), ), '#default_value' => $this->options['format'], ); @@ -201,16 +201,19 @@ class webform_handler_field_submission_data extends views_handler_field { // whereas $component refers to the submission component. } - $render = array(); - $format = $this->options['format']; - - _webform_client_form_add_component($webform, $component, NULL, $render, $render, $submission->data, $format); - $render = $render[$component['form_key']]; - - // Remove display label. - $render['#theme_wrappers'] = array(); - - return render($render); + if ($this->options['format'] == 'html') { + $render = array('#submission' => $submission); + _webform_client_form_add_component($webform, $component, NULL, $render, $render, $submission->data, 'html'); + $render = $render[$component['form_key']]; + // Remove display label. + $render['#theme_wrappers'] = array(); + } + else { + // Plain text format is generated via invoking the table output to ensure output is sanitised. + $data = isset($submission->data[$component['cid']]) ? $submission->data[$component['cid']] : NULL; + $render = webform_component_invoke($component['type'], 'table', $component, $data); + } + return $render; } } } diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_filter_submission_data.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_filter_submission_data.inc index b591d79a..f1adc6c6 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_filter_submission_data.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_filter_submission_data.inc @@ -28,8 +28,8 @@ class webform_handler_filter_submission_data extends views_handler_filter_string ); $operators['<'] = array( 'title' => t('Less than'), - 'short' => t('>'), - 'method' => 'op_greater_than', + 'short' => t('<'), + 'method' => 'op_less_than', 'values' => 1, ); diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_numeric_data.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_numeric_data.inc new file mode 100644 index 00000000..d3b15ba7 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_numeric_data.inc @@ -0,0 +1,173 @@ +<?php + +/** + * @file + * Definition of handlers for using numeric submission data. + */ + +/** + * Extended version of the numeric field handler specialized for Webform values. + * + * @ingroup views_field_handlers + */ +class webform_handler_field_numeric_data extends views_handler_field_numeric { + + var $formula = NULL; + + /** + * Constructor + */ + function construct() { + parent::construct(); + $this->formula = TRUE; + } + + /** + * Get the formula for this argument. + * + * $this->ensure_my_table() MUST have been called prior to this. + */ + function get_formula() { + return ("(0.0 + $this->table_alias.$this->real_field)"); + } + + /** + * Called to add the field to a query. + */ + function query() { + $this->ensure_my_table(); + // Add the field. + $params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array(); + $this->field_alias = $this->query->add_field(NULL, $this->get_formula(), $this->table_alias . '_' . $this->field, $params); + + $this->add_additional_fields(); + } + + /** + * Shortcut to get a handler's raw field value. + * + * This should be overridden for handlers with formulae or other + * non-standard fields. Because this takes an argument, fields + * overriding this can just call return parent::get_field($formula) + */ + function get_field($field = NULL) { + return parent::get_field($this->get_formula()); + } + +} + +/** + * Numeric filter handler that works with Webform numeric submission data. + * + * @ingroup views_filter_handlers + */ +class webform_handler_filter_numeric_data extends views_handler_filter_numeric { + + /** + * Get the formula for this argument. + * + * $this->ensure_my_table() MUST have been called prior to this. + */ + function get_formula() { + return ("(0.0 + $this->table_alias.$this->real_field)"); + } + + /** + * Called to add the filter to a query. + */ + function query() { + $this->ensure_my_table(); + + $info = $this->operators(); + if (!empty($info[$this->operator]['method'])) { + $this->{$info[$this->operator]['method']}($this->get_formula()); + } + } + + /** + * Adds a simple operator condition to the query. + */ + function op_simple($field) { + static $sequence = 1; + $param = ":value" . $sequence++; + $this->query->add_where_expression($this->options['group'], + $field . $this->operator . $param, + array($param => $this->value['value'])); + } + + /** + * Adds a between or not-between condition to the query. + */ + function op_between($field) { + static $sequence = 1; + $min = ":min" . $sequence; + $max = ":max" . $sequence++; + if ($this->operator == 'between') { + $this->query->add_where_expression($this->options['group'], + "($min <= $field AND $field <= $max)", + array($min => $this->value['min'], $max => $this->value['max'])); + } + else { + $this->query->add_where_expression($this->options['group'], + "($min > $field OR $field > $max)", + array($min => $this->value['min'], $max => $this->value['max'])); + } + } + + /** + * Adds an empty or not-empty condition to the query. + */ + function op_empty($field) { + if ($this->operator == 'empty') { + $operator = "IS NULL"; + } + else { + $operator = "IS NOT NULL"; + } + + $this->query->add_where_expression($this->options['group'], + "$field $operator"); + } + + /** + * Adds a regular expression condition to the query. + */ + function op_regex($field) { + static $sequence = 1; + $param = ":expression" . $sequence++; + + $this->query->add_where_expression($this->options['group'], + "$field RLIKE $param", + array($param => $this->value['value'])); + } + +} + +/** + * Sort handler that works with Webform numeric submission data. + * + * @ingroup views_sort_handlers + */ +class webform_handler_sort_numeric_data extends views_handler_sort { + + /** + * Get the formula for this sort. + * + * $this->ensure_my_table() MUST have been called prior to this. + */ + function get_formula() { + return ("(0.0 + $this->table_alias.$this->real_field)"); + } + + /** + * Called to add the sort to a query. + */ + function query() { + $this->ensure_my_table(); + // Add the field. + $alias = $this->query->add_field(NULL, $this->get_formula(), $this->table_alias . '_' . $this->field . '_sort'); + // Add the sort for the field using only the alias. + $this->query->add_orderby(NULL, NULL, $this->options['order'], $alias); + } + +} diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_plugin_row_submission_view.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_plugin_row_submission_view.inc index 1437b4db..f964a4d4 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_plugin_row_submission_view.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_plugin_row_submission_view.inc @@ -76,9 +76,9 @@ class webform_views_plugin_row_submission_view extends views_plugin_row { if (isset($this->submissions[$row->{$this->field_alias}])) { $submission = $this->submissions[$row->{$this->field_alias}]; $node = $this->nodes[$submission->nid]; - $submission->view = $this->view; - $build = webform_submission_render($node, $submission, NULL, $this->options['format']); + $format = $this->options['format']; + $build = webform_submission_render($node, $submission, NULL, $format); // Add extra theme functions: $themes = array(); @@ -87,7 +87,9 @@ class webform_views_plugin_row_submission_view extends views_plugin_row { } $build['#theme'] = $themes; - return drupal_render($build); + // Render built submission, and if unsanitized plain text is used, make it safe for display. + $render = drupal_render($build); + return $format == 'html' ? $render : nl2br(check_plain($render)); } } } diff --git a/profiles/wcm_base/modules/contrib/webform/webform.api.php b/profiles/wcm_base/modules/contrib/webform/webform.api.php index a2bc31f6..88e0a634 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.api.php +++ b/profiles/wcm_base/modules/contrib/webform/webform.api.php @@ -669,9 +669,9 @@ function hook_webform_results_access($node, $account) { * * @see webform_results_clear_access(). * - * @param $node object + * @param object $node * The Webform node to check access on. - * @param $account object + * @param object $account * The user account to check access on. * @return boolean * TRUE or FALSE if the user can access the webform results. @@ -697,9 +697,9 @@ function hook_webform_results_clear_access($node, $account) { * * @see webform_node_update_access(). * - * @param $node object + * @param object $node * The Webform node to check access on. - * @param $account object + * @param object $account * The user account to check access on. * @return boolean|NULL * TRUE or FALSE if the user can access the webform results, or NULL if @@ -890,10 +890,14 @@ function _webform_edit_component($component) { * Whether or not to filter the contents of descriptions and values when * rendering the component. Values need to be unfiltered to be editable by * Form Builder. + * @param $submission + * The submission from which this component is being rendered. Usually not + * needed. Used by _webform_render_date() to validate using the submission's + * completion date. * * @see _webform_client_form_add_component() */ -function _webform_render_component($component, $value = NULL, $filter = TRUE) { +function _webform_render_component($component, $value = NULL, $filter = TRUE, $submission = NULL) { $form_item = array( '#type' => 'textfield', '#title' => $filter ? webform_filter_xss($component['name']) : $component['name'], @@ -1090,6 +1094,9 @@ function _webform_theme_component() { * Boolean flag determining if the details about a single component are being * shown. May be used to provided detailed information about a single * component's analysis, such as showing "Other" options within a select list. + * @param $join + * An optional SelectQuery object to be used to join with the submissions + * table to restrict the submissions being analyzed. * @return * An array containing one or more of the following keys: * - table_rows: If this component has numeric data that can be represented in @@ -1110,7 +1117,7 @@ function _webform_theme_component() { * * @see _webform_defaults_component() */ -function _webform_analysis_component($component, $sids = array(), $single = FALSE) { +function _webform_analysis_component($component, $sids = array(), $single = FALSE, $join = NULL) { // Generate the list of options and questions. $options = _webform_select_options_from_text($component['extra']['options'], TRUE); $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE); @@ -1129,6 +1136,10 @@ function _webform_analysis_component($component, $sids = array(), $single = FALS $query->condition('sid', $sids, 'IN'); } + if ($join) { + $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid'); + } + $result = $query->execute(); $counts = array(); foreach ($result as $data) { @@ -1156,7 +1167,7 @@ function _webform_analysis_component($component, $sids = array(), $single = FALS $other = array(); $other[] = l(t('More information'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']); - array( + return array( 'table_header' => $header, 'table_rows' => $rows, 'other_data' => $other, diff --git a/profiles/wcm_base/modules/contrib/webform/webform.drush.inc b/profiles/wcm_base/modules/contrib/webform/webform.drush.inc index 35acd75d..23f0ae5b 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.drush.inc +++ b/profiles/wcm_base/modules/contrib/webform/webform.drush.inc @@ -15,16 +15,17 @@ function webform_drush_command() { 'file' => 'The file path to export to (defaults to print to stdout)', 'format' => 'The exporter format to use. Out-of-the-box this may be "delimited" or "excel".', 'delimiter' => 'Delimiter between columns (defaults to site-wide setting). This option may need to be wrapped in quotes. i.e. --delimter="\t".', - 'components' => 'Comma-separated list of components IDs to include in the export. May also include the built-in special options "serial", "sid", "time", "draft", "ip_address", "uid", and "username".', - 'header-keys' => 'Integer 0 or 1 value. Set to 1 to print header components by their field keys instead of labels.', + 'components' => 'Comma-separated list of components IDs to include.' . "\n" . + 'May also include "serial", "sid", "time", "complete_time", "modified_time", "draft", "ip_address", "uid", and "username".', + 'header-keys' => 'Integer -1 for none, 0 for label (default) or 1 for field key.', 'select-keys' => 'Integer 0 or 1 value. Set to 1 to print select list values by their field keys instead of labels.', 'select-format' => 'Set to "separate" (default) or "compact" to determine how select list values are exported.', - 'range-type' => 'To export a specific range of submissions, specify one of the following range types: "all", "new", "latest", or "range".', + 'range-type' => 'Range of submissions to export: "all", "new", "latest", "range" (by sid, default if start is supplied), "range-serial", or "range-date".', 'range-latest' => 'Integer specifying the latest X submissions will be downloaded. Used if "range-type" is "latest" or no other range options are provided.', - 'range-start' => 'Alternative to "start" option. The submission ID at which to start exporting. Used if "range-type" is "range" or no other range options are provided.', - 'range-end' => 'Alternative to "end" option. The submission ID at which to end exporting. Used if "range-type" is "range" or no other range options are provided.', - 'batch-size' => 'The size of batches in rows (default 10000). If encoutering out of memory errors, set this number lower to export fewer submissions per batch.', - 'skip-header' => 'Skip output of header rows (defaults to printing header)', + 'range-start' => 'The submission ID, serial number, or start date at which to start exporting.', + 'range-end' => 'The submission ID, serial number, or end date at which to end exporting.', + 'completion-type' => 'Submissions to be included: "finished", "draft" or "all" (default).', + 'batch-size' => 'The size of batches in rows (default 10000). If encountering out of memory errors, set this number lower to export fewer submissions per batch.', ), 'aliases' => array('wfx'), ); @@ -33,23 +34,19 @@ function webform_drush_command() { } /** - * Exports a webform in a series of batches, useful for large data dumps that - * would otherwise time out due to memory consumption. + * Exports a webform via drush, useful for large data dumps that would otherwise + * time out due to memory consumption. * * @param int $nid * Node ID of the webform that we want to export. - * @param $file_name - * The name of the file where we're temporarily storing the exported webform - * data. Because this function calls itself repeatedly, you never need to - * specify a filename. It will be automatically generated on the first run, - * and will continue to use that file on all subsequent runs. - * @param $batch_number - * Number of this particular batch run. Like $file_name, this shouldn't be - * called explicitly, but is passed back to this function on subsequent runs. */ -function drush_webform_export($nid = FALSE, $file_name = NULL, $batch_number = NULL) { +function drush_webform_export($nid = FALSE) { if (!$nid) { - return drush_set_error('The node ID of the webform you want to export is required'); + return drush_set_error('The node ID of the webform you want to export is required.'); + } + $node = node_load($nid); + if (!$node) { + return drush_set_error(dt('Node !nid was not found.', array('!nid' => $nid))); } module_load_include('inc', 'webform', 'includes/webform.submissions'); @@ -57,7 +54,6 @@ function drush_webform_export($nid = FALSE, $file_name = NULL, $batch_number = N module_load_include('inc', 'webform', 'includes/webform.components'); module_load_include('inc', 'webform', 'includes/webform.report'); - $node = node_load($nid); // Pull in options from drush to override the defaults. $format = drush_get_option('format', 'delimited'); @@ -68,27 +64,59 @@ function drush_webform_export($nid = FALSE, $file_name = NULL, $batch_number = N $options['components'] = is_array($options['components']) ? $options['components'] : explode(',', $options['components']); // Get the range options. - $range_input = drush_get_merged_prefixed_options('range-'); - foreach ($range_input as $option_name => $option_value) { - $option_name = str_replace('range-', '', $option_name); - $option_name = str_replace('-', '_', $option_name); - $option_name = $option_name === 'type' ? 'range_type' : $option_name; - $range_options[$option_name] = $option_value; - } - - // Determine the range type based on provided input. - $options['range']['range_type'] = drush_get_option('range-type', NULL); - if (!isset($options['range']['range_type'])) { - if (isset($options['range']['start'])) { - $options['range']['range_type'] = 'range'; + unset($options['range']['range_type']); + foreach (drush_get_merged_prefixed_options('range-') as $option_name => $option_value) { + if ($option_name == 'type' && in_array($option_value, array('all', 'new', 'latest', 'range', 'range-serial', 'range-date'))) { + $options['range']['range_type'] = str_replace('-', '_', $option_value); } - elseif (isset($options['range']['latest'])) { - $options['range']['range_type'] = 'latest'; + elseif (in_array($option_name, array('start', 'end', 'latest')) && is_numeric($option_value)) { + $options['range'][$option_name] = $option_value; + } + elseif (in_array($option_name, array('start', 'end')) && strtotime($option_value)) { + $options['range']['range_type'] = 'range_date'; + $options['range'][$option_name . '_date'] = $option_value; } else { - $options['range']['range_type'] = 'all'; + return drush_set_error(dt('Unsupported range option or argument: !opt=!val', + array('!opt' => "range-$option_name", '!val' => $option_value))); } } + + // Determine the range type based on provided input, if not explicitly set. + if (empty($options['range']['range_type'])) { + $options['range']['range_type'] = isset($options['range']['start']) + ? 'range' + : (isset($options['range']['latest']) + ? 'latest' + : 'all'); + } + + // Set defaults for any missing range arguments. + switch ($options['range']['range_type']) { + case 'latest': + if (empty($options['range']['latest'])) { + drush_log('Argument range-latest defaulted to 100.', 'ok'); + $options['range']['latest'] = 100; + } + break; + case 'range': + case 'range_serial': + if (empty($options['range']['start'])) { + $options['range']['start'] = 1; + } + break; + case 'range_date': + if (empty($options['range']['start_date'])) { + $options['range']['start_date'] = "1/1/1970"; + } + break; + } + + // Get the preferred completion type + $options['range']['completion_type'] = drush_get_option('completion-type', NULL); + if (isset($options['range']['completion_type']) && !in_array($options['range']['completion_type'], array('finished', 'draft', 'all'))) { + return drush_set_error('Unsupported completion-type. The available options are "finished", "draft", or "all".'); + } // Set the export options. $options['range']['batch_size'] = drush_get_option('batch-size', 10000); diff --git a/profiles/wcm_base/modules/contrib/webform/webform.info b/profiles/wcm_base/modules/contrib/webform/webform.info index 340c55c1..4676e9cd 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.info +++ b/profiles/wcm_base/modules/contrib/webform/webform.info @@ -10,11 +10,14 @@ dependencies[] = views test_dependencies[] = token ; Files that contain classes: +files[] = includes/webform.webformconditionals.inc + files[] = includes/exporters/webform_exporter_delimited.inc files[] = includes/exporters/webform_exporter_excel_delimited.inc files[] = includes/exporters/webform_exporter_excel_xlsx.inc files[] = includes/exporters/webform_exporter.inc +files[] = views/webform_handler_area_result_pager.inc files[] = views/webform_handler_field_form_body.inc files[] = views/webform_handler_field_is_draft.inc files[] = views/webform_handler_field_node_link_edit.inc @@ -26,9 +29,9 @@ files[] = views/webform_handler_field_webform_status.inc files[] = views/webform_handler_filter_is_draft.inc files[] = views/webform_handler_filter_submission_data.inc files[] = views/webform_handler_filter_webform_status.inc -files[] = views/webform_handler_area_result_pager.inc -files[] = views/webform_plugin_row_submission_view.inc +files[] = views/webform_handler_numeric_data.inc files[] = views/webform_handler_relationship_submission_data.inc +files[] = views/webform_plugin_row_submission_view.inc files[] = views/webform.views.inc files[] = tests/components.test @@ -37,9 +40,9 @@ files[] = tests/permissions.test files[] = tests/submission.test files[] = tests/webform.test -; Information added by Drupal.org packaging script on 2014-11-25 -version = "7.x-4.2" +; Information added by Drupal.org packaging script on 2015-04-29 +version = "7.x-4.8" core = "7.x" project = "webform" -datestamp = "1416925905" +datestamp = "1430334485" diff --git a/profiles/wcm_base/modules/contrib/webform/webform.install b/profiles/wcm_base/modules/contrib/webform/webform.install index 9838fa4a..86f07614 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.install +++ b/profiles/wcm_base/modules/contrib/webform/webform.install @@ -79,6 +79,13 @@ function webform_schema() { 'not null' => TRUE, 'default' => 1, ), + 'confidential' => array( + 'description' => 'Boolean value for whether to anonymize submissions.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), 'submit_text' => array( 'description' => 'The title of the submit button on the form.', 'type' => 'varchar', @@ -283,22 +290,7 @@ function webform_schema() { 'default' => 0, ), 'andor' => array( - 'description' => 'Whether to AND or OR the actions in this group. All actions within the same crid should have the same andor value.', - 'type' => 'varchar', - 'length' => 128, - ), - 'action' => array( - 'description' => 'The action to be performed on the target. Typically "show" or "hide" for targets of type "component", and "send" for targets of type "email".', - 'type' => 'varchar', - 'length' => 128, - ), - 'target_type' => array( - 'description' => 'The type of target to be affected. Either "component" or "email". Indicates what type of ID the "target" column contains.', - 'type' => 'varchar', - 'length' => 128, - ), - 'target' => array( - 'description' => 'The ID of the target to be affected. Typically a component ID.', + 'description' => 'Whether to AND or OR the actions in this group. All actions within the same rgid should have the same andor value.', 'type' => 'varchar', 'length' => 128, ), @@ -365,6 +357,63 @@ function webform_schema() { 'primary key' => array('nid', 'rgid', 'rid'), ); + $schema['webform_conditional_actions'] = array( + 'description' => 'Holds information about conditional actions.', + 'fields' => array( + 'nid' => array( + 'description' => 'The node identifier of a webform.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'rgid' => array( + 'description' => 'The rule group identifier for this group of rules.', + 'type' => 'int', + 'size' => 'small', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'aid' => array( + 'description' => 'The rule identifier for this conditional action.', + 'type' => 'int', + 'size' => 'small', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'target_type' => array( + 'description' => 'The type of target to be affected. Currently always "component". Indicates what type of ID the "target" column contains.', + 'type' => 'varchar', + 'length' => 128, + ), + 'target' => array( + 'description' => 'The ID of the target to be affected. Typically a component ID.', + 'type' => 'varchar', + 'length' => 128, + ), + 'invert' => array( + 'description' => 'If inverted, execute when rule(s) are false.', + 'type' => 'int', + 'size' => 'small', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'action' => array( + 'description' => 'The action to be performed on the target.', + 'type' => 'varchar', + 'length' => 128, + ), + 'argument' => array( + 'description' => 'Optional argument for action.', + 'type' => 'text', + ), + ), + 'primary key' => array('nid', 'rgid', 'aid'), + ); + $schema['webform_emails'] = array( 'description' => 'Holds information regarding e-mails that should be sent upon submitting a webform', 'fields' => array( @@ -429,8 +478,16 @@ function webform_schema() { 'not null' => TRUE, 'default' => 0, ), + 'exclude_empty' => array( + 'description' => 'Determines if the e-mail will include component with an empty value.', + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), 'extra' => array( - 'description' => 'A serialized array of additional options for the e-mail configuration, including excluded components and value mapping for the TO and FROM addresses for select lists.', + 'description' => 'A serialized array of additional options for the e-mail configuration, including value mapping for the TO and FROM addresses for select lists.', 'type' => 'text', 'not null' => TRUE, ), @@ -495,8 +552,27 @@ function webform_schema() { 'not null' => TRUE, 'default' => 0, ), + 'highest_valid_page' => array( + 'description' => 'For drafts, the highest validated page number.', + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), 'submitted' => array( - 'description' => 'Timestamp of when the form was submitted.', + 'description' => 'Timestamp when the form was first saved as draft or submitted.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'completed' => array( + 'description' => 'Timestamp when the form was submitted as complete (not draft).', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'modified' => array( + 'description' => 'Timestamp when the form was last saved (complete or draft).', 'type' => 'int', 'not null' => TRUE, 'default' => 0, @@ -561,7 +637,7 @@ function webform_schema() { 'indexes' => array( 'nid' => array('nid'), 'sid_nid' => array('sid', 'nid'), - 'data' => array(array('data', 64)), + // For all but MS SQL Server databases, 64-character index is created on the data column after the schema is installed. ), ); @@ -666,6 +742,12 @@ function webform_install() { // Disable comments by default on Webform nodes. variable_set('comment_webform', '0'); } + + // Note: MS SQL Server does not support size-limited indexes and the column + // type (text) is too big to fit inside index size limits. + if (!db_index_exists('webform_submitted_data', 'data') && db_driver() != 'sqlsrv') { + db_add_index('webform_submitted_data', 'data', array(array('data', 64))); + } } /** @@ -673,28 +755,32 @@ function webform_install() { */ function webform_uninstall() { // Unset webform variables. - variable_del('webform_node_types_primary'); - variable_del('webform_disabled_components'); + variable_del('webform_blocks'); variable_del('webform_tracking_mode'); - variable_del('webform_default_from_address'); + variable_del('webform_allowed_tags'); + variable_del('webform_email_address_format'); + variable_del('webform_email_address_individual'); variable_del('webform_default_from_name'); + variable_del('webform_default_from_address'); variable_del('webform_default_subject'); + variable_del('webform_email_replyto'); + variable_del('webform_email_html_capable'); variable_del('webform_default_format'); variable_del('webform_format_override'); - variable_del('webform_csv_delimiter'); - variable_del('webform_allowed_tags'); - variable_del('webform_blocks'); - variable_del('webform_search_index'); - variable_del('webform_email_address_format'); - variable_del('webform_export_format'); - variable_del('webform_submission_access_control'); - variable_del('webform_update_batch_size'); variable_del('webform_email_select_max'); - variable_del('webform_tracking_mode'); + variable_del('webform_node_types_primary'); + variable_del('webform_date_type'); + variable_del('webform_export_format'); + variable_del('webform_csv_delimiter'); + variable_del('webform_export_wordwrap'); + variable_del('webform_excel_legacy_exporter'); variable_del('webform_progressbar_style'); variable_del('webform_progressbar_label_first'); variable_del('webform_progressbar_label_confirmation'); - variable_del('webform_excel_legacy_exporter'); + variable_del('webform_table'); + variable_del('webform_submission_access_control'); + variable_del('webform_update_batch_size'); + variable_del('webform_disabled_components'); foreach (node_type_get_names() as $type => $name) { variable_del('webform_node_' . $type); @@ -710,6 +796,23 @@ function webform_uninstall() { // Delete uploaded files. $filepath = file_build_uri('webform'); file_unmanaged_delete_recursive($filepath); + + // Delete the content type "webform" if: + // a) there are no existing nodes of type webform and + // b) no additional fields have been defined for node type webform, beyond + // the default body field. + $query = new EntityFieldQuery(); + $results = $query->entityCondition('entity_type', 'node') + ->entityCondition('bundle', 'webform') + ->range(0,1) + ->execute(); + $instances = field_info_instances('node', 'webform'); + unset($instances['body']); + if (!$results && !$instances) { + node_type_delete('webform'); + drupal_flush_all_caches(); + } + } /** @@ -1017,7 +1120,7 @@ function webform_update_7319(&$sandbox) { } // Process all files attached to a given revision during the same batch. - $limit = variable_get('webform_update_batch_size', 100); + $limit = webform_variable_get('webform_update_batch_size'); $files = db_select('file_managed', 'f') ->fields('f') ->condition('uri', '%' . db_like('://webform/') . '%', 'LIKE') @@ -1128,7 +1231,7 @@ function webform_update_7401(&$sandbox) { '[current-user:$1]', ); - $limit = variable_get('webform_update_batch_size', 100); + $limit = webform_variable_get('webform_update_batch_size'); $processed_count = _webform_update_7401_batch($sandbox, $patterns, $replacements, $dpatterns, $dreplacements, $limit); // If less than limit was processed, the update process is finished. @@ -1393,7 +1496,7 @@ function webform_update_7403(&$sandbox) { ->fetchField(); } - $limit = variable_get('webform_update_batch_size', 100); + $limit = webform_variable_get('webform_update_batch_size'); $webforms = db_select('webform', 'w') ->fields('w') ->condition('nid', $sandbox['last_nid_processed'], '>') @@ -1657,7 +1760,7 @@ function webform_update_7411(&$sandbox) { '[submission:values$1]', ); - $limit = variable_get('webform_update_batch_size', 100); + $limit = webform_variable_get('webform_update_batch_size'); $processed_count = _webform_update_7401_batch($sandbox, $patterns, $replacements, $dpatterns, $dreplacements, $limit); // If less than limit was processed, the update process is finished. @@ -1893,12 +1996,15 @@ function webform_update_7418() { db_change_field('webform_emails', 'extra', 'extra', $spec); } - /** * Add an index on submitted data. */ function webform_update_7419() { - db_add_index('webform_submitted_data', 'data', array(array('data', 64))); + // Note: MS SQL Server does not support size-limited indexes and the column + // type (text) is too big to fit inside index size limits. + if (!db_index_exists('webform_submitted_data', 'data') && db_driver() != 'sqlsrv') { + db_add_index('webform_submitted_data', 'data', array(array('data', 64))); + } } /** @@ -1908,3 +2014,249 @@ function webform_update_7420() { user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('edit webform components')); } +/** + * Set the administrator option for an HTML-capable mail system to the current automatically-detected option. + */ +function webform_update_7421() { + $capable = array_reduce(array('mandrill', 'mimemail', 'htmlmail'), + function($carry, $module) { return $carry || module_exists($module); }, + FALSE); + variable_set('webform_email_html_capable', $capable); + return $capable + ? t('An HTML-capable module is installed. The option to send HTML e-mail is enabled. ') + : t('No commonly-known HTML capable module is installed. The option to send HTML e-mail is disabled.'); +} + +/** + * Remove the administrator option "Include webform forms in search index" and rely on the Search Index view mode instead. + */ +function webform_update_7422() { + variable_del('webform_search_index'); + return module_exists('search') + ? t('Webform forms will now be included in the search index only if the Webform "field" is displayed in the "Search index" view mode.') + : NULL; +} + +/** + * Convert conditionals to be able to support multiple actions per conditional. Backup your database before proceeding. WARNING: Sites with many, many conditionals should execute this update via drush to avoid a PHP timeout. + */ +function webform_update_7423() { + // Create webform_condtional_actions table. + // The table might already exist if this update previously timed-out + if (!db_table_exists('webform_conditional_actions')) { + $schema['webform_conditional_actions'] = array( + 'description' => 'Holds information about conditional actions.', + 'fields' => array( + 'nid' => array( + 'description' => 'The node identifier of a webform.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'rgid' => array( + 'description' => 'The rule group identifier for this group of rules.', + 'type' => 'int', + 'size' => 'small', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'aid' => array( + 'description' => 'The rule identifier for this conditional action.', + 'type' => 'int', + 'size' => 'small', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'target_type' => array( + 'description' => 'The type of target to be affected. Currently always "component". Indicates what type of ID the "target" column contains.', + 'type' => 'varchar', + 'length' => 128, + ), + 'target' => array( + 'description' => 'The ID of the target to be affected. Typically a component ID.', + 'type' => 'varchar', + 'length' => 128, + ), + 'invert' => array( + 'description' => 'If inverted, execute when rule(s) are false.', + 'type' => 'int', + 'size' => 'small', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'action' => array( + 'description' => 'The action to be performed on the target.', + 'type' => 'varchar', + 'length' => 128, + ), + 'argument' => array( + 'description' => 'Optional argument for action.', + 'type' => 'text', + ), + ), + 'primary key' => array('nid', 'rgid', 'aid'), + ); + db_create_table('webform_conditional_actions', $schema['webform_conditional_actions']); + } + + // In a site with many, many conditionals, the db_insert may timeout. Start a transaction to ensure atomic action. + $tx = db_transaction(); + // Copy target information from existing webform_conditional table to new webfrom_condtional_actions table. + $select = db_select('webform_conditional', 'c') + ->fields('c', array('nid', 'rgid', 'action', 'target_type', 'target')) + ->orderBy('nid')->orderBy('rgid'); + $select->addExpression("''", 'argument'); + db_insert('webform_conditional_actions') + ->from($select) + ->execute(); + + // Commit the insert + unset($tx); + + // Remove unneeded columns from webform_conditional. + foreach (array('action', 'target_type', 'target') as $fieldname) { + if (db_field_exists('webform_conditional', $fieldname)) { + db_drop_field('webform_conditional', $fieldname); + } + } + + // Rebuild the registry because this point release contains a new class: WebformConditionals. + registry_rebuild(); + + return t('Webform database tables were successfully adjusted to allow more than one action for each conditional.'); +} + +/** + * Convert conditional actions of "hide" to "isn't shown". + */ +function webform_update_7424() { + $count = db_update('webform_conditional_actions') + ->fields(array('action' => 'show', 'invert' => 1)) + ->condition('action', 'hide') + ->execute(); + return format_plural($count, + '1 "hide" conditional converted to "isn\'t" shown.', + '@count conditionals converted to "isn\'t" shown.'); +} + +/** + * Add "exclude empty" option to emails. + */ +function webform_update_7425() { + // Add next_serial column to webform. + $spec = array( + 'description' => 'Determines if the e-mail will include component with an empty value.', + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ); + if (!db_field_exists('webform_emails', 'exclude_empty')) { + db_add_field('webform_emails', 'exclude_empty', $spec); + } + + // Clear the views cache since this release use the webform_analysis view. + cache_clear_all('*', 'cache_views', TRUE); + + return t('Webform e-mails were sucessfully updated to add the option to exclude empty components.'); +} + +/** + * Add configuration to continue sending individual e-mails to multiple recipients. + */ +function webform_update_7426() { + variable_set('webform_email_address_individual', 1); +} + +/** + * Add database columns for submission completed and modified timestamps. Sites with many submissions may wish to use drush to execute this update. + */ +function webform_update_7427() { + // Create new timestamp columns. + $specs = array( + 'completed' => array( + 'description' => 'Timestamp when the form was submitted as complete (not draft).', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'modified' => array( + 'description' => 'Timestamp when the form was last saved (complete or draft).', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ); + foreach ($specs as $field_name => $spec) { + if (!db_field_exists('webform_submissions', $field_name)) { + db_add_field('webform_submissions', $field_name, $spec); + } + } + + // In a site with many submissions, the db_update may timeout. Start a transaction to ensure atomic action. + $tx = db_transaction(); + // Copy submitted to completed for non-draft submissions. + db_update('webform_submissions') + ->expression('completed', 'submitted') + ->condition('is_draft', 0) + ->execute(); + // Commit the update. + unset($tx); + + // Start another transaction. + $tx = db_transaction(); + db_update('webform_submissions') + ->expression('modified', 'submitted') + ->execute(); + // Commit the update. + unset($tx); + + // Clear the views cache since to see the completed and modified views fields. + cache_clear_all('*', 'cache_views', TRUE); + + return t('Webform submissions were updated with completed and modified timestamps.'); +} + +/** + * Add a "confidential" option to webforms. + */ +function webform_update_7428() { + // Add confidential column to webform. + if (!db_field_exists('webform', 'confidential')) { + $spec = array( + 'description' => 'Boolean value for whether to anonymize submissions.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('webform', 'confidential', $spec); + } + + return t('Webforms may now be configured to anonymize confidential submissions.'); +} + +/** + * Add a column to the submission table to store the page on which to resume a draft. Sites with many, many submissions may wish to execute this update with 'drush updatedb'. + */ +function webform_update_7429() { + // Add highest_valid_page column to webform_submissions. + if (!db_field_exists('webform_submissions', 'highest_valid_page')) { + $spec = array( + 'description' => 'For drafts, the highest validated page number.', + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('webform_submissions', 'highest_valid_page', $spec); + } + + return t('Webforms will now resume draft submissions on the page where the submitter left off.'); +} + diff --git a/profiles/wcm_base/modules/contrib/webform/webform.module b/profiles/wcm_base/modules/contrib/webform/webform.module index dd12c876..5936c261 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.module +++ b/profiles/wcm_base/modules/contrib/webform/webform.module @@ -1,6 +1,7 @@ <?php /** + * @file * This module provides a simple way to create forms and questionnaires. * * The initial development of this module was sponsered by ÅF Industri AB, Open @@ -185,7 +186,7 @@ function webform_menu() { 'access callback' => 'webform_node_update_access', 'access arguments' => array(1), 'file' => 'includes/webform.emails.inc', - 'type' => MENU_CALLBACK, + 'type' => MENU_LOCAL_TASK, ); $items['node/%webform_menu/webform/emails/%webform_menu_email/clone'] = array( 'load arguments' => array(1), @@ -193,14 +194,14 @@ function webform_menu() { 'access callback' => 'webform_node_update_access', 'access arguments' => array(1), 'file' => 'includes/webform.emails.inc', - 'type' => MENU_CALLBACK, + 'type' => MENU_LOCAL_TASK, ); $items['node/%webform_menu/webform/emails/%webform_menu_email/delete'] = array( 'load arguments' => array(1), 'page arguments' => array('webform_email_delete_form', 1, 4), 'access callback' => 'webform_node_update_access', 'access arguments' => array(1), - 'type' => MENU_CALLBACK, + 'type' => MENU_LOCAL_TASK, ); // Node component forms. @@ -237,7 +238,7 @@ function webform_menu() { 'load arguments' => array(3), 'page callback' => 'webform_select_options_ajax', 'access callback' => 'webform_node_update_access', - 'access arguments' => array(1), + 'access arguments' => array(3), 'file' => 'components/select.inc', 'type' => MENU_CALLBACK, ); @@ -518,6 +519,19 @@ function webform_menu_email_load($eid, $nid) { return $email; } +/** + * Return the access token for a submission. + * + * @param object $submission + * The submission object. + * + * @return string + * The access token for the submission. + */ +function webform_get_submission_access_token($submission) { + return md5($submission->submitted . $submission->sid . drupal_get_private_key()); +} + /** * Access function for confirmation pages. * @@ -557,12 +571,13 @@ function webform_confirmation_page_access($node) { // match the hash in the query string. elseif ((int) $user->uid === 0 && (int) $submission->uid === 0) { $hash_query = !empty($_GET['token']) ? $_GET['token'] : NULL; - $hash = md5($submission->submitted . $submission->sid . drupal_get_private_key()); + $hash = webform_get_submission_access_token($submission); if ($hash_query === $hash) { return TRUE; } } - } else { + } + else { // No submission exists (such as auto-deleted by another module, such as // webform_clear), just ensure that the user has access to view the node page. if (node_access('view', $node)) { @@ -596,7 +611,9 @@ function webform_submission_access($node, $submission, $op = 'view', $account = $access_own_submission = isset($submission) && user_access('access own webform submissions', $account) && (($account->uid && $account->uid == $submission->uid) || isset($_SESSION['webform_submission'][$submission->sid])); $access_node_submissions = user_access('access own webform results', $account) && $account->uid == $node->uid; - $general_access = $access_all || $access_own_submission || $access_node_submissions; + $token_access = $submission && isset($_GET['token']) && $_GET['token'] == webform_get_submission_access_token($submission); + + $general_access = $access_all || $access_own_submission || $access_node_submissions || $token_access; // Disable the page cache for anonymous users in this access callback, // otherwise the "Access denied" page gets cached. @@ -997,6 +1014,8 @@ function webform_webform_component_info() { 'features' => array( 'email_address' => TRUE, 'spam_analysis' => TRUE, + 'placeholder' => TRUE, + 'conditional_action_set' => TRUE, ), ), 'fieldset' => array( @@ -1037,6 +1056,7 @@ function webform_webform_component_info() { 'private' => FALSE, 'wrapper_classes' => FALSE, 'css_classes' => FALSE, + 'conditional_action_set' => TRUE, ), ), 'markup' => array( @@ -1054,6 +1074,7 @@ function webform_webform_component_info() { 'private' => FALSE, 'wrapper_classes' => FALSE, 'css_classes' => FALSE, + 'conditional_action_set' => TRUE, ), 'file' => 'components/markup.inc', ), @@ -1090,6 +1111,7 @@ function webform_webform_component_info() { 'default_value' => FALSE, 'email_address' => TRUE, 'email_name' => TRUE, + 'conditional_action_set' => TRUE, ), 'conditional_type' => 'select', ), @@ -1099,6 +1121,8 @@ function webform_webform_component_info() { 'file' => 'components/textarea.inc', 'features' => array( 'spam_analysis' => TRUE, + 'placeholder' => TRUE, + 'conditional_action_set' => TRUE, ), ), 'textfield' => array( @@ -1108,6 +1132,8 @@ function webform_webform_component_info() { 'features' => array( 'email_name' => TRUE, 'spam_analysis' => TRUE, + 'placeholder' => TRUE, + 'conditional_action_set' => TRUE, ), ), 'time' => array( @@ -1213,6 +1239,7 @@ function webform_webform_submission_presave($node, &$submission) { $has_file_components = FALSE; $new_fids = array(); $old_fids = array(); + $renameable = array(); foreach ($node->webform['components'] as $cid => $component) { if ($component['type'] == 'file') { @@ -1222,6 +1249,9 @@ function webform_webform_submission_presave($node, &$submission) { if (empty($value)) { unset($submission->data[$cid][$key]); } + if (strlen($component['extra']['rename'])) { + $renameable[$cid][] = $value; + } } $new_fids = array_merge($new_fids, $submission->data[$cid]); } @@ -1243,13 +1273,20 @@ function webform_webform_submission_presave($node, &$submission) { } } + // Only rename files if this is the first time the submission is being saved as finished. + if ($submission->is_draft || (isset($old_submission) && !$old_submission->is_draft)) { + $renameable = array(); + } + // Save the list of added or removed files so we can add usage in // hook_webform_submission_insert() or _update(). $submission->file_usage = array( // Diff the old against new to determine what files were deleted. 'deleted_fids' => array_diff($old_fids, $new_fids), // Diff the new files against old to determine new uploads. - 'added_fids' => array_diff($new_fids, $old_fids) + 'added_fids' => array_diff($new_fids, $old_fids), + // A list of files which need renaming with tokens. + 'renameable' => $renameable, ); } } @@ -1261,6 +1298,7 @@ function webform_webform_submission_insert($node, $submission) { if (isset($submission->file_usage)) { webform_component_include('file'); webform_file_usage_adjust($submission); + webform_file_rename($node, $submission); } } @@ -1271,6 +1309,7 @@ function webform_webform_submission_update($node, $submission) { if (isset($submission->file_usage)) { webform_component_include('file'); webform_file_usage_adjust($submission); + webform_file_rename($node, $submission); } } @@ -1523,6 +1562,7 @@ function webform_node_update($node) { $inserted_eids = array_diff($current_eids, $original_eids); foreach ($all_eids as $eid) { + $node->webform['emails'][$eid]['nid'] = $node->nid; if (in_array($eid, $inserted_eids)) { webform_email_insert($node->webform['emails'][$eid]); } @@ -1530,7 +1570,6 @@ function webform_node_update($node) { webform_email_delete($node, $original->webform['emails'][$eid]); } elseif ($node->webform['emails'][$eid] != $original->webform['emails'][$eid]) { - $node->webform['emails'][$eid]['nid'] = $node->nid; webform_email_update($node->webform['emails'][$eid]); } } @@ -1569,6 +1608,7 @@ function webform_node_delete($node) { db_delete('webform_component')->condition('nid', $node->nid)->execute(); db_delete('webform_conditional')->condition('nid', $node->nid)->execute(); db_delete('webform_conditional_rules')->condition('nid', $node->nid)->execute(); + db_delete('webform_conditional_actions')->condition('nid', $node->nid)->execute(); db_delete('webform_emails')->condition('nid', $node->nid)->execute(); db_delete('webform_roles')->condition('nid', $node->nid)->execute(); db_delete('webform_submissions')->condition('nid', $node->nid)->execute(); @@ -1588,6 +1628,7 @@ function webform_node_defaults() { 'block' => '0', 'allow_draft' => '0', 'auto_save' => '0', + 'confidential' => '0', 'submit_notice' => '1', 'submit_text' => '', 'next_serial' => 1, @@ -1687,8 +1728,8 @@ function webform_node_load($nodes, $types) { foreach ($nodes[$nid]->webform['emails'] as $eid => $email) { $nodes[$nid]->webform['emails'][$eid]['excluded_components'] = array_filter(explode(',', $email['excluded_components'])); $nodes[$nid]->webform['emails'][$eid]['extra'] = unserialize($email['extra']); - if (variable_get('webform_format_override', 0)) { - $nodes[$nid]->webform['emails'][$eid]['html'] = variable_get('webform_default_format', 0); + if (webform_variable_get('webform_format_override')) { + $nodes[$nid]->webform['emails'][$eid]['html'] = webform_variable_get('webform_default_format'); } } @@ -1733,8 +1774,18 @@ function webform_node_load($nodes, $types) { foreach ($rules as $rule) { $nodes[$nid]->webform['conditionals'][$rule->rgid]['rules'][$rule->rid] = (array) $rule; } + $actions = db_select('webform_conditional_actions') + ->fields('webform_conditional_actions') + ->condition('nid', $nid) + ->orderBy('rgid') + ->orderBy('aid') + ->execute(); + foreach ($actions as $action) { + $nodes[$nid]->webform['conditionals'][$action->rgid]['actions'][$action->aid] = (array) $action; + } } } + } /** @@ -1822,22 +1873,23 @@ function webform_node_view($node, $view_mode) { return; } - // Do not include the form in the search index if indexing is disabled. - if (module_exists('search') && $view_mode == 'search_index' && !variable_get('webform_search_index', 1)) { + // If the webform is not set to display in this view mode, return early. + // View mode of 'form' is exempted to allow blocks and views to force display. + $extra_fields = field_extra_fields_get_display('node', $node->type, $view_mode); + if ($view_mode != 'form' && empty($extra_fields['webform']['visible'])) { return; } $submission = FALSE; $submission_count = 0; $page = node_is_page($node); - $enabled = TRUE; $logging_in = FALSE; $total_limit_exceeded = FALSE; $user_limit_exceeded = FALSE; $closed = FALSE; - $allowed_roles = array(); - // If a teaser, tell the form to load subsequent pages on the node page. + // If a teaser, tell the form to load subsequent pages on the node page. A + // special exception is made for this view mode only. if ($view_mode == 'teaser' && !isset($node->webform['action'])) { $query = array_diff_key($_GET, array('q' => '')); $node->webform['action'] = url('node/' . $node->nid, array('query' => $query)); @@ -1853,21 +1905,10 @@ function webform_node_view($node, $view_mode) { if ($node->webform['status'] == 0) { $closed = TRUE; $enabled = FALSE; + $allowed_roles = array(); } else { - // Check if the user's role can submit this webform. - if (variable_get('webform_submission_access_control', 1)) { - foreach ($node->webform['roles'] as $rid) { - $allowed_roles[$rid] = isset($user->roles[$rid]) ? TRUE : FALSE; - } - if (array_search(TRUE, $allowed_roles) === FALSE && $user->uid != 1) { - $enabled = FALSE; - } - } - else { - // If not using Webform submission access control, allow for all roles. - $allowed_roles = array_keys(user_roles()); - } + $allowed_roles = _webform_allowed_roles($node, $enabled); // $enabled set by reference. } // Get a count of previous submissions by this user. Note that the @@ -1907,14 +1948,14 @@ function webform_node_view($node, $view_mode) { } // Check if this user has a draft for this webform. - $is_draft = FALSE; + $resume_draft = FALSE; if (($node->webform['allow_draft'] || $node->webform['auto_save']) && $user->uid != 0) { // Draft found - display form with draft data for further editing. if ($draft_sid = _webform_fetch_draft_sid($node->nid, $user->uid)) { module_load_include('inc', 'webform', 'includes/webform.submissions'); $submission = webform_get_submission($node->nid, $draft_sid); $enabled = TRUE; - $is_draft = TRUE; + $resume_draft = TRUE; } } @@ -1927,7 +1968,7 @@ function webform_node_view($node, $view_mode) { } // If this is the first time, generate the form array. else { - $form = drupal_get_form('webform_client_form_' . $node->nid, $node, $submission, $is_draft); + $form = drupal_get_form('webform_client_form_' . $node->nid, $node, $submission, $resume_draft); $cached_forms[$node->nid] = $form; } @@ -1948,10 +1989,45 @@ function webform_node_view($node, $view_mode) { '#page' => $page, '#form' => $form, '#enabled' => $enabled, + '#visible' => $extra_fields['webform']['visible'], '#weight' => 10, ); } +/** + * Helper. Generates an array of allowed roles. + * + * @param object $node + * The loaded node object containing a webform. + * @param boolean $user_is_allowed + * Reference to boolean to be set to whether the current user is allowed. + * @return array + * Associative array of allowed roles indexed by the role id with a boolean + * value indicating if the current user has this role. + */ +function _webform_allowed_roles($node, &$user_is_allowed) { + global $user; + if ($node->webform['confidential']) { + // Confidential webform may only be submitted anonymously, including uid 1. + $user_is_allowed = user_is_anonymous(); + $allowed_roles = array(DRUPAL_ANONYMOUS_RID => $user_is_allowed); + } + elseif (webform_variable_get('webform_submission_access_control')) { + // Check if the user's role can submit this webform. + $allowed_roles = array(); + foreach ($node->webform['roles'] as $rid) { + $allowed_roles[$rid] = isset($user->roles[$rid]); + } + $user_is_allowed = $user->uid == 1 || array_search(TRUE, $allowed_roles); + } + else { + // If not using Webform submission access control, allow all roles. + $user_is_allowed = TRUE; + $allowed_roles = array_fill_keys(array_keys(user_roles()), TRUE); + } + return $allowed_roles; +} + /** * Output the Webform into the node content. * @@ -2007,6 +2083,10 @@ function theme_webform_view_messages($variables) { if ($closed) { $message = t('Submissions for this form are closed.'); } + elseif ($node->webform['confidential'] && user_is_logged_in()) { + $message = t('This form is confidential. You much <a href="!url">Log out</a> to submit it.', + array('!url' => url('/user/logout', array('query' => array('destination' => request_uri()))))); + } // If open and not allowed to submit the form, give an explanation. elseif (array_search(TRUE, $allowed_roles) === FALSE && $user->uid != 1) { if (empty($allowed_roles)) { @@ -2042,7 +2122,6 @@ function theme_webform_view_messages($variables) { else { $message = t('You may not submit another entry at this time.'); } - $type = 'error'; } elseif ($total_limit_exceeded && !$cached) { if ($node->webform['total_submit_interval'] == -1 && $node->webform['total_submit_limit'] > 1) { @@ -2051,7 +2130,6 @@ function theme_webform_view_messages($variables) { else { $message = t('You may not submit another entry at this time.'); } - $type = 'error'; } // If the user has submitted before, give them a link to their submissions. @@ -2106,11 +2184,12 @@ function webform_block_view($delta = '') { global $user; // Load the block-specific configuration settings. - $webform_blocks = variable_get('webform_blocks', array()); + $webform_blocks = webform_variable_get('webform_blocks'); $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array(); $settings += array( 'display' => 'form', 'pages_block' => 1, + 'confirmation_block' => 0, ); // Get the node ID from delta. @@ -2134,9 +2213,7 @@ function webform_block_view($delta = '') { // This is a webform node block. $node->webform_block = TRUE; - - // Use the node title for the block title. - $subject = $node->title; + $node->webform['confirmation_block'] = $settings['confirmation_block']; // If not displaying pages in the block, set the #action property on the form. if ($settings['pages_block']) { @@ -2148,24 +2225,66 @@ function webform_block_view($delta = '') { } // Generate the content of the block based on display settings. + $content = array(); if ($settings['display'] == 'form') { - webform_node_view($node, 'full'); - $content = isset($node->content['webform']) ? $node->content['webform'] : array(); + webform_node_view($node, 'form'); + if (isset($node->content['webform'])) { + $content = $node->content['webform']; + if (!$node->content['webform']['#visible']) { + // If the webform form is only shown in a block and not as within the + // node, remove the content from the node. + unset($node->content['webform']); + } + } } else { $content = node_view($node, $settings['display']); } + // Check for an in-block confirmation message. + if (isset($_SESSION['webform_confirmation'][$nid])) { + if ($_SESSION['webform_confirmation'][$nid]['confirmation_page']) { + // Replace form with confirmation page. + $content = array( + '#theme' => array('webform_confirmation_' . $node->nid, 'webform_confirmation'), + '#node' => $node, + '#sid' => $_SESSION['webform_confirmation'][$nid]['sid'], + ); + } elseif (strlen(trim(strip_tags($node->webform['confirmation'])))) { + // Display confirmation link drupal status messages, but in the block. + $message = webform_replace_tokens($node->webform['confirmation'], + $node, + webform_get_submission($nid, $_SESSION['webform_confirmation'][$nid]['confirmation_page']), + NULL, + $node->webform['confirmation_format']); + $content = array( + 'confirmation_message' => array( + '#markup' => "<div class=\"messages status webform-confirmation\">\n" . + '<h2 class="element-invisible">' . t('Status message') . "</h2>\n" . + $message . + "</div>\n", + '#weight' => -1, + ), + 'webform_view' => $content, + ); + + } + unset($_SESSION['webform_confirmation'][$nid]); + if (empty($_SESSION['webform_confirmation'])) { + unset($_SESSION['webform_confirmation']); + } + } + // Add contextual links for the webform node if they aren't already there. if (!isset($content['#contextual_links']['node'])) { $content['#contextual_links']['node'] = array('node', array($node->nid)); } - // Create the block. + // Create the block, using the node title for the block title. // Note that we render the content immediately here rather than passing back // a renderable so that if the block is empty it is hidden. $block = array( - 'subject' => $subject, + 'subject' => check_plain($node->title), 'content' => drupal_render($content), ); return $block; @@ -2179,11 +2298,12 @@ function webform_block_configure($delta = '') { $node = node_load($nid); // Load the block-specific configuration settings. - $webform_blocks = variable_get('webform_blocks', array()); + $webform_blocks = webform_variable_get('webform_blocks'); $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array(); $settings += array( 'display' => 'form', 'pages_block' => 1, + 'confirmation_block' => 0, ); // Build a list of view modes for this node. @@ -2215,6 +2335,16 @@ function webform_block_configure($delta = '') { '#description' => t('If your webform has multiple pages, you may change the behavior of the "Next" button. This will also affect where validation messages show up after an error.'), ); + $form['confirmation_block'] = array( + '#type' => 'radios', + '#title' => t('Confirmation message'), + '#options' => array( + 0 => t('Display as configured in the webform'), + 1 => t('Display the confirmation page in the block on the same page (no redirect)'), + ), + '#default_value' => $settings['confirmation_block'], + '#description' => t('This setting overrides the webform\'s configuration and redirection location settings when the webform is submitted via this block.'), + ); return $form; } @@ -2223,11 +2353,12 @@ function webform_block_configure($delta = '') { */ function webform_block_save($delta = '', $edit = array()) { // Load the previously defined block-specific configuration settings. - $settings = variable_get('webform_blocks', array()); + $settings = webform_variable_get('webform_blocks'); // Build the settings array. $new_settings[$delta] = array( 'display' => $edit['display'], 'pages_block' => $edit['pages_block'], + 'confirmation_block' => $edit['confirmation_block'], ); // We store settings for multiple blocks in just one variable // so we merge the existing settings with the new ones before save. @@ -2248,14 +2379,15 @@ function webform_block_save($delta = '', $edit = array()) { * @param $submission * An object containing information about the form submission if we're * displaying a result. - * @param $is_draft - * Optional. Set to TRUE if displaying a draft. + * @param $resume_draft + * Optional. Set to TRUE when resuming a draft and skipping past previously- + * validated pages is desired. * @param $filter * Whether or not to filter the contents of descriptions and values when * building the form. Values need to be unfiltered to be editable by * Form Builder. */ -function webform_client_form($form, &$form_state, $node, $submission = FALSE, $is_draft = FALSE, $filter = TRUE) { +function webform_client_form($form, &$form_state, $node, $submission = FALSE, $resume_draft = FALSE, $filter = TRUE) { global $user; // Attach necessary JavaScript and CSS. @@ -2272,10 +2404,15 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i $submission = webform_get_submission($node->nid, $form_state['values']['details']['sid']); } + $finished = isset($submission->is_draft) ? (!$submission->is_draft) : 0; + $submit_button_text = $finished + ? t('Save') + : (empty($node->webform['submit_text']) ? t('Submit') : t($node->webform['submit_text'])); + // Bind arguments to $form to make them available in theming and form_alter. $form['#node'] = $node; $form['#submission'] = $submission; - $form['#is_draft'] = $submission ? $submission->is_draft : $is_draft; + $form['#is_draft'] = $submission && $submission->is_draft; $form['#filter'] = $filter; // Add a theme function for this form. @@ -2320,6 +2457,15 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i $form_state['webform']['page_count']++; } $form_state['webform']['preview'] = $node->webform['preview']; + + // If this is the first time this draft has been restore and presented to + // the user, let them know that they are looking at a draft, rather than + // a new form. This applies to the node view page, but not to a submission + // edit page (where they presummably know what submission they are + // editing). + if ($resume_draft && empty($form_state['input'])) { + drupal_set_message(t('A partially-completed form was found. Please complete the remaining portions.')); + } } else { $form_state['webform']['component_tree'] = $form_state['storage']['component_tree']; @@ -2328,17 +2474,6 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i $form_state['webform']['preview'] = $form_state['storage']['preview']; } - // Shorten up our variable names. - $component_tree = $form_state['webform']['component_tree']; - $page_count = $form_state['webform']['page_count']; - $page_num = $form_state['webform']['page_num']; - $preview = $form_state['webform']['preview']; - - if ($page_count > 1) { - $next_page_labels = array(); - $prev_page_labels = array(); - } - // Set the input values based on whether we're editing an existing // submission or not. $input_values = isset($submission->data) ? $submission->data : array(); @@ -2359,6 +2494,46 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i } } + // Generate conditional topological order & report any errors. + $sorter = webform_get_conditional_sorter($node); + $sorter->reportErrors(); + + // Excecute the condtionals on the current input values + $input_values = $sorter->executeConditionals($input_values); + + // Allow values from other pages to be sent to browser for conditionals. + $form['#conditional_values'] = $input_values; + + // For resuming a previous draft, find the next page after the last + // validated page. + if (!isset($form_state['storage']['page_num']) && $submission && $submission->is_draft && $submission->highest_valid_page) { + // Find the + // 1) previous/next non-empty page, or + // 2) the preview page, or + // 3) the preview page, forcing its display if the form would unexpectedly submit, or + // 4) page 1 even if empty, if no other previous page would be shown + $form_state['webform']['page_num'] = $submission->highest_valid_page; + do { + $form_state['webform']['page_num']++; + } while (!webform_get_conditional_sorter($node)->pageVisibility($form_state['webform']['page_num'])); + if (!$form_state['webform']['preview'] && $form_state['webform']['page_num'] == $form_state['webform']['page_count'] + (int)!$form_state['webform']['preview']) { + // Force a preview to avert an unintended submission via Next. + $form_state['webform']['preview'] = TRUE; + $form_state['webform']['page_count']++; + // The form hasn't been submitted (ever) and the preview code will + // expect $form_state['values']['submitted'] to be set from a previous + // submission, so provide these values here. + $form_state['values']['submitted'] = $input_values; + } + $form_state['storage']['submitted'] = $input_values; + } + + // Shorten up our variable names. + $component_tree = $form_state['webform']['component_tree']; + $page_count = $form_state['webform']['page_count']; + $page_num = $form_state['webform']['page_num']; + $preview = $form_state['webform']['preview']; + if ($page_count > 1) { $page_labels = webform_page_labels($node, $form_state); $form['progressbar'] = array( @@ -2371,6 +2546,11 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i ); } + // Check whether a previous submission was truncated. The length of the client form is not + // estimated before submission because a) the determination may not be accurate for some + // webform components and b) the error will be apparent upon submission. + webform_input_vars_check($form, $form_state, 'submitted'); + // Recursively add components to the form. The unfiltered version of the // form (typically used in Form Builder), includes all components. foreach ($component_tree['children'] as $cid => $component) { @@ -2378,7 +2558,7 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i $next_page_labels[$component['page_num'] - 1] = !empty($component['extra']['next_page_label']) ? t($component['extra']['next_page_label']) : t('Next Page >'); $prev_page_labels[$component['page_num']] = !empty($component['extra']['prev_page_label']) ? t($component['extra']['prev_page_label']) : t('< Previous Page'); } - if ($filter == FALSE || _webform_client_form_rule_check($node, $component, $page_num, $input_values)) { + if (!$filter || $sorter->componentVisibility($cid, $page_num)) { $component_value = isset($input_values[$cid]) ? $input_values[$cid] : NULL; _webform_client_form_add_component($node, $component, $component_value, $form['submitted'], $form, $input_values, 'form', $page_num, $filter); } @@ -2390,17 +2570,14 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i // Add the preview if needed. if ($preview && $page_num === $page_count) { - $preview_submission = $submission ? $submission : webform_submission_create($node, $user, $form_state, TRUE); + $preview_submission = webform_submission_create($node, $user, $form_state, TRUE, $submission); $preview_message = $node->webform['preview_message']; - $submit_button_text = empty($node->webform['submit_text']) ? t('Submit') : t($node->webform['submit_text']); if (strlen(trim(strip_tags($preview_message))) === 0) { $preview_message = t('Please review your submission. Your submission is not complete until you press the "!button" button!', array('!button' => $submit_button_text)); } - $preview_message = webform_replace_tokens($preview_message, $node, $preview_submission); - $preview_message = check_markup($preview_message, $node->webform['preview_message_format']); $form['preview_message'] = array( '#type' => 'markup', - '#markup' => $preview_message, + '#markup' => webform_replace_tokens($preview_message, $node, $preview_submission, NULL, $node->webform['preview_message_format']), ); $form['preview'] = webform_submission_render($node, $preview_submission, NULL, 'html', $node->webform['preview_excluded_components']); @@ -2430,7 +2607,7 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i ); $form['details']['finished'] = array( '#type' => 'hidden', - '#value' => isset($submission->is_draft) ? (!$submission->is_draft) : 0, + '#value' => $finished, ); // Add process functions to remove the IDs forced upon buttons and wrappers. @@ -2476,9 +2653,7 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $i if ($page_num == $page_count) { $form['actions']['submit'] = array( '#type' => 'submit', - '#value' => ($form['details']['finished']['#value']) - ? t('Save') - : (empty($node->webform['submit_text']) ? t('Submit') : t($node->webform['submit_text'])), + '#value' => $submit_button_text, '#weight' => 10, '#attributes' => array( 'class' => array('webform-submit', 'button-primary'), @@ -2524,173 +2699,6 @@ function webform_client_form_process($form, $form_state) { return $form; } -/** - * Check if a component should be displayed on the current page. - * - * @param $node - * The full node object. - * @param $component - * The target component that is being checked if it should be shown. - * @param $page_num - * The page number of the component that is being checked. If the number "0" - * is passed in, the component visibility is checked regardless of its page - * number. - * @param $input_values - * An array of all the values in the form used for comparison. Values from - * $form_state['values']['submitted'] or $submission->data may be used. - * @param $format - * The format the component will be displayed in. May be one of the following: - * - form: Show in an editable form. - * - html: Show in HTML results. - * - text: Show in plain text. - * - * @return - * A Webform constant of one of the following: - * - WEBFORM_CONDITIONAL_EXCLUDE (0): The component should be hidden. - * - WEBFORM_CONDITIONAL_INCLUDE (1): The component should be shown. - * - WEBFORM_CONDITIONAL_SAME_PAGE (2): The component should be hidden, but - * needs to be rendered on the page because at least one source component - * is on the same page. The field will be hidden with JavaScript. This - * constant is only used if the $format parameter is "form". - */ -function _webform_client_form_rule_check($node, $component, $page_num, $input_values, $format = 'form') { - // Hold a static map of target CID to conditionals for lookup efficiency. - static $target_maps = array(); - static $is_shown = array(); - static $cyclic_component = array(); - - if (!isset($target_maps[$node->nid])) { - $target_map = array(); - foreach ($node->webform['conditionals'] as $conditional) { - if ($conditional['target_type'] == 'component') { - $target_map[$conditional['target']][] = $conditional; - } - } - $target_maps[$node->nid] = $target_map; - } - else { - $target_map = $target_maps[$node->nid]; - } - - // Short cut the rest of this function if the answer has already been calculated - if (isset($is_shown[$node->nid][$page_num][$component['cid']])) { - return $is_shown[$node->nid][$page_num][$component['cid']]; - } - - // Short cut the rest of this function if no conditionals are defined. - if (empty($target_map)) { - return WEBFORM_CONDITIONAL_INCLUDE; - } - - // Check the rules for this entire page. Note individual page breaks are - // checked down below in the individual component rule checks. - $show_page = TRUE; - if ($component['page_num'] > 1 && $component['type'] != 'pagebreak') { - foreach ($node->webform['components'] as $cid => $page_component) { - if ($page_component['type'] == 'pagebreak' && $page_component['page_num'] == $page_num) { - $show_page = _webform_client_form_rule_check($node, $page_component, $page_num, $input_values, $format); - break; - } - } - } - - // Check any parents' visibility rules. - $show_parent = $show_page; - if ($show_parent && $component['pid'] && isset($node->webform['components'][$component['pid']])) { - $parent_component = $node->webform['components'][$component['pid']]; - $show_parent = _webform_client_form_rule_check($node, $parent_component, $page_num, $input_values, $format); - } - - // Check the individual component rules. - $show_component = $show_parent; - if ($show_component && ($page_num == 0 || $component['page_num'] == $page_num) && isset($target_map[$component['cid']])) { - module_load_include('inc', 'webform', 'includes/webform.conditionals'); - $operators = webform_conditional_operators(); - $conditionals = $target_map[$component['cid']]; - foreach ($conditionals as $conditional) { - $conditional_result = TRUE; - - // Execute each comparison callback. - $conditional_results = array(); - foreach ($conditional['rules'] as $rule) { - // TODO: Support other source types besides components? - if ($rule['source_type'] !== 'component') { - continue; - } - $source_component = $node->webform['components'][$rule['source']]; - $source_cid = $source_component['cid']; - - // If destined for form output, and a source component is on the same - // page as the current page, we are unable to tell if the target - // component is needed, so we return the same page constant, which - // evaluates to TRUE (a value of 2), if the component would otherwise - // be hidden. - if ($format === 'form' && $source_component['page_num'] == $page_num) { - if (in_array($component['cid'], $cyclic_component)) { - // Cyclic dependency detected. Assume intrapage_dependent. - return WEBFORM_CONDITIONAL_INCLUDE; - } - array_push($cyclic_component, $component['cid']); - if (_webform_client_form_rule_check($node, $source_component, $page_num, $input_values, $format)) { - $intrapage_dependent = TRUE; - } - array_pop($cyclic_component); - } - - $source_values = array(); - if (isset($input_values[$source_cid])) { - $component_value = $input_values[$source_cid]; - // For select_or_other components, use only the select values because $source_values must not be a nested array. - // During preview, the array is already flattened. - if ($source_component['type'] === 'select' && - !empty($source_component['extra']['other_option']) && - isset($component_value['select'])) { - $component_value = $component_value['select']; - } - $source_values = is_array($component_value) ? $component_value : array($component_value); - } - - // Determine the operator and callback. - $conditional_type = webform_component_property($source_component['type'], 'conditional_type'); - $operator_info = $operators[$conditional_type]; - - // Perform the comparison callback and build the results for this group. - $comparison_callback = $operator_info[$rule['operator']]['comparison callback']; - $conditional_results[] = $comparison_callback($source_values, $rule['value']); - } - - // Calculate the and/or result. - $filtered_results = array_filter($conditional_results); - if ($conditional['andor'] === 'or') { - $conditional_result = count($filtered_results) > 0; - } - else { - $conditional_result = count($filtered_results) === count($conditional_results); - } - - // Flip the result of the action is to hide. - if ($conditional['action'] == 'hide') { - $show_component = !$conditional_result; - } - else { - $show_component = $conditional_result; - } - if (!$show_component && isset($intrapage_dependent)) { - $show_component = WEBFORM_CONDITIONAL_SAME_PAGE; - } - } - } - - // Convert the result to our defined constants. - if (is_bool($show_component)) { - $show_component = $show_component ? WEBFORM_CONDITIONAL_INCLUDE : WEBFORM_CONDITIONAL_EXCLUDE; - } - - // Remember whether this item is shown on this page - $is_shown[$node->nid][$page_num][$component['cid']] = $show_component; - - return $show_component; -} /** * Add a component to a renderable array. Called recursively for fieldsets. @@ -2764,7 +2772,7 @@ function _webform_client_form_add_component($node, $component, $component_value, // version of the form (such as for Form Builder). elseif ($component['page_num'] == $page_num || $filter == FALSE) { // Add this user-defined field to the form (with all the values that are always available). - if ($element = webform_component_invoke($component['type'], 'render', $component, $component_value, $filter)) { + if ($element = webform_component_invoke($component['type'], 'render', $component, $component_value, $filter, $form['#submission'])) { // Set access based on the private property. $element += array('#access' => TRUE); $element['#access'] = $element['#access'] && $component_access; @@ -2778,6 +2786,12 @@ function _webform_client_form_add_component($node, $component, $component_value, $element['#webform_private'] = $component['extra']['private']; } + // The 'placeholder' option is in some components, but it's not a real + // property. Add it for Form Builder compatibility. + if (webform_component_feature($component['type'], 'placeholder')) { + $element['#webform_placeholder'] = $component['extra']['placeholder']; + } + // Add custom CSS classes to the field and wrapper. _webform_component_classes($element, $component); @@ -2800,9 +2814,12 @@ function _webform_client_form_add_component($node, $component, $component_value, } if (isset($component['children']) && is_array($component['children'])) { + $sorter = webform_get_conditional_sorter($node); foreach ($component['children'] as $scid => $subcomponent) { $subcomponent_value = isset($input_values[$scid]) ? $input_values[$scid] : NULL; - if (_webform_client_form_rule_check($node, $subcomponent, $page_num, $input_values, $format)) { + // Include if always shown, or for forms, also if currently hidden but might be shown due to conditionals. + $visibility = $sorter->componentVisibility($scid, $subcomponent['page_num']); + if ($visibility == WebformConditionals::componentShown || ($format == 'form' && $visibility)) { _webform_client_form_add_component($node, $subcomponent, $subcomponent_value, $parent_fieldset[$component['form_key']], $form, $input_values, $format, $page_num, $filter); } } @@ -2826,17 +2843,7 @@ function webform_client_form_prevalidate($form, &$form_state) { // Check if the user is allowed to submit based on role. This check is // repeated here to ensure the user is still logged in at the time of // submission, otherwise a stale form in another window may be allowed. - $allowed_role = TRUE; - $allowed_roles = array(); - if (variable_get('webform_submission_access_control', 1) && !$finished) { - $allowed_roles = array(); - foreach ($node->webform['roles'] as $rid) { - $allowed_roles[$rid] = isset($user->roles[$rid]) ? TRUE : FALSE; - } - if (array_search(TRUE, $allowed_roles) === FALSE && $user->uid != 1) { - $allowed_role = FALSE; - } - } + $allowed_roles = _webform_allowed_roles($node, $allowed_role); // $allowed_role set by reference. // Check that the submissions have not exceeded the total submission limit. $total_limit_exceeded = FALSE; @@ -2876,6 +2883,10 @@ function webform_client_form_validate($form, &$form_state) { // Prevalidation failed. The form cannot be submitted. Do not attemp futher validation. return; } + if ($form_state['webform']['preview'] && $form_state['webform']['page_count'] === $form_state['webform']['page_num']) { + // Form has already passed validation and is on the preview page. + return; + } module_load_include('inc', 'webform', 'includes/webform.submissions'); $node = $form['#node']; @@ -2888,11 +2899,14 @@ function webform_client_form_validate($form, &$form_state) { foreach ($new_values as $cid => $values) { $input_values[$cid] = $values; } + // Ensure that all conditionally-hidden values are removed. + $input_values = webform_get_conditional_sorter($node)->executeConditionals($input_values, $form_state['webform']['page_num']); } else { $input_values = NULL; } + // Run all #element_validate and #required checks. These are skipped initially // by setting #validated = TRUE on all components when they are added. _webform_client_form_validate($form, $form_state, 'webform_client_form', $input_values); @@ -2905,15 +2919,33 @@ function webform_client_form_validate($form, &$form_state) { * a different property to ensure that validation has occurred. */ function _webform_client_form_validate(&$elements, &$form_state, $form_id = NULL, $input_values = NULL) { - // Webform-specific enhancement, only validate the field if it was used in - // this submission. This both skips validation on the field and sets the value - // of the field to NULL, preventing any dangerous input. if (isset($input_values) && isset($elements['#webform_component'])) { - $needs_validation = _webform_client_form_rule_check($form_state['complete form']['#node'], $elements['#webform_component'], 0, $input_values); - if (!$needs_validation) { + $sorter = webform_get_conditional_sorter($form_state['complete form']['#node']); + $cid = $elements['#webform_component']['cid']; + $page_num = $form_state['values']['details']['page_num']; + // Webform-specific enhancement, only validate the field if it was used in this submission. + // This both skips validation on the field and sets the value of the field to NULL, preventing any dangerous input. + // Short-circuit validation for a hidden component (hidden by rules dependent upon component on previous pages), + // or a component this is dependent upon values on the current page, but is hidden based upon their current values. + if ($sorter->componentVisibility($cid, $page_num) != WebformConditionals::componentShown) { form_set_value($elements, NULL, $form_state); return; } + + // Check for changes in requires status made by conditionals. + $required = $sorter->componentRequired($cid, $page_num); + if (isset($required)) { + $elements['#required'] = $required; + + // Some components, e.g. grids, have nested sub-elements. Extend required + // to any sub-components. + foreach (element_children($elements) as $key) { + if (isset($elements[$key]) && $elements[$key] && !isset($elements[$key]['#webform_component'])) { + // Child is *not* a component. + $elements[$key]['#required'] = $required; + } + } + } } // Recurse through all children. @@ -2923,7 +2955,7 @@ function _webform_client_form_validate(&$elements, &$form_state, $form_id = NULL } } // Validate the current input. - if (isset($elements['#webform_validated']) && $elements['#webform_validated'] == FALSE) { + if (isset($elements['#webform_validated']) && !$elements['#webform_validated']) { if (isset($elements['#needs_validation'])) { // Make sure a value is passed when the field is required. // A simple call to empty() will not cut it here as some fields, like @@ -3085,23 +3117,23 @@ function webform_client_form_pages($form, &$form_state) { // Assume the form is completed unless the page logic says otherwise. $form_state['webform_completed'] = TRUE; + // Merge any stored submission data for multistep forms. + $original_values = is_array($form_state['values']['submitted']) ? $form_state['values']['submitted'] : array(); + if (isset($form_state['storage']['submitted'])) { + // Array + operator keeps all elements of left operand and discards any duplicate elements in right operand. + $original_values += $form_state['storage']['submitted']; + } + + // Execute conditionals on submission values. + $form_state['values']['submitted'] = webform_get_conditional_sorter($node)->executeConditionals($original_values); + // Check for a multi-page form that is not yet complete. $submit_op = !empty($form['actions']['submit']['#value']) ? $form['actions']['submit']['#value'] : t('Submit'); $draft_op = !empty($form['actions']['draft']['#value']) ? $form['actions']['draft']['#value'] : t('Save Draft'); if (!in_array($form_state['values']['op'], array($submit_op, $draft_op, '__AUTOSAVE__'))) { - // Store values from the current page in the form state storage. - if (is_array($form_state['values']['submitted'])) { - foreach ($form_state['values']['submitted'] as $key => $val) { - $form_state['storage']['submitted'][$key] = $val; - } - } - // Update form state values with those from storage. - if (isset($form_state['storage']['submitted'])) { - foreach ($form_state['storage']['submitted'] as $key => $val) { - $form_state['values']['submitted'][$key] = $val; - } - } + // Store values from the current page in the form state storage. + $form_state['storage']['submitted'] = $form_state['values']['submitted']; // Set the page number. if (!isset($form_state['storage']['page_num'])) { @@ -3111,7 +3143,7 @@ function webform_client_form_pages($form, &$form_state) { $forward = 1; } elseif (end($form_state['clicked_button']['#parents']) == 'previous') { - $forward = 0; + $forward = -1; } $current_page = $form_state['storage']['page_num']; @@ -3122,87 +3154,26 @@ function webform_client_form_pages($form, &$form_state) { // 3) the preview page, forcing its display if the form would unexpectedly submit, or // 4) page 1 even if empty, if no other previous page would be shown $preview_page_num = $form_state['storage']['page_count'] + (int)!$form_state['webform']['preview']; - $page_zero = array( - array( - 'type' => 'faux', - 'page_num' => 0, - ), - array( - 'type' => 'pagebreak', - 'page_num' => 1, - ), - ); - $page_preview = array( - array( - 'type' => 'pagebreak', - 'page_num' => $preview_page_num, - ), - array( - 'type' => 'faux', - 'page_num' => $preview_page_num, - ), - ); - $components = array_merge($page_zero, $node->webform['components'], $page_preview); - if (!$forward) { - $components = array_reverse($components); + $page_num = $current_page; + do { + $page_num += $forward; + } while (!webform_get_conditional_sorter($node)->pageVisibility($page_num)); + if (!$form_state['webform']['preview'] && $page_num == $preview_page_num) { + // Force a preview to avert an unintended submission via Next. + $form_state['webform']['preview'] = TRUE; + $form_state['storage']['preview'] = TRUE; + $form_state['storage']['page_count']++; } - foreach ($components as $component) { - if ($component['type'] == 'pagebreak') { - if ($forward - ? ($component['page_num'] > $form_state['storage']['page_num']) - : ($component['page_num'] <= $form_state['storage']['page_num'])){ - $previous_pagebreak = $component; - } - } - elseif (isset($previous_pagebreak)) { - // If a component is shown on this page, advance to this page. - $page_num = $previous_pagebreak['page_num'] + $forward - 1; - if ($component['page_num'] == $page_num && - ($component['type'] == 'faux' || _webform_client_form_rule_check($node, $component, $page_num, $form_state['values']['submitted']) == WEBFORM_CONDITIONAL_INCLUDE)) { - if ($component['type'] == 'faux') { - if ($forward) { - if (!$form_state['webform']['preview']) { - // Force a preview to avert an unintended submission via Next. - $form_state['webform']['preview'] = TRUE; - $form_state['storage']['preview'] = TRUE; - $form_state['storage']['page_count']++; - } - } - else { - $page_num = max (1, $page_num); - } - } - $form_state['storage']['page_num'] = $page_num; - break; // LOOP EXIT - } - } - } - + $form_state['storage']['page_num'] = $page_num; } // The form is done if the page number is greater than the page count. $form_state['webform_completed'] = $form_state['storage']['page_num'] > $form_state['storage']['page_count']; } - // Merge any stored submission data for multistep forms. - if (isset($form_state['storage']['submitted'])) { - $original_values = is_array($form_state['values']['submitted']) ? $form_state['values']['submitted'] : array(); - unset($form_state['values']['submitted']); - - foreach ($form_state['storage']['submitted'] as $key => $val) { - $form_state['values']['submitted'][$key] = $val; - } - foreach ($original_values as $key => $val) { - $form_state['values']['submitted'][$key] = $val; - } - - // Remove the variable so it doesn't show up in the additional processing. - unset($original_values); - } - // Inform the submit handlers that a draft will be saved. $form_state['save_draft'] = in_array($form_state['values']['op'], array($draft_op, '__AUTOSAVE__')) || - ($node->webform['auto_save'] && !$form_state['webform_completed'] && user_is_logged_in()); + ($node->webform['auto_save'] && !$form_state['values']['details']['finished'] && !$form_state['webform_completed'] && user_is_logged_in()); // Determine what we need to do on the next page. if (!empty($form_state['save_draft']) || !$form_state['webform_completed']) { @@ -3242,8 +3213,13 @@ function webform_client_form_submit($form, &$form_state) { // Merge with new submission data. The + operator maintains numeric keys. // This maintains existing data with just-submitted data when a user resumes // a submission previously saved as a draft. - $new_data = webform_submission_data($node, $form_state['values']['submitted']); - $submission->data = $new_data + $submission->data; + // Remove any existing data on this and previous pages. If components are hidden, they may + // be in the $submission->data but absent entirely from $new_data; + $page_map = webform_get_conditional_sorter($node)->getPageMap(); + for ($page_nr = 1; $page_nr <= $form_state['webform']['page_num']; $page_nr++) { + $submission->data = array_diff_key($submission->data, $page_map[$page_nr]); + } + $submission->data = webform_submission_data($node, $form_state['values']['submitted']) + $submission->data; } else { // Create a new submission object. @@ -3251,7 +3227,16 @@ function webform_client_form_submit($form, &$form_state) { // Since this is a new submission, a new sid is needed. $sid = NULL; } + + // Save draft state, and for drafts, save the current page (if clicking next) + // or the previous page (if not) as the last valid page. $submission->is_draft = $is_draft; + $submission->highest_valid_page = 0; + if ($is_draft) { + $submission->highest_valid_page = end($form_state['clicked_button']['#parents']) == 'next' && $form_state['values']['op'] != '__AUTOSAVE__' + ? $form_state['webform']['page_num'] + : $form_state['webform']['page_num'] - 1; + } // If there is no data to be saved (such as on a multipage form with no fields // on the first page), process no further. Submissions with no data cannot @@ -3268,7 +3253,7 @@ function webform_client_form_submit($form, &$form_state) { // Set a cookie including the server's submission time. The cookie expires // in the length of the interval plus a day to compensate for timezones. - $tracking_mode = variable_get('webform_tracking_mode', 'cookie'); + $tracking_mode = webform_variable_get('webform_tracking_mode'); if ($tracking_mode === 'cookie' || $tracking_mode === 'strict') { $cookie_name = 'webform-' . $node->nid; $time = REQUEST_TIME; @@ -3296,47 +3281,62 @@ function webform_client_form_submit($form, &$form_state) { } // Strip out empty tags added by WYSIWYG editors if needed. - $confirmation = strlen(trim(strip_tags($node->webform['confirmation']))) ? $node->webform['confirmation'] : ''; - $confirmation = webform_replace_tokens($confirmation, $node, $submission, NULL, TRUE); + $message = strlen(trim(strip_tags($node->webform['confirmation']))) > 0; // Check confirmation and redirect_url fields. - $message = NULL; $redirect = NULL; - $external_url = FALSE; $redirect_url = trim($node->webform['redirect_url']); if (isset($form['actions']['draft']['#value']) && $form_state['values']['op'] == $form['actions']['draft']['#value']) { $message = t('Submission saved. You may return to this form later and it will restore the current values.'); } elseif ($is_draft) { - // No redirect needed + // No redirect needed. No confirmation message needed. + $message = FALSE; } elseif (!empty($form_state['values']['details']['finished'])) { $message = t('Submission updated.'); $redirect = "node/{$node->nid}/submission/$sid"; } + elseif (!empty($node->webform['confirmation_block'])) { + $message = FALSE; + // Webform was submitted in a block and the confirmation message is to be + // displayed in the block. + $_SESSION['webform_confirmation'][$node->nid] = array( + 'sid' => $sid, + 'confirmation_page' => $redirect_url == '<confirmation>', + ); + drupal_page_is_cacheable(FALSE); + } elseif ($redirect_url == '<none>') { - // No redirect needed + // No redirect needed. Show a confirmatin message if there is one. } elseif ($redirect_url == '<confirmation>') { + // No confirmation message needed because it will be shown on the + // confirmation page. + $message = FALSE; $query = array('sid' => $sid); if ((int) $user->uid === 0) { - $query['token'] = md5($submission->submitted . $submission->sid . drupal_get_private_key()); + $query['token'] = webform_get_submission_access_token($submission); } $redirect = array('node/' . $node->nid . '/done', array('query' => $query)); } else { - // Clean up the redirect URL, filter it for tokens and detect external domains. + // Clean up the redirect URL, filter it for tokens and detect external + // domains. If the redirect is to an external URL, then don't show the + // confirmation message. $redirect = webform_replace_url_tokens($redirect_url, $node, $submission); - $external_url = $redirect[1]['#webform_external']; + if ($redirect[1]['#webform_external']) { + $message = FALSE; + } } // Show a message if manually set. - if (isset($message)) { - drupal_set_message(webform_replace_tokens($message, $node, NULL, NULL, TRUE)); + if (is_string($message)) { + drupal_set_message($message); } // If redirecting and we have a confirmation message, show it as a message. - elseif (!$is_draft && !$external_url && (!empty($redirect_url) && $redirect_url != '<confirmation>') && !empty($confirmation)) { - drupal_set_message(check_markup(webform_replace_tokens($confirmation, $node), $node->webform['confirmation_format'], '', TRUE)); + elseif ($message) { + drupal_set_message(webform_replace_tokens($node->webform['confirmation'], $node, $submission, NULL, $node->webform['confirmation_format'])); } $form_state['redirect'] = $redirect; @@ -3393,7 +3393,7 @@ function _webform_client_form_submit_flatten($node, $fieldset, $parent = 0) { */ function _webform_confirmation($node) { drupal_set_title($node->title); - webform_set_breadcrumb($node); + webform_set_breadcrumb($node, TRUE); $sid = isset($_GET['sid']) ? $_GET['sid'] : NULL; return theme(array('webform_confirmation_' . $node->nid, 'webform_confirmation'), array('node' => $node, 'sid' => $sid)); } @@ -3409,10 +3409,12 @@ function template_preprocess_webform_form(&$vars) { $vars['nid'] = $vars['form']['submission']['#value']->nid; } - if (!empty($vars['form']['#node']->webform['conditionals'])) { + if (!empty($vars['form']['#node']->webform['conditionals']) && empty($vars['form']['preview'])) { module_load_include('inc', 'webform', 'includes/webform.conditionals'); - $submission_data = isset($vars['form']['#parameters'][1]['storage']['submitted']) ? $vars['form']['#parameters'][1]['storage']['submitted'] : array(); - $settings = webform_conditional_prepare_javascript($vars['form']['#node'], $submission_data); + $submission_data = isset($vars['form']['#conditional_values']) ? $vars['form']['#conditional_values'] : array(); + $settings = webform_conditional_prepare_javascript($vars['form']['#node'], + $submission_data, + $vars['form']['details']['page_num']['#value']); drupal_add_js(array('webform' => array('conditionals' => array('webform-client-form-' . $vars['nid'] => $settings))), 'setting'); } @@ -3422,23 +3424,28 @@ function template_preprocess_webform_form(&$vars) { * Prepare for theming of the webform submission confirmation. */ function template_preprocess_webform_confirmation(&$vars) { - $confirmation = check_markup($vars['node']->webform['confirmation'], $vars['node']->webform['confirmation_format'], '', TRUE); + $node = $vars['node']; + // Strip out empty tags added by WYSIWYG editors if needed. + $confirmation = $node->webform['confirmation']; + $confirmation = strlen(trim(strip_tags($confirmation))) ? $confirmation : ''; // Replace tokens. module_load_include('inc', 'webform', 'includes/webform.submissions'); - $submission = webform_get_submission($vars['node']->nid, $vars['sid']); - $confirmation = webform_replace_tokens($confirmation, $vars['node'], $submission, NULL, TRUE); + $submission = webform_get_submission($node->nid, $vars['sid']); + $vars['confirmation_message'] = webform_replace_tokens($confirmation, $node, $submission, NULL, $node->webform['confirmation_format']); - // Strip out empty tags added by WYSIWYG editors if needed. - $vars['confirmation_message'] = strlen(trim(strip_tags($confirmation))) ? $confirmation : ''; + // URL back to form (or same page for in-block confirmations). + $vars['url'] = empty($node->webform_block) + ? url('node/'. $node->nid) + : url(current_path(), array('query' => drupal_get_query_parameters())); // Progress bar. $vars['progressbar'] = ''; - $page_labels = webform_page_labels($vars['node']); + $page_labels = webform_page_labels($node); $page_count = count($page_labels); - if ($vars['node']->webform['progressbar_include_confirmation'] && $page_count > 2) { + if ($node->webform['progressbar_include_confirmation'] && $page_count > 2) { $vars['progressbar'] = theme('webform_progressbar', array( - 'node' => $vars['node'], + 'node' => $node, 'page_num' => $page_count, 'page_count' => $page_count, 'page_labels' => $page_labels, @@ -3468,7 +3475,7 @@ function template_preprocess_webform_mail_message(&$vars) { global $user; $vars['user'] = $user; - $vars['ip_address'] = ip_address(); + $vars['ip_address'] = webform_ip_address($vars['node']); } /** @@ -3490,11 +3497,11 @@ function webform_element_title_display($element) { * Drupal forcibly adds IDs to all form elements, including those that do not * need them for any reason, such as the actions wrapper or submit buttons. We * use this process function wherever we wish to remove an ID from an element. - * Because #states require IDs, they are only removed if the states array is - * empty. + * Because #states and #ajax require IDs, they are only removed if the states + * and ajax arrays are empty. */ function webform_pre_render_remove_id($element) { - if (empty($element['#states'])) { + if (empty($element['#states']) && empty($element['#ajax'])) { $element['#id'] = NULL; // Removing array parents is required to prevent theme_container from adding // an empty ID attribute. @@ -3798,6 +3805,19 @@ function _webform_fetch_draft_sid($nid, $uid) { return $sid; } +/** + * Returns a new or cached WebformConditionals object for the specified node. + * + * @param object $node + * The loaded webform node. + * @returns object + * Object of type WebformConditionals, possibly with the conditionals already + * analyzed for dependencies. + */ +function webform_get_conditional_sorter($node) { + return WebformConditionals::factory($node); +} + /** * This function is deprecated! Use webform_replace_tokens() instead. * @@ -3822,15 +3842,17 @@ function _webform_filter_values($string, $node = NULL, $submission = NULL, $emai * If replacing tokens within the context of an e-mail, the Webform e-mail * settings array. * @param $sanitize - * Boolean value indicating if the results will be displayed as HTML output. - * This will be passed to token_replace(). If FALSE, the contents returned - * will be unsanitized. This will also result in all Webform submission tokens - * being returned as plain-text, without HTML markup, in preparation for - * e-mailing or other text-only purposes (default values, etc.) + * Boolean or format name value indicating if the results will be displayed as + * HTML output. If FALSE, the contents returned will be unsanitized. This will + * also result in all Webform submission tokens being returned as plain-text, + * without HTML markup, in preparation for e-mailing or other text-only + * purposes (default values, etc.). If TRUE, the tokens only are sanitized by + * token_replace. Otherwise $sanitize is the machine name of an import filter + * to be used with check_markup(). */ function webform_replace_tokens($string, $node = NULL, $submission = NULL, $email = NULL, $sanitize = FALSE) { // Don't do any filtering if the string is empty. - if (strlen(trim($string)) == 0) { + if (!strlen(trim($string))) { return $string; } @@ -3844,7 +3866,50 @@ function webform_replace_tokens($string, $node = NULL, $submission = NULL, $emai if ($email) { $token_data['webform-email'] = $email; } - return token_replace($string, $token_data, array('clear' => true, 'sanitize' => $sanitize)); + $clear = is_bool($sanitize); + $string = token_replace($string, $token_data, array('clear' => $clear, 'sanitize' => $sanitize === TRUE)); + if (!$clear) { + $string = webform_clear_tokens(check_markup($string, $sanitize)); + } + return $string; +} + +/** + * Removes tokens from string. + * + * Call this function in cases where you need to remove unreplaced tokens but + * can't call webform_replace_tokens() with the option $clear = TRUE. + * + * In some cases the function token_replace() in webform_replace_tokens() needs + * to be called with the option 'clear' => FALSE, to not remove input filters. + * For security reasons webform_replace_tokens() is called before + * check_markup(), where input filters get replaced. Tokens won't be replaced if + * there is no value provided. These tokens i.e. [current-page:query:*] needs to + * be removed to not show up in the output. + * + * @param string $text + * The text to have its tokens removed. + * @see token_replace() + */ +function webform_clear_tokens($text) { + if (empty($text)) { + return $text; + } + + $text_tokens = token_scan($text); + if (empty($text_tokens)) { + return $text; + } + + $replacements = array(); + foreach ($text_tokens as $type => $tokens) { + $replacements += array_fill_keys($tokens, ''); + } + + $tokens = array_keys($replacements); + $values = array_values($replacements); + + return str_replace($tokens, $values, $text); } /** @@ -4020,9 +4085,21 @@ function webform_page_labels($node, $form_state = array()) { */ function webform_variable_get($variable) { switch ($variable) { + case 'webform_blocks': + $result = variable_get('webform_blocks', array()); + break; + case 'webform_tracking_mode': + $result = variable_get('webform_tracking_mode', 'cookie'); + break; case 'webform_allowed_tags': $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img')); break; + case 'webform_email_address_format': + $result = variable_get('webform_email_address_format', 'long'); + break; + case 'webform_email_address_individual': + $result = variable_get('webform_email_address_individual', 0); + break; case 'webform_default_from_name': $result = variable_get('webform_default_from_name', variable_get('site_name', '')); break; @@ -4032,12 +4109,30 @@ function webform_variable_get($variable) { case 'webform_default_subject': $result = variable_get('webform_default_subject', t('Form submission from: [node:title]')); break; + case 'webform_email_replyto': + $result = variable_get('webform_email_replyto', TRUE); + break; + case 'webform_email_html_capable': + $result = variable_get('webform_email_html_capable', FALSE); + break; + case 'webform_default_format': + $result = variable_get('webform_default_format', 0); + break; + case 'webform_format_override': + $result = variable_get('webform_format_override', 0); + break; + case 'webform_email_select_max': + $result = variable_get('webform_email_select_max', 50); + break; case 'webform_node_types': $result = webform_node_types(); break; case 'webform_node_types_primary': $result = variable_get('webform_node_types_primary', array('webform')); break; + case 'webform_date_type': + $result = variable_get('webform_date_type', 'medium'); + break; case 'webform_export_format': module_load_include('inc', 'webform', 'includes/webform.export'); $options = webform_export_list(); @@ -4047,6 +4142,12 @@ function webform_variable_get($variable) { case 'webform_csv_delimiter': $result = variable_get('webform_csv_delimiter', '\t'); break; + case 'webform_export_wordwrap': + $result = variable_get('webform_export_wordwrap', 0); + break; + case 'webform_excel_legacy_exporter': + $result = variable_get('webform_excel_legacy_exporter', 0); + break; case 'webform_progressbar_style': $result = variable_get('webform_progressbar_style', array('progressbar_bar', 'progressbar_pagebreak_labels', 'progressbar_include_confirmation')); break; @@ -4059,6 +4160,15 @@ function webform_variable_get($variable) { case 'webform_table': $result = variable_get('webform_table', FALSE); break; + case 'webform_submission_access_control': + $result = variable_get('webform_submission_access_control', 1); + break; + case 'webform_update_batch_size': + $result = variable_get('webform_update_batch_size', 100); + break; + case 'webform_disabled_components': + $result = variable_get('webform_disabled_components', array()); + break; } return $result; } @@ -4104,33 +4214,47 @@ function theme_webform_token_help($variables) { return render($help); } +/** + * Convert a name into an identifier that is safe for machine names, classes, + * and other ASCII uses. + */ function _webform_safe_name($name) { $new = trim($name); - - // If transliteration is available, use it to convert names to ASCII. - if (function_exists('transliteration_get')) { - $new = transliteration_get($new, ''); - $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new); - } - else { - $new = str_replace( - array(' ', '-', '/', '€', 'ƒ', 'Š', 'Ž', 'š', 'ž', 'Ÿ', '¢', '¥', 'µ', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'à', 'á', 'â', 'ã', 'ä', 'å', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', 'Œ', 'œ', 'Æ', 'Ð', 'Þ', 'ß', 'æ', 'ð', 'þ'), - array('_', '_', '_', 'E', 'f', 'S', 'Z', 's', 'z', 'Y', 'c', 'Y', 'u', 'A', 'A', 'A', 'A', 'A', 'A', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'y', 'y', 'OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'), - $new); - } - + $new = _webform_transliterate($new); + $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new); $new = drupal_strtolower($new); $new = preg_replace('/[^a-z0-9_]/', '', $new); return $new; } +/** + * Transliterate common non-English characters to 7-bit ASCII. + */ +function _webform_transliterate($name) { + // If transliteration is available, use it to convert names to ASCII. + return function_exists('transliteration_get') + ? transliteration_get($name, '') + : str_replace(array('€', 'ƒ', 'Š', 'Ž', 'š', 'ž', 'Ÿ', '¢', '¥', 'µ', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'à', 'á', 'â', 'ã', 'ä', 'å', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', 'Œ', 'œ', 'Æ', 'Ð', 'Þ', 'ß', 'æ', 'ð', 'þ'), + array('E', 'f', 'S', 'Z', 's', 'z', 'Y', 'c', 'Y', 'u', 'A', 'A', 'A', 'A', 'A', 'A', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'y', 'y', 'OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'), + $name); +} + /** * Given an email address and a name, format an e-mail address. * + * The address can be the cid of a component with multiple values. When $single + * is true, a single address is return (the first of any multiples). When not + * true, an array of addresses is returned. + * + * Note that multiple names could be used with multiple addresses, but this + * capability is not currently possible with the webform UI. Separate names + * are only used with the From address, which is always single. + * * @param $address * The e-mail address. * @param $name - * The name to be used in the formatted address. + * The name to be used in the formatted address. If the address contains a + * name in 'Some Name <somename@example.com>' format, $name is ignored. * @param $node * The webform node if replacements will be done. * @param $submission @@ -4142,12 +4266,16 @@ function _webform_safe_name($name) { * multiple addresses. This is useful to ensure a single e-mail will be * returned for the "From" address. * @param $format - * The e-mail format, defaults to the site-wide setting. May be either "short" - * or "long". + * The e-mail format, defaults to the site-wide setting. May be "short", + * "long", or NULL for the system default. + * @param $mapping + * A mapping array to be applied to the address values. + * @return string|array + * The formatted e-mail address -- or addresses (if not $single) */ -function webform_format_email_address($address, $name, $node = NULL, $submission = NULL, $encode = TRUE, $single = TRUE, $format = NULL) { +function webform_format_email_address($address, $name, $node = NULL, $submission = NULL, $encode = TRUE, $single = TRUE, $format = NULL, $mapping = NULL) { if (!isset($format)) { - $format = variable_get('webform_email_address_format', 'long'); + $format = webform_variable_get('webform_email_address_format'); } if ($name == 'default') { @@ -4161,13 +4289,19 @@ function webform_format_email_address($address, $name, $node = NULL, $submission // Convert the FROM name to be the label of select lists. if (webform_component_implements($component['type'], 'options')) { $options = webform_component_invoke($component['type'], 'options', $component); - $name = isset($options[$name[0]]) ? $options[$name[0]] : $name; + foreach ($name as &$one_name) { + $name = isset($options[$one_name]) ? $options[$one_name] : $name; + } + unset($one_name); // Drop PHP reference. } } else { $name = t('Value of !component', array('!component' => $node->webform['components'][$name]['name'])); } } + elseif (!isset($name)) { + $name = ''; + } if ($address == 'default') { $address = webform_variable_get('webform_default_from_address'); @@ -4177,6 +4311,9 @@ function webform_format_email_address($address, $name, $node = NULL, $submission $values = $submission->data[$address]; $address = array(); foreach ($values as $value) { + if (isset($mapping) && isset($mapping[$value])) { + $value = $mapping[$value]; + } $address = array_merge($address, explode(',', $value)); } } @@ -4185,33 +4322,130 @@ function webform_format_email_address($address, $name, $node = NULL, $submission } } - // Convert arrays into a single value for From values. - if ($single) { - $address = is_array($address) ? reset($address) : $address; - $name = is_array($name) ? reset($name) : $name; + // Convert single values to an array to simplify processing. + $address = is_array($address) ? $address : explode(',', $address); + $name = is_array($name) ? $name : array($name); + $name_shortage = count($address) - count($name); + if ($name_shortage > 0) { + $name += array_fill(count($name), $name_shortage, $name[0]); } - // Address may be an array if a component value was used on checkboxes. - if (is_array($address)) { - foreach ($address as $key => $individual_address) { - $address[$key] = webform_replace_tokens($individual_address, $node, $submission); + foreach ($address as $key => $individual_address) { + $individual_address = webform_replace_tokens($individual_address, $node, $submission); + $email_parts = webform_parse_email_address($individual_address); + if ($format == 'long' && !empty($name[$key]) && !strlen($email_parts['name'])) { + $individual_name = $name[$key]; + $individual_name = webform_replace_tokens($individual_name, $node, $submission); + if ($encode) { + $individual_name = mime_header_encode($individual_name); + } + $individual_name = trim($individual_name); + $individual_address = '"' . $individual_name . '" <' . $individual_address . '>'; } + $address[$key] = $individual_address; } - else { - $address = webform_replace_tokens($address, $node, $submission); + + return $single ? reset($address) : $address; + +} + +/** + * Validates an email form element. + * + * @param string $emails + * An email or list of comma-seperated email addresses. Passed by reference. + * Empty emails will be eliminated, and mutiple addresses will be seperated + * with a comma and space. + * @param string $form_name + * The name of the form element to receive an error, in form_set_error format. + * @param boolean $allow_empty + * TRUE if optional. FALSE if required. + * @param boolean $allow_multiple + * TRUE if a list of emails is allowed. FALSE if only one. + * @param string $format + * 'short', 'long', or NULL (for default) format. Long format has a name and + * the address in angle brackets. + * @return integer|boolean + * The number of valid addresses found, or FALSE for an invalid email found. + */ +function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multiple, $format = NULL) { + $nr_valid = webform_valid_email_address($emails, $format); + if ($nr_valid === FALSE) { + form_set_error($form_name, t('The entered e-mail address "@email" does not appear valid.', array('@email' => $emails))); + } + elseif ($nr_valid === 0 && !$allow_empty) { + form_set_error($form_name, t('When adding a new custom e-mail, the e-mail field is required.')); + } + elseif ($nr_valid > 1 && !$allow_multiple) { + form_set_error($form_name, t('Only one e-mail address is allowed.')); } + return $nr_valid; +} - if ($format == 'long' && !empty($name)) { - $name = webform_replace_tokens($name, $node, $submission); - if ($encode) { - $name = mime_header_encode($name); +/** + * Validates email address(es) with optional name(s). + * + * @param string $emails + * An email address, a list of comma-separated email addresses. If all the + * addresses are valid, the list of trimmed, non-empty emails is returned by + * reference. + * @param string $format + * 'short', 'long', or NULL (for default) format. Long format has a name and + * the address in angle brackets. + * @return boolean|integer + * Returns FALSE if an invalid e-mail address was found, 0 if no email + * address(es) were found, or the number of valid e-mail addresses found. + */ +function webform_valid_email_address(&$emails, $format = NULL) { + $email_array = array_filter(array_map('trim', explode(',', $emails))); + $count = 0; + foreach ($email_array as $email) { + $matches = webform_parse_email_address($email, $format); + if (!valid_email_address($matches['address'])) { + return FALSE; } - $name = trim($name); - return '"' . $name . '" <' . $address . '>'; + $count++; } - else { - return $address; + $emails = implode(', ', $email_array); + return $count; +} + +/** + * Parses an e-mail address into name and address. + * + * @param string $email + * The email address to be parsed, with an optional name. + * @param string $format + * 'short', 'long', or NULL (for default) format. Long format has a name and + * the address in angle brackets. + * @return array + * Associative array indexed by 'name' and 'address'. + */ +function webform_parse_email_address($email, $format = NULL) { + if (!$format) { + $format = webform_variable_get('webform_email_address_format'); + } + if ($format == 'long') { + // Match e-mails of the form 'My Name <email@domain.com>' as follows: + // ^ = beginning of string + // "? = optional quote + // ([^<]*?) = match optional characters that aren't a < (non-greedy) + // "? = optional quote + // SPACE* = optional spaces + // (?:<(.*)>) = < matching stuff > (without the angle brakets) + // $ = end of string + preg_match('/^"?([^<]*?)"? *(?:<(.*)>)?$/', $email, $matches); + if (isset($matches[2]) && strlen($matches[2])) { + return array( + 'name' => $matches[1], + 'address' => $matches[2], + ); + } } + return array( + 'name' => '', + 'address' => $email, + ); } /** @@ -4319,11 +4553,11 @@ function _webform_components_tree_sort($tree) { * Get a list of all available component definitions. */ function webform_components($include_disabled = FALSE, $reset = FALSE) { - static $components, $disabled; + static $components, $enabled; if (!isset($components) || $reset) { $components = array(); - $disabled = array_flip(variable_get('webform_disabled_components', array())); + $disabled = array_flip(webform_variable_get('webform_disabled_components')); foreach (module_implements('webform_component_info') as $module) { $module_components = module_invoke($module, 'webform_component_info'); foreach ($module_components as $type => $info) { @@ -4333,10 +4567,13 @@ function webform_components($include_disabled = FALSE, $reset = FALSE) { $components += $module_components; } drupal_alter('webform_component_info', $components); - ksort($components); + uasort($components, function($a, $b) { + return strnatcasecmp($a['label'], $b['label']); + }); + $enabled = array_diff_key($components, $disabled); } - return $include_disabled ? $components : array_diff_key($components, $disabled); + return $include_disabled ? $components : $enabled; } /** @@ -4440,30 +4677,65 @@ function webform_disable_page_cache() { /** * Set the necessary breadcrumb for the page we are on. + * + * @param object $node + * The loaded webform node. + * @param boolean|object $submission + * The submission if the current page is viewing or dealing with a submission, + * or TRUE to just include the webform node in the breadcrumbs (used for + * the submission completion confirmation page), or NULL for no extra + * processing */ function webform_set_breadcrumb($node, $submission = NULL) { - $breadcrumb = drupal_get_breadcrumb(); - - if (isset($node)) { - $webform_breadcrumb = array(); - $webform_breadcrumb[] = empty($breadcrumb) ? l(t('Home'), '<front>') : array_shift($breadcrumb); - $webform_breadcrumb[] = $node_link = l($node->title, 'node/' . $node->nid); - if (isset($submission)) { - $last_link = array_shift($breadcrumb); + $node_path = "node/{$node->nid}"; + + // Set the href of the current menu item to be the node's path. This has two + // effects. The active trail will be to the node's prefered menu tree + // location, expanding the menu as appropriate. And the breadcrumbs will be + // set as if the current page were under the node's preferred location. + // Note that menu_tree_set_path() could be used to set the path for the menu, + // but it will not affect the breadcrumbs when the webform is not in the + // default menu. + menu_set_item(NULL, array('href' => $node_path) + menu_get_item()); + + if ($submission) { + $breadcrumb = menu_get_active_breadcrumb(); + + // Append the node title (or its menu name), in case it isn't in the path already. + $active_trail = menu_get_active_trail(); + $last_active = end($active_trail); + $breadcrumb[] = $last_active['href'] === $node_path && !empty($last_active['in_active_trail']) + ? l($last_active['title'], $node_path, $last_active['localized_options']) + : l($node->title, $node_path); + + // Setting the current menu href will cause the submission title and current + // tab (if not the default tab) to be added to the active path when the + // webform is in the default location in the menu (node/NID). The title + // is desirable, but the tab name (e.g. Edit or Delete) isn't. + if (preg_match('/href=".*"/', end($breadcrumb), $matches)) { + foreach ($breadcrumb as $index => $link) { + if (stripos($link, $matches[0]) !== FALSE) { + $breadcrumb = array_slice($breadcrumb, 0, $index + 1); + break; + } + } + } + + // If the user is dealing with a submission, then the breadcrumb should + // be fudged to allow them to return to a likely list of webforms. + // Note that this isn't necessarily where they came from, but it's the + // best guess available. + if (is_object($submission)) { if (webform_results_access($node)) { - $webform_breadcrumb[] = l(t('Webform results'), 'node/' . $node->nid . '/webform-results'); + $breadcrumb[] = l(t('Webform results'), $node_path . '/webform-results'); } elseif (user_access('access own webform results')) { - $webform_breadcrumb[] = l(t('Submissions'), 'node/' . $node->nid . '/submissions'); - } - if (isset($last_link) && $last_link != $node_link) { - $webform_breadcrumb[] = $last_link; + $breadcrumb[] = l(t('Submissions'), $node_path . '/submissions'); } } - $breadcrumb = $webform_breadcrumb; - } - drupal_set_breadcrumb($breadcrumb); + drupal_set_breadcrumb($breadcrumb); + } } /** @@ -4554,30 +4826,73 @@ function webform_date_string($array, $type = NULL) { /** * Get a date format according to the site settings. * - * @param $size - * A choice of 'short', 'medium', or 'long' date formats. + * @param $type + * A choice of 'short', 'medium', 'long' , or other user-defined date formats. + * Use NULL for the webform-specific date format choosen in the webform + * settings. + * @param array $exclude + * An array containing 'day', 'month', and/or 'year' if they should be + * removed from the format. + * @return string + * A date/time format string. */ -function webform_date_format($size = 'medium') { +function webform_date_format($type = NULL, $exclude = array()) { + static $formats = array(); + $id = $type . ':' . implode('', $exclude); + if (!isset($formats[$id])) { + $type_name = $type ? $type : webform_variable_get('webform_date_type'); + // Format date according to site's given format. - $format = variable_get('date_format_' . $size, 'D, m/d/Y - H:i'); - $time = 'aABgGhHisueIOPTZ'; - $day_of_week = 'Dlw'; - $special = ',-: '; - $date_format = trim($format, $time . $day_of_week . $special); + $format = variable_get('date_format_' . $type_name, 'D, m/d/Y'); + + // Date/Time formatting characters + // WHAT REQUIRED (at least 1) OPTIONAL (allowed but insufficient) + // --------------------------------------------------------------------------- + // Day-of-week DlNw + // Day dj Stz + // Month FmMn + // Year oYy L + // + // NOT ALLOWED + // -------------------------------------------------------------------------- + // Time aABgGhHisueIOPTZ + // Special /.,-: <space> + + // Strip Time and Special characters from the beginning and end of format. + $date_format = trim($format, 'aABgGhHisueIOPTZ/.,-: '); // Ensure that a day, month, and year value are present. Use a default - // format if all the values are not found. - if (!preg_match('/[dj]/', $date_format) || !preg_match('/[FmMn]/', $date_format) || !preg_match('/[oYy]/', $date_format)) { + // format if all the values are not found. This regular expression uses + // (?= ), the positive lookahead assertion. It asserts that there are some + // optional characters (.*) followed by one of the day, month, or year + // characters. Because it is an assertion, it doesn't consume the + // characters, so the day, month, and year can be in any order. + if (!preg_match('/(?=.*[dj])(?=.*[FmMn])(?=.*[oYy])/', $date_format)) { $date_format = 'm/d/Y'; } - return $date_format; + // Remove any excluded portions. + $strip = array( + 'day' => 'DlNwdjStz', + 'month' => 'FmMn', + 'year' => 'oYyL', + ); + foreach ($exclude as $field) { + // Strip the format and any trailing /.,-: or space. + $date_format = preg_replace('#[' . $strip[$field] . ']+[/\.,\-: ]*#', '', $date_format); + $date_format = trim($date_format, '/.,-: '); + } + + $formats[$id] = $date_format; + } + + return $formats[$id]; } /** * Return a date in the desired format taking into consideration user timezones. */ -function webform_strtodate($format, $string, $timezone_name = NULL) { +function webform_strtodate($format, $string, $timezone_name = NULL, $reference_timestamp = NULL) { global $user; // Adjust the time based on the user or site timezone. @@ -4594,7 +4909,23 @@ function webform_strtodate($format, $string, $timezone_name = NULL) { // only supported for DateTime in PHP 5.3 and higher. try { @$timezone = new DateTimeZone($timezone_name); - @$datetime = new DateTime($string, $timezone); + if (isset($reference_timestamp)) { + // A reference for relative dates has been provided. + // 1) Convert the reference timestamp (in UTC) to a DateTime. + // 2) Set to time zone to the user or system timezone, recreating + // the reference time in the appropriate time zone. + // 3) Set the time to midnight because when a non-referenced relative + // date is created without a time, it is created at midnight (0:00). + // 4) Adjust to the specified relative (or absolute) time. + + @$datetime = new DateTime('@' . $reference_timestamp); + @$datetime->setTimezone($timezone) + ->setTime(0, 0, 0) + ->modify($string); + } + else { + @$datetime = new DateTime($string, $timezone); + } return @$datetime->format($format); } catch (Exception $e) { @@ -4602,7 +4933,7 @@ function webform_strtodate($format, $string, $timezone_name = NULL) { } } else { - return date($format, strtotime($string)); + return date($format, isset($reference_timestamp) ? strtotime($string, $reference_timestamp) : strtotime($string)); } } @@ -4634,54 +4965,10 @@ function webform_tt($name, $string, $langcode = NULL, $update = FALSE) { } /** - * Check if there are any HTML mail systems available for Webform to use. - * - * @return bool - * TRUE if Webform can send HTML emails, FALSE if not. + * Returns an IP Address or anonymized IP Address for confidential webforms. */ -function webform_email_html_capable() { - $capable = &drupal_static(__FUNCTION__); - if (isset($capable)) { - return $capable; - } - // Build a list of HTML-capable mail systems. - $systems = array(); - if (module_exists('mandrill')) { - $systems[] = 'MandrillMailSystem'; - } - if (module_exists('mimemail')) { - $systems[] = 'MimeMailSystem'; - $systems[] = 'MimeMailSystem__SmtpMailSystem'; - } - if (module_exists('htmlmail')) { - $systems[] = 'HTMLMailSystem'; - $systems[] = 'HTMLMailSystem__SmtpMailSystem'; - } - if (module_exists('mailsystem')) { - $systems[] = 'MailsystemDelegateMailSystem'; - } - // Allow other modules to alter the list. - drupal_alter('webform_html_capable_mail_systems', $systems); - - if (!count($systems)) { - $capable = FALSE; - return FALSE; - } - $mail_systems = variable_get('mail_system', array('default-system' => 'DefaultMailSystem')); - // If an HTML mail system exists then we will use it for Webform emails, even - // if it's not already specified. - if (!isset($mail_systems['webform']) || !in_array($mail_systems['webform'], $systems)) { - // Use the system default system, if that's HTML capable. - if (in_array($mail_systems['default-system'], $systems)) { - $GLOBALS['conf']['mail_system']['webform'] = $mail_systems['default-system']; - } - // Otherwise, use the first available HTML-capable system. - else { - $GLOBALS['conf']['mail_system']['webform'] = reset($systems); - } - } - $capable = TRUE; - return TRUE; +function webform_ip_address($node) { + return $node->webform['confidential'] ? t('(unknown)') : ip_address(); } /** @@ -4900,3 +5187,140 @@ function webform_clone_node_alter(&$node, $context) { $node->webform['next_serial'] = $defaults['next_serial']; } } + +/** + * Check if the last form submission exceeded the servers max_input_vars + * limit and optionally preflight the current form to be returned in this + * request. + * + * @param array $form + * Reference to the form, which will be changed if $parent_key is set. + * @param array $form_state + * Form's state or NULL for no form state check. + * @param string $detect_key + * A key that will always be present in the posted data when an actual form + * submission has been made. + * @param string parent_key + * Omit to not preflight the form, or the array key for the parent of where + * the preflight warning should be inserted into the form. + */ +function webform_input_vars_check(&$form, $form_state, $detect_key, $parent_key = NULL) { + if (isset($parent_key)) { + $form['#pre_render'] = array('webform_pre_render_input_vars'); + $form['#input_var_waring_parent'] = $parent_key; + } + if (!empty($form_state['input']) && key_exists($detect_key, $form_state['input']) && !key_exists('form_id', $form_state['input'])) { + // A form was submitted with POST, but the form_id was missing. The most likely cause of this + // is that the POST was truncated because PHP exceeded its max_input_vars limit. + $subs = array( + '@count' => webform_count_terminals($_POST), + '@limit' => (int)ini_get('max_input_vars'), + ); + drupal_set_message(user_access('administer site configuration') + ? t('This form could not be submitted because $_POST was truncated to @count input vars. PHP max_input_vars is @limit and needs to be increased.', $subs) + : t('This form could not be submitted because it exceeds the server configuration. Contact the administrator.'), + 'error'); + watchdog('webform', + 'POST truncated to @count input vars. PHP max_input_vars is @limit. Increase max_input_vars.', + $subs, + WATCHDOG_ERROR); + } +} + +/** + * Checks the number of input form elements on this page to ensure that the + * PHP max_input_vars limit is not exceeded. + * + * Install this function as a #pre_render function. + */ +function webform_pre_render_input_vars($element) { + // Determine the limit on input vars for this server configuration. + $limit = ini_get('max_input_vars'); + if ($limit) { + // Estimate the number of input vars needed to see if the PHP limit has been exceeded. + $count = 1 + webform_count_input_vars($element); // Additional input_vars: op + if ($count > $limit * 0.95) { + $subs = array( + '@count' => $count, + '@limit' => $limit, + ); + $warning = array( + '#markup' => '<div class="messages warning">' . + (user_access('administer site configuration') + ? t('This form contains @count input elements. PHP max_input_vars is @limit and should be increased.', $subs) + : t('This form may be too long to work properly. Contact the administrator.')) + . '</div>', + '#weight' => -1, + ); + if ($element['#input_var_waring_parent']) { + $element[$element['#input_var_waring_parent']]['input_vars_warning'] = $warning; + } + else { + $element['input_vars_warning'] = $warning; + } + watchdog('webform', + 'Page contains @count input elements but PHP max_input_vars is only @limit. Increase max_input_vars.', + $subs, + WATCHDOG_ERROR); + } + } + return $element; +} + +/** + * Counts the number of input form elements. + * + * Note that this is somewhat imprecise. The number of input vars returned in + * $_POST can vary with the form element. For example, a multiple-select + * listbox returns one input var for each selection actually made. + * + * The primary use for this count is for the conditionals page, where only + * select, textfield, hidden, and token elements are used. If a more accurate + * count for webform_client_form is needed, a mechanism to predict the number + * of input elements for each component type and each component instance would + * be needed. + * + * @param array $element + * The form whose elements should be counted. + * @return integer + * The number of elements in the form that will result in $_POST entries. + */ +function webform_count_input_vars($element) { + static $input_types = array( + 'checkbox' => 1, + 'date' => 1, + 'file' => 1, + 'managed_file' => 1, + 'password' => 1, + 'password_confirm' => 1, + 'radios' => 1, + 'select' => 1, + 'textfield' => 1, + 'textarea' => 1, + 'token' => 1, + 'weight' => 1, + 'hidden' => 1, + 'value' => 1, + 'webform_email' => 1, + 'webform_number' => 1, + ); + $children = array_intersect_key($element, array_flip(element_children($element))); + return $children + ? array_reduce($children, function($carry, $item) {return $carry + webform_count_input_vars($item);}, 0) + : (isset($element['#type']) && isset($input_types[$element['#type']]) ? $input_types[$element['#type']] : 0); +} + +/** + * Counts terminals in an array. Useful for counting how many input_vars were + * returned in $_POST. + * + * @params $a + * Array or array element to be counted + * @return integer + * Number of non-array elements within $a. + */ +function webform_count_terminals($a) { + return is_array($a) + ? array_reduce($a, function($carry, $item) {return $carry + webform_count_terminals($item);}, 0) + : 1; +} diff --git a/profiles/wcm_base/modules/contrib/webform/webform.tokens.inc b/profiles/wcm_base/modules/contrib/webform/webform.tokens.inc index 575c0113..658177b7 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.tokens.inc +++ b/profiles/wcm_base/modules/contrib/webform/webform.tokens.inc @@ -24,9 +24,23 @@ function webform_token_info() { 'name' => t('Submission ID'), 'description' => t('The unique indentifier for the webform submission.'), ); + $info['tokens']['submission']['access-token'] = array( + 'name' => t('Access token'), + 'description' => t('The security token used to gain access to this webform submission.'), + ); $info['tokens']['submission']['date'] = array( 'name' => t('Date submitted'), - 'description' => t('The date the webform was submitted.'), + 'description' => t('The date the webform was first save as draft or completed.'), + 'type' => 'date', + ); + $info['tokens']['submission']['completed_date'] = array( + 'name' => t('Date completed'), + 'description' => t('The date the webform was first completed (not draft).'), + 'type' => 'date', + ); + $info['tokens']['submission']['modified_date'] = array( + 'name' => t('Date modified'), + 'description' => t('The date the webform was last saved (draft or completed).'), 'type' => 'date', ); $info['tokens']['submission']['ip-address'] = array( @@ -50,7 +64,13 @@ function webform_token_info() { ); $info['tokens']['submission']['values'] = array( 'name' => t('Webform submission values'), - 'description' => t('Webform tokens from submitted data. Replace the "?" with the "field key", including any parent field keys separated by colons. You may append ":label" for just the label or ":withlabel" for both the label and value together. Append ":key" for just the key in a key|label pair.'), + 'description' => t('<div>Webform tokens from submitted data. Replace the "?" with the "field key", including any parent field keys separated by colons. You can append:</div><ul>' . + '<li>the question key for just that one question (grid components).</li>' . + '<li>the option key for just that one option (grid and select components).' . + '<li>":nolabel" for the value without the label (the default)</li>' . + '<li>":label" for just the label.</li>' . + '<li>":withlabel" for both the label and value together.</li>' . + '<li>":key" for just the key in a key|label pair (grid and select components).</li></ul>'), 'dynamic' => TRUE, ); @@ -92,8 +112,19 @@ function webform_tokens($type, $tokens, array $data = array(), array $options = case 'sid': $replacements[$original] = $submission->sid ? $submission->sid : ''; break; + case 'access-token': + $replacements[$original] = webform_get_submission_access_token($submission); + break; case 'date': - $replacements[$original] = format_date($submission->submitted, 'medium', '', NULL, $language_code); + $replacements[$original] = format_date($submission->submitted, webform_variable_get('webform_date_type'), '', NULL, $language_code); + break; + case 'completed_date': + if ($submission->completed) { + $replacements[$original] = format_date($submission->completed, webform_variable_get('webform_date_type'), '', NULL, $language_code); + } + break; + case 'modified_date': + $replacements[$original] = format_date($submission->modified, webform_variable_get('webform_date_type'), '', NULL, $language_code); break; case 'ip-address': $replacements[$original] = $sanitize ? check_plain($submission->remote_addr) : $submission->remote_addr; @@ -151,19 +182,29 @@ function webform_tokens($type, $tokens, array $data = array(), array $options = // not displayed when printing the whole renderable. $display_element['#access'] = TRUE; - // For grid components, see if optional option key is present. + // For grid components, see if optional question key is present. $matched_token = $parent_token; if ($display_element['#webform_component']['type'] === 'grid') { - list($option_key) = explode(':', substr($name, strlen($parent_token) + 1)); - if (strlen($option_key) && isset($display_element[$option_key]['#value'])) { + list($question_key) = explode(':', substr($name, strlen($matched_token) + 1)); + if (strlen($question_key) && isset($display_element[$question_key]['#value'])) { // Generate a faux select component for this grid question. $select_component = _webform_defaults_select(); $select_component['type'] = 'select'; $select_component['nid'] = $display_element['#webform_component']['nid']; - $select_component['name'] = $display_element['#grid_questions'][$option_key]; + $select_component['name'] = $display_element['#grid_questions'][$question_key]; $select_component['extra']['items'] = $display_element['#webform_component']['extra']['options']; - $display_element = _webform_display_select($select_component, $display_element[$option_key]['#value'], $format); + $display_element = _webform_display_select($select_component, $display_element[$question_key]['#value'], $format); $display_element['#webform_component'] = $select_component; + $matched_token .= ':' . $question_key; + } + } + + // For select components, see if the optional option key is present. + if ($display_element['#webform_component']['type'] === 'select') { + list($option_key) = explode(':', substr($name, strlen($matched_token) + 1)); + if (strlen($option_key) && strpos("\n" . $display_element['#webform_component']['extra']['items'], "\n" . $option_key . '|') !== FALSE) { + // Return only this specified option and no other values. + $display_element['#value'] = array_intersect($display_element['#value'], array($option_key)); $matched_token .= ':' . $option_key; } } @@ -208,6 +249,12 @@ function webform_tokens($type, $tokens, array $data = array(), array $options = if ($date_tokens = token_find_with_prefix($tokens, 'date')) { $replacements += token_generate('date', $date_tokens, array('date' => $submission->submitted), $options); } + if ($submission->completed && ($date_tokens = token_find_with_prefix($tokens, 'completed_date'))) { + $replacements += token_generate('date', $date_tokens, array('date' => $submission->completed), $options); + } + if ($date_tokens = token_find_with_prefix($tokens, 'modified_date')) { + $replacements += token_generate('date', $date_tokens, array('date' => $submission->modified), $options); + } if (($user_tokens = token_find_with_prefix($tokens, 'user')) && $account = user_load($submission->uid)) { $replacements += token_generate('user', $user_tokens, array('user' => $account), $options); } diff --git a/profiles/wcm_base/modules/contrib/workbench/workbench.info b/profiles/wcm_base/modules/contrib/workbench/workbench.info index d7cbf84f..1b7e04c1 100644 --- a/profiles/wcm_base/modules/contrib/workbench/workbench.info +++ b/profiles/wcm_base/modules/contrib/workbench/workbench.info @@ -4,3 +4,11 @@ package = Workbench core = 7.x configure = admin/config/workbench/settings dependencies[] = views + + +; Information added by drush on 2015-05-19 +version = "7.x-1.2" +core = "7.x" +project = "workbench" +datestamp = "1432070348" + diff --git a/profiles/wcm_base/modules/contrib/workbench_media/workbench_media.info b/profiles/wcm_base/modules/contrib/workbench_media/workbench_media.info index 9eed11b0..16a65632 100644 --- a/profiles/wcm_base/modules/contrib/workbench_media/workbench_media.info +++ b/profiles/wcm_base/modules/contrib/workbench_media/workbench_media.info @@ -4,3 +4,11 @@ package = Workbench core = 7.x dependencies[] = workbench dependencies[] = media + + +; Information added by drush on 2015-05-19 +version = "7.x-2.1" +core = "7.x" +project = "workbench_media" +datestamp = "1432070350" + diff --git a/profiles/wcm_base/modules/contrib/workbench_moderation/tests/workbench_moderation_test.info b/profiles/wcm_base/modules/contrib/workbench_moderation/tests/workbench_moderation_test.info index 0f9e0139..b18fff76 100644 --- a/profiles/wcm_base/modules/contrib/workbench_moderation/tests/workbench_moderation_test.info +++ b/profiles/wcm_base/modules/contrib/workbench_moderation/tests/workbench_moderation_test.info @@ -3,3 +3,11 @@ description = Test module for Workbench Moderation. package = Workbench core = 7.x hidden = TRUE + + +; Information added by drush on 2015-05-19 +version = "7.x-1.4+6-dev" +core = "7.x" +project = "workbench_moderation" +datestamp = "1432070351" + diff --git a/profiles/wcm_base/modules/contrib/workbench_moderation/workbench_moderation.info b/profiles/wcm_base/modules/contrib/workbench_moderation/workbench_moderation.info index 3f9939f1..857d42cc 100644 --- a/profiles/wcm_base/modules/contrib/workbench_moderation/workbench_moderation.info +++ b/profiles/wcm_base/modules/contrib/workbench_moderation/workbench_moderation.info @@ -16,3 +16,11 @@ files[] = workbench_moderation.migrate.inc files[] = tests/external_node_update.test files[] = tests/workbench_moderation.test files[] = tests/workbench_moderation.files.test + + +; Information added by drush on 2015-05-19 +version = "7.x-1.4+6-dev" +core = "7.x" +project = "workbench_moderation" +datestamp = "1432070351" + diff --git a/profiles/wcm_base/modules/custom/ocio_front_page/ocio_front_page.info b/profiles/wcm_base/modules/custom/ocio_front_page/ocio_front_page.info index 075b9d1a..6c97d636 100644 --- a/profiles/wcm_base/modules/custom/ocio_front_page/ocio_front_page.info +++ b/profiles/wcm_base/modules/custom/ocio_front_page/ocio_front_page.info @@ -13,4 +13,3 @@ features[ctools][] = strongarm:strongarm:1 features[features_api][] = api:2 features[page_manager_pages][] = front_page features[variable][] = site_frontpage -mtime = 1423497373 diff --git a/profiles/wcm_base/modules/custom/ocio_front_page/ocio_front_page.pages_default.inc b/profiles/wcm_base/modules/custom/ocio_front_page/ocio_front_page.pages_default.inc index f54e3c6d..a61eed7e 100644 --- a/profiles/wcm_base/modules/custom/ocio_front_page/ocio_front_page.pages_default.inc +++ b/profiles/wcm_base/modules/custom/ocio_front_page/ocio_front_page.pages_default.inc @@ -73,156 +73,6 @@ function ocio_front_page_default_page_manager_pages() { $display->uuid = '94dfb6d1-435f-475f-a373-a8115ccafaea'; $display->content = array(); $display->panels = array(); - $pane = new stdClass(); - $pane->pid = 'new-888d16ef-7df1-48f7-9681-603dfe1f2c04'; - $pane->panel = 'contentmain'; - $pane->type = 'fieldable_panels_pane'; - $pane->subtype = 'fpid:1'; - $pane->shown = TRUE; - $pane->access = array(); - $pane->configuration = array( - 'view_mode' => 'Full', - ); - $pane->cache = array(); - $pane->style = array(); - $pane->css = array(); - $pane->extras = array(); - $pane->position = 0; - $pane->locks = array(); - $pane->uuid = '888d16ef-7df1-48f7-9681-603dfe1f2c04'; - $display->content['new-888d16ef-7df1-48f7-9681-603dfe1f2c04'] = $pane; - $display->panels['contentmain'][0] = 'new-888d16ef-7df1-48f7-9681-603dfe1f2c04'; - $pane = new stdClass(); - $pane->pid = 'new-bbb55cfa-bec0-407d-824c-267f832834e6'; - $pane->panel = 'contentmain'; - $pane->type = 'fieldable_panels_pane'; - $pane->subtype = 'fpid:5'; - $pane->shown = TRUE; - $pane->access = array(); - $pane->configuration = array( - 'view_mode' => 'Full', - ); - $pane->cache = array(); - $pane->style = array(); - $pane->css = array(); - $pane->extras = array(); - $pane->position = 1; - $pane->locks = array(); - $pane->uuid = 'bbb55cfa-bec0-407d-824c-267f832834e6'; - $display->content['new-bbb55cfa-bec0-407d-824c-267f832834e6'] = $pane; - $display->panels['contentmain'][1] = 'new-bbb55cfa-bec0-407d-824c-267f832834e6'; - $pane = new stdClass(); - $pane->pid = 'new-59c3a414-6d59-4cfe-91ad-f0adb3954471'; - $pane->panel = 'footer'; - $pane->type = 'views_panes'; - $pane->subtype = 'ocio_news_archive-teasers_pane'; - $pane->shown = TRUE; - $pane->access = array(); - $pane->configuration = array( - 'items_per_page' => '3', - 'override_title' => '', - 'override_title_text' => '', - 'view_settings' => 'fields', - 'header_type' => 'none', - 'view_mode' => 'teaser', - 'widget_title' => 'News Teaser Feed', - ); - $pane->cache = array(); - $pane->style = array(); - $pane->css = array(); - $pane->extras = array(); - $pane->position = 0; - $pane->locks = array(); - $pane->uuid = '59c3a414-6d59-4cfe-91ad-f0adb3954471'; - $display->content['new-59c3a414-6d59-4cfe-91ad-f0adb3954471'] = $pane; - $display->panels['footer'][0] = 'new-59c3a414-6d59-4cfe-91ad-f0adb3954471'; - $pane = new stdClass(); - $pane->pid = 'new-92d719bb-4177-49f6-89ee-250a989a2fa7'; - $pane->panel = 'sidebar'; - $pane->type = 'views_panes'; - $pane->subtype = 'ocio_news_archive-titles_pane'; - $pane->shown = TRUE; - $pane->access = array(); - $pane->configuration = array( - 'items_per_page' => '8', - 'override_title' => '', - 'override_title_text' => '', - 'view_settings' => 'fields', - 'header_type' => 'none', - 'view_mode' => 'teaser', - 'widget_title' => 'Recent News', - ); - $pane->cache = array(); - $pane->style = array(); - $pane->css = array(); - $pane->extras = array(); - $pane->position = 0; - $pane->locks = array(); - $pane->uuid = '92d719bb-4177-49f6-89ee-250a989a2fa7'; - $display->content['new-92d719bb-4177-49f6-89ee-250a989a2fa7'] = $pane; - $display->panels['sidebar'][0] = 'new-92d719bb-4177-49f6-89ee-250a989a2fa7'; - $pane = new stdClass(); - $pane->pid = 'new-286dc6ed-b56d-477a-a3f2-292058d2c062'; - $pane->panel = 'sidebar'; - $pane->type = 'views_panes'; - $pane->subtype = 'panopoly_widgets_general_content-list_of_content'; - $pane->shown = TRUE; - $pane->access = array(); - $pane->configuration = array( - 'items_per_page' => '3', - 'fields_override' => array( - 'field_featured_image' => 0, - 'title' => 1, - 'created' => 0, - 'name' => 0, - ), - 'exposed' => array( - 'type' => 'faq', - 'sort_order' => 'DESC', - 'sort_by' => 'created', - ), - 'override_title' => '', - 'override_title_text' => '', - 'view_settings' => 'fields', - 'header_type' => 'none', - 'view_mode' => 'teaser', - 'widget_title' => 'FAQs', - ); - $pane->cache = array(); - $pane->style = array(); - $pane->css = array(); - $pane->extras = array(); - $pane->position = 1; - $pane->locks = array(); - $pane->uuid = '286dc6ed-b56d-477a-a3f2-292058d2c062'; - $display->content['new-286dc6ed-b56d-477a-a3f2-292058d2c062'] = $pane; - $display->panels['sidebar'][1] = 'new-286dc6ed-b56d-477a-a3f2-292058d2c062'; - $pane = new stdClass(); - $pane->pid = 'new-86dd92e0-3723-4bc0-ac69-0e38f3819721'; - $pane->panel = 'sidebar'; - $pane->type = 'node'; - $pane->subtype = 'node'; - $pane->shown = TRUE; - $pane->access = array(); - $pane->configuration = array( - 'nid' => '23', - 'links' => 1, - 'leave_node_title' => 0, - 'identifier' => '', - 'build_mode' => 'full', - 'link_node_title' => 0, - 'override_title' => 1, - 'override_title_text' => 'Example Existing Node', - ); - $pane->cache = array(); - $pane->style = array(); - $pane->css = array(); - $pane->extras = array(); - $pane->position = 2; - $pane->locks = array(); - $pane->uuid = '86dd92e0-3723-4bc0-ac69-0e38f3819721'; - $display->content['new-86dd92e0-3723-4bc0-ac69-0e38f3819721'] = $pane; - $display->panels['sidebar'][2] = 'new-86dd92e0-3723-4bc0-ac69-0e38f3819721'; $display->hide_title = PANELS_TITLE_NONE; $display->title_pane = '0'; $handler->conf['display'] = $display; diff --git a/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.features.user_permission.inc b/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.features.user_permission.inc index 8f24440e..679df27c 100644 --- a/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.features.user_permission.inc +++ b/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.features.user_permission.inc @@ -2689,6 +2689,10 @@ function ocio_permissions_user_default_permissions() { 'name' => 'use manualcrop', 'roles' => array( 'administrator' => 'administrator', + 'contributor' => 'contributor', + 'editor' => 'editor', + 'site builder' => 'site builder', + 'site manager' => 'site manager', ), 'module' => 'manualcrop', ); diff --git a/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.info b/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.info index 5e7c8ae0..f874debf 100644 --- a/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.info +++ b/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.info @@ -317,7 +317,6 @@ features[user_permission][] = override web_form sticky option features[user_permission][] = rebuild tablefield features[user_permission][] = rename features features[user_permission][] = revert revisions -features[user_permission][] = save draft features[user_permission][] = search content features[user_permission][] = switch shortcut sets features[user_permission][] = switch users diff --git a/profiles/wcm_base/modules/custom/ocio_search/ocio_search.make b/profiles/wcm_base/modules/custom/ocio_search/ocio_search.make new file mode 100644 index 00000000..80b069b6 --- /dev/null +++ b/profiles/wcm_base/modules/custom/ocio_search/ocio_search.make @@ -0,0 +1,16 @@ +; OCIO Search Makefile + +api = 2 +core = 7.x + + +;modules + +projects[apachesolr][version] = 1.7 +projects[apachesolr][subdir] = contrib + +projects[apachesolr_file][version] = 1.x-dev +projects[apachesolr_file][subdir] = contrib + +projects[search_api_attachments][version] = 1.3 +projects[search_api_attachments][subdir] = contrib diff --git a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.user_permission.inc b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.user_permission.inc index 73d8d1b5..ec97df6b 100644 --- a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.user_permission.inc +++ b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.user_permission.inc @@ -237,15 +237,6 @@ function ocio_user_config_user_default_permissions() { 'module' => 'administerusersbyrole', ); - // Exported permission: 'import users'. - $permissions['import users'] = array( - 'name' => 'import users', - 'roles' => array( - 'administrator' => 'administrator', - ), - 'module' => 'user_import', - ); - // Exported permission: 'select account cancellation method'. $permissions['select account cancellation method'] = array( 'name' => 'select account cancellation method', diff --git a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.info b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.info index c00b14af..fbfed419 100644 --- a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.info +++ b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.info @@ -5,11 +5,11 @@ package = OCIO Configuration version = 7.x-1.0 project = ocio_user_config dependencies[] = administerusersbyrole +dependencies[] = ctools dependencies[] = features dependencies[] = role_delegation dependencies[] = strongarm dependencies[] = user -dependencies[] = user_import features[ctools][] = strongarm:strongarm:1 features[features_api][] = api:2 features[user_permission][] = access user profiles @@ -35,15 +35,11 @@ features[user_permission][] = edit users with role 4 features[user_permission][] = edit users with role 5 features[user_permission][] = edit users with role 6 features[user_permission][] = edit users with role 7 -features[user_permission][] = import users features[user_permission][] = select account cancellation method features[user_role][] = administrator features[user_role][] = contributor features[user_role][] = editor features[user_role][] = site builder features[user_role][] = site manager -features[variable][] = user_import_settings features[variable][] = views_defaults -features_exclude[dependencies][ctools] = ctools features_exclude[dependencies][ocio_user_config] = ocio_user_config -mtime = 1422318043 diff --git a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.strongarm.inc b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.strongarm.inc index a49d8ad5..7be4b92a 100644 --- a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.strongarm.inc +++ b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.strongarm.inc @@ -10,19 +10,13 @@ function ocio_user_config_strongarm() { $export = array(); - $strongarm = new stdClass(); - $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ - $strongarm->api_version = 1; - $strongarm->name = 'user_import_settings'; - $strongarm->value = '7'; - $export['user_import_settings'] = $strongarm; - $strongarm = new stdClass(); $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ $strongarm->api_version = 1; $strongarm->name = 'views_defaults'; $strongarm->value = array( 'admin_views_user' => TRUE, + 'workbench_files' => TRUE, ); $export['views_defaults'] = $strongarm; diff --git a/profiles/wcm_base/modules/custom/ocio_user_directory/ocio_user_directory.features.field_instance.inc b/profiles/wcm_base/modules/custom/ocio_user_directory/ocio_user_directory.features.field_instance.inc index c5766bf2..1226c3a9 100644 --- a/profiles/wcm_base/modules/custom/ocio_user_directory/ocio_user_directory.features.field_instance.inc +++ b/profiles/wcm_base/modules/custom/ocio_user_directory/ocio_user_directory.features.field_instance.inc @@ -454,13 +454,15 @@ function ocio_user_directory_field_default_field_instances() { 'settings' => array( 'manualcrop_crop_info' => 1, 'manualcrop_default_crop_area' => 1, - 'manualcrop_enable' => 0, + 'manualcrop_enable' => 1, 'manualcrop_inline_crop' => 0, 'manualcrop_instant_crop' => 0, 'manualcrop_instant_preview' => 1, 'manualcrop_keyboard' => 1, - 'manualcrop_maximize_default_crop_area' => 0, - 'manualcrop_require_cropping' => array(), + 'manualcrop_maximize_default_crop_area' => 1, + 'manualcrop_require_cropping' => array( + 'user_picture' => 'user_picture', + ), 'manualcrop_styles_list' => array(), 'manualcrop_styles_mode' => 'include', 'manualcrop_thumblist' => 0, diff --git a/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.info b/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.info index ce41d8d3..2cc62003 100644 --- a/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.info +++ b/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.info @@ -28,4 +28,3 @@ features[variable][] = node_submitted_web_form features[variable][] = webform_node_web_form features[variable][] = workbench_moderation_default_state_web_form features_exclude[field_base][body] = body -mtime = 1422132548 diff --git a/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.strongarm.inc b/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.strongarm.inc index 83b85f31..8b47d573 100644 --- a/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.strongarm.inc +++ b/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.strongarm.inc @@ -93,7 +93,9 @@ function ocio_web_form_strongarm() { $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ $strongarm->api_version = 1; $strongarm->name = 'node_options_web_form'; - $strongarm->value = array(); + $strongarm->value = array( + 0 => 'status', + ); $export['node_options_web_form'] = $strongarm; $strongarm = new stdClass(); diff --git a/profiles/wcm_base/themes/ocio_omega_3/css/layouts/ocio-3/ocio-3.layout.css b/profiles/wcm_base/themes/ocio_omega_3/css/layouts/ocio-3/ocio-3.layout.css index dea17ba3..72d01719 100644 --- a/profiles/wcm_base/themes/ocio_omega_3/css/layouts/ocio-3/ocio-3.layout.css +++ b/profiles/wcm_base/themes/ocio_omega_3/css/layouts/ocio-3/ocio-3.layout.css @@ -136,7 +136,7 @@ body.html { clear: both; } -.panels-ipe-display-container { +.panel-panel { padding: 0 2.5% 2% 2.5%; } diff --git a/profiles/wcm_base/themes/ocio_omega_3/css/layouts/ocio-3/ocio-3.layout.no-query.css b/profiles/wcm_base/themes/ocio_omega_3/css/layouts/ocio-3/ocio-3.layout.no-query.css index 2f49a34b..b368ab13 100644 --- a/profiles/wcm_base/themes/ocio_omega_3/css/layouts/ocio-3/ocio-3.layout.no-query.css +++ b/profiles/wcm_base/themes/ocio_omega_3/css/layouts/ocio-3/ocio-3.layout.no-query.css @@ -128,7 +128,7 @@ body.html { clear: both; } -.panels-ipe-display-container { +.panel-panel { padding: 0 2.5% 2% 2.5%; } diff --git a/profiles/wcm_base/themes/ocio_omega_3/css/ocio-3.no-query.css b/profiles/wcm_base/themes/ocio_omega_3/css/ocio-3.no-query.css index c0074f55..194f3b12 100644 --- a/profiles/wcm_base/themes/ocio_omega_3/css/ocio-3.no-query.css +++ b/profiles/wcm_base/themes/ocio_omega_3/css/ocio-3.no-query.css @@ -124,7 +124,7 @@ body.html { clear: both; } -.panels-ipe-display-container { +.panel-panel { padding: 0 2.5% 2% 2.5%; } diff --git a/profiles/wcm_base/themes/ocio_omega_3/css/ocio-3.styles.css b/profiles/wcm_base/themes/ocio_omega_3/css/ocio-3.styles.css index 7e45447c..73c8826b 100644 --- a/profiles/wcm_base/themes/ocio_omega_3/css/ocio-3.styles.css +++ b/profiles/wcm_base/themes/ocio_omega_3/css/ocio-3.styles.css @@ -132,7 +132,7 @@ body.html { clear: both; } -.panels-ipe-display-container { +.panel-panel { padding: 0 2.5% 2% 2.5%; } diff --git a/profiles/wcm_base/themes/ocio_omega_3/sass/base/_layout-base.scss b/profiles/wcm_base/themes/ocio_omega_3/sass/base/_layout-base.scss index 82e75edc..96d4caa1 100644 --- a/profiles/wcm_base/themes/ocio_omega_3/sass/base/_layout-base.scss +++ b/profiles/wcm_base/themes/ocio_omega_3/sass/base/_layout-base.scss @@ -37,7 +37,7 @@ body.html { @include pie-clearfix; } -.panels-ipe-display-container { +.panel-panel { padding: 0 2.5% 2% 2.5%; } diff --git a/profiles/wcm_base/wcm_base.info b/profiles/wcm_base/wcm_base.info index 179d10f5..a8733aee 100644 --- a/profiles/wcm_base/wcm_base.info +++ b/profiles/wcm_base/wcm_base.info @@ -55,13 +55,8 @@ dependencies[] = flexslider dependencies[] = flexslider_views dependencies[] = override_node_options dependencies[] = uuid_features -dependencies[] = user_import dependencies[] = views_bulk_operations dependencies[] = webform -dependencies[] = apachesolr -dependencies[] = apachesolr_search -dependencies[] = search_api_attachments -dependencies[] = apachesolr_file ; Panopoly Foundation diff --git a/profiles/wcm_base/wcm_base.make b/profiles/wcm_base/wcm_base.make index ecb3591a..842e91ec 100644 --- a/profiles/wcm_base/wcm_base.make +++ b/profiles/wcm_base/wcm_base.make @@ -4,12 +4,6 @@ core = 7.x ; ********************************************** ; ***************** CONTRIB ******************** -projects[apachesolr][version] = 1.7 -projects[apachesolr][subdir] = contrib - -projects[apachesolr_file][version] = 1.x-dev -projects[apachesolr_file][subdir] = contrib - projects[calendar][version] = 3.5 projects[calendar][subdir] = contrib @@ -66,25 +60,19 @@ projects[realname][subdir] = contrib projects[redis][version] = 2.12 projects[redis][subdir] = contrib -projects[search_api_attachments][version] = 1.3 -projects[search_api_attachments][subdir] = contrib - projects[token][version] = 1.5 projects[token][subdir] = contrib projects[token_filter][version] = 1.1 projects[token_filter][subdir] = contrib -projects[user_import][version] = 2.2 -projects[user_import][subdir] = contrib - projects[views_accordion][version] = 1.1 projects[views_accordion][subdir] = contrib projects[views_bulk_operations][version] = 3.2 projects[views_bulk_operations][subdir] = contrib -projects[webform][version] = 4.2 +projects[webform][version] = 4.8 projects[webform][subdir] = contrib diff --git a/sites/all/libraries/README.txt b/sites/all/libraries/README.txt new file mode 100644 index 00000000..d027ec24 --- /dev/null +++ b/sites/all/libraries/README.txt @@ -0,0 +1,2 @@ +This directory should be used to place downloaded and custom libraries (such as +Javascript libraries) which are used by contributed or custom modules. -- GitLab