Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
orderbuyer.php
1<?php
2
4
10use \Bitrix\Sale\Internals\Input;
12use Bitrix\Sale;
13
14Loc::loadMessages(__FILE__);
15
17{
18 public static function getEdit(Sale\Order $order, $showProfiles = false, $profileId = 0)
19 {
20 $data = self::prepareData($order);
21
22 $result = '
23 <div class="adm-bus-table-container">
24 <table border="0" cellspacing="0" cellpadding="0" width="100%" class="adm-detail-content-table edit-table">
25 <tbody>
26 <tr'.(intval($data["USER_ID"]) > 0 ? ' style="display: none"': '' ).' id="sale-order-buyer-find-button-wrap">
27 <td class="adm-detail-content-cell-l fwb" width="40%">
28 &nbsp;
29 </td>
30 <td class="adm-detail-content-cell-r">
31 <input type="button" name="FIND_BUYER" value="'.Loc::getMessage("SALE_ORDER_BUYER_FIND").'" onclick="BX.Sale.Admin.OrderBuyer.showChooseBuyerWindow(\''.LANGUAGE_ID.'\')"><br>
32 <i>'.Loc::getMessage("SALE_ORDER_BUYER_START_TO_CREATE").':</i>
33 </td>
34 </tr>
35 <tr'.(intval($data["USER_ID"]) <= 0 ? ' style="display: none"': '' ).' id="sale-order-buyer-name-wrap">
36 <td class="adm-detail-content-cell-l" width="40%">'.Loc::getMessage("SALE_ORDER_BUYER").':</td>
37 <td class="adm-detail-content-cell-r">
38 <div class="adm-s-order-person-choose">'.static::renderBuyerLink($data, ['id="BUYER_USER_NAME"']).'
39 &nbsp;
40 <a class="adm-s-bus-morelinkqhsw" onclick="BX.Sale.Admin.OrderBuyer.showChooseBuyerWindow(\''.LANGUAGE_ID.'\')" href="javascript:void(0);">
41 '.Loc::getMessage("SALE_ORDER_BUYER_CHANGE").'
42 </a>&nbsp;
43 <a class="adm-s-bus-morelinkqhsw" onclick="BX.Sale.Admin.OrderBuyer.clearBuyer();" href="javascript:void(0);">
44 '.Loc::getMessage("SALE_ORDER_BUYER_CLEAR").'
45 </a>
46 <input type="hidden" name="USER_ID" id="USER_ID" value="'.intval($data["USER_ID"]).'" onchange="BX.Sale.Admin.OrderBuyer.onBuyerIdChange(this);">
47 </div>
48 </td>
49 </tr>
50 <tr>
51 <td class="adm-detail-content-cell-l fwb">'.Loc::getMessage("SALE_ORDER_BUYER_PAYER_TYPE").':</td>
52 <td class="adm-detail-content-cell-r">'.
53 \Bitrix\Sale\Helpers\Admin\OrderEdit::makeSelectHtml(
54 "PERSON_TYPE_ID",
55 self::getBuyerTypesList($order->getSiteId()),
56 isset($data["PERSON_TYPE_ID"]) ? $data["PERSON_TYPE_ID"] : "",
57 false,
58 array(
59 "class" => "adm-bus-select",
60 "id" => "PERSON_TYPE_ID",
61 "onchange" => "BX.Sale.Admin.OrderBuyer.onBuyerTypeChange(this.value);"
62 )
63 ).
64 '</td>
65 </tr>
66 <tr id="sale-order-buyer-profiles-list-row"'.($showProfiles ? '' : ' style="display:none;"').'>
67 <td class="adm-detail-content-cell-l">'.Loc::getMessage("SALE_ORDER_BUYER_CHOOSE_PROFILE").':</td>
68 <td class="adm-detail-content-cell-r"><div id="BUYER_PROFILE_ID_CONTAINER">';
69 if($showProfiles)
70 {
71 $result .= \Bitrix\Sale\Helpers\Admin\OrderEdit::makeSelectHtml(
72 "BUYER_PROFILE_ID",
73 self::getBuyerProfilesList($data["USER_ID"], $data["PERSON_TYPE_ID"]),
74 $profileId,
75 false,
76 array(
77 "class" => "adm-bus-select",
78 "id" => "BUYER_PROFILE_ID",
79 "onchange" => "BX.Sale.Admin.OrderBuyer.onBuyerProfileChange();"
80 )
81 );
82 }
83
84 $result .= '</div></td>
85 </tr>
86 </tbody>
87 </table>
88 </div>'.
89 '<div id="order_properties_container"></div>'.
90 '<div>'.self::getOrderPropertiesByJS($order).'</div>'.
91 '<div class="adm-bus-table-container caption border sale-order-props-group">'.
92 '<div class="adm-bus-table-caption-title">'.Loc::getMessage("SALE_ORDER_BUYER_COMMENT").'</div>
93 <table border="0" cellspacing="0" cellpadding="0" width="100%" class="adm-detail-content-table edit-table ">
94 <tbody>
95 <tr>
96 <td class="adm-detail-content-cell-l" width="40%">'.Loc::getMessage("SALE_ORDER_BUYER_ORDERCOMMENT").':</td>
97 <td class="adm-detail-content-cell-r">
98 <textarea style="width:400px;min-height:100px;" name="USER_DESCRIPTION" id="USER_DESCRIPTION">'.
99 htmlspecialcharsbx($data["USER_DESCRIPTION"]).
100 '</textarea>
101 </td>
102 </tr>
103 </tbody>
104 </table>
105 </div>';
106
107 return $result;
108 }
109 public static function getPropsEdit(Sale\Order $order)
110 {
111 $result = '
112 <div>
113 <div id="order_properties_container_add"></div>'.
114 '<div>'.self::getOrderRelPropertiesByJS($order).'</div>'.
115 '</div>';
116
117 return $result;
118 }
119
120 public static function getView(Sale\Order $order)
121 {
122 $data = self::prepareData($order);
123 $buyersList = self::getBuyerTypesList($order->getSiteId());
124
125 return '
126 <div class="adm-bus-table-container">
127 <table border="0" cellspacing="0" cellpadding="0" width="100%" class="adm-detail-content-table edit-table">
128 <tbody>
129 <tr>
130 <td class="adm-detail-content-cell-l" width="40%">'.Loc::getMessage("SALE_ORDER_BUYER").':</td>
131 <td class="adm-detail-content-cell-r">
132 <div>'.static::renderBuyerLink($data).'</div>
133 </td>
134 </tr>
135 <tr>
136 <td class="adm-detail-content-cell-l">'.Loc::getMessage("SALE_ORDER_BUYER_PAYER_TYPE").':</td>
137 <td class="adm-detail-content-cell-r">'.htmlspecialcharsbx($buyersList[$data["PERSON_TYPE_ID"]]).
138 '</td>
139 </tr>
140 </tbody>
141 </table>
142 </div>'.
143 self::getOrderPropertiesHtml($order, true).
144 '<div class="adm-bus-table-container caption border sale-order-props-group">'.
145 '<div class="adm-bus-table-caption-title">'.Loc::getMessage("SALE_ORDER_BUYER_COMMENT").'</div>
146 <table border="0" cellspacing="0" cellpadding="0" width="100%" class="adm-detail-content-table edit-table ">
147 <tbody>
148 <tr>
149 <td class="adm-detail-content-cell-l" width="40%">'.Loc::getMessage("SALE_ORDER_BUYER_ORDERCOMMENT").':</td>
150 <td class="adm-detail-content-cell-r"><p id="sale-adm-user-description-view" style="color:gray; max-width:800px; overflow:auto;">'.($data["USER_DESCRIPTION"] <> '' ? nl2br(htmlspecialcharsbx($data["USER_DESCRIPTION"])) : Loc::getMessage("SALE_ORDER_BUYER_NO")).'</p></td>
151 </tr>
152 </tbody>
153 </table>
154 </div>';
155
156 }
157
158 protected static function renderBuyerLink($data, $attr=[])
159 {
160 return '<a href="'.$data["BUYER_URL"].'" '.(count($attr)>0 ? implode(' ', $attr):"").'>'.htmlspecialcharsbx($data["BUYER_USER_NAME"]).'</a>'; die;
161 }
162
163 public static function getScripts()
164 {
165 \Bitrix\Main\Page\Asset::getInstance()->addJs("/bitrix/js/sale/admin/order_buyer.js");
166 \Bitrix\Main\Page\Asset::getInstance()->addJs('/bitrix/js/sale/input.js');
167 \Bitrix\Sale\PropertyValueCollection::initJs();
168 $langPhrases = array("SALE_ORDER_BUYER_CREATE_NEW", "SALE_ORDER_BUYER_UNKNOWN_GROUP");
169
170 $result = '<script type="text/javascript">'.
171 'BX.Sale.Admin.OrderBuyer.isFeatureSaleAccountsEnabled = '.(\CBXFeatures::IsFeatureEnabled('SaleAccounts') ? 'true' : 'false').';';
172
173 foreach($langPhrases as $phrase)
174 $result .= ' BX.message({'.$phrase.': "'.\CUtil::jsEscape(Loc::getMessage($phrase)).'"});';
175
176 $result .= '
177 BX.ready(function(){
178 BX.Sale.Admin.OrderEditPage.registerFieldsUpdaters( BX.Sale.Admin.OrderBuyer.getFieldsUpdaters() );
179 });
180 </script>
181 ';
182
183 return $result;
184 }
185
186 public static function getDefaultPersonType($siteId)
187 {
188 $personTypes = self::getBuyerTypesList($siteId);
189 reset($personTypes);
190 return key($personTypes);
191 }
192
193 public static function prepareData(Sale\Order $order)
194 {
195 if (\CBXFeatures::IsFeatureEnabled('SaleAccounts'))
196 {
197 $strBuyerProfileUrl = '/bitrix/admin/sale_buyers_profile.php?USER_ID='.intval($order->getUserId()).'&lang='.LANGUAGE_ID;
198 }
199 else
200 {
201 $strBuyerProfileUrl = '/bitrix/admin/user_edit.php?ID='.$order->getUserId().'&lang='.LANGUAGE_ID;
202 }
203
204 $result = array(
205 "USER_ID" => intval($order->getUserId()),
206 "PERSON_TYPE_ID" => $order->getPersonTypeId(),
207 "BUYER_USER_NAME" => OrderEdit::getUserName(
208 $order->getUserId(),
209 $order->getSiteId()
210 ),
211 "USER_DESCRIPTION" => $order->getField("USER_DESCRIPTION"),
212 "BUYER_URL" => $strBuyerProfileUrl
213 );
214
215 return $result;
216 }
217
218 public static function getBuyerTypesList($siteId)
219 {
220 static $result = array();
221
222 if(!isset($result[$siteId]))
223 {
224 $result[$siteId] = array();
225 $dbPersonType = \CSalePersonType::GetList(array("SORT" => "ASC", "NAME" => "ASC"), array("ACTIVE" => "Y", "LID"=> $siteId));
226
227 while ($personType = $dbPersonType->Fetch())
228 $result[$siteId][$personType["ID"]] = $personType["NAME"]." [".$personType["ID"]."]";
229 }
230
231 return $result[$siteId];
232 }
233
234 public static function getProfileParams($userId, $profileId)
235 {
236 return OrderUserProperties::getProfileValues($profileId);
237 }
238
239 public static function getUserProfiles($userId, $personTypeId = null)
240 {
241 if(intval($userId) <=0)
242 return array();
243
244 $result = array();
245 $profilesResult = OrderUserProperties::loadProfiles($userId, $personTypeId);
246 $profiles = $profilesResult->getData();
247
248 if(is_array($profiles))
249 {
250 foreach($profiles as $typeId => $types)
251 {
252 if(!isset($result[$typeId]))
253 $result[$typeId] = array();
254
255 foreach($types as $key => $value)
256 {
257 $result[$typeId][$key] = $value["VALUES"];
258 }
259 }
260 }
261
262 if($personTypeId && empty($result[$personTypeId]))
263 {
264 $result[$personTypeId] = array(self::getProfileValuesFromPrevOrder($userId, $personTypeId));
265
266 if(empty($result[$personTypeId]))
267 $result[$personTypeId] = array(self::getProfileValuesFromUser($userId, $personTypeId));
268 }
269
270 return $result;
271 }
272
273 protected static function getProfileValuesFromPrevOrder($userId, $personTypeId)
274 {
275 if(intval($personTypeId) <= 0)
276 throw new ArgumentNullException('userId');
277
278 if(intval($personTypeId) <= 0)
279 throw new ArgumentNullException('personTypeId');
280
281 $registry = Sale\Registry::getInstance(Sale\Registry::REGISTRY_TYPE_ORDER);
283 $orderClass = $registry->getOrderClassName();
284
285 $res = $orderClass::getList(array(
286 'filter' => array(
287 'USER_ID' => $userId
288 ),
289 'order' => array('DATE_INSERT' => 'DESC'),
290 'select' => array('ID')
291 ));
292
293 if(!$order = $res->fetch())
294 {
295 return array();
296 }
297
299 $order = $orderClass::load($order['ID']);
300
301 if(!$order)
302 return array();
303
304 $propCollection = $order->getPropertyCollection();
305
306 if(!$propCollection)
307 return array();
308
309 $result = array();
310
311 $pRes = OrderPropsTable::getList(array(
312 'filter' => array(
313 'PERSON_TYPE_ID' => $personTypeId,
314 'ACTIVE' => 'Y',
315 'USER_PROPS' => 'Y'
316 )
317 ));
318
319 while($prop = $pRes->fetch())
320 {
321 if($prop['DEFAULT_VALUE'] <> '')
322 {
323 $result[$prop['ID']] = $prop['DEFAULT_VALUE'];
324 }
325 else
326 {
327 $property = null;
328
329 if($prop['IS_EMAIL'] == 'Y')
330 $property = $propCollection->getUserEmail();
331 elseif($prop['IS_PAYER'] == 'Y')
332 $property = $propCollection->getPayerName();
333 elseif($prop['IS_PHONE'] == 'Y')
334 $property = $propCollection->getPhone();
335 elseif($prop['IS_ADDRESS'] == 'Y')
336 $property = $propCollection->getAddress();
337
338 if($property)
339 $result[$prop['ID']] = $property->getValue();
340 }
341 }
342
343 return $result;
344 }
345
346 protected static function getProfileValuesFromUser($userId, $personTypeId)
347 {
348 if(intval($personTypeId) <= 0)
349 throw new ArgumentNullException('userId');
350
351 if(intval($personTypeId) <= 0)
352 throw new ArgumentNullException('personTypeId');
353
354 $uRes = UserTable::getById($userId);
355
356 if(!$user= $uRes->fetch())
357 return array();
358
359 $result = array();
360
361 $pRes = OrderPropsTable::getList(array(
362 'filter' => array(
363 'PERSON_TYPE_ID' => $personTypeId,
364 'ACTIVE' => 'Y',
365 'USER_PROPS' => 'Y'
366 )
367 ));
368
369 while($prop = $pRes->fetch())
370 {
371 if($prop['DEFAULT_VALUE'] <> '')
372 {
373 $result[$prop['ID']] = $prop['DEFAULT_VALUE'];
374
375 }
376 elseif($prop['IS_EMAIL'] == 'Y' && !empty($user['EMAIL']))
377 {
378 $result[$prop['ID']] = $user['EMAIL'];
379 }
380 elseif($prop['IS_PAYER'] == 'Y')
381 {
382 $name = '';
383
384 if(!empty($user['LAST_NAME']))
385 $name .= $user['LAST_NAME'];
386
387 if(!empty($user['NAME']))
388 $name .= $user['NAME'];
389
390 if(!empty($user['SECOND_NAME']))
391 $name .= $user['SECOND_NAME'];
392
393 if($name <> '')
394 $result[$prop['ID']] = $name;
395 }
396 elseif($prop['IS_PHONE'] == 'Y' && !empty($user['PERSONAL_MOBILE']))
397 {
398 $result[$prop['ID']] = $user['PERSONAL_MOBILE'];
399 }
400 elseif($prop['IS_ADDRESS'] == 'Y')
401 {
402 $address = '';
403
404 if(!empty($user['PERSONAL_STREET']))
405 $address .= $user['PERSONAL_STREET'];
406
407 if(!empty($user['PERSONAL_CITY']))
408 $address .= $user['PERSONAL_CITY'];
409
410 if(!empty($user['PERSONAL_STATE']))
411 $address .= $user['PERSONAL_STATE'];
412
413 if(!empty($user['PERSONAL_ZIP']))
414 $address .= $user['PERSONAL_ZIP'];
415
416 if(!empty($user['PERSONAL_COUNTRY']))
417 $address .= $user['PERSONAL_COUNTRY'];
418
419 $result[$prop['ID']] = $address;
420 }
421 }
422
423 return $result;
424 }
425
426 public static function getBuyerProfilesList($userId, $personTypeId = null)
427 {
428 $result = array(0 => Loc::getMessage("SALE_ORDER_BUYER_CREATE_NEW"));
429
430 if(intval($userId) > 0)
431 {
432 $profilesResult = OrderUserProperties::loadProfiles($userId, $personTypeId);
433 $profiles = $profilesResult->getData();
434
435 if(is_array($profiles))
436 foreach($profiles as $types)
437 foreach($types as $key => $value)
438 $result[$key] = htmlspecialcharsback($value["NAME"]);
439 }
440
441 return $result;
442 }
443
444 public static function getOrderPropertiesHtml(Sale\Order $order, $readonly = false)
445 {
446 $propertyCollection = $order->getPropertyCollection();
447 $result = "";
448
449 foreach ($propertyCollection->getGroups() as $group)
450 {
451 $resultBody = "";
452
453 $groupProperties = $propertyCollection->getPropertiesByGroupId($group['ID']);
454
455 if(!is_array($groupProperties))
456 continue;
457
459 foreach ($propertyCollection->getPropertiesByGroupId($group['ID']) as $property)
460 {
461 $propertyValue = $property->getValue();
462
463 if ($readonly
464 && (
465 !isset($propertyValue)
466 || (is_array($propertyValue) && empty($propertyValue))
467 || $propertyValue === ""
468 )
469 )
470 continue;
471
472 $p = $property->getProperty();
473
474 if($p['IS_PHONE'] == 'Y' && $readonly)
475 {
476 $phoneVal = $property->getValue();
477
478 if($phoneVal != '')
479 {
480 if(!is_array($phoneVal))
481 $phoneVal = array($phoneVal);
482
483 $showHtml = '';
484
485 foreach($phoneVal as $number)
486 {
487 $number = str_replace("'", "", htmlspecialcharsbx($number));
488
489 if($showHtml <> '')
490 $showHtml .= ', ';
491
492 $showHtml .= '<a href="javascript:void(0)" onclick="BX.Sale.Admin.OrderEditPage.desktopMakeCall(\''.$number.'\');">'.
493 $number.
494 '</a>';
495 }
496 }
497 else
498 {
499 $showHtml = '';
500 }
501 }
502 else
503 {
504 $showHtml = (($readonly) ? $property->getViewHtml() : $property->getEditHtml());
505 }
506
507 $resultBody .= '
508 <tr>
509 <td class="adm-detail-content-cell-l" width="40%" valign="top">'.htmlspecialcharsbx($property->getName()).':</td>
510 <td class="adm-detail-content-cell-r"><div>'.$showHtml.'</div></td>
511 </tr>';
512 }
513
514 if (!empty($resultBody))
515 {
516 $result .= '<div class="adm-bus-table-container caption border sale-order-props-group">
517 <div class="adm-bus-table-caption-title">'.htmlspecialcharsbx($group['NAME']).'</div>
518 <table border="0" cellspacing="0" cellpadding="0" width="100%" class="adm-detail-content-table edit-table ">
519 <tbody>'.$resultBody.'
520 </tbody>
521 </table>
522 </div>';
523 }
524
525 }
526
527 return $result;
528 }
529
530 public static function getRelPropData(Sale\Order $order)
531 {
532 $result = array();
533 $groups = array();
534
535 $items = $order->getPropertyCollection()->getArray();
536 foreach ($items as $key => $item)
537 {
538 if ($key == 'properties')
539 {
540 $result[$key] = array();
541 foreach ($item as $property)
542 {
543 if (!empty($property['RELATION']))
544 {
545 if ($property['TYPE'] === 'ENUM' && is_array($property['OPTIONS']))
546 {
547 $property['OPTIONS_SORT'] = array_keys($property['OPTIONS']);
548 }
549 $result[$key][] = $property;
550 $groups[$property['PROPS_GROUP_ID']] = true;
551 }
552 }
553 }
554 else
555 {
556 $result[$key] = $item;
557 }
558 }
559
560 foreach ($result['groups'] as $i => $group)
561 {
562 if (!isset($groups[$group['ID']]))
563 unset($result['groups'][$i]);
564 }
565
566 return $result;
567 }
568
569 public static function getNotRelPropData(Sale\Order $order)
570 {
571 $result = array();
572 $groups = array();
573
574 $items = $order->getPropertyCollection()->getArray();
575 foreach ($items as $key => $item)
576 {
577 if ($key == 'properties')
578 {
579 $result[$key] = array();
580 foreach ($item as $property)
581 {
582 if (empty($property['RELATION']))
583 {
584 $result[$key][] = $property;
585 $groups[$property['PROPS_GROUP_ID']] = true;
586 }
587 }
588 }
589 else
590 {
591 $result[$key] = $item;
592 }
593 }
594
595 foreach ($result['groups'] as $i => $group)
596 {
597 if (!isset($groups[$group['ID']]) && $group['ID'] != 0)
598 unset($result['groups'][$i]);
599 }
600
601 return $result;
602 }
603
604 public static function getOrderPropertiesByJS(Sale\Order $order)
605 {
606 if ($order->getId() > 0)
607 $result = $order->getPropertyCollection()->getArray();
608 else
609 $result = self::getNotRelPropData($order);
610
611 if (!empty($result['properties']))
612 {
613 $propertyTypes = Input\Manager::getTypes();
614 $baseTypes = array();
615 foreach ($propertyTypes as $typeName => $typeData)
616 {
617 if (mb_strpos($typeData['CLASS'], 'Bitrix\\Sale\\Internals\\Input') !== false)
618 $baseTypes[] = $typeName;
619 }
620
621 foreach ($result['properties'] as &$property)
622 {
623 $propertyClassName = $propertyTypes[$property['TYPE']]['CLASS'];
624
625 if (
626 !in_array($property['TYPE'], $baseTypes)
627 && class_exists($propertyClassName)
628 && new $propertyClassName instanceof Input\Base
629 )
630 {
631 ob_start();
632 $propertyCustomName = "PROPERTIES[".$property['ID']."]";
634 echo $propertyClassName::getEditHtml($propertyCustomName, $property, $property['VALUE']);
635 $property['EDIT_HTML'] = ob_get_contents();
636 ob_end_clean();
637 $property['TYPE'] = 'UF';
638 }
639
640 if ($property['TYPE'] === 'ENUM' && is_array($property['OPTIONS']))
641 {
642 $property['OPTIONS_SORT'] = array_keys($property['OPTIONS']);
643 }
644 }
645 }
646
647 if (!empty($result['groups']) && !empty($result['properties']))
648 {
649 $groupIndexList = array();
650 foreach ($result['groups'] as $groupdData)
651 {
652 $groupIndexList[] = intval($groupdData['ID']);
653 }
654
655 if (!empty($groupIndexList))
656 {
657 foreach ($result['properties'] as $index => $propertyData)
658 {
659 if (array_key_exists('PROPS_GROUP_ID', $propertyData))
660 {
661 if (!in_array($propertyData['PROPS_GROUP_ID'], $groupIndexList))
662 {
663 $result['properties'][$index]['PROPS_GROUP_ID'] = 0;
664 }
665 }
666 }
667 }
668
669 }
670
671 return '
672 <script type="text/javascript">
673 BX.ready(function(){
674 BX.Sale.Admin.OrderBuyer.setOrderPropsArray('.\CUtil::PhpToJSObject($result).');
675 });
676 </script>
677 ';
678 }
679
680 public static function getOrderRelPropertiesByJS(Sale\Order $order)
681 {
682 $result = self::getRelPropData($order);
683
684 return '
685 <script type="text/javascript">
686 BX.ready(function(){
687 BX.Sale.Admin.OrderBuyer.setOrderRelPropsArray('.\CUtil::PhpToJSObject($result).');
688
689 var updater = [];
690 updater["RELATED_PROPS"] = {
691 callback: BX.Sale.Admin.OrderBuyer.setOrderRelPropsArray,
692 context: this
693 };
694
695 BX.Sale.Admin.OrderEditPage.registerFieldsUpdaters(updater);
696 });
697 </script>
698 ';
699 }
700}
static loadMessages($file)
Definition loc.php:64
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29
static getEdit(Sale\Order $order, $showProfiles=false, $profileId=0)