commerce_product_bundles-8.x-1.0/src/Element/BundleRefDefaultVariationsRendered.php
src/Element/BundleRefDefaultVariationsRendered.php
<?php
namespace Drupal\commerce_product_bundles\Element;
use Drupal\commerce_product\Entity\ProductAttributeValue;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Radios;
use Drupal\Component\Utility\Html as HtmlUtility;
/**
* Provides a form input element for Rendering Bundle Variations - referenced variations as radio buttons.
*
* Example usage:
* @code
* $form['rendered_product_bundle'] = [
* '#type' => 'bundle_ref_default_variations_rendered',
* '#theme_wrappers' => ['bundle_ref_default_select_wrapper'],
* '#title' => 'Bundle',
* '#refItems' => [1,2,3],
* '#quantity' => 3,
* '#required' => TRUE,
* '#attribute_title' => NULL,
* '#options' => [],
* '#limit_validation_errors' => [],
* '#ajax' => []
* ];
* ];
* @endcode
*
* @FormElement("bundle_ref_default_variations_rendered")
*/
class BundleRefDefaultVariationsRendered extends Radios {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#process' => [
[$class, 'processRadios'],
],
'#theme_wrappers' => ['radios'],
'#pre_render' => [
[$class, 'preRenderBundleFormElement'],
],
];
}
/**
* Expands a radios element into individual radio elements.
*/
public static function processRadios(&$element, FormStateInterface $form_state, &$complete_form) {
if (count($element['#refItems']) > 0) {
// Get Attributes value if any.
$attribute_values = self::getAttributeValues($element['#refItems']);
// Set default indicators.
$attributeType = NULL;
$hasAttributes = FALSE;
$options_available = FALSE;
$elementName = [];
// Set weight.
$weight = 0;
// Loop through all ref element and construct element.
foreach ($element['#refItems'] as $key => $variation) {
// There are available options so set this one to TRUE ;).
$options_available = TRUE;
// Define product attribute.
$product_attribute_values = $attribute_values[$key];
// Products with attributes logic.
if ($product_attribute_values[0] instanceof ProductAttributeValue) {
// Set indicator to true.
$hasAttributes = TRUE;
// Get attribute value.
$markup_attribute = $product_attribute_values[0]->getName();
$rendered_attribute_all = [];
// Get type - Defaults to first attribute value.
$attributeType = $product_attribute_values[0]->getAttributeId();
$attributeTypeAll = [];
foreach ($product_attribute_values as $key_attr => $product_attribute_value) {
$attributeTypeAll[] = $product_attribute_value->getAttributeId();
$rendered_attribute_all[] = $product_attribute_value->getName();
}
// Implode attribute values.
if($attributeTypeAll) {
$attributeType = implode(' + ', $attributeTypeAll);
}
// Implode attribute values.
if($rendered_attribute_all) {
$markup_attribute = implode(' + ', $rendered_attribute_all);
$elementName[$key] = $markup_attribute;
}
} else {
// If PV has NO attributes use title > by model of 'Product variation title' widget.
// @see Drupal\commerce_product\Plugin\Field\FieldWidget\ProductVariationTitleWidget().
$markup_attribute = $product_attribute_values[0];
}
// Mutual logic.
if (isset($element['#default_value']) && $element['#default_value'] == $key) {
$attributes['class'][] = 'product--bundle--rendered-variations__selected';
}
// Maintain order of options as defined in #options, in case the element
// defines custom option sub-elements, but does not define all option
// sub-elements.
$weight += 0.001;
// Element options value.
$element['#options'][$key] = $variation->label();
$element += [$key => []];
// Generate the parents as the autogenerator does, so we will have a
// unique id for each radio button.
$parents_for_id = array_merge($element['#parents'], [$key]);
// Get element attributes.
$attributes = $element['#attributes'];
if (isset($element['#default_value']) && $element['#default_value'] == $key) {
$attributes['class'][] = 'product--rendered-attribute__selected';
}
// Construct element.
$element[$key] += [
'#type' => 'radio',
'#title' => $markup_attribute,
'#return_value' => $key,
'#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE,
'#attributes' => [],
'#parents' => $element['#parents'],
'#id' => HtmlUtility::getUniqueId('edit-' . implode('-', $parents_for_id)),
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
// Errors should only be shown on the parent radios element.
'#error_no_message' => TRUE,
'#weight' => $weight,
];
}
// Add 'Choose' selection for options with product attributes.
if($hasAttributes) {
// Get 'Choose' markup.
$attribute_name = '';
if(isset($elementName[$element['#value']])) {
$attribute_name = $elementName[$element['#value']];
}
$html = '<p>' . t('Choose @attr_name: <b>@attr_value</b>', [
'@attr_name' => str_replace('_', ' ', $attributeType),
'@attr_value' => $attribute_name
]) . '</p>';
$element['#attribute_title'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => $html,
'#attributes' => [],
'#weight' => -1,
];
$element['#attributes']['class'][] = 'product-bundles--rendered-variations';
} else {
// If PV has no attributes defined.
$element['#attribute_title'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => t('Select'),
'#attributes' => [],
'#weight' => -1,
];
$element['#attributes']['class'][] = 'product-bundles--rendered-variations';
}
// If we have no options.
if (!$options_available) {
// Do not show title or description if there is no selection.
$element['#title_display'] = 'invisible';
$element['#description_display'] = 'invisible';
}
}
return $element;
}
/**
* Gets the attribute values of a given set of variations or PV title if
* no attributes are defined for this variation.
*
* @TODO implement support form multiple product attributes. For now this works only with single attribute.
*
* @param array $variations
*
* @return array
*/
protected static function getAttributeValues(array $variations) {
$values = [];
foreach ($variations as $variation) {
// Get attributes value.
$attribute_values = $variation->getAttributeValues();
if($attribute_values) {
foreach ($attribute_values as $attribute_value) {
if ($attribute_value) {
$values[$variation->id()][] = $attribute_value;
}
else {
$values['_none'] = '';
}
}
}
// We are dealing with PV without attributes.
else {
$values[$variation->id()][] = $variation->label();
}
}
return $values;
}
/**
* Adds form element theming to an element if its title or description is set.
* This is used as a pre render function for bundle default ref. el..
*
* @param $element
*
* @return mixed
*/
public static function preRenderBundleFormElement($element) {
// Set the element's title attribute to show #title as a tooltip, if needed.
if (isset($element['#title']) && $element['#title_display'] == 'attribute') {
$element['#attributes']['title'] = $element['#title'];
if (!empty($element['#required'])) {
// Append an indication that this field is required.
$element['#attributes']['title'] .= ' (' . t('Required') . ')';
}
}
if (isset($element['#title']) || isset($element['#description'])) {
// @see #type 'fieldgroup'
$element['#attributes']['id'] = $element['#id'] . '--wrapper';
$element['#theme_wrappers'][] = 'bundle_ref_default_select';
$element['#attributes']['class'][] = 'fieldgroup';
$element['#attributes']['class'][] = 'form-composite';
}
return $element;
}
}
