uc_product.module in Ubercart 8.4
Same filename and directory in other branches
The product module for Ubercart.
Provides information that is common to all products, and user-defined product classes for more specification.
File
uc_product/uc_product.moduleView source
<?php
/**
* @file
* The product module for Ubercart.
*
* Provides information that is common to all products, and user-defined product
* classes for more specification.
*/
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\node\NodeTypeInterface;
use Drupal\node\Entity\Node;
use Drupal\uc_cart\Entity\CartItem;
use Drupal\uc_product\Event\ProductLoadEvent;
use Drupal\uc_product\Form\AddToCartForm;
/**
* Implements hook_page_attachments().
*/
function uc_product_page_attachments(&$page) {
$page['#attached']['library'][] = 'uc_product/uc_product.styles';
}
/**
* Implements hook_theme().
*/
function uc_product_theme() {
return [
'uc_product_price' => [
'render element' => 'element',
'file' => 'uc_product.theme.inc',
'function' => 'theme_uc_product_price',
],
];
}
/**
* Theme preprocess function for Ubercart product image fields.
*/
function uc_product_preprocess_field(&$variables) {
if ($variables['element']['#formatter'] == 'uc_product_image') {
$variables['attributes']['class'][] = 'uc-product-image';
}
}
/**
* Implements hook_node_insert().
*/
function uc_product_node_insert($node) {
// Set sample values for Devel Generate.
if (!empty($node->devel_generate) && uc_product_is_product($node)) {
$fields = [
'model',
'cost',
'price',
'weight',
'dimensions',
'shippable',
];
foreach ($fields as $field) {
$node->{$field}
->generateSampleItems();
}
}
uc_product_node_update($node);
}
/**
* Implements hook_node_update().
*/
function uc_product_node_update($node) {
if (!uc_product_is_product($node)) {
return;
}
$connection = \Drupal::database();
$connection
->merge('uc_products')
->keys([
'vid' => $node
->getRevisionId(),
'nid' => $node
->id(),
])
->fields([
'model' => $node->model->value,
'cost' => $node->cost->value,
'price' => $node->price->value,
'weight' => $node->weight->value,
'weight_units' => $node->weight->units,
'length' => $node->dimensions->length,
'width' => $node->dimensions->width,
'height' => $node->dimensions->height,
'length_units' => $node->dimensions->units,
'pkg_qty' => $node->pkg_qty->value,
'default_qty' => $node->default_qty->value,
'shippable' => (int) $node->shippable->value,
])
->execute();
}
/**
* Implements hook_node_load().
*/
function uc_product_node_load($nodes) {
$vids = [];
foreach ($nodes as $node) {
if (uc_product_is_product($node)) {
$vids[$node
->id()] = $node
->getRevisionId();
}
}
if (!empty($vids)) {
$connection = \Drupal::database();
$result = $connection
->query('SELECT nid, model, cost, price, weight, weight_units, length, width, height, length_units, pkg_qty, default_qty, shippable FROM {uc_products} WHERE vid IN (:vids[])', [
':vids[]' => $vids,
]);
$fields = [
'model',
'cost',
'price',
'weight',
'pkg_qty',
'default_qty',
'shippable',
];
foreach ($result as $record) {
foreach ($fields as $name) {
$nodes[$record->nid]->{$name}->value = $record->{$name};
}
$nodes[$record->nid]->weight->units = $record->weight_units;
$nodes[$record->nid]->dimensions->length = $record->length;
$nodes[$record->nid]->dimensions->width = $record->width;
$nodes[$record->nid]->dimensions->height = $record->height;
$nodes[$record->nid]->dimensions->units = $record->length_units;
$nodes[$record->nid]->display_price = $nodes[$record->nid]->price->value;
$nodes[$record->nid]->display_price_suffixes = [];
}
}
}
/**
* Gets a specific, cloned, altered variant of a product node.
*
* Generally, you should always use uc_product_load_variant() instead,
* except when node_load() cannot be invoked, e.g. when implementing
* hook_node_load().
*
* @param $node
* The product node to alter. Throws an exception if this is already a
* product variant.
* @param array $data
* Optional data to add to the product before invoking the alter hooks.
*
* @return
* An variant of the product, altered based on the provided data.
*/
function _uc_product_get_variant($node, $data = FALSE) {
if (!empty($node->variant)) {
throw new Exception(t('Cannot create a variant of a variant.'));
}
$node = clone $node;
if (!empty($data)) {
$node->data = $data;
}
// Ensure that $node->data is an array (user module leaves it serialized).
if (isset($node->data) && !is_array($node->data)) {
$node->data = unserialize($node->data);
}
\Drupal::moduleHandler()
->alter('uc_product', $node);
$node->variant = TRUE;
if (!isset($node->data['module'])) {
$node->data['module'] = 'uc_product';
}
return $node;
}
/**
* Loads a specific altered variant of a product node.
*
* The (possibly cached) base product remains unaltered.
*
* @param $nid
* The nid of the product to load.
* @param array $data
* Optional data to add to the product before invoking the alter hooks.
*
* @return
* A variant of the product, altered based on the provided data, or FALSE
* if the node is not found.
*/
function uc_product_load_variant($nid, $data = FALSE) {
if ($node = Node::load($nid)) {
return _uc_product_get_variant($node, $data);
}
else {
return FALSE;
}
}
/**
* Implements hook_uc_product_alter().
*
* Invokes rules event to allow product modifications.
*/
function uc_product_uc_product_alter(&$node) {
/* rules_invoke_event('uc_product_load', $node); */
$event = new ProductLoadEvent($node);
\Drupal::service('event_dispatcher')
->dispatch($event::EVENT_NAME, $event);
}
/**
* Implements hook_node_delete().
*/
function uc_product_node_delete($node) {
if (!uc_product_is_product($node)) {
return;
}
$features = uc_product_feature_load_multiple($node
->id());
foreach ($features as $feature) {
uc_product_feature_delete($feature->pfid);
}
$connection = \Drupal::database();
$connection
->delete('uc_products')
->condition('nid', $node
->id())
->execute();
}
/**
* Dynamically replaces parts of a product view based on form input.
*
* If a module adds an input field to the add-to-cart form which affects some
* aspect of a product (e.g. display price or weight), it should attach an
* #ajax callback to that form element, and use this function in the callback
* to build updated content for the affected fields.
*
* @param \Drupal\Core\Ajax\AjaxResponse $response
* The response object to add the Ajax commands to.
* @param $form_state
* The current form state. This must contain a 'variant' entry in the
* 'storage' array which represents the product as configured by user input
* data. In most cases, this is provided automatically by
* AddToCartForm::validateForm().
* @param $keys
* An array of keys in the built product content which should be replaced
* (e.g. 'display_price').
*/
function uc_product_view_ajax_commands(AjaxResponse $response, $form_state, $keys) {
if (\Drupal::config('uc_product.settings')
->get('update_node_view') && $form_state
->has('variant')) {
$node_div = '.uc-product-' . $form_state
->get('variant')->nid;
$build = node_view($form_state
->get('variant'));
foreach ($keys as $key) {
if (isset($build[$key])) {
$id = $node_div . '.' . str_replace('_', '-', $key);
$response
->addCommand(new ReplaceCommand($id, drupal_render($build[$key])));
}
}
}
}
/**
* Implements hook_node_view().
*/
function uc_product_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
if (!uc_product_is_product($node)) {
return;
}
uc_product_view_product($build, $node, $display, $view_mode);
}
/**
* Renders product related content for product-type modules.
*/
function uc_product_view_product(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
// Give modules a chance to alter this product. If it is a variant, this will
// have been done already by uc_product_load_variant(), so we check a flag to
// be sure not to alter twice -- cf. entity_prepare_view().
$variant = empty($node->variant) ? _uc_product_get_variant($node) : $node;
// Skip the add to cart form in comment reply forms.
if (\Drupal::routeMatch()
->getRouteName() != 'comment.reply') {
// Build the 'add to cart' form, and use the updated variant based on data
// provided by the form (e.g. attribute default options).
if (\Drupal::moduleHandler()
->moduleExists('uc_cart') && $variant
->id() && empty($variant->data['display_only'])) {
$form_object = new AddToCartForm($node
->id());
$add_to_cart_form = \Drupal::formBuilder()
->getForm($form_object, $variant);
if (\Drupal::config('uc_product.settings')
->get('update_node_view')) {
$variant = $add_to_cart_form['node']['#value'];
}
}
}
$build['display_price'] = [
'#theme' => 'uc_product_price',
'#value' => $variant->display_price,
'#suffixes' => $variant->display_price_suffixes,
'#attributes' => [
'class' => [
'display-price',
'uc-product-' . $node
->id(),
],
],
];
if (isset($add_to_cart_form)) {
$build['add_to_cart'] = $add_to_cart_form;
}
$build['#node'] = $variant;
}
/**
* Implements hook_theme_suggestions_HOOK_alter().
*
* Product classes default to using node--product.html.twig if they don't have
* their own template.
*/
function uc_product_theme_suggestions_node_alter(array &$suggestions, array $variables) {
if (uc_product_is_product($variables['elements']['#node'])) {
$suggestions[] = 'node__product';
}
}
/**
* Implements hook_preprocess_html().
*
* Adds a body class to product node pages.
*
* @see html.html.twig
*/
function uc_product_preprocess_html(&$variables) {
$request = \Drupal::request();
if ($request->attributes
->has('node')) {
if (uc_product_is_product($request->attributes
->get('node'))) {
$variables['attributes']['class'][] = 'uc-product-node';
}
}
}
/**
* Implements hook_form_FORM_ID_alter() for node_type_form().
*
* Adds a default image field setting to product content types.
*/
function uc_product_form_node_type_form_alter(&$form, FormStateInterface $form_state) {
/** @var \Drupal\node\NodeTypeInterface $type */
$type = $form_state
->getFormObject()
->getEntity();
$form['uc_product'] = [
'#type' => 'details',
'#title' => t('Ubercart product settings'),
'#group' => 'additional_settings',
'#tree' => TRUE,
'#attached' => [
'library' => [
'uc_product/uc_product.scripts',
],
],
];
$form['uc_product']['product'] = [
'#type' => 'checkbox',
'#title' => t('Content type is a product'),
'#default_value' => $type
->getThirdPartySetting('uc_product', 'product', FALSE),
'#weight' => -10,
];
// Shippable.
$form['uc_product']['shippable'] = [
'#type' => 'checkbox',
'#title' => t('Product is shippable'),
'#default_value' => $type
->getThirdPartySetting('uc_product', 'shippable', TRUE),
'#description' => t('This setting can still be overridden on the node form.'),
'#weight' => -5,
];
// Image field.
$entity_type = $type
->getEntityType();
if (!empty($entity_type)) {
$options = [
'' => t('None'),
];
$instances = \Drupal::service('entity_field.manager')
->getFieldDefinitions('node', $type
->id());
foreach ($instances as $field_name => $instance) {
if ($instance
->getType() == 'image') {
$options[$field_name] = $instance
->label();
}
}
$form['uc_product']['image_field'] = [
'#type' => 'select',
'#title' => t('Product image field'),
'#default_value' => $type
->getThirdPartySetting('uc_product', 'image_field', 'uc_product_image'),
'#options' => $options,
'#description' => t('The selected field will be used on Ubercart pages to represent the products of this content type.'),
'#weight' => -4,
];
}
$form['#entity_builders'][] = 'uc_product_form_node_type_form_builder';
}
/**
* Entity builder for the node type form with product options.
*
* @see uc_product_form_node_type_form_alter()
*/
function uc_product_form_node_type_form_builder($entity_type, NodeTypeInterface $type, &$form, FormStateInterface $form_state) {
$type
->setThirdPartySetting('uc_product', 'product', (bool) $form_state
->getValue([
'uc_product',
'product',
]));
$type
->setThirdPartySetting('uc_product', 'shippable', (bool) $form_state
->getValue([
'uc_product',
'shippable',
]));
$type
->setThirdPartySetting('uc_product', 'image_field', $form_state
->getValue([
'uc_product',
'image_field',
]));
}
/**
* Implements hook_entity_extra_field_info().
*/
function uc_product_entity_extra_field_info() {
$extra = [];
foreach (uc_product_types() as $type) {
$extra['node'][$type] = [
'display' => [
'display_price' => [
'label' => t('Display price'),
'description' => t('High-visibility sell price.'),
'weight' => -1,
],
'add_to_cart' => [
'label' => t('Add to cart form'),
'description' => t('Add to cart form'),
'weight' => 10,
],
],
];
}
return $extra;
}
/**
* Implements hook_entity_bundle_field_info().
*/
function uc_product_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
if ($entity_type
->id() == 'node' && uc_product_is_product($bundle)) {
$fields['model'] = BaseFieldDefinition::create('string')
->setLabel(t('SKU'))
->setDescription(t('Product SKU/model.'))
->setRequired(TRUE)
->setCustomStorage(TRUE)
->setDefaultValue('')
->setSetting('max_length', 40)
->setDisplayOptions('form', [
'type' => 'string_textfield',
'weight' => 1,
'settings' => [
'size' => 32,
],
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'type' => 'string',
'label' => 'inline',
'weight' => 1,
])
->setDisplayConfigurable('view', TRUE);
$fields['cost'] = BaseFieldDefinition::create('uc_price')
->setLabel(t('Cost'))
->setDescription(t("Your store's cost."))
->setRequired(TRUE)
->setCustomStorage(TRUE)
->setDefaultValue('0.00')
->setSetting('min', 0)
->setDisplayOptions('form', [
'region' => 'hidden',
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'region' => 'hidden',
])
->setDisplayConfigurable('view', TRUE);
$fields['price'] = BaseFieldDefinition::create('uc_price')
->setLabel(t('Price'))
->setDescription(t('Customer purchase price.'))
->setRequired(TRUE)
->setCustomStorage(TRUE)
->setDefaultValue('0.00')
->setSetting('min', 0)
->setDisplayOptions('form', [
'type' => 'uc_price',
'weight' => 2,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'type' => 'uc_price',
'label' => 'inline',
'weight' => 2,
])
->setDisplayConfigurable('view', TRUE);
$fields['shippable'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Shippable'))
->setCustomStorage(TRUE)
->setDefaultValue(NodeType::load($bundle)
->getThirdPartySetting('uc_product', 'shippable', TRUE))
->setSetting('on_label', t('Product is shippable'))
->setSetting('off_label', t('Product is not shippable'))
->setDisplayOptions('form', [
'type' => 'boolean_checkbox',
'settings' => [
'display_label' => TRUE,
],
'weight' => 3,
])
->setDisplayConfigurable('form', TRUE);
$fields['weight'] = BaseFieldDefinition::create('uc_weight')
->setLabel(t('Weight'))
->setRequired(TRUE)
->setCustomStorage(TRUE)
->setDefaultValue([
'value' => 0,
'units' => \Drupal::config('uc_store.settings')
->get('weight.units'),
])
->setDisplayOptions('form', [
'type' => 'uc_weight',
'weight' => 4,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'type' => 'uc_weight',
'label' => 'inline',
'weight' => 4,
])
->setDisplayConfigurable('view', TRUE);
$fields['dimensions'] = BaseFieldDefinition::create('uc_dimensions')
->setLabel(t('Dimensions'))
->setRequired(TRUE)
->setCustomStorage(TRUE)
->setDefaultValue([
'length' => 0,
'width' => 0,
'height' => 0,
'units' => \Drupal::config('uc_store.settings')
->get('length.units'),
])
->setDisplayOptions('form', [
'type' => 'uc_dimensions',
'weight' => 5,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'type' => 'uc_dimensions',
'label' => 'inline',
'weight' => 5,
])
->setDisplayConfigurable('view', TRUE);
$fields['pkg_qty'] = BaseFieldDefinition::create('integer')
->setLabel(t('Maximum package quantity'))
->setDescription(t('At most, how many of these items can fit in your largest box? Orders that exceed this value will be split into multiple packages when retrieving shipping quotes.'))
->setRequired(TRUE)
->setCustomStorage(TRUE)
->setDefaultValue('1')
->setDisplayOptions('form', [
'region' => 'hidden',
])
->setDisplayConfigurable('form', TRUE);
$fields['default_qty'] = BaseFieldDefinition::create('integer')
->setLabel(t('Default quantity to add to cart'))
->setDescription(t('Use 0 to disable the quantity field next to the add to cart button.'))
->setRequired(TRUE)
->setCustomStorage(TRUE)
->setDefaultValue('1')
->setDisplayOptions('form', [
'region' => 'hidden',
])
->setDisplayConfigurable('form', TRUE);
return $fields;
}
}
/**
* Implements hook_uc_product_types().
*/
function uc_product_uc_product_types() {
$query = \Drupal::entityQuery('node_type')
->condition('third_party_settings.uc_product.product', TRUE);
return $query
->execute();
}
/**
* Implements hook_uc_store_status().
*
* Displays the status of the product image handlers.
*
* @see uc_product_image_defaults()
*/
function uc_product_uc_store_status() {
$instances = \Drupal::service('entity_field.manager')
->getFieldDefinitions('node', 'product');
$field = NodeType::load('product')
->getThirdPartySetting('uc_product', 'image_field', 'uc_product_image');
if (isset($instances[$field])) {
$status = 'ok';
$description = t('Product image support has been automatically configured by Ubercart.');
}
else {
$status = 'warning';
$description = t('<a href=":url">Click here</a> to automatically configure core image support.', [
':url' => Url::fromRoute('uc_product.image_defaults')
->toString(),
]) . ' ' . t('(This action is not required and should not be taken if you do not need images or have implemented your own image support.)');
}
return [
[
'status' => $status,
'title' => t('Images'),
'desc' => $description,
],
];
}
/**
* Implements hook_uc_cart_display().
*/
function uc_product_uc_cart_display($item) {
$node = $item->nid->entity;
$element = [];
$element['nid'] = [
'#type' => 'value',
'#value' => $node
->id(),
];
$element['module'] = [
'#type' => 'value',
'#value' => 'uc_product',
];
$element['remove'] = [
'#type' => 'submit',
'#value' => t('Remove'),
];
if ($node
->access('view')) {
$element['title'] = [
'#type' => 'link',
'#title' => $item->title,
'#url' => $node
->toUrl(),
];
}
else {
$element['title'] = [
'#markup' => $item->title,
];
}
$element['#total'] = $item->price->value * $item->qty->value;
$element['#suffixes'] = [];
$element['data'] = [
'#type' => 'hidden',
'#value' => serialize($item->data
->first()
->toArray()),
];
$element['qty'] = [
'#type' => 'uc_quantity',
'#title' => t('Quantity'),
'#title_display' => 'invisible',
'#default_value' => $item->qty->value,
'#allow_zero' => TRUE,
];
$element['description'] = [
'#markup' => '',
];
if ($description = uc_product_get_description($item)) {
$element['description']['#markup'] = $description;
}
return $element;
}
/**
* Implements hook_uc_update_cart_item().
*/
function uc_product_uc_update_cart_item($nid, $data = [], $qty, $cid = NULL) {
$cart = \Drupal::service('uc_cart.manager')
->get($cid);
$result = \Drupal::entityQuery('uc_cart_item')
->condition('cart_id', $cart
->getId())
->condition('nid', $nid)
->condition('data', serialize($data))
->execute();
if (!empty($result)) {
$item = CartItem::load(current(array_keys($result)));
if ($item->qty->value != $qty) {
$item->qty->value = $qty;
$item
->save();
// Invalidate the cache.
Cache::invalidateTags([
'uc_cart:' . $cid,
]);
}
}
}
/**
* Implements hook_uc_add_to_cart_data().
*/
function uc_product_uc_add_to_cart_data($form_values) {
if (isset($form_values['nid'])) {
$node = Node::load($form_values['nid']);
return [
'shippable' => $node->shippable->value,
'type' => $node
->getType(),
];
}
else {
return [
'shippable' => NodeType::load('product')
->getThirdPartySetting('uc_product', 'shippable', TRUE),
'type' => 'product',
];
}
}
/**
* Returns an array of product node types.
*/
function uc_product_types() {
return \Drupal::moduleHandler()
->invokeAll('uc_product_types');
}
/**
* Determines whether or not a given node or node type is a product.
*
* @param mixed $node
* Either a full node object/array, a node ID, or a node type.
*
* @return bool
* TRUE or FALSE indicating whether or not a node type is a product node type.
*/
function uc_product_is_product($node) {
// Load the node object if we received an integer as an argument.
if (is_numeric($node)) {
$node = Node::load($node);
}
// Determine the node type based on the data type of $node.
if (is_object($node)) {
$type = $node
->getType();
}
elseif (is_array($node)) {
$type = $node['type'];
}
elseif (is_string($node)) {
$type = $node;
}
else {
// If no node type was found, go ahead and return FALSE.
return FALSE;
}
// Return TRUE or FALSE depending on whether or not the node type is in the
// product types array.
return in_array($type, uc_product_types());
}
/**
* Determines whether or not a given form array is a product node form.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state object to examine.
*
* @return bool
* TRUE or FALSE indicating whether or not the form is a product node form.
*/
function uc_product_is_product_form(FormStateInterface $form_state) {
$bundle = $form_state
->getFormObject()
->getEntity()
->bundle();
return uc_product_is_product($bundle);
}
/**
* Gets all models of a product (node).
*
* Gathers any modules' models on this node, then add the node's SKU and the
* optional 'Any' option.
*
* @param int $nid
* The node ID of the product.
* @param string $add_blank
* String to use for the initial blank entry. If not desired, set to NULL
* or FALSE. Make sure to localize the string first. Defaults to '- Any -'.
*
* @return array
* An associative array of model numbers. The key for '- Any -' is the empty
* string.
*/
function uc_product_get_models($nid, $add_blank = TRUE) {
// Get any modules' SKUs on this node.
$models = \Drupal::moduleHandler()
->invokeAll('uc_product_models', [
$nid,
]);
// Add the base SKU of the node.
$connection = \Drupal::database();
$models[] = $connection
->query('SELECT model FROM {uc_products} WHERE nid = :nid', [
':nid' => $nid,
])
->fetchField();
// Now we map the SKUs to the keys, for form handling, etc.
$models = array_combine($models, $models);
// Sort the SKUs.
asort($models);
// And finally, we prepend 'Any' so it's the first option.
if (!empty($add_blank) || $add_blank === '') {
if ($add_blank === TRUE) {
$add_blank = t('- Any -');
}
return [
'' => $add_blank,
] + $models;
}
return $models;
}
/**
* Returns a product node's first attached image.
*
* @param int $nid
* The node's id.
* @param string $style
* The image style used to format the image. 'uc_product' by default.
*
* @return array
* A renderable array of the first product image, linked to the
* product node, or an empty array if no image is available.
*/
function uc_product_get_picture($nid, $style = 'uc_product') {
$product = Node::load($nid);
if (!$product) {
return [];
}
$field_name = $product->type->entity
->getThirdPartySetting('uc_product', 'image_field', 'uc_product_image');
$output = [];
if ($field_name && !empty($product->{$field_name})) {
$elements = $product->{$field_name}
->view([
'label' => 'hidden',
'type' => 'image',
'settings' => [
'image_link' => 'content',
'image_style' => $style,
],
]);
// Extract the part of the render array we need.
$output = isset($elements[0]) ? $elements[0] : [];
if (isset($elements['#access'])) {
$output['#access'] = $elements['#access'];
}
}
return $output;
}
/**
* Returns HTML for the product description.
*
* Modules adding information use hook_uc_product_description() and modules
* wanting to alter the output before rendering can do so by implementing
* hook_uc_product_description_alter(). By default, all descriptions supplied
* by modules via hook_uc_product_description() are concatenated together.
*
* @param $product
* Product.
*
* @return string
* HTML rendered product description.
*/
function uc_product_get_description($product) {
// Run through implementations of hook_uc_product_description().
$description = \Drupal::moduleHandler()
->invokeAll('uc_product_description', [
$product,
]);
// Now allow alterations via hook_uc_product_description_alter().
\Drupal::moduleHandler()
->alter('uc_product_description', $description, $product);
return drupal_render($description);
}
/**
* Returns data for a product feature, given a feature ID and array key.
*
* @param string $fid
* The string ID of the product feature you want to get data from.
* @param string $key
* The key in the product feature array you want: title, callback, delete,
* settings.
*
* @return
* The value of the key you specify.
*/
function uc_product_feature_data($fid, $key) {
static $features;
if (empty($features)) {
foreach (\Drupal::moduleHandler()
->invokeAll('uc_product_feature') as $feature) {
$features[$feature['id']] = $feature;
}
}
return $features[$fid][$key];
}
/**
* Saves a product feature to a product node.
*
* @param array $data
* An array consisting of the following keys:
* - pfid: (optional) When editing an existing product feature, the numeric
* ID of the feature.
* - nid: The numeric ID of the product node.
* - fid: The string ID of the feature type.
* - description: The string describing the feature for the overview table.
*/
function uc_product_feature_save(array &$data) {
$connection = \Drupal::database();
if (empty($data['pfid'])) {
unset($data['pfid']);
$data['pfid'] = $connection
->insert('uc_product_features')
->fields($data)
->execute();
\Drupal::messenger()
->addMessage(t('The product feature has been added.'));
}
else {
$connection
->merge('uc_product_features')
->key([
'pfid' => $data['pfid'],
])
->fields($data)
->execute();
\Drupal::messenger()
->addMessage(t('The product feature has been updated.'));
}
}
/**
* Loads all product feature for a node.
*
* @param int $nid
* The product node ID.
*
* @return array
* The array of all product features object.
*/
function uc_product_feature_load_multiple($nid) {
$connection = \Drupal::database();
$features = $connection
->query('SELECT * FROM {uc_product_features} WHERE nid = :nid ORDER BY pfid ASC', [
':nid' => $nid,
])
->fetchAllAssoc('pfid');
return $features;
}
/**
* Loads a product feature object.
*
* @todo Should return an object instead of array.
*
* @param $pfid
* The product feature ID.
* @param $fid
* Optional. Specify a specific feature id.
*
* @return array
* The product feature array.
*/
function uc_product_feature_load($pfid) {
$connection = \Drupal::database();
$feature = $connection
->query('SELECT * FROM {uc_product_features} WHERE pfid = :pfid', [
':pfid' => $pfid,
])
->fetchAssoc();
return $feature;
}
/**
* Deletes a product feature object.
*
* @param $pfid
* The product feature ID.
*
* @return
* The product feature object.
*/
function uc_product_feature_delete($pfid) {
$feature = uc_product_feature_load($pfid);
// Call the delete function for this product feature if it exists.
$func = uc_product_feature_data($feature['fid'], 'delete');
if (function_exists($func)) {
$func($pfid);
}
$connection = \Drupal::database();
$connection
->delete('uc_product_features')
->condition('pfid', $pfid)
->execute();
return SAVED_DELETED;
}
/**
* Implements hook_node_type_insert().
*/
function uc_product_node_type_insert(NodeTypeInterface $type) {
if ($type
->getThirdPartySetting('uc_product', 'product', FALSE)) {
uc_product_add_default_image_field($type
->id());
$defaults = [
'model' => '',
'cost' => 0,
'price' => 0,
'weight' => 0,
'weight_units' => \Drupal::config('uc_store.settings')
->get('weight.units'),
'length' => 0,
'width' => 0,
'height' => 0,
'length_units' => \Drupal::config('uc_store.settings')
->get('length.units'),
'pkg_qty' => 1,
'default_qty' => 1,
'shippable' => $type
->getThirdPartySetting('uc_product', 'shippable', TRUE),
];
$connection = \Drupal::database();
$result = $connection
->query('SELECT n.vid, n.nid FROM {node} n LEFT JOIN {uc_products} p ON n.vid = p.vid WHERE n.type = :type AND p.vid IS NULL', [
':type' => $type
->id(),
]);
foreach ($result as $node) {
$connection
->insert('uc_products')
->fields($defaults + [
'nid' => $node->nid,
'vid' => $node->vid,
])
->execute();
}
}
}
/**
* Implements hook_node_type_update().
*/
function uc_product_node_type_update(NodeTypeInterface $type) {
if (!$type->original
->getThirdPartySetting('uc_product', 'product', FALSE)) {
uc_product_node_type_insert($type);
}
}
/**
* Creates a file field with an image field widget, and attach it to products.
*
* This field is used by default on the product page, as well as on the cart
* and catalog pages to represent the products they list. Instances are added
* to new product classes, and other node types that claim product-ness should
* call this function for themselves.
*
* @param $type
* The content type to which the image field is to be attached. This may be a
* a single type as a string, or an array of types. If NULL, all product
* types get an instance of the field.
*/
function uc_product_add_default_image_field($type = NULL) {
// Set up field if it doesn't exist.
if (!FieldStorageConfig::loadByName('node', 'uc_product_image')) {
FieldStorageConfig::create([
'entity_type' => 'node',
'field_name' => 'uc_product_image',
'type' => 'image',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'display_field' => 0,
],
])
->save();
}
if ($type) {
// Accept single or multiple types as input.
$types = (array) $type;
}
else {
$types = uc_product_types();
}
foreach ($types as $type) {
$field_name = NodeType::load($type)
->getThirdPartySetting('uc_product', 'image_field', 'uc_product_image');
// Only add the instance if it doesn't exist. Don't overwrite any changes.
if ($field_name && !FieldConfig::loadByName('node', $type, $field_name)) {
FieldConfig::create([
'entity_type' => 'node',
'bundle' => $type,
'field_name' => $field_name,
'label' => t('Image'),
'weight' => -2,
])
->save();
NodeType::load($type)
->setThirdPartySetting('uc_product', 'image_field', $field_name)
->save();
\Drupal::service('entity_display.repository')
->getFormDisplay('node', $type)
->setComponent($field_name, [
'type' => 'image_image',
])
->save();
\Drupal::service('entity_display.repository')
->getViewDisplay('node', $type)
->setComponent($field_name, [
'label' => 'hidden',
'type' => 'uc_product_image',
])
->save();
\Drupal::service('entity_display.repository')
->getViewDisplay('node', $type, 'teaser')
->setComponent($field_name, [
'label' => 'hidden',
'type' => 'uc_product_image',
])
->save();
}
}
}
Functions
Name | Description |
---|---|
uc_product_add_default_image_field | Creates a file field with an image field widget, and attach it to products. |
uc_product_entity_bundle_field_info | Implements hook_entity_bundle_field_info(). |
uc_product_entity_extra_field_info | Implements hook_entity_extra_field_info(). |
uc_product_feature_data | Returns data for a product feature, given a feature ID and array key. |
uc_product_feature_delete | Deletes a product feature object. |
uc_product_feature_load | Loads a product feature object. |
uc_product_feature_load_multiple | Loads all product feature for a node. |
uc_product_feature_save | Saves a product feature to a product node. |
uc_product_form_node_type_form_alter | Implements hook_form_FORM_ID_alter() for node_type_form(). |
uc_product_form_node_type_form_builder | Entity builder for the node type form with product options. |
uc_product_get_description | Returns HTML for the product description. |
uc_product_get_models | Gets all models of a product (node). |
uc_product_get_picture | Returns a product node's first attached image. |
uc_product_is_product | Determines whether or not a given node or node type is a product. |
uc_product_is_product_form | Determines whether or not a given form array is a product node form. |
uc_product_load_variant | Loads a specific altered variant of a product node. |
uc_product_node_delete | Implements hook_node_delete(). |
uc_product_node_insert | Implements hook_node_insert(). |
uc_product_node_load | Implements hook_node_load(). |
uc_product_node_type_insert | Implements hook_node_type_insert(). |
uc_product_node_type_update | Implements hook_node_type_update(). |
uc_product_node_update | Implements hook_node_update(). |
uc_product_node_view | Implements hook_node_view(). |
uc_product_page_attachments | Implements hook_page_attachments(). |
uc_product_preprocess_field | Theme preprocess function for Ubercart product image fields. |
uc_product_preprocess_html | Implements hook_preprocess_html(). |
uc_product_theme | Implements hook_theme(). |
uc_product_theme_suggestions_node_alter | Implements hook_theme_suggestions_HOOK_alter(). |
uc_product_types | Returns an array of product node types. |
uc_product_uc_add_to_cart_data | Implements hook_uc_add_to_cart_data(). |
uc_product_uc_cart_display | Implements hook_uc_cart_display(). |
uc_product_uc_product_alter | Implements hook_uc_product_alter(). |
uc_product_uc_product_types | Implements hook_uc_product_types(). |
uc_product_uc_store_status | Implements hook_uc_store_status(). |
uc_product_uc_update_cart_item | Implements hook_uc_update_cart_item(). |
uc_product_view_ajax_commands | Dynamically replaces parts of a product view based on form input. |
uc_product_view_product | Renders product related content for product-type modules. |
_uc_product_get_variant | Gets a specific, cloned, altered variant of a product node. |