1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27: 28: 29: 30: 31: 32: 33:
34: class Mage_Catalog_Model_Api2_Product_Validator_Product extends Mage_Api2_Model_Resource_Validator
35: {
36: 37: 38:
39: const MAX_DECIMAL_VALUE = 99999999.9999;
40:
41: 42: 43: 44: 45:
46: protected $_product = null;
47:
48: 49: 50: 51: 52:
53: protected $_operation = null;
54:
55: public function __construct($options)
56: {
57: if (isset($options['product'])) {
58: if ($options['product'] instanceof Mage_Catalog_Model_Product) {
59: $this->_product = $options['product'];
60: } else {
61: throw new Exception("Passed parameter 'product' is wrong.");
62: }
63: }
64:
65: if (!isset($options['operation']) || empty($options['operation'])) {
66: throw new Exception("Passed parameter 'operation' is empty.");
67: }
68: $this->_operation = $options['operation'];
69: }
70:
71: 72: 73: 74: 75:
76: protected function _getProduct()
77: {
78: return $this->_product;
79: }
80:
81: 82: 83: 84: 85:
86: protected function _isUpdate()
87: {
88: return $this->_operation == Mage_Api2_Model_Resource::OPERATION_UPDATE;
89: }
90:
91: 92: 93: 94: 95: 96:
97: public function isValidData(array $data)
98: {
99: if ($this->_isUpdate()) {
100: $product = $this->_getProduct();
101: if (!is_null($product) && $product->getId()) {
102: $data['attribute_set_id'] = $product->getAttributeSetId();
103: $data['type_id'] = $product->getTypeId();
104: }
105: }
106:
107: try {
108: $this->_validateProductType($data);
109:
110: $productEntity = Mage::getModel('eav/entity_type')->loadByCode(Mage_Catalog_Model_Product::ENTITY);
111: $this->_validateAttributeSet($data, $productEntity);
112: $this->_validateSku($data);
113: $this->_validateGiftOptions($data);
114: $this->_validateGroupPrice($data);
115: $this->_validateTierPrice($data);
116: $this->_validateStockData($data);
117: $this->_validateAttributes($data, $productEntity);
118: $isSatisfied = count($this->getErrors()) == 0;
119: } catch (Mage_Api2_Exception $e) {
120: $this->_addError($e->getMessage());
121: $isSatisfied = false;
122: }
123:
124:
125: return $isSatisfied;
126: }
127:
128: 129: 130: 131: 132: 133: 134:
135: protected function _validateAttributes($data, $productEntity)
136: {
137: if (!isset($data['attribute_set_id']) || empty($data['attribute_set_id'])) {
138: $this->_critical('Missing "attribute_set_id" in request.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
139: }
140: if (!isset($data['type_id']) || empty($data['type_id'])) {
141: $this->_critical('Missing "type_id" in request.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
142: }
143:
144: if (isset($data['weight']) && !empty($data['weight']) && $data['weight'] > 0
145: && !Zend_Validate::is($data['weight'], 'Between', array(0, self::MAX_DECIMAL_VALUE))) {
146: $this->_addError('The "weight" value is not within the specified range.');
147: }
148:
149:
150: if (isset($data['msrp_display_actual_price_type'])) {
151: $data['msrp_display_actual_price_type'] = (string) $data['msrp_display_actual_price_type'];
152: }
153: $requiredAttributes = array('attribute_set_id');
154: $positiveNumberAttributes = array('weight', 'price', 'special_price', 'msrp');
155:
156: foreach ($productEntity->getAttributeCollection($data['attribute_set_id']) as $attribute) {
157: $attributeCode = $attribute->getAttributeCode();
158: $value = false;
159: $isSet = false;
160: if (isset($data[$attribute->getAttributeCode()])) {
161: $value = $data[$attribute->getAttributeCode()];
162: $isSet = true;
163: }
164: $applicable = false;
165: if (!$attribute->getApplyTo() || in_array($data['type_id'], $attribute->getApplyTo())) {
166: $applicable = true;
167: }
168:
169: if (!$applicable && !$attribute->isStatic() && $isSet) {
170: $productTypes = Mage_Catalog_Model_Product_Type::getTypes();
171: $this->_addError(sprintf('Attribute "%s" is not applicable for product type "%s"', $attributeCode,
172: $productTypes[$data['type_id']]['label']));
173: }
174:
175: if ($applicable && $isSet) {
176:
177: if ($attribute->usesSource()
178:
179: && !(empty($value) && $attribute->getIsRequired())) {
180: $allowedValues = $this->_getAttributeAllowedValues($attribute->getSource()->getAllOptions());
181: if (!is_array($value)) {
182:
183: $value = array($value);
184: }
185: foreach ($value as $selectValue) {
186: $useStrictMode = !is_numeric($selectValue);
187: if (!in_array($selectValue, $allowedValues, $useStrictMode)
188: && !$this->_isConfigValueUsed($data, $attributeCode)) {
189: $this->_addError(sprintf('Invalid value "%s" for attribute "%s".',
190: $selectValue, $attributeCode));
191: }
192: }
193: }
194:
195: if ($attribute->getBackendType() == 'datetime') {
196: try {
197: $attribute->getBackend()->formatDate($value);
198: } catch (Zend_Date_Exception $e) {
199: $this->_addError(sprintf('Invalid date in the "%s" field.', $attributeCode));
200: }
201: }
202:
203: if (in_array($attributeCode, $positiveNumberAttributes) && (!empty($value) && $value !== 0)
204: && (!is_numeric($value) || $value < 0)
205: ) {
206: $this->_addError(sprintf('Please enter a number 0 or greater in the "%s" field.', $attributeCode));
207: }
208: }
209:
210: if ($applicable && $attribute->getIsRequired() && $attribute->getIsVisible()) {
211: if (!in_array($attributeCode, $positiveNumberAttributes) || $value !== 0) {
212: $requiredAttributes[] = $attribute->getAttributeCode();
213: }
214: }
215: }
216:
217: foreach ($requiredAttributes as $key) {
218: if (!array_key_exists($key, $data)) {
219: if (!$this->_isUpdate()) {
220: $this->_addError(sprintf('Missing "%s" in request.', $key));
221: continue;
222: }
223: } else if (!is_numeric($data[$key]) && empty($data[$key])) {
224: $this->_addError(sprintf('Empty value for "%s" in request.', $key));
225: }
226: }
227: }
228:
229: 230: 231: 232: 233: 234:
235: protected function _validateProductType($data)
236: {
237: if ($this->_isUpdate()) {
238: return true;
239: }
240: if (!isset($data['type_id']) || empty($data['type_id'])) {
241: $this->_critical('Missing "type_id" in request.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
242: }
243: if (!array_key_exists($data['type_id'], Mage_Catalog_Model_Product_Type::getTypes())) {
244: $this->_critical('Invalid product type.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
245: }
246: }
247:
248: 249: 250: 251: 252: 253: 254:
255: protected function _validateAttributeSet($data, $productEntity)
256: {
257: if ($this->_isUpdate()) {
258: return true;
259: }
260: if (!isset($data['attribute_set_id']) || empty($data['attribute_set_id'])) {
261: $this->_critical('Missing "attribute_set_id" in request.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
262: }
263:
264: $attributeSet = Mage::getModel('eav/entity_attribute_set')->load($data['attribute_set_id']);
265: if (!$attributeSet->getId() || $productEntity->getEntityTypeId() != $attributeSet->getEntityTypeId()) {
266: $this->_critical('Invalid attribute set.', Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
267: }
268: }
269:
270: 271: 272: 273: 274: 275:
276: protected function _validateSku($data)
277: {
278: if ($this->_isUpdate() && !isset($data['sku'])) {
279: return true;
280: }
281: if (!Zend_Validate::is((string)$data['sku'], 'StringLength', array('min' => 0, 'max' => 64))) {
282: $this->_addError('SKU length should be 64 characters maximum.');
283: }
284: }
285:
286: 287: 288: 289: 290:
291: protected function _validateGiftOptions($data)
292: {
293: if (isset($data['gift_wrapping_price'])) {
294: if (!(is_numeric($data['gift_wrapping_price']) && $data['gift_wrapping_price'] >= 0)) {
295: $this->_addError('Please enter a number 0 or greater in the "gift_wrapping_price" field.');
296: }
297: }
298: }
299:
300: 301: 302: 303: 304:
305: protected function _validateGroupPrice($data)
306: {
307: if (isset($data['group_price']) && is_array($data['group_price'])) {
308: $groupPrices = $data['group_price'];
309: foreach ($groupPrices as $index => $groupPrice) {
310: $fieldSet = 'group_price:' . $index;
311: $this->_validateWebsiteIdForGroupPrice($groupPrice, $fieldSet);
312: $this->_validateCustomerGroup($groupPrice, $fieldSet);
313: $this->_validatePositiveNumber($groupPrice, $fieldSet, 'price', true, true);
314: }
315: }
316: }
317:
318: 319: 320: 321: 322:
323: protected function _validateTierPrice($data)
324: {
325: if (isset($data['tier_price']) && is_array($data['tier_price'])) {
326: $tierPrices = $data['tier_price'];
327: foreach ($tierPrices as $index => $tierPrice) {
328: $fieldSet = 'tier_price:' . $index;
329: $this->_validateWebsiteIdForGroupPrice($tierPrice, $fieldSet);
330: $this->_validateCustomerGroup($tierPrice, $fieldSet);
331: $this->_validatePositiveNumber($tierPrice, $fieldSet, 'price_qty');
332: $this->_validatePositiveNumber($tierPrice, $fieldSet, 'price');
333: }
334: }
335: }
336:
337: 338: 339: 340: 341: 342:
343: protected function _validateWebsiteIdForGroupPrice($data, $fieldSet)
344: {
345: if (!isset($data['website_id'])) {
346: $this->_addError(sprintf('The "website_id" value in the "%s" set is a required field.', $fieldSet));
347: } else {
348:
349: $catalogHelper = Mage::helper('catalog');
350: $website = Mage::getModel('core/website')->load($data['website_id']);
351: $isAllWebsitesValue = is_numeric($data['website_id']) && ($data['website_id'] == 0);
352: $isGlobalPriceScope = (int)$catalogHelper->getPriceScope() == Mage_Catalog_Helper_Data::PRICE_SCOPE_GLOBAL;
353: if (is_null($website->getId()) || ($isGlobalPriceScope && !$isAllWebsitesValue)) {
354: $this->_addError(sprintf('Invalid "website_id" value in the "%s" set.', $fieldSet));
355: }
356: }
357: }
358:
359: 360: 361: 362: 363:
364: protected function _validateStockData($data)
365: {
366: if (isset($data['stock_data']) && is_array($data['stock_data'])) {
367: $stockData = $data['stock_data'];
368: $fieldSet = 'stock_data';
369: if (!(isset($stockData['use_config_manage_stock']) && $stockData['use_config_manage_stock'])) {
370: $this->_validateBoolean($stockData, $fieldSet, 'manage_stock');
371: }
372: if ($this->_isManageStockEnabled($stockData)) {
373: $this->_validateNumeric($stockData, $fieldSet, 'qty');
374: $this->_validatePositiveNumber($stockData, $fieldSet, 'min_qty', false, true, true);
375: $this->_validateNumeric($stockData, $fieldSet, 'notify_stock_qty', false, true);
376: $this->_validateBoolean($stockData, $fieldSet, 'is_qty_decimal');
377: if (isset($stockData['is_qty_decimal']) && (bool) $stockData['is_qty_decimal'] == true) {
378: $this->_validateBoolean($stockData, $fieldSet, 'is_decimal_divided');
379: }
380: $this->_validateBoolean($stockData, $fieldSet, 'enable_qty_increments', true);
381: if (isset($stockData['enable_qty_increments']) && (bool) $stockData['enable_qty_increments'] == true) {
382: $this->_validatePositiveNumeric($stockData, $fieldSet, 'qty_increments', false, true);
383: }
384: if (Mage::helper('catalog')->isModuleEnabled('Mage_CatalogInventory')) {
385: $this->_validateSource($stockData, $fieldSet, 'backorders',
386: 'cataloginventory/source_backorders', true);
387: $this->_validateSource($stockData, $fieldSet, 'is_in_stock', 'cataloginventory/source_stock');
388: }
389: }
390:
391: $this->_validatePositiveNumeric($stockData, $fieldSet, 'min_sale_qty', false, true);
392: $this->_validatePositiveNumeric($stockData, $fieldSet, 'max_sale_qty', false, true);
393: }
394: }
395:
396: 397: 398: 399: 400: 401:
402: protected function _isManageStockEnabled($stockData)
403: {
404: if (!(isset($stockData['use_config_manage_stock']) && $stockData['use_config_manage_stock'])) {
405: $manageStock = isset($stockData['manage_stock']) && $stockData['manage_stock'];
406: } else {
407: $manageStock = Mage::getStoreConfig(
408: Mage_CatalogInventory_Model_Stock_Item::XML_PATH_ITEM . 'manage_stock');
409: }
410: return (bool) $manageStock;
411: }
412:
413: 414: 415: 416: 417: 418:
419: protected function _validateCustomerGroup($data, $fieldSet)
420: {
421: if (!isset($data['cust_group'])) {
422: $this->_addError(sprintf('The "cust_group" value in the "%s" set is a required field.', $fieldSet));
423: } else {
424: if (!is_numeric($data['cust_group'])) {
425: $this->_addError(sprintf('Invalid "cust_group" value in the "%s" set', $fieldSet));
426: } else {
427: $customerGroup = Mage::getModel('customer/group')->load($data['cust_group']);
428: if (is_null($customerGroup->getId())) {
429: $this->_addError(sprintf('Invalid "cust_group" value in the "%s" set', $fieldSet));
430: }
431: }
432: }
433: }
434:
435: 436: 437: 438: 439: 440: 441: 442: 443: 444:
445: protected function _validatePositiveNumber($data, $fieldSet, $field, $required = true, $equalsZero = false,
446: $skipIfConfigValueUsed = false)
447: {
448:
449: if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) {
450: if (!isset($data[$field]) && $required) {
451: $this->_addError(sprintf('The "%s" value in the "%s" set is a required field.', $field, $fieldSet));
452: }
453:
454: if (isset($data[$field])) {
455: $isValid = $equalsZero ? $data[$field] >= 0 : $data[$field] > 0;
456: if (!(is_numeric($data[$field]) && $isValid)) {
457: $message = $equalsZero
458: ? 'Please enter a number 0 or greater in the "%s" field in the "%s" set.'
459: : 'Please enter a number greater than 0 in the "%s" field in the "%s" set.';
460: $this->_addError(sprintf($message, $field, $fieldSet));
461: }
462: }
463: }
464: }
465:
466: 467: 468: 469: 470: 471: 472: 473: 474:
475: protected function _validatePositiveNumeric($data, $fieldSet, $field, $required = false,
476: $skipIfConfigValueUsed = false)
477: {
478:
479: if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) {
480: if (!isset($data[$field]) && $required) {
481: $this->_addError(sprintf('The "%s" value in the "%s" set is a required field.',$field, $fieldSet));
482: }
483:
484: if (isset($data[$field]) && (!is_numeric($data[$field]) || $data[$field] < 0)) {
485: $this->_addError(sprintf('Please use numbers only in the "%s" field in the "%s" set. ' .
486: 'Please avoid spaces or other non numeric characters.', $field, $fieldSet));
487: }
488: }
489: }
490:
491: 492: 493: 494: 495: 496: 497: 498: 499:
500: protected function _validateNumeric($data, $fieldSet, $field, $required = false, $skipIfConfigValueUsed = false)
501: {
502:
503: if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) {
504: if (!isset($data[$field]) && $required) {
505: $this->_addError(sprintf('The "%s" value in the "%s" set is a required field.',$field, $fieldSet));
506: }
507:
508: if (isset($data[$field]) && !is_numeric($data[$field])) {
509: $this->_addError(sprintf('Please enter a valid number in the "%s" field in the "%s" set.',
510: $field, $fieldSet));
511: }
512: }
513: }
514:
515: 516: 517: 518: 519: 520: 521: 522: 523:
524: protected function _validateSource($data, $fieldSet, $field, $sourceModelName, $skipIfConfigValueUsed = false)
525: {
526:
527: if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) {
528: if (isset($data[$field])) {
529: $sourceModel = Mage::getSingleton($sourceModelName);
530: if ($sourceModel) {
531: $allowedValues = $this->_getAttributeAllowedValues($sourceModel->toOptionArray());
532: $useStrictMode = !is_numeric($data[$field]);
533: if (!in_array($data[$field], $allowedValues, $useStrictMode)) {
534: $this->_addError(sprintf('Invalid "%s" value in the "%s" set.', $field, $fieldSet));
535: }
536: }
537: }
538: }
539: }
540:
541: 542: 543: 544: 545: 546: 547: 548:
549: protected function _validateBoolean($data, $fieldSet, $field, $skipIfConfigValueUsed = false)
550: {
551:
552: if (!($skipIfConfigValueUsed && $this->_isConfigValueUsed($data, $field))) {
553: if (isset($data[$field])) {
554: $allowedValues = $this->_getAttributeAllowedValues(
555: Mage::getSingleton('eav/entity_attribute_source_boolean')->getAllOptions());
556: $useStrictMode = !is_numeric($data[$field]);
557: if (!in_array($data[$field], $allowedValues, $useStrictMode)) {
558: $this->_addError(sprintf('Invalid "%s" value in the "%s" set.', $field, $fieldSet));
559: }
560: }
561: }
562: }
563:
564: 565: 566: 567: 568: 569:
570: protected function _getAttributeAllowedValues(array $options)
571: {
572: $values = array();
573: foreach ($options as $option) {
574: if (isset($option['value'])) {
575: $value = $option['value'];
576: if (is_array($value)) {
577: $values = array_merge($values, $this->_getAttributeAllowedValues($value));
578: } else {
579: $values[] = $value;
580: }
581: }
582: }
583:
584: return $values;
585: }
586:
587: 588: 589: 590: 591: 592: 593:
594: protected function _isConfigValueUsed($data, $field)
595: {
596: return isset($data["use_config_$field"]) && $data["use_config_$field"];
597: }
598:
599: 600: 601: 602: 603: 604: 605:
606: protected function _critical($message, $code)
607: {
608: throw new Mage_Api2_Exception($message, $code);
609: }
610: }
611: