webform_view.inc in Webform view 7.4
Same filename and directory in other branches
Additional component for webform that allows views to be used as embeddable elements.
File
webform_view.incView source
<?php
/**
* @file
* Additional component for webform that allows views to be used as
* embeddable elements.
*/
/**
* Implements _webform_defaults_component().
*
* Define the extra params to save as settings for webform embedded view.
* Specifically - the view ID.
*
* @see _webform_select_view()
*/
function _webform_defaults_view() {
return array(
'name' => '',
'form_key' => NULL,
'mandatory' => 0,
'pid' => 0,
'weight' => 0,
'value' => '',
'extra' => array(
'view' => '',
'filter_field_id' => 'quantity',
'contextual_filters' => '',
'description' => '',
'private' => FALSE,
'title_display' => 0,
),
);
}
/**
* Presents the view options when editing a webform view component.
*
* Allows an editor to select which view to pull in and embed.
*
* Implements _webform_edit_component().
*/
function _webform_edit_view($component) {
$form = array();
$form['webform_view'] = array(
'#type' => 'fieldset',
'#title' => t('Webform View'),
);
$options = _webform_view_options();
$form['webform_view']['view'] = array(
'#type' => 'select',
'#title' => t('View'),
'#options' => $options,
'#parents' => array(
'extra',
'view',
),
'#default_value' => $component['extra']['view'],
'#description' => t("Choose an existing view that will be embedded in the webform. Each row of this view will be displayed and selections made there will become form fields that will be submitted as part of this form."),
);
// Need to expose an option to the editor to choose
// which of the sub-fields is the trigger or 'quantity' one.
// If the 'quantity' field is blank, the data for that row
// is not serialized at all. But the user should not be locked down to
// 'quantity' as the magic keyword. So we have to expose it instead.
// To enumerate the subfields, need to look at the current webform proper.
$webform_node = node_load($component['nid']);
$subfields = array();
$subfields['<none>'] = '<none>';
foreach ($webform_node->webform['components'] as $field) {
if (isset($component['cid']) && $field['pid'] == $component['cid']) {
$subfields[$field['form_key']] = $field['form_key'];
}
}
$form['webform_view']['filter_field_id'] = array(
'#type' => 'select',
'#title' => t('Required field (in rows)'),
'#options' => $subfields,
'#parents' => array(
'extra',
'filter_field_id',
),
'#default_value' => $component['extra']['filter_field_id'],
'#description' => t("Rows where this field is blank will be excluded from results, eg set it to the 'quantity' field to discard info when 'quantity' is zero, or set it to the 'yes' field to ignore submissions where the answer is 'no'. This prevents reports from filling up with dozens of lines with 'quantity:0' next to them. If you choose '<none>', then all rows will always be submitted, without any clean-up."),
);
// Pass arguments through to the view.
// Use the same method as Views UI Preview does.
$form['webform_view']['contextual_filters'] = array(
'#type' => 'textfield',
'#title' => t('Contextual filters'),
'#parents' => array(
'extra',
'contextual_filters',
),
'#default_value' => $component['extra']['contextual_filters'],
'#description' => t('You can pass arguments through to the view. Separate contextual filter values with a "/".'),
);
return $form;
}
/**
* Load Webform view options - a list of available views.
*/
function _webform_view_options() {
static $options;
if (!isset($options)) {
$views = views_get_enabled_views();
$options = array();
foreach ($views as $view_id => $view) {
$options[$view->human_name] = array();
foreach ($view->display as $display_id => $display) {
// Create a key here, identical to that used by views_block for 'delta'
$key = $view_id . '-' . $display_id;
$options[$view->human_name][$key] = (object) array(
'option' => array(
$key => $display->display_title,
),
);
}
}
drupal_alter('webform_view_options', $options);
}
return $options;
}
/**
* Show the embedded view on the webform.
*
* The returned renderable array representing the element to be themed
* may include form elements & nesting.
*
* Implements _webform_render_component().
*/
function _webform_render_view($component, $value_serialized = NULL, $filter = TRUE) {
if (!empty($value_serialized) && is_array($value_serialized)) {
$value = unserialize(reset($value_serialized));
}
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$view_key = $component['extra']['view'];
$args = explode('/', $component['extra']['contextual_filters']);
$args = array_filter($args);
$placeholder_fields = array();
// Nicely stolen from views_block_view()
// If the delta doesn't contain valid data return nothing.
$explode = explode('-', $view_key);
if (count($explode) != 2) {
return NULL;
}
list($name, $display_id) = $explode;
// Load the view.
if (!($view = views_get_view($name))) {
watchdog('webform_view', 'Invalid view - cannot embed %name %display_id', array(
'%name' => $name,
'%display_id' => $display_id,
));
return NULL;
}
if (!$view
->access($display_id)) {
watchdog('webform_view', 'View access denied - cannot embed %name %display_id', array(
'%name' => $name,
'%display_id' => $display_id,
));
}
// execute_display produces cooked text.
// It can include a placeholder string for our form elements,
// if a field chooses to use the 'webform_placeholder' as a renderer.
$output = $view
->preview($display_id, $args);
if (empty($output)) {
return NULL;
}
// Go through the rows to prepare the webform-like fields.
// Do this by CLONING the children elements already nested in this one.
foreach ($view->result as $index => $row) {
// Key on the real row ID if possible.
$key = isset($row->{$view->base_field}) ? $row->{$view->base_field} : $index;
// If reviewing or editing fields, the $value array may be available.
// It's our job to put it back into the element.
$component_value = isset($value[$key]) ? $value[$key] : array();
// This is now an array representing the data last stored in this row.
// One of these fields should probably be a textfield that can hold the
// item identifier
// Should use tokens to prefill it. Would be good.
// For now, just steal values direct from the row result.
// A token like [node_title] will pull the id from a view -
// as long as the view row has that data.
// I can't expose to the user what those machine names are however...
// https://drupal.org/node/2099751
// They just have to guess.
$tokens = array();
foreach ($row as $token_key => $token_val) {
if (is_string($token_val)) {
$tokens["[{$token_key}]"] = $token_val;
}
elseif (is_array($token_val) && ($sub_val = reset($token_val))) {
if (isset($sub_val) && isset($sub_val['raw']) && isset($sub_val['raw']['value'])) {
$tokens["[{$token_key}]"] = $sub_val['raw']['value'];
}
}
}
// Each component that is defined as a child of this view component
// gets replicated.
if (empty($component['children'])) {
watchdog('webform_view', '
The embedded webform component "!component" in "!webform" has no
"child" elements defined.
If you want the webform to submit data, please create a webform
field element and nest it beneath the embedded view.
...And then configure the view to expose this field in its layout.
', array(
'!component' => l($component['name'], '/node/' . $node->nid . '/webform/components/' . $component['cid']),
'!webform' => l($node->title, '/node/' . $node->nid . '/webform/components/'),
), WATCHDOG_NOTICE);
$component['children'] = array();
}
foreach ((array) $component['children'] as $webform_component) {
// Some webform welements are not native. @see webform_element_info
$type = $webform_component['type'];
$webform_elements = array(
'number' => 'webform_number',
);
if (isset($webform_elements[$type])) {
$type = $webform_elements[$type];
}
// Use replacements to put the item identifier into a field value.
$default_value = NULL;
if (!empty($webform_component['value'])) {
$default_value = str_replace(array_keys($tokens), array_values($tokens), $webform_component['value']);
}
// Copy any previously set value into our element default value.
if (isset($component_value[$webform_component['form_key']])) {
$default_value = $component_value[$webform_component['form_key']];
}
// Webform components are very unlike normal form elements.
// that's a bore. Copy some parameters like size across.
// Can I delegate this to each elements own webform_render_THING
// callback - I feel like doing this by hand could be errorful.?
$render_func = '_webform_render_' . $webform_component['type'];
if (function_exists($render_func)) {
// The value arg is apparently expected to be an array? Whatever.
if (!is_array($default_value)) {
$default_value = array(
$default_value,
);
}
$placeholder_fields[$key][$webform_component['form_key']] = $render_func($webform_component, $default_value);
$placeholder_fields[$key][$webform_component['form_key']]['#webform_component'] = $webform_component;
}
else {
/*
//////SNIP THIS
// This was me doing it by hand - probably redundant if ^ is working.
$placeholder_fields[$key][$webform_component['form_key']] = array(
'#type' => $type,
'#title' => $webform_component['name'],
'#size' => isset($webform_component['extra']['width'])
? $webform_component['extra']['width'] : NULL,
'#default_value' => isset($default_value) ? $default_value : NULL,
'#webform_component' => $webform_component,
// Need to call in the theme wrapper to get field markup
// and title to show.
'#theme_wrappers' => ($type == 'hidden')
? array() : array('webform_element'),
);
/////
*/
}
}
}
// $display = $view->display[$view->current_display];
$element = array(
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#required' => $component['mandatory'],
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#theme_wrappers' => array(
'webform_element',
),
'#theme' => 'webform_view_embedded',
// Needed to disable double-wrapping of radios and checkboxes.
'#pre_render' => array(),
'#translatable' => array(
'title',
'description',
'options',
),
// When webform renders elements, the content is expected to be inside
// element #children. #markup is not respected.
'#type' => 'markup',
);
// Add the made-up placeholder fields as children.
$element += $placeholder_fields;
// Add our embedded view as a renderable.
// Using #markup so that even if rendering starts falling apart later,
// it will still show up. Normally however, this value gets caught and
// preprocessed in theme_webform_view_embedded()
$element['view']['#markup'] = $output;
// Don't do this until we are finished with the data.
// $view->destroy();
return $element;
}
/**
* Declare rendering routines for our element.
*
* Called by webform to register theme functions that the individual
* cpmponent uses.
*
* Implements _webform_theme_COMPONENT().
*/
function _webform_theme_view() {
// Declares different theme func for on-screen and text-output of the results.
// Note, not the form element.
return array(
// Text version.
'webform_display_view' => array(
'render element' => 'element',
),
// HTML version.
// This renders the element when in the actual webform
// needs to be named _embedded to avoid a name collision.
'webform_view_embedded' => array(
'render element' => 'element',
),
);
}
/**
* Implements _webform_display_component().
*
* The $value available here has been flattened and serialized
* - unpack it before use.
*/
function _webform_display_view($component, $value_serialized, $format = 'html', $submission = array()) {
$value = array();
if (!empty($submission)) {
foreach ($component['children'] as $cid => $field) {
if ($field['type'] == 'hidden') {
// Data is stored in the first hidden field
$value = unserialize($submission->data[$cid][0]);
break;
}
}
}
return array(
'#title' => $component['name'],
'#weight' => $component['weight'],
'#theme' => 'webform_display_view',
'#theme_wrappers' => $format == 'html' ? array(
'webform_element',
) : array(
'webform_element_text',
),
'#format' => $format,
'#value' => (array) $value,
'#translatable' => array(
'title',
'options',
),
);
}
/**
* Render the embedded view element in the form.
*
* Most content has now been prepared and the child items rendered.
* My job is to MOVE the rendered form element markup and embed it into the
* view displays where the placeholders are.
*/
function theme_webform_view_embedded($variables) {
$element = $variables['element'];
// The rendered, already-built view is text in $element['view']['#markup'].
// The form elements are my direct children.
// Build an array of replacements for str_replace efficiency.
$replacements = array();
foreach (element_children($element) as $key) {
$replace_pattern = "[webform_view_" . $key . "_placeholder]";
// Child item has already been rendered/built by now. (#printed is true)
// Copy its processed text in here.
if (!empty($element[$key]['#printed'])) {
$rendered_child = $element[$key]['#children'];
}
else {
// Dunno why, but maybe I should render i myself.
// Used to be required, maybe this should never happen.
$rendered_child = drupal_render($element[$key]);
// TODO: check if this code is still reachable.
}
$replacements[$replace_pattern] = $rendered_child;
}
// I earlier placed the rendered view into $element['view']['#markup']
$element['view']['#markup'] = str_replace(array_keys($replacements), array_values($replacements), $element['view']['#markup']);
return $element['view']['#markup'];
}
/**
* Format the output of data for this component.
*
* This renders the data that's been submitted.
* As seen on the results pages, also used for the email.
*/
function theme_webform_display_view($variables) {
$element = $variables['element'];
// May need to unpack and flatten nested arrays (multiple checkbox options).
foreach ($element['#value'] as $delta => $row) {
foreach ($row as $col => $cell) {
// Some fields may be so arrive as arrays. Stringify when theming.
if (is_array($cell)) {
$element['#value'][$delta][$col] = implode(', ', $cell);
}
}
}
// Theme the order like a table.
if ($element['#format'] == 'html') {
$first_row = reset($element['#value']);
$header = array();
if ($first_row) {
foreach ($first_row as $key => $val) {
$header[] = t($element[$key]['#title']);
}
}
return theme('table', array(
'header' => $header,
'rows' => $element['#value'],
));
}
else {
return array_to_plaintext_table($element['#value']);
}
}
/**
* Rendering function to emulate table layout.
*
* @param array $rows
* Structured data. A two-dimensional array.
*
* @return string
* HTML rendering of the input.
*/
function array_to_plaintext_table($rows) {
$cols = array();
// Count the col widths first.
foreach ($rows as $delta => $row) {
foreach ($row as $col => $cell) {
$cols[$col] = max(@$cols[$col], strlen($cell), strlen($col));
}
}
// Now format.
$lines = array();
$printf_format = '';
// Build the string template.
foreach ($cols as $colwidth) {
$printf_format .= "%-{$colwidth}s : ";
}
// First the header row.
$lines[] = call_user_func_array('sprintf', array(
'format' => $printf_format,
) + array_keys($cols));
foreach ($rows as $row) {
$lines[] = call_user_func_array('sprintf', array(
'format' => $printf_format,
) + $row);
}
return implode("\n", $lines);
}
Functions
Name![]() |
Description |
---|---|
array_to_plaintext_table | Rendering function to emulate table layout. |
theme_webform_display_view | Format the output of data for this component. |
theme_webform_view_embedded | Render the embedded view element in the form. |
_webform_defaults_view | Implements _webform_defaults_component(). |
_webform_display_view | Implements _webform_display_component(). |
_webform_edit_view | Presents the view options when editing a webform view component. |
_webform_render_view | Show the embedded view on the webform. |
_webform_theme_view | Declare rendering routines for our element. |
_webform_view_options | Load Webform view options - a list of available views. |