1 <?php
2
3 4 5 6 7 8
9 class Product extends MediawebPage{
10
11
12 static $can_be_root = false;
13 static $allowed_children = 'none';
14 static $default_parent = 'Catalog';
15
16 static $db = array(
17
18 'Description' => 'Text',
19 'Vendor' => 'Varchar',
20
21 'Price' => 'CatalogPrice',
22 'BasePrice' => 'CatalogPrice',
23 'CostPrice' => 'CatalogPrice',
24 'Discount' => 'CatalogPrice',
25
26 'Available' => 'Boolean',
27 'AllowPurchase' => 'Boolean',
28
29 'ImportID' => 'Varchar(100)',
30 'SKU' => 'Varchar',
31
32 'Quantity' => 'Int',
33
34 'Weight' => 'Int',
35
36 'GroupID' => 'Varchar(255)',
37 'GroupTitle' => 'Varchar(255)',
38 );
39
40 static $defaults = array(
41 'ShowInMenus' => 1,
42 'ShowOnlyInTab' => 1,
43 'Available' => 1,
44 'AllowPurchase' => 1,
45 'BasePrice' => 0,
46 'CostPrice' => 0,
47 );
48
49 static $casting = array(
50 'KilosWeight' => 'Decimal',
51 );
52
53 static $has_one = array(
54 'Photo' => 'Image',
55 'ProductVAT' => 'VAT',
56 );
57
58 static $has_many = array(
59 'ParamValues' => 'ProductParamValue',
60 'Variations' => 'ProductVariation',
61 );
62
63 static $indexes = array(
64 'ImportID' => true,
65 'Vendor' => true,
66 );
67
68 static $summary_fields = array('SKU', 'Title', 'BasePrice', 'CostPrice', 'Vendor', 'ImportID', 'Available', 'AllowPurchase');
69
70 static $fulltext_fields = array('Title', 'Content', 'Description', 'BasePrice', 'CostPrice', 'Vendor');
71
72 static $searchable_fields = array(
73 'Title' => 'PartialMatchFilter',
74 'ImportID' => array('field' => 'TextFieldWithEmptyFlag', 'filter'=>'ExactMatchFilterWithEmpty'),
75 'SKU' => 'ExactMatchFilter',
76 'Available' => 'ExactMatchFilter',
77 'Price' => array('field'=>'RangeField', 'filter'=>'WithinRangeFilter'),
78 'Vendor' => array('field' => 'TextFieldWithEmptyFlag', 'filter'=>'ExactMatchFilterWithEmpty')
79 );
80
81
82 static $possible_filter_fields = array('Title', 'Content', 'Description', 'Price', 'Vendor', 'Available', 'AllowPurchase');
83
84 static function get_possible_filter_fields() {
85 $rs = array();
86 foreach(self::$possible_filter_fields as $field) {
87 $rs[$field] = singleton(Catalog::$subpage_children)->fieldLabel($field);
88 }
89 return $rs;
90 }
91
92 93 94 95 96 97
98 static function set_searchable_fields(array $data) {
99 self::$searchable_fields = $data;
100 }
101
102 103 104 105 106
107 static function get_searchable_fields() {
108 return self::$searchable_fields;
109 }
110
111 112 113 114
115 private static $sort_options = array('title' => 'Title ASC', 'pricea' => 'Price', 'priced' => 'Price DESC');
116
117 118 119 120 121 122
123 static function set_sort_options(Array $data) {
124 self::$sort_options = $data;
125 }
126
127 128 129 130 131
132 static function get_sort_options() {
133 return self::$sort_options;
134 }
135
136 137 138 139 140
141 static function sort_options_orderby($sort) {
142 return (array_key_exists($sort, self::$sort_options)) ? self::$sort_options[$sort] : '';
143 }
144
145 146 147 148 149 150 151
152 static function sort_options_dropdown_map($addDefault = false) {
153 $map = array();
154 if ($addDefault) {
155 $map[''] = _t('Product.SortOption_default', 'Default');
156 }
157 foreach (self::get_sort_options() as $key => $val) {
158 $map[$key] = _t('Product.SortOption_'.$key, ucfirst($key));
159 }
160 return $map;
161 }
162
163 164 165 166 167
168 static $order_empty_price = false;
169
170 static function allow_order_empty_price($val = true) {
171 self::$order_empty_price = $val;
172 }
173
174 175 176
177 static $order_button_class = 'SimpleOrderButton';
178
179 static function set_order_button_class($value) {
180 self::$order_button_class = $value;
181 }
182
183 184 185
186 static $empty_order_button_class = 'SimpleOrderButton';
187
188 static function set_empty_order_button_class($value) {
189 self::$empty_order_button_class = $value;
190 }
191
192
193 protected static $simple_order_additional_fields = array(
194 );
195
196 197 198 199 200
201 static function get_simple_order_additional_fields() {
202 return self::$simple_order_additional_fields;
203 }
204
205 206 207 208 209 210
211 static function add_simple_order_additional_field($name, $params) {
212 self::$simple_order_additional_fields[$name] = $params;
213 }
214
215 216 217 218 219
220 static function set_simple_order_additional_fields($fields) {
221 self::$simple_order_additional_fields = $fields;
222 }
223
224 function WeightNice() {
225 if ($this->Weight > 100000) {
226 return sprintf(_t("Product.NiceKiloWeight"), $this->dbObject('KilosWeight')->Nice(0));
227 }
228 elseif ($this->Weight > 1000) {
229 return sprintf(_t("Product.NiceKiloWeight"), $this->dbObject('KilosWeight')->Nice(1));
230 }
231 return sprintf(_t("Product.NiceGrammWeight"), $this->dbObject('Weight')->Nice());
232 }
233
234 function getKilosWeight() {
235 return $this->Weight / 1000;
236 }
237
238 239 240 241 242 243
244
245 function MainPhoto() {
246 if ($this->PhotoID && $this->Photo()->ID) {
247 return $this->Photo();
248 }
249 if ($this->Photos()->Count()) {
250 return $this->Photos()->First()->Photo();
251 }
252 return false;
253 }
254
255 256 257 258 259 260
261
262 function AllPhotos() {
263 $photos = $this->Photos();
264 if ($this->PhotoID && $this->Photo()->ID) {
265 $mainPhoto = new MediawebPage_Photo();
266 $mainPhoto->Caption = $this->Title;
267 $mainPhoto->MediawebPageID = $this->ID;
268 $mainPhoto->PhotoID = $this->Photo()->ID;
269 $photos->unshift($mainPhoto);
270 }
271 return $photos;
272 }
273
274
275
276
277 function ListParamValues() {
278 $join = "JOIN ProductParam ON ProductParam.ID = ProductParamValue.ProductParamID";
279 if ($this->Parent()->OwnParams) {
280 $join .= " JOIN Catalog_EnabledParams ON ProductParam.ID = Catalog_EnabledParams.ProductParamID AND Catalog_EnabledParams.CatalogID = {$this->ParentID}";
281 }
282 $values = DataObject::get('ProductParamValue', "ProductID = {$this->ID} AND ProductParam.ShowInList = 1", "ProductParam.Sort", $join);
283
284 $rs = ProductParam::group_values_list($values);
285 return $rs;
286 }
287
288
289 function ViewParamValues() {
290 $join = "JOIN ProductParam ON ProductParam.ID = ProductParamValue.ProductParamID";
291 if ($this->Parent()->OwnParams) {
292 $join .= " JOIN Catalog_EnabledParams ON ProductParam.ID = Catalog_EnabledParams.ProductParamID AND Catalog_EnabledParams.CatalogID = {$this->ParentID}";
293 }
294 $values = DataObject::get('ProductParamValue', "ProductID = {$this->ID} AND ProductParam.ShowInView = 1", "ProductParam.Sort", $join);
295
296 $rs = ProductParam::group_values_list($values);
297 return $rs;
298 }
299
300
301 function ParamValue($title) {
302 $join = "JOIN ProductParam ON ProductParam.ID = ProductParamValue.ProductParamID";
303 if ($this->Parent()->OwnParams) {
304 $join .= " JOIN Catalog_EnabledParams ON ProductParam.ID = Catalog_EnabledParams.ProductParamID AND Catalog_EnabledParams.CatalogID = {$this->ParentID}";
305 }
306 $title = Convert::raw2sql($title);
307 $value = DataObject::get('ProductParamValue', "ProductVariationID = {$this->ID} AND ProductParam.TechTitle = '{$title}' ", "", $join);
308 if ($value) {
309 return $value->First();
310 }
311 return false;
312 }
313
314
315 function GroupedProducts() {
316 if ($this->GroupID) {
317 return DataObject::get('Product', "GroupID = '".Convert::raw2sql($this->GroupID)."'");
318 }
319 }
320
321 function AvailableTitle() {
322 return ($this->Available) ? 'Есть' : 'Нет';
323 }
324
325 public function defaultSearchColumns() {
326 return implode(',', $this->stat('fulltext_fields'));
327 }
328
329 public function __construct($record = null, $isSingleton = false) {
330 if (!isset($record['Price']) && isset($record['ID'])) {
331 $p = DataObject::get_by_id('Product', $record['ID']);
332 $record = $p->record;
333 }
334 parent::__construct($record, $isSingleton);
335 }
336
337
338
339 static function import_find($importID) {
340 return DataObject::get_one('Product', "ImportID = '" . Convert::raw2sql($importID) . "'");
341 }
342
343 344 345
346 static $possibleFields = array(
347 'Title', 'Available', 'AllowPurchase', 'BasePrice', 'CostPrice', 'Vendor', 'SKU', 'Description', 'Content', 'URLSegment', 'MenuTitle', 'MetaTitle', 'MetaDescription', 'MetaKeywords', 'Sort'
348 );
349
350 351 352 353 354
355 static function addPossibleFields($fields) {
356 if ($fields)
357 foreach($fields as $field)
358 self::$possibleFields[] = $field;
359 }
360
361 function getProductAdminLink(){
362 return "<a target='_blank' href='/admin/show/{$this->ID}'>{$this->Title}</a>";
363 }
364
365 366 367 368 369 370
371 function importUpdate($importLog, $data) {
372 if (!$this->importValidate($importLog, $data)) {
373 return false;
374 }
375
376 $rs = $this->extend('onBeforeImport', $importLog, $data);
377 if ($rs && max($rs) == false) return false;
378
379 $this->ImportID = $data['id'];
380 foreach(self::$possibleFields as $field) {
381 if (isset($data[$field]))
382 $this->{$field} = Convert::raw2sql($data[$field]);
383 }
384
385
386 if (isset($data['Photo'])) {
387 $this->PhotoID = ($data['Photo']) ? $data['Photo']->ID : 0;
388 }
389
390
391 if (isset($data['params'])) {
392 foreach($data['params'] as $param) {
393
394 $name = Convert::raw2sql($param->attributes()->name);
395
396 if ($this->db($name)) {
397 $value = Convert::raw2sql($param);
398 $this->{$name} = $value;
399 } else {
400 $importLog->addLog("У товара не определено поле {$name}!", 'error');
401 }
402 }
403 }
404
405 if (!$this->ID)
406 $this->ParentID = ($data['ParentID'] !== false) ? $data['ParentID'] : 0;
407
408 if (isset($data['ParentID']) && $data['ParentID'] === false) {
409 $this->write();
410 } else {
411 $this->doPublish();
412 }
413
414
415
416 if ($data['Photos']) {
417
418 $oldPhotos = $this->Photos();
419 $oldPhotosIDs = $oldPhotos->getIdList();
420 $this->Photos()->removeAll();
421
422 foreach($data['Photos'] as $importedImage) {
423 if ($mwPhoto = $oldPhotos->find('PhotoID', $importedImage->ID)) {
424 unset($oldPhotosIDs[$mwPhoto->ID]);
425 } else {
426 $mwPhoto = new MediawebPage_Photo();
427 $mwPhoto->PhotoID = $importedImage->ID;
428 }
429 $mwPhoto->MediawebPageID = $this->ID;
430 $mwPhoto->Caption = $importedImage->Title;
431 $mwPhoto->write();
432 }
433
434 if (count($oldPhotosIDs) > 0) {
435 foreach($oldPhotosIDs as $oldPhotosID) {
436 if ($oldPhoto = $oldPhotos->find('ID', $oldPhotosID)) {
437 $oldPhoto->delete();
438 }
439 }
440 }
441 }
442
443
444 if ($data['Files']) {
445
446 $oldFiles = $this->Files();
447 $oldFilesIDs = $oldFiles->getIdList();
448 $this->Files()->removeAll();
449
450 foreach($data['Files'] as $importedFile) {
451 if ($mwFile = $oldFiles->find('AttachID', $importedFile->ID)) {
452 unset($oldFilesIDs[$mwFile->ID]);
453 } else {
454 $mwFile = new MediawebPage_File();
455 $mwFile->AttachID = $importedFile->ID;
456 }
457 $mwFile->Caption = $importedFile->Title;
458 $mwFile->MediawebPageID = $this->ID;
459 $mwFile->write();
460 }
461
462 if (count($oldFilesIDs) > 0) {
463 foreach($oldFilesIDs as $oldFilesID) {
464 if ($oldFile = $oldFiles->find('ID', $oldFilesID)) {
465 $oldFile->delete();
466 }
467 }
468 }
469 }
470
471
472 if ($data['SpecialCatalogs']) {
473 $productSpecialCatalogs = array();
474
475 $allSpecialCatalogs = DataObject::get('SpecialCatalog');
476 if ($allSpecialCatalogs) {
477 foreach($allSpecialCatalogs as $specialCatalog) {
478 $specialCatalogProducts = $specialCatalog->Products()->getIdList();
479 if (isset($specialCatalogProducts[$this->ID])) {
480 $productSpecialCatalogs[$specialCatalog->ID] = $specialCatalog->ID;
481 }
482 }
483 }
484
485
486 foreach($data['SpecialCatalogs'] as $specialCatalog) {
487 if (isset($productSpecialCatalogs[$specialCatalog->ID])) {
488 unset($productSpecialCatalogs[$specialCatalog->ID]);
489 } else {
490 $specialCatalog->Products()->add($this);
491 }
492 }
493
494 if (count($productSpecialCatalogs)) {
495 foreach($productSpecialCatalogs as $productSpecialCatalog) {
496 if ($oldSpecialCatalog = $allSpecialCatalogs->find('ID', $productSpecialCatalog)) {
497 $oldSpecialCatalog->Products()->remove($this);
498 }
499 }
500 }
501 }
502 $this->extend('onAfterImport');
503 return true;
504 }
505
506 static function import_parse_bool_value($value) {
507 if (in_array($rs, array('true', '1'))) {
508 return 1;
509 }
510 if (in_array($rs, array('false', '0'))) {
511 return 0;
512 }
513 return null;
514 }
515
516 517 518 519 520
521 function importValidate($importLog, & $data) {
522
523 if ((!$this->Title) && (!isset($data['Title']) || trim($data['Title']) == '')) {
524 $importLog->addLog("Товар с id='{$data['id']}' не имеет названия!", 'error');
525 return false;
526 }
527 if (isset($data['Title']) && trim($data['Title']) == '') {
528 $importLog->addLog("Товар с id='{$data['id']}' не имеет названия!", 'error');
529 return false;
530 }
531 if (isset($data['Available'])) {
532 $rs = self::import_parse_bool_value($data['Available']);
533 if ($rs === null) {
534 $importLog->addLog(sprintf(_t('Product.ImportBadBoolValue'), "Наличие товара на складе", $data['Title']), 'warning');
535 } else {
536 $data['Available'] = $rs;
537 }
538 }
539 if (isset($data['AllowPurchase'])) {
540 $rs = self::import_parse_bool_value($data['AllowPurchase']);
541 if ($rs === null) {
542 $importLog->addLog(sprintf(_t('Product.ImportBadBoolValue'), "Разрешено покупать", $data['Title']), 'warning');
543 } else {
544 $data['AllowPurchase'] = $rs;
545 }
546 }
547 if (isset($data['BasePrice']) && !is_numeric($data['BasePrice'])) {
548 $importLog->addLog("Параметр 'Цена' товара {$data['Title']} должен быть числовым!", 'error');
549 return false;
550 }
551 if (isset($data['CostPrice']) && !is_numeric($data['CostPrice'])) {
552 $importLog->addLog("Параметр 'Цена по акции' товара {$data['Title']} должен быть числовым!", 'error');
553 $data['CostPrice'] = 0;
554 }
555 if (isset($data['Sort']) && $data['Sort'] != (int)$data['Sort']) {
556 $importLog->addLog("Параметр Sort товара {$data['Title']} не является целым числом!", 'warning');
557 $data['Sort'] = 0;
558 }
559
560 $rs = $this->extend('importValidate', $importLog, $data);
561 if ($rs && max($rs) == false) return false;
562
563 return true;
564 }
565
566 567 568
569 function importClearAll($importLog) {
570 $oldMode = Versioned::get_reading_mode();
571
572 Versioned::reading_stage('Stage');
573 $products = DataObject::get('Product');
574 if ($products)
575 foreach($products as $product) {
576 $product->doUnpublish();
577 $product->delete();
578 }
579
580 Versioned::reading_stage('Live');
581 $products = DataObject::get('Product');
582 if ($products)
583 foreach($products as $product) {
584 $product->doUnpublish();
585 $product->delete();
586 }
587
588 Versioned::set_reading_mode($oldMode);
589 }
590
591
592
593
594 public function getCMSFields() {
595 SiteTree::disableCMSFieldsExtensions();
596 $fields = parent::getCMSFields();
597 SiteTree::enableCMSFieldsExtensions();
598
599 $fields->replaceField('ParentType', new HiddenField('ParentType', '', 'subpage'));
600
601 $fields->replaceField('Title', new TextField('Title', _t('Product.db_Title', 'Наименование')));
602
603 if ($allVATs = DataObject::get('VAT')) {
604 $fields->addFieldToTab('Root.Content.Main', new DropdownField("ProductVATID", $this->fieldLabel('ProductVAT'), $allVATs->map('ID', 'Title', _t('VAT.SelectVAT'))));
605 }
606
607
608
609 $cat = $this->Parent();
610 while ($cat->Parent() && $cat->Parent()->ClassName == self::$default_parent) {
611 $cat = $cat->Parent();
612 }
613 $parentIDField = new TreeDropdownField('ParentID', $this->fieldLabel('ParentID'), 'SiteTree');
614 $parentIDField->setTreeBaseID($cat->ParentID);
615 $parentIDField->setFilterFunction(create_function('$node', 'return ($node->ClassName == "'.self::$default_parent.'");'));
616
617 $fields->replaceField('ParentID', $parentIDField);
618
619 $fields->addFieldToTab('Root.Content.Main', new NumericField('BasePrice', $this->fieldLabel('BasePrice')));
620 $fields->addFieldToTab('Root.Content.Main', new NumericField('CostPrice', $this->fieldLabel('CostPrice')));
621
622 $fields->addFieldToTab('Root.Content.Main', new CheckboxField('Available', $this->fieldLabel('Available')));
623 $fields->addFieldToTab('Root.Content.Main', new CheckboxField('AllowPurchase', $this->fieldLabel('AllowPurchase')));
624
625 $fields->addFieldToTab('Root.Content.Main', $imgField = new ImageField('Photo', $this->fieldLabel('Photo')));
626 if ($folder = $this->getAssociatedFolder()) {
627 $folderPath = substr_replace(str_replace(ASSETS_DIR.'/', '', $folder->Filename), "", -1);
628 $imgField->setFolderName($folderPath);
629 }
630
631 $fields->addFieldToTab('Root.Content.Main', new TextField('Vendor', $this->fieldLabel('Vendor')));
632 $fields->addFieldToTab('Root.Content.Main', new TextField('ImportID', $this->fieldLabel('ImportID')));
633 $fields->addFieldToTab('Root.Content.Main', new TextField('SKU', $this->fieldLabel('SKU')));
634 $fields->addFieldToTab('Root.Content.Main', new TextField('GroupID', $this->fieldLabel('GroupID')));
635
636 if ($allVATs = DataObject::get('VAT')) {
637 $fields->addFieldToTab('Root.Content.Main', new DropdownField("ProductVATID", $this->fieldLabel('ProductVAT'), $allVATs->map('ID', 'Title', _t('VAT.SelectVAT'))));
638 }
639
640 $fields->addFieldToTab('Root.Content.Main', new TextareaField('Description', $this->fieldLabel('Description')));
641 $fields->addFieldToTab('Root.Content.Main', new TextField('Quantity', $this->fieldLabel('Quantity')));
642 $fields->addFieldToTab('Root.Content.Main', new NumericField('Weight', $this->fieldLabel('Weight')));
643 $fields->addFieldToTab('Root.Content.Main', new TextField('GroupID', $this->fieldLabel('GroupID')));
644
645 if (HtmlEditorConfig::get_active() != 'cms') {
646 HtmlEditorConfig::set_active('cms');
647 }
648
649 $fields->addFieldToTab('Root.Content', new Tab("FullContent", $this->fieldLabel('Content')), 'Metadata');
650 $fields->addFieldToTab('Root.Content.FullContent', $fields->dataFieldByName('Content'));
651
652 if (Catalog::$use_additional_params) {
653
654 if ($this->Parent() && $this->Parent()->hasMethod('Params') && ($categoryParams = $this->Parent()->getNonVariationCatalogParams()) && $categoryParams->Count()) {
655 $tab = $fields->findOrMakeTab('Root.Content.Params', 'Параметры');
656 $paramValues = new DataObjectSet();
657 foreach($categoryParams as $categoryParam) {
658 if ($categoryParam->Type == 'bool') {
659 $tab->push(new ProductParamValue_BoolValueField('ParamValues', $categoryParam, $this->ID));
660 } else {
661 if ($categoryParam->MultiValues) {
662 if ($categoryParam->PossibleValuesList()) {
663 $tab->push(new ProductParamValue_MultiValueSetField('ParamValues', $categoryParam, $this->ID));
664 } else {
665 $tab->push(new LiteralField('ParamTitle', "<h4>{$categoryParam->Title}</h4>"));
666 $rate = new ProductParamValue();
667 $fieldList = array(
668 "Value" => $rate->fieldLabel('Value'),
669 );
670 $fieldTypes = array(
671 'Value' => 'TextField',
672 );
673
674 $ctf = new TableField("ParamValues_{$categoryParam->ID}", "ProductParamValue", $fieldList, $fieldTypes, 'ProductParamID', $categoryParam->ID);
675 $ctf->setExtraData(array('ProductID' => $this->ID));
676 $params = new DataObjectSet();
677 if ($allParamValues = DataObject::get('ProductParamValue', "ProductParamID = {$categoryParam->ID} AND ProductID = {$this->ID}")) {
678 $params = $allParamValues;
679 }
680 $ctf->setCustomSourceItems($params);
681 $tab->push($ctf);
682 }
683 } else {
684 if ($categoryParam->PossibleValuesList()) {
685 $tab->push(new ProductParamValue_MultiValueField('ParamValues', $categoryParam, $this->ID));
686 } else {
687 $tab->push(new ProductParamValue_ValueField('ParamValues', $categoryParam, $this->ID));
688 }
689 }
690 }
691
692 }
693
694 $rate = new ProductParamValue();
695 $fieldList = array(
696 "ID" => $rate->fieldLabel('ID'),
697 "ParamTitle" => $rate->fieldLabel('Title'),
698 "Value" => $rate->fieldLabel('Value'),
699
700 );
701 $fieldTypes = array(
702 'ID' => 'ReadonlyField',
703 'ParamTitle' => 'ReadonlyField',
704 'Value' => 'TextField',
705 );
706 $tablefield = new TableField("ParamValues", "ProductParamValue", $fieldList, $fieldTypes);
707 $tablefield->setCustomSourceItems($paramValues);
708 }
709 }
710
711
712 if (Catalog::$use_variations) {
713 if ($this->Parent() && $this->Parent()->hasMethod('Params') && ($variationParams = $this->Parent()->getVariationCatalogParams())) {
714 $tab = $fields->findOrMakeTab('Root.Content.Variations', 'Вариации');
715 $ctf = $this->getVariationsTable();
716 $ctf->setPopupSize(1000, 600);
717 $tab->push($ctf);
718 }
719 }
720 $this->extend('updateCMSFields', $fields);
721
722 $this->extend('hideCMSFields', $fields);
723 return $fields;
724 }
725
726 function getVariationsTable() {
727 $singleton = singleton('ProductVariation');
728 $query = $singleton->buildSQL("\"ProductID\" = '{$this->ID}'");
729 $variations = $singleton->buildDataObjectSet($query->execute());
730 $filter = $variations ? "\"ID\" IN ('" . implode("','", $variations->column('ID')) . "')" : "\"ID\" < '0'";
731
732 $summaryfields= $singleton->summaryFields();
733
734 if ($this->Parent() && $this->Parent()->hasMethod('Params') && ($variationParams = $this->Parent()->getVariationCatalogParams())) {
735 foreach($variationParams as $attribute){
736 $summaryfields["AttributeProxy.Val".$attribute->TechTitle] = $attribute->Title;
737 }
738 }
739 $tableField = new HasManyComplexTableField(
740 $this,
741 'Variations',
742 'ProductVariation',
743 $summaryfields,
744 null,
745 $filter
746 );
747 $tableField->Markable = 0;
748 $tableField->setRelationAutoSetting(true);
749 return $tableField;
750 }
751
752 public function getCMSActions() {
753 Requirements::javascript('catalog/javascript/ProductAction.js');
754 $fields = parent::getCMSActions();
755 $fields->push(new HiddenField('backLink', '', $this->Parent()->ID));
756 $fields->insertFirst(new FormAction('goBack', _t('Product.BACKBUTTON', 'Back')));
757 return $fields;
758 }
759
760 public function populateDefaults() {
761 parent::populateDefaults();
762
763 $sc = SiteConfig::current_site_config();
764 $this->ShowInMenus = ($sc->ProductShowInTree) ? 1 : 0;
765 $this->ShowOnlyInTab = ($sc->ProductShowInTree) ? 0 : 1;
766 }
767
768 function onBeforeWrite() {
769 parent::onBeforeWrite();
770
771 if (Catalog::$use_variations && $this->Variations()->exists()) {
772 if (($allVariations = $this->AllVariations()) && $allVariations->Count()) {
773 $allVariations->sort('VariationPrice');
774 $this->BasePrice = $allVariations->First()->getBasePrice();
775 $this->CostPrice = $allVariations->First()->getCostPrice();
776 } else {
777 $this->BasePrice = 0;
778 $this->CostPrice = 0;
779 }
780 $this->Price = ($this->CostPrice > 0) ? $this->CostPrice : $this->BasePrice;
781 } else {
782 if ($this->isChanged('BasePrice') || $this->isChanged('CostPrice')) {
783 $this->Price = ($this->CostPrice > 0) ? $this->CostPrice : $this->BasePrice;
784 }
785 }
786 $this->Discount = (($this->CostPrice > 0) ? ($this->BasePrice - $this->CostPrice) : 0);
787
788 $sc = SiteConfig::current_site_config();
789 if ($sc->ProductShowInTree) {
790 $this->ShowOnlyInTab = 0;
791 }
792 else {
793 $this->ShowInMenus = 0;
794 }
795 }
796
797 function onAfterDelete() {
798 parent::onAfterDelete();
799
800 if ($this->IsDeletedFromStage && !$this->ExistsOnLive) {
801 DB::Query("DELETE FROM `ProductParamValue` WHERE ProductID = {$this->ID}");
802 }
803 }
804
805 function getVAT(){
806 if ($this->ProductVATID && ($vat = $this->ProductVAT())) {
807 return $vat;
808 }
809 if ($this->ParentID && ($parent = $this->Parent())) {
810 return $parent->getVAT();
811 }
812 return false;
813 }
814
815 816 817 818 819
820 function VirtualPages() {
821 if (!$this->ID)
822 return null;
823 if (class_exists('Subsite')) {
824 return Subsite::get_from_all_subsites('VirtualProduct', "\"CopyContentFromID\" = " . (int) $this->ID);
825 } else {
826 return DataObject::get('VirtualProduct', "\"CopyContentFromID\" = " . (int) $this->ID);
827 }
828 }
829
830 public function canCreate($member = null) {
831 return (DataObject::get_one('Catalog')) ? parent::canCreate($member) : false;
832 }
833
834 function fieldLabels($includerelations = true) {
835 $labels = parent::fieldLabels($includerelations);
836 $labels['Title'] = _t('Product.db_Title', 'Title');
837 return $labels;
838 }
839
840 841 842 843 844
845 function orderButtonClass() {
846 if (!$this->AllowPurchase) {
847 return false;
848 }
849 if ($this->Price == 0) {
850 if (self::$order_empty_price)
851 return self::$empty_order_button_class;
852 return false;
853 }
854 $class = self::$order_button_class;
855 $this->extend('updateOrderButtonClass', $class);
856 return $class;
857 }
858
859 860 861 862 863
864
865 function OrderButton() {
866 $buttonClass = $this->orderButtonClass();
867 if (!$buttonClass)
868 return false;
869 return new $buttonClass($this->ID);
870 }
871
872 873 874 875 876
877 function OrderButtonOne() {
878 if ($button = $this->OrderButton())
879 return $button->One();
880 return false;
881 }
882
883 884 885 886 887
888 function OrderButtonWithNum() {
889 if ($button = $this->OrderButton()) {
890 return $button->WithNum();
891 }
892 return false;
893 }
894
895 896 897 898 899
900
901 function getRealPrice() {
902 $price = $this->Price;
903 $this->extend('updatePrice', $price);
904 return $price;
905 }
906
907 908 909 910 911 912 913
914 function Price($onlyreal=false) {
915
916 $real = $this->getRealPrice();
917 $client = $real;
918 $delta = 0;
919 $percent = 0;
920 $decimal = 2;
921
922 if ($this->CostPrice == 0) {
923
924 $member = Member::currentUser();
925 if ($member){
926 if ($member->hasMethod('getPersonalDiscount') && $percent < $member->getPersonalDiscount()) {
927 $percent = $member->getPersonalDiscount();
928 }
929 }
930
931 $this->extend('updateDiscount', $percent);
932 }
933
934 $sc = SiteConfig::current_site_config();
935 $ceil = $sc->CatalogDiscountCeil;
936
937 if ($ceil) {
938 $real = ceil($real);
939 $client = ceil($client);
940 $decimal = 0;
941 }
942
943 if ($percent) {
944 $delta = ($ceil) ? ceil(($real / 100) * $percent) : round(($real / 100) * $percent, 2);
945 $client = $real - $delta;
946 }
947
948 if ($onlyreal !== false) {
949 return $client;
950 }
951
952 return new ArrayData(array(
953 'Real' => DBField::create('CatalogPrice', $real),
954 'Client' => DBField::create('CatalogPrice', $client),
955 'Delta' => DBField::create('CatalogPrice', $delta),
956 'Percent' => $percent,
957 'Discount' => ($percent > 0) ? 1 : 0,
958 'Currency' => $sc->CatalogCurrency,
959 ));
960 }
961
962
963 function OldPrice() {
964 if ($this->HasDiscount()) {
965 return $this->BasePrice;
966 }
967 return 0;
968 }
969
970 function HasDiscount() {
971 return ($this->CostPrice > 0);
972 }
973
974 function Currency() {
975 return SiteConfig::current_site_config()->CatalogCurrency;
976 }
977
978 function getOrderItem() {
979
980 }
981
982
983 function AvailableVariations() {
984 return $this->Variations("Available = 1");
985 }
986
987
988 function AllowPurchaseVariations() {
989 return $this->Variations("AllowPurchase = 1");
990 }
991
992 993 994 995 996 997
998 function AllVariations() {
999 if (SiteConfig::current_site_config()->UseOnlyAllowPurchaseVariations) {
1000 return $this->AllowPurchaseVariations();
1001 }
1002 return $this->Variations();
1003 }
1004
1005
1006 function MinPriceVariation() {
1007 if ($allVariations = $this->AllVariations()) {
1008 $allVariations->sort('VariationPrice');
1009 return $allVariations->First()->Price();
1010 }
1011 }
1012
1013
1014 function MaxPriceVariation() {
1015 if (($allVariations = $this->AllVariations()) && ($allVariations->Count() > 1)) {
1016 $allVariations->sort('VariationPrice');
1017 return $allVariations->Last()->Price();
1018 }
1019 return false;
1020 }
1021
1022 function HasSomePrices() {
1023 if ($this->MinPriceVariation() && $this->MaxPriceVariation() && ($this->MinPriceVariation()->Real != $this->MaxPriceVariation()->Real)) {
1024 return true;
1025 }
1026 return false;
1027 }
1028
1029 1030 1031 1032 1033 1034
1035
1036 function VariationsSelector() {
1037 return $this->customise(array(
1038 'Variations' => $this->VariationsParamValues(),
1039 ))->renderWith('VariationsSelector');
1040 }
1041
1042
1043 function VariationsParamValues() {
1044 return self::combine_variations($this->AllVariations());
1045 }
1046
1047
1048
1049 static function combine_variations($variations) {
1050 $rs = array();
1051 foreach($variations as $variation) {
1052 foreach($variation->ParamValues() as $paramValue) {
1053 if (!isset($rs[$paramValue->ProductParamID])) {
1054 $rs[$paramValue->ProductParamID] = new DataObjectSet();
1055 }
1056 $t = $rs[$paramValue->ProductParamID];
1057 if (!$t->find('Value', $paramValue->Value)) {
1058 $t->push($paramValue);
1059 }
1060 $t->sort('Value');
1061 }
1062 }
1063 $list = new DataObjectSet();
1064 foreach($rs as $paramID=>$paramValues) {
1065 if ($paramValues && $paramValues->Count()) {
1066 $list->push(new ArrayData(array(
1067 'Param' => $paramValues->First()->ProductParam(),
1068 'Values' => $paramValues
1069 )));
1070 }
1071 }
1072 return $list;
1073 }
1074
1075 function VariationOrderButtonLink($ID='') {
1076 return $this->Link("variation_order_button/{$ID}");
1077 }
1078
1079 function PurchaseStatus() {
1080 if (Catalog::$use_variations && $this->Variations()) {
1081 return 'HasVariations';
1082 }
1083 if (!$this->Available && !$this->AllowPurchase) {
1084 return 'NoOrder';
1085 }
1086 if (!$this->Available && $this->AllowPurchase) {
1087 return 'PreOrder';
1088 }
1089 if ($this->Available && !$this->AllowPurchase) {
1090 return 'NoOrder';
1091 }
1092 if ($this->Available && $this->AllowPurchase) {
1093 return 'InStock';
1094 }
1095 return 'NoOrder';
1096 }
1097
1098 function PurchaseStatusTitle() {
1099 return _t('Product.PurchaseStatus_' . $this->PurchaseStatus());
1100 }
1101
1102 function canOrder() {
1103 if (in_array($this->PurchaseStatus(), array('PreOrder', 'InStock'))) {
1104 return true;
1105 }
1106 return false;
1107 }
1108 }
1109
1110 class Product_Controller extends Page_Controller {
1111 private $siblings = null;
1112 private $next = null;
1113 private $prev = null;
1114
1115 1116 1117 1118 1119
1120 function Siblings() {
1121 if (!isset($this->siblings)) {
1122 $catalog = new Catalog_Controller($this->Parent());
1123 $catalog->init();
1124 $this->siblings = $this->Parent()->filteredProducts($catalog->CurrentSort, $catalog->FilterData);
1125 }
1126 return $this->siblings;
1127 }
1128
1129 1130 1131 1132 1133
1134 function NextProduct() {
1135 if (!isset($this->next)) {
1136 $list = $this->Siblings()->toArray();
1137 while ($row = array_shift($list)) {
1138 if ($row->ID == $this->dataRecord->ID) break;
1139 }
1140 $this->next = array_shift($list);
1141 }
1142 return $this->next;
1143 }
1144
1145 1146 1147 1148 1149
1150 function PrevProduct() {
1151 if (!isset($this->prev)) {
1152 $this->prev = false;
1153 $list = $this->Siblings()->toArray();
1154 $prev = array_shift($list);
1155 while ($row = array_shift($list)) {
1156 if ($row->ID == $this->dataRecord->ID) {
1157 $this->prev = $prev;
1158 break;
1159 }
1160 $prev = $row;
1161 }
1162 }
1163 return $this->prev;
1164 }
1165
1166 1167 1168 1169 1170
1171 function product_variations() {
1172 if ($this->Variations()->exists()) {
1173 $variations = $this->Variations()->data();
1174 return json_encode(array('Variations' => $variations));
1175 }
1176 }
1177
1178 function variation_order_button($request) {
1179 if ($variation = DataObject::get_by_id('ProductVariation', (int)$request->param('ID'))) {
1180 return $this->customise(array(
1181 'Variation' => $variation
1182 ));
1183 }
1184 return $this->httpError(404);
1185 }
1186 }
1187
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-