<?php

/**
 * @file
 * Tests for adding and displaying order reference fields.
 */

/**
 * Functional tests for the Order Reference module.
 */
class CommerceOrderReferenceAdminTest extends CommerceBaseTestCase {
  /**
   * Orders generated.
   */
  protected $orders = array();

  /**
   * Display node type.
   */
  protected $display_type;

  /**
   * Order reference field name
   */
  protected $field_name;

  /**
   * Order reference field info.
   */
  protected $field;

  /**
   * Order reference field instance info.
   */
  protected $field_instance;

  /**
   * Implementation of getInfo().
   */
  public static function getInfo() {
    return array(
      'name' => 'Order reference',
      'description' => 'Test adding and configuring order reference fields.',
      'group' => 'Drupal Commerce',
    );
  }

  /**
   * Implementation of setUp().
   */
  function setUp() {
    $modules = parent::setUpHelper('all');
    parent::setUp($modules);

    // Create a site admin + store admin user and login.
    $this->site_admin = $this->createUserWithPermissionHelper(array('site admin', 'store admin'));
    $this->drupalLogin($this->site_admin);

    // Create a dummy order display content type without order reference
    // fields.
    $this->display_type = $this->createDummyOrderDisplayContentType('order_display', FALSE);

    // Create dummy order entities.
    $this->orders[1] = $this->createDummyOrder();
    $this->orders[2] = $this->createDummyOrder();

    // Access to the manage fields screen.
    $this->drupalGet('admin/structure/types/manage/' . strtr($this->display_type->type, '_', '-') . '/fields');

    // Add a new order reference field
    $edit = array(
      'fields[_add_new_field][label]' => 'Order',
      'fields[_add_new_field][field_name]' => 'order',
      'fields[_add_new_field][type]' => 'commerce_order_reference',
      'fields[_add_new_field][widget_type]' => 'options_select',
    );
    $this->drupalPost(NULL, $edit, t('Save'));

    // Save the field settings, which are empty.
    $this->drupalPost(NULL, array(), t('Save field settings'));

    // Save the default field instance settings.
    $this->drupalPost(NULL, array(), t('Save settings'));

    // Clear field's cache.
    field_cache_clear();

    // Get the field information just saved.
    $this->field_name = 'field_order';
    $this->field = field_info_field($this->field_name);
    $this->field_instance = field_info_instance('node', $this->field_name, $this->display_type->type);
  }

/**
   * Create a dummy order display content type.
   *
   * @param $type
   *  Machine name of the content type to create. Also used for human readable
   *  name to keep things simple.
   * @param $attach_order_reference_field
   *  If TRUE, automatically add a order reference field to the new content
   *  type.
   * @param $field_name
   *  Only used if $attach_order_reference_field is TRUE. Sets the name for
   *  the field instance to attach. Creates the field if it doesn't exist.
   * @param $cardinality_reference_field
   *  Only used if $attach_order_reference_field is TRUE. Sets the
   *  cardinality for the field instance to attach.
   * @return
   *  An object for the content type.
   * @see attachOrderReferenceField()
   */
  public function createDummyOrderDisplayContentType($type = 'order_display', $attach_order_reference_field = TRUE, $field_name = 'field_order', $cardinality_reference_field = 1) {
    // If the specified node type already exists, return it now.
    if ($content_type = node_type_load($type)) {
      return $content_type;
    }

    $content_type = array(
      'type' => $type,
      'name' => $type, // Don't use a human readable name here to keep it simple.
      'base' => 'node_content',
      'description' => 'Use <em>order displays</em> to display order for sale to your customers.',
      'custom' => 1,
      'modified' => 1,
      'locked' => 0,
    );
    $content_type = node_type_set_defaults($content_type);
    node_type_save($content_type);
    node_add_body_field($content_type);
    $this->pass("Created content type: $type");


    if ($attach_order_reference_field) {
      // Maybe $instance should be returned as well
      $instance = $this->attachOrderReferenceField($type, $field_name, $cardinality_reference_field);
    }

    return $content_type;
  }

/**
   * Attach a order reference field to a given content type. Creates the field
   * if the given name doesn't already exist. Automatically sets the display
   * formatters to be the "add to cart form" for the teaser and full modes.
   *
   * @param $content_type
   *  Name of the content type that should have a field instance attached.
   * @param $field_name
   *  Only used if $attach_order_reference_field is TRUE. Sets the name for
   *  the field instance to attach. Creates the field if it doesn't exist.
   * @return
   *  An object containing the field instance that was created.
   * @see createDummyOrderDisplayContentType()
   */
  public function attachOrderReferenceField($content_type = 'order_display', $field_name = 'field_order', $cardinality = 1) {
    if (module_exists('commerce_order')) {
      // Check if the field has already been created.
      $field_info = field_info_field($field_name);
      if (empty($field_info)) {
        // Add an order reference field to the order display node type
        $field = array(
          'field_name' => $field_name,
          'type' => 'commerce_order_reference',
          'cardinality' => $cardinality,
          'translatable' => FALSE,
        );
        field_create_field($field);
        $this->pass("New field created: $field_name");
      } else {
        debug("NOTE: attachOrderReferenceField attempting to create field <code>$field_name</code> that already exists. This is fine and this message is just for your information.");
      }

      // Check that this instance doesn't already exist
      $instance = field_info_instance('node', $field_name, $content_type);
      if (empty($insance)) {
        // Add an instance of the field to the given content type
        $instance = array(
          'field_name' => $field_name,
          'entity_type' => 'node',
          'label' => 'Order',
          'bundle' => $content_type,
          'description' => 'Choose a order to display for sale.',
          'required' => TRUE,

          'widget' => array(
            'type' => 'options_select',
          ),

          'display' => array(
            'default' => array(
              'label' => 'hidden',
              'type' => 'commerce_cart_add_to_cart_form',
            ),
            'teaser' => array(
              'label' => 'hidden',
              'type' => 'commerce_cart_add_to_cart_form',
            ),
          ),
        );
        field_create_instance($instance);
        $this->pass("Create field instance of field <code>$field_name</code> on content type <code>$content_type</code>");
      } else {
        $this->fail("Test Developer: You attempted to create a field that already exists. Field: $field_name -- Content Type: $content_type");
      }
      return $instance;
    } else {
      $this->fail('Cannot create order reference field because Order module is not enabled.');
    }
  }

