Advertisement
Guest User

uc_product.module

a guest
Aug 7th, 2012
173
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 53.79 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * @file
  5.  * The product module for Ubercart.
  6.  *
  7.  * Provides information that is common to all products, and user-defined product
  8.  * classes for more specification.
  9.  */
  10.  
  11.  
  12. /**
  13.  * Implements hook_menu().
  14.  */
  15. function uc_product_menu() {
  16.   $items = array();
  17.  
  18.   $items['admin/store/products'] = array(
  19.     'title' => 'Products',
  20.     'description' => 'Administer products, classes, and more.',
  21.     'page callback' => 'drupal_goto',
  22.     'page arguments' => array('admin/store/products/view'),
  23.     'access arguments' => array('administer products'),
  24.     'weight' => -2,
  25.     'position' => 'left',
  26.   );
  27.   // admin/store/products/view is provided by Views.
  28.   $items['admin/store/products/classes'] = array(
  29.     'title' => 'Manage classes',
  30.     'description' => 'Create and edit product node types.',
  31.     'access arguments' => array('administer product classes'),
  32.     'page callback' => 'uc_product_class_default',
  33.     'weight' => -2,
  34.     'file' => 'uc_product.admin.inc',
  35.   );
  36.   $items['admin/store/settings/products'] = array(
  37.     'title' => 'Products',
  38.     'description' => 'Configure product settings.',
  39.     'access arguments' => array('administer products'),
  40.     'page callback' => 'drupal_get_form',
  41.     'page arguments' => array('uc_product_settings_form'),
  42.     'file' => 'uc_product.admin.inc',
  43.   );
  44.  
  45.   // Insert subitems into the edit node page for product types.
  46.   $items['node/%node/edit/product'] = array(
  47.     'title' => 'Product',
  48.     'access callback' => 'uc_product_edit_access',
  49.     'access arguments' => array(1),
  50.     'weight' => -10,
  51.     'type' => MENU_DEFAULT_LOCAL_TASK,
  52.     'file' => 'uc_product.admin.inc',
  53.   );
  54.   $features = module_invoke_all('uc_product_feature');
  55.   if (!empty($features)) {
  56.     $items['node/%node/edit/features'] = array(
  57.       'title' => 'Features',
  58.       'page callback' => 'uc_product_features',
  59.       'page arguments' => array(1),
  60.       'access callback' => 'uc_product_feature_access',
  61.       'access arguments' => array(1),
  62.       'weight' => 10,
  63.       'type' => MENU_LOCAL_TASK,
  64.       'file' => 'uc_product.admin.inc',
  65.     );
  66.  
  67.     $items['node/%node/edit/features/%/%'] = array(
  68.       'page callback' => 'uc_product_feature_edit',
  69.       'page arguments' => array(1, 4, 5),
  70.       'access callback' => 'uc_product_feature_access',
  71.       'access arguments' => array(1),
  72.       'file' => 'uc_product.admin.inc',
  73.     );
  74.  
  75.     $items['node/%node/edit/features/%/%uc_product_feature/delete'] = array(
  76.       'title' => 'Delete feature',
  77.       'page callback' => 'drupal_get_form',
  78.       'page arguments' => array('uc_product_feature_confirm_delete', 1, 4, 5),
  79.       'access callback' => 'uc_product_feature_access',
  80.       'access arguments' => array(1),
  81.       'file' => 'uc_product.admin.inc',
  82.     );
  83.   }
  84.  
  85.   $items['admin/store/settings/products/defaults'] = array(
  86.     'access arguments' => array('administer products'),
  87.     'page callback' => 'uc_product_image_defaults',
  88.     'file' => 'uc_product.admin.inc',
  89.   );
  90.   $items['admin/store/products/classes/%uc_product_class'] = array(
  91.     'title' => 'Product class',
  92.     'access arguments' => array('administer product classes'),
  93.     'page callback' => 'drupal_get_form',
  94.     'page arguments' => array('uc_product_class_form', 4),
  95.     'file' => 'uc_product.admin.inc',
  96.   );
  97.   $items['admin/store/products/classes/%uc_product_class/edit'] = array(
  98.     'title' => 'Edit',
  99.     'type' => MENU_DEFAULT_LOCAL_TASK,
  100.     'weight' => -5,
  101.     'file' => 'uc_product.admin.inc',
  102.   );
  103.   $items['admin/store/products/classes/%uc_product_class/delete'] = array(
  104.     'access callback' => 'uc_product_class_delete_access',
  105.     'access arguments' => array(4),
  106.     'page callback' => 'drupal_get_form',
  107.     'page arguments' => array('uc_product_class_delete_confirm', 4),
  108.     'file' => 'uc_product.admin.inc',
  109.   );
  110.  
  111.   // Define an autocomplete path for products using the title or SKU.
  112.   $items['autocomplete/uc_product_title_sku'] = array(
  113.     'page callback' => 'uc_product_title_sku_autocomplete',
  114.     'access arguments' => array('access content'),
  115.     'type' => MENU_CALLBACK,
  116.     'file' => 'uc_product.pages.inc',
  117.   );
  118.  
  119.   return $items;
  120. }
  121.  
  122. /**
  123.  * Menu access callback for deleting product classes.
  124.  */
  125. function uc_product_class_delete_access($class) {
  126.   return user_access('administer product classes') && !$class->locked;
  127. }
  128.  
  129. /**
  130.  * Implements hook_admin_paths().
  131.  */
  132. function uc_product_admin_paths() {
  133.   $paths = array(
  134.     'node/*/edit/product' => TRUE,
  135.     'node/*/edit/features' => TRUE,
  136.     'node/*/edit/features/*' => TRUE,
  137.   );
  138.  
  139.   return $paths;
  140. }
  141.  
  142. /**
  143.  * Implements hook_permission().
  144.  */
  145. function uc_product_permission() {
  146.   $perms = array(
  147.     'administer products' => array(
  148.       'title' => t('Administer products'),
  149.     ),
  150.     'administer product classes' => array(
  151.       'title' => t('Administer product classes'),
  152.     ),
  153.     'administer product features' => array(
  154.       'title' => t('Administer product features'),
  155.     ),
  156.     'administer own product features' => array(
  157.       'title' => t('Administer own product features'),
  158.     ),
  159.   );
  160.  
  161.   return $perms;
  162. }
  163.  
  164. /**
  165.  * Implements hook_access().
  166.  */
  167. function uc_product_node_access($op, $node, $account) {
  168.   $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
  169.  
  170.   if ($type == 'product') {
  171.     $type = '';
  172.   }
  173.   else {
  174.     $type .= ' ';
  175.   }
  176.   switch ($op) {
  177.     case 'create':
  178.       return user_access('create ' . $type . 'products', $account);
  179.     case 'update':
  180.       if (user_access('edit all ' . $type . 'products', $account) || (user_access('edit own ' . $type . 'products', $account) && ($account->uid == $node->uid))) {
  181.         return NODE_ACCESS_ALLOW;
  182.       }
  183.       break;
  184.     case 'delete':
  185.       if (user_access('delete all ' . $type . 'products', $account) || (user_access('delete own ' . $type . 'products', $account) && ($account->uid == $node->uid))) {
  186.         return NODE_ACCESS_ALLOW;
  187.       }
  188.       break;
  189.   }
  190. }
  191.  
  192. /**
  193.  * Menu access callback for 'node/%node/edit/features'.
  194.  */
  195. function uc_product_feature_access($node) {
  196.   global $user;
  197.   return uc_product_is_product($node) &&
  198.     ((user_access('administer product features') || (user_access('administer own product features') && ($user->uid == $node->uid))));
  199. }
  200.  
  201. /**
  202.  * Implements hook_theme().
  203.  */
  204. function uc_product_theme() {
  205.   return array(
  206.     'uc_product_model' => array(
  207.       'variables' => array('model' => '', 'view_mode' => 'full'),
  208.       'file' => 'uc_product.theme.inc',
  209.     ),
  210.     'uc_product_body' => array(
  211.       'variables' => array('body' => '', 'view_mode' => 'full'),
  212.       'file' => 'uc_product.theme.inc',
  213.     ),
  214.     'uc_product_add_to_cart' => array(
  215.       'variables' => array('form' => NULL, 'view_mode' => 'full'),
  216.       'file' => 'uc_product.theme.inc',
  217.     ),
  218.     'uc_product_price' => array(
  219.       'render element' => 'element',
  220.       'file' => 'uc_product.theme.inc',
  221.     ),
  222.     'uc_product_weight' => array(
  223.       'variables' => array('amount' => 0, 'units' => NULL, 'view_mode' => 'full'),
  224.       'file' => 'uc_product.theme.inc',
  225.     ),
  226.     'uc_product_dimensions' => array(
  227.       'variables' => array('length' => 0, 'width' => 0, 'height' => 0, 'units' => NULL, 'view_mode' => 'full'),
  228.       'file' => 'uc_product.theme.inc',
  229.     ),
  230.     'uc_product_image' => array(
  231.       'variables' => array('images' => array(), 'view_mode' => 'full'),
  232.       'file' => 'uc_product.theme.inc',
  233.     ),
  234.     'uc_product_feature_add_form' => array(
  235.       'render element' => 'form',
  236.       'file' => 'uc_product.admin.inc',
  237.     ),
  238.   );
  239. }
  240.  
  241. /**
  242.  * Implements hook_field_formatter_info().
  243.  */
  244. function uc_product_field_formatter_info() {
  245.   return array(
  246.     'uc_product_image' => array(
  247.       'label' => t('Ubercart product'),
  248.       'description' => t('The first image is displayed with the "uc_product" image style, and subsequent images are displayed below it with the "uc_thumbnail" image style.'),
  249.       'field types' => array('image'),
  250.     ),
  251.   );
  252. }
  253.  
  254. /**
  255.  * Implements hook_field_formatter_view().
  256.  */
  257. function uc_product_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  258.   $element = array();
  259.  
  260.   switch ($display['type']) {
  261.     case 'uc_product_image':
  262.       $element[0] = array(
  263.         '#theme' => 'uc_product_image',
  264.         '#images' => $items,
  265.       );
  266.       break;
  267.   }
  268.  
  269.   return $element;
  270. }
  271.  
  272. /**
  273.  * Implements hook_node_info().
  274.  *
  275.  * Creates node types for each product class and other product modules.
  276.  */
  277. function uc_product_node_info($reset = FALSE) {
  278.   static $types = array();
  279.   $title_label = t('Name');
  280.  
  281.   if (empty($types) || $reset) {
  282.     $types = array();
  283.     $defaults = module_invoke_all('uc_product_default_classes');
  284.     $types['product'] = isset($defaults['product']) ? $defaults['product'] : array();
  285.     $types['product'] += array(
  286.       'name' => t('Product'),
  287.       'base' => 'uc_product',
  288.       'description' => t('Use <em>products</em> to represent items for sale on the website, including all the unique information that can be attributed to a specific model number.'),
  289.       'title_label' => $title_label,
  290.     );
  291.  
  292.     $classes = uc_product_class_load(NULL, $reset);
  293.     foreach ($classes as $class) {
  294.       $class = (array) $class;
  295.       $class['base'] = 'uc_product';
  296.       $types[$class['pcid']] = $class + array(
  297.         'title_label' => $title_label,
  298.       );
  299.     }
  300.   }
  301.   return $types;
  302. }
  303.  
  304. /**
  305.  * Implements hook_node_type_update().
  306.  *
  307.  * Ensure product class names and descriptions are synchronised if the
  308.  * content type is updated.
  309.  */
  310. function uc_product_node_type_update($type) {
  311.   db_update('uc_product_classes')
  312.     ->fields(array(
  313.       'name' => $type->name,
  314.       'description' => $type->description,
  315.     ))
  316.     ->condition('pcid', $type->type)
  317.     ->execute();
  318. }
  319.  
  320. /**
  321.  * Implements hook_forms().
  322.  *
  323.  * Register an "add to cart" form for each product to prevent id collisions.
  324.  */
  325. function uc_product_forms($form_id, $args) {
  326.   $forms = array();
  327.   if (isset($args[0]) && isset($args[0]->nid) && isset($args[0]->type)) {
  328.     $product = $args[0];
  329.     if (in_array($product->type, array_keys(uc_product_node_info()))) {
  330.       $forms['uc_product_add_to_cart_form_' . $product->nid] = array('callback' => 'uc_product_add_to_cart_form');
  331.       $forms['uc_catalog_buy_it_now_form_' . $product->nid] = array('callback' => 'uc_catalog_buy_it_now_form');
  332.     }
  333.   }
  334.   return $forms;
  335. }
  336.  
  337. /**
  338.  * Menu access callback for 'node/%node/edit/product'.
  339.  */
  340. function uc_product_edit_access($node) {
  341.   // Re-inherit access callback for 'node/%node/edit'.
  342.   return uc_product_is_product($node) && node_access('update', $node);
  343. }
  344.  
  345. /**
  346.  * Implements hook_form().
  347.  *
  348.  * @ingroup forms
  349.  */
  350. function uc_product_form($node, $form_state) {
  351.   $type = node_type_get_type($node);
  352.  
  353.   $form['title'] = array(
  354.     '#type' => 'textfield',
  355.     '#title' => check_plain($type->title_label),
  356.     '#required' => TRUE,
  357.     '#default_value' => $node->title,
  358.     '#maxlength' => 255,
  359.     '#weight' => -5,
  360.   );
  361.  
  362.   $form['base'] = array(
  363.     '#type' => 'fieldset',
  364.     '#title' => t('Product information'),
  365.     '#collapsible' => TRUE,
  366.     '#collapsed' => TRUE,
  367.     '#weight' => -10,
  368.     '#attributes' => array('class' => array('product-field')),
  369.     '#group' => 'additional_settings',
  370.     '#attached' => array(
  371.       'js' => array(
  372.         'vertical-tabs' => drupal_get_path('module', 'uc_product') . '/uc_product.js',
  373.       ),
  374.     ),
  375.   );
  376.   $form['base']['model'] = array(
  377.     '#type' => 'textfield',
  378.     '#title' => t('SKU'),
  379.     '#required' => TRUE,
  380.     '#default_value' => $node->model,
  381.     '#description' => t('Product SKU/model.'),
  382.     '#weight' => 0,
  383.     '#size' => 32,
  384.   );
  385.  
  386.   $form['base']['prices'] = array(
  387.     '#type' => 'container',
  388.     '#attributes' => array('class' => array('uc-inline-form', 'clearfix')),
  389.     '#weight' => 5,
  390.   );
  391.  
  392.   $form['base']['prices']['list_price'] = array(
  393.     '#type' => 'uc_price',
  394.     '#title' => t('List price'),
  395.     '#default_value' => $node->list_price,
  396.     '#description' => t('The listed MSRP.'),
  397.     '#weight' => 0,
  398.   );
  399.   $form['base']['prices']['cost'] = array(
  400.     '#type' => 'uc_price',
  401.     '#title' => t('Cost'),
  402.     '#default_value' => $node->cost,
  403.     '#description' => t("Your store's cost."),
  404.     '#weight' => 1,
  405.   );
  406.   $form['base']['prices']['sell_price'] = array(
  407.     '#type' => 'uc_price',
  408.     '#title' => t('Sell price'),
  409.     '#required' => TRUE,
  410.     '#default_value' => $node->sell_price,
  411.     '#description' => t('Customer purchase price.'),
  412.     '#weight' => 2,
  413.   );
  414.  
  415.   $form['base']['shippable'] = array(
  416.     '#type' => 'checkbox',
  417.     '#title' => t('Product is shippable.'),
  418.     '#default_value' => $node->shippable,
  419.     '#weight' => 10,
  420.   );
  421.  
  422.   $form['base']['weight'] = array(
  423.     '#type' => 'container',
  424.     '#attributes' => array('class' => array('uc-inline-form', 'clearfix')),
  425.     '#states' => array(
  426.       'invisible' => array(
  427.         'input[name="shippable"]' => array('checked' => FALSE),
  428.       ),
  429.     ),
  430.     '#weight' => 15,
  431.   );
  432.   $form['base']['weight']['weight'] = array(
  433.     '#type' => 'textfield',
  434.     '#title' => t('Weight'),
  435.     '#default_value' => $node->weight,
  436.     '#size' => 10,
  437.     '#element_validate' => array('uc_store_validate_number'),
  438.   );
  439.   $units = array(
  440.     'lb' => t('Pounds'),
  441.     'kg' => t('Kilograms'),
  442.     'oz' => t('Ounces'),
  443.     'g' => t('Grams'),
  444.   );
  445.   $form['base']['weight']['weight_units'] = array(
  446.     '#type' => 'select',
  447.     '#title' => t('Units'),
  448.     '#default_value' => $node->weight_units,
  449.     '#options' => $units,
  450.   );
  451.  
  452.   $form['base']['dimensions'] = array(
  453.     '#type' => 'container',
  454.     '#attributes' => array('class' => array('uc-inline-form', 'clearfix')),
  455.     '#states' => array(
  456.       'invisible' => array(
  457.         'input[name="shippable"]' => array('checked' => FALSE),
  458.       ),
  459.     ),
  460.     '#weight' => 20,
  461.   );
  462.   $form['base']['dimensions']['dim_length'] = array(
  463.     '#type' => 'textfield',
  464.     '#title' => t('Length'),
  465.     '#default_value' => $node->length,
  466.     '#size' => 10,
  467.     '#element_validate' => array('uc_store_validate_number'),
  468.   );
  469.   $form['base']['dimensions']['dim_width'] = array(
  470.     '#type' => 'textfield',
  471.     '#title' => t('Width'),
  472.     '#default_value' => $node->width,
  473.     '#size' => 10,
  474.     '#element_validate' => array('uc_store_validate_number'),
  475.   );
  476.   $form['base']['dimensions']['dim_height'] = array(
  477.     '#type' => 'textfield',
  478.     '#title' => t('Height'),
  479.     '#default_value' => $node->height,
  480.     '#size' => 10,
  481.     '#element_validate' => array('uc_store_validate_number'),
  482.   );
  483.   $form['base']['dimensions']['length_units'] = array(
  484.     '#type' => 'select',
  485.     '#title' => t('Units'),
  486.     '#options' => array(
  487.       'in' => t('Inches'),
  488.       'ft' => t('Feet'),
  489.       'cm' => t('Centimeters'),
  490.       'mm' => t('Millimeters'),
  491.     ),
  492.     '#default_value' => $node->length_units,
  493.   );
  494.   $form['base']['pkg_qty'] = array(
  495.     '#type' => 'uc_quantity',
  496.     '#title' => t('Maximum package quantity'),
  497.     '#default_value' => $node->pkg_qty,
  498.     '#description' => 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.'),
  499.     '#weight' => 25,
  500.     '#states' => array(
  501.       'invisible' => array(
  502.         'input[name="shippable"]' => array('checked' => FALSE),
  503.       ),
  504.     ),
  505.   );
  506.  
  507.   if (variable_get('uc_product_add_to_cart_qty', FALSE)) {
  508.     $form['base']['default_qty'] = array(
  509.       '#type' => 'uc_quantity',
  510.       '#title' => t('Default quantity to add to cart'),
  511.       '#default_value' => $node->default_qty,
  512.       '#description' => t('Use 0 to disable the quantity field next to the add to cart button.'),
  513.       '#weight' => 27,
  514.       '#allow_zero' => TRUE,
  515.     );
  516.   }
  517.   else {
  518.     $form['base']['default_qty'] = array(
  519.       '#type' => 'value',
  520.       '#value' => $node->default_qty,
  521.     );
  522.   }
  523.  
  524.   $form['base']['ordering'] = array(
  525.     '#type' => 'weight',
  526.     '#title' => t('List position'),
  527.     '#description' => t("Specify a value to set this product's position in product lists.<br />Products in the same position will be sorted alphabetically."),
  528.     '#delta' => 25,
  529.     '#default_value' => $node->ordering,
  530.     '#weight' => 30,
  531.   );
  532.  
  533.   return $form;
  534. }
  535.  
  536. /**
  537.  * Implements hook_prepare().
  538.  */
  539. function uc_product_prepare($node) {
  540.   $defaults = array(
  541.     'model' => '',
  542.     'list_price' => 0,
  543.     'cost' => 0,
  544.     'sell_price' => 0,
  545.     'weight' => 0,
  546.     'weight_units' => variable_get('uc_weight_unit', 'lb'),
  547.     'length' => 0,
  548.     'width' => 0,
  549.     'height' => 0,
  550.     'length_units' => variable_get('uc_length_unit', 'in'),
  551.     'pkg_qty' => 1,
  552.     'default_qty' => 1,
  553.     'shippable' => variable_get('uc_product_shippable_' . $node->type, 1),
  554.     'ordering' => 0,
  555.   );
  556.  
  557.   foreach ($defaults as $key => $value) {
  558.     if (!isset($node->$key)) {
  559.       $node->$key = $value;
  560.     }
  561.   }
  562. }
  563.  
  564. /**
  565.  * Implements hook_insert().
  566.  */
  567. function uc_product_insert($node) {
  568.   if (isset($node->dim_length)) {
  569.     $node->length = $node->dim_length;
  570.   }
  571.   if (isset($node->dim_width)) {
  572.     $node->width = $node->dim_width;
  573.   }
  574.   if (isset($node->dim_height)) {
  575.     $node->height = $node->dim_height;
  576.   }
  577.  
  578.   if (!isset($node->unique_hash)) {
  579.     $node->unique_hash = md5($node->vid . $node->nid . $node->model . $node->list_price . $node->cost . $node->sell_price . $node->weight . $node->weight_units . $node->length . $node->width . $node->height . $node->length_units . $node->pkg_qty . $node->default_qty . $node->shippable . REQUEST_TIME);
  580.   }
  581.  
  582.   drupal_write_record('uc_products', $node);
  583. }
  584.  
  585. /**
  586.  * Implements hook_update().
  587.  */
  588. function uc_product_update($node) {
  589.   if (isset($node->dim_length)) {
  590.     $node->length = $node->dim_length;
  591.   }
  592.   if (isset($node->dim_width)) {
  593.     $node->width = $node->dim_width;
  594.   }
  595.   if (isset($node->dim_height)) {
  596.     $node->height = $node->dim_height;
  597.   }
  598.  
  599.   if (!empty($node->revision)) {
  600.     drupal_write_record('uc_products', $node);
  601.   }
  602.   else {
  603.     drupal_write_record('uc_products', $node, 'vid');
  604.   }
  605. }
  606.  
  607. /**
  608.  * Implements hook_load().
  609.  */
  610. function uc_product_load($nodes) {
  611.   $vids = array();
  612.   foreach ($nodes as $node) {
  613.     $vids[$node->nid] = $node->vid;
  614.   }
  615.  
  616.   $result = db_query('SELECT nid, model, list_price, cost, sell_price, weight, weight_units, length, width, height, length_units, pkg_qty, default_qty, unique_hash, ordering, shippable FROM {uc_products} WHERE vid IN (:vids)', array(':vids' => $vids));
  617.   foreach ($result as $node) {
  618.     foreach ($node as $field => $value) {
  619.       $nodes[$node->nid]->$field = $value;
  620.     }
  621.     $nodes[$node->nid]->price = $nodes[$node->nid]->sell_price;
  622.   }
  623. }
  624.  
  625. /**
  626.  * Gets a specific, cloned, altered variant of a product node.
  627.  *
  628.  * Generally, you should always use uc_product_load_variant() instead,
  629.  * except when node_load() cannot be invoked, e.g. when implementing
  630.  * hook_node_load().
  631.  *
  632.  * @param $node
  633.  *   The product node to alter. Throws an exception if this is already a
  634.  *   product variant.
  635.  * @param $data
  636.  *   Optional data to add to the product before invoking the alter hooks.
  637.  *
  638.  * @return
  639.  *   An variant of the product, altered based on the provided data.
  640.  */
  641. function _uc_product_get_variant($node, $data = FALSE) {
  642.   if (!empty($node->variant)) {
  643.     throw new Exception(t('Cannot create a variant of a variant.'));
  644.   }
  645.   $node = clone $node;
  646.   if (!empty($data)) {
  647.     $node->data = $data;
  648.   }
  649.  
  650.   // Ensure that $node->data is an array (user module leaves it serialized).
  651.   if (isset($node->data) && !is_array($node->data)) {
  652.     $node->data = unserialize($node->data);
  653.   }
  654.  
  655.   drupal_alter('uc_product', $node);
  656.   $node->variant = TRUE;
  657.   if (!isset($node->data['module'])) {
  658.     $node->data['module'] = 'uc_product';
  659.   }
  660.   return $node;
  661. }
  662.  
  663. /**
  664.  * Loads a specific altered variant of a product node.
  665.  *
  666.  * The (possibly cached) base product remains unaltered.
  667.  *
  668.  * @param $nid
  669.  *   The nid of the product to load.
  670.  * @param $data
  671.  *   Optional data to add to the product before invoking the alter hooks.
  672.  *
  673.  * @return
  674.  *   An variant of the product, altered based on the provided data.
  675.  */
  676. function uc_product_load_variant($nid, $data = FALSE) {
  677.   return _uc_product_get_variant(node_load($nid), $data);
  678. }
  679.  
  680. /**
  681.  * Implements hook_uc_product_alter().
  682.  *
  683.  * Invokes rules event to allow product modifications.
  684.  */
  685. function uc_product_uc_product_alter(&$node) {
  686.   if (module_exists('rules')) {
  687.     rules_invoke_event('uc_product_load', $node);
  688.   }
  689. }
  690.  
  691. /**
  692.  * Implements hook_delete().
  693.  */
  694. function uc_product_delete(&$node) {
  695.   $features = uc_product_feature_load_multiple($node->nid);
  696.   foreach ($features as $feature) {
  697.     uc_product_feature_delete($feature->pfid);
  698.   }
  699.  
  700.   db_delete('uc_products')
  701.     ->condition('nid', $node->nid)
  702.     ->execute();
  703. }
  704.  
  705. /**
  706.  * Returns a list of ajax commands which can be used to replace portions of a product view
  707.  * based on user input to the add-to-cart form.
  708.  *
  709.  * If a module adds an input field to the add-to-cart form which affects some aspect of a product
  710.  * (e.g. display price or weight), it should attach an #ajax callback to that form element, and use
  711.  * this function in the callback to build updated content for the affected fields based on user input.
  712.  *
  713.  * @param $form_state
  714.  *   The current form state.  This must contain a 'variant' entry in the 'storage' array which represents
  715.  *   the product as configured by user input data. In most cases, this is provided automatically by
  716.  *   uc_product_add_to_cart_form_validate().
  717.  *
  718.  * @param $keys
  719.  *   An array of keys in the built product content which should be replaced (e.g. 'display_price').
  720.  *
  721.  * @return
  722.  *   An array of ajax commands to replace each of the display fields with it's updated value.
  723.  */
  724. function uc_product_view_ajax_commands($form_state, $keys) {
  725.   $commands = array();
  726.   if (variable_get('uc_product_update_node_view', FALSE) && !empty($form_state['storage']['variant'])) {
  727.     $node_div = '#node-' . $form_state['storage']['variant']->nid;
  728.     $build = node_view($form_state['storage']['variant']);
  729.     foreach ($keys as $key) {
  730.       if (isset($build[$key])) {
  731.         $id = $node_div . ' .' . str_replace('_', '-', $key);
  732.         $commands[] = ajax_command_replace($id, drupal_render($build[$key]));
  733.       }
  734.     }
  735.   }
  736.   return $commands;
  737. }
  738.  
  739. /**
  740.  * Implements hook_view().
  741.  */
  742. function uc_product_view($node, $view_mode) {
  743.   // Give modules a chance to alter this product.  If it is a variant, this will
  744.   // have been done already by uc_product_load_variant(), so we check a flag to
  745.   // be sure not to alter twice -- cf. entity_prepare_view().
  746.   $variant = empty($node->variant) ? _uc_product_get_variant($node) : $node;
  747.  
  748.   // Build the add_to_cart form, and use the updated variant based on data provided by the form
  749.   // (e.g. attribute default options).
  750.   if (module_exists('uc_cart') && empty($variant->data['display_only'])) {
  751.     $add_to_cart_form = drupal_get_form('uc_product_add_to_cart_form_' . $variant->nid, $variant);
  752.     if (variable_get('uc_product_update_node_view', FALSE)) {
  753.       $variant = $add_to_cart_form['node']['#value'];
  754.     }
  755.   }
  756.  
  757.  
  758.   $node->content['display_price'] = array(
  759.     '#theme' => 'uc_product_price',
  760.     '#value' => $variant->price,
  761.     '#suffixes' => array(),
  762.     '#attributes' => array(
  763.       'class' => array(
  764.         'display-price',
  765.       ),
  766.     ),
  767.   );
  768.   $node->content['model'] = array(
  769.     '#theme' => 'uc_product_model',
  770.     '#model' => $variant->model,
  771.     '#view_mode' => $view_mode,
  772.   );
  773.   $node->content['list_price'] = array(
  774.     '#theme' => 'uc_product_price',
  775.     '#title' => t('List price:'),
  776.     '#value' => $variant->list_price,
  777.     '#attributes' => array(
  778.       'class' => array(
  779.         'list-price',
  780.       ),
  781.     ),
  782.   );
  783.   $node->content['cost'] = array(
  784.     '#theme' => 'uc_product_price',
  785.     '#title' => t('Cost:'),
  786.     '#value' => $variant->cost,
  787.     '#attributes' => array(
  788.       'class' => array(
  789.         'cost',
  790.       ),
  791.     ),
  792.     '#access' => user_access('administer products'),
  793.   );
  794.   $node->content['sell_price'] = array(
  795.     '#theme' => 'uc_product_price',
  796.     '#title' => t('Price:'),
  797.     '#value' => $variant->sell_price,
  798.     '#attributes' => array(
  799.       'class' => array(
  800.         'sell-price',
  801.       ),
  802.     ),
  803.   );
  804.   $node->content['weight'] = array(
  805.     '#theme' => 'uc_product_weight',
  806.     '#amount' => $variant->weight,
  807.     '#units' => $variant->weight_units,
  808.     '#view_mode' => $view_mode,
  809.   );
  810.   $node->content['dimensions'] = array(
  811.     '#theme' => 'uc_product_dimensions',
  812.     '#length' => $variant->length,
  813.     '#width' => $variant->width,
  814.     '#height' => $variant->height,
  815.     '#units' => $variant->length_units,
  816.     '#view_mode' => $view_mode,
  817.   );
  818.  
  819.   if (isset($add_to_cart_form)) {
  820.     $node->content['add_to_cart'] = array(
  821.       '#theme' => 'uc_product_add_to_cart',
  822.       '#view_mode' => $view_mode,
  823.       '#form' => $add_to_cart_form,
  824.     );
  825.   }
  826.  
  827.   $node->content['#node'] = $variant;
  828.  
  829.   return $node;
  830. }
  831.  
  832. /**
  833.  * Implements hook_preprocess_node().
  834.  *
  835.  * Product classes default to using node--product.tpl.php if they don't have
  836.  * their own template.
  837.  *
  838.  * @see theme()
  839.  * @see MODULE_preprocess_HOOK()
  840.  */
  841. function uc_product_preprocess_node(&$variables) {
  842.   if (uc_product_is_product($variables['type'])) {
  843.     array_unshift($variables['theme_hook_suggestions'], 'node__product');
  844.   }
  845. }
  846.  
  847. /**
  848.  * Implements hook_preprocess_html().
  849.  *
  850.  * Adds a body class to product node pages.
  851.  *
  852.  * @see html.tpl.php
  853.  */
  854. function uc_product_preprocess_html(&$variables) {
  855.   if ($node = menu_get_object()) {
  856.     if (uc_product_is_product($node)) {
  857.       $variables['classes_array'][] = 'uc-product-node';
  858.     }
  859.   }
  860. }
  861.  
  862. /**
  863.  * Implements hook_form_alter().
  864.  *
  865.  * @see uc_product_save_continue_submit()
  866.  */
  867. function uc_product_form_alter(&$form, &$form_state, $form_id) {
  868.   // Add a button to product node forms to continue editing after saving.
  869.   if (uc_product_is_product_form($form)) {
  870.     $form['actions']['save_continue'] = array(
  871.       '#type' => 'submit',
  872.       '#value' => t('Save and continue'),
  873.       '#weight' => 7,
  874.       '#submit' => array('node_form_submit', 'uc_product_save_continue_submit'),
  875.     );
  876.   }
  877. }
  878.  
  879. /**
  880.  * After the node is saved, redirects to the node edit page.
  881.  *
  882.  * Some modules add local tasks to product edit forms, but only after it has an
  883.  * nid. Redirecting facilitates the common workflow of continuing to those
  884.  * tasks.
  885.  */
  886. function uc_product_save_continue_submit($form, &$form_state) {
  887.   $form_state['redirect'] = 'node/' . $form_state['nid'] . '/edit';
  888. }
  889.  
  890. /**
  891.  * Implements hook_form_FORM_ID_alter().
  892.  *
  893.  * Adds a default image field setting to product content types.
  894.  */
  895. function uc_product_form_node_type_form_alter(&$form, &$form_state) {
  896.   $type = $form['#node_type'];
  897.  
  898.   if (!uc_product_is_product($type->type)) {
  899.     return;
  900.   }
  901.  
  902.   $form['uc_product'] = array(
  903.     '#type' => 'fieldset',
  904.     '#title' => t('Ubercart product settings'),
  905.     '#collapsible' => TRUE,
  906.     '#collapsed' => TRUE,
  907.     '#group' => 'additional_settings',
  908.     '#attached' => array(
  909.       'js' => array(
  910.         'vertical-tabs' => drupal_get_path('module', 'uc_product') . '/uc_product.js',
  911.       ),
  912.     ),
  913.   );
  914.  
  915.   // Shippable.
  916.   $form['uc_product']['uc_product_shippable'] = array(
  917.     '#type' => 'checkbox',
  918.     '#title' => t('Product and its derivatives are shippable.'),
  919.     '#default_value' => variable_get('uc_product_shippable_' . $type->type, 1),
  920.     '#description' => t('This setting can still be overridden on the node form.'),
  921.     '#return_value' => 1,
  922.     '#weight' => -5,
  923.   );
  924.  
  925.   // Image field.
  926.   $instances = field_info_instances('node', $type->type);
  927.  
  928.   $options = array('' => t('None'));
  929.   foreach ($instances as $field_name => $instance) {
  930.     if (strpos($instance['widget']['type'], 'image') !== FALSE) {
  931.       $options[$field_name] = $instance['label'];
  932.     }
  933.   }
  934.  
  935.   $form['uc_product']['uc_image'] = array(
  936.     '#type' => 'select',
  937.     '#title' => t('Product image field'),
  938.     '#default_value' => variable_get('uc_image_' . $type->type, NULL),
  939.     '#options' => $options,
  940.     '#description' => t('The selected field will be used on Ubercart pages to represent the products of this content type.'),
  941.     '#weight' => -4,
  942.   );
  943. }
  944.  
  945. /**
  946.  * Implements hook_field_extra_fields().
  947.  *
  948.  * Adds the "Product information".
  949.  */
  950. function uc_product_field_extra_fields() {
  951.   $extra = array();
  952.  
  953.   foreach (uc_product_types() as $type) {
  954.     $extra['node'][$type] = array(
  955.       'display' => array(
  956.         'display_price' => array(
  957.           'label' => t('Display price'),
  958.           'description' => t('High-visibility sell price.'),
  959.           'weight' => -1,
  960.         ),
  961.         'model' => array(
  962.           'label' => t('SKU'),
  963.           'description' => t('Product SKU/model.'),
  964.           'weight' => 0,
  965.         ),
  966.         'list_price' => array(
  967.           'label' => t('List price'),
  968.           'description' => t('The listed MSRP.'),
  969.           'weight' => 2,
  970.         ),
  971.         'cost' => array(
  972.           'label' => t('Cost'),
  973.           'description' => t("Your store's cost."),
  974.           'weight' => 3,
  975.         ),
  976.         'sell_price' => array(
  977.           'label' => t('Sell price'),
  978.           'description' => t('Customer purchase price.'),
  979.           'weight' => 4,
  980.         ),
  981.         'weight' => array(
  982.           'label' => t('Weight'),
  983.           'description' => t('Physical weight (lbs, kg, etc.).'),
  984.           'weight' => 5,
  985.         ),
  986.         'dimensions' => array(
  987.           'label' => t('Dimensions'),
  988.           'description' => t('Physical dimensions of the packaged product.'),
  989.           'weight' => 6,
  990.         ),
  991.         'add_to_cart' => array(
  992.           'label' => t('Add to cart form'),
  993.           'description' => t('Add to cart form'),
  994.           'weight' => 10,
  995.         ),
  996.       ),
  997.     );
  998.   }
  999.  
  1000.   return $extra;
  1001. }
  1002.  
  1003. /**
  1004.  * Implements hook_field_delete_instance().
  1005.  *
  1006.  * Resets a content type's default image field setting when that field instance
  1007.  * is removed.
  1008.  */
  1009. function uc_product_field_delete_instance($instance) {
  1010.   if ($instance['entity_type'] == 'node' && $instance['field_name'] == variable_get('uc_image_' . $instance['bundle'], NULL)) {
  1011.     variable_del('uc_image_' . $instance['bundle']);
  1012.   }
  1013. }
  1014.  
  1015. /**
  1016.  * Implements hook_uc_product_types().
  1017.  */
  1018. function uc_product_uc_product_types() {
  1019.   return array_keys(uc_product_node_info());
  1020. }
  1021.  
  1022. /**
  1023.  * Implements hook_uc_store_status().
  1024.  *
  1025.  * Displays the status of the product image handlers.
  1026.  *
  1027.  * @see uc_product_image_defaults()
  1028.  */
  1029. function uc_product_uc_store_status() {
  1030.   module_load_include('inc', 'content', 'includes/content.crud');
  1031.   // Check for filefields on products.
  1032.   if ($field = variable_get('uc_image_product', '')) {
  1033.     $instances = field_info_instances('node', 'product');
  1034.     $field_check = (bool) count($instances[$field]);
  1035.   }
  1036.   else {
  1037.     $field_check = FALSE;
  1038.   }
  1039.  
  1040.   if ($field_check) {
  1041.     $status = 'ok';
  1042.     $description = t('Product image support has been automatically configured by Ubercart.');
  1043.   }
  1044.   else {
  1045.     $status = 'warning';
  1046.     $description = t('<a href="!url">Click here</a> to automatically configure the following items for core image support:', array('!url' => url('admin/store/settings/products/defaults')));
  1047.  
  1048.     $items[] = t('The Image file field has not been created for products.');
  1049.  
  1050.     $description .= theme('item_list', array('items' => $items)) . t('(This action is not required and should not be taken if you do not need images or have implemented your own image support.)');
  1051.   }
  1052.  
  1053.   return array(array('status' => $status, 'title' => t('Images'), 'desc' => $description));
  1054. }
  1055.  
  1056. /**
  1057.  * Implements hook_uc_cart_display().
  1058.  */
  1059. function uc_product_uc_cart_display($item) {
  1060.   $node = node_load($item->nid);
  1061.  
  1062.   $element = array();
  1063.   $element['nid'] = array('#type' => 'value', '#value' => $node->nid);
  1064.   $element['module'] = array('#type' => 'value', '#value' => 'uc_product');
  1065.   $element['remove'] = array('#type' => 'submit', '#value' => t('Remove'));
  1066.  
  1067.   $element['title'] = array(
  1068.     '#markup' => node_access('view', $node) ? l($item->title, 'node/' . $node->nid) : check_plain($item->title),
  1069.   );
  1070.  
  1071.   $element['#total'] = $item->price * $item->qty;
  1072.   $element['#suffixes'] = array();
  1073.   $element['data'] = array('#type' => 'hidden', '#value' => serialize($item->data));
  1074.   $element['qty'] = array(
  1075.     '#type' => 'uc_quantity',
  1076.     '#title' => t('Quantity'),
  1077.     '#title_display' => 'invisible',
  1078.     '#default_value' => $item->qty,
  1079.     '#allow_zero' => TRUE,
  1080.   );
  1081.  
  1082.   $element['description'] = array('#markup' => '');
  1083.   if ($description = uc_product_get_description($item)) {
  1084.     $element['description']['#markup'] = $description;
  1085.   }
  1086.  
  1087.   return $element;
  1088. }
  1089.  
  1090. /**
  1091.  * Implements hook_uc_update_cart_item().
  1092.  */
  1093. function uc_product_uc_update_cart_item($nid, $data = array(), $qty, $cid = NULL) {
  1094.   if (!$nid) return NULL;
  1095.   $cid = !(is_null($cid) || empty($cid)) ? $cid : uc_cart_get_id();
  1096.   if ($qty < 1) {
  1097.     uc_cart_remove_item($nid, $cid, $data);
  1098.   }
  1099.   else {
  1100.     db_update('uc_cart_products')
  1101.       ->fields(array(
  1102.         'qty' => $qty,
  1103.         'changed' => REQUEST_TIME,
  1104.       ))
  1105.       ->condition('nid', $nid)
  1106.       ->condition('cart_id', $cid)
  1107.       ->condition('data', serialize($data))
  1108.       ->execute();
  1109.   }
  1110. }
  1111.  
  1112. /**
  1113.  * Implements hook_uc_add_to_cart_data().
  1114.  */
  1115. function uc_product_uc_add_to_cart_data($form_values) {
  1116.   if (isset($form_values['nid'])) {
  1117.     $node = node_load($form_values['nid']);
  1118.     return array(
  1119.       'shippable' => isset($node->shippable) ? $node->shippable : variable_get('uc_product_shippable_' . $node->type, 1),
  1120.       'type' => $node->type
  1121.     );
  1122.   }
  1123.   else {
  1124.     return array(
  1125.       'shippable' => variable_get('uc_product_shippable_product', 1),
  1126.       'type' => 'product',
  1127.     );
  1128.   }
  1129. }
  1130.  
  1131. /**
  1132.  * Implements hook_uc_product_class().
  1133.  */
  1134. function uc_product_uc_product_class($pcid, $op) {
  1135.   switch ($op) {
  1136.     case 'insert':
  1137.       db_update('node_type')
  1138.         ->fields(array(
  1139.           'base' => 'uc_product',
  1140.           'custom' => 0,
  1141.         ))
  1142.         ->condition('type', $pcid)
  1143.         ->execute();
  1144.       $result = db_query("SELECT n.vid, n.nid, p.unique_hash FROM {node} AS n LEFT JOIN {uc_products} AS p ON n.vid = p.vid WHERE n.type = :pcid", array(':pcid' => $pcid));
  1145.       foreach ($result as $node) {
  1146.         if (!$node->unique_hash) {
  1147.           $node->weight_units = variable_get('uc_weight_unit', 'lb');
  1148.           $node->length_units = variable_get('uc_length_unit', 'in');
  1149.           $node->pkg_qty = 1;
  1150.           $node->default_qty = 1;
  1151.           $node->shippable = 1;
  1152.           uc_product_insert($node);
  1153.         }
  1154.       }
  1155.     break;
  1156.   }
  1157. }
  1158.  
  1159. /**
  1160.  * Form builder for uc_catalog_buy_it_now_form().
  1161.  *
  1162.  * @see uc_product_forms()
  1163.  * @see uc_catalog_buy_it_now_form_validate()
  1164.  * @see uc_catalog_buy_it_now_form_submit()
  1165.  * @ingroup forms
  1166.  */
  1167. function uc_catalog_buy_it_now_form($form, &$form_state, $node) {
  1168.   $form['nid'] = array('#type' => 'hidden', '#value' => $node->nid);
  1169.   $form['actions'] = array('#type' => 'actions');
  1170.   $form['actions']['submit'] = array(
  1171.     '#type' => 'submit',
  1172.     '#value' => t('Add to cart'),
  1173.     '#id' => 'edit-submit-' . $node->nid,
  1174.     '#attributes' => array(
  1175.       'class' => array('list-add-to-cart'),
  1176.     ),
  1177.   );
  1178.  
  1179.   uc_form_alter($form, $form_state, __FUNCTION__);
  1180.  
  1181.   return $form;
  1182. }
  1183.  
  1184. /**
  1185.  * Redirects to the product page if attributes need to be selected.
  1186.  *
  1187.  * @see uc_catalog_buy_it_now_form()
  1188.  * @see uc_catalog_buy_it_now_form_submit()
  1189.  */
  1190. function uc_catalog_buy_it_now_form_validate($form, &$form_state) {
  1191.   if (module_exists('uc_attribute')) {
  1192.     $attributes = uc_product_get_attributes($form_state['values']['nid']);
  1193.     if (!empty($attributes)) {
  1194.       drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error');
  1195.       drupal_goto('node/' . $form_state['values']['nid']);
  1196.     }
  1197.   }
  1198. }
  1199.  
  1200. /**
  1201.  * Form submission handler for uc_catalog_buy_it_now_form().
  1202.  *
  1203.  * @see uc_catalog_buy_it_now_form()
  1204.  * @see uc_catalog_buy_it_now_form_validate()
  1205.  */
  1206. function uc_catalog_buy_it_now_form_submit($form, &$form_state) {
  1207.   $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], 1,  module_invoke_all('uc_add_to_cart_data', $form_state['values']), NULL, variable_get('uc_cart_add_item_msg', TRUE));
  1208. }
  1209.  
  1210. /**
  1211.  * Form to add the $node product to the cart.
  1212.  *
  1213.  * @param $node
  1214.  *   A product node.
  1215.  *
  1216.  * @see uc_product_forms()
  1217.  * @see uc_product_add_to_cart_form_submit()
  1218.  * @ingroup forms
  1219.  */
  1220. function uc_product_add_to_cart_form($form, &$form_state, $node) {
  1221.   $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
  1222.  
  1223.   if ($node->default_qty > 0 && variable_get('uc_product_add_to_cart_qty', FALSE)) {
  1224.     $form['qty'] = array(
  1225.       '#type' => 'uc_quantity',
  1226.       '#title' => t('Quantity'),
  1227.       '#default_value' => $node->default_qty,
  1228.     );
  1229.   }
  1230.   else {
  1231.     $form['qty'] = array('#type' => 'hidden', '#value' => $node->default_qty ? $node->default_qty : 1);
  1232.   }
  1233.  
  1234.   $form['actions'] = array('#type' => 'actions');
  1235.   $form['actions']['submit'] = array(
  1236.     '#type' => 'submit',
  1237.     '#value' => t('Add to cart'),
  1238.     '#id' => 'edit-submit-' . $node->nid,
  1239.     '#attributes' => array(
  1240.       'class' => array('node-add-to-cart'),
  1241.     ),
  1242.   );
  1243.  
  1244.   $form['node'] = array(
  1245.     '#type' => 'value',
  1246.     '#value' => isset($form_state['storage']['variant']) ? $form_state['storage']['variant'] : $node,
  1247.   );
  1248.  
  1249.   uc_form_alter($form, $form_state, __FUNCTION__);
  1250.  
  1251.   return $form;
  1252. }
  1253.  
  1254. /**
  1255.  * Form validation handler for uc_product_add_to_cart_form().
  1256.  */
  1257. function uc_product_add_to_cart_form_validate($form, &$form_state) {
  1258.   $form_state['storage']['variant'] = uc_product_load_variant($form_state['values']['nid'], module_invoke_all('uc_add_to_cart_data', $form_state['values']));
  1259. }
  1260.  
  1261. /**
  1262.  * Form submission handler for uc_product_add_to_cart_form().
  1263.  *
  1264.  * @see uc_product_add_to_cart_form()
  1265.  */
  1266. function uc_product_add_to_cart_form_submit($form, &$form_state) {
  1267.   $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], $form_state['values']['qty'],  module_invoke_all('uc_add_to_cart_data', $form_state['values']), NULL, variable_get('uc_cart_add_item_msg', TRUE));
  1268. }
  1269.  
  1270. /**
  1271.  * Returns an array of product node types.
  1272.  */
  1273. function uc_product_types() {
  1274.   return module_invoke_all('uc_product_types');
  1275. }
  1276.  
  1277. /**
  1278.  * Returns an associative array of product node type names keyed by ID.
  1279.  */
  1280. function uc_product_type_names() {
  1281.   $names = array();
  1282.  
  1283.   // Get all the node meta data.
  1284.   $node_info = module_invoke_all('node_info');
  1285.  
  1286.   // Loop through each product node type.
  1287.   foreach (uc_product_types() as $type) {
  1288.     $names[$type] = $node_info[$type]['name'];
  1289.   }
  1290.  
  1291.   return $names;
  1292. }
  1293.  
  1294. /**
  1295.  * Determines whether or not a given node or node type is a product.
  1296.  *
  1297.  * @param $node
  1298.  *   Either a full node object/array, a node ID, or a node type.
  1299.  *
  1300.  * @return
  1301.  *   TRUE or FALSE indicating whether or not a node type is a product node type.
  1302.  */
  1303. function uc_product_is_product($node) {
  1304.   // Load the node object if we received an integer as an argument.
  1305.   if (is_int($node)) {
  1306.     $node = node_load($node);
  1307.   }
  1308.  
  1309.   // Determine the node type based on the data type of $node.
  1310.   if (is_object($node)) {
  1311.     $type = $node->type;
  1312.   }
  1313.   elseif (is_array($node)) {
  1314.     $type = $node['type'];
  1315.   }
  1316.   elseif (is_string($node)) {
  1317.     $type = $node;
  1318.   }
  1319.  
  1320.   // If no node type was found, go ahead and return FALSE.
  1321.   if (!$type) {
  1322.     return FALSE;
  1323.   }
  1324.  
  1325.   // Return TRUE or FALSE depending on whether or not the node type is in the
  1326.   // product types array.
  1327.   return in_array($type, uc_product_types());
  1328. }
  1329.  
  1330. /**
  1331.  * Determines whether or not a given form array is a product node form.
  1332.  *
  1333.  * @param $form
  1334.  *   The form array to examine.
  1335.  *
  1336.  * @return
  1337.  *   TRUE or FALSE indicating whether or not the form is a product node form.
  1338.  */
  1339. function uc_product_is_product_form($form) {
  1340.   return !empty($form['#node_edit_form']) && uc_product_is_product($form['#node']);
  1341. }
  1342.  
  1343. /**
  1344.  * Gets all models of a product (node).
  1345.  *
  1346.  * Gathers any modules' models on this node, then add the node's SKU and the
  1347.  * optional 'Any' option.
  1348.  *
  1349.  * @param $nid
  1350.  *   The node ID of the product.
  1351.  * @param $add_blank
  1352.  *   String to use for the initial blank entry. If not desired, set to NULL
  1353.  *   or FALSE. Make sure to localize the string first. Defaults to '- Any -'.
  1354.  *
  1355.  * @return
  1356.  *   An associative array of model numbers. The key for '- Any -' is the empty
  1357.  *   string.
  1358.  */
  1359. function uc_product_get_models($nid, $add_blank = TRUE) {
  1360.   // Get any modules' SKUs on this node.
  1361.   $models = module_invoke_all('uc_product_models', $nid);
  1362.   // Add the base SKU of the node.
  1363.   $models[] = db_query('SELECT model FROM {uc_products} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
  1364.  
  1365.   // Now we map the SKUs to the keys, for form handling, etc.
  1366.   $models = drupal_map_assoc($models);
  1367.   // Sort the SKUs.
  1368.   asort($models);
  1369.  
  1370.   // And finally, we prepend 'Any' so it's the first option.
  1371.   if (!empty($add_blank) || $add_blank === '') {
  1372.     if ($add_blank === TRUE) {
  1373.       $add_blank = t('- Any -');
  1374.     }
  1375.     return array('' => $add_blank) + $models;
  1376.   }
  1377.  
  1378.   return $models;
  1379. }
  1380.  
  1381. /**
  1382.  * Gets the cost of a product node.
  1383.  *
  1384.  * @param $node_id
  1385.  *   nid of the selected node
  1386.  *
  1387.  * @return
  1388.  *   float - cost
  1389.  */
  1390. function uc_product_get_cost($node_id) {
  1391.   $product = node_load($node_id);
  1392.   return $product->cost;
  1393. }
  1394.  
  1395. /**
  1396.  * Returns a product node's first attached image.
  1397.  *
  1398.  * @param $node_id
  1399.  *   The node's id.
  1400.  * @param $style
  1401.  *   The image style used to format the image. 'uc_product' by default.
  1402.  *
  1403.  * @return
  1404.  *   A renderable array of the first product image, linked to the
  1405.  *   product node, or an empty array if no image is available.
  1406.  */
  1407. function uc_product_get_picture($nid, $style = 'uc_product') {
  1408.   $product = node_load($nid);
  1409.   if (!$product) {
  1410.     return array();
  1411.   }
  1412.   $field_name = variable_get('uc_image_' . $product->type, 'uc_product_image');
  1413.   $output = array();
  1414.  
  1415.   if ($field_name && isset($product->$field_name)) {
  1416.     $elements = field_view_field('node', $product, $field_name, array(
  1417.       'label' => 'hidden',
  1418.       'type' => 'image',
  1419.       'settings' => array(
  1420.         'image_link' => 'content',
  1421.         'image_style' => $style,
  1422.       ),
  1423.     ));
  1424.  
  1425.     // Extract the part of the render array we need.
  1426.     $output = isset($elements[0]) ? $elements[0] : array();
  1427.     if (isset($elements['#access'])) {
  1428.       $output['#access'] = $elements['#access'];
  1429.     }
  1430.   }
  1431.  
  1432.   return $output;
  1433. }
  1434.  
  1435. /**
  1436.  * Finds the JavaScript image display module to use on product pages.
  1437.  */
  1438. function uc_product_get_image_widget() {
  1439.   static $got_widget = FALSE, $image_widget = array();
  1440.  
  1441.   // Get the current image widget, if any.
  1442.   if (!$got_widget) {
  1443.     // Invoke hook to find widgets.
  1444.     $image_widgets = uc_store_get_image_widgets();
  1445.  
  1446.     // Find widget preference, if any.
  1447.     $widget_name = variable_get('uc_product_image_widget', NULL);
  1448.  
  1449.     if ($widget_name != NULL) {
  1450.       // Widget to use has been set in admin menu.
  1451.       $image_widget = $image_widgets[$widget_name];
  1452.     }
  1453.     else {
  1454.       // Widget to use has not been chosen, so default to first element of
  1455.       // array, if any.
  1456.       $keys = array_keys($image_widgets);
  1457.       if ($keys) {
  1458.         $image_widget = $image_widgets[$keys[0]];
  1459.         variable_set('uc_product_image_widget', $keys[0]);
  1460.       }
  1461.     }
  1462.  
  1463.     $got_widget = TRUE;
  1464.   }
  1465.  
  1466.   return $image_widget;
  1467. }
  1468.  
  1469. /**
  1470.  * Returns HTML for the product description.
  1471.  *
  1472.  * Modules adding information use hook_uc_product_description() and modules
  1473.  * wanting to alter the output before rendering can do so by implementing
  1474.  * hook_uc_product_description_alter(). By default, all descriptions supplied
  1475.  * by modules via hook_uc_product_description() are concatenated together.
  1476.  *
  1477.  * NOTE: hook_uc_product_description() supercedes the deprecated
  1478.  * hook_cart_item_description().
  1479.  *
  1480.  * @param $product
  1481.  *   Product.
  1482.  *
  1483.  * @return
  1484.  *   HTML rendered product description.
  1485.  */
  1486. function uc_product_get_description($product) {
  1487.   // Run through implementations of hook_uc_product_description().
  1488.   $description = module_invoke_all('uc_product_description', $product);
  1489.  
  1490.   // Now allow alterations via hook_uc_product_description_alter().
  1491.   drupal_alter('uc_product_description', $description, $product);
  1492.  
  1493.   return drupal_render($description);
  1494. }
  1495.  
  1496. /**
  1497.  * Load a product class or all classes.
  1498.  */
  1499. function uc_product_class_load($class_id = NULL, $reset = FALSE) {
  1500.   static $classes;
  1501.   if (!isset($classes) || $reset) {
  1502.     // Load classes from database.
  1503.     $classes = array();
  1504.     $result = db_query("SELECT * FROM {uc_product_classes}");
  1505.     while ($class = $result->fetchObject()) {
  1506.       $class->locked = FALSE;
  1507.       $classes[$class->pcid] = $class;
  1508.     }
  1509.  
  1510.     // Merge any module-defined classes.
  1511.     foreach (module_invoke_all('uc_product_default_classes') as $pcid => $class) {
  1512.       // The default class can be overridden by a module if needed,
  1513.       // but is not treated as a real class elsewhere.
  1514.       if ($pcid == 'product') {
  1515.         continue;
  1516.       }
  1517.       $class += array(
  1518.         'pcid' => $pcid,
  1519.         'name' => $pcid,
  1520.         'description' => '',
  1521.       );
  1522.  
  1523.       if (isset($classes[$pcid])) {
  1524.         // Merge defaults with the existing database info.
  1525.         $classes[$pcid] = (object) array_merge($class, (array) $classes[$pcid]);
  1526.       }
  1527.       else {
  1528.         // Ensure the class info is saved in the database.
  1529.         drupal_write_record('uc_product_classes', $class);
  1530.         $classes[$pcid] = (object) $class;
  1531.       }
  1532.  
  1533.       // Module-defined classes cannot be deleted.
  1534.       $classes[$pcid]->locked = TRUE;
  1535.     }
  1536.   }
  1537.  
  1538.   return is_null($class_id) ? $classes : $classes[$class_id];
  1539. }
  1540.  
  1541. /**
  1542.  * Returns data for a product feature, given a feature ID and array key.
  1543.  *
  1544.  * @param $fid
  1545.  *   The string ID of the product feature you want to get data from.
  1546.  * @param $key
  1547.  *   The key in the product feature array you want: title, callback, delete,
  1548.  *   settings.
  1549.  *
  1550.  * @return
  1551.  *   The value of the key you specify.
  1552.  */
  1553. function uc_product_feature_data($fid, $key) {
  1554.   static $features;
  1555.  
  1556.   if (empty($features)) {
  1557.     foreach (module_invoke_all('uc_product_feature') as $feature) {
  1558.       $features[$feature['id']] = $feature;
  1559.     }
  1560.   }
  1561.  
  1562.   return $features[$fid][$key];
  1563. }
  1564.  
  1565. /**
  1566.  * Returns a form array with some default hidden values and submit button.
  1567.  *
  1568.  * @param $form
  1569.  *   The form array you wish to add the elements to.
  1570.  *
  1571.  * @return
  1572.  *   The form array with elements added for the nid, pfid, submit button, and
  1573.  *   cancel link.
  1574.  *
  1575.  * @ingroup forms
  1576.  */
  1577. function uc_product_feature_form($form, &$form_state, $node, $feature) {
  1578.   $form['nid'] = array(
  1579.     '#type' => 'hidden',
  1580.     '#value' => $node->nid,
  1581.   );
  1582.  
  1583.   // Forms to add a feature are only given an empty array.
  1584.   if (!empty($feature)) {
  1585.     $form['pfid'] = array(
  1586.       '#type' => 'hidden',
  1587.       '#value' => $feature['pfid'],
  1588.     );
  1589.   }
  1590.  
  1591.   $form['actions'] = array('#type' => 'actions');
  1592.   $form['actions']['submit'] = array(
  1593.     '#type' => 'submit',
  1594.     '#value' => t('Save feature'),
  1595.   );
  1596.   $form['actions']['cancel'] = array(
  1597.     '#markup' => l(t('Cancel'), 'node/' . $node->nid . '/edit/features'),
  1598.   );
  1599.  
  1600.   return $form;
  1601. }
  1602.  
  1603. /**
  1604.  * Saves a product feature to a product node.
  1605.  *
  1606.  * @param $data
  1607.  *   An array consisting of the following keys:
  1608.  *   - pfid: (optional) When editing an existing product feature, the numeric
  1609.  *     ID of the feature.
  1610.  *   - nid: The numeric ID of the product node.
  1611.  *   - fid: The string ID of the feature type.
  1612.  *   - description: The string describing the feature for the overview table.
  1613.  */
  1614. function uc_product_feature_save(&$data) {
  1615.   if (empty($data['nid']) && arg(0) == 'node' && intval(arg(1)) > 0) {
  1616.     $data['nid'] = intval(arg(1));
  1617.   }
  1618.   if (empty($data['pfid'])) {
  1619.     if (arg(0) == 'node' && arg(3) == 'features' && intval(arg(5)) > 0) {
  1620.       $data['pfid'] = intval(arg(5));
  1621.     }
  1622.   }
  1623.  
  1624.   // First attempt to update an existing row.
  1625.   $result = drupal_write_record('uc_product_features', $data, !empty($data['pfid']) ? 'pfid' : array());
  1626.  
  1627.   // Otherwise insert this feature as a new row.
  1628.   if ($result == SAVED_NEW) {
  1629.     drupal_set_message(t('The product feature has been added.'));
  1630.   }
  1631.   elseif ($result == SAVED_UPDATED) {
  1632.     drupal_set_message(t('The product feature has been updated.'));
  1633.   }
  1634.   else {
  1635.     drupal_set_message(t('The product feature was unable to be saved.'));
  1636.   }
  1637.  
  1638.   return 'node/' . $data['nid'] . '/edit/features';
  1639. }
  1640.  
  1641. /**
  1642.  * Loads all product feature for a node.
  1643.  *
  1644.  * @param $nid
  1645.  *   The product node ID.
  1646.  *
  1647.  * @return
  1648.  *   The array of all product features object.
  1649.  */
  1650. function uc_product_feature_load_multiple($nid) {
  1651.   $features = db_query("SELECT * FROM {uc_product_features} WHERE nid = :nid ORDER BY pfid ASC", array(':nid' => $nid))->fetchAllAssoc('pfid');
  1652.  
  1653.   return $features;
  1654. }
  1655.  
  1656. /**
  1657.  * Loads a product feature object.
  1658.  *
  1659.  * @todo: should return an object instead of array.
  1660.  *
  1661.  * @param $pfid
  1662.  *   The product feature ID.
  1663.  * @param $fid
  1664.  *   Optional. Specify a specific feature id.
  1665.  *
  1666.  * @return
  1667.  *   The product feature array.
  1668.  */
  1669. function uc_product_feature_load($pfid) {
  1670.   $feature = db_query("SELECT * FROM {uc_product_features} WHERE pfid = :pfid", array(':pfid' => $pfid))->fetchAssoc();
  1671.  
  1672.   return $feature;
  1673. }
  1674.  
  1675. /**
  1676.  * Deletes a product feature object.
  1677.  *
  1678.  * @param $pfid
  1679.  *   The product feature ID.
  1680.  *
  1681.  * @return
  1682.  *   The product feature object.
  1683.  */
  1684. function uc_product_feature_delete($pfid) {
  1685.   $feature = uc_product_feature_load($pfid);
  1686.  
  1687.   // Call the delete function for this product feature if it exists.
  1688.   $func = uc_product_feature_data($feature['fid'], 'delete');
  1689.   if (function_exists($func)) {
  1690.     $func($feature);
  1691.   }
  1692.   db_delete('uc_product_features')
  1693.     ->condition('pfid', $pfid)
  1694.     ->execute();
  1695.  
  1696.   return SAVED_DELETED;
  1697. }
  1698.  
  1699. /**
  1700.  * Creates a file field with an image field widget, and attach it to products.
  1701.  *
  1702.  * This field is used by default on the product page, as well as on the cart
  1703.  * and catalog pages to represent the products they list. Instances are added
  1704.  * to new product classes, and other node types that claim product-ness should
  1705.  * call this function for themselves.
  1706.  *
  1707.  * @param $type
  1708.  *   The content type to which the image field is to be attached. This may be a
  1709.  *   a single type as a string, or an array of types. If NULL, all product
  1710.  *   types get an instance of the field.
  1711.  */
  1712. function uc_product_add_default_image_field($type = NULL) {
  1713.   $field = field_info_field('uc_product_image');
  1714.  
  1715.   // Set up field if it doesn't exist.
  1716.   if (!$field) {
  1717.     $field = array(
  1718.       'field_name' => 'uc_product_image',
  1719.       'type' => 'image',
  1720.       'cardinality' => FIELD_CARDINALITY_UNLIMITED,
  1721.     );
  1722.  
  1723.     field_create_field($field);
  1724.  
  1725.     // Initialize this because field_info_field() would have set it.
  1726.     $field['bundles'] = array();
  1727.   }
  1728.  
  1729.   $label = t('Image');
  1730.   $new_instance = array(
  1731.     'entity_type' => 'node',
  1732.     'label' => $label,
  1733.     'weight' => -2,
  1734.     'widget' => array(
  1735.       'type' => 'image_image',
  1736.     ),
  1737.     'display' => array(
  1738.       'full' => array(
  1739.         'label' => 'hidden',
  1740.         'type' => 'uc_product_image',
  1741.       ),
  1742.       'teaser' => array(
  1743.         'label' => 'hidden',
  1744.         'type' => 'uc_product_image',
  1745.       ),
  1746.     ),
  1747.   );
  1748.  
  1749.   if ($type) {
  1750.     // Accept single or multiple types as input.
  1751.     $types = (array) $type;
  1752.   }
  1753.   else {
  1754.     $types = uc_product_types();
  1755.   }
  1756.   foreach ($types as $type) {
  1757.     $new_instance['bundle'] = $type;
  1758.  
  1759.     $field_name = variable_get('uc_image_' . $type, '');
  1760.     if (empty($field_name)) {
  1761.       $field_name = 'uc_product_image';
  1762.     }
  1763.     $new_instance['field_name'] = $field_name;
  1764.  
  1765.     $instance = field_info_instance('node', $field_name, $type);
  1766.  
  1767.     // Only add the instance if it doesn't exist. Don't overwrite any changes.
  1768.     if (!$instance) {
  1769.       field_create_instance($new_instance);
  1770.       variable_set('uc_image_' . $type, $field_name);
  1771.     }
  1772.   }
  1773. }
  1774.  
  1775. /**
  1776.  * Implements hook_image_default_styles().
  1777.  */
  1778. function uc_product_image_default_styles() {
  1779.   $styles = array();
  1780.  
  1781.   $styles['uc_product'] = array(
  1782.     'effects' => array(
  1783.       array(
  1784.         'name' => 'image_scale',
  1785.         'data' => array(
  1786.           'width' => '250',
  1787.           'height' => '250',
  1788.           'upscale' => 0,
  1789.         ),
  1790.         'weight' => '0',
  1791.       ),
  1792.     ),
  1793.   );
  1794.  
  1795.   $styles['uc_product_full'] = array(
  1796.     'effects' => array(),
  1797.   );
  1798.  
  1799.   $styles['uc_product_list'] = array(
  1800.     'effects' => array(
  1801.       array(
  1802.         'name' => 'image_scale',
  1803.         'data' => array(
  1804.           'width' => '100',
  1805.           'height' => '100',
  1806.           'upscale' => 0,
  1807.         ),
  1808.         'weight' => '0',
  1809.       ),
  1810.     ),
  1811.   );
  1812.  
  1813.   $styles['uc_thumbnail'] = array(
  1814.     'effects' => array(
  1815.       array(
  1816.         'name' => 'image_scale',
  1817.         'data' => array(
  1818.           'width' => '35',
  1819.           'height' => '35',
  1820.           'upscale' => 0,
  1821.         ),
  1822.         'weight' => '0',
  1823.       ),
  1824.     ),
  1825.   );
  1826.  
  1827.   return $styles;
  1828. }
  1829.  
  1830. /**
  1831.  * Implements hook_views_api().
  1832.  */
  1833. function uc_product_views_api() {
  1834.   return array(
  1835.     'api' => '2.0',
  1836.     'path' => drupal_get_path('module', 'uc_product') . '/views',
  1837.   );
  1838. }
  1839.  
  1840.  
  1841. /**
  1842.  * Implements hook_features_api().
  1843.  */
  1844. function uc_product_features_api() {
  1845.   return array(
  1846.     'uc_product_classes' => array(
  1847.       'name' => t('Ubercart product classes'),
  1848.       'feature_source' => TRUE,
  1849.       'default_hook' => 'uc_product_default_classes',
  1850.       'file' => drupal_get_path('module', 'uc_product') . '/uc_product.features.inc',
  1851.     ),
  1852.   );
  1853. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement