You are here

commerce_apd.module in Auto Product Display 7

Contain main functions for Auto Product Display

File

commerce_apd.module
View source
<?php

/**
 * @file
 * Contain main functions for Auto Product Display
 */

/**
 * Implements hook_menu().
 */
function commerce_apd_menu() {

  // Add menu to open the configuration page.
  $items["admin/commerce/config/auto-product-display"] = array(
    "title" => "Auto product display",
    "description" => "Configure the auto product display behavior.",
    "type" => MENU_NORMAL_ITEM,
    "access arguments" => array(
      "configure auto product display",
    ),
    "page callback" => "drupal_get_form",
    "page arguments" => array(
      "_commerce_apd_configuration",
    ),
    "file" => "commerce_apd_admin.inc",
  );
  return $items;
}

/**
 * Implements hook_entity_update().
 */
function commerce_apd_entity_update($entity, $type) {

  // Check if the updated entity is commerce product.
  if ($type == "commerce_product") {

    // Get the product ID.
    $product_id = $entity->product_id;

    // Get the referencing nodes.
    $referencing_nodes = _commerce_apd_get_nodes_referencing($product_id);

    // Loop through the nodes
    foreach ($referencing_nodes as $nid) {

      // Update node status based on the product status.
      $node = node_load($nid);
      $node->status = $entity->status;
      node_save($node);
    }
  }
}

/**
 * Implements hook_permission().
 * Add permission to configure auto product display.
 */
function commerce_apd_permission() {
  return array(
    "configure auto product display" => array(
      "title" => t("configure auto product display"),
      "description" => t("Configure the auto product display behavior."),
    ),
  );
}

/**
 * Implements hook_form_alter().
 */
function commerce_apd_form_commerce_product_ui_product_delete_form_alter(&$form, &$form_state, $form_id) {
  $content_type = _commerce_apd_get_content_type($form_state['product']->type);

  // If we aren't attaching the product to any specific content type, ignore.
  if ($content_type == "") {
    return;
  }

  // Add one submit handler to delete product display after product deletion.
  $form['#submit'][] = '_commerce_apd_delete_referencing_node';

  // Reverse the array so the new function will be executed first.
  $form['#submit'] = array_reverse($form['#submit']);
}

/**
 * Form submission handler for deleting product display.
 */
function _commerce_apd_delete_referencing_node($form, &$form_state) {

  // Get the referencing node.
  $referencing_nodes = _commerce_apd_get_nodes_referencing($form_state['product']->product_id);

  // Make sure there is only one node referenced to the product and the configuration is set for that.
  if (count($referencing_nodes) == 1 && variable_get("commerce_apd_auto_delete_display", 0) == 1) {
    node_delete($referencing_nodes[0]);
  }
}

/**
 * Implements hook_form_alter().
 * Alter the product creation form to add a new option to
 * automatically create product display.
 */
function commerce_apd_form_commerce_product_ui_product_form_alter(&$form, &$form_state, $form_id) {

  // Get the default content type for product display.
  $content_type = _commerce_apd_get_content_type($form_state['commerce_product']->type);

  // If we aren't attaching the product to any specific content type, ignore.
  if ($content_type == "") {

    // Notify user that this product has no product display.
    drupal_set_message(t("This product is not configured to has product display, so it should be added manually when creating product display."));
    return;
  }

  // Get the referencing node(s).
  $existing_references = array();
  if (isset($form_state['commerce_product']->product_id)) {
    $referenced_from = _commerce_apd_get_nodes_referencing($form_state['commerce_product']->product_id);
    if (!empty($referenced_from)) {
      foreach ($referenced_from as $reference) {
        $existing_references[] = $reference;
      }
    }
  }

  // Count the existing references.
  $count_existing_references = count($existing_references);

  // If new product creation or no referencing node found.
  if (isset($form_state['commerce_product']->is_new) || $count_existing_references <= 1) {

    // Check if the product display creation form should be displayed automatically.
    $show_form = variable_get("commerce_apd_show_product_display_form", 0);

    // Show the checkbox if the form for creating product display is not configured
    // to be displayed automatically and there is no node referencing to it.
    if ($show_form == 0 && $count_existing_references == 0) {

      // Display the option for creating product display.
      // Use AJAX to displaying or hiding the form.
      $form['auto_display'] = array(
        '#type' => 'checkbox',
        '#title' => t('Create product display'),
        '#description' => t('Check this options if you want to create the display for this product.'),
        '#weight' => $form['actions']['#weight'] - 2,
        '#ajax' => array(
          'event' => 'click',
          'callback' => '_commerce_apd_product_display_form',
          'wrapper' => 'create-display-fieldset',
        ),
      );
    }

    // If there is only one node referencing this product.
    if ($count_existing_references == 1) {

      // Load the node.
      $node = node_load($existing_references[0]);

      // Save node id to a hidden input for deciding to update node or create new node
      // when the form is submitted.
      $form['commerce_apd_referenced_nid'] = array(
        "#type" => "hidden",
        "#value" => $existing_references[0],
      );
    }
    else {

      // Prepare node object.
      $node = new stdClass();
      $node->type = $content_type;
    }

    // Get the field name which type is product reference.
    $field_product = _commerce_apd_get_product_field($content_type);

    // Create product display fieldset to hold sub-form.
    // Initially it will be hidden using inline style.
    $form['product_display'] = array(
      '#type' => 'fieldset',
      '#attributes' => array(
        'id' => 'create-display-fieldset',
        'style' => 'display:none',
      ),
      '#legend' => $show_form == 0 && $count_existing_references == 0 ? t('Product display') : '',
      '#weight' => $form['actions']['#weight'] - 1,
    );

    // If the form is not shown automatically and the value of the auto display checkbox is checked,
    // or there is node referencing to this product, or for is form is set to be shown automatically,
    // We will display the form.
    $auto_display = isset($form_state['values']['auto_display']) ? $form_state['values']['auto_display'] : FALSE;
    if ($show_form == 0 && $auto_display == TRUE || $count_existing_references == 1 || $show_form == 1) {

      // Show the fieldset by removing the style.
      unset($form['product_display']['#attributes']['style']);

      // Attach the product display form to product creation variable.
      field_attach_form('node', $node, $form['product_display'], $form_state);

      // Check if product display title can be defined manually or just automatically.
      if (variable_get("commerce_apd_auto_product_display_title", 1) == 0) {

        // The trick to find the minimum weight from each form components.
        $min_weight = 0;
        foreach ($form['product_display'] as $key => $component) {
          if (substr($key, 0, 1) !== "#" && is_array($component)) {
            if (isset($component['#weight'])) {
              if ($component['#weight'] < $min_weight) {
                $min_weight = $component['#weight'];
              }
            }
          }
        }

        // Display new field to override product display title.
        // This field will be displayed first. That's why we need the minimum weight.
        $form['product_display']['display_title'] = array(
          '#title' => t('Displayed title'),
          '#description' => t('The title that will be displayed in front-end. Leave it empty to use the same title as the product.'),
          '#type' => 'textfield',
          '#weight' => $min_weight - 1,
          '#default_value' => isset($node->title) ? $node->title : '',
        );
      }

      // Don't include the product reference field because it will be filled
      // automatically after form submission.
      unset($form['product_display'][$field_product]);
    }

    // Add submit handler function.
    $form['actions']['submit']['#submit'][] = 'commerce_apd_product_form_reference_submit';
  }
  else {

    // If there are more than one nodes referencing this product,
    // they will be displayed is list with the update link.
    // We made fieldset for containing the list.
    $form['referencing_nodes'] = array(
      '#type' => 'fieldset',
      '#title' => t('Product display'),
      '#weight' => $form['actions']['#weight'],
    );

    // Get the referencing nodes including the 'update' link
    // for updating the product display.
    $rows = array();
    foreach ($existing_references as $key => $existing_reference) {
      $rows[] = _commerce_apd_get_product_reference_detail($existing_reference, $form_state['commerce_product']->product_id);
    }

    // Finally, display them in a table.
    $form['referencing_nodes']['table'] = array(
      '#theme' => 'table',
      '#header' => array(
        'Title',
        'Language',
        'Action',
      ),
      '#rows' => $rows,
      '#empty' => t('No content available.'),
    );
  }
}

/**
 * Handle the form submission process. Product display will be created here.
 */