  /**
   * Test if the field is correctly created and attached to the order
   * display.
   */
  public function testCommerceOrderReferenceCreateField() {
    // Check at database level.
    $this->assertTrue(in_array($this->display_type->type, $this->field['bundles']['node']), t('Field is present in the order display bundle'));
    $this->assertTrue($this->field_instance['field_name'] == $this->field_name, t('Field instance is present in the order display bundle'));

    // Check in the admin page for the content display.
    $this->drupalGet('admin/structure/types/manage/' . strtr($this->display_type->type, '_', '-') . '/fields');
    $this->assertText('Order', t('Reference order field label found'));
    $this->assertText($this->field_name, t('Reference order field name found'));

    // The order selector should appear in the order display creation page.
    $this->drupalGet('node/add/' . strtr($this->display_type->type, '_', '-'));
    $this->assertFieldById('edit-field-order-und', NULL, t('Field selector is present in order display creation'));
  }

  /**
   * Test editing the field.
   */
  public function testCommerceOrderReferenceEditField() {
    // Navigate to the edit field page.
    $this->drupalGet('admin/structure/types/manage/' . strtr($this->display_type->type, '_', '-') . '/fields/field_order');

    // Alter the field to be required and unlimited.
    $edit = array(
      'instance[required]' => 1,
      'field[cardinality]' => -1,
    );
    $this->drupalPost(NULL, $edit, t('Save settings'));

    // Check the message of configuration saved.
    $this->assertRaw(t('Saved %label configuration.', array('%label' => $this->field_instance['label'])), t('Message of saved field displayed'));

    // Navigate again to the edit field page to check if the values have been
    // saved.
    $this->drupalGet('admin/structure/types/manage/' . strtr($this->display_type->type, '_', '-') . '/fields/' . $this->field_name);
    $this->assertFieldChecked('edit-instance-required', t('Required field is checked'));
    $this->assertOptionSelected('edit-field-cardinality', -1, t('Field can have unlimited values'));
    $this->assertFieldByXPath("//select[@id='edit-field-order-und' and @multiple='multiple']", NULL, t('Multiple order selector for default values'));

    // Clear field's cache.
    field_cache_clear();

    // Also the order creation form should have the field required and with
    // a multiple select widget.
    $this->drupalGet('node/add');
    $this->drupalGet('node/add/' . strtr($this->display_type->type, '_', '-'));

    $this->assertFieldByXPath("//select[@id='edit-field-order-und' and @multiple='multiple']", NULL, t('Multiple order selector for default values'));
  }

  /**
   * Test if the field is correctly attached to a user.
   */
  public function testCommerceOrderReferenceAttachToUser() {

    // Access user manage fields page.
    $this->drupalGet('admin/config/people/accounts/fields');

    // Add a new order reference field
    $edit = array(
      'fields[_add_new_field][label]' => 'Order',
      'fields[_add_new_field][field_name]' => 'user_order',
      'fields[_add_new_field][type]' => 'commerce_order_reference',
      'fields[_add_new_field][widget_type]' => 'options_select',
    );
    $this->drupalPost(NULL, $edit, t('Save'));

    // Save the field settings, which are empty.
    $this->drupalPost(NULL, array(), t('Save field settings'));

    // Save the field instance settings.
    $this->drupalPost(NULL, array('instance[settings][user_register_form]' => 1), t('Save settings'));

    // Clear field's cache.
    field_cache_clear();

    // Check the field at database level.
    $field = field_info_field('field_user_order');
    $field_instance = field_info_instance('user', 'field_user_order','user');
    $this->assertTrue(in_array('user', $field['bundles']['user']), t('Field is present in the user'));
    $this->assertTrue($field_instance['field_name'] == 'field_user_order', t('Field instance is present in the user bundle'));

    // Check in the admin page for the user display.
    $this->drupalGet('admin/config/people/accounts/fields');
    $this->assertText('Order', t('Reference order field label found'));
    $this->assertText('field_user_order', t('Reference order field name found'));

    // The order selector should appear in the order display creation page.
    $this->drupalGet('admin/people/create');
    $this->assertFieldById('edit-field-user-order-und', NULL, t('Field selector is present in user creation'));
  }

  /**
   * Test adding some referenced orders.
   */
  public function testCommerceOrderReferenceReferenceOrders() {
    // Add a new order
    $new_order = $this->createDummyOrder();

    // Check at database level
    $field_orders = commerce_order_reference_match_orders($this->field_name, NULL, array($new_order->order_id));
    $this->assertFalse(empty($field_orders), t('Order is in the available orders of the field'));

    $order_title = t('Order: @id', array('@id' => $new_order->order_id));
    // Check if it is in the reference field for order displays.
    $this->drupalGet('admin/structure/types/manage/' . strtr($this->display_type->type, '_', '-') . '/fields/' . $this->field_name);
    $select_options = $this->xpath("//select['edit-field-order-und']//option");
    $this->assertTrue(in_array($order_title, $select_options), t('Order is available in the select'));

    // Check if it is in the order display creation select form.
    $this->drupalGet('node/add/' . strtr($this->display_type->type, '_', '-'));
    $select_options = $this->xpath("//select['edit-field-order-und']//option");
    $this->assertTrue(in_array($order_title, $select_options), t('Order is available in the select'));
  }

