le * Show/hide attributes based on the values of other attributes. * * Some attributes may not be applicable depending upon the value of another * attribute. It may be desireable to hide the attribute unless an appropriate * value is selected for the other attribute to avoid confusing users. This * module has an administrative interface for specifying the dependencies * and Javascript code for hiding and showing the attributes. */ /** * Implements hook_menu(). */ function uc_dropdown_attributes_menu() { $items = array(); $items['node/%node/edit/dependencies'] = array( 'title' => 'Dependencies', 'description' => 'Product attribute dependency administration.', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_dropdown_attributes_product', 1), 'access callback' => 'uc_attribute_product_access', 'access arguments' => array(1), 'theme callback' => 'uc_dropdown_attributes_admin_theme', 'type' => MENU_LOCAL_TASK, 'weight' => 1, 'file' => 'dependent_dropdown.inc', ); $items['node/%/dependencies/%/dependency/%'] = array( 'type' => MENU_CALLBACK, 'page callback' => 'uc_dropdown_attributes_dependency', 'page arguments' => array(1, 3, 5), 'access callback' => TRUE, ); $items['admin/store/products/classes/%/dependencies'] = array( 'title' => 'Dependencies', 'description' => 'Product class attribute dependency administration.', 'type' => MENU_LOCAL_TASK, 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_dropdown_attributes_class', 4), 'access arguments' => array('administer product classes'), 'file' => 'dependent_dropdown.inc', ); return $items; } /** * Implements hook_form_alter(). */ function uc_dropdown_attributes_form_alter(array &$form, array &$form_state, string $form_id): void { if (preg_match('/^uc_product_kit_add_to_cart_form.*/', $form_id)) { foreach ($form['products'] as $key => $value) { if (is_numeric($key)) { $type = uc_dropdown_attributes_dependency_type((int) $key); if (!is_null($type)) { $values = $form_state['values'] ?? []; uc_dropdown_attributes_product_alter((int) $key, $form['products'][$key], $values, $type, $form_state['rebuild']); if (!isset($form['#after_build']) || !in_array('_uc_dropdown_attributes_kit_build', $form['#after_build'])) { $form['#after_build'][] = '_uc_dropdown_attributes_kit_build'; } } } } } if (preg_match('/^uc_product_add_to_cart_form.*/', $form_id)) { $nid = $form['nid']['#value']; $type = uc_dropdown_attributes_dependency_type((int) $nid); if (!is_null($type)) { $values = $form_state['values'] ?? []; uc_dropdown_attributes_product_alter((int) $nid, $form, $values, $type, $form_state['rebuild']); $form['node_id'] = array( '#type' => 'hidden', '#value' => $nid, ); switch ($type) { case 'node': $form['#after_build'][] = '_uc_dropdown_attributes_product_build'; break; case 'class': $form['#after_build'][] = '_uc_dropdown_attributes_class_build'; break; } } } } /** * Alter products in preparation for drop down attributes. * * Adds the 'Please select' and removes the default value. Ubercart does this * for required attributes but since these attributes can no longer be required * if the attributes are dependent then this reproduces the same thing. * * @param int $nid * Node ID. * @param array $form * Product form. * @param array $form_values * Values from $form_state. * @param string $type * 'node' for dependencies defined on the node level; * 'class' for dependencies defined on the product class. * @param bool $rebuild * Whether the form is being rebuilt. */ function uc_dropdown_attributes_product_alter(int $nid, array &$form, array $form_values, string $type, bool $rebuild): void { switch ($type) { case 'node': $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_attributes} WHERE nid=:nid'; $attributes = db_query($sql, array(':nid' => $nid)); break; case 'class': $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_classes} WHERE pcid=:pcid'; $pcid = uc_dropdown_attributes_get_type($nid); $attributes = db_query($sql, array(':pcid' => $pcid)); break; default: $attributes = new stdClass(); break; } $parent_aids = array(); if (!$rebuild) { $data = $form['node']['#value']->data; } $load = FALSE; foreach ($attributes as $attribute) { $parent_aids[$attribute->parent_aid] = $attribute->parent_aid; if (isset($form['attributes'][$attribute->aid]['#options']) && count($form['attributes'][$attribute->aid]['#options']) && $attribute->required) { switch ($form['attributes'][$attribute->aid]['#type']) { case 'select': $form['attributes'][$attribute->aid]['#options'] = array('' => t('Please select')) + $form['attributes'][$attribute->aid]['#options']; $form['attributes'][$attribute->aid]['#default_value'] = ''; if (!$rebuild && isset($data['attributes'][$attribute->aid])) { $data['attributes'][$attribute->aid] = ''; $load = TRUE; } break; case 'radios': $form['attributes'][$attribute->aid]['#default_value'] = NULL; if (!$rebuild && isset($data['attributes'][$attribute->aid])) { $data['attributes'][$attribute->aid] = ''; $load = TRUE; } // Validation does not work if required set later. if (isset($form_values['attributes'][$attribute->parent_aid])) { $parent_value = $form_values['attributes'][$attribute->parent_aid]; $values = unserialize($attribute->parent_values); $parent_aid = $attribute->parent_aid; if (is_array($values) && in_array($parent_value, $values)) { $form['attributes'][$attribute->aid]['#required'] = TRUE; } } break; case 'checkboxes': $form['attributes'][$attribute->aid]['#default_value'] = array(); if (!$rebuild && isset($data['attributes'][$attribute->aid])) { $data['attributes'][$attribute->aid] = array(); $load = TRUE; } break; } } } if ($load) { $form['node']['#value'] = uc_product_load_variant($nid, $data); } $update_node = variable_get('uc_product_update_node_view', 0); if (!$update_node) { // If Ubercart update is not enabled then ajax needs to be attached to // parent attributes. foreach ($parent_aids as $aid) { $form['attributes'][$aid]['#ajax'] = array( 'callback' => 'uc_dropdown_attributes_ajax_callback', 'wrapper' => $form['attributes']['#id'], ); } } } /** * Ajax callback for attribute selection form elements. */ function uc_dropdown_attributes_ajax_callback(array $form, array $form_state) { if (count($form_state['triggering_element']['#parents']) == 4) { // This is a product kit. $key = $form_state['triggering_element']['#parents'][1]; return $form['products'][$key]['attributes']; } return $form['attributes']; } /** * Form build for products. * * Callback for $form['#after_build'] for products. Adds the CSS to hide * the dependent attributes. */ function _uc_dropdown_attributes_product_build(array $form, array &$form_state) { $nid = $form['nid']['#value']; $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_attributes} WHERE nid=:nid'; $attributes = db_query($sql, array(':nid' => $nid)); if (isset($form_state['triggering_element'])) { $parents = $form_state['triggering_element']['#parents']; $parent_aid = $parents[count($parents) - 1]; $parent_value = $form_state['triggering_element']['#value']; uc_dropdown_attributes_remove_values((int) $parent_aid, $parent_value, (int) $nid, 'node', $form_state['values']); } foreach ($attributes as $attribute) { if (!isset($form_state['values']['attributes'][$attribute->parent_aid])) { continue; } $parent_value = $form_state['values']['attributes'][$attribute->parent_aid]; if ($form['attributes'][$attribute->parent_aid]['#type'] == 'checkboxes') { $parent_values = array_diff((array) $parent_value, array(0)); $values = unserialize($attribute->parent_values); if (is_array($values) && count(array_intersect($parent_values, $values))) { // Show dependent attribute. if ($attribute->required) { $form['attributes'][$attribute->aid]['#required'] = TRUE; } } else { // Hide dependent attribute. $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values, regardless of 'required' status. uc_dropdown_attributes_clear_input($form, $form_state, (int) $attribute->aid); } } else { if ($parent_value) { // A value has been entered in parent attribute. $values = unserialize($attribute->parent_values); if (is_array($values) && array_key_exists($parent_value, $values)) { // Show dependent attribute. if ($attribute->required) { $form['attributes'][$attribute->aid]['#required'] = TRUE; } } else { // Hide dependent attribute. $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values, regardless of 'required' status. uc_dropdown_attributes_clear_input($form, $form_state, (int) $attribute->aid); } } else { $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values, regardless of 'required' status. uc_dropdown_attributes_clear_input($form, $form_state, (int) $attribute->aid); } } } return $form; } /** * Clear inputs for attribute. * * @param array $form * Form array. * @param array $form_state * Form state array. * @param int $aid * Attribute ID. */ function uc_dropdown_attributes_clear_input(array &$form, array &$form_state, int $aid): void { // Use nullsafe operator and null coalescing for PHP 8.0 compatibility $attributes_input = $form_state['input']['attributes'] ?? null; if ($attributes_input === null || !isset($form['attributes'][$aid])) { return; } $type = $form['attributes'][$aid]['#type'] ?? null; if ($type === null) { return; } switch ($type) { case 'checkboxes': if (isset($attributes_input[$aid])) { foreach ($attributes_input[$aid] as $oid => $value) { $form_state['input']['attributes'][$aid][$oid] = NULL; } } break; case 'radios': case 'select': $form_state['input']['attributes'][$aid] = ''; break; case 'textfield': $form_state['input']['attributes'][$aid] = ''; break; default: break; } } /** * Form build for classes. * * Callback for $form['#after_build'] for product classes. Adds * the CSS to hide the dependent attributes. */ function _uc_dropdown_attributes_class_build(array $form, array &$form_state) { $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_classes} WHERE pcid=:pcid'; $pcid = uc_dropdown_attributes_get_type($form['nid']['#value']); $attributes = db_query($sql, array(':pcid' => $pcid)); if (isset($form_state['triggering_element'])) { $parents = $form_state['triggering_element']['#parents']; $parent_aid = $parents[count($parents) - 1]; $parent_value = $form_state['triggering_element']['#value']; uc_dropdown_attributes_remove_values((int) $parent_aid, $parent_value, $pcid, 'class', $form_state['values']); } $parent_value = null; // Initialize parent_value before the loop foreach ($attributes as $attribute) { if (isset($form_state['values']['attributes'])) { $parent_value = $form_state['values']['attributes'][$attribute->parent_aid]; } if ($parent_value) { $values = unserialize($attribute->parent_values); if ($form['attributes'][$attribute->parent_aid]['#type'] == 'checkboxes') { $parent_values = array_diff((array) $parent_value, array(0)); if (is_array($values) && count(array_intersect($parent_values, $values))) { if ($attribute->required) { $form['attributes'][$attribute->aid]['#required'] = TRUE; } } else { $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if (count($form['attributes'][$attribute->aid]['#value']) > 0) { $form['attributes'][$attribute->aid]['#value'] = array(); } } } else { if (is_array($values) && array_key_exists($parent_value, $values)) { if ($attribute->required) { $form['attributes'][$attribute->aid]['#required'] = TRUE; } } else { $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if ($form['attributes'][$attribute->aid]['#value'] != '') { $form['attributes'][$attribute->aid]['#value'] = ''; } } } } else { $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if (isset($form['attributes'][$attribute->aid]['#value']) && $form['attributes'][$attribute->aid]['#value'] != '') { $form['attributes'][$attribute->aid]['#value'] = ''; } } } return $form; } /** * Form build for product kits. * * Callback for $form['#after_build'] for product kits. Renders the dependent * attributes and stores the html as a Javascript array. */ function _uc_dropdown_attributes_kit_build(array $form, array &$form_state) { foreach ($form['products'] as $key => $value) { if (is_numeric($key)) { $type = uc_dropdown_attributes_dependency_type((int) $key); switch ($type) { case 'node': $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_attributes} WHERE nid=:nid'; $id = $key; $attributes = db_query($sql, array(':nid' => $key)); break; case 'class': $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_classes} WHERE pcid=:pcid'; $pcid = uc_dropdown_attributes_get_type((int) $key); $id = $pcid; $attributes = db_query($sql, array(':pcid' => $pcid)); break; default: $attributes = array(); break; } if (isset($form_state['triggering_element'])) { $parents = $form_state['triggering_element']['#parents']; $parent_aid = $parents[count($parents) - 1]; $parent_value = $form_state['triggering_element']['#value']; uc_dropdown_attributes_remove_values((int) $parent_aid, $parent_value, $id, $type, $form_state['values']['products'][$key]); } foreach ($attributes as $attribute) { $aid = $attribute->aid; $parent_aid = $attribute->parent_aid; $parent_value = $form_state['values']['products'][$key]['attributes'][$parent_aid]; if ($parent_value) { $values = unserialize($attribute->parent_values); if (is_array($values) && array_key_exists($parent_value, $values)) { if ($attribute->required) { $form['products'][$key]['attributes'][$aid]['#required'] = TRUE; } } else { $form['products'][$key]['attributes'][$aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if ($form['products'][$key]['attributes'][$aid]['#value'] != '') { $form['products'][$key]['attributes'][$aid]['#value'] = ''; } } } else { $form['products'][$key]['attributes'][$aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if ($form['products'][$key]['attributes'][$aid]['#value'] != '') { $form['products'][$key]['attributes'][$aid]['#value'] = ''; } } } } } return $form; } /** * Unset values of orphaned children. * * Called recursively to remove orphaned values from form_state. * * @param int $parent_aid * Attribute ID of the triggering element. * @param mixed $parent_value * Option ID of the value of the triggering element. * @param mixed $id * Node ID or product class ID. * @param string $type * 'node' or 'class'. * @param array $form_values * Part of the form_state array containing the attributes. */ function uc_dropdown_attributes_remove_values(int $parent_aid, $parent_value, $id, string $type, array &$form_values): void { switch ($type) { case 'node': $sql = 'SELECT aid, required, parent_values FROM {uc_dropdown_attributes} WHERE nid=:nid AND parent_aid=:parent_aid'; $children = db_query($sql, array( ':nid' => $id, ':parent_aid' => $parent_aid) ); break; case 'class': $sql = 'SELECT aid, required, parent_values FROM {uc_dropdown_classes} WHERE pcid=:pcid AND parent_aid=:parent_aid'; $children = db_query($sql, array( ':pcid' => $id, ':parent_aid' => $parent_aid) ); break; default: $children = array(); break; } foreach ($children as $child) { if ($child->required) { if (isset($form_values['attributes'][$child->aid])) { $value = $form_values['attributes'][$child->aid]; $form_values['attributes'][$child->aid] = ''; if ($value) { $values = unserialize($child->parent_values); if (is_array($values) && !in_array($parent_value, $values)) { uc_dropdown_attributes_remove_values((int) $child->aid, $value, $id, $type, $form_values); } } } } } } /** * Add the style to hide the attribute. * * @param string $html * HTML for the element. * * @return string * Modified element HTML. */ function uc_dropdown_attributes_post_render(string $html): string { $pos = strpos($html, '>'); if ($pos !== false) { $html = substr_replace($html, ' style="display:none;">', $pos, 1); } return $html; } /** * Retrieves the attribute dependencies. * * Callback to supply attribute dependencies to Javascript. * * @param int $nid * Node ID. * @param string $id * String containing attribute ID. * * @return object * JSON structure. */ function uc_dropdown_attributes_dependency(int $nid, string $id, string $parent_id) { $temp = explode('-', $id); if ($temp[1] == 'attributes') { $aid = $temp[2]; } else { $aid = $temp[4]; } $result = new stdClass(); $query = 'SELECT parent_values, required FROM {uc_dropdown_attributes} WHERE nid=:nid && aid=:aid'; $db_result = db_query($query, array(':nid' => $nid, ':aid' => $aid)); foreach ($db_result as $item) { $result->status = TRUE; $result->nid = $nid; $result->id = $id; $result->parent_id = $parent_id; $result->parent_values = unserialize($item->parent_values); $result->required = $item->required; drupal_json_output($result); return; } $pcid = uc_dropdown_attributes_get_type($nid); $query = 'SELECT parent_values, required FROM {uc_dropdown_classes} WHERE pcid=:pcid && aid=:aid'; $db_result = db_query($query, array(':pcid' => $pcid, ':aid' => $aid)); foreach ($db_result as $item) { $result->status = TRUE; $result->nid = $nid; $result->id = $id; $result->parent_id = $parent_id; $result->parent_values = unserialize($item->parent_values); $result->required = $item->required; drupal_json_output($result); return; } $result->status = FALSE; drupal_json_output($result); } /** * Create an attribute dependency. * * A public function that creates and stores an attribute dependency for a * product. * * @param int $nid * Node ID. * @param int $aid * Attribute ID of the dependent (child) attribute. * @param int $parent_aid * Attribute ID of the parent attribute. * @param array $options * Array of the Option IDs that trigger the dependent attribute. * @param bool $required * TRUE if the dependent (child) attribute is required when it appears and * FALSE if it is not required. */ function uc_dropdown_attributes_product_create_dependency(int $nid, int $aid, int $parent_aid, array $options, bool $required): void { $attribute = uc_attribute_load($aid); $dep = db_insert('uc_dropdown_attributes') ->fields(array( 'nid' => $nid, 'aid' => $aid, 'parent_aid' => $parent_aid, 'parent_values' => serialize($options), 'required' => $required, )) ->execute(); // Need to check to make sure attribute is not required all the time. $sql = 'SELECT nid, aid, required FROM {uc_product_attributes} WHERE nid=:nid && aid=:aid'; $result = db_query($sql, array(':nid' => $nid, ':aid' => $aid)); foreach ($result as $item) { if ($item->required == 1) { $dep = db_update('uc_product_attributes') ->fields(array( 'required' => 0, )) ->condition('nid', $item->nid) ->condition('aid', $item->aid) ->execute(); } } } /** * Create an attribute dependency for product classes. * * A public function that creates and stores an attribute dependency for * product classes. * * @param int $pcid * Product class ID. * @param int $aid * Attribute ID of the dependent (child) attribute. * @param int $parent_aid * Attribute ID of the parent attribute. * @param array $options * Array of the Option IDs that trigger the dependent attribute. * @param bool $required * TRUE if the dependent (child) attribute is required when it appears and * FALSE if it is not required. */ function uc_dropdown_attributes_class_create_dependency(int $pcid, int $aid, int $parent_aid, array $options, bool $required): void { $attribute = uc_attribute_load($aid); $dep = db_insert('uc_dropdown_classes') ->fields(array( 'pcid' => $pcid, 'aid' => $aid, 'parent_aid' => $parent_aid, 'parent_values' => serialize($options), 'required' => $required, )) ->execute(); // Need to check to make sure attribute is not required all the time. $sql = 'SELECT pcid, aid, required FROM {uc_class_attributes} WHERE pcid=:pcid && aid=:aid'; $result = db_query($sql, array(':pcid' => $pcid, ':aid' => $aid)); foreach ($result as $item) { if ($item->required == 1) { $dep = db_update('uc_class_attributes') ->fields(array( 'required' => 0, )) ->condition('pcid', $item->pcid) ->condition('aid', $item->aid) ->execute(); } } } /** * Implements hook_theme(). */ function uc_dropdown_attributes_theme(): array { return array( 'uc_dropdown_attributes_product' => array( 'render element' => 'form', ), 'uc_dropdown_attributes_class' => array( 'render element' => 'form', ), ); } /** * Retrieve product class from the node ID. * * @param int $nid * Node id. * * @return string * The type field from the node object. */ function uc_dropdown_attributes_get_type(int $nid): string { $sql = 'SELECT type FROM {node} WHERE nid=:nid'; $type = db_query($sql, array(':nid' => $nid))->fetchField(); return $type; } /** * Retrieve whether dependencies are defined by node or class. * * @param int $nid * Node id. * * @return string|null * 'node' for dependencies defined on the node level; * 'class' for dependencies defined on the product class; otherwise, NULL. */ function uc_dropdown_attributes_dependency_type(int $nid): ?string { $sql = 'SELECT COUNT(*) FROM {uc_dropdown_attributes} WHERE nid=:nid'; $count = db_query($sql, array(':nid' => $nid))->fetchField(); if ($count > 0) { return 'node'; } $pcid = uc_dropdown_attributes_get_type($nid); $sql = 'SELECT COUNT(*) FROM {uc_dropdown_classes} WHERE pcid=:pcid'; $count = db_query($sql, array(':pcid' => $pcid))->fetchField(); if ($count > 0) { return 'class'; } return NULL; } /** * Determine the correct theme to use for dependencies admin page. * * @return string * theme to use or empty string if default theme. */ function uc_dropdown_attributes_admin_theme(): string { if (variable_get('node_admin_theme', 0) == 1) { return (string)variable_get('admin_theme', ''); } return ''; } /** * Implements hook_form_FORM_ID_alter() for uc_order_edit_form(). */ function uc_dropdown_attributes_form_uc_order_edit_form_alter(array &$form, array &$form_state): void { if (isset($form_state['products_action']) && $form_state['products_action'] == 'add_product') { $product_type = $form['product_controls']['node']['#value']->type; if ($product_type == 'product_kit') { foreach ($form['product_controls']['sub_products'] as $nid => $product) { if (is_numeric($nid)) { $type = uc_dropdown_attributes_dependency_type((int) $nid); if (!is_null($type)) { uc_dropdown_attributes_order_product_alter((int) $nid, $form['product_controls']['sub_products'][$nid]['attributes'], $type); if (!in_array('_uc_dropdown_attributes_order_product_kit_build', $form['#after_build'])) { $form['#after_build'][] = '_uc_dropdown_attributes_order_product_kit_build'; } } } } } else{ $nid = $form['product_controls']['node']['#value']->nid; $type = uc_dropdown_attributes_dependency_type((int) $nid); if (!is_null($type)) { uc_dropdown_attributes_order_product_alter((int) $nid, $form['product_controls']['attributes'], $type); $form['nid'] = array( '#type' => 'hidden', '#value' => $nid, ); switch ($type) { case 'node': $form['#after_build'][] = '_uc_dropdown_attributes_order_product_build'; break; case 'class': $form['#after_build'][] = '_uc_dropdown_attributes_order_class_build'; break; } } } } } /** * Alter products on oder page in preparation for drop down attributes. * * Adds the 'Please select' and removes the default value. Ubercart does this * for required attributes but since these attributes can no longer be required * if the attributes are dependent then this reproduces the same thing. * * @param int $nid * Node ID. * @param array $form_attributes * Attributes part of the product form. * @param string $type * 'node' for dependencies defined on the node level; * 'class' for dependencies defined on the product class. */ function uc_dropdown_attributes_order_product_alter(int $nid, array &$form_attributes, string $type): void { switch ($type) { case 'node': $sql = 'SELECT aid, parent_aid, required FROM {uc_dropdown_attributes} WHERE nid=:nid'; $attributes = db_query($sql, array(':nid' => $nid)); break; case 'class': $sql = 'SELECT aid, parent_aid, required FROM {uc_dropdown_classes} WHERE pcid=:pcid'; $pcid = uc_dropdown_attributes_get_type($nid); $attributes = db_query($sql, array(':pcid' => $pcid)); break; } $parent_aids = array(); foreach ($attributes as $attribute) { $parent_aids[$attribute->parent_aid] = $attribute->parent_aid; if (isset($form_attributes[$attribute->aid]['#options']) && count($form_attributes[$attribute->aid]['#options']) && $attribute->required) { switch ($form_attributes[$attribute->aid]['#type']) { case 'select': $form_attributes[$attribute->aid]['#options'] = array('' => t('Please select')) + $form_attributes[$attribute->aid]['#options']; $form_attributes[$attribute->aid]['#default_value'] = ''; break; case 'radios': $form_attributes[$attribute->aid]['#default_value'] = ''; break; case 'checkboxes': $form_attributes[$attribute->aid]['#default_value'] = array(); break; } } } foreach ($parent_aids as $aid) { $form_attributes[$aid]['#ajax'] = array( 'callback' => 'uc_dropdown_attributes_order_ajax_callback', 'wrapper' => $form_attributes['#id'], ); } } /** * Ajax callback for order attribute selection form elements. */ function uc_dropdown_attributes_order_ajax_callback(array $form, array $form_state) { if (in_array('sub_products', $form_state['triggering_element']['#parents'])) { // This is a product kit. $nid = $form_state['triggering_element']['#parents'][2]; return $form['product_controls']['sub_products'][$nid]['attributes']; } return $form['product_controls']['attributes']; } /** * Form build for products for the order page. * * Callback for $form['#after_build'] for products. Adds the CSS to hide * the dependent attributes. */ function _uc_dropdown_attributes_order_product_build(array $form, array &$form_state) { $nid = $form['nid']['#value']; $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_attributes} WHERE nid=:nid'; $attributes = db_query($sql, array(':nid' => $nid)); if (isset($form_state['triggering_element'])) { $parents = $form_state['triggering_element']['#parents']; $parent_aid = $parents[count($parents)-1]; $parent_value = $form_state['triggering_element']['#value']; uc_dropdown_attributes_remove_values((int) $parent_aid, $parent_value, (int) $nid, 'node', $form_state['values']['product_controls']); } uc_dropdown_attributes_order_attribute_display( $attributes, $form['product_controls']['attributes'], $form_state['values']['product_controls']['attributes'] ); return $form; } /** * Form build for classes for the order page. * * Callback for $form['#after_build'] for product classes. * Adds the CSS to hide the dependent attributes. */ function _uc_dropdown_attributes_order_class_build(array $form, array &$form_state) { $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_classes} WHERE pcid=:pcid'; $pcid = uc_dropdown_attributes_get_type($form['nid']['#value']); $attributes = db_query($sql, array(':pcid' => $pcid)); if (isset($form_state['triggering_element'])) { $parents = $form_state['triggering_element']['#parents']; $parent_aid = $parents[count($parents)-1]; $parent_value = $form_state['triggering_element']['#value']; // Note that values are stored in input, not values. uc_dropdown_attributes_remove_values((int) $parent_aid, $parent_value, $pcid, 'class', $form_state['input']['product_controls']); } uc_dropdown_attributes_order_attribute_display( $attributes, $form['product_controls']['attributes'], $form_state['values']['product_controls']['attributes'] ); return $form; } /** * Form build for product kits for the order page. * * Callback for $form['#after_build'] for products. Adds the CSS to hide * the dependent attributes. */ function _uc_dropdown_attributes_order_product_kit_build(array $form, array &$form_state) { foreach ($form['product_controls']['sub_products'] as $nid => $product) { if (is_numeric($nid)) { $type = uc_dropdown_attributes_dependency_type((int) $nid); switch ($type) { case 'node': $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_attributes} WHERE nid=:nid'; $attributes = db_query($sql, array(':nid' => $nid)); if (isset($form_state['triggering_element']) && $nid == $form_state['triggering_element']['#parents'][2]) { $parents = $form_state['triggering_element']['#parents']; $parent_aid = $parents[count($parents)-1]; $parent_value = $form_state['triggering_element']['#value']; uc_dropdown_attributes_remove_values((int) $parent_aid, $parent_value, (int) $nid, 'node', $form_state['values']['product_controls']['sub_products'][$nid]); } uc_dropdown_attributes_order_attribute_display( $attributes, $form['product_controls']['sub_products'][$nid]['attributes'], $form_state['values']['product_controls']['sub_products'][$nid]['attributes'] ); break; case 'class': $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_classes} WHERE pcid=:pcid'; $pcid = uc_dropdown_attributes_get_type((int) $nid); $attributes = db_query($sql, array(':pcid' => $pcid)); if (isset($form_state['triggering_element']) && $nid == $form_state['triggering_element']['#parents'][2]) { $parents = $form_state['triggering_element']['#parents']; $parent_aid = $parents[count($parents)-1]; $parent_value = $form_state['triggering_element']['#value']; // Note that values are stored in input, not values. uc_dropdown_attributes_remove_values((int) $parent_aid, $parent_value, $pcid, 'class', $form_state['input']['product_controls']['sub_products'][$nid]); } uc_dropdown_attributes_order_attribute_display( $attributes, $form['product_controls']['sub_products'][$nid]['attributes'], $form_state['values']['product_controls']['sub_products'][$nid]['attributes'] ); break; } } } return $form; } /** * Alter display of attributes on the oder page. * * @param array $attributes * Array of attribute dependency objects. * @param array &$form_attributes * Attributes part of the product form. * @param array $form_state_attributes * Attributes part of the product form_state. */ function uc_dropdown_attributes_order_attribute_display(array $attributes, array &$form_attributes, array $form_state_attributes): void { foreach ($attributes as $attribute) { if (!isset($form_state_attributes[$attribute->parent_aid])) { continue; } $parent_value = $form_state_attributes[$attribute->parent_aid]; if ($parent_value) { // A value has been entered in parent attribute. $values = unserialize($attribute->parent_values); if (is_array($values) && array_key_exists($parent_value, $values)) { // Show dependent attribute. if ($attribute->required) { $form_attributes[$attribute->aid]['#required'] = TRUE; } } else { // Hide dependent attribute. $form_attributes[$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if (isset($form_attributes[$attribute->aid]['#value']) && $form_attributes[$attribute->aid]['#value'] != '') { $form_attributes[$attribute->aid]['#value'] = ''; } } } else { $form_attributes[$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if (isset($form_attributes[$attribute->aid]['#value']) && $form_attributes[$attribute->aid]['#value'] != '') { $form_attributes[$attribute->aid]['#value'] = ''; } } } } /** * Form build for classes in product kits for the order page. * * Callback for $form['#after_build'] for product classes. Adds * the CSS to hide the dependent attributes. */ function _uc_dropdown_attributes_order_class_kit_build(array $form, array &$form_state) { $sql = 'SELECT aid, parent_aid, parent_values, required FROM {uc_dropdown_classes} WHERE pcid=:pcid'; $pcid = uc_dropdown_attributes_get_type($form['nid']['#value']); $attributes = db_query($sql, array(':pcid' => $pcid)); if (isset($form_state['triggering_element'])) { $parents = $form_state['triggering_element']['#parents']; $parent_aid = $parents[count($parents)-1]; $parent_value = $form_state['triggering_element']['#value']; uc_dropdown_attributes_remove_values((int) $parent_aid, $parent_value, $pcid, 'class', $form_state['values']['product_controls']); } $parent_value = null; foreach ($attributes as $attribute) { if (isset($form_state['values']['product_controls']['attributes'][$attribute->parent_aid])) { $parent_value = $form_state['values']['product_controls']['attributes'][$attribute->parent_aid]; } if ($parent_value) { $values = unserialize($attribute->parent_values); if (is_array($values) && array_key_exists($parent_value, $values)) { if ($attribute->required) { $form['product_controls']['attributes'][$attribute->aid]['#required'] = TRUE; } } else { $form['product_controls']['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if (isset($form['product_controls']['attributes'][$attribute->aid]['#value']) && $form['product_controls']['attributes'][$attribute->aid]['#value'] != '') { $form['product_controls']['attributes'][$attribute->aid]['#value'] = ''; } } } else { $form['product_controls']['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render'; // Bug fix: Always clear hidden attribute values. if (isset($form['product_controls']['attributes'][$attribute->aid]['#value']) && $form['product_controls']['attributes'][$attribute->aid]['#value'] != '') { $form['product_controls']['attributes'][$attribute->aid]['#value'] = ''; } } } return $form; } /** * Implements hook_node_load(). * * Ubercart fails to load attributes for product classes. */ function uc_dropdown_attributes_node_load(array $nodes): void { foreach ($nodes as $node) { if (uc_product_is_product($node)) { if (empty($node->attributes)) { $node->attributes = uc_class_get_attributes($node->type); } } } }BIG A KM 4-40 In-Place Infrared Asphalt Recycling Heater Trailer 40 sq ft For Sale | Asphalt Sealcoating Direct

You are here

BIG A KM 4-40 In-Place Infrared Asphalt Recycling Heater Trailer 40 sq ft

image: BIG A KM 4-40 unfolded operational mode
image: BIG A KM 4-40 folded trailer mode
MPN: 
BIG-A-KM-4-40
SKU: BIG-A-KM-4-40
BIG A Asphalt Infrared Heater Trailer 40 sq ft
List price: $26049
$24049
 
Free Shipping to lower US 48
Contract Available
Built on Order
Note: Lift gate service is unavailable for this product.
Shipping Terminal: You pick up the item at the designated shipping terminal. Alternate Location: We deliver to a location of your choosing where a fork lift is available.
To qualify for free shipping, you fully understand and agree to the terms of shipping. Call us if you have questions.
To qualify for free shipping, you fully understand and agree to the terms of shipping. Call us if you have questions.
Quick Overview: 
  • Quad heating zones (2) 2’ x 4’ and (2) 3’ x 4’ totaling 40 sq ft
  • Fold-up trailer design for pull-behind transport
  • Holds 3 propane bottles (not included)

Description

In-Place infrared asphalt recycling heaters are one of the most efficient and permanent ways of repairing cracks, alligator cracks, dips and bird baths in asphalt roads or parking lots. Chances are, you already know this and that's why you're looking at our BIG A KM 4-40. What you get with this in-place infrared recycler heater is a fantastic 40 sq ft of coverage, allowing you to heat almost twice as much area as our BIG A KM 2-18X. In addition, this unit is designed as a unique pull-behind trailer unit weighing in at only 810 lbs so you can pull it with even the most modest of pickup trucks.

Purpose

The main purpose of using an in-place infrared asphalt recycling heater, is to recycle existing asphalt without cutting out the old, by raising it to the temperature that is required for proper hot mix asphalt. (usually around 350º F)

By raising the temperature of the existing asphalt and softening the existing binder, it allows the old asphalt to be re-used, rejuvenated and mixed with new patch to creates a "like new" repairs, without the overhead of cutting out the entire asphalt section and filling it back in with all new hot asphalt patch. In reality, if you have an infrared asphalt heater, the only time a full cut and replacement is necessary is when the sub-grade needs fixing. Why go to the trouble of cutting and removing the old when you can simply just re-use it in place?

Benefit

The benefit to owning a BIG A KM 4-40 In-Place Infrared Asphalt Recycling Heater is the unique trailer design that folds up and allows you to transport it with a regular pick-up truck. Throw a hot box reclaimer ready with hot mix asphalt in the bed of the pickup truck and you ready to do some serious asphalt patch work.

Costing several thousand less than our larger 48 sq ft model, this 40 sq ft in-place infrared recycling heater is perfect for city maintenance and contracting professionals on a very strict budget who need something larger or more convenient than a 22 sq ft model.

Usage

Using a portable infrared asphalt heater isn't hard, but does require steps to ensure the right work is performed.

  1. Heat the existing asphalt
  2. Scarify the existing asphalt
  3. Add rejuvenator asphalt oils
  4. Add new hot asphalt material
  5. Mix everything together
  6. Level the asphalt
  7. Compact with a vibratory machine or roller
  8. Move to the next section

Heat Cycling Technology

The BIG A KM 2-18X heating is controlled by an automatic timed on/off heating cycle, setup to be 53 seconds on and 10 seconds off. This maintains proper temperature within the heating elements and allows a more efficient use of fuel consumption. By alternating the heating cycles on this portable infrared asphalt heater, it minimizes the chance of burning the asphalt. In addition, this unit is built with a one shot timer that will turn off after 10 minutes of heating to eliminate the potential cooking or overheating of the asphalt.

Watch the BIG A In-Place Infrared Asphalt Recycling Heater In Action

Warranty

1 Year Limited

This product comes with a 1 year (12 month) limited warranty from the time of purchase.

Parts that are not explicitly made by this manufacturer, may come with a different warranty period or policy. See this products respective operations manual for a detailed version of the warranty policy and a full list of items that are covered or may become void due to improper maintenance.

Features

  • 4 independently controlled heating zones

  • Six (6) 1” Ceramic Refractory Blanket across all four zones

  • Mounted on four (4) independent swivel 4 inch steel casters

  • Automatic electric pilot ignition

  • Heat Cycling technology to ensure proper and efficient fuel flow

  • Fine tune control of air fuel mixture

  • Emergency stop button located on control panel

  • Keyed run switch to prevent unauthorized access

  • Includes a controllable 12V DC fan blower to provide air movement throughout the zones

  • 2.5 watt ultra thin solar charger for battery maintenance

You Might Like

Specifications

General

Heating Elements

Six (6) 1” Ceramic Refractory Blankets

Heating Zones

Quad (2) 2’ x 4’ and (2) 3’ x 4’

Heating Area

40 sq ft

Fuel

Propane

Fuel Containers

Three (3) LP Bottles (not included) 30lb recommended

Fuel Consumption

20 Lbs. per hour

Electrical

12V Deep Cycle Marin Gel Battery

Heat Output

700,000 BTU

Trailer

Hitch Coupling

2"

Hitch Wiring Harness

Four standard wire harness

Suspension

Spring Axle

Leaf Springs

4 @ 1,250 lbs capacity each

Axle Capacity

2,000 lbs

Three Ply Tires

580 lbs capacity each

Stop, Turn and Tail Lights

Included

Side Marker Lights

Included

Unit Specifications

Working Dimensions

5’ L x 8’ W

Transit Dimensions

8’4" L x 6’6” W

Weight

810 lbs

Construction

16 ga mild steel

Color

Chrome Yellow

Documents

Compare Similar Products

BIG A Asphalt Infrared Heater Trailer 40 sq ft BIG A Asphalt Infrared Heater Trailer 48 sq ft
$24049 $25987
image: BIG A KM 4-40 unfolded operational mode image: BIG A KM 4-48 Unfolded Operational Mode
BIG-A-KM-4-40 BIG-A-KM-4-48
General

Heating Elements

Six (6) 1” Ceramic Refractory Blankets

Heating Zones

Quad (2) 2’ x 4’ and (2) 3’ x 4’

Heating Area

40 sq ft

Fuel

Propane

Fuel Containers

Three (3) LP Bottles (not included) 30lb recommended

Fuel Consumption

20 Lbs. per hour

Electrical

12V Deep Cycle Marin Gel Battery

Heat Output

700,000 BTU

Trailer

Hitch Coupling

2"

Hitch Wiring Harness

Four standard wire harness

Suspension

Spring Axle

Leaf Springs

4 @ 1,250 lbs capacity each

Axle Capacity

2,000 lbs

Three Ply Tires

580 lbs capacity each

Stop, Turn and Tail Lights

Included

Side Marker Lights

Included

Unit Specifications

Working Dimensions

5’ L x 8’ W

Transit Dimensions

8’4" L x 6’6” W

Weight

810 lbs

Construction

16 ga mild steel

Color

Chrome Yellow

General

Heating Elements

Four (4) 1” Ceramic Refractory Blankets

Heating Zones

Quad (2) 2’ x 4’ and (2) 4’ x 4’

Heating Area

48 sq ft

Fuel

Propane

Fuel Containers

Three (3) LP Bottles (not included) 30lb recommended

Fuel Consumption

25 Lbs. per hour

Electrical

12V Deep Cycle Marin Gel Battery

Heat Output

840,000 BTU

Trailer

Hitch Coupling

2"

Hitch Wiring Harness

Four standard wire harness

Suspension

Spring Axle

Leaf Springs

4 @ 1,250 lbs capacity each

Axle Capacity

2,000 lbs

Three Ply Tires

580 lbs capacity each

Stop, Turn and Tail Lights

Included

Side Marker Lights

Included

Unit Specifications

Working Dimensions

6’ L x 8’ W

Transit Dimensions

8’4" L x 7’6” W

Weight

860 lbs

Construction

16 ga mild steel

Color

Chrome Yellow

You are here

Shipping Weight
Weight: 810 lb
Shipping Dimensions
Dimensions: 60 in × 96 in × 60 in