/**
  * Check that current product variation is valid
  *
  * @param Array $data Submitted data
  * @return Boolean Returns TRUE if the submitted data is valid, otherwise FALSE.
  */
 function php($data)
 {
     $valid = parent::php($data);
     $fields = $this->form->Fields();
     //Check that variation exists if necessary
     $form = $this->form;
     $request = $this->form->getRequest();
     //Get product variations from options sent
     //TODO refactor this
     $productVariations = new DataObjectSet();
     $options = $request->postVar('Options');
     $product = DataObject::get_by_id($data['ProductClass'], $data['ProductID']);
     $variations = $product ? $product->Variations() : new DataObjectSet();
     if ($variations && $variations->exists()) {
         foreach ($variations as $variation) {
             $variationOptions = $variation->Options()->map('AttributeID', 'ID');
             if ($options == $variationOptions && $variation->isEnabled()) {
                 $productVariations->push($variation);
             }
         }
     }
     if ((!$productVariations || !$productVariations->exists()) && $product && $product->requiresVariation()) {
         $this->form->sessionMessage(_t('Form.VARIATIONS_REQUIRED', 'This product requires options before it can be added to the cart.'), 'bad');
         //Have to set an error for Form::validate()
         $this->errors[] = true;
         $valid = false;
         return $valid;
     }
     //Validate that the product/variation being added is inStock()
     $stockLevel = 0;
     if ($product) {
         if ($product->requiresVariation()) {
             $stockLevel = $productVariations->First()->StockLevel()->Level;
         } else {
             $stockLevel = $product->StockLevel()->Level;
         }
     }
     if ($stockLevel == 0) {
         $this->form->sessionMessage(_t('Form.STOCK_LEVEL', ''), 'bad');
         //Have to set an error for Form::validate()
         $this->errors[] = true;
         $valid = false;
     }
     //Validate the quantity is not greater than the available stock
     $quantity = $request->postVar('Quantity');
     if ($stockLevel > 0 && $stockLevel < $quantity) {
         $this->form->sessionMessage(_t('Form.STOCK_LEVEL_MORE_THAN_QUANTITY', 'The quantity is greater than available stock for this product.'), 'bad');
         //Have to set an error for Form::validate()
         $this->errors[] = true;
         $valid = false;
     }
     return $valid;
 }
 /**
  * Test {@link DataObjectSet->exists()}
  */
 function testExists()
 {
     // Test an empty set
     $set = new DataObjectSet();
     $this->assertFalse($set->exists(), 'Empty set doesn\'t exist.');
     // Test a non-empty set
     $set = DataObject::get('DataObjectSetTest_TeamComment', '', "\"ID\" ASC");
     $this->assertTrue($set->exists(), 'Non-empty set does exist.');
 }
 /**
  * Get options for a product and return for use in the form
  * Must get options for nextAttributeID, but these options should be filtered so 
  * that only the options for the variations that match attributeID and optionID
  * are returned.
  * 
  * In other words, do not just return options for a product, return options for product
  * variations.
  * 
  * Usually called via AJAX.
  * 
  * @param SS_HTTPRequest $request
  * @return String JSON encoded string for use to update options in select fields on Product page
  */
 public function options(SS_HTTPRequest $request)
 {
     $data = array();
     $product = $this->data();
     $options = new DataObjectSet();
     $variations = $product->Variations();
     $filteredVariations = new DataObjectSet();
     $attributeOptions = $request->postVar('Options');
     $nextAttributeID = $request->postVar('NextAttributeID');
     //Filter variations to match attribute ID and option ID
     //Variations need to have the same option for each attribute ID in POST data to be considered
     if ($variations && $variations->exists()) {
         foreach ($variations as $variation) {
             $variationOptions = array();
             //if ($attributeOptions && is_array($attributeOptions)) {
             foreach ($attributeOptions as $attributeID => $optionID) {
                 //Get option for attribute ID, if this variation has options for every attribute in the array then add it to filtered
                 $attributeOption = $variation->getOptionForAttribute($attributeID);
                 if ($attributeOption && $attributeOption->ID == $optionID) {
                     $variationOptions[$attributeID] = $optionID;
                 }
             }
             //}
             if ($variationOptions == $attributeOptions && $variation->isEnabled()) {
                 $filteredVariations->push($variation);
             }
         }
     }
     //Find options in filtered variations that match next attribute ID
     //All variations must have options for all attributes so this is belt and braces really
     if ($filteredVariations && $filteredVariations->exists()) {
         foreach ($filteredVariations as $variation) {
             $attributeOption = $variation->getOptionForAttribute($nextAttributeID);
             if ($attributeOption) {
                 $options->push($attributeOption);
             }
         }
     }
     if ($options && $options->exists()) {
         $map = $options->map();
         //This resets the array counter to 0 which ruins the attribute IDs
         //array_unshift($map, 'Please Select');
         $data['options'] = $map;
         $data['count'] = count($map);
         $data['nextAttributeID'] = $nextAttributeID;
     }
     return json_encode($data);
 }
 /**
  * Add an item to the order representing the product, 
  * if an item for this product exists increase the quantity. Update the Order total afterward.
  * 
  * @param DataObject $product The product to be represented by this order item
  * @param DataObjectSet $productOptions The product variations to be added, usually just one
  */
 function addItem(DataObject $product, $quantity = 1, DataObjectSet $productOptions = null)
 {
     //Check that product options exist if product requires them
     //TODO perform this validation in Item->validate(), cannot at this stage because Item is written before ItemOption, no transactions, chicken/egg problem
     if ((!$productOptions || !$productOptions->exists()) && $product->requiresVariation()) {
         user_error("Cannot add item to cart, product options are required.", E_USER_WARNING);
         //Debug::friendlyError();
         return;
     }
     //Increment the quantity if this item exists already
     $item = $this->findIdenticalItem($product, $productOptions);
     if ($item && $item->exists()) {
         $item->Quantity = $item->Quantity + $quantity;
         $item->write();
     } else {
         //TODO this needs transactions for Item->validate() to check that ItemOptions exist for Item before it is written
         $item = new Item();
         $item->ObjectID = $product->ID;
         $item->ObjectClass = $product->class;
         $item->ObjectVersion = $product->Version;
         $item->Amount->setAmount($product->Amount->getAmount());
         $item->Amount->setCurrency($product->Amount->getCurrency());
         $item->Quantity = $quantity;
         $item->OrderID = $this->ID;
         $item->write();
         if ($productOptions && $productOptions->exists()) {
             foreach ($productOptions as $productOption) {
                 $itemOption = new ItemOption();
                 $itemOption->ObjectID = $productOption->ID;
                 $itemOption->ObjectClass = $productOption->class;
                 $itemOption->ObjectVersion = $productOption->Version;
                 $itemOption->Amount->setAmount($productOption->Amount->getAmount());
                 $itemOption->Amount->setCurrency($productOption->Amount->getCurrency());
                 $itemOption->ItemID = $item->ID;
                 $itemOption->write();
             }
         }
     }
     $this->updateTotal();
 }