  /**
   * Test the limit of referenceable order types.
   * @todo
   * /
  public function testCommerceOrderReferenceTestReferenceableTypes() {
  }
  */

  /**
   * Test the display of fields pulled from the order.
   */
  public function testCommerceOrderReferenceDisplayFields() {
    // Go to manage fields screen of the order display.
    $this->drupalGet('admin/structure/types/manage/' . strtr($this->display_type->type, '_', '-') . '/display');
    // Load all the fields that are pulled with the order and check if they
    // are in the display screen.
    $extra_fields = commerce_order_reference_field_extra_fields();
    foreach ($extra_fields['node'][$this->display_type->type]['display'] as $display) {
      $this->assertText($display['label'], t('Field %field_label is present in the manage display screen', array('%field_label' => $display['label'])));
    }
  }

  /**
   * Creates a order display node with an associated order.
   *
   * @param $order_ids
   *  Array of order IDs to use for the order reference field.
   * @param $title
   *  Optional title for the order node. Will default to a random name.
   * @param $order_display_content_type
   *  Machine name for the order display content type to use for creating the
   *  node. Defaults to 'order_display'.
   * @param $order_ref_field_name
   *  Machine name for the order reference field on this order display
   *  content type. Defaults to 'field_order'.
   * @return
   *  The newly saved $node object.
   */
  public function createDummyOrderNode($order_ids, $title = '', $order_display_content_type = 'order_display', $order_ref_field_name = 'field_order') {
    if (module_exists('commerce_order')) {
      if (empty($title)) {
        $title = $this->randomString(10);
      }
      $node = (object) array('type' => $order_display_content_type);
      node_object_prepare($node);
      $node->uid = 1;
      $node->title = $title;
      foreach ($order_ids as $order_id) {
        $node->{$order_ref_field_name}[LANGUAGE_NONE][]['order_id'] = $order_id;
      }
      node_save($node);
      return $node;
    } else {
      $this->fail(t('Cannot use use createOrderNode because the order module is not enabled.'));
    }
  }

  /**
   * Test the link formatter.
   */
  public function testCommerceOrderReferenceLinkFormatter() {
    // Go to manage fields screen of the order display.
    $this->drupalGet('admin/structure/types/manage/' . strtr($this->display_type->type, '_', '-') . '/display');

    // Change the default value for SKU with link.
    $this->drupalPost(NULL, array('fields[field_order][type]' => 'commerce_order_reference_link'), t('Save'));

    // Check if the value has been saved.
    $this->assertText(t('Your settings have been saved.'), t('Settings saved message displayed'));
    $this->assertOptionSelected('edit-fields-field-order-type', 'commerce_order_reference_link', t('Correct value is selected.'));

    // Clear field's cache and reload field instance information just saved.
    field_cache_clear();
    $field_instance = field_info_instance('node', $this->field_name, $this->display_type->type);

    // Check if the value has been stored in db.
    $this->assertTrue($field_instance['display']['default']['type'] == 'commerce_order_reference_link', t('Option correctly stored in db'));

    // Create a order display using one order already generated.
    $order = reset($this->orders);
    $order_node = $this->createDummyOrderNode(array($order->order_id), 'Test order');

   // Access to the order display node.
    $this->drupalGet('node/' . $order_node->nid);

    // Generate and check the expected ouptut.
    if (commerce_order_access('view', $order)) {
      $render = array(
        '#type' => 'link',
        '#title' => t('Order @id', array('@id' => $order->order_id)),
        '#href' => 'admin/commerce/orders/' . $order->order_id,
      );
    }
    else {
      $render = array(
        '#markup' => t('Order @id', array('@id' => $item['order_id'])),
      );
    }
    $this->assertRaw(drupal_render($render), t('Order link is displayed correctly'));
  }
}
