- Fixed #16160: Call to undefined function ezi18n()
[tinyz:tinyz.git] / kernel / shop / ezshopoperationcollection.php
1 <?php
2 //
3 // Definition of eZShopOperationCollection class
4 //
5 // Created on: <01-Nov-2002 13:51:17 amos>
6 //
7 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
8 // SOFTWARE NAME: eZ Publish
9 // SOFTWARE RELEASE: 4.1.x
10 // COPYRIGHT NOTICE: Copyright (C) 1999-2010 eZ Systems AS
11 // SOFTWARE LICENSE: GNU General Public License v2.0
12 // NOTICE: >
13 //   This program is free software; you can redistribute it and/or
14 //   modify it under the terms of version 2.0  of the GNU General
15 //   Public License as published by the Free Software Foundation.
16 //
17 //   This program is distributed in the hope that it will be useful,
18 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 //   GNU General Public License for more details.
21 //
22 //   You should have received a copy of version 2.0 of the GNU General
23 //   Public License along with this program; if not, write to the Free
24 //   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 //   MA 02110-1301, USA.
26 //
27 //
28 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
29 //
30
31 /*! \file
32 */
33
34 /*!
35   \class eZShopOperationCollection ezcontentoperationcollection.php
36   \brief The class eZShopOperationCollection does
37
38 */
39 class eZShopOperationCollection
40 {
41     /*!
42      Constructor
43     */
44     function eZShopOperationCollection()
45     {
46     }
47
48     function fetchOrder( $orderID )
49     {
50         return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
51     }
52
53     /*!
54      Operation entry: Extracts user country from order account info and recalculates VAT with country given.
55      */
56     function handleUserCountry( $orderID )
57     {
58         // If user country is not required to calculate VAT then do nothing.
59         if ( !eZVATManager::isDynamicVatChargingEnabled() || !eZVATManager::isUserCountryRequired() )
60             return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
61
62         $user = eZUser::currentUser();
63
64         // Get order's account information and extract user country from it.
65         $order = eZOrder::fetch( $orderID );
66
67         if ( !$order )
68         {
69             eZDebug::writeError( "No such order: $orderID" );
70             return array( 'status' => eZModuleOperationInfo::STATUS_CANCELLED );
71         }
72
73         if ( $user->attribute( 'is_logged_in' ) )
74             $userCountry = eZVATManager::getUserCountry( $user, false );
75
76         $acctInfo = $order->attribute( 'account_information' );
77         if ( isset( $acctInfo['country'] ) )
78         {
79             $country = $acctInfo['country'];
80
81             // If user is registered and logged in
82             // and country is not yet specified for the user
83             // then save entered country to the user information.
84             if ( !isset( $userCountry ) || !$userCountry )
85                 eZVATManager::setUserCountry( $user, $country );
86         }
87         elseif ( isset( $userCountry ) && $userCountry )
88         {
89             // If country is not set in shop account handler, we get it from logged user's information.
90             $country = $userCountry;
91         }
92         else
93         {
94             $header = ezpI18n::translate( 'kernel/shop', 'Error checking out' );
95             $msg = ezpI18n::translate( 'kernel/shop',
96                            'Unable to calculate VAT percentage because your country is unknown. ' .
97                            'You can either fill country manually in your account information (if you are a registered user) ' .
98                            'or contact site administrator.' );
99
100             require_once( "kernel/common/template.php" );
101             $tpl = templateInit();
102             $tpl->setVariable( "error_header",  $header );
103             $tpl->setVariable( "error_list", array( $msg ) );
104
105             $operationResult = array(
106                 'status' => eZModuleOperationInfo::STATUS_CANCELLED,
107                 'result' => array( 'content' => $tpl->fetch( "design:shop/cancelconfirmorder.tpl" ) )
108                 );
109             return $operationResult;
110         }
111
112         // Recalculate VAT for order's product collection items
113         // according to the specified user country.
114
115         $productCollection = $order->attribute( 'productcollection' );
116         if ( !$productCollection )
117         {
118             eZDebug::writeError( "Cannot find product collection for order " . $order->attribute( 'id' ),
119                                  "eZShopOperationCollection::handleUserCountry" );
120             return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
121         }
122
123         $items = eZProductCollectionItem::fetchList( array( 'productcollection_id' => $productCollection->attribute( 'id' ) ) );
124         $vatIsKnown = true;
125         $db = eZDB::instance();
126         $db->begin();
127         foreach( $items as $item )
128         {
129             $productContentObject = $item->attribute( 'contentobject' );
130
131             // Look up price object.
132             $priceObj = null;
133             $attributes =  $productContentObject->contentObjectAttributes();
134             foreach ( $attributes as $attribute )
135             {
136                 $dataType = $attribute->dataType();
137                 if ( eZShopFunctions::isProductDatatype( $dataType->isA() ) )
138                 {
139                     $priceObj = $attribute->content();
140                     break;
141                 }
142             }
143             if ( !is_object( $priceObj ) )
144                 continue;
145
146             // If the product is assigned a fixed VAT type then skip the product.
147             $vatType = $priceObj->VATType();
148             if ( !$vatType->attribute( 'is_dynamic' ) )
149                 continue;
150
151             // Update item's VAT percentage.
152             $vatValue = $priceObj->VATPercent( $productContentObject, $country );
153             eZDebug::writeNotice( "Updating product item collection item ('" .
154                                   $productContentObject->attribute( 'name' ) . "'): " .
155                                   "setting VAT $vatValue% according to order's country '$country'." );
156             $item->setAttribute( "vat_value", $vatValue );
157
158             $item->store();
159         }
160         $db->commit();
161
162         return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
163     }
164
165     /*!
166      Operation entry: Adds order item: shipping.
167      \params $orderID contains the order id for the shipping handler.
168
169      The function handleShipping() are runned in the process of confirmorder and
170      is the final function for creating an order_item in the order confirmation.
171
172      An example for an array that should be returned by the function
173      eZShippingManager::getShippingInfo( $productCollectionID ):
174      \code
175      array( 'shipping_items' => array( array( 'description' => 'Shipping vat: 12%',
176                                               'cost'        => 50.25,
177                                               'vat_value'   => 12,
178                                               'is_vat_inc'  => 0 ),
179                                        array( 'description' => 'Shipping vat: 25%',
180                                               'cost'        => 100.75,
181                                               'vat_value'   => 25,
182                                               'is_vat_inc'  => 0 ) ),
183             'description' => 'Total Shipping',
184             'cost'        => 182.22,
185             'vat_value'   => false,
186             'is_vat_inc'  => 1 );
187      \endcode
188
189      An example for the shippingvalues with only one shippingitem, old standard.
190      \code
191      array( 'description' => 'Total Shipping vat: 16%',
192             'cost'        => 10.25,
193             'vat_value'   => 16,
194             'is_vat_inc'  => 1 );
195      \endcode
196
197      The returned array for each shipping item should consist of these keys:
198      - order_id - The order id for the current order.
199      - description - An own description of the shipping item.
200      - cost - A float value of the cost for the shipping.
201      - vat_value - The vat value that should be added to the shipping item.
202      - is_vat_inc - Either 0, 1 or false. 0: The cost is excluded VAT.
203                                           1: the cost is included VAT.
204                                       false: The cost is combined by several other VAT prices.
205
206      This function may also send additional parameters to be used in other templates, like
207      in the basket.
208     */
209     function handleShipping( $orderID )
210     {
211         do // we prevent high nesting levels by using breaks
212         {
213             $order = eZOrder::fetch( $orderID );
214             if ( !$order )
215                 break;
216             $productCollectionID = $order->attribute( 'productcollection_id' );
217
218             $shippingInfo = eZShippingManager::getShippingInfo( $productCollectionID );
219             if ( !isset( $shippingInfo ) )
220                 break;
221
222             // check if the order item has been added before.
223             $orderItems = $order->orderItemsByType( 'ezcustomshipping' );
224
225             // If orderitems allready exists, remove them first.
226             if ( $orderItems )
227             {
228                 foreach ( $orderItems as $orderItem )
229                 {
230                     $orderItem->remove();
231                 }
232                 $purgeStatus = eZShippingManager::purgeShippingInfo( $productCollectionID );
233             }
234
235             if ( isset( $shippingInfo['shipping_items'] ) and
236                  is_array( $shippingInfo['shipping_items'] ) )
237             {
238                 // Add a new order item for each shipping.
239                 foreach ( $shippingInfo['shipping_items'] as $orderItemShippingInfo )
240                 {
241                     $orderItem = new eZOrderItem( array( 'order_id' => $orderID,
242                                                          'description' => $orderItemShippingInfo['description'],
243                                                          'price' => $orderItemShippingInfo['cost'],
244                                                          'vat_value' => $orderItemShippingInfo['vat_value'],
245                                                          'is_vat_inc' => $orderItemShippingInfo['is_vat_inc'],
246                                                          'type' => 'ezcustomshipping' ) );
247                     $orderItem->store();
248                 }
249             }
250             else
251             {
252                 // Made for backwards compability, if the array order_items are not supplied.
253                 if ( !isset( $shippingInfo['vat_value'] ) )
254                 {
255                     $shippingInfo['vat_value'] = 0;
256                 }
257
258                 if ( !isset( $shippingInfo['is_vat_inc'] ) )
259                 {
260                     $shippingInfo['is_vat_inc'] = 1;
261                 }
262
263                 $orderItem = new eZOrderItem( array( 'order_id' => $orderID,
264                                                      'description' => $shippingInfo['description'],
265                                                      'price' => $shippingInfo['cost'],
266                                                      'vat' => $shippingInfo['vat_value'],
267                                                      'is_vat_inc' => $shippingInfo['is_vat_inc'],
268                                                      'type' => 'ezcustomshipping' ) );
269                 $orderItem->store();
270             }
271
272         } while ( false );
273
274         return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
275     }
276
277     /*!
278      Operation entry: Updates shipping info for items in the current basket.
279     */
280     function updateShippingInfo( $objectID, $optionList )
281     {
282         $basket = eZBasket::currentBasket();
283         $shippingInfo = eZShippingManager::updateShippingInfo( $basket->attribute( 'productcollection_id' ) );
284         return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
285     }
286
287     function updateBasket( $itemCountList, $itemIDList )
288     {
289         if ( is_array( $itemCountList ) && is_array( $itemIDList ) && count( $itemCountList ) == count ( $itemIDList ) )
290         {
291             $basket = eZBasket::currentBasket();
292             if ( is_object( $basket ) )
293             {
294                 $productCollectionID = $basket->attribute( 'productcollection_id' );
295
296                 $i = 0;
297                 foreach ( $itemIDList as $id )
298                 {
299                     $item = eZProductCollectionItem::fetch( $id );
300                     if ( is_object( $item ) && $item->attribute( 'productcollection_id' ) == $productCollectionID )
301                     {
302                         if ( is_numeric( $itemCountList[$i] ) and $itemCountList[$i] == 0 )
303                         {
304                             $item->remove();
305                         }
306                         else
307                         {
308                             $item->setAttribute( 'item_count', $itemCountList[$i] );
309                             $item->store();
310                         }
311                     }
312                     ++$i;
313                 }
314             }
315         }
316     }
317
318     /*!
319      Operation entry: Adds the object \a $objectID with options \a $optionList to the current basket.
320     */
321     function addToBasket( $objectID, $optionList, $quantity )
322     {
323         $object = eZContentObject::fetch( $objectID );
324         $nodeID = $object->attribute( 'main_node_id' );
325         $price = 0.0;
326         $isVATIncluded = true;
327         $attributes = $object->contentObjectAttributes();
328
329         $priceFound = false;
330
331         foreach ( $attributes as $attribute )
332         {
333             $dataType = $attribute->dataType();
334             if ( eZShopFunctions::isProductDatatype( $dataType->isA() ) )
335             {
336                 $priceObj = $attribute->content();
337                 $price += $priceObj->attribute( 'price' );
338                 $priceFound = true;
339             }
340         }
341
342         if ( !$priceFound )
343         {
344             eZDebug::writeError( 'Attempted to add object without price to basket.' );
345             return array( 'status' => eZModuleOperationInfo::STATUS_CANCELLED );
346         }
347
348         $currency = $priceObj->attribute( 'currency' );
349
350         // Check for 'option sets' in option list.
351         // If found each 'option set' will be added as a separate product purchase.
352         $hasOptionSet = false;
353         foreach ( array_keys( $optionList ) as $optionKey )
354         {
355             if ( substr( $optionKey, 0, 4 ) == 'set_' )
356             {
357                 $returnStatus = eZShopOperationCollection::addToBasket( $objectID, $optionList[$optionKey] );
358                 // If adding one 'option set' fails we should stop immediately
359                 if ( $returnStatus['status'] == eZModuleOperationInfo::STATUS_CANCELLED )
360                     return $returnStatus;
361                 $hasOptionSet = true;
362             }
363         }
364         if ( $hasOptionSet )
365             return $returnStatus;
366
367
368         $unvalidatedAttributes = array();
369         foreach ( $attributes as $attribute )
370         {
371             $dataType = $attribute->dataType();
372
373             if ( $dataType->isAddToBasketValidationRequired() )
374             {
375                 $errors = array();
376                 if ( $attribute->validateAddToBasket( $optionList[$attribute->attribute('id')], $errors ) !== eZInputValidator::STATE_ACCEPTED )
377                 {
378                     $description = $errors;
379                     $contentClassAttribute = $attribute->contentClassAttribute();
380                     $attributeName = $contentClassAttribute->attribute( 'name' );
381                     $unvalidatedAttributes[] = array( "name" => $attributeName,
382                                                       "description" => $description );
383                 }
384             }
385         }
386         if ( count( $unvalidatedAttributes ) > 0 )
387         {
388             return array( 'status' => eZModuleOperationInfo::STATUS_CANCELLED,
389                           'reason' => 'validation',
390                           'error_data' => $unvalidatedAttributes );
391         }
392
393         $basket = eZBasket::currentBasket();
394
395         /* Check if the item with the same options is not already in the basket: */
396         $itemID = false;
397         $collection = $basket->attribute( 'productcollection' );
398         if ( !$collection )
399         {
400             eZDebug::writeError( 'Unable to find product collection.' );
401             return array( 'status' => eZModuleOperationInfo::STATUS_CANCELLED );
402         }
403         else
404         {
405             $collection->setAttribute( 'currency_code', $currency );
406             $collection->store();
407
408             $count = 0;
409             /* Calculate number of options passed via the HTTP variable: */
410             foreach ( array_keys( $optionList ) as $key )
411             {
412                 if ( is_array( $optionList[$key] ) )
413                     $count += count( $optionList[$key] );
414                 else
415                     $count++;
416             }
417             $collectionItems = $collection->itemList( false );
418             foreach ( $collectionItems as $item )
419             {
420                 /* For all items in the basket which have the same object_id: */
421                 if ( $item['contentobject_id'] == $objectID )
422                 {
423                     $options = eZProductCollectionItemOption::fetchList( $item['id'], false );
424                     /* If the number of option for this item is not the same as in the HTTP variable: */
425                     if ( count( $options ) != $count )
426                     {
427                         break;
428                     }
429                     $theSame = true;
430                     foreach ( $options as $option )
431                     {
432                         /* If any option differs, go away: */
433                         if ( ( is_array( $optionList[$option['object_attribute_id']] ) &&
434                                !in_array( $option['option_item_id'], $optionList[$option['object_attribute_id']] ) )
435                              || ( !is_array( $optionList[$option['object_attribute_id']] ) &&
436                                   $option['option_item_id'] != $optionList[$option['object_attribute_id']] ) )
437                         {
438                             $theSame = false;
439                             break;
440                         }
441                     }
442                     if ( $theSame )
443                     {
444                         $itemID = $item['id'];
445                         break;
446                     }
447                 }
448             }
449
450             if ( $itemID )
451             {
452                 /* If found in the basket, just increment number of that items: */
453                 $item = eZProductCollectionItem::fetch( $itemID );
454                 $item->setAttribute( 'item_count', $quantity + $item->attribute( 'item_count' ) );
455                 $item->store();
456             }
457             else
458             {
459                 $item = eZProductCollectionItem::create( $basket->attribute( "productcollection_id" ) );
460
461                 $item->setAttribute( 'name', $object->attribute( 'name' ) );
462                 $item->setAttribute( "contentobject_id", $objectID );
463                 $item->setAttribute( "item_count", $quantity );
464                 $item->setAttribute( "price", $price );
465                 if ( $priceObj->attribute( 'is_vat_included' ) )
466                 {
467                     $item->setAttribute( "is_vat_inc", '1' );
468                 }
469                 else
470                 {
471                     $item->setAttribute( "is_vat_inc", '0' );
472                 }
473                 $item->setAttribute( "vat_value", $priceObj->attribute( 'vat_percent' ) );
474                 $item->setAttribute( "discount", $priceObj->attribute( 'discount_percent' ) );
475                 $item->store();
476                 $priceWithoutOptions = $price;
477
478                 $optionIDList = array();
479                 foreach ( array_keys( $optionList ) as $key )
480                 {
481                     $attributeID = $key;
482                     $optionString = $optionList[$key];
483                     if ( is_array( $optionString ) )
484                     {
485                         foreach ( $optionString as $optionID )
486                         {
487                             $optionIDList[] = array( 'attribute_id' => $attributeID,
488                                                      'option_string' => $optionID );
489                         }
490                     }
491                     else
492                     {
493                         $optionIDList[] = array( 'attribute_id' => $attributeID,
494                                                  'option_string' => $optionString );
495                     }
496                 }
497
498                 $db = eZDB::instance();
499                 $db->begin();
500                 foreach ( $optionIDList as $optionIDItem )
501                 {
502                     $attributeID = $optionIDItem['attribute_id'];
503                     $optionString = $optionIDItem['option_string'];
504
505                     $attribute = eZContentObjectAttribute::fetch( $attributeID, $object->attribute( 'current_version' ) );
506                     $dataType = $attribute->dataType();
507                     $optionData = $dataType->productOptionInformation( $attribute, $optionString, $item );
508                     if ( $optionData )
509                     {
510                         $optionData['additional_price'] = eZShopFunctions::convertAdditionalPrice( $currency, $optionData['additional_price'] );
511                         $optionItem = eZProductCollectionItemOption::create( $item->attribute( 'id' ), $optionData['id'], $optionData['name'],
512                                                                              $optionData['value'], $optionData['additional_price'], $attributeID );
513                         $optionItem->store();
514                         $price += $optionData['additional_price'];
515                     }
516                 }
517
518                 if ( $price != $priceWithoutOptions )
519                 {
520                     $item->setAttribute( "price", $price );
521                     $item->store();
522                 }
523                 $db->commit();
524             }
525         }
526
527         return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
528     }
529
530     function activateOrder( $orderID )
531     {
532         $order = eZOrder::fetch( $orderID );
533
534         $db = eZDB::instance();
535         $db->begin();
536         $order->activate();
537
538         $basket = eZBasket::currentBasket( true, $orderID);
539         $basket->remove();
540         $db->commit();
541
542         eZHTTPTool::instance()->setSessionVariable( "UserOrderID", $orderID );
543         return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
544     }
545
546     function sendOrderEmails( $orderID )
547     {
548         $order = eZOrder::fetch( $orderID );
549
550         // Fetch the shop account handler
551         $accountHandler = eZShopAccountHandler::instance();
552         $email = $accountHandler->email( $order );
553
554         // Fetch the confirm order handler
555         $confirmOrderHandler = eZConfirmOrderHandler::instance();
556         $params = array( 'email' => $email,
557                          'order' => $order );
558         $confirmOrderStatus = $confirmOrderHandler->execute( $params );
559
560         return array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
561     }
562
563     /*!
564      Verify that we have a valid currency before the the order can continue.
565     */
566     function checkCurrency( $orderID )
567     {
568         $returnStatus = array( 'status' => eZModuleOperationInfo::STATUS_CONTINUE );
569         $order = eZOrder::fetch( $orderID );
570         $productCollection = $order->attribute( 'productcollection' );
571         $currencyCode = $productCollection->attribute( 'currency_code' );
572         $currencyCode = trim( $currencyCode );
573         if ( $currencyCode == '' )
574         {
575             $returnStatus = array( 'status' => eZModuleOperationInfo::STATUS_CANCELLED );
576         }
577
578         $locale = eZLocale::instance();
579         $localeCurrencyCode = $locale->currencyShortName();
580
581         // Reverse logic to avoid calling eZCurrencyData::currencyExists() if the first expression is true.
582         if ( !( $currencyCode == $localeCurrencyCode or
583                 eZCurrencyData::currencyExists( $currencyCode ) ) )
584         {
585             $returnStatus = array( 'status' => eZModuleOperationInfo::STATUS_CANCELLED );
586         }
587         return $returnStatus;
588     }
589 }
590
591 ?>