function commerce_apd_product_form_reference_submit(&$form, &$form_state) {
  global $user;

  // Check if the form for creating product display is configured to be displayed automatically.
  $show_form = variable_get("commerce_apd_show_product_display_form", 0);

  // Get the value of "create product display" checkbox if it exists.
  $auto_display = isset($form_state['values']['auto_display']) ? $form_state['values']['auto_display'] : FALSE;

  // Check if the product display should be created automatically or
  // the form for creating product display is configured to be displayed automatically.
  if ($auto_display == TRUE || $show_form == 1 || isset($form_state['values']['commerce_apd_referenced_nid'])) {

    // Get the default content type for product display.
    $content_type = _commerce_apd_get_content_type($form_state['commerce_product']->type);

    // Get the field name which type is product reference.
    $field_product = _commerce_apd_get_product_field($content_type);

    // TO DO: Find a better way to store language for node.
    $lang = LANGUAGE_NONE;

    // If there is no submitted value storing the referenced node id, create a new node.
    if (!isset($form_state['values']['commerce_apd_referenced_nid'])) {

      // Set some fields value.
      $node = new stdClass();
      $node->is_new = TRUE;
      $node->type = $content_type;
      $node->uid = $user->uid;

      // Variable for storing temporary attached form.
      $form2 = array();

      // Variable for storing product display form which
      // fields will be attached to node.
      $node_form = array();

      // Attached product display form to retreive its field.
      field_attach_form('node', $node, $form2, $form_state);

      // Unset the product reference field.
      unset($form2[$field_product]);

      // Loop through attached form
      foreach ($form2 as $field => $value) {

        // Fill the node form for submission.
        if (isset($form[$field])) {
          $node_form[$field] = $form[$field];
        }
      }

      // Add other fields (if any) to the node class,
      // so they value can also be saved.
      entity_form_submit_build_entity('node', $node, $node_form, $form_state);

      // Check if product display title can be defined manually or just automatically.
      $auto_product_display_title = true;
      if (variable_get("commerce_apd_auto_product_display_title", 1) == 0) {
        $auto_product_display_title = false;
      }

      // Check if the product display title will be overriden.
      // This code must be placed after running entity_form_submit_build_entity above.
      $display_title = isset($form_state['values']['display_title']) ? $form_state['values']['display_title'] : '';
      if ($display_title != '' && $auto_product_display_title == false) {
        $node->title = $form_state['values']['display_title'];
      }
      else {
        $node->title = $form_state['values']['title'];
      }

      // Save the product reference field.
      $node->{$field_product} = array(
        $lang => array(
          array(
            'product_id' => $form_state['build_info']['args'][0]->product_id,
          ),
        ),
      );

      // Check node publishing option.
      $options = variable_get('node_options_' . $node->type, 0);
      if ($options == 0) {
        $node->status = 1;
      }
      else {
        if (in_array('status', $options)) {
          $node->status = 1;
        }
        else {
          $node->status = 0;
        }
      }

      // Check node promotion.
      if ($options == 0) {
        $node->promote = 1;
      }
      else {
        if (in_array('promote', $options)) {
          $node->promote = 1;
        }
        else {
          $node->promote = 0;
        }
      }

      // Check sticky option.
      if (is_array($options)) {
        if (in_array('sticky', $options)) {
          $node->sticky = 1;
        }
        else {
          $node->sticky = 0;
        }
      }
      else {
        $node->sticky = 0;
      }

      // Check node comment option.
      $options = variable_get('comment_' . $node->type, array());
      if (count($options) == 0) {

        // Comment is open by default.
        $node->comment = 2;
      }
      else {
        $node->comment = intval($options);
      }

      // Set language here.
      $node->language = $lang;

      // Finally save it.
      node_save($node);
    }
    else {

      // Update existing node.
      $node = node_load($form_state['values']['commerce_apd_referenced_nid']);

      // Make sure if node exists.
      if (is_object($node)) {

        // Variable for storing temporary attached form.
        $form2 = array();

        // Variable for storing product display form which
        // fields will be attached to node.
        $node_form = array();

        // Attached product display form to retreive its field.
        field_attach_form('node', $node, $form2, $form_state);

        // Unset the product reference field.
        unset($form2[$field_product]);

        // Loop through attached form
        foreach ($form2 as $field => $value) {

          // Fill the node form for submission.
          if (isset($form[$field])) {
            $node_form[$field] = $form[$field];
          }
        }

        // Add other fields (if any) to the node class,
        // so they value can also be saved.
        entity_form_submit_build_entity('node', $node, $node_form, $form_state);

        // Check if product display title can be defined manually or just automatically.
        $auto_product_display_title = true;
        if (variable_get("commerce_apd_auto_product_display_title", 1) == 0) {
          $auto_product_display_title = false;
        }

        // Check if the product display title will be overriden.
        // This code must be placed after running entity_form_submit_build_entity above.
        $display_title = isset($form_state['values']['display_title']) ? $form_state['values']['display_title'] : '';
        if ($display_title != '' && $auto_product_display_title == false) {
          $node->title = $form_state['values']['display_title'];
        }
        else {
          $node->title = $form_state['values']['title'];
        }

        // Set language here.
        $node->language = $lang;

        // Check node revision option.
        $options = variable_get('node_options_' . $node->type, array());
        $node->revision = in_array('revision', $options);

        // Finally update it.
        node_save($node);
      }
    }
  }
}

/**
 * Get the field name in product display that has reference to product.
 */
function _commerce_apd_get_product_reference_fields() {
  $product_reference_fields = array();
  $fields = field_info_fields();
  foreach ($fields as $field) {
    if ($field['type'] == 'commerce_product_reference' && $field['field_name'] != 'commerce_product') {
      $product_reference_fields[] = $field['field_name'];
    }
  }
  return $product_reference_fields;
}

/**
 * Get the entities that has reference to a product.
 */
function _commerce_apd_get_nodes_referencing($product_id) {
  $product_fields = _commerce_apd_get_product_reference_fields();
  $entities = array();
  foreach ($product_fields as $field) {
    $result = db_query('SELECT entity_id FROM {field_data_' . $field . '} df WHERE df.' . $field . '_product_id = :pid AND df.entity_type = :entity_type', array(
      ':pid' => $product_id,
      ':entity_type' => 'node',
    ));
    foreach ($result as $reference) {
      $entities[] = $reference->entity_id;
    }
  }
  return $entities;
}

/**
 * Get the detail information of node that has reference to a product.
 */
function _commerce_apd_get_product_reference_detail($existing_reference, $product_id) {
  if ($existing_reference != '') {
    $node_obj = db_query('SELECT n.nid, n.title, n.language FROM {node} n WHERE nid = :nid', array(
      ':nid' => $existing_reference,
    ))
      ->fetchObject();
    $node_title = $node_obj->title;
    $node_id = $node_obj->nid;
    $node_language = $node_obj->language;
  }
  else {
    $node_title = '';
    $node_id = '';
    $node_language = '';
  }
  return array(
    'title' => $node_title,
    'language' => $node_language,
    'link' => l(t('view'), 'node/' . $node_id) . ' &nbsp;&nbsp;&nbsp; ' . l(t('edit'), 'node/' . $node_id . '/edit', array(
      'query' => array(
        'destination' => 'admin/commerce/products/' . $product_id . '/edit?destination=admin/commerce/products',
      ),
    )),
  );
}

/**
 * Get one field from content type which type is product reference.
 */
function _commerce_apd_get_product_field($content_type) {
  $field = "";

  // Select the product reference field from content type (LIMIT TO 1 PRODUCT DISPLAY ONLY).
  $rs = db_query_range("SELECT a.field_name FROM {field_config_instance} a\n    INNER JOIN {field_config} b ON a.field_id = b.id\n    WHERE a.entity_type = :type AND a.bundle= :bundle AND\n      b.type = :reference AND b.module = :reference", 0, 1, array(
    ':type' => 'node',
    ':bundle' => $content_type,
    ':reference' => 'commerce_product_reference',
  ));
  if ($rs
    ->rowCount() > 0) {
    $data = $rs
      ->fetchObject();
    $field = $data->field_name;
  }
  return $field;
}

/**
 * Get default product display content type for product.
 */
function _commerce_apd_get_content_type($product_type) {
  $content_type = variable_get("commerce_apd_product_display", array());
  if (isset($content_type[$product_type])) {
    $content_type = $content_type[$product_type];
  }
  else {
    $content_type = "";
  }
  return $content_type;
}

/**
 * Function for handling AJAX request for showing create product display form.
 */
function _commerce_apd_product_display_form($form, $form_state) {

  // Return the product display section only.
  return $form['product_display'];
}

Functions

Namesort descending Description
commerce_apd_entity_update Implements hook_entity_update().
commerce_apd_form_commerce_product_ui_product_delete_form_alter Implements hook_form_alter().
commerce_apd_form_commerce_product_ui_product_form_alter Implements hook_form_alter(). Alter the product creation form to add a new option to automatically create product display.
commerce_apd_menu Implements hook_menu().
commerce_apd_permission Implements hook_permission(). Add permission to configure auto product display.
commerce_apd_product_form_reference_submit Handle the form submission process. Product display will be created here.
_commerce_apd_delete_referencing_node Form submission handler for deleting product display.
_commerce_apd_get_content_type Get default product display content type for product.
_commerce_apd_get_nodes_referencing Get the entities that has reference to a product.
_commerce_apd_get_product_field Get one field from content type which type is product reference.
_commerce_apd_get_product_reference_detail Get the detail information of node that has reference to a product.
_commerce_apd_get_product_reference_fields Get the field name in product display that has reference to product.
_commerce_apd_product_display_form Function for handling AJAX request for showing create product display